@s-age/kernelee
v0.4.0
Published
TypeScript port of swift-kernelee — core dispatch (KernelSymbol / Verb / KernelBuilder / Kernel / CommandBus) + pipes (Pipe / PipeBuilder / compose / run) + fork (parallel fan-out) + Buffer (observable state) + defineCallable (typed port factory, the @cal
Downloads
395
Maintainers
Readme
kernelee
A UNIX-pipe-like, message-driven, forward-only control framework for TypeScript. Control is data (messages), not a call hierarchy. A port of swift-kernelee — the Swift implementation's semantics are the source of truth, translated into TS idioms. Zero runtime dependencies, ESM, strict.
See it in action:
kernelee-lifegame — Conway's
Game of Life driving the generation loop with divert and parallel row
chunks with fork.

Every flow above is a Pipe value — the
devtools panel renders
it from Pipe.descriptors without executing anything, alongside live
traces from the same connection.
The model
Three ideas, borrowed from the shell:
Everything is a message. A command is a typed symbol + payload (
kernel.call(increment, 41)), not a method on an object graph. One kernel is the single chokepoint every message passes through — which is what makes tracing and a static wiring graph possible.Flows are pipes. Multi-step control chains left to right like a UNIX pipe (
fetchNote | map | tap | render) and seals into a first-classPipe<I, O>value whose topology is readable without running it.Handlers steer with a
Verb— and only forward. Instead of return-value-or-throw, a handler answers with control as data:next(value) // keep flowing to the next stage abort(value) // stop here — the value is the result divert(diversion(otherPipe, p)) // discard remaining stages, jump into another pipe fail(error) // reject the whole flowNothing flows backwards:
dispatchis fire-and-forget with no return path, anddivertswaps the remaining stages rather than calling and returning — a self-diverting loop (an agent loop) runs O(1) stack across any number of hops.
Why not Redux / XState / effect-ts?
They answer different questions. Redux answers "how does state change"
(actions → reducer) and leaves multi-step control flow to add-ons (thunks,
sagas). XState answers "which states are legal" (statecharts). effect-ts
answers "how do I make effects typed and composable" with its own runtime
(fibers). kernelee answers "what is the flow, as a value" — and keeps
observable state (Buffer) deliberately thin, with transition logic in
plain functions. If you want reducer-centric state management and its
middleware ecosystem, use Redux; if you want to model legal state machines,
use XState. kernelee is for when the flow itself — dispatch, fan-out, divert
loops — should be an inspectable piece of data, on plain async/await.
Install
npm install @s-age/kerneleeQuick start
import { symbol, next, fail, pipeline, KernelBuilder, type Kernel } from '@s-age/kernelee';
const increment = symbol<number, number>('math.increment');
const guarded = symbol<number, number>('math.guarded');
const reload = symbol<void, void>('notes.reload');
const builder = new KernelBuilder();
builder.register(increment, (n) => n + 1); // leaf: value-returning
builder.registerVerb(guarded, (n) => // leaf: verb-returning
n < 0 ? fail(new Error('negative')) : next(n * 2));
builder.register(reload, async (kernel: Kernel, _: void) => { // composing: kernel-first
await kernel.call(increment, 1);
});
const kernel = builder.build({ onError: (symbolId, error) => { /* sink */ } });
await kernel.call(increment, 41); // => 42 (typed)
kernel.dispatch(reload, undefined); // fire-and-forget, serialized in submission order, failures go to onErrorMulti-step flows chain like a UNIX pipe and freeze into a typed value:
const toDto = pipeline(fetchNote) // KernelSymbol<NoteId, Note>
.map((note) => ({ note, seenAt: Date.now() }))
.tap(saveAudit) // side effect, forwards the original value
.map((c) => c.note).pipe(renderDto) // payload assembly is a visible map node
.seal(); // Pipe<NoteId, NoteDto>
const dto = await kernel.compose(toDto, id); // typed final value
toDto.descriptors; // static shape, readable without runningGotchas:
- Leaf vs composing is discriminated by declared parameter count
(
fn.length >= 2means kernel-first). Default and rest parameters break the discrimination. - A composing handler's lambda needs parameter type annotations
(
(kernel: Kernel, n: number) => …) — TS settles the overload before contextually typing the lambda. A wrong annotation is a compile error.
Documentation
Design notes and full API semantics live in docs/:
- Pipes & fork —
pipeline/divert(O(1) loops) /tap/map/effect, and parallel fan-out with fork (including how cancellation differs from Swift). - Buffer — observable state cells (
defineState/mutate/subscribe),useSyncExternalStorefit,KernelErrorState. - defineCallable & actionsOf — the typed port
factory (the
@callablemacro's TS counterpart) and redux-styledispatch(action). - Tracing — span propagation through the
Kernel.invokechokepoint,onTrace, theTraceStatebuffer cell. - Wiring graph —
describePipe/projectWiringGraph/validateWiringGraph: a static JSON snapshot of the app's wiring. - Transport adapters — why the core ships no delivery layer, and the two APIs a bridge package builds on.
- Swift ↔ TS correspondence — the full port table, including every deliberate non-correspondence.
Not ported yet: time-travel (trace forest reconstruction,
Buffer.capture / restore) — swift-kernelee ships it, and the TS port
plans to build it on TraceState. Today,
kernelee-devtools-bridge
already rides a Buffer snapshot on every traced message, so the state at
any past point is inspectable; what's missing is restoring the live app to
it. Delivery/UI layers are out of scope by design — the core stays
zero-dependency and exposes the seams instead.
Ecosystem
The delivery and tooling layers the core deliberately excludes live in sibling packages:
- react-kernelee — React bindings
for the
Buffer(useBuffer/useDispatch/useKernelError). - kernelee-devtools-bridge — dev-only WS bridge + browser panel for the wiring graph and live traces.
- kernelee-mcp-tools — MCP server exposing a static scan of a kernelee app's wiring to coding agents.
- kernelee-lifegame — the showcase app (Conway's Game of Life).
Development
npm test # vitest run
npm run typecheck # tsc --noEmit
npm run build # tsc → dist/ (with declarations)License
MIT © s-age
