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.addObservedtodos.clearOutside this targettodos.removeOutside this targetcounter.incrementOutside this target
One Action
Log completion of todos.add. Other Actions keep running without this observer.
import { defineInterceptor } from '@bitakit/core';export default defineInterceptor({ action: 'todos.add', after: ({ id }) => console.debug('Completed:', id),});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
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()Observedtodos.list.clear()Outside this targetcounter.value.increment()Outside this target
One method
Observe calls to add through the todos.list service. Other methods are unaffected by this observer.
import { defineInterceptor } from '@bitakit/core';export default defineInterceptor({ service: 'todos.list', method: 'add', before: ({ service, method }) => console.debug('Called:', service, method),});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
| Capability | Action interceptor | Service interceptor |
|---|---|---|
| Inspect input | input | args |
| Prevent execution | before returns false | No |
Await before | Yes | No |
| Observe completion/failure | after / onError | after / onError |
| Replace result or wrap retries | No | No |
| Internal service self-call | Only the enclosing Action is observed | Bypasses 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
| Setting | Behavior |
|---|---|
action: 'counter.increment' | One Action |
action: 'counter.*' | Matching Actions in that namespace |
action: ['counter.increment', 'counter.reset'] | Several targets |
No action or service | All Actions of the interceptor's plugin |
| Plugin interceptor | Plugin scope by default; scope: 'global' opts in to other plugins |
App interceptor under src/interceptors/ | Global by default; scope: 'plugin' restricts it |
order | Lower values first; registration order breaks ties |
service | Exact name, list, prefix wildcard or all services; aliases match too |
No method | Every 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.