BitaKit
How-to guides

Interceptors

Observe an Action, add a gate, or trace a service call without changing its outcome.

Interceptors add behavior around existing operations. Start with one observation; keep the operation itself in its Action or service.

Action interceptors

1. Choose what to log

This independent example assumes a Todos plugin with todos.add, todos.clear and todos.remove. Select one Action, all Todos Actions, or all Actions in the application. The highlighted operations are observed; the code below is the whole file to create.

  • todos.addObserved
  • todos.clearOutside this target
  • todos.removeOutside this target
  • counter.incrementOutside this target

One Action

Log completion of todos.add. Other Actions keep running without this observer.

src/plugins/todos/interceptors/log.ts
import { defineInterceptor } from '@bitakit/core';export default defineInterceptor({  action: 'todos.add',  after: ({ id }) => console.debug('Completed:', id),});
Select a target to see which example operations it observes and the corresponding logging definition. This illustrates selection; it does not execute operations.

after logs completed runs. The interceptor does not replace the operation or its result. The wildcard matches Action IDs, not groups membership.

2. Add a gate only when needed

For the Counter tutorial, target action: 'counter.increment' and add this property to skip an increment when the count is already 10 or higher:

before: ({ services }) => services.counter.value.state.get().count < 10,

false skips execution with reason intercepted. A promise from before is awaited. This is a client execution rule; server authorization still belongs on the server.

Gate or observe an Action

Gate or observe an Action. For an otherwise runnable Action, before hooks run in order and may be awaited. False skips. after and onError observe without replacing the outcome. Other execution guards may also skip an Action.
For an otherwise runnable Action, before hooks run in order and may be awaited. False skips. after and onError observe without replacing the outcome. Other execution guards may also skip an Action.

3. Observe failure

In an Action interceptor, add this property when the feature needs error reporting:

onError: ({ id, error }) => console.error('Failed:', id, error),

A skipped run is not a failed run. after observes completed runs; onError observes failures, including a rejected before hook. Their return values do not replace the result, their promises are not awaited by execution, and their own failures are reported separately.

Service interceptors

Select one method, every method on a service, or all services. Here todos.list has add and clear methods. The observer logs the service and method names without modifying arguments or results.

  • todos.list.add()Observed
  • todos.list.clear()Outside this target
  • counter.value.increment()Outside this target

One method

Observe calls to add through the todos.list service. Other methods are unaffected by this observer.

src/plugins/todos/interceptors/log-method.ts
import { defineInterceptor } from '@bitakit/core';export default defineInterceptor({  service: 'todos.list',  method: 'add',  before: ({ service, method }) => console.debug('Called:', service, method),});
Select a target to see which example operations it observes and the corresponding logging definition. This illustrates selection; it does not execute operations.

This observes calls through the service container, including calls outside an Action. Known service names infer method arguments and awaited result types. service: 'todos.*' also selects a service-name prefix; omit method to include every public method.

Observe a service call

Observe a service call. Service hooks cannot gate or replace execution. Their return values are ignored and async observers are not awaited. The caller receives the original value, promise or error; internal self-calls bypass the wrapper.
Service hooks cannot gate or replace execution. Their return values are ignored and async observers are not awaited. The caller receives the original value, promise or error; internal self-calls bypass the wrapper.
CapabilityAction interceptorService interceptor
Inspect inputinputargs
Prevent executionbefore returns falseNo
Await beforeYesNo
Observe completion/failureafter / onErrorafter / onError
Replace result or wrap retriesNoNo
Internal service self-callOnly the enclosing Action is observedBypasses service hooks

Do not mutate arguments or results inside observers. Service callback errors are isolated; async callback results are ignored. Disposal disables observation and cleans up the owned instance.

Matching, order and context

SettingBehavior
action: 'counter.increment'One Action
action: 'counter.*'Matching Actions in that namespace
action: ['counter.increment', 'counter.reset']Several targets
No action or serviceAll Actions of the interceptor's plugin
Plugin interceptorPlugin scope by default; scope: 'global' opts in to other plugins
App interceptor under src/interceptors/Global by default; scope: 'plugin' restricts it
orderLower values first; registration order breaks ties
serviceExact name, list, prefix wildcard or all services; aliases match too
No methodEvery public method of the matching service

There is no separate “plugin hooks, then global hooks” stage. Matching Action definitions share the ordered sequence. action and service are mutually exclusive; default-export an array when one file contains multiple definitions.

Action hooks receive the execution context (id, raw input, signal, opaque environment, get, update) plus services, config, app and translate. This is not the Action handler's full context: do not assume settings or events are directly available on an interceptor. Service hooks receive service, method, args and, where applicable, result or error.

Without interceptors.enabled in bitakit.config.ts, all discovered app interceptors are active; a configured list selects them. See Patterns at a glance to choose between an interceptor, an event, a store and Action options.

On this page