BitaKit
Concepts

Type safety

Carry contracts from definitions to consumers.

Owner: @bitakit/core types and discovery; React bindings consume the same contracts through @bitakit/ui.

BitaKit is designed around type-safe feature composition. Define the contract at its source, then let service access, action execution, events and bindings carry the types to callers.

BoundaryWhat TypeScript knows
defineContract('auth').of<T>()The shared provider and consumer contract
Discovered service factoryMethod arguments and return types of services.<plugin>.<file>
Discovered action definitionAction ID, input and completed result
events.tsEvent names, payloads and which events are cancelable
configs/The resolved config tree of each plugin through ConfigMap
Declared slotsSlot names through SlotMap

Typed action inputs

import { defineAction } from '@bitakit/core';
import { z } from 'zod';

export default defineAction({
  title: 'Create todo',
  input: z.object({ title: z.string().min(1) }),
  execute: ({ services, input }) => services.todos.list.add(input.title), // input is { title: string }
});

With an input schema, the input and the result are inferred and the schema validates at runtime. Without one, name the input explicitly: defineAction<string>({ ... }); the result is then unknown, and defineAction<string, number> names both.

In a discovered contribution, the effective ID maps to that input and result in the generated ActionMap, so useAction('todos.create'), <Action id="todos.create" input={...}> and app.actions.todos.create.execute(...) autocomplete the ID and check the input. Keep the generated src/bitakit.gen.tsx in the host TypeScript project.

Strict names

Generated apps reject unknown names at compile time: discovery augments ActionMapOptions with { strict: true }, and that one switch covers every generated name.

NameChecked where
Action IDs and inputs<Action id>, ActionButton, ActionLink, useAction, Action slots
Event namesevents.emit, events.on, useEvent; on also accepts <plugin>.* patterns of declared events and *
Slot namesPluginSlot, useSlot
Setting keysuseSetting; settings.<plugin>.<name> in services and Actions
Service namesuseService, useServiceState; services.<plugin>.<name> in services and Actions

Installed package plugins add their record-form Actions and their events to the same maps. Set strict: false in bitakit.config.ts to allow dynamic names, or augment the option yourself in a manual composition:

// src/bitakit-types.d.ts
import '@bitakit/core';

declare module '@bitakit/core' {
  interface ActionMapOptions {
    strict: true;
  }
}

Typed services, events and config

Discovery derives ServiceMap from each service factory's return type, EventMap and CancelableEventMap from defineEvents in events.ts, and ConfigMap from defineConfig schemas or defaults. Manual compositions augment the same interfaces:

import '@bitakit/core';

declare module '@bitakit/core' {
  interface ServiceMap {
    'todos.list': { add(title: string): void; remove(id: number): void };
  }
  interface EventMap {
    'todos.changed': { kind: 'added' | 'removed'; id: number };
  }
}

Known names require their declared payload; unknown names remain allowed with unknown data. These augmentations provide types, not runtime registration. requires separately lists services from other plugins, which BitaKit checks at startup.

Runtime boundaries

TypeScript does not validate network responses, stored JSON or user input. Parse untrusted values with the application's schema library — an action's input schema does this for action inputs — and keep authorization on the server. A config fallback is not a schema. Typed service access does not guarantee an optional provider is mounted; check app.has('auth') or declare the dependency.

This is why these guides identify the typed boundaries precisely rather than claiming every dynamic string or external response is automatically safe.

On this page