@supernovae-st/nika lets a TypeScript application run .nika workflows and
prove what they did. It is a thin, typed client: the Nika engine parses,
checks, runs and records every workflow, and the SDK carries the engine’s
answers to your code. It never parses a workflow, re-implements a check or
computes a proof itself.
Two doors, one client
One class,Nika, reaches the engine through one of two transports. The
constructor chooses; every method keeps its name and meaning on both.
- Local engine (
new Nika()): the SDK starts the engine that npm installed with the package (nika run <file> --json, argv only, never through a shell), in your working directory. Nothing else runs on your machine. - Your server (
new Nika({ url, token })): the SDK talks to anika serve --bindyou operate. The server holds durable jobs and schedules, so a run survives your process restarting.
The run lifecycle
run() returns only after the engine has admitted the workflow. Everything
after that is observation of one admitted run.
- Admission. The engine checks the file (the same audit
nika checkprints), the inputs and the spend cap. A refusal makesrun()throw aNikaOperationErrorcarrying the engine’s code; no run, event, trace or receipt exists. - Observation.
run.events()streams lifecycle events in one vocabulary on both doors. Events are presentation: optional to render, never the source of truth. - Settlement.
run.result()resolves once with the engine’s result:succeeded,failed,paused,cancelledorinterrupted. An admitted failure resolves as data; it does not throw. - Proof. The result carries an engine-issued
receipt.
traceVerify(receipt)asks the engine to check the run’s hash-chained record against it.
The lifecycle on both doors, as an illustration with captured content. new Nika() and new Nika({ url, token }) take the same methods; two lines of demo.mts, the quick start in the package README, change between them: line 3, the constructor, takes url, token and allowInsecureHttp: true, which a plain http:// URL to a loopback host needs, and line 6, the run options, takes an idempotencyKey in place of maxCostUsd, which a server run refuses. Admission: the engine refuses list-files.nika with NIKA-SEC-004, run() throws NikaOperationError and no run exists, while hello.nika is admitted. Observation: six local events, or two from nika serve. Settlement: run.result() reads succeeded. Proof: six hash-chained journal lines end in run_sealed, traceVerify(receipt) answers verified locally and sealed from the server, and one changed byte makes the verifier report BROKEN at line 5. An illustration: every code line, event, verdict, hash and version on screen was captured from @supernovae-st/nika 0.123.0, its bundled engine nika 0.123.0 (0bfde3765) and nika serve 0.123.0 in a mock/echo rehearsal; the cards, lanes, packets and pacing are drawn, and a real run takes milliseconds.
Who owns what
The words the pages use
What differs between the doors
Measured with@supernovae-st/nika@0.123.0 and its bundled engine, and
nika serve 0.123.0:
Your first run, either door
The quickstart runs one workflow on the local engine in five minutes; the server quickstart startsnika serve and runs the same workflow over HTTP. The methods are the same,
and two lines change over HTTP: the constructor takes url and token
(with allowInsecureHttp: true for a plain http:// URL to a loopback
host), and run() takes an idempotencyKey in place of maxCostUsd, which
a server run refuses. On the local engine:
Which version is this?
Which version is this?