API reference
- @nonchalant/core
- @nonchalant/dom
- @nonchalant/wire
- @nonchalant/durable
- @nonchalant/host
- @nonchalant/inspect
Every public export of every package, with its signature and a line on what it does. The concepts explain the model behind each one; the links in the right-hand column go there. Error handling covers what each piece does when something fails.
Generic parameters follow one convention throughout: T is the state a
process yields, In its message union, Args/A its spawn arguments, and
S a registry schema ({ [name]: Definition }).
@nonchalant/core
The runtime. No dependencies, no DOM.
Processes
| export | signature | what it does | concept |
|---|---|---|---|
spawn |
spawn(proc, args, opts?) → Process<T | undefined, In>; with opts.initial, Process<T, In> |
Runs an async generator as a supervised process. A bare spawn from inside a process body attaches to that process only during the synchronous part of a step; use self.spawn there, which is owned across await. |
spawn |
derive |
derive<T>(fn: () => T) → Process<T> |
A memoised computation over other processes. Recomputes when what it read changes; notifies only when its result changes. No mailbox. | derive |
cell |
cell<T>(initial: T) → Process<T, T> |
Sugar for widget state: a process whose messages are its next values. | Layers |
reducer |
reducer<T, In, A = void>(init: (args: A) => T, reduce: Reducer<T, In>) → Proc<T, In, A> |
Sugar for the loop-switch-yield process: yields init(args), then reduce(state, msg) for each message whose result is a different state (!==). init reruns on an on-crash restart; under durable, the restored snapshot replaces it. Named after reduce. |
Layers |
channel |
channel<In>(signal?: AbortSignal) → Self<In> & Disposable |
A standalone mailbox implementing Self, for middleware and for driving a generator in tests. Iteration ends when signal aborts or the channel is disposed, and children spawned through its spawn are disposed then. |
Self |
mount |
mount<Out>(sink: Sink<Out>, view: ProcessBase<Out | undefined> | Out) → Disposable |
Attaches a view to any sink. @nonchalant/dom exports a DOM-specific mount that most code uses instead. |
Views and sinks |
onProcessError |
onProcessError(handler: (error: unknown, name: string) => void) → () => void |
Observes every process crash, including ones a restart recovers from: the thrown value and the generator function's name, a microtask after the crash. One handler at a time; returns its remover. With none installed, a crash shows only on the handle and in rejected calls. | Error handling |
SpawnOpts<T>:
| option | type | default | meaning |
|---|---|---|---|
initial |
T |
none | First readable value; decides Process<T> vs Process<T | undefined>. |
restart |
'never' | 'on-crash' |
'never' |
'on-crash' re-runs the generator from its args after a throw; queued casts replay, pending and queued calls reject. |
maxRestarts |
number (non-negative integer or Infinity) |
3 |
Restart budget; past it the crash is terminal. |
mailbox |
number (non-negative integer) |
unbounded | Queue bound; overflow drops the oldest message (a dropped call rejects) with a one-time warning. |
quiet |
boolean |
false |
The process's crashes are expected and surfaced elsewhere (as stale and rejected calls), so onProcessError skips them. The wire client's remote refs use it: a disconnect is not a bug. |
The Process face
| member | type | what it does |
|---|---|---|
p() |
T |
The latest yield. Subscribes by path inside a tracked context; a plain snapshot elsewhere. |
p.cast(msg) |
(msg: Casts<In>) => void |
Fire-and-forget. Present only when In has Cast members. |
p.call(msg) |
(msg: Req) => Promise<Res> |
Request/response. Present only when In has Call members. Rejects on crash, finish, or dispose; a call rejected by a crash never runs in a restarted instance. |
p.pending |
boolean |
Working toward its next yield. |
p.stale |
boolean |
The value survived a crash, a disposal, or a lost connection. |
p.error |
unknown |
The last failure, if any. |
p[Symbol.asyncIterator]() |
AsyncIterator<T> |
A lossy latest-value stream; each iterator is its own subscription. It ends when the process returns, crashes terminally, or is disposed. |
p[Symbol.dispose]() |
void |
Starts teardown; does not wait for async finally work. |
p[Symbol.asyncDispose]() |
Promise<void> |
Starts teardown and resolves when this process and its owned children have settled. |
Reactive graph
| export | signature | what it does | concept |
|---|---|---|---|
effect |
effect(fn: () => void | (() => void)) → () => void |
Runs fn now and whenever what it read changes. A returned function is its cleanup, run before each re-run and on stop. Returns the stop function. |
effect |
untracked |
untracked<T>(fn: () => T) → T |
Runs fn without recording reads. |
effect |
flush |
flush() → void |
Runs queued effects now instead of on the next microtask. Rethrows the first effect error after running the rest. | The graph |
binding, rebind, unbind |
binding(fn) → Binding; rebind(b, fn); unbind(b) |
For sinks: an effect whose body rebind swaps and re-runs once on the same node, keeping the subscriptions both bodies share. The DOM sink rebinds a re-rendered row's fresh closures this way. |
graph internals |
Registry
| export | signature | what it does | concept |
|---|---|---|---|
define |
define(proc, opts?: DefineOpts<T>) → Definition<T, In, A>; with opts.initial, Definition<T, In, A, never> |
A schema entry: the generator a name resolves to, plus spawn options and evict. |
Registry |
registry |
registry<S>(defs: S, opts?: RegistryOpts) → RegistryHandle<S> |
A local registry over a typed schema. | Registry |
RegistryHandle.lookup |
lookup(name, args?) → ProcessOf<S[name]> |
Get-or-spawn by name plus arguments (argument key order ignored). Reads are T | undefined unless the definition has initial. Throws for a name not in the schema. |
Registry |
RegistryHandle.evict |
evict(name, args?) → void |
Disposes and forgets one entry, or every entry under the name, busy or not. | Registry |
DefineOpts<T> is SpawnOpts<T> plus evict?: number: milliseconds to keep an
unwatched process alive (finite, non-negative; omit to never auto-evict). A
process that is busy when its idle window ends gets another window.
RegistryOpts is { maxEntries?: number } (positive; omit for no cap). Past
the cap, the least recently looked-up unwatched entries are disposed.
Watched entries, and busy ones (holding unanswered calls, or a self.busy() hold), are never evicted, so a registry whose every entry is watched
can sit above the cap until watchers leave. A watcher is an effect, or a
derive or iterator an effect reads through; a derive read only as a snapshot
does not keep an entry alive.
Diff and patch
| export | signature | what it does | concept |
|---|---|---|---|
reconcile |
reconcile(prev: Json, next: Json) → Patch |
The structural diff every yield goes through: ops on RFC 6901 paths. A record key whose value is undefined counts as absent. |
The graph |
applyPatch |
applyPatch(doc: Json, patch: Patch) → Json |
Applies a patch without mutating doc. Throws on a malformed path, a non-canonical array index (RFC 6901: digits, no leading zero), or an out-of-range splice. |
Wire |
Types
| type | what it is |
|---|---|
Cast<Msg> |
A one-way message: Msg & { reply?: never }. |
Call<Req, Res> |
A message that expects an answer: Req & { reply: (res: Res) => void }. |
Casts<In> / Calls<In> |
The cast / call members of a message union. |
Res<M> |
The reply type of a call member. |
Process<T, In> |
The outside face: ProcessBase<T> plus cast and call where In allows them. |
ProcessBase<T> |
Read, pending, stale, error, iteration, and disposal. |
Self<In> |
The inside face: an AsyncIterable<In> mailbox, signal, latest(), cast, spawn (same signature as spawn; the child is owned by this process from anywhere in the body, including after an await), and busy() (a Disposable hold: a registry does not evict the process while one is open). |
Reducer<T, In> |
(state: T, msg: In) => T: what reducer folds with. Returns the next state, or the same state for no change; a Call member answers through msg.reply before returning. |
Proc<T, In, Args> |
(self: Self<In>, args: Args) => AsyncGenerator<T, unknown, undefined>: what spawn runs. A return value is not a T, so driving one by hand needs a done check before reading next().value. |
Definition<T, In, Args, Before = undefined> |
A phantom-typed schema entry. Before is what a read returns ahead of the first yield: undefined, or never when define got initial. |
Schema |
{ [name: string]: Definition<unknown, unknown, unknown> }. |
Registry<S> |
Anything with a typed lookup: a local registry or a connect() connection. |
ProcessOf<D> / ArgsOf<D> |
The process and argument types a definition resolves to. |
RegistryHandle<S>, RegistryOpts, DefineOpts<T>, SpawnOpts<T> |
See above. |
VNode |
{ tag, ns?, attrs, children }: a view node as plain data. |
Slot |
What a child position accepts: primitives, VNode, thunks, processes, promises, async iterables, arrays of slots. |
Sink<Out> |
A render target: { mount(view): Disposable }. |
Json, Op, Patch |
JSON values, and the set / del / splice patch ops. |
@nonchalant/dom
Views as plain data, and the DOM sink.
| export | signature | what it does | concept |
|---|---|---|---|
mount |
mount(container: Element, view: View) → Disposable |
Renders a VNode, a thunk, or a view process into container. Disposal unbinds every binding beneath. |
Views and sinks |
domSink |
domSink(container: Element) → Sink<VNode> |
The DOM sink for core's generic mount(sink, view). |
Views and sinks |
h |
h<K extends string>(tag: K, attrs?: AttrsFor<K>, ...children: Slot[]) → VNode |
The generic constructor, for SVG, MathML, custom elements, or any tag without a named export. | Views and sinks |
tagFn |
tagFn<K extends string>(tag: K) → TagFn<K> |
Makes a named constructor like the ones in /tags. |
|
onRenderError |
onRenderError(handler: RenderErrorHandler) → () => void |
Routes render failure reports (a throwing binding, a rejected slot promise) away from console.error. Returns a restore function. |
Error handling |
Attrs |
type | Untyped attributes: key, exit, ns, and any other name as a static value, an on* listener function, or a binding. What the sink reads, and the escape hatch. |
|
AttrsFor<K> |
type | The attributes for tag name K: HtmlAttrs for an HTML tag, SvgAttrs for an SVG tag, Attrs for anything else or for K = string. |
|
HtmlAttrs<E, M> |
type | An HTML element's writable attribute-backed properties (under their attribute names: for, tabindex, colspan), data-*, aria-*, and lowercase on* listeners typed from its event map. |
|
SvgAttrs<E> |
type | Typed lowercase on* listeners; every other name is unchecked Attrs. |
|
TagFn<K> |
type | (attrs?: AttrsFor<K>, ...children: Slot[]) => VNode. |
|
View |
type | VNode | ProcessBase<VNode | undefined> | (() => VNode | null | undefined). |
|
RenderErrorHandler |
type | (what: string, error: unknown) => void. |
Attributes. false and null remove an attribute, true sets it empty,
anything else is stringified; aria-* renders booleans as "true"/"false".
value, checked, and selected are set as properties, after the element's
children exist, so a <select value> can match an option declared inside it.
Text and attribute values are never parsed as HTML.
URL and handler policy. Two attribute-level injection routes are closed in the sink:
- A
javascript:URL inhref,src,action,formaction, orxlink:hrefis removed instead of set. The scheme is tested after stripping U+0000–U+0020 (the characters browsers ignore there), so' java script:'is caught too. Other schemes,data:included, pass: treat user-supplied URLs as untrusted and allow-list them yourself. - An
on*attribute takes only a function. Any other value (a string of script, say) sets no attribute and no listener, and warns once in the console.
Both are enforced by the "attribute-level injection" tests in
packages/dom/test/render.test.ts. Console warnings, once per distinct
message, also flag camelCase listeners (onClick listens for an event named
Click, which never fires) and object attribute values.
Typed attributes. Named tags and h('tag', …) check attributes per
element: input({ value }) accepts a string, number, or a binding to one;
div({ onlick }) and div({ onClick }) are type errors; a listener's
currentTarget is the element's own type. SVG listeners are typed, but SVG
attributes (d, fill, viewBox) are not in the DOM lib, so they are
unchecked. For a name the DOM lib does not know, widen the tag:
h(tag as string, attrs) takes plain Attrs. The negative cases live in
packages/dom/test/attrs.check.ts.
@nonchalant/dom/tags exports one TagFn per HTML element: html head body title header footer main nav section article aside h1–h6 div p ul ol li dl dt dd pre blockquote figure figcaption hr span a em strong small code kbd samp sub sup i b u mark time br wbr var_ img picture video audio source track canvas iframe embed object table caption colgroup col thead tbody tfoot tr td th form fieldset legend label input button select optgroup option textarea output progress meter datalist details summary dialog menu template slot noscript.
var_ is <var> (a reserved word); anything else goes through h.
@nonchalant/wire
The protocol, its codec, transports, and both ends of a connection. Isomorphic.
| export | signature | what it does | concept |
|---|---|---|---|
connect |
connect<S>(transport: Transport, opts?: ConnectOpts) → Connection<S> |
A registry whose lookups are served by the other end. Remote processes have the full Process face; close() tears it down. Casts made while disconnected queue per ref (the newest castQueue, default 64) and are sent after the re-lookup; calls made while disconnected reject at once. A call's reply is not ordered against the process's yields. |
Wire |
expose |
expose(reg: Exposable, transport: Transport, opts?: ExposeOpts) → () => void |
Serves a registry, or any Exposable gateway, over a transport. Returns a disposer that releases every watch. |
Wire |
WireError |
class WireError extends Error { detail: Json } |
What remote failures reject and throw with. detail is the JSON the host sent. |
Error handling |
webSocketTransport |
webSocketTransport(url, opts?: { retryDelay? }) → Transport & { close() } |
A reconnecting WebSocket client. Redials with exponential backoff (base 500 ms, up to 8×, jittered to 50–100%). | Wire |
portTransport |
portTransport(port: MessageEndpoint) → Transport |
A transport over a Worker, MessagePort, or worker_threads port. |
Wire |
workerEndpoint |
workerEndpoint() → MessageEndpoint |
Inside a browser worker, the global scope as a port back to the page. | Wire |
broadcastChannelTransport |
broadcastChannelTransport(name) → Transport & { close(); announce() } |
A bus between tabs. The hosting tab calls announce() once it serves. |
Wire |
memoryPair |
memoryPair(opts?: MemoryPairOpts) → MemoryLink |
An in-memory client/host pair with disconnect(), reconnect(), and settle(), for tests. A message in flight at a disconnect is dropped, even if the link heals before it would land. manual: true holds every message until deliver(to) or settle() hands it over. |
Testing |
encode |
encode(msg: ClientMsg | HostMsg) → string |
Serializes a protocol message. | Protocol |
PROTOCOL |
3 |
The protocol revision. Every lookup carries it as v; a host answers a lookup with a different v with a raise. GET /schema on @nonchalant/host reports it too. |
Protocol |
decodeClient / decodeHost |
(data: string) → ClientMsg | null / HostMsg | null |
Validates and decodes one direction; null for garbage or the wrong direction. |
Protocol |
| type | what it is |
|---|---|
Transport |
{ send(data: string): void; subscribe(handlers): () => void } |
TransportHandlers |
{ message(data: string): void; open?(): void; close?(): void } |
MemoryLink |
{ client, host, disconnect(), reconnect(), settle(), inFlight(to), deliver(to) }; inFlight and deliver are for manual mode |
MemoryPairOpts |
{ manual?: boolean; disconnected?: boolean }: hold messages until delivered by hand; start partitioned until the first reconnect() |
LinkEnd |
'client' | 'host' |
ConnectOpts |
{ castQueue?: number }: casts kept per ref while disconnected (default 64; 0 keeps none) |
MessageEndpoint |
{ postMessage(data), addEventListener('message', fn), start?() } |
WebSocketTransportOpts |
{ retryDelay?: number } |
Connection<S> |
Registry<S> & { close(): void } |
Exposable |
The gateway expose serves: lookup(name: string, ...args: unknown[]): unknown, plus two optional members below. A registry is one. |
ExposeOpts |
{ maxWatches?: number; lookupRate?: { max: number; perMs: number; burst?: number } }, below. |
ClientMsg / HostMsg |
The eight protocol ops: lookup cast call exit / yield reply done raise. |
Exposable members:
| member | type | meaning |
|---|---|---|
lookup |
(name: string, ...args: unknown[]) => unknown |
Resolves a client lookup. Throwing answers the lookup with a raise. |
admit? |
(name: string, msg: { type: string } & { [key: string]: Json }) => Json | undefined |
Screens each client message for the process looked up under name. Return it, or a replacement (say, with the sender stamped from the session), to deliver it; return undefined or throw to refuse it. A refused call rejects; a refused cast is dropped. Runs after the host's own check that the message is an object with a string type, which applies with or without admit. |
principal? |
string |
Who the session acts for. A string callId in a call's message is rewritten into this principal's namespace before delivery, so one client cannot collide with, or read, another's recorded answers by reusing an id. Omit to deliver ids untouched. |
ExposeOpts:
| option | default | meaning |
|---|---|---|
maxWatches |
no cap | Refs one session may watch at once. A lookup past it raises; a re-lookup on an existing ref is always allowed. |
lookupRate |
no cap | { max, perMs, burst? }: a token bucket for one session's lookups. max tokens refill evenly over each perMs, up to burst (default max) held at once; the bucket starts full. A lookup with no token left raises. @nonchalant/host sets 100 per 10 s with a burst of 500. |
@nonchalant/durable
Durable processes: a message journal, an effect journal, durable calls, and a storage port. Isomorphic. Processes on the server is the guide.
| export | signature | what it does |
|---|---|---|
durable |
durable<T extends Json, In extends Json | DurableCall, Args>(proc: DurableProc<T, In, Args>, opts: DurableOpts<T, Args>) → Proc<T, In, Args>; or (proc: DurableTxProc<T, In, Args, Tx>, opts: DurableOpts<T, Args, TransactionalStore<Tx>>) |
Wraps a process so each message is journaled before it is handled, and its state, cursor, and call answers are committed together when the process asks for the next one. The result is an ordinary Proc. A body that uses local takes the second form, so its store must be transactional. After a restart, a body reading self.latest() takes the oldest message an earlier activation started before anything newer. |
memoryStore |
memoryStore(now?: () => number) → MemoryStore |
The in-memory Store adapter, for tests and demos. now (default Date.now) stamps committed answers for prune. |
memoryTxStore |
memoryTxStore(now?: () => number) → MemoryTxStore |
memoryStore plus in-memory tables that a local step writes through (MemoryTx), committed in the same synchronous turn as the step's record. The reference TransactionalStore. |
Fenced |
class Fenced extends Error |
What a store write rejects with when a later load of the same key has claimed it. A durable process that meets it returns quietly and leaves the key to the newer activation. |
scheduler |
scheduler(opts: SchedulerOpts) → Scheduler |
Wakes keys whose journaled deadlines (or failed attempts) are due: asks the store's due every interval and calls wake(key) for each, usually a registry lookup. Runs a pass at once; disposable. |
storeConformance |
storeConformance(make: (now: () => number) => ConformanceStore | Promise<ConformanceStore>, t: ConformanceTest) → void |
From @nonchalant/durable/conformance. Registers the Store contract as tests through the runner's own describe/it/expect; make returns an empty store stamping answers with now. |
transactionalConformance |
transactionalConformance<Tx>(make: (now: () => number) => TxHarness<Tx> | Promise<TxHarness<Tx>>, t: ConformanceTest) → void |
From @nonchalant/durable/conformance. Registers the stepTx contract: one test per crash point inside a local step (before the transaction, fn throwing, a result that cannot be recorded, after commit), a stale epoch refused before fn runs, and overlapping calls running fn once. Run it alongside storeConformance. |
| type | what it is |
|---|---|
DurableProc<T, In, Args> |
(self: Self<In>, args: Args, durable: Durable<T>) => AsyncGenerator<T> |
Durable<T> |
restored: T | undefined (the last committed state, migrated); step(name, fn: (idempotencyKey: string) => R | Promise<R>) (an effect recorded exactly once; fn gets ${key}#${seq}#${index}, stable across replays); call(name, invoke: (callId: string) => Promise<R>) (a call with a replay-stable id); sleep(name, ms) (a journaled deadline, which also makes the key due for a scheduler). |
DurableTxProc<T, In, Args, Tx> |
(self: Self<In>, args: Args, durable: DurableTx<T, Tx>) => AsyncGenerator<T>: a body that runs on a TransactionalStore<Tx>. A DurableProc is assignable to it. |
DurableTx<T, Tx> |
Durable<T> plus local(name, fn: (tx: Tx) => R). fn runs synchronously inside the transaction that records its result, so a write to the store's own database happens exactly once, and a replay returns the record without calling fn. A throw from fn rolls back and counts as the message's attempt. A promise returned from fn is refused. Make calls and casts after it, never inside it. |
DurableOpts<T, Args> |
Below. |
DurableCall |
{ readonly callId: string; readonly reply: (res: never) => void }: a call a durable process accepts carries its own id. Only the first reply in a message counts. A dead-lettered call's caller rejects with the handler's error, even if the handler replied first. A call self.latest() passes over rejects with call dropped, once the commit that skipped it lands. |
Store |
The storage port, eight methods, below. |
TransactionalStore<Tx> |
Store & { stepTx(key, epoch, seq, index, name, fn: (tx: Tx) => Json): Promise<Json> }. In one transaction, stepTx refuses a stale epoch with Fenced before calling fn, and returns an existing record at (seq, index) without calling fn. Otherwise it calls fn once and records its result. If fn throws, or its result cannot be recorded, nothing it wrote lands, and a throw rejects with fn's own error. |
Loaded |
{ snapshot: Json | undefined; version: number; cursor: number; epoch: number }: what load returns. |
Logged |
{ seq: number; msg: Json; callId?: string }: one journaled message. |
StepRecord |
{ index: number; name: string; result: Json }: one recorded effect; negative indices record failed attempts. |
Commit |
{ snapshot: Json | undefined; version: number; cursor: number; results: [string, Json][]; dead?: DeadLetter }: one message's acknowledgement. |
DeadLetter |
Logged & { error: string }: a message given up on, with its last failure. |
MemoryStore |
Store & { keys(): number; dead(key): DeadLetter[]; wake(key): number | undefined; prune(before: number): void }: a key count for reclamation tests, a key's dead letters, when a key next comes due (undefined if it has no wake time), and a retention sweep that forgets answers committed before before. |
MemoryTxStore |
MemoryStore & TransactionalStore<MemoryTx> & { table(name): Map<string, Json> }: table reads a table's committed rows, outside any transaction. |
MemoryTx |
{ get(table, id): Json | undefined; set(table, id, row: Json): void; delete(table, id): void; rows(table): [string, Json][] }: reads inside the transaction see its own writes, and rows are stored as copies. |
Scheduler |
Disposable & { tick(): Promise<string[]> }: tick runs one pass now and resolves with the keys it woke; disposing stops the passes. |
SchedulerOpts |
Below. |
ConformanceStore |
Store & { dead(key): DeadLetter[] | Promise<…>; prune(before: number): void | Promise<void> }: what the conformance suite needs beyond the port. |
TxHarness<Tx> |
{ store: TransactionalStore<Tx>; put(tx: Tx, id: string, value: number): void; get(id: string): number | undefined | Promise<…> }: how the transactional suite writes one row inside a step and reads it back as committed. |
ConformanceTest |
{ describe, it, expect }: the subset of a test runner the suite calls (expect(x).toBe, .toStrictEqual). |
DurableOpts<T, Args>:
| option | default | meaning |
|---|---|---|
store |
required | The Store adapter. |
key |
required | (args: Args) => string: the storage identity of an instance, usually from its lookup args. |
now |
Date.now |
Wall clock for sleep and the wake times it records; injected so tests do not wait. |
version |
0 |
The snapshot schema version this code writes. |
migrate |
none | (old: Json, from: number) => T: brings a snapshot committed under another version up to date. Required once version moves. |
maxAttempts |
no limit | How many times one message may crash the process before it is dead-lettered and the cursor steps past it. |
onPoison |
none | (key: string, dead: DeadLetter) => void: told after a message is dead-lettered. |
redeliverAfter |
30_000 |
How long a message may go unacknowledged before its key is due and a scheduler wakes it, presuming the activation handling it gone. Set it above your slowest message; a sleep sets its own deadline. |
SchedulerOpts:
| option | default | meaning |
|---|---|---|
store |
required | The Store whose wake times it reads. |
wake |
required | (key: string) => unknown: activate one due key, e.g. (id) => reg.lookup('order', { id }). |
interval |
1000 |
Milliseconds between passes; the next pass is scheduled after the last one ends. |
lease |
30_000 |
How long a woken key stays hidden from other passes, on any scheduler, before it may be woken again. |
limit |
100 |
Keys fetched per due call; a pass keeps fetching until it gets fewer. At least 1. |
now |
Date.now |
The clock wake times are compared against; the same one the durable processes use. |
onError |
console.error |
Told when a pass or a wake throws. The pass goes on. |
Store methods. Every write carries the epoch load handed out and must
change nothing and reject with Fenced if the key has been claimed since;
commit is one transaction.
| method | signature | job |
|---|---|---|
load |
(key) => Promise<Loaded> |
Claim the key (raise its epoch) and return the acknowledged state. |
append |
(key, epoch, msg: Json, callId?, wakeAt?: number) => Promise<number> |
Journal an inbound message before it is handled; returns its sequence number. With wakeAt, a key that has no wake time becomes due then. |
pending |
(key, cursor) => Promise<Logged[]> |
Messages after cursor, in order: what a restart replays. |
putStep |
(key, epoch, seq, index, name, result: Json, wakeAt?: number) => Promise<void> |
Record one completed effect, or a failed attempt, of message seq. With wakeAt, the key is also due at that time, replacing any earlier wake. |
steps |
(key, seq) => Promise<StepRecord[]> |
What is already recorded for that message. |
commit |
(key, epoch, commit: Commit) => Promise<void> |
Acknowledge one message: snapshot, version, cursor, answers, and any dead letter land together or not at all, with the key's wake time moved to commit.wakeAt if messages remain past the cursor, and cleared if none do. |
stepTx |
(key, epoch, seq, index, name, fn: (tx: Tx) => Json) => Promise<Json> |
TransactionalStore only: run fn and record its result in one transaction (see TransactionalStore above). |
result |
(key, callId) => Promise<Json | undefined> |
The answer already given to that call, if any. |
due |
(now: number, until: number, limit: number) => Promise<string[]> |
Up to limit keys whose wake time is at or before now, earliest first; in the same operation each one's wake time moves to until (a lease). |
@nonchalant/host
The Node WebSocket host. Hosting safely is the guide.
| export | signature | what it does |
|---|---|---|
serve |
serve<S>(defs: S, opts?: ServeOpts<S>) → Promise<HostHandle<S>> |
Hosts a registry over WebSockets; resolves once listening, or at once when given a server to share. Each connection is its own expose session. GET /schema serves { protocol, names }. |
ServeOpts<S> |
type | See below. |
OriginPolicy |
(origin: string | undefined, request) => boolean | Promise<boolean> |
A callback form of allowedOrigins. |
RateLimit |
{ max: number; perMs: number; burst?: number } |
A token bucket: max tokens per perMs, up to burst (default max) at once. |
HostHandle<S> |
{ registry, port, url, sessions(), close() } |
The running host; registry is host-side access to the same processes. port and url are read from the bound address (and throw until a shared server listens). close() disposes every process the host spawned and leaves a shared server listening. |
| option | default | meaning |
|---|---|---|
port |
0 (ephemeral) |
TCP port. Not with server. |
host |
'127.0.0.1' |
The interface to listen on; '0.0.0.0' or '::' for every interface. Not with server. |
server |
none | An existing http or https server to share: the host answers its path upgrades and GET /schema, passes every other request and upgrade to the app's handlers (attach them before calling serve), and does not listen. |
path |
'/' |
WebSocket path. |
allowedOrigins |
loopback origins and clients with no Origin |
Array of origins, or an OriginPolicy. An array rejects clients with no Origin. |
authorize |
allow | Authenticates the schema request and the upgrade. |
scope |
the shared registry | (request, reg) => Exposable | Promise<Exposable>: builds the gateway each connection's lookups and messages go through, once per connection, after authorize. Throwing rejects the upgrade (500). |
maxPayloadBytes |
1 MiB | Larger client messages close the connection (code 1009). |
maxWatchesPerConnection |
1000 (Infinity for no cap) |
Lookups past it raise to that client. |
lookupRate |
{ max: 100, perMs: 10_000, burst: 500 } |
Token bucket for one client's lookups (its scope's principal, else its remote address), kept across its reconnects; one with no token left raises to that client. |
totalLookupRate |
{ max: 1_000, perMs: 1_000, burst: 10_000 } |
The same bucket shared by every connection; one past it raises host lookup rate exceeded. |
maxEntries |
10 000 | Registry cap: past it the least recently looked-up unwatched entries are disposed. Infinity for no cap. |
heartbeatMs |
30 000 (0 disables) | Pings each socket; a missed pong terminates it and releases its watches. |
maxBufferedBytes |
8 MiB | Outbound bytes one socket may have queued before the host terminates it. |
Each connection is a principal: the one its scope gateway names, or else a
fresh random one, so call ids are always namespaced per connection. serve
throws at once on a port outside 0–65535, an empty host, port or host
together with server, a negative or non-finite heartbeatMs, a
maxWatchesPerConnection that is neither a non-negative integer nor Infinity, a malformed lookupRate or totalLookupRate, a
non-positive maxBufferedBytes, or a maxEntries that is neither a positive
integer nor Infinity.
@nonchalant/inspect
The process inspector: a recorder over core's instrument hook, time travel,
and a panel. Depends on core and dom. The inspector is the
guide.
The core hook
Exported from @nonchalant/core; the inspector is built on it.
| export | signature | what it does |
|---|---|---|
instrument |
instrument(sink: (event: ProcessEvent) => void) → () => void |
Reports every process event to sink, synchronously, as it happens. One sink at a time; returns its remover. With none installed, each event site costs one check. |
ProcessEvent |
type | A union on type: spawn (parent, name, key, args, state), cast (msg), call (msg, call), reply (call, value), yield (ops: the patch from the previous state), status (pending, stale, errored), crash (error), restart (attempt), exit (reason: 'done' | 'crashed' | 'disposed'). Every event carries the process id. |
The recorder
| export | signature | what it does |
|---|---|---|
inspect |
inspect(opts?: { size?: number }) → Inspector |
Installs the sink and records into a ring of size entries (default 1000). The inspector's own processes are not recorded. |
Inspector |
{ recording, tree, timeline, stateAt, adopt, [Symbol.dispose] } |
recording: Process<Recording, RecorderMsg> owns the data; tree: Process<TreeNode[]> and timeline: Process<Entry[]> derive from it; stateAt(id, seq) reconstructs a state; adopt(fn) marks what fn spawns as the inspector's own. Disposing stops recording. |
Recording |
{ size, seq, events: Entry[], procs: { [id]: ProcNode } } |
The recorded data, all plain JSON. |
Entry |
type | One timeline row: a ProcessEvent other than status, summarized to JSON, plus its seq. |
ProcNode |
type | One recorded process: id, parent, name, key, status, the three flags, its current state, and the base/baseSeq time travel starts from. |
stateAt |
stateAt(rec: Recording, id: number, seq: number) → Json | null | undefined |
A process's state just after event seq: base plus every retained yield patch up to seq. undefined if the process is unknown or seq is older than the ring retains. |
record |
record(rec: Recording, d: Draft) → Recording |
The recorder's reducer: fold one event in, trimming the ring. |
clear |
clear(rec: Recording) → Recording |
Drop every entry and every ended process. |
tree |
tree(procs) → TreeNode[] |
Processes not yet disposed, nested by ownership. |
summarize |
summarize(value: unknown) → Json |
Plain JSON by reference; anything else as a bracketed label. |
draft |
draft(e: ProcessEvent) → Draft |
An event with its live values summarized. |
The panel
| export | signature | what it does |
|---|---|---|
mountInspector |
mountInspector(el: Element, inspector?: Inspector) → Disposable & { inspector } |
Renders the tree, the timeline and the detail pane into el. Starts an inspector unless given one, and disposes the one it started. |