@spfn/core 0.3.0-beta.30 → 0.3.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/dist/state/client.js +27 -16
- package/dist/state/client.js.map +1 -1
- package/package.json +7 -7
- package/src/state/README.md +10 -5
package/README.md
CHANGED
|
@@ -43,11 +43,12 @@ Next.js owns the frontend, Drizzle owns the SQL, and your services own the rules
|
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
pnpm add @spfn/core@beta drizzle-orm@1.0.0-rc.4 postgres pg
|
|
46
|
-
# optional peer: next ^16.
|
|
46
|
+
# optional peer: next ^16.4.1 (only for the Next.js bridge)
|
|
47
47
|
# optional peer: react ^18 || ^19 (only for @spfn/core/state/react)
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
Node
|
|
50
|
+
Node `^22.23.4 || ^24.22.1 || >=26.12.1` — earlier releases lack the fix for
|
|
51
|
+
CVE-2026-111497, and Node 20 reached end of life without one. ESM only.
|
|
51
52
|
|
|
52
53
|
> **Declare Drizzle and the Postgres drivers in your own app, not just in SPFN.**
|
|
53
54
|
> `@spfn/core` takes `drizzle-orm` as a peer dependency, and Drizzle changes how it
|
|
@@ -855,7 +856,7 @@ There is no `.` entry in `exports` — by design. Subpaths keep server-only code
|
|
|
855
856
|
client bundles, which a single barrel file cannot do.
|
|
856
857
|
|
|
857
858
|
**Which Node version?**
|
|
858
|
-
|
|
859
|
+
`^22.23.4 || ^24.22.1 || >=26.12.1`. The package is ESM only.
|
|
859
860
|
|
|
860
861
|
**Where do my business rules go?**
|
|
861
862
|
In services and repositories, not in route handlers. A handler validates, calls, and
|
package/dist/state/client.js
CHANGED
|
@@ -297,6 +297,10 @@ var BrowserStateClient = class {
|
|
|
297
297
|
const fields = this.manifest[id].key;
|
|
298
298
|
return fields.length ? Object.fromEntries(fields.map((field) => [field, query[field]])) : void 0;
|
|
299
299
|
}
|
|
300
|
+
/** The slot of state `id` on the key `query` carries; `undefined` when the query lacks one of its key fields. */
|
|
301
|
+
carriedSlot(id, query) {
|
|
302
|
+
return this.manifest[id].key.every((field) => field in query) ? this.slot(id, this.keyIn(id, query)) : void 0;
|
|
303
|
+
}
|
|
300
304
|
/** Why the manifest refuses `subscribe(id, key)` — an unknown id, or key fields not the state's. */
|
|
301
305
|
refusal(id, key) {
|
|
302
306
|
const state = this.manifest[id];
|
|
@@ -389,18 +393,19 @@ var BrowserStateClient = class {
|
|
|
389
393
|
}
|
|
390
394
|
}
|
|
391
395
|
/**
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
* a newer connection exists, nor when a grant came after the
|
|
396
|
+
* Every state the snapshot omits whose key the query carried (or a keyless
|
|
397
|
+
* one), watched or not, is not served to this subject: `'revoked'` on that
|
|
398
|
+
* key. Not when a newer connection exists, nor when a grant came after the
|
|
399
|
+
* read started. Every watched slot the query carries is one of these.
|
|
395
400
|
*/
|
|
396
401
|
applyOmitted(snapshot, query, tag) {
|
|
397
402
|
if (tag.generation !== this.generation) {
|
|
398
403
|
return;
|
|
399
404
|
}
|
|
400
|
-
for (const
|
|
401
|
-
const
|
|
402
|
-
const grantedSince = (this.grants.get(slot) ?? 0) > tag.tick;
|
|
403
|
-
if (
|
|
405
|
+
for (const id of Object.keys(this.manifest)) {
|
|
406
|
+
const slot = this.carriedSlot(id, query);
|
|
407
|
+
const grantedSince = slot !== void 0 && (this.grants.get(slot) ?? 0) > tag.tick;
|
|
408
|
+
if (slot !== void 0 && !(id in snapshot.states) && !grantedSince) {
|
|
404
409
|
this.store.revoke(slot, tag.generation);
|
|
405
410
|
}
|
|
406
411
|
}
|
|
@@ -587,9 +592,9 @@ var BrowserStateClient = class {
|
|
|
587
592
|
* must cover the frames sent while no stream was open. The server says in
|
|
588
593
|
* `connected` which states it served their current frame right after it;
|
|
589
594
|
* the snapshot is read only when one it did not serve is neither denied
|
|
590
|
-
* nor served — or when the server predates `initial`. A denied state
|
|
591
|
-
*
|
|
592
|
-
* be. A connection already replaced is ignored.
|
|
595
|
+
* nor served — or when the server predates `initial`. A denied state whose
|
|
596
|
+
* key the stream carries is `'revoked'`, watched or not, as an omission
|
|
597
|
+
* from that snapshot would be. A connection already replaced is ignored.
|
|
593
598
|
*/
|
|
594
599
|
onConnected(connected, stream) {
|
|
595
600
|
if (stream !== this.stream || stream.generation !== this.generation) {
|
|
@@ -605,11 +610,15 @@ var BrowserStateClient = class {
|
|
|
605
610
|
this.refresh(stream.query);
|
|
606
611
|
}
|
|
607
612
|
}
|
|
608
|
-
/**
|
|
613
|
+
/**
|
|
614
|
+
* Each state the connection denies, on the key its query carries, marked
|
|
615
|
+
* `'revoked'` by it, watched or not. A keyed state denied because the query
|
|
616
|
+
* lacks one of its key fields has no carried key, so nothing is marked.
|
|
617
|
+
*/
|
|
609
618
|
revokeDenied(denied, stream) {
|
|
610
619
|
for (const id of Object.keys(this.manifest)) {
|
|
611
|
-
const slot = this.
|
|
612
|
-
if (denied.has(stateEventName(id))
|
|
620
|
+
const slot = this.carriedSlot(id, stream.query);
|
|
621
|
+
if (slot !== void 0 && denied.has(stateEventName(id))) {
|
|
613
622
|
this.store.revoke(slot, stream.generation);
|
|
614
623
|
}
|
|
615
624
|
}
|
|
@@ -636,14 +645,16 @@ var BrowserStateClient = class {
|
|
|
636
645
|
this.serve(this.slot(id, key), frame, tag.generation);
|
|
637
646
|
}
|
|
638
647
|
/**
|
|
639
|
-
* A frame
|
|
640
|
-
*
|
|
648
|
+
* A frame on the key the stream's query carries is applied, watched or not,
|
|
649
|
+
* so a later subscribe that does not reopen the stream is served the newest
|
|
650
|
+
* value; a frame for any other key is dropped unless watched — the server's
|
|
651
|
+
* filter is the first guard, this the second. A frame is served under its
|
|
641
652
|
* connection's generation: the server sends one only on a connection that
|
|
642
653
|
* allows the state, so a newer connection's frame lifts an older revoke.
|
|
643
654
|
*/
|
|
644
655
|
onFrame(id, frame, stream) {
|
|
645
656
|
const slot = this.slot(id, frame.key);
|
|
646
|
-
if (this.watched.has(slot)) {
|
|
657
|
+
if (slot === this.carriedSlot(id, stream.query) || this.watched.has(slot)) {
|
|
647
658
|
this.serve(slot, frame, stream.generation);
|
|
648
659
|
}
|
|
649
660
|
}
|
package/dist/state/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/state/client-store.ts","../../src/state/client.ts"],"names":["notify"],"mappings":"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","file":"client.js","sourcesContent":["/**\n * State Client Store\n *\n * One frozen entry per `(id, key)` and the ordering rule that decides whether\n * an arrival — a snapshot entry, a frame, a read or a write answer — replaces\n * it. An entry is replaced only when its status, value or data changes, so\n * `current()` returns the same object until then: `useSyncExternalStore`\n * compares by reference and would re-render forever on a fresh object per\n * call. A newer `at` alone advances the slot's ordering stamp, not the entry.\n */\n\nimport type { StateEntry, StateFrame, StateListener } from './client-types';\n\n/** Key fields, flat strings by construction (`.key()` refuses other fields). */\nexport type StateKeyFields = Record<string, string>;\n\n/** A key as text with its fields sorted, so equal keys written in any order name one entry. */\nexport function stableKey(key: StateKeyFields | undefined): string\n{\n if (!key)\n {\n return '';\n }\n\n return JSON.stringify(Object.keys(key).sort().map(field => [field, key[field]]));\n}\n\n/** The inverse of `stableKey`. */\nexport function parseKey(text: string): StateKeyFields | undefined\n{\n return text ? Object.fromEntries(JSON.parse(text) as [string, string][]) : undefined;\n}\n\n/** Deep equality of JSON values, which is all a frame carries. */\nfunction sameJson(a: unknown, b: unknown): boolean\n{\n if (a === b)\n {\n return true;\n }\n\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null || Array.isArray(a) !== Array.isArray(b))\n {\n return false;\n }\n\n const aKeys = Object.keys(a);\n const bRecord = b as Record<string, unknown>;\n\n return aKeys.length === Object.keys(b).length\n && aKeys.every(field => field in bRecord && sameJson((a as Record<string, unknown>)[field], bRecord[field]));\n}\n\n/**\n * The ordering rule for the value: an arrival older than the slot's stamp —\n * the newest `at` seen — is dropped. An equal `at` is by design the same\n * value (one read stamps one `at`), so it is not older and `changes` decides.\n */\nfunction isOlder(stamp: string | undefined, arrival: StateFrame): boolean\n{\n return stamp !== undefined && arrival.at < stamp;\n}\n\n/**\n * Whether an arrival says something the entry does not. An entry marked\n * revoked before any value arrived has no `at`: anything changes it.\n */\nfunction changes(current: StateEntry | undefined, arrival: StateFrame): boolean\n{\n return current?.at === undefined || !sameJson(current.value, arrival.value) || !sameJson(current.data, arrival.data);\n}\n\nfunction entryOf(frame: StateFrame, status: StateStatus): StateEntry\n{\n const { at, value, data } = frame;\n\n return Object.freeze(data === undefined ? { at, value, status } : { at, value, data, status });\n}\n\ntype StateStatus = StateEntry['status'];\n\nexport class StateStore\n{\n private readonly entries = new Map<string, StateEntry>();\n\n /**\n * The newest `at` seen per slot, kept apart from the entry: an arrival\n * that changes nothing still orders the ones after it, while the entry's\n * `at` stays the `at` of the read that last changed the value.\n */\n private readonly stamps = new Map<string, string>();\n\n private readonly listeners = new Map<string, Set<StateListener>>();\n\n /**\n * The slots the server stopped serving, with the generation of the\n * connection that said so. Kept apart from the entries: a revoke can\n * arrive before the slot has a value, and an arrival afterwards supplies\n * the value without clearing the mark.\n */\n private readonly revoked = new Map<string, number>();\n\n /** The store slot of `(id, key)`. */\n static slot(id: string, keyText: string): string\n {\n return `${id}\\u0000${keyText}`;\n }\n\n current(slot: string): StateEntry | undefined\n {\n return this.entries.get(slot);\n }\n\n /**\n * Apply an arrival: the value under the ordering rule, the status from the\n * revoked mark. Listeners hear only a changed status, value or data.\n */\n apply(slot: string, frame: StateFrame): void\n {\n const current = this.entries.get(slot);\n const status: StateStatus = this.revoked.has(slot) ? 'revoked' : 'ready';\n const ordered = !isOlder(this.stamps.get(slot), frame);\n\n if (ordered)\n {\n this.stamps.set(slot, frame.at);\n }\n\n if (ordered && changes(current, frame))\n {\n this.replace(slot, entryOf(frame, status));\n }\n else if (current!.status !== status)\n {\n // Not applied, so the current entry has a value: only its status changes.\n this.replace(slot, entryOf(current as StateFrame, status));\n }\n }\n\n /** Mark `slot` revoked by the connection of `generation`; its value, if any, is kept. */\n revoke(slot: string, generation: number): void\n {\n this.revoked.set(slot, generation);\n const entry = this.entries.get(slot);\n\n if (entry?.status !== 'revoked')\n {\n this.replace(slot, Object.freeze(entry ? { ...entry, status: 'revoked' as const } : { status: 'revoked' as const }));\n }\n }\n\n /** The generation of the connection that revoked `slot`, or `undefined` while it is served. */\n revokedBy(slot: string): number | undefined\n {\n return this.revoked.get(slot);\n }\n\n /** Clear the revoked mark; the next arrival for `slot` is `'ready'`. */\n allow(slot: string): void\n {\n this.revoked.delete(slot);\n }\n\n listen(slot: string, listener: StateListener): void\n {\n const set = this.listeners.get(slot) ?? new Set();\n set.add(listener);\n this.listeners.set(slot, set);\n }\n\n unlisten(slot: string, listener?: StateListener): void\n {\n const set = this.listeners.get(slot);\n\n if (listener)\n {\n set?.delete(listener);\n }\n\n if (!listener || set?.size === 0)\n {\n this.listeners.delete(slot);\n }\n }\n\n clearListeners(): void\n {\n this.listeners.clear();\n }\n\n /** Replace the entry and notify; a throwing listener is logged and does not stop the others. */\n private replace(slot: string, entry: StateEntry): void\n {\n this.entries.set(slot, entry);\n\n for (const listener of [...this.listeners.get(slot) ?? []])\n {\n notify(slot, listener);\n }\n }\n}\n\nfunction notify(slot: string, listener: StateListener): void\n{\n try\n {\n listener();\n }\n catch (error)\n {\n // The state id only: the key is the app's data.\n console.warn(`[state] A listener of state \"${slot.split('\\u0000')[0]}\" threw:`, error);\n }\n}\n","/**\n * State Client\n *\n * The browser side of a state router (`@spfn/core/state`): it calls the derived\n * read, write and snapshot routes, keeps one SSE stream open for every state of\n * the router, and orders snapshots against frames per `(id, key)`. It knows the\n * router by type — `typeof states` — and at runtime by the manifest every\n * snapshot carries; it imports no server code.\n *\n * @example\n * ```typescript\n * import { createStateClient } from '@spfn/core/state/client';\n * import { createAuthSSEClient } from '@spfn/core/event/sse/client';\n * import type { states } from '@/server/states';\n *\n * export const state = createStateClient<typeof states>({ sse: createAuthSSEClient() });\n *\n * const stop = state.subscribe('runtime', { workspaceId }, () => render(state.current('runtime', { workspaceId })));\n * ```\n */\n\nimport type { ConnectedPayload, SSESubscriptionChange } from '@spfn/core/event/sse';\nimport { createApi } from '@spfn/core/nextjs';\nimport { stableKey, StateStore, type StateKeyFields } from './client-store';\nimport type {\n AnyStateRouter,\n ConnectionListener,\n ConnectionStatus,\n StateClient,\n StateClientConfig,\n StateClientInitial,\n StateEntry,\n StateFrame,\n StateListener,\n} from './client-types';\n\nexport type {\n AnyStateRouter,\n ConnectionListener,\n ConnectionStatus,\n InferStateData,\n StateCatalog,\n StateFrameOf,\n InferStateInput,\n InferStateKey,\n InferStateValue,\n StateClient,\n StateClientConfig,\n StateClientInitial,\n StateEntry,\n StateFrame,\n StateId,\n StateKeyArgs,\n StateListener,\n WritableStateId,\n} from './client-types';\n\n/** A frame as the SSE event carries it. */\ntype WireFrame = StateFrame & { key?: StateKeyFields; subject?: string };\n\n/** Every state of the router, in declaration order: its key fields and whether it has a write. */\ntype Manifest = Record<string, { key: string[]; write: boolean }>;\n\ninterface Snapshot\n{\n at: string;\n states: Record<string, { value: unknown; data?: unknown }>;\n manifest: Manifest;\n}\n\ntype RouteCalls = Record<string, { call(input: object): Promise<unknown> }>;\n\n/** One `(id, key)` someone watches, and how many subscriptions hold it. */\ninterface Watch\n{\n id: string;\n key: StateKeyFields | undefined;\n count: number;\n}\n\n/**\n * The stream as one `sse.subscribe` opened it; `generation` names its current\n * connection. `closedByClient`: the client closed it itself, so its `onClose`\n * is no failure; `failed`: the SSE client gave up on it.\n */\ninterface Stream\n{\n query: StateKeyFields;\n text: string;\n generation: number;\n closedByClient: boolean;\n failed: boolean;\n close: () => void;\n}\n\n/** When an automatic read was started: the connection generation and the client's clock. */\ninterface ReadTag\n{\n generation: number;\n tick: number;\n}\n\nconst CONNECTED: ConnectionStatus = Object.freeze({ status: 'connected' });\nconst FAILED: ConnectionStatus = Object.freeze({ status: 'failed' });\nconst STARTING: ConnectionStatus = Object.freeze({ status: 'reconnecting', attempt: 0 });\n\nfunction reconnecting(attempt: number): ConnectionStatus\n{\n return attempt === 0 ? STARTING : Object.freeze({ status: 'reconnecting', attempt });\n}\n\nfunction sameStatus(a: ConnectionStatus, b: ConnectionStatus): boolean\n{\n return a.status === b.status && (a as { attempt?: number }).attempt === (b as { attempt?: number }).attempt;\n}\n\n/** `get<Id>State` / `put<Id>State`, the names the server router derives. */\nfunction routeName(method: 'get' | 'put', id: string): string\n{\n return `${method}${id[0].toUpperCase()}${id.slice(1)}State`;\n}\n\n/** The wire name of a state's SSE event, as the server derives it. */\nfunction stateEventName(id: string): string\n{\n return `state.${id}.changed`;\n}\n\n/** Whether `query` carries every field of `key` with its value. */\nfunction carries(query: StateKeyFields, key: StateKeyFields): boolean\n{\n return Object.entries(key).every(([field, value]) => query[field] === value);\n}\n\nfunction sortedFields(fields: string[]): string\n{\n return [...fields].sort().join(', ');\n}\n\n/**\n * Build the client for `typeof states`. The first subscription (or call)\n * reads the snapshot, whose manifest names every state — or takes it from\n * `initial`, the snapshot the server rendered with; the stream then\n * requests every state event — the server's `grant` can only activate an\n * event the connection requested. The router type parameter is what types\n * `initial.snapshot`; without it the client is untyped, as before.\n */\nexport function createStateClient<TStates extends AnyStateRouter>(config: StateClientConfig<TStates>): StateClient<TStates>\n{\n return new BrowserStateClient(config) as unknown as StateClient<TStates>;\n}\n\nclass BrowserStateClient\n{\n private readonly store = new StateStore();\n private readonly api: RouteCalls;\n private readonly watched = new Map<string, Watch>();\n private readonly reportError: (error: unknown) => void;\n private manifest: Manifest | undefined;\n private manifestRead: Promise<void> | undefined;\n private stream: Stream | undefined;\n private syncQueued = false;\n\n private status = STARTING;\n private readonly connectionListeners = new Set<ConnectionListener>();\n private readonly onOnline = (): void => this.retry();\n\n /** The retries of the first snapshot since its last start, against `maxRetries`. */\n private retries = 0;\n\n /** The lifetime whose subscriptions wait on the first snapshot — its read in flight, or its retry timer set. */\n private waitingLifetime: number | undefined;\n private retryTimer: ReturnType<typeof setTimeout> | undefined;\n\n /** Bumped by `close()`: a snapshot started before it is discarded. */\n private lifetime = 0;\n\n /** Bumped on every connection open; tags the snapshot read for it. */\n private generation = 0;\n\n /** Bumped on every automatic read and every grant, to tell which came first. */\n private clock = 0;\n\n /** The clock at the latest grant of each slot. */\n private readonly grants = new Map<string, number>();\n\n constructor(private readonly config: StateClientConfig)\n {\n this.api = createApi<any>(config.api) as unknown as RouteCalls;\n this.reportError = config.onError ?? (error => console.error('[state] A refresh failed:', error));\n\n if (config.initial)\n {\n this.hydrate(config.initial);\n }\n\n // Absent in Node and in a server render.\n if (typeof globalThis.addEventListener === 'function')\n {\n globalThis.addEventListener('online', this.onOnline);\n }\n }\n\n async get(id: string, key?: StateKeyFields): Promise<StateFrame>\n {\n await this.requireManifest();\n const frame = await this.api[routeName('get', id)].call(key ? { query: key } : {}) as StateFrame;\n this.store.apply(this.slot(id, key), frame);\n\n return frame;\n }\n\n async write(id: string, input: unknown, key?: StateKeyFields): Promise<StateFrame>\n {\n if (!(await this.requireManifest())[id]?.write)\n {\n throw new Error(`State client: state \"${id}\" declares no .write(), so it has no write route.`);\n }\n\n const frame = await this.api[routeName('put', id)].call({ body: input, ...(key ? { query: key } : {}) }) as StateFrame;\n this.store.apply(this.slot(id, key), frame);\n\n return frame;\n }\n\n snapshot(query: StateKeyFields = this.streamQuery()): Promise<Snapshot>\n {\n return this.readSnapshot(query, this.tag());\n }\n\n subscribe(id: string, ...args: [StateListener] | [StateKeyFields, StateListener]): () => void\n {\n const listener = args[args.length - 1] as StateListener;\n const key = args.length === 2 ? args[0] as StateKeyFields : undefined;\n const refusal = this.manifest && this.refusal(id, key);\n\n if (refusal)\n {\n this.reportError(refusal);\n\n return () => undefined;\n }\n\n // One wrapper per subscription: the same listener subscribed twice is two subscriptions.\n const notify = () => listener();\n const slot = this.slot(id, key);\n this.store.listen(slot, notify);\n this.watch(slot, id, key, 1);\n let live = true;\n\n return () =>\n {\n if (live)\n {\n live = false;\n this.store.unlisten(slot, notify);\n this.watch(slot, id, key, -1);\n }\n };\n }\n\n current(id: string, key?: StateKeyFields): StateEntry | undefined\n {\n return this.store.current(this.slot(id, key));\n }\n\n connection(): ConnectionStatus\n {\n return this.status;\n }\n\n subscribeConnection(listener: ConnectionListener): () => void\n {\n // One wrapper per subscription: the same listener subscribed twice is two subscriptions.\n const notify: ConnectionListener = status => listener(status);\n this.connectionListeners.add(notify);\n\n return () =>\n {\n this.connectionListeners.delete(notify);\n };\n }\n\n /**\n * Restart what gave up or is still retrying, now: the first snapshot\n * while the manifest is unknown, else the stream — a new `sse.subscribe`,\n * whose attempt count starts at 0. Nothing to do when connected or with\n * nothing watched — so after `close()`, until a new `subscribe`.\n */\n retry(): void\n {\n if (this.status === CONNECTED || this.watched.size === 0)\n {\n return;\n }\n\n if (this.manifest)\n {\n this.openStream(this.streamQuery());\n }\n else\n {\n this.rereadManifest();\n }\n }\n\n close(): void\n {\n this.lifetime++;\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.retries = 0;\n this.closeStream();\n this.watched.clear();\n this.store.clearListeners();\n this.connectionListeners.clear();\n this.status = STARTING;\n\n if (typeof globalThis.removeEventListener === 'function')\n {\n globalThis.removeEventListener('online', this.onOnline);\n }\n }\n\n /** The status is `next` from now; each connection listener hears it, a throwing one logged. */\n private setConnection(next: ConnectionStatus): void\n {\n if (sameStatus(next, this.status))\n {\n return;\n }\n\n this.status = next;\n\n for (const listener of [...this.connectionListeners])\n {\n try\n {\n listener(next);\n }\n catch (error)\n {\n console.warn('[state] A connection listener threw:', error);\n }\n }\n }\n\n /**\n * Start from the snapshot the server rendered with: its manifest, then\n * each state present, as a read snapshot applies them, under generation 0\n * — no connection yet. Nothing is marked `'revoked'`: nothing is watched,\n * and an omission in the render's snapshot is not a denial by the stream.\n */\n private hydrate(initial: StateClientInitial): void\n {\n const snapshot = initial.snapshot as Snapshot;\n this.installManifest(snapshot.manifest);\n this.applyPresent(snapshot, initial.query ?? {}, this.tag());\n }\n\n private slot(id: string, key: StateKeyFields | undefined): string\n {\n return StateStore.slot(id, stableKey(key));\n }\n\n private tag(): ReadTag\n {\n return { generation: this.generation, tick: ++this.clock };\n }\n\n /** The key of state `id` the query names; `undefined` for a keyless state. */\n private keyIn(id: string, query: StateKeyFields): StateKeyFields | undefined\n {\n const fields = this.manifest![id].key;\n\n return fields.length ? Object.fromEntries(fields.map(field => [field, query[field]])) : undefined;\n }\n\n /** Why the manifest refuses `subscribe(id, key)` — an unknown id, or key fields not the state's. */\n private refusal(id: string, key: StateKeyFields | undefined): Error | undefined\n {\n const state = this.manifest![id];\n\n if (!state)\n {\n return new Error(`State client: the server's manifest has no state \"${id}\".`);\n }\n\n const expected = sortedFields(state.key);\n const given = sortedFields(Object.keys(key ?? {}));\n\n return expected === given\n ? undefined\n : new Error(`State client: state \"${id}\" takes the key fields [${expected}], given [${given}].`);\n }\n\n /** The first snapshot, read once however many callers wait for it. */\n private ensureManifest(): Promise<void>\n {\n if (this.manifest)\n {\n return Promise.resolve();\n }\n\n this.manifestRead ??= this.snapshot()\n .then(() => undefined)\n .finally(() =>\n {\n this.manifestRead = undefined;\n });\n\n return this.manifestRead;\n }\n\n /** The manifest for a call the app made; one whose wait outlived `close()` is refused. */\n private async requireManifest(): Promise<Manifest>\n {\n await this.ensureManifest();\n\n if (!this.manifest)\n {\n throw new Error('State client: the client was closed while the first snapshot was in flight.');\n }\n\n return this.manifest;\n }\n\n /**\n * Read the snapshot and apply it: the manifest first — `keyIn` and the\n * key checks need it — then each state present, then each watched state\n * it omits. A snapshot that resolves after `close()` is discarded.\n */\n private async readSnapshot(query: StateKeyFields, tag: ReadTag): Promise<Snapshot>\n {\n const lifetime = this.lifetime;\n const snapshot = await this.api.getStates.call({ query }) as Snapshot;\n\n if (lifetime === this.lifetime)\n {\n this.installManifest(snapshot.manifest);\n this.applyPresent(snapshot, query, tag);\n this.applyOmitted(snapshot, query, tag);\n }\n\n return snapshot;\n }\n\n /**\n * The manifest is static; the first one stays. Subscriptions made before\n * it are checked now, and the stream opens for those it accepts —\n * whichever call (`subscribe`, `get`, `write`, `snapshot`) brought it.\n */\n private installManifest(manifest: Manifest): void\n {\n if (this.manifest)\n {\n return;\n }\n\n this.manifest = manifest;\n\n for (const [slot, watch] of this.watched)\n {\n const refusal = this.refusal(watch.id, watch.key);\n\n if (refusal)\n {\n this.watched.delete(slot);\n this.store.unlisten(slot);\n this.reportError(refusal);\n }\n }\n\n if (this.watched.size > 0)\n {\n this.queueSync();\n }\n }\n\n /**\n * An arrival from a read started for the connection of `tag`: a\n * connection newer than the one that revoked the slot lifts the mark, an\n * older or the same connection leaves `'revoked'`. The lift is by\n * generation alone — a frame or a write answer of the new connection may\n * land first with a newer `at` — and the ordering rule decides only the value.\n */\n private serve(slot: string, frame: StateFrame, generation: number): void\n {\n if ((this.store.revokedBy(slot) ?? Infinity) < generation)\n {\n this.store.allow(slot);\n }\n\n this.store.apply(slot, frame);\n }\n\n /**\n * A state present is served, under the generation rule of `serve`. A state\n * the manifest does not describe, or a keyed one whose key fields the query\n * lacks, is skipped: the server never serves either, but a hand-built\n * `initial` might.\n */\n private applyPresent(snapshot: Snapshot, query: StateKeyFields, tag: ReadTag): void\n {\n for (const [id, result] of Object.entries(snapshot.states))\n {\n if (this.manifest![id]?.key.every(field => field in query))\n {\n this.serve(this.slot(id, this.keyIn(id, query)), { at: snapshot.at, ...result }, tag.generation);\n }\n }\n }\n\n /**\n * A watched state whose key the query carried (or a keyless one) and that\n * the snapshot omits is not served to this subject: `'revoked'`. Not when\n * a newer connection exists, nor when a grant came after the read started.\n */\n private applyOmitted(snapshot: Snapshot, query: StateKeyFields, tag: ReadTag): void\n {\n if (tag.generation !== this.generation)\n {\n return;\n }\n\n for (const [slot, watch] of this.watched)\n {\n const asked = !watch.key || carries(query, watch.key);\n const grantedSince = (this.grants.get(slot) ?? 0) > tag.tick;\n\n if (asked && !(watch.id in snapshot.states) && !grantedSince)\n {\n this.store.revoke(slot, tag.generation);\n }\n }\n }\n\n private watch(slot: string, id: string, key: StateKeyFields | undefined, delta: 1 | -1): void\n {\n const count = (this.watched.get(slot)?.count ?? 0) + delta;\n\n if (count > 0)\n {\n this.watched.set(slot, { id, key, count });\n }\n else\n {\n this.watched.delete(slot);\n }\n\n // A removal that leaves subscribers keeps a failed client failed; the last removal closes and resets.\n if (delta === 1 || this.watched.size === 0 || this.status !== FAILED)\n {\n this.queueSync();\n }\n }\n\n /**\n * One stream for the client, its query the union of the subscribed keys.\n * Batched to the end of the task, so components mounting together (or a\n * StrictMode unmount-remount) open the stream once.\n */\n private queueSync(): void\n {\n if (!this.syncQueued)\n {\n this.syncQueued = true;\n queueMicrotask(() =>\n {\n this.syncQueued = false;\n this.sync();\n });\n }\n }\n\n /**\n * Until the manifest is known, subscriptions wait for the first snapshot;\n * the stream opens after it, never before — its `events=` comes from it.\n * A client built with `initial` knows it from the start. With nothing\n * watched the stream closes, the retry count starts over for the next\n * wait, and the status is the start value: no stream, nothing answered.\n */\n private sync(): void\n {\n if (this.watched.size === 0)\n {\n this.retries = 0;\n this.closeStream();\n this.setConnection(STARTING);\n\n return;\n }\n\n if (!this.manifest)\n {\n this.awaitManifest();\n\n return;\n }\n\n // A stream the SSE client gave up on counts as absent.\n const query = this.streamQuery();\n\n if (this.stream?.failed || this.stream?.text !== stableKey(query))\n {\n this.openStream(query);\n }\n }\n\n /**\n * One waiter per lifetime on the first snapshot. A failure goes to\n * `onError`, and the read is retried after `retryDelay` — the stream that\n * would reconnect on its own does not exist yet. A retry stops on\n * `close()`, when nothing is watched (`sync` closes instead), or after\n * `maxRetries`; a wait started after that last one restarts the count.\n */\n private awaitManifest(): void\n {\n const lifetime = this.lifetime;\n\n if (this.waitingLifetime === lifetime)\n {\n return;\n }\n\n if (this.status === FAILED)\n {\n this.retries = 0;\n this.setConnection(STARTING);\n }\n\n this.waitingLifetime = lifetime;\n this.ensureManifest().then(\n () => lifetime === this.lifetime && this.resync(),\n error =>\n {\n this.reportError(error);\n\n if (lifetime === this.lifetime)\n {\n this.retryLater();\n }\n },\n );\n }\n\n /** The wait is over; `sync` opens the stream, or waits again when a closed lifetime's read was discarded. */\n private resync(): void\n {\n this.waitingLifetime = undefined;\n this.sync();\n }\n\n /**\n * Read the first snapshot again after `retryDelay`, as the SSE client\n * reconnects a dropped stream — or, with `maxRetries` spent, give up:\n * `'failed'`, no timer, no wait.\n */\n private retryLater(): void\n {\n const maxRetries = this.config.maxRetries ?? 0;\n\n if (maxRetries > 0 && this.retries >= maxRetries)\n {\n this.waitingLifetime = undefined;\n this.setConnection(FAILED);\n\n return;\n }\n\n this.retries++;\n this.retryTimer = setTimeout(() =>\n {\n this.retryTimer = undefined;\n this.resync();\n }, this.config.retryDelay ?? 3000);\n // A timer in Node has `unref`: a pending retry does not keep the process alive.\n (this.retryTimer as { unref?: () => void }).unref?.();\n }\n\n /**\n * `retry()` before the manifest: the count starts over and the first\n * snapshot is read now, its pending timer cleared. A read in flight is\n * already the retry; it is not doubled.\n */\n private rereadManifest(): void\n {\n this.retries = 0;\n\n if (this.waitingLifetime === this.lifetime && this.retryTimer === undefined)\n {\n return;\n }\n\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.setConnection(STARTING);\n this.resync();\n }\n\n /**\n * Every subscribed key's fields; one connection carries one value per\n * field, so on a conflict the latest subscribed key wins. The keys it\n * shadows are read once when the stream opens and get no frames.\n */\n private streamQuery(): StateKeyFields\n {\n return Object.assign({}, ...[...this.watched.values()].map(watch => watch.key ?? {}));\n }\n\n /**\n * Open (or reopen) the stream. `events` is every state of the manifest, in\n * its order, never only the subscribed ones: a state subscribed later would\n * otherwise be unrequested, and the server's `grant` cannot activate it.\n * Unless connected, the status is `'reconnecting'` 0 until it opens: a\n * new subscription counts its attempts from 0. A reopen on a key change\n * while connected stays `'connected'`.\n */\n private openStream(query: StateKeyFields): void\n {\n this.closeStream();\n\n if (this.status !== CONNECTED)\n {\n this.setConnection(STARTING);\n }\n\n const ids = Object.keys(this.manifest!);\n const stream: Stream = { query, text: stableKey(query), generation: 0, closedByClient: false, failed: false, close: () => undefined };\n stream.close = this.config.sse.subscribe({\n events: ids.map(stateEventName),\n handlers: Object.fromEntries(ids.map(id => [stateEventName(id), (frame: WireFrame) => this.onFrame(id, frame, stream)])),\n query,\n onOpen: () =>\n {\n stream.generation = ++this.generation;\n this.streamStatus(stream, CONNECTED);\n },\n onReconnect: attempt => this.streamStatus(stream, reconnecting(attempt)),\n onClose: () => this.onStreamClose(stream),\n onConnected: connected => this.onConnected(connected, stream),\n onSubscriptionChange: change => this.onSubscriptionChange(change, stream),\n });\n\n this.stream = stream;\n }\n\n /** Flagged first: the SSE client fires `onClose` on this teardown too, and it is no failure. */\n private closeStream(): void\n {\n if (this.stream)\n {\n this.stream.closedByClient = true;\n this.stream.close();\n this.stream = undefined;\n }\n }\n\n /** The status of a callback of `stream`, when no other stream replaced it. */\n private streamStatus(stream: Stream, status: ConnectionStatus): void\n {\n if (stream === this.stream)\n {\n this.setConnection(status);\n }\n }\n\n /**\n * The SSE client fires `onClose` on every teardown; one the client did\n * not start is the reconnect attempts exhausted: `'failed'`, and the\n * stream counts as absent for the next `sync`.\n */\n private onStreamClose(stream: Stream): void\n {\n if (!stream.closedByClient && stream === this.stream)\n {\n stream.failed = true;\n this.setConnection(FAILED);\n }\n }\n\n /**\n * Every connection — the first, a reconnect, a reopen on a key change —\n * must cover the frames sent while no stream was open. The server says in\n * `connected` which states it served their current frame right after it;\n * the snapshot is read only when one it did not serve is neither denied\n * nor served — or when the server predates `initial`. A denied state the\n * client watches is `'revoked'`, as an omission from that snapshot would\n * be. A connection already replaced is ignored.\n */\n private onConnected(connected: ConnectedPayload, stream: Stream): void\n {\n if (stream !== this.stream || stream.generation !== this.generation)\n {\n return;\n }\n\n const denied = new Set(connected.deniedEvents);\n const served = new Set(connected.initial ?? []);\n const unserved = Object.keys(this.manifest!).map(stateEventName).filter(name => !denied.has(name) && !served.has(name));\n\n this.revokeDenied(denied, stream);\n\n if (connected.initial && unserved.length === 0)\n {\n this.readShadowedKeys(stream.query);\n }\n else\n {\n this.refresh(stream.query);\n }\n }\n\n /** Each watched state the connection denies, on the key it carries, marked `'revoked'` by it. */\n private revokeDenied(denied: Set<string>, stream: Stream): void\n {\n for (const id of Object.keys(this.manifest!))\n {\n const slot = this.slot(id, this.keyIn(id, stream.query));\n\n if (denied.has(stateEventName(id)) && this.watched.has(slot))\n {\n this.store.revoke(slot, stream.generation);\n }\n }\n }\n\n /** The snapshot for the stream's query, and one read for each subscribed key the query shadows. */\n private refresh(query: StateKeyFields): void\n {\n this.readSnapshot(query, this.tag()).catch(this.reportError);\n this.readShadowedKeys(query);\n }\n\n /** One read for each subscribed key the query shadows: the stream serves one key per field. */\n private readShadowedKeys(query: StateKeyFields): void\n {\n for (const watch of this.watched.values())\n {\n if (watch.key && !carries(query, watch.key))\n {\n this.readShadowed(watch.id, watch.key, this.tag()).catch(this.reportError);\n }\n }\n }\n\n /**\n * A shadowed key gets no frames, no grant and no snapshot entry; the\n * server answering its own read is the evidence of access instead.\n */\n private async readShadowed(id: string, key: StateKeyFields, tag: ReadTag): Promise<void>\n {\n const frame = await this.api[routeName('get', id)].call({ query: key }) as StateFrame;\n this.serve(this.slot(id, key), frame, tag.generation);\n }\n\n /**\n * A frame for a key nobody here subscribed is dropped — the server's filter\n * is the first guard, this the second. A frame is served under its\n * connection's generation: the server sends one only on a connection that\n * allows the state, so a newer connection's frame lifts an older revoke.\n */\n private onFrame(id: string, frame: WireFrame, stream: Stream): void\n {\n const slot = this.slot(id, frame.key);\n\n if (this.watched.has(slot))\n {\n this.serve(slot, frame, stream.generation);\n }\n }\n\n /**\n * A revoke or a grant concerns the key the connection carries — the\n * server scopes both by the connect query — and so does the read after a\n * grant. Revoked: the entry keeps its value, marked `'revoked'`, until a\n * grant. Granted: the mark is cleared and the key read once; the server\n * sends a frame too, in either order, and both arrive `'ready'`.\n */\n private onSubscriptionChange(change: SSESubscriptionChange<any>, stream: Stream): void\n {\n const id = /^state\\.(.+)\\.changed$/.exec(change.event as string)?.[1];\n\n if (id === undefined || !this.manifest![id])\n {\n return;\n }\n\n const key = this.keyIn(id, stream.query);\n const slot = this.slot(id, key);\n\n if (change.status === 'revoked')\n {\n this.store.revoke(slot, stream.generation);\n\n return;\n }\n\n this.grants.set(slot, ++this.clock);\n this.store.allow(slot);\n\n if (this.watched.has(slot))\n {\n this.get(id, key).catch(this.reportError);\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/state/client-store.ts","../../src/state/client.ts"],"names":["notify"],"mappings":"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","file":"client.js","sourcesContent":["/**\n * State Client Store\n *\n * One frozen entry per `(id, key)` and the ordering rule that decides whether\n * an arrival — a snapshot entry, a frame, a read or a write answer — replaces\n * it. An entry is replaced only when its status, value or data changes, so\n * `current()` returns the same object until then: `useSyncExternalStore`\n * compares by reference and would re-render forever on a fresh object per\n * call. A newer `at` alone advances the slot's ordering stamp, not the entry.\n */\n\nimport type { StateEntry, StateFrame, StateListener } from './client-types';\n\n/** Key fields, flat strings by construction (`.key()` refuses other fields). */\nexport type StateKeyFields = Record<string, string>;\n\n/** A key as text with its fields sorted, so equal keys written in any order name one entry. */\nexport function stableKey(key: StateKeyFields | undefined): string\n{\n if (!key)\n {\n return '';\n }\n\n return JSON.stringify(Object.keys(key).sort().map(field => [field, key[field]]));\n}\n\n/** The inverse of `stableKey`. */\nexport function parseKey(text: string): StateKeyFields | undefined\n{\n return text ? Object.fromEntries(JSON.parse(text) as [string, string][]) : undefined;\n}\n\n/** Deep equality of JSON values, which is all a frame carries. */\nfunction sameJson(a: unknown, b: unknown): boolean\n{\n if (a === b)\n {\n return true;\n }\n\n if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null || Array.isArray(a) !== Array.isArray(b))\n {\n return false;\n }\n\n const aKeys = Object.keys(a);\n const bRecord = b as Record<string, unknown>;\n\n return aKeys.length === Object.keys(b).length\n && aKeys.every(field => field in bRecord && sameJson((a as Record<string, unknown>)[field], bRecord[field]));\n}\n\n/**\n * The ordering rule for the value: an arrival older than the slot's stamp —\n * the newest `at` seen — is dropped. An equal `at` is by design the same\n * value (one read stamps one `at`), so it is not older and `changes` decides.\n */\nfunction isOlder(stamp: string | undefined, arrival: StateFrame): boolean\n{\n return stamp !== undefined && arrival.at < stamp;\n}\n\n/**\n * Whether an arrival says something the entry does not. An entry marked\n * revoked before any value arrived has no `at`: anything changes it.\n */\nfunction changes(current: StateEntry | undefined, arrival: StateFrame): boolean\n{\n return current?.at === undefined || !sameJson(current.value, arrival.value) || !sameJson(current.data, arrival.data);\n}\n\nfunction entryOf(frame: StateFrame, status: StateStatus): StateEntry\n{\n const { at, value, data } = frame;\n\n return Object.freeze(data === undefined ? { at, value, status } : { at, value, data, status });\n}\n\ntype StateStatus = StateEntry['status'];\n\nexport class StateStore\n{\n private readonly entries = new Map<string, StateEntry>();\n\n /**\n * The newest `at` seen per slot, kept apart from the entry: an arrival\n * that changes nothing still orders the ones after it, while the entry's\n * `at` stays the `at` of the read that last changed the value.\n */\n private readonly stamps = new Map<string, string>();\n\n private readonly listeners = new Map<string, Set<StateListener>>();\n\n /**\n * The slots the server stopped serving, with the generation of the\n * connection that said so. Kept apart from the entries: a revoke can\n * arrive before the slot has a value, and an arrival afterwards supplies\n * the value without clearing the mark.\n */\n private readonly revoked = new Map<string, number>();\n\n /** The store slot of `(id, key)`. */\n static slot(id: string, keyText: string): string\n {\n return `${id}\\u0000${keyText}`;\n }\n\n current(slot: string): StateEntry | undefined\n {\n return this.entries.get(slot);\n }\n\n /**\n * Apply an arrival: the value under the ordering rule, the status from the\n * revoked mark. Listeners hear only a changed status, value or data.\n */\n apply(slot: string, frame: StateFrame): void\n {\n const current = this.entries.get(slot);\n const status: StateStatus = this.revoked.has(slot) ? 'revoked' : 'ready';\n const ordered = !isOlder(this.stamps.get(slot), frame);\n\n if (ordered)\n {\n this.stamps.set(slot, frame.at);\n }\n\n if (ordered && changes(current, frame))\n {\n this.replace(slot, entryOf(frame, status));\n }\n else if (current!.status !== status)\n {\n // Not applied, so the current entry has a value: only its status changes.\n this.replace(slot, entryOf(current as StateFrame, status));\n }\n }\n\n /** Mark `slot` revoked by the connection of `generation`; its value, if any, is kept. */\n revoke(slot: string, generation: number): void\n {\n this.revoked.set(slot, generation);\n const entry = this.entries.get(slot);\n\n if (entry?.status !== 'revoked')\n {\n this.replace(slot, Object.freeze(entry ? { ...entry, status: 'revoked' as const } : { status: 'revoked' as const }));\n }\n }\n\n /** The generation of the connection that revoked `slot`, or `undefined` while it is served. */\n revokedBy(slot: string): number | undefined\n {\n return this.revoked.get(slot);\n }\n\n /** Clear the revoked mark; the next arrival for `slot` is `'ready'`. */\n allow(slot: string): void\n {\n this.revoked.delete(slot);\n }\n\n listen(slot: string, listener: StateListener): void\n {\n const set = this.listeners.get(slot) ?? new Set();\n set.add(listener);\n this.listeners.set(slot, set);\n }\n\n unlisten(slot: string, listener?: StateListener): void\n {\n const set = this.listeners.get(slot);\n\n if (listener)\n {\n set?.delete(listener);\n }\n\n if (!listener || set?.size === 0)\n {\n this.listeners.delete(slot);\n }\n }\n\n clearListeners(): void\n {\n this.listeners.clear();\n }\n\n /** Replace the entry and notify; a throwing listener is logged and does not stop the others. */\n private replace(slot: string, entry: StateEntry): void\n {\n this.entries.set(slot, entry);\n\n for (const listener of [...this.listeners.get(slot) ?? []])\n {\n notify(slot, listener);\n }\n }\n}\n\nfunction notify(slot: string, listener: StateListener): void\n{\n try\n {\n listener();\n }\n catch (error)\n {\n // The state id only: the key is the app's data.\n console.warn(`[state] A listener of state \"${slot.split('\\u0000')[0]}\" threw:`, error);\n }\n}\n","/**\n * State Client\n *\n * The browser side of a state router (`@spfn/core/state`): it calls the derived\n * read, write and snapshot routes, keeps one SSE stream open for every state of\n * the router, and orders snapshots against frames per `(id, key)`. It knows the\n * router by type — `typeof states` — and at runtime by the manifest every\n * snapshot carries; it imports no server code.\n *\n * @example\n * ```typescript\n * import { createStateClient } from '@spfn/core/state/client';\n * import { createAuthSSEClient } from '@spfn/core/event/sse/client';\n * import type { states } from '@/server/states';\n *\n * export const state = createStateClient<typeof states>({ sse: createAuthSSEClient() });\n *\n * const stop = state.subscribe('runtime', { workspaceId }, () => render(state.current('runtime', { workspaceId })));\n * ```\n */\n\nimport type { ConnectedPayload, SSESubscriptionChange } from '@spfn/core/event/sse';\nimport { createApi } from '@spfn/core/nextjs';\nimport { stableKey, StateStore, type StateKeyFields } from './client-store';\nimport type {\n AnyStateRouter,\n ConnectionListener,\n ConnectionStatus,\n StateClient,\n StateClientConfig,\n StateClientInitial,\n StateEntry,\n StateFrame,\n StateListener,\n} from './client-types';\n\nexport type {\n AnyStateRouter,\n ConnectionListener,\n ConnectionStatus,\n InferStateData,\n StateCatalog,\n StateFrameOf,\n InferStateInput,\n InferStateKey,\n InferStateValue,\n StateClient,\n StateClientConfig,\n StateClientInitial,\n StateEntry,\n StateFrame,\n StateId,\n StateKeyArgs,\n StateListener,\n WritableStateId,\n} from './client-types';\n\n/** A frame as the SSE event carries it. */\ntype WireFrame = StateFrame & { key?: StateKeyFields; subject?: string };\n\n/** Every state of the router, in declaration order: its key fields and whether it has a write. */\ntype Manifest = Record<string, { key: string[]; write: boolean }>;\n\ninterface Snapshot\n{\n at: string;\n states: Record<string, { value: unknown; data?: unknown }>;\n manifest: Manifest;\n}\n\ntype RouteCalls = Record<string, { call(input: object): Promise<unknown> }>;\n\n/** One `(id, key)` someone watches, and how many subscriptions hold it. */\ninterface Watch\n{\n id: string;\n key: StateKeyFields | undefined;\n count: number;\n}\n\n/**\n * The stream as one `sse.subscribe` opened it; `generation` names its current\n * connection. `closedByClient`: the client closed it itself, so its `onClose`\n * is no failure; `failed`: the SSE client gave up on it.\n */\ninterface Stream\n{\n query: StateKeyFields;\n text: string;\n generation: number;\n closedByClient: boolean;\n failed: boolean;\n close: () => void;\n}\n\n/** When an automatic read was started: the connection generation and the client's clock. */\ninterface ReadTag\n{\n generation: number;\n tick: number;\n}\n\nconst CONNECTED: ConnectionStatus = Object.freeze({ status: 'connected' });\nconst FAILED: ConnectionStatus = Object.freeze({ status: 'failed' });\nconst STARTING: ConnectionStatus = Object.freeze({ status: 'reconnecting', attempt: 0 });\n\nfunction reconnecting(attempt: number): ConnectionStatus\n{\n return attempt === 0 ? STARTING : Object.freeze({ status: 'reconnecting', attempt });\n}\n\nfunction sameStatus(a: ConnectionStatus, b: ConnectionStatus): boolean\n{\n return a.status === b.status && (a as { attempt?: number }).attempt === (b as { attempt?: number }).attempt;\n}\n\n/** `get<Id>State` / `put<Id>State`, the names the server router derives. */\nfunction routeName(method: 'get' | 'put', id: string): string\n{\n return `${method}${id[0].toUpperCase()}${id.slice(1)}State`;\n}\n\n/** The wire name of a state's SSE event, as the server derives it. */\nfunction stateEventName(id: string): string\n{\n return `state.${id}.changed`;\n}\n\n/** Whether `query` carries every field of `key` with its value. */\nfunction carries(query: StateKeyFields, key: StateKeyFields): boolean\n{\n return Object.entries(key).every(([field, value]) => query[field] === value);\n}\n\nfunction sortedFields(fields: string[]): string\n{\n return [...fields].sort().join(', ');\n}\n\n/**\n * Build the client for `typeof states`. The first subscription (or call)\n * reads the snapshot, whose manifest names every state — or takes it from\n * `initial`, the snapshot the server rendered with; the stream then\n * requests every state event — the server's `grant` can only activate an\n * event the connection requested. The router type parameter is what types\n * `initial.snapshot`; without it the client is untyped, as before.\n */\nexport function createStateClient<TStates extends AnyStateRouter>(config: StateClientConfig<TStates>): StateClient<TStates>\n{\n return new BrowserStateClient(config) as unknown as StateClient<TStates>;\n}\n\nclass BrowserStateClient\n{\n private readonly store = new StateStore();\n private readonly api: RouteCalls;\n private readonly watched = new Map<string, Watch>();\n private readonly reportError: (error: unknown) => void;\n private manifest: Manifest | undefined;\n private manifestRead: Promise<void> | undefined;\n private stream: Stream | undefined;\n private syncQueued = false;\n\n private status = STARTING;\n private readonly connectionListeners = new Set<ConnectionListener>();\n private readonly onOnline = (): void => this.retry();\n\n /** The retries of the first snapshot since its last start, against `maxRetries`. */\n private retries = 0;\n\n /** The lifetime whose subscriptions wait on the first snapshot — its read in flight, or its retry timer set. */\n private waitingLifetime: number | undefined;\n private retryTimer: ReturnType<typeof setTimeout> | undefined;\n\n /** Bumped by `close()`: a snapshot started before it is discarded. */\n private lifetime = 0;\n\n /** Bumped on every connection open; tags the snapshot read for it. */\n private generation = 0;\n\n /** Bumped on every automatic read and every grant, to tell which came first. */\n private clock = 0;\n\n /** The clock at the latest grant of each slot. */\n private readonly grants = new Map<string, number>();\n\n constructor(private readonly config: StateClientConfig)\n {\n this.api = createApi<any>(config.api) as unknown as RouteCalls;\n this.reportError = config.onError ?? (error => console.error('[state] A refresh failed:', error));\n\n if (config.initial)\n {\n this.hydrate(config.initial);\n }\n\n // Absent in Node and in a server render.\n if (typeof globalThis.addEventListener === 'function')\n {\n globalThis.addEventListener('online', this.onOnline);\n }\n }\n\n async get(id: string, key?: StateKeyFields): Promise<StateFrame>\n {\n await this.requireManifest();\n const frame = await this.api[routeName('get', id)].call(key ? { query: key } : {}) as StateFrame;\n this.store.apply(this.slot(id, key), frame);\n\n return frame;\n }\n\n async write(id: string, input: unknown, key?: StateKeyFields): Promise<StateFrame>\n {\n if (!(await this.requireManifest())[id]?.write)\n {\n throw new Error(`State client: state \"${id}\" declares no .write(), so it has no write route.`);\n }\n\n const frame = await this.api[routeName('put', id)].call({ body: input, ...(key ? { query: key } : {}) }) as StateFrame;\n this.store.apply(this.slot(id, key), frame);\n\n return frame;\n }\n\n snapshot(query: StateKeyFields = this.streamQuery()): Promise<Snapshot>\n {\n return this.readSnapshot(query, this.tag());\n }\n\n subscribe(id: string, ...args: [StateListener] | [StateKeyFields, StateListener]): () => void\n {\n const listener = args[args.length - 1] as StateListener;\n const key = args.length === 2 ? args[0] as StateKeyFields : undefined;\n const refusal = this.manifest && this.refusal(id, key);\n\n if (refusal)\n {\n this.reportError(refusal);\n\n return () => undefined;\n }\n\n // One wrapper per subscription: the same listener subscribed twice is two subscriptions.\n const notify = () => listener();\n const slot = this.slot(id, key);\n this.store.listen(slot, notify);\n this.watch(slot, id, key, 1);\n let live = true;\n\n return () =>\n {\n if (live)\n {\n live = false;\n this.store.unlisten(slot, notify);\n this.watch(slot, id, key, -1);\n }\n };\n }\n\n current(id: string, key?: StateKeyFields): StateEntry | undefined\n {\n return this.store.current(this.slot(id, key));\n }\n\n connection(): ConnectionStatus\n {\n return this.status;\n }\n\n subscribeConnection(listener: ConnectionListener): () => void\n {\n // One wrapper per subscription: the same listener subscribed twice is two subscriptions.\n const notify: ConnectionListener = status => listener(status);\n this.connectionListeners.add(notify);\n\n return () =>\n {\n this.connectionListeners.delete(notify);\n };\n }\n\n /**\n * Restart what gave up or is still retrying, now: the first snapshot\n * while the manifest is unknown, else the stream — a new `sse.subscribe`,\n * whose attempt count starts at 0. Nothing to do when connected or with\n * nothing watched — so after `close()`, until a new `subscribe`.\n */\n retry(): void\n {\n if (this.status === CONNECTED || this.watched.size === 0)\n {\n return;\n }\n\n if (this.manifest)\n {\n this.openStream(this.streamQuery());\n }\n else\n {\n this.rereadManifest();\n }\n }\n\n close(): void\n {\n this.lifetime++;\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.retries = 0;\n this.closeStream();\n this.watched.clear();\n this.store.clearListeners();\n this.connectionListeners.clear();\n this.status = STARTING;\n\n if (typeof globalThis.removeEventListener === 'function')\n {\n globalThis.removeEventListener('online', this.onOnline);\n }\n }\n\n /** The status is `next` from now; each connection listener hears it, a throwing one logged. */\n private setConnection(next: ConnectionStatus): void\n {\n if (sameStatus(next, this.status))\n {\n return;\n }\n\n this.status = next;\n\n for (const listener of [...this.connectionListeners])\n {\n try\n {\n listener(next);\n }\n catch (error)\n {\n console.warn('[state] A connection listener threw:', error);\n }\n }\n }\n\n /**\n * Start from the snapshot the server rendered with: its manifest, then\n * each state present, as a read snapshot applies them, under generation 0\n * — no connection yet. Nothing is marked `'revoked'`: nothing is watched,\n * and an omission in the render's snapshot is not a denial by the stream.\n */\n private hydrate(initial: StateClientInitial): void\n {\n const snapshot = initial.snapshot as Snapshot;\n this.installManifest(snapshot.manifest);\n this.applyPresent(snapshot, initial.query ?? {}, this.tag());\n }\n\n private slot(id: string, key: StateKeyFields | undefined): string\n {\n return StateStore.slot(id, stableKey(key));\n }\n\n private tag(): ReadTag\n {\n return { generation: this.generation, tick: ++this.clock };\n }\n\n /** The key of state `id` the query names; `undefined` for a keyless state. */\n private keyIn(id: string, query: StateKeyFields): StateKeyFields | undefined\n {\n const fields = this.manifest![id].key;\n\n return fields.length ? Object.fromEntries(fields.map(field => [field, query[field]])) : undefined;\n }\n\n /** The slot of state `id` on the key `query` carries; `undefined` when the query lacks one of its key fields. */\n private carriedSlot(id: string, query: StateKeyFields): string | undefined\n {\n return this.manifest![id].key.every(field => field in query) ? this.slot(id, this.keyIn(id, query)) : undefined;\n }\n\n /** Why the manifest refuses `subscribe(id, key)` — an unknown id, or key fields not the state's. */\n private refusal(id: string, key: StateKeyFields | undefined): Error | undefined\n {\n const state = this.manifest![id];\n\n if (!state)\n {\n return new Error(`State client: the server's manifest has no state \"${id}\".`);\n }\n\n const expected = sortedFields(state.key);\n const given = sortedFields(Object.keys(key ?? {}));\n\n return expected === given\n ? undefined\n : new Error(`State client: state \"${id}\" takes the key fields [${expected}], given [${given}].`);\n }\n\n /** The first snapshot, read once however many callers wait for it. */\n private ensureManifest(): Promise<void>\n {\n if (this.manifest)\n {\n return Promise.resolve();\n }\n\n this.manifestRead ??= this.snapshot()\n .then(() => undefined)\n .finally(() =>\n {\n this.manifestRead = undefined;\n });\n\n return this.manifestRead;\n }\n\n /** The manifest for a call the app made; one whose wait outlived `close()` is refused. */\n private async requireManifest(): Promise<Manifest>\n {\n await this.ensureManifest();\n\n if (!this.manifest)\n {\n throw new Error('State client: the client was closed while the first snapshot was in flight.');\n }\n\n return this.manifest;\n }\n\n /**\n * Read the snapshot and apply it: the manifest first — `keyIn` and the\n * key checks need it — then each state present, then each watched state\n * it omits. A snapshot that resolves after `close()` is discarded.\n */\n private async readSnapshot(query: StateKeyFields, tag: ReadTag): Promise<Snapshot>\n {\n const lifetime = this.lifetime;\n const snapshot = await this.api.getStates.call({ query }) as Snapshot;\n\n if (lifetime === this.lifetime)\n {\n this.installManifest(snapshot.manifest);\n this.applyPresent(snapshot, query, tag);\n this.applyOmitted(snapshot, query, tag);\n }\n\n return snapshot;\n }\n\n /**\n * The manifest is static; the first one stays. Subscriptions made before\n * it are checked now, and the stream opens for those it accepts —\n * whichever call (`subscribe`, `get`, `write`, `snapshot`) brought it.\n */\n private installManifest(manifest: Manifest): void\n {\n if (this.manifest)\n {\n return;\n }\n\n this.manifest = manifest;\n\n for (const [slot, watch] of this.watched)\n {\n const refusal = this.refusal(watch.id, watch.key);\n\n if (refusal)\n {\n this.watched.delete(slot);\n this.store.unlisten(slot);\n this.reportError(refusal);\n }\n }\n\n if (this.watched.size > 0)\n {\n this.queueSync();\n }\n }\n\n /**\n * An arrival from a read started for the connection of `tag`: a\n * connection newer than the one that revoked the slot lifts the mark, an\n * older or the same connection leaves `'revoked'`. The lift is by\n * generation alone — a frame or a write answer of the new connection may\n * land first with a newer `at` — and the ordering rule decides only the value.\n */\n private serve(slot: string, frame: StateFrame, generation: number): void\n {\n if ((this.store.revokedBy(slot) ?? Infinity) < generation)\n {\n this.store.allow(slot);\n }\n\n this.store.apply(slot, frame);\n }\n\n /**\n * A state present is served, under the generation rule of `serve`. A state\n * the manifest does not describe, or a keyed one whose key fields the query\n * lacks, is skipped: the server never serves either, but a hand-built\n * `initial` might.\n */\n private applyPresent(snapshot: Snapshot, query: StateKeyFields, tag: ReadTag): void\n {\n for (const [id, result] of Object.entries(snapshot.states))\n {\n if (this.manifest![id]?.key.every(field => field in query))\n {\n this.serve(this.slot(id, this.keyIn(id, query)), { at: snapshot.at, ...result }, tag.generation);\n }\n }\n }\n\n /**\n * Every state the snapshot omits whose key the query carried (or a keyless\n * one), watched or not, is not served to this subject: `'revoked'` on that\n * key. Not when a newer connection exists, nor when a grant came after the\n * read started. Every watched slot the query carries is one of these.\n */\n private applyOmitted(snapshot: Snapshot, query: StateKeyFields, tag: ReadTag): void\n {\n if (tag.generation !== this.generation)\n {\n return;\n }\n\n for (const id of Object.keys(this.manifest!))\n {\n const slot = this.carriedSlot(id, query);\n const grantedSince = slot !== undefined && (this.grants.get(slot) ?? 0) > tag.tick;\n\n if (slot !== undefined && !(id in snapshot.states) && !grantedSince)\n {\n this.store.revoke(slot, tag.generation);\n }\n }\n }\n\n private watch(slot: string, id: string, key: StateKeyFields | undefined, delta: 1 | -1): void\n {\n const count = (this.watched.get(slot)?.count ?? 0) + delta;\n\n if (count > 0)\n {\n this.watched.set(slot, { id, key, count });\n }\n else\n {\n this.watched.delete(slot);\n }\n\n // A removal that leaves subscribers keeps a failed client failed; the last removal closes and resets.\n if (delta === 1 || this.watched.size === 0 || this.status !== FAILED)\n {\n this.queueSync();\n }\n }\n\n /**\n * One stream for the client, its query the union of the subscribed keys.\n * Batched to the end of the task, so components mounting together (or a\n * StrictMode unmount-remount) open the stream once.\n */\n private queueSync(): void\n {\n if (!this.syncQueued)\n {\n this.syncQueued = true;\n queueMicrotask(() =>\n {\n this.syncQueued = false;\n this.sync();\n });\n }\n }\n\n /**\n * Until the manifest is known, subscriptions wait for the first snapshot;\n * the stream opens after it, never before — its `events=` comes from it.\n * A client built with `initial` knows it from the start. With nothing\n * watched the stream closes, the retry count starts over for the next\n * wait, and the status is the start value: no stream, nothing answered.\n */\n private sync(): void\n {\n if (this.watched.size === 0)\n {\n this.retries = 0;\n this.closeStream();\n this.setConnection(STARTING);\n\n return;\n }\n\n if (!this.manifest)\n {\n this.awaitManifest();\n\n return;\n }\n\n // A stream the SSE client gave up on counts as absent.\n const query = this.streamQuery();\n\n if (this.stream?.failed || this.stream?.text !== stableKey(query))\n {\n this.openStream(query);\n }\n }\n\n /**\n * One waiter per lifetime on the first snapshot. A failure goes to\n * `onError`, and the read is retried after `retryDelay` — the stream that\n * would reconnect on its own does not exist yet. A retry stops on\n * `close()`, when nothing is watched (`sync` closes instead), or after\n * `maxRetries`; a wait started after that last one restarts the count.\n */\n private awaitManifest(): void\n {\n const lifetime = this.lifetime;\n\n if (this.waitingLifetime === lifetime)\n {\n return;\n }\n\n if (this.status === FAILED)\n {\n this.retries = 0;\n this.setConnection(STARTING);\n }\n\n this.waitingLifetime = lifetime;\n this.ensureManifest().then(\n () => lifetime === this.lifetime && this.resync(),\n error =>\n {\n this.reportError(error);\n\n if (lifetime === this.lifetime)\n {\n this.retryLater();\n }\n },\n );\n }\n\n /** The wait is over; `sync` opens the stream, or waits again when a closed lifetime's read was discarded. */\n private resync(): void\n {\n this.waitingLifetime = undefined;\n this.sync();\n }\n\n /**\n * Read the first snapshot again after `retryDelay`, as the SSE client\n * reconnects a dropped stream — or, with `maxRetries` spent, give up:\n * `'failed'`, no timer, no wait.\n */\n private retryLater(): void\n {\n const maxRetries = this.config.maxRetries ?? 0;\n\n if (maxRetries > 0 && this.retries >= maxRetries)\n {\n this.waitingLifetime = undefined;\n this.setConnection(FAILED);\n\n return;\n }\n\n this.retries++;\n this.retryTimer = setTimeout(() =>\n {\n this.retryTimer = undefined;\n this.resync();\n }, this.config.retryDelay ?? 3000);\n // A timer in Node has `unref`: a pending retry does not keep the process alive.\n (this.retryTimer as { unref?: () => void }).unref?.();\n }\n\n /**\n * `retry()` before the manifest: the count starts over and the first\n * snapshot is read now, its pending timer cleared. A read in flight is\n * already the retry; it is not doubled.\n */\n private rereadManifest(): void\n {\n this.retries = 0;\n\n if (this.waitingLifetime === this.lifetime && this.retryTimer === undefined)\n {\n return;\n }\n\n clearTimeout(this.retryTimer);\n this.retryTimer = undefined;\n this.setConnection(STARTING);\n this.resync();\n }\n\n /**\n * Every subscribed key's fields; one connection carries one value per\n * field, so on a conflict the latest subscribed key wins. The keys it\n * shadows are read once when the stream opens and get no frames.\n */\n private streamQuery(): StateKeyFields\n {\n return Object.assign({}, ...[...this.watched.values()].map(watch => watch.key ?? {}));\n }\n\n /**\n * Open (or reopen) the stream. `events` is every state of the manifest, in\n * its order, never only the subscribed ones: a state subscribed later would\n * otherwise be unrequested, and the server's `grant` cannot activate it.\n * Unless connected, the status is `'reconnecting'` 0 until it opens: a\n * new subscription counts its attempts from 0. A reopen on a key change\n * while connected stays `'connected'`.\n */\n private openStream(query: StateKeyFields): void\n {\n this.closeStream();\n\n if (this.status !== CONNECTED)\n {\n this.setConnection(STARTING);\n }\n\n const ids = Object.keys(this.manifest!);\n const stream: Stream = { query, text: stableKey(query), generation: 0, closedByClient: false, failed: false, close: () => undefined };\n stream.close = this.config.sse.subscribe({\n events: ids.map(stateEventName),\n handlers: Object.fromEntries(ids.map(id => [stateEventName(id), (frame: WireFrame) => this.onFrame(id, frame, stream)])),\n query,\n onOpen: () =>\n {\n stream.generation = ++this.generation;\n this.streamStatus(stream, CONNECTED);\n },\n onReconnect: attempt => this.streamStatus(stream, reconnecting(attempt)),\n onClose: () => this.onStreamClose(stream),\n onConnected: connected => this.onConnected(connected, stream),\n onSubscriptionChange: change => this.onSubscriptionChange(change, stream),\n });\n\n this.stream = stream;\n }\n\n /** Flagged first: the SSE client fires `onClose` on this teardown too, and it is no failure. */\n private closeStream(): void\n {\n if (this.stream)\n {\n this.stream.closedByClient = true;\n this.stream.close();\n this.stream = undefined;\n }\n }\n\n /** The status of a callback of `stream`, when no other stream replaced it. */\n private streamStatus(stream: Stream, status: ConnectionStatus): void\n {\n if (stream === this.stream)\n {\n this.setConnection(status);\n }\n }\n\n /**\n * The SSE client fires `onClose` on every teardown; one the client did\n * not start is the reconnect attempts exhausted: `'failed'`, and the\n * stream counts as absent for the next `sync`.\n */\n private onStreamClose(stream: Stream): void\n {\n if (!stream.closedByClient && stream === this.stream)\n {\n stream.failed = true;\n this.setConnection(FAILED);\n }\n }\n\n /**\n * Every connection — the first, a reconnect, a reopen on a key change —\n * must cover the frames sent while no stream was open. The server says in\n * `connected` which states it served their current frame right after it;\n * the snapshot is read only when one it did not serve is neither denied\n * nor served — or when the server predates `initial`. A denied state whose\n * key the stream carries is `'revoked'`, watched or not, as an omission\n * from that snapshot would be. A connection already replaced is ignored.\n */\n private onConnected(connected: ConnectedPayload, stream: Stream): void\n {\n if (stream !== this.stream || stream.generation !== this.generation)\n {\n return;\n }\n\n const denied = new Set(connected.deniedEvents);\n const served = new Set(connected.initial ?? []);\n const unserved = Object.keys(this.manifest!).map(stateEventName).filter(name => !denied.has(name) && !served.has(name));\n\n this.revokeDenied(denied, stream);\n\n if (connected.initial && unserved.length === 0)\n {\n this.readShadowedKeys(stream.query);\n }\n else\n {\n this.refresh(stream.query);\n }\n }\n\n /**\n * Each state the connection denies, on the key its query carries, marked\n * `'revoked'` by it, watched or not. A keyed state denied because the query\n * lacks one of its key fields has no carried key, so nothing is marked.\n */\n private revokeDenied(denied: Set<string>, stream: Stream): void\n {\n for (const id of Object.keys(this.manifest!))\n {\n const slot = this.carriedSlot(id, stream.query);\n\n if (slot !== undefined && denied.has(stateEventName(id)))\n {\n this.store.revoke(slot, stream.generation);\n }\n }\n }\n\n /** The snapshot for the stream's query, and one read for each subscribed key the query shadows. */\n private refresh(query: StateKeyFields): void\n {\n this.readSnapshot(query, this.tag()).catch(this.reportError);\n this.readShadowedKeys(query);\n }\n\n /** One read for each subscribed key the query shadows: the stream serves one key per field. */\n private readShadowedKeys(query: StateKeyFields): void\n {\n for (const watch of this.watched.values())\n {\n if (watch.key && !carries(query, watch.key))\n {\n this.readShadowed(watch.id, watch.key, this.tag()).catch(this.reportError);\n }\n }\n }\n\n /**\n * A shadowed key gets no frames, no grant and no snapshot entry; the\n * server answering its own read is the evidence of access instead.\n */\n private async readShadowed(id: string, key: StateKeyFields, tag: ReadTag): Promise<void>\n {\n const frame = await this.api[routeName('get', id)].call({ query: key }) as StateFrame;\n this.serve(this.slot(id, key), frame, tag.generation);\n }\n\n /**\n * A frame on the key the stream's query carries is applied, watched or not,\n * so a later subscribe that does not reopen the stream is served the newest\n * value; a frame for any other key is dropped unless watched — the server's\n * filter is the first guard, this the second. A frame is served under its\n * connection's generation: the server sends one only on a connection that\n * allows the state, so a newer connection's frame lifts an older revoke.\n */\n private onFrame(id: string, frame: WireFrame, stream: Stream): void\n {\n const slot = this.slot(id, frame.key);\n\n if (slot === this.carriedSlot(id, stream.query) || this.watched.has(slot))\n {\n this.serve(slot, frame, stream.generation);\n }\n }\n\n /**\n * A revoke or a grant concerns the key the connection carries — the\n * server scopes both by the connect query — and so does the read after a\n * grant. Revoked: the entry keeps its value, marked `'revoked'`, until a\n * grant. Granted: the mark is cleared and the key read once; the server\n * sends a frame too, in either order, and both arrive `'ready'`.\n */\n private onSubscriptionChange(change: SSESubscriptionChange<any>, stream: Stream): void\n {\n const id = /^state\\.(.+)\\.changed$/.exec(change.event as string)?.[1];\n\n if (id === undefined || !this.manifest![id])\n {\n return;\n }\n\n const key = this.keyIn(id, stream.query);\n const slot = this.slot(id, key);\n\n if (change.status === 'revoked')\n {\n this.store.revoke(slot, stream.generation);\n\n return;\n }\n\n this.grants.set(slot, ++this.clock);\n this.store.allow(slot);\n\n if (this.watched.has(slot))\n {\n this.get(id, key).catch(this.reportError);\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfn/core",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.32",
|
|
4
4
|
"description": "Full-stack TypeScript backend for Next.js: file-based typed routes, Drizzle entities and repositories, PostgreSQL transactions and a generated end-to-end client, in one fixed vertical slice per feature",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -214,7 +214,7 @@
|
|
|
214
214
|
},
|
|
215
215
|
"homepage": "https://github.com/fxylabs/spfn/tree/main/packages/core#readme",
|
|
216
216
|
"engines": {
|
|
217
|
-
"node": ">=
|
|
217
|
+
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
|
|
218
218
|
},
|
|
219
219
|
"dependencies": {
|
|
220
220
|
"@hono/node-server": "^2.0.5",
|
|
@@ -223,14 +223,14 @@
|
|
|
223
223
|
"chokidar": "^4.0.3",
|
|
224
224
|
"dotenv": "^17.2.3",
|
|
225
225
|
"drizzle-typebox": "^0.3.3",
|
|
226
|
-
"hono": "^4.13.
|
|
226
|
+
"hono": "^4.13.11",
|
|
227
227
|
"jiti": "^2.6.1",
|
|
228
228
|
"micromatch": "^4.0.8",
|
|
229
229
|
"pg-boss": "^11.1.2",
|
|
230
230
|
"postgres": "^3.4.0",
|
|
231
231
|
"typescript": "^5.3.3",
|
|
232
|
-
"undici": "^7.
|
|
233
|
-
"zod": "^4.
|
|
232
|
+
"undici": "^7.30.1",
|
|
233
|
+
"zod": "^4.6.6"
|
|
234
234
|
},
|
|
235
235
|
"optionalDependencies": {
|
|
236
236
|
"ioredis": "^5.4.1",
|
|
@@ -248,7 +248,7 @@
|
|
|
248
248
|
"drizzle-orm": "1.0.0-rc.4",
|
|
249
249
|
"jsdom": "^27.0.0",
|
|
250
250
|
"madge": "^8.0.0",
|
|
251
|
-
"next": "^16.
|
|
251
|
+
"next": "^16.4.1",
|
|
252
252
|
"react": "^19.2.1",
|
|
253
253
|
"react-dom": "^19.2.1",
|
|
254
254
|
"tsup": "^8.5.0",
|
|
@@ -256,7 +256,7 @@
|
|
|
256
256
|
},
|
|
257
257
|
"peerDependencies": {
|
|
258
258
|
"drizzle-orm": ">=1.0.0-rc.4 <2",
|
|
259
|
-
"next": "^16.
|
|
259
|
+
"next": "^16.4.1",
|
|
260
260
|
"react": "^18.0.0 || ^19.0.0"
|
|
261
261
|
},
|
|
262
262
|
"peerDependenciesMeta": {
|
package/src/state/README.md
CHANGED
|
@@ -602,7 +602,7 @@ export function Shell({ initial, workspaceId }: { initial: StateClientInitial<ty
|
|
|
602
602
|
| Member | Behaviour |
|
|
603
603
|
| --- | --- |
|
|
604
604
|
| `get(id, key?)` | `GET` `get<Id>State`, key fields in the query. The answer is applied under the ordering rule and returned |
|
|
605
|
-
| `snapshot(query?)` | `GET getStates`, by default with the stream's query. Each state in the answer is applied, and each
|
|
605
|
+
| `snapshot(query?)` | `GET getStates`, by default with the stream's query. Each state in the answer is applied, and each state it omits whose key the query carries — subscribed or not — is marked `'revoked'` (below); the snapshot is returned. A rejected fetch rejects and changes nothing |
|
|
606
606
|
| `subscribe(id, key?, listener)` | Registers the listener for `(id, key)` and returns its unsubscribe (idempotent). The listener is called when the entry's `status`, `value` or `data` changes — never for an arrival that differs only by a newer `at` (Ordering, below). The client's first subscriber reads the snapshot, then opens the stream (below) |
|
|
607
607
|
| `write(id, input, key?)` | `put<Id>State` with the input as the body, key fields in the query. The answer is applied at once — before the frame the write also sends — and returned. Typed only for a state with a write, and requires the key on a keyed one |
|
|
608
608
|
| `current(id, key?)` | Synchronous: `{ at, value, data?, status }`, `{ status: 'revoked' }` for a state revoked before any value arrived, or `undefined`. The same frozen object until a change is applied |
|
|
@@ -635,7 +635,12 @@ entry, and compares each arrival against it.
|
|
|
635
635
|
| `at` older than the stamp | dropped |
|
|
636
636
|
| a snapshot older than one state's stamp | that state keeps its entry; the others take the snapshot |
|
|
637
637
|
| a write answer | applied like a frame — its own frame, arriving later with the same `at` and value, changes nothing |
|
|
638
|
-
| a frame for a key not
|
|
638
|
+
| a frame for a key the stream's query does not carry | dropped (the server already filters by the connect query; this is the second guard) |
|
|
639
|
+
|
|
640
|
+
Frames for every state the stream's query carries — keyless, or keyed on the key the query carries
|
|
641
|
+
— are applied whether or not anything here subscribes to them. A later subscribe that does not
|
|
642
|
+
reopen the stream (a keyless state, or a key the query already carries) is then served the newest
|
|
643
|
+
value — never the older snapshot, never nothing.
|
|
639
644
|
|
|
640
645
|
Every stream open — the first, a reconnect, a reopen on a key change — brings each state's
|
|
641
646
|
current value with a newer `at`: the stream's initial frame, or the snapshot re-read when the
|
|
@@ -671,7 +676,7 @@ changed.
|
|
|
671
676
|
ordering rule, so whatever changed between the render and the open arrives as a newer `at`.
|
|
672
677
|
The client reads the snapshot only when a state it requested is neither in `initial` nor in
|
|
673
678
|
`deniedEvents` — a state whose read threw on the server — or when `initial` is absent (a
|
|
674
|
-
server before it); a state in `deniedEvents`
|
|
679
|
+
server before it); a state in `deniedEvents` is `'revoked'` on the carried key, watched or not.
|
|
675
680
|
(It all runs on the stream's `connected`, not at `onReconnect`: a read before the new stream
|
|
676
681
|
is live would leave the same gap.)
|
|
677
682
|
- **One value per key field per connection.** A connect query carries one `workspaceId`; when
|
|
@@ -737,8 +742,8 @@ value has arrived yet.
|
|
|
737
742
|
| Arrival | Client |
|
|
738
743
|
| --- | --- |
|
|
739
744
|
| `subscription.revoked` for `state.<id>.changed` | the entry of `id` for **the key the connection carries** (keyless: its one entry) gets `status: 'revoked'`, value kept, listeners notified. The server scopes a revoke by the connect query, so a shadowed key of the same state is untouched |
|
|
740
|
-
| `connected.deniedEvents` names a
|
|
741
|
-
| a snapshot that omits a
|
|
745
|
+
| `connected.deniedEvents` names a state — keyless, or keyed on the key the connection carries — subscribed or not | `'revoked'`, value kept, listeners notified: the connect check (`canRead` false) denied it. A state denied because the query lacks one of its key fields is not marked under any key; a shadowed key of the same state is untouched |
|
|
746
|
+
| a snapshot that omits a state — keyless, or keyed with its key in the query — subscribed or not | `'revoked'`, value kept if one exists, listeners notified. This is also a state denied at connect (`canRead` false, absent from the snapshot), and a reconnect after access was lost while the stream was down, when no revoke could arrive. A keyed state whose key the query did not carry is not marked |
|
|
742
747
|
| a frame, a read or a snapshot started before the revoke | supplies its value under the ordering rule; the status stays `'revoked'` |
|
|
743
748
|
| `subscription.granted` for it | clears the mark for the carried key and reads it once (`get`); the server also sends a frame, before or after the control frame. Both arrive `'ready'`, and either order ends at the latest value |
|
|
744
749
|
| a frame — the initial one included — or the snapshot of a connection opened after the revoke, holding the state | clears the mark: that connection's own connect check served it |
|