BitaKit
Concepts

Patterns at a glance

Choose an Action, interceptor, event or store, with a visual flow and a tiny example.

Start with the job you need. Each diagram explains one mechanism; follow the link for its options.

You want toUseInstead of building
Request an operationActiononClick handlers with hand-kept disabled/busy state
Observe or gate an ActionAction interceptorWrappers around every call site
Trace a service methodService interceptorLogging added inside each method
Announce a fact to other pluginsEventA shared event bus or direct calls between features
Display changing stateService storeA second state store mirrored by effects
Save a user preferenceSettingsYour own storage, form and Settings route
Place a widget or navigation itemSlotsA registry or a menu list that every feature edits

For package responsibilities, see the architecture map.

One Action, several controls

// counter/actions/increment.ts
export default defineAction({
  title: 'Increment',
  execute: ({ services }) => services.counter.value.increment(),
});

Import defineAction from @bitakit/core. Existing bindings reference counter.increment; each call carries its own input. A slot chooses where a control appears.

One Action, every control

One Action, every control. The numbered path is one execution; the accent path is the state update that reaches every bound control. Guards and interceptors still apply.
The numbered path is one execution; the accent path is the state update that reaches every bound control. Guards and interceptors still apply.

Use Action options for confirmation, concurrency and feedback. Native navigation stays a real link; do not also invoke a handler when following it. See the Action loop diagram for the complete UI → Action → Service → Store → UI cycle.

Gate or observe an Action

// counter/interceptors/audit.ts
import { defineInterceptor } from '@bitakit/core';
export default defineInterceptor({
  action: 'counter.increment',
  after: ({ id }) => console.debug('Completed:', id),
});

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.

before may be async; false skips. after and onError observe, without replacing results. See Action interceptors.

Observe a service call

// counter/interceptors/calls.ts
import { defineInterceptor } from '@bitakit/core';
export default defineInterceptor({
  service: 'counter.value',
  method: 'increment',
  before: ({ args }) => console.debug('Called with:', args),
});

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.

Internal self-calls bypass the wrapper. See service interceptors.

Notify other plugins

// counter/events.ts
import { defineEvents, event } from '@bitakit/core';
export default defineEvents({ incremented: event<number>() });
// Emit in a Counter Action or service; listen in another service factory.
events.emit('counter.incremented', count);
events.on('counter.incremented', ({ data }) => console.log(data));

Notify other plugins

Notify other plugins. Exact-name listeners run first, then matching wildcard groups. Normal emit does not await async handlers or replay earlier events. Listener failures do not stop the other listeners. Subscriptions end with their owner.
Exact-name listeners run first, then matching wildcard groups. Normal emit does not await async handlers or replay earlier events. Listener failures do not stop the other listeners. Subscriptions end with their owner.

Subscriptions are owned and released. Ordinary emit does not await async handlers; cancelable events await handlers and report whether a listener cancelled. Cancellation is coordination, not server authorization.

Follow current state

// In a service factory, state is the single owner of the value.
const state = store({ count: 0 });
const increment = () => state.set((s) => ({ count: s.count + 1 }));
// In a component, read its service; no copied count state or bootstrap effect.
const count = useServiceState('counter.value', (state) => state.count, 0);

One store, several readers

One store, several readers. A service owns the current value. Both the page and Action presentation react to its store. Use an event for a notification, not as a replacement for reading current state.
A service owns the current value. Both the page and Action presentation react to its store. Use an event for a notification, not as a replacement for reading current state.

See the service lesson for complete files. For persisted preferences, use defineSettings and useSetting; the runtime settings store remains the value owner.

On this page