experimental-a2 0.4.0 → 0.5.0
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/CHANGELOG.md +51 -0
- package/dist/{ai-B4YhEnfw.d.ts → ai-CFNeCrRl.d.ts} +3 -2
- package/dist/ai-CFNeCrRl.d.ts.map +1 -0
- package/dist/ai-server.browser.js +2 -0
- package/dist/ai-server.browser.js.map +1 -0
- package/dist/ai-server.d.ts +4 -3
- package/dist/ai-server.d.ts.map +1 -0
- package/dist/ai-server.js +4 -2
- package/dist/ai-server.js.map +1 -0
- package/dist/ai.d.ts +1 -1
- package/dist/ai.js +3 -1
- package/dist/ai.js.map +1 -0
- package/dist/cli-B3VuxoDe.js +2 -0
- package/dist/cli-B3VuxoDe.js.map +1 -0
- package/dist/cli-bin.js +2 -0
- package/dist/cli-bin.js.map +1 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -0
- package/dist/{client-Bt4tAKi9.js → client-BKlyLiOU.js} +295 -85
- package/dist/client-BKlyLiOU.js.map +1 -0
- package/dist/{client-BrfDXQ8A.d.ts → client-D7mvIXrF.d.ts} +40 -4
- package/dist/client-D7mvIXrF.d.ts.map +1 -0
- package/dist/client.d.ts +2 -2
- package/dist/client.js +1 -1
- package/dist/contract-48bUMgcL.js +2 -0
- package/dist/contract-48bUMgcL.js.map +1 -0
- package/dist/contract-jIfaR085.d.ts +2 -1
- package/dist/contract-jIfaR085.d.ts.map +1 -0
- package/dist/devtools-J_jZ2vQf.d.ts +2 -1
- package/dist/devtools-J_jZ2vQf.d.ts.map +1 -0
- package/dist/devtools-kJJaORn-.js +2 -0
- package/dist/devtools-kJJaORn-.js.map +1 -0
- package/dist/devtools-server.browser.js +2 -0
- package/dist/devtools-server.browser.js.map +1 -0
- package/dist/devtools-server.d.ts +2 -1
- package/dist/devtools-server.d.ts.map +1 -0
- package/dist/devtools-server.js +2 -0
- package/dist/devtools-server.js.map +1 -0
- package/dist/errors-BQuJpe82.js +2 -0
- package/dist/errors-BQuJpe82.js.map +1 -0
- package/dist/errors-W6nwJ-fm.d.ts +2 -1
- package/dist/errors-W6nwJ-fm.d.ts.map +1 -0
- package/dist/http.d.ts +121 -72
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +503 -178
- package/dist/http.js.map +1 -0
- package/dist/idempotent-replay-DuqEkYA7.js +2 -0
- package/dist/idempotent-replay-DuqEkYA7.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/inspection-DaxB5jM2.js +2 -0
- package/dist/inspection-DaxB5jM2.js.map +1 -0
- package/dist/{internal-aEotMzu_.js → internal-DstsI6Re.js} +3 -1
- package/dist/internal-DstsI6Re.js.map +1 -0
- package/dist/otel.d.ts +2 -1
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +2 -0
- package/dist/otel.js.map +1 -0
- package/dist/platform-B4TnJtWu.js +2 -0
- package/dist/platform-B4TnJtWu.js.map +1 -0
- package/dist/react.d.ts +12 -3
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +5 -1
- package/dist/react.js.map +1 -0
- package/dist/retryable-lazy-DZWmHpii.js +2 -0
- package/dist/retryable-lazy-DZWmHpii.js.map +1 -0
- package/dist/scheduler-qstash.d.ts +4 -3
- package/dist/scheduler-qstash.d.ts.map +1 -0
- package/dist/scheduler-qstash.js +4 -2
- package/dist/scheduler-qstash.js.map +1 -0
- package/dist/scheduler-task-BpzhPnRS.js +2 -0
- package/dist/scheduler-task-BpzhPnRS.js.map +1 -0
- package/dist/scheduler-vercel.d.ts +4 -3
- package/dist/scheduler-vercel.d.ts.map +1 -0
- package/dist/scheduler-vercel.js +4 -2
- package/dist/scheduler-vercel.js.map +1 -0
- package/dist/{server-CcNnFnoW.js → server-C72KOw51.js} +100 -48
- package/dist/server-C72KOw51.js.map +1 -0
- package/dist/{server-YtPq7hjw.d.ts → server-DUF9pjsx.d.ts} +5 -4
- package/dist/server-DUF9pjsx.d.ts.map +1 -0
- package/dist/server.browser.js +2 -0
- package/dist/server.browser.js.map +1 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1 -1
- package/dist/{store-C3sNAaBT.d.ts → store-DysUkTH3.d.ts} +10 -1
- package/dist/store-DysUkTH3.d.ts.map +1 -0
- package/dist/store-N8PXxDAS.js +2 -0
- package/dist/store-N8PXxDAS.js.map +1 -0
- package/dist/store-codec-DTG0Ftek.js +2 -0
- package/dist/store-codec-DTG0Ftek.js.map +1 -0
- package/dist/store-memory.d.ts +3 -2
- package/dist/store-memory.d.ts.map +1 -0
- package/dist/store-memory.js +19 -11
- package/dist/store-memory.js.map +1 -0
- package/dist/{store-polling-DgrrAE3d.js → store-polling-dSeLxzfb.js} +3 -1
- package/dist/store-polling-dSeLxzfb.js.map +1 -0
- package/dist/store-postgres.d.ts +3 -2
- package/dist/store-postgres.d.ts.map +1 -0
- package/dist/store-postgres.js +57 -1
- package/dist/store-postgres.js.map +1 -0
- package/dist/{store-redis-core-DWqx3F47.js → store-redis-core-BFLwz0Wj.js} +3 -1
- package/dist/store-redis-core-BFLwz0Wj.js.map +1 -0
- package/dist/store-redis-http.d.ts +3 -2
- package/dist/store-redis-http.d.ts.map +1 -0
- package/dist/store-redis-http.js +4 -2
- package/dist/store-redis-http.js.map +1 -0
- package/dist/store-redis.d.ts +3 -2
- package/dist/store-redis.d.ts.map +1 -0
- package/dist/store-redis.js +5 -3
- package/dist/store-redis.js.map +1 -0
- package/dist/store-sqlite.d.ts +3 -2
- package/dist/store-sqlite.d.ts.map +1 -0
- package/dist/store-sqlite.js +3 -1
- package/dist/store-sqlite.js.map +1 -0
- package/dist/telemetry-BjYHTfh2.d.ts +2 -1
- package/dist/telemetry-BjYHTfh2.d.ts.map +1 -0
- package/dist/testing.browser.js +2 -0
- package/dist/testing.browser.js.map +1 -0
- package/dist/testing.d.ts +2 -1
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +2 -0
- package/dist/testing.js.map +1 -0
- package/dist/validate-XKT4FSNn.js +2 -0
- package/dist/validate-XKT4FSNn.js.map +1 -0
- package/dist/{wire-DCUZBUlT.js → wire-BFQmSJ-9.js} +77 -15
- package/dist/wire-BFQmSJ-9.js.map +1 -0
- package/docs/guides/03-react.mdx +59 -39
- package/docs/guides/06-ai-agents.mdx +5 -27
- package/docs/guides/09-presence.mdx +19 -40
- package/docs/guides/10-transports.mdx +49 -40
- package/docs/reference/01-api.mdx +107 -26
- package/docs/reference/02-errors.mdx +4 -2
- package/package.json +2 -1
- package/src/ai-coordinator.ts +358 -0
- package/src/ai-projector.ts +524 -0
- package/src/ai-sdk-step.ts +261 -0
- package/src/ai-server.browser.ts +5 -0
- package/src/ai-server.ts +1719 -0
- package/src/ai.ts +2155 -0
- package/src/cache-indexeddb.ts +10 -0
- package/src/cli-bin.ts +5 -0
- package/src/cli.ts +1046 -0
- package/src/client.ts +1826 -0
- package/src/contract.ts +206 -0
- package/src/deterministic-id.ts +72 -0
- package/src/devtools-app.ts +989 -0
- package/src/devtools-server.browser.ts +5 -0
- package/src/devtools-server.ts +604 -0
- package/src/devtools.ts +716 -0
- package/src/errors.ts +50 -0
- package/src/http.ts +394 -0
- package/src/idempotent-replay.ts +53 -0
- package/src/index.ts +37 -0
- package/src/inspection.ts +39 -0
- package/src/internal.ts +426 -0
- package/src/otel.ts +59 -0
- package/src/platform.ts +60 -0
- package/src/push-envelope.ts +137 -0
- package/src/react.ts +284 -0
- package/src/reducer.ts +108 -0
- package/src/retryable-lazy.ts +27 -0
- package/src/scheduler-qstash.ts +915 -0
- package/src/scheduler-task.ts +106 -0
- package/src/scheduler-vercel.ts +437 -0
- package/src/server.browser.ts +12 -0
- package/src/server.ts +2700 -0
- package/src/session-socket.ts +548 -0
- package/src/sse.ts +141 -0
- package/src/standard-schema.ts +77 -0
- package/src/store-codec.ts +10 -0
- package/src/store-memory.ts +788 -0
- package/src/store-polling.ts +102 -0
- package/src/store-postgres.ts +1212 -0
- package/src/store-redis-core.ts +1494 -0
- package/src/store-redis-http.ts +116 -0
- package/src/store-redis.ts +458 -0
- package/src/store-sqlite.ts +1108 -0
- package/src/store.ts +385 -0
- package/src/telemetry.ts +47 -0
- package/src/testing.browser.ts +5 -0
- package/src/testing.ts +185 -0
- package/src/validate.ts +39 -0
- package/src/wire.ts +454 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-BKlyLiOU.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\n * experimental-a2/client — the framework-agnostic session client.\n *\n * Everything the browser needs to read a session live and push\n * optimistically, with no framework attached: the SSE subscription with\n * frontier resume and reconnection, the optimistic push queue with\n * ack/rollback, and the local fold through the same reducer the server\n * uses. `experimental-a2/react`'s `createReact` is a thin binding over it, and the\n * store contract (`subscribe`/`getSnapshot`) is exactly what\n * `useSyncExternalStore` wants.\n *\n * The client is a replica, never an access path — the routes it talks\n * to authorize every read and write.\n */\n\n// oxlint-disable no-await-in-loop -- retry/backoff/stream loops are sequential by nature\nimport { A2Error } from './errors.ts'\nimport {\n PRESENCE_TIMINGS,\n STREAM_TIMINGS,\n nullProtoRecord,\n} from './internal.ts'\nimport type {\n AppendInput,\n ContractEvent,\n EventDefs,\n PresenceDefs,\n PresenceMap,\n PresencePatch,\n WithPresence,\n} from './contract.ts'\nimport type { Reducer } from './reducer.ts'\nimport { validateSync } from './validate.ts'\nimport {\n errorFromWire,\n eventFromWire,\n isWireEvent,\n isWirePresencePatch,\n isWirePresenceSnapshot,\n parseSocketFrame,\n presencePatchFromWire,\n presenceSnapshotFromWire,\n type SocketDownFrame,\n type SocketPresenceFrame,\n type SocketPushFrame,\n type SocketSubscribeFrame,\n type SocketUnsubscribeFrame,\n type WireEvent,\n type WirePresencePatch,\n type WirePresenceSnapshot,\n} from './wire.ts'\n\nexport type ConnectionStatus = 'idle' | 'connecting' | 'live' | 'closed'\n\n/**\n * The connection, as a discriminated union — impossible states are\n * unrepresentable: an error only exists while disconnected,\n * `reconnects` only once a connection has been attempted.\n * \"Reconnecting…\" is `status === 'connecting' && reconnects > 0`.\n */\nexport type Connection =\n | { status: 'idle' }\n | {\n status: 'connecting'\n /** Drops of an established stream so far. `0` = first connect. */\n reconnects: number\n /** Why the last connection ended; `null` on the first connect. */\n error: Error | null\n }\n | { status: 'live'; reconnects: number }\n | { status: 'closed' }\n\n/**\n * What `push` returns: resolves at the server ack (exactly like a\n * plain promise — `await push(...)` gives the acked events), and\n * carries `confirmed` for the later moment when the live stream has\n * delivered the whole batch back and the optimistic overlay entry\n * retired — the view now shows server truth. `confirmed` is lazy:\n * never accessed, never created. A rejected push rejects both.\n */\nexport type PushResult<D extends EventDefs> = Promise<ContractEvent<D>[]> & {\n readonly confirmed: Promise<ContractEvent<D>[]>\n}\n\nexport type LoadHistoryOptions = {\n /**\n * Exclusive upper bound for the fetch. Defaults to the oldest\n * already-loaded index — successive calls page backward — or one\n * past the hydration frontier while nothing is loaded yet.\n */\n before?: number\n /** Page size; defaults to 50. */\n limit?: number\n}\n\n/** Backscroll progress, derived from the raw event feed. */\nexport type HistoryState = {\n /** A `loadHistory` fetch is in flight. */\n loading: boolean\n /** The feed reaches index 1 — nothing older remains to fetch. */\n complete: boolean\n /** The oldest loaded index; `null` while nothing is loaded. */\n oldestLoaded: number | null\n}\n\n/** One immutable view of the session — stable identity between changes. */\nexport type SessionSnapshot<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = WithPresence<\n P,\n {\n /** The replicated presence map, this client included (local echo). */\n presence: PresenceMap<P>\n }\n> & {\n /** The live view: server events folded, optimistic pushes applied. */\n state: S\n /** The raw event feed: server truth — observed, seeded, or\n * backscrolled via `loadHistory` — plus pending optimistic events\n * (provisional indexes past the frontier). `state` never folds\n * backscrolled events; they are display data below the frontier. */\n events: ContractEvent<D>[]\n /** The stream frontier: the last server-confirmed index. This is the\n * `lastSeenIndex` cancellation wants. */\n index: number\n /** Backscroll progress — `loadHistory` moves it. */\n history: HistoryState\n connection: Connection\n}\n\n/**\n * The presence member of a client session — intersected in via\n * `WithPresence`, so it exists exactly when the contract declares\n * presence fields.\n */\nexport type SessionClientPresence<P extends PresenceDefs> = {\n /**\n * Fire-and-forget presence: validates locally against the reducer's\n * presence schemas, merges into the local map immediately (the\n * echo), and coalesces on the wire to a fixed cadence — a leading\n * send immediately, then at most one merged send per interval,\n * later values winning field-wise. `null` clears a field. No ack,\n * no retry, no rollback: a lost patch is repainted by the next one.\n * Requires the session's `participant`.\n */\n setPresence(values: PresencePatch<P>['values']): void\n}\n\nexport type SessionClient<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = WithPresence<P, SessionClientPresence<P>> & {\n readonly sessionId: string\n subscribe(listener: () => void): () => void\n getSnapshot(): SessionSnapshot<D, S, P>\n /**\n * Optimistic append: validates locally against the reducer's event\n * schemas (instant `INVALID_PAYLOAD`, no flicker), applies to the\n * local fold, POSTs, swaps in the ack, rolls back on rejection.\n * Auto-retries only `STORE_UNAVAILABLE`. Resolves with the appended\n * events as the server recorded them. Overlapping calls on this\n * handle enter the transport in invocation order without delaying\n * their optimistic folds.\n */\n push(...events: AppendInput<D>[]): PushResult<D>\n /**\n * Backscroll: fetch a bounded slice of events from below the\n * frontier and merge it into `events` — deduped, ordered, shared by\n * every handle of this session. Folded state and the optimistic\n * overlay are never touched. Calls serialize per session and skip\n * already-loaded ranges, so overlapping backscrolls never fetch the\n * same range twice. Resolves with the events in the requested range.\n * Requires an http api; the `ws` transport has no history lane and\n * this throws a TypeError there.\n */\n loadHistory(options?: LoadHistoryOptions): Promise<ContractEvent<D>[]>\n /** Open the live stream (idempotent while open). Reconnects with\n * backoff and resumes from the frontier until `close()`. */\n connect(): void\n /**\n * Stop the live stream. Not terminal: `connect()` starts it again\n * from the current frontier — which is what makes the React\n * StrictMode mount dance (setup → cleanup → setup) work.\n */\n close(): void\n}\n\nexport type SessionOptions<D extends EventDefs, S> = {\n initialState?: S\n initialIndex?: number\n /** Server-rendered history through `initialIndex`. Seeds the event feed. */\n initialEvents?: ContractEvent<D>[]\n /**\n * This client's presence identity — required to call `setPresence`.\n * Caller-supplied (a user id, a tab nonce, a guest name): A2 does\n * not invent an identity story. Bound once per session handle.\n */\n participant?: string\n}\n\nexport type A2Client<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n session(\n sessionId: string,\n options?: SessionOptions<D, S>,\n ): SessionClient<D, S, P>\n}\n\n/**\n * The wire the client rides. The string form is one route serving GET\n * (SSE stream) + POST (push). `http` splits the verbs across two\n * routes, for when platform duration limits differ per verb. `ws`\n * rides everything — every session of this client — over one\n * multiplexed WebSocket, served by `sessionsSocket`.\n */\nexport type ClientApi =\n | string // one route: GET SSE stream + POST push\n | { type: 'http'; push: string; stream: string } // split routes\n | { type: 'ws'; url: string } // one socket, all sessions, both directions\n\n/**\n * The socket surface the `ws` transport drives — satisfied structurally\n * by the DOM `WebSocket` and Node's alike, and small enough to fake in\n * tests.\n */\nexport type ClientWebSocket = {\n readonly readyState: number\n send(data: string): void\n close(code?: number, reason?: string): void\n addEventListener(type: 'open', listener: () => void): void\n addEventListener(\n type: 'message',\n listener: (event: { data: unknown }) => void,\n ): void\n addEventListener(\n type: 'close',\n listener: (event: { code: number; reason: string }) => void,\n ): void\n addEventListener(type: 'error', listener: () => void): void\n}\n\nexport type ClientWebSocketConstructor = new (url: string) => ClientWebSocket\n\nexport type CreateClientOptions<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n reducer: Reducer<D, S, P>\n /** The route (or routes) the client speaks — see `ClientApi`. */\n api: ClientApi\n /** Injectable fetch — defaults to the global. */\n fetch?: typeof globalThis.fetch\n /** Injectable WebSocket constructor for the `ws` api — defaults to\n * the global. The http transports never touch it. */\n webSocket?: ClientWebSocketConstructor\n /** How long an idle session keeps its in-memory identity, in\n * milliseconds. Defaults to five minutes; `Infinity` disables GC. */\n gcTime?: number\n}\n\nconst PUSH_ATTEMPTS = 3\nconst DEFAULT_GC_TIME_MS = 5 * 60 * 1_000\nconst DEFAULT_HISTORY_LIMIT = 50\n\n// ── the transport seam ───────────────────────────────────────────────\n// Everything above it — optimistic queue, retry loop, presence\n// throttle, reconnect/backoff, stall watchdog — is transport-agnostic.\n\n/** The parsed frame lanes every transport yields: the SSE frame\n * discrimination today, the ws `kind` discriminant tomorrow. `ping`\n * carries no data — it exists so the watchdog sees liveness. */\ntype TransportFrame =\n | { kind: 'event'; event: WireEvent }\n | { kind: 'presence'; patch: WirePresencePatch }\n | { kind: 'presence-snapshot'; snapshot: WirePresenceSnapshot }\n | { kind: 'ping' }\n\ntype TransportPushBody = {\n sessionId: string\n events: Array<{ id: string; type: string; payload: unknown }>\n}\n\ntype TransportPresenceEnvelope = {\n sessionId: string\n presence: {\n participant: string\n values: Record<string, unknown>\n seen: number\n at: number\n }\n}\n\ntype Transport = {\n /**\n * One live read: the first frame means the stream is established\n * (any kind — a bare `ping` when no data is pending), the iterable\n * ending or throwing is the disconnect. The caller owns\n * reconnect/backoff and counts every frame as liveness.\n */\n connect(opts: {\n sessionId: string\n startAfter: number\n signal: AbortSignal\n }): AsyncIterable<TransportFrame>\n /** One attempt (the retry loop lives above the seam): resolves with\n * the acked wire events, throws the wire-deserialized A2Error. */\n push(body: TransportPushBody): Promise<WireEvent[]>\n /** One bounded cold read of the log, oldest first — the lane\n * `loadHistory` rides. Absent on transports without a\n * request/response wire (ws), where `loadHistory` fails fast. */\n history?(opts: {\n sessionId: string\n gte: number\n lte: number\n }): Promise<WireEvent[]>\n /** Fire-and-forget; failures are dropped (presence repaints). */\n sendPresence(envelope: TransportPresenceEnvelope): void\n /**\n * Resolve when the session's wire can carry a push again, or after\n * `maxWaitMs` — whichever comes first; never rejects. Transports\n * whose pushes are connection-bound (ws) implement it so the retry\n * loop waits for the wire instead of a fixed clock; HTTP pushes are\n * stream-independent and omit it.\n */\n awaitReady?(sessionId: string, maxWaitMs: number): Promise<void>\n}\n\nconst httpTransport = (\n routes: { push: string; stream: string },\n fetchImpl: typeof globalThis.fetch,\n // Contracts that declare no presence skip presence frames unparsed,\n // exactly like unknown named frames.\n presence: boolean,\n): Transport => ({\n async *connect({ sessionId, startAfter, signal }) {\n const sep = routes.stream.includes('?') ? '&' : '?'\n const res = await fetchImpl(\n `${routes.stream}${sep}sessionId=${encodeURIComponent(sessionId)}&index=${startAfter}`,\n { headers: { accept: 'text/event-stream' }, signal },\n )\n if (!res.ok || !res.body) {\n throw new Error(`stream failed with ${res.status}`)\n }\n yield { kind: 'ping' } // connected — headers in hand, before any bytes\n const decoder = new TextDecoder()\n const reader = res.body.getReader()\n let buffer = ''\n try {\n for (;;) {\n // oxlint-disable-next-line no-await-in-loop -- stream read loop\n const { done, value } = await reader.read()\n if (done) return\n // Any bytes count — heartbeat comments included. Liveness is\n // about the connection, not about events.\n yield { kind: 'ping' }\n buffer += decoder.decode(value, { stream: true })\n for (;;) {\n const boundary = buffer.indexOf('\\n\\n')\n if (boundary === -1) break\n const frame = buffer.slice(0, boundary)\n buffer = buffer.slice(boundary + 2)\n let eventName: string | undefined\n const dataLines: string[] = []\n for (const line of frame.split('\\n')) {\n if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).trimStart())\n } else if (line.startsWith('event:')) {\n eventName = line.slice(6).trim()\n }\n }\n const data = dataLines.join('\\n')\n if (!data) continue\n if (eventName === undefined) {\n const parsed: unknown = JSON.parse(data)\n if (isWireEvent(parsed)) yield { kind: 'event', event: parsed }\n } else if (presence && eventName === 'presence-snapshot') {\n const parsed: unknown = JSON.parse(data)\n if (isWirePresenceSnapshot(parsed)) {\n yield { kind: 'presence-snapshot', snapshot: parsed }\n }\n } else if (presence && eventName === 'presence') {\n const parsed: unknown = JSON.parse(data)\n if (isWirePresencePatch(parsed)) {\n yield { kind: 'presence', patch: parsed }\n }\n }\n // Other named frames: skipped — old clients stay compatible\n // with future lanes the same way.\n }\n }\n } finally {\n reader.cancel().catch(() => {})\n }\n },\n\n async push(body) {\n const res = await fetchImpl(routes.push, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (res.ok) {\n const rows = (await res.json()) as unknown\n if (!Array.isArray(rows) || !rows.every(isWireEvent)) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n 'push ack was not a list of events',\n )\n }\n return rows\n }\n throw (\n errorFromWire(await res.json().catch(() => null)) ??\n new A2Error('STORE_UNAVAILABLE', `push failed with ${res.status}`)\n )\n },\n\n async history({ sessionId, gte, lte }) {\n const sep = routes.stream.includes('?') ? '&' : '?'\n const res = await fetchImpl(\n `${routes.stream}${sep}sessionId=${encodeURIComponent(sessionId)}>e=${gte}<e=${lte}`,\n { headers: { accept: 'application/json' } },\n )\n if (!res.ok) {\n throw (\n errorFromWire(await res.json().catch(() => null)) ??\n new A2Error('STORE_UNAVAILABLE', `history failed with ${res.status}`)\n )\n }\n const rows = (await res.json()) as unknown\n if (!Array.isArray(rows) || !rows.every(isWireEvent)) {\n throw new A2Error(\n 'STORE_UNAVAILABLE',\n 'history response was not a list of events',\n )\n }\n return rows\n },\n\n sendPresence(envelope) {\n void (async () => {\n try {\n const res = await fetchImpl(routes.push, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(envelope),\n })\n await res.arrayBuffer()\n } catch {\n // Dropped by design — nothing was ever true.\n }\n })()\n },\n})\n\n/** `WebSocket.OPEN` — fixed by the spec, identical in every runtime. */\nconst WS_OPEN = 1\n\n/** One session's lane on the shared ws socket — created per `connect`\n * attempt, so `startAfter` is that attempt's resume frontier. */\ntype WsChannel = {\n sessionId: string\n startAfter: number\n queue: TransportFrame[]\n /** The server confirmed the subscribe — pushes and presence ride. */\n live: boolean\n /** The server already dropped it — no unsubscribe frame owed. */\n dropped: boolean\n ended: boolean\n failure: Error | null\n wake: (() => void) | null\n}\n\nconst wakeChannel = (channel: WsChannel): void => {\n channel.wake?.()\n channel.wake = null\n}\n\nconst subscribeFrame = (\n sessions: Array<{ id: string; index: number }>,\n): string => {\n const frame: SocketSubscribeFrame = { kind: 'subscribe', sessions }\n return JSON.stringify(frame)\n}\n\nconst wsTransport = (\n url: string,\n webSocketImpl: ClientWebSocketConstructor | undefined,\n presence: boolean,\n): Transport => {\n type PendingAck = {\n resolve: (events: WireEvent[]) => void\n reject: (error: A2Error) => void\n }\n // ONE socket serves every session of this client (specs §13's\n // multiplexed superset): channels subscribe on it at their own\n // frontiers, down frames route back by sessionId, acks by req.\n const channels = new Map<string, WsChannel>()\n const pending = new Map<number, PendingAck>()\n /** Push retries parked in `awaitReady`, flushed per session on its\n * subscribe confirmation. */\n const readyWaiters = new Map<string, Set<() => void>>()\n let socket: ClientWebSocket | null = null\n let socketOpen = false\n let watchdog: ReturnType<typeof setTimeout> | undefined\n let nextReq = 1\n\n const flushReadyWaiters = (sessionId: string): void => {\n const waiters = readyWaiters.get(sessionId)\n if (!waiters) return\n readyWaiters.delete(sessionId)\n for (const waiter of waiters) waiter()\n }\n\n const socketUrl = (): string => {\n let resolved: URL\n if (/^(https?|wss?):\\/\\//i.test(url)) {\n resolved = new URL(url)\n } else {\n // A relative url resolves against the page, exactly like fetch.\n // Outside a browser (SSR, tests) there is nothing to resolve\n // against — demand an absolute url there.\n const base = (globalThis as { location?: { href: string } }).location\n ?.href\n if (base === undefined) {\n throw new TypeError(\n `cannot resolve the relative ws url '${url}' without a browser location — pass an absolute ws:// or wss:// url`,\n )\n }\n resolved = new URL(url, base)\n }\n if (resolved.protocol === 'http:') resolved.protocol = 'ws:'\n else if (resolved.protocol === 'https:') resolved.protocol = 'wss:'\n return resolved.href\n }\n\n const trySend = (data: string): void => {\n if (socket === null || !socketOpen) return\n try {\n socket.send(data)\n } catch {\n // Dropped — the socket is going down and its close event settles\n // everything that depended on this frame.\n }\n }\n\n const deliver = (channel: WsChannel, frame: TransportFrame): void => {\n channel.queue.push(frame)\n wakeChannel(channel)\n }\n\n const settleChannel = (channel: WsChannel, failure: Error | null): void => {\n if (channel.ended) return\n channel.ended = true\n channel.failure = failure\n wakeChannel(channel)\n }\n\n // Unhook the socket from the transport state and reject every\n // outstanding ack — nothing was acked, and the client-generated ids\n // make the retries idempotent (§13).\n const releaseSocket = (): ClientWebSocket | null => {\n const current = socket\n socket = null\n socketOpen = false\n clearTimeout(watchdog)\n watchdog = undefined\n if (pending.size > 0) {\n const rejection = new A2Error(\n 'STORE_UNAVAILABLE',\n 'socket closed with the ack outstanding',\n )\n for (const waiter of pending.values()) waiter.reject(rejection)\n pending.clear()\n }\n return current\n }\n\n // Idempotent from every direction (close event, error event, stall):\n // every channel ends — each session's reconnect loop re-subscribes at\n // its own current frontier.\n const failSocket = (failure: Error | null): void => {\n const current = releaseSocket()\n for (const channel of channels.values()) settleChannel(channel, failure)\n try {\n current?.close(1000)\n } catch {\n // Already closed — it is down either way.\n }\n }\n\n const detach = (channel: WsChannel): void => {\n if (channels.get(channel.sessionId) !== channel) return\n channels.delete(channel.sessionId)\n if (!channel.dropped) {\n const frame: SocketUnsubscribeFrame = {\n kind: 'unsubscribe',\n sessions: [channel.sessionId],\n }\n trySend(JSON.stringify(frame))\n }\n if (channels.size === 0) {\n // The last session left — the shared socket has no reason to live.\n const current = releaseSocket()\n try {\n current?.close(1000)\n } catch {\n // Already closed — it is down either way.\n }\n }\n }\n\n // One heartbeat watchdog per socket: any frame is liveness, and\n // silence past the stall timeout means the connection is dead in a\n // way the socket won't report.\n const armWatchdog = (created: ClientWebSocket): void => {\n clearTimeout(watchdog)\n watchdog = setTimeout(() => {\n if (socket !== created) return\n failSocket(\n new Error(\n `socket stalled: no data for ${STREAM_TIMINGS.stallTimeoutMs}ms`,\n ),\n )\n }, STREAM_TIMINGS.stallTimeoutMs)\n ;(watchdog as { unref?: () => void }).unref?.()\n }\n\n const route = (frame: SocketDownFrame): void => {\n switch (frame.kind) {\n case 'ping': {\n // Global liveness — every live channel's stall watchdog feeds\n // on it, the way each SSE stream feeds on its own heartbeat.\n for (const channel of channels.values()) {\n if (channel.live && !channel.ended) {\n deliver(channel, { kind: 'ping' })\n }\n }\n return\n }\n case 'subscribed': {\n const channel = channels.get(frame.sessionId)\n if (channel === undefined || channel.ended || channel.live) return\n channel.live = true\n deliver(channel, { kind: 'ping' }) // the connected signal\n flushReadyWaiters(frame.sessionId)\n return\n }\n case 'unsubscribed': {\n const channel = channels.get(frame.sessionId)\n if (channel === undefined) return\n channel.dropped = true\n settleChannel(\n channel,\n frame.reason === undefined ? null : new Error(frame.reason),\n )\n return\n }\n case 'ack': {\n const waiter = pending.get(frame.req)\n if (waiter === undefined) return\n pending.delete(frame.req)\n if ('events' in frame) waiter.resolve(frame.events)\n else waiter.reject(frame.error)\n return\n }\n case 'event': {\n // Pre-confirmation frames are safe to drop: the subscription\n // being confirmed replays from this channel's own frontier.\n const channel = channels.get(frame.event.sessionId)\n if (channel !== undefined && channel.live && !channel.ended) {\n deliver(channel, { kind: 'event', event: frame.event })\n }\n return\n }\n case 'presence': {\n // Contracts that declare no presence skip presence frames,\n // exactly like the http transport.\n if (!presence || frame.sessionId === undefined) return\n const channel = channels.get(frame.sessionId)\n if (channel !== undefined && channel.live && !channel.ended) {\n deliver(channel, { kind: 'presence', patch: frame.patch })\n }\n return\n }\n case 'presence-snapshot': {\n if (!presence || frame.sessionId === undefined) return\n const channel = channels.get(frame.sessionId)\n if (channel !== undefined && channel.live && !channel.ended) {\n deliver(channel, {\n kind: 'presence-snapshot',\n snapshot: frame.snapshot,\n })\n }\n return\n }\n }\n }\n\n const ensureSocket = (): void => {\n if (socket !== null) return\n if (webSocketImpl === undefined) {\n throw new TypeError(\n 'no WebSocket implementation available — pass one via createClient({ webSocket }) where the global is missing',\n )\n }\n const created = new webSocketImpl(socketUrl())\n socket = created\n socketOpen = false\n armWatchdog(created)\n created.addEventListener('open', () => {\n if (socket !== created) return\n socketOpen = true\n armWatchdog(created)\n const sessions = [...channels.values()]\n .filter((channel) => !channel.ended)\n .map((channel) => ({\n id: channel.sessionId,\n index: channel.startAfter,\n }))\n if (sessions.length > 0) trySend(subscribeFrame(sessions))\n })\n created.addEventListener('message', (event) => {\n if (socket !== created) return\n if (typeof event.data !== 'string') return\n armWatchdog(created)\n const frame = parseSocketFrame(event.data)\n if (frame === null) return\n route(frame)\n })\n created.addEventListener('close', (event) => {\n if (socket !== created) return\n failSocket(\n event.code === 1000\n ? null // deliberate server close — reconnect, but not an error\n : new Error(\n `socket closed (${event.code}${event.reason ? `: ${event.reason}` : ''})`,\n ),\n )\n })\n created.addEventListener('error', () => {\n if (socket !== created) return\n failSocket(new Error('socket error'))\n })\n }\n\n return {\n async *connect({ sessionId, startAfter, signal }) {\n const channel: WsChannel = {\n sessionId,\n startAfter,\n queue: [],\n live: false,\n dropped: false,\n ended: false,\n failure: null,\n wake: null,\n }\n channels.set(sessionId, channel)\n try {\n ensureSocket()\n if (socketOpen) {\n trySend(subscribeFrame([{ id: sessionId, index: startAfter }]))\n }\n } catch (error) {\n if (channels.get(sessionId) === channel) channels.delete(sessionId)\n throw error\n }\n\n const onAbort = (): void => {\n settleChannel(\n channel,\n signal.reason instanceof Error\n ? signal.reason\n : new Error('stream aborted'),\n )\n // Synchronously, not from the pump's finally: the watchdog and\n // close() must not leave a dead subscription behind.\n detach(channel)\n }\n if (signal.aborted) onAbort()\n else signal.addEventListener('abort', onAbort, { once: true })\n\n // An unconfirmed subscribe receives no frames at all (global\n // pings only feed live channels), so a server that never answers\n // — a single-session route, say — would hang this session\n // silently. Fail it loudly instead; the reconnect loop retries.\n const pendingSubscribe = setTimeout(() => {\n if (channel.live) return\n settleChannel(\n channel,\n new Error(\n 'subscribe not confirmed: is the ws route serving the multiplexed socket (handle with options.upgrade)?',\n ),\n )\n }, STREAM_TIMINGS.stallTimeoutMs)\n ;(pendingSubscribe as { unref?: () => void }).unref?.()\n\n try {\n for (;;) {\n while (channel.queue.length > 0) yield channel.queue.shift()!\n if (channel.ended) {\n if (channel.failure) throw channel.failure\n return\n }\n // oxlint-disable-next-line no-await-in-loop -- frame pump\n await new Promise<void>((resolve) => {\n channel.wake = resolve\n })\n }\n } finally {\n clearTimeout(pendingSubscribe)\n signal.removeEventListener('abort', onAbort)\n settleChannel(channel, null)\n detach(channel)\n }\n },\n\n async push(body) {\n const channel = channels.get(body.sessionId)\n const target = socket\n if (\n channel === undefined ||\n !channel.live ||\n channel.ended ||\n target === null ||\n !socketOpen ||\n target.readyState !== WS_OPEN\n ) {\n // Retryable by contract: the reconnect loop restores the\n // socket and the retry rides the same event ids.\n throw new A2Error('STORE_UNAVAILABLE', 'no open socket for the session')\n }\n const req = nextReq\n nextReq += 1\n return await new Promise<WireEvent[]>((resolve, reject) => {\n pending.set(req, { resolve, reject })\n const frame: SocketPushFrame = {\n kind: 'push',\n req,\n sessionId: body.sessionId,\n events: body.events,\n }\n try {\n target.send(JSON.stringify(frame))\n } catch (cause) {\n pending.delete(req)\n reject(\n new A2Error('STORE_UNAVAILABLE', 'socket send failed', { cause }),\n )\n }\n })\n },\n\n awaitReady(sessionId, maxWaitMs) {\n const channel = channels.get(sessionId)\n if (\n channel !== undefined &&\n channel.live &&\n !channel.ended &&\n socketOpen\n ) {\n return Promise.resolve()\n }\n return new Promise((resolve) => {\n let waiters = readyWaiters.get(sessionId)\n if (!waiters) {\n waiters = new Set()\n readyWaiters.set(sessionId, waiters)\n }\n let settled = false\n const done = (): void => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n waiters.delete(done)\n if (waiters.size === 0) readyWaiters.delete(sessionId)\n // oxlint-disable-next-line promise/no-multiple-resolved -- `settled` guards the two callers (timer, open flush); the rule cannot see through the flag\n resolve()\n }\n // The cap keeps a session nobody reconnects (write-only usage,\n // an unreachable server) from parking a push forever — the\n // attempt then fails fast and the loop moves on.\n const timer = setTimeout(done, maxWaitMs)\n ;(timer as { unref?: () => void }).unref?.()\n waiters.add(done)\n })\n },\n sendPresence(envelope) {\n const channel = channels.get(envelope.sessionId)\n if (channel === undefined || !channel.live || channel.ended) {\n return // dropped by design — the reconnect re-send covers the gap\n }\n const frame: SocketPresenceFrame = {\n kind: 'presence',\n sessionId: envelope.sessionId,\n ...envelope.presence,\n }\n trySend(JSON.stringify(frame))\n },\n }\n}\n\nconst transportFor = (\n api: ClientApi,\n fetchImpl: typeof globalThis.fetch,\n webSocketImpl: ClientWebSocketConstructor | undefined,\n presence: boolean,\n): Transport => {\n if (typeof api === 'string') {\n return httpTransport({ push: api, stream: api }, fetchImpl, presence)\n }\n if (api.type === 'http') {\n return httpTransport(\n { push: api.push, stream: api.stream },\n fetchImpl,\n presence,\n )\n }\n return wsTransport(api.url, webSocketImpl, presence)\n}\n\ntype Pending<D extends EventDefs> = {\n id: string\n type: string\n payload: unknown\n createdAt: Date\n /** Set once the POST ack returns; the entry leaves the overlay when\n * the stream delivers its index. */\n acked?: ContractEvent<D>\n}\n\ntype SessionRuntime<\n D extends EventDefs,\n S,\n P extends PresenceDefs,\n> = SessionClient<D, S, P> & {\n hydrate(options?: SessionOptions<D, S>): void\n canEvict(): boolean\n}\n\ntype CachedSession<D extends EventDefs, S, P extends PresenceDefs> = {\n runtime: SessionRuntime<D, S, P>\n timer?: ReturnType<typeof setTimeout>\n}\n\nexport function createClient<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n>(options: CreateClientOptions<D, S, P>): A2Client<D, S, P> {\n const { reducer, api } = options\n const presenceDefs: Readonly<PresenceDefs> = reducer.presence\n const declaresPresence = Object.keys(presenceDefs).length > 0\n const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis)\n const webSocketImpl =\n options.webSocket ??\n (globalThis as { WebSocket?: ClientWebSocketConstructor }).WebSocket\n const transport = transportFor(\n api,\n fetchImpl,\n webSocketImpl,\n declaresPresence,\n )\n const gcTime = options.gcTime ?? DEFAULT_GC_TIME_MS\n if (Number.isNaN(gcTime) || gcTime < 0) {\n throw new RangeError('gcTime must be a non-negative number or Infinity')\n }\n const sessions = new Map<string, CachedSession<D, S, P>>()\n\n const scheduleGc = (sessionId: string, delay = gcTime): void => {\n const entry = sessions.get(sessionId)\n if (!entry) return\n clearTimeout(entry.timer)\n if (gcTime === Infinity) return\n entry.timer = setTimeout(() => {\n const current = sessions.get(sessionId)\n if (current !== entry) return\n if (entry.runtime.canEvict()) {\n sessions.delete(sessionId)\n return\n }\n // `gcTime: 0` is useful for tests and explicit no-cache clients,\n // but must not spin while a session is mounted or a push is in flight.\n scheduleGc(sessionId, Math.max(gcTime, 1_000))\n }, delay)\n ;(entry.timer as { unref?: () => void }).unref?.()\n }\n\n const touch = (sessionId: string): void => {\n scheduleGc(sessionId)\n }\n\n const makeSession = (\n sessionId: string,\n sessionOptions?: SessionOptions<D, S>,\n ): SessionRuntime<D, S, P> => {\n // ── server truth ─────────────────────────────────────────────\n let frontier = sessionOptions?.initialIndex ?? 0\n let foldedState: S =\n sessionOptions?.initialState !== undefined\n ? sessionOptions.initialState\n : reducer.initialState\n const serverEvents: ContractEvent<D>[] = (\n sessionOptions?.initialEvents ?? []\n )\n .filter((event) => event.index <= frontier)\n .toSorted((a, b) => a.index - b.index)\n\n // ── optimistic overlay ───────────────────────────────────────\n let pending: Pending<D>[] = []\n\n // ── the presence plane ───────────────────────────────────────\n // Never cached, never folded. Foreign entries replicate from the\n // stream; the own entry is the local echo — server copies of self\n // are ignored (the echo is at least as new, and comparing it to\n // server stamps would put two clocks in one order).\n let participant = sessionOptions?.participant\n // Participant and field keys arrive off the wire, so the map and\n // every entry are built null-prototype — see `nullProtoRecord`.\n let presenceState: PresenceMap = nullProtoRecord()\n let presenceLastSentAt = 0\n /** The last LWW stamp issued — see `nextPresenceAt`. */\n let presenceLastAt = 0\n /** Values awaiting the trailing send — non-null exactly while\n * `presenceTimer` is armed. */\n let presenceBuffer: Record<string, unknown> | null = null\n let presenceTimer: ReturnType<typeof setTimeout> | undefined\n /** Own fields set while the stream was down — re-sent once on\n * reconnect (their POSTs may have been lost with the network). */\n let presenceResend: Record<string, unknown> | null = null\n\n // ── store plumbing ───────────────────────────────────────────\n const listeners = new Set<() => void>()\n let snapshot: SessionSnapshot<D, S> | null = null\n let status: ConnectionStatus = 'idle'\n let reconnects = 0\n let lastError: Error | null = null\n let inFlightPushes = 0\n let pushWireTail: Promise<void> = Promise.resolve()\n let deferredHydration: SessionOptions<D, S> | undefined\n let hydrationNotification: ReturnType<typeof setTimeout> | undefined\n\n const emit = (): void => {\n // copy: a listener may unsubscribe (or subscribe) mid-notify\n // oxlint-disable-next-line no-useless-spread\n for (const listener of [...listeners]) listener()\n }\n\n const notify = (): void => {\n snapshot = null\n emit()\n }\n\n const notifyHydrated = (): void => {\n // `session(id, serverFold)` is also called while a destination\n // provider renders. Its own snapshot must advance synchronously,\n // but notifying an outgoing provider during that render would be a\n // React setState-in-render violation.\n snapshot = null\n clearTimeout(hydrationNotification)\n hydrationNotification = setTimeout(emit, 0)\n ;(hydrationNotification as { unref?: () => void }).unref?.()\n }\n\n const mergeServerEvents = (\n events: ContractEvent<D>[] | undefined,\n throughIndex: number,\n ): boolean => {\n if (!events || events.length === 0) return false\n const byIndex = new Map(serverEvents.map((event) => [event.index, event]))\n let changed = false\n for (const event of events) {\n if (event.index > throughIndex) continue\n const existing = byIndex.get(event.index)\n if (existing?.id === event.id) continue\n byIndex.set(event.index, event)\n changed = true\n }\n if (!changed) return false\n serverEvents.splice(\n 0,\n serverEvents.length,\n ...[...byIndex.values()].toSorted((a, b) => a.index - b.index),\n )\n return true\n }\n\n const overlayEvents = (): ContractEvent<D>[] => {\n // Acked entries first (they have a server-assigned order), then\n // unacked in push order with provisional indexes.\n const acked = pending\n .filter((p) => p.acked)\n .map((p) => p.acked!)\n .toSorted((a, b) => a.index - b.index)\n const maxKnown = acked.at(-1)?.index ?? frontier\n const unacked = pending\n .filter((p) => !p.acked)\n .map(\n (p, i) =>\n ({\n id: p.id,\n type: p.type,\n payload: p.payload,\n index: maxKnown + 1 + i, // provisional\n sessionId,\n createdAt: p.createdAt,\n }) as ContractEvent<D>,\n )\n return [...acked, ...unacked]\n }\n\n const connection = (): Connection => {\n switch (status) {\n case 'idle':\n return { status: 'idle' }\n case 'closed':\n return { status: 'closed' }\n case 'live':\n return { status: 'live', reconnects }\n case 'connecting':\n return { status: 'connecting', reconnects, error: lastError }\n }\n }\n\n const historyState = (): HistoryState => {\n const oldestLoaded = serverEvents[0]?.index ?? null\n return {\n loading: historyLoads > 0,\n // Index 1 loaded — or a frontier of 0, an empty log as far as\n // this client knows — means nothing older is left to fetch.\n complete: oldestLoaded === 1 || frontier === 0,\n oldestLoaded,\n }\n }\n\n const buildSnapshot = (): SessionSnapshot<D, S, P> => {\n const overlay = overlayEvents()\n let state = foldedState\n for (const event of overlay) state = reducer.fold(state, event)\n return {\n state,\n events: [...serverEvents, ...overlay],\n index: frontier,\n history: historyState(),\n connection: connection(),\n ...(declaresPresence\n ? { presence: presenceState as PresenceMap<P> }\n : {}),\n } as SessionSnapshot<D, S, P>\n }\n\n // ── presence ─────────────────────────────────────────────────\n /**\n * The per-session monotonic LWW stamp: wall-seeded, guarded\n * `max(now, last + 1)`, so overlapping sends can never invert —\n * the wall clock seeds it because it is the only monotonic state\n * that survives a reload without A2 owning persistence.\n */\n const nextPresenceAt = (): number => {\n presenceLastAt = Math.max(Date.now(), presenceLastAt + 1)\n return presenceLastAt\n }\n\n // Copy-on-write throughout: snapshots alias `presenceState`, so\n // every change replaces the map and the touched entry instead of\n // mutating them.\n const applyPresencePatch = (patch: PresencePatch): void => {\n if (patch.participant === participant) return // the echo is self truth\n const entry: PresenceMap[string] = Object.assign(\n nullProtoRecord<PresenceMap[string]>(),\n presenceState[patch.participant],\n )\n let changed = false\n for (const [field, value] of Object.entries(patch.values)) {\n if (value === undefined) continue\n const existing = entry[field]\n // Field-wise LWW: an older stamp loses, a tie goes to the wire.\n if (existing && existing.at.getTime() > patch.at.getTime()) continue\n if (value === null) {\n if (existing) {\n delete entry[field]\n changed = true\n }\n } else {\n entry[field] = { value, seen: patch.seen, at: patch.at }\n changed = true\n }\n }\n if (!changed) return\n const next = Object.assign(nullProtoRecord<PresenceMap>(), presenceState)\n if (Object.keys(entry).length === 0) delete next[patch.participant]\n else next[patch.participant] = entry\n presenceState = next\n notify()\n }\n\n const applyPresenceSnapshot = (incoming: PresenceMap): void => {\n const next: PresenceMap = nullProtoRecord()\n for (const [id, fields] of Object.entries(incoming)) {\n if (id === participant) continue\n next[id] = fields\n }\n // Foreign entries replaced wholesale; the own echo survives.\n const own =\n participant === undefined ? undefined : presenceState[participant]\n if (participant !== undefined && own !== undefined)\n next[participant] = own\n presenceState = next\n notify()\n }\n\n const echoPresence = (\n id: string,\n values: Record<string, unknown>,\n atMs: number,\n ): void => {\n const entry: PresenceMap[string] = Object.assign(\n nullProtoRecord<PresenceMap[string]>(),\n presenceState[id],\n )\n const at = new Date(atMs)\n for (const [field, value] of Object.entries(values)) {\n if (value === null) delete entry[field]\n else entry[field] = { value, seen: frontier, at }\n }\n const next = Object.assign(nullProtoRecord<PresenceMap>(), presenceState)\n if (Object.keys(entry).length === 0) delete next[id]\n else next[id] = entry\n presenceState = next\n notify()\n }\n\n // One send, fire-and-forget: no retry, no rollback, failures\n // dropped silently — the next send repaints. Sends are NOT\n // serialized on the ack and may overlap in flight: safe, because\n // merges are LWW by `at` per (participant, field) and one\n // sender's stamps are monotonic. `seen` is the frontier at send\n // time, not at set time; `at` is the sender's monotonic stamp.\n const sendPresence = (\n values: Record<string, unknown>,\n at: number,\n ): void => {\n presenceLastSentAt = Date.now()\n transport.sendPresence({\n sessionId,\n // Every path here starts at setPresence, which requires the\n // participant.\n presence: { participant: participant!, values, seen: frontier, at },\n })\n touch(sessionId)\n }\n\n // The fixed-cadence throttle: a set outside the interval sends on\n // the leading edge; within it, values merge field-wise into one\n // trailing send at the boundary. The cadence never couples to RTT\n // — waiting on the ack served no purpose on this plane and\n // stretched remote motion on slow links.\n const queuePresenceSend = (\n values: Record<string, unknown>,\n at?: number,\n ): void => {\n if (presenceTimer !== undefined) {\n presenceBuffer = { ...presenceBuffer, ...values }\n return\n }\n const wait =\n presenceLastSentAt + PRESENCE_TIMINGS.sendIntervalMs - Date.now()\n if (wait <= 0) {\n sendPresence(values, at ?? nextPresenceAt())\n return\n }\n presenceBuffer = { ...presenceBuffer, ...values }\n presenceTimer = setTimeout(() => {\n presenceTimer = undefined\n const buffered = presenceBuffer\n presenceBuffer = null\n // The trailing send stamps at fire time — always past every\n // stamp its merged values echoed with.\n if (buffered) sendPresence(buffered, nextPresenceAt())\n }, wait)\n ;(presenceTimer as { unref?: () => void }).unref?.()\n }\n\n const setPresence = (values: PresencePatch<P>['values']): void => {\n if (participant === undefined) {\n throw new TypeError(\n 'setPresence requires a participant — pass one in the session options (the SessionProvider participant prop)',\n )\n }\n const validated: Record<string, unknown> = nullProtoRecord()\n for (const [field, value] of Object.entries(values)) {\n if (value === undefined) continue\n const schema = Object.hasOwn(presenceDefs, field)\n ? presenceDefs[field]\n : undefined\n if (!schema) {\n throw new A2Error(\n 'UNKNOWN_PRESENCE_FIELD',\n `no presence field '${field}' in the reducer's vocabulary`,\n )\n }\n if (value === null) {\n validated[field] = null\n continue\n }\n const result = validateSync(schema, value, `presence field '${field}'`)\n if (result.issues) {\n throw new A2Error(\n 'INVALID_PAYLOAD',\n `invalid value for presence field '${field}'`,\n { details: result.issues },\n )\n }\n validated[field] = result.value\n }\n if (Object.keys(validated).length === 0) return\n // One stamp per set: the echo and a leading send share it, so\n // the local map and the wire agree on this write's LWW position.\n const at = nextPresenceAt()\n echoPresence(participant, validated, at)\n if (status !== 'live') {\n presenceResend = { ...presenceResend, ...validated }\n }\n queuePresenceSend(validated, at)\n touch(sessionId)\n }\n\n /** Pushes awaiting stream confirmation — resolved by `ingest` the\n * moment the frontier passes their batch. */\n let confirmWatchers: Array<{ index: number; resolve: () => void }> = []\n\n /** The single ingest point: every server-confirmed event, in log\n * order, from the stream. */\n const ingest = (event: ContractEvent<D>): void => {\n if (event.index <= frontier) return\n frontier = event.index\n serverEvents.push(event)\n foldedState = reducer.fold(foldedState, event)\n pending = pending.filter((p) => p.id !== event.id)\n if (confirmWatchers.some((w) => w.index <= frontier)) {\n const due = confirmWatchers.filter((w) => w.index <= frontier)\n confirmWatchers = confirmWatchers.filter((w) => w.index > frontier)\n for (const watcher of due) watcher.resolve()\n }\n notify()\n touch(sessionId)\n }\n\n // ── push ─────────────────────────────────────────────────────\n const validated = (\n events: AppendInput<D>[],\n ): Array<{ id: string; type: string; payload: unknown }> =>\n events.map((event) => {\n const schema = Object.hasOwn(reducer.events, event.type)\n ? reducer.events[event.type]\n : undefined\n if (!schema) {\n throw new A2Error(\n 'UNKNOWN_EVENT_TYPE',\n `no event type '${String(event.type)}' in the reducer's vocabulary`,\n )\n }\n const result = validateSync(\n schema,\n event.payload,\n `event '${String(event.type)}'`,\n )\n if (result.issues) {\n throw new A2Error(\n 'INVALID_PAYLOAD',\n `invalid payload for event '${String(event.type)}'`,\n { details: result.issues },\n )\n }\n return {\n id: event.id ?? crypto.randomUUID(),\n type: event.type,\n payload: result.value,\n }\n })\n\n const post = async (\n body: TransportPushBody,\n ): Promise<ContractEvent<D>[]> => {\n let lastPushError: A2Error = new A2Error(\n 'STORE_UNAVAILABLE',\n 'push failed',\n )\n for (let attempt = 1; attempt <= PUSH_ATTEMPTS; attempt += 1) {\n try {\n const rows = await transport.push(body)\n return rows.map((row) => eventFromWire(row) as ContractEvent<D>)\n } catch (err) {\n lastPushError =\n err instanceof A2Error\n ? err\n : new A2Error('STORE_UNAVAILABLE', 'push request failed', {\n cause: err,\n })\n }\n // Only the documented retryable code retries; identical ids\n // make the retry idempotent on the server.\n if (lastPushError.code !== 'STORE_UNAVAILABLE') throw lastPushError\n if (attempt < PUSH_ATTEMPTS) {\n const sleepMs = 250 * attempt\n // Wait for the clock AND, when the transport can tell us,\n // the wire. The pacing sleep is a floor: a failure acked\n // over a healthy socket (a shedding store) must not re-fire\n // at wire speed. A push caught in a routine reconnect blip\n // additionally parks until the subscription is confirmed;\n // that cap scales with the reconnect backoff so an\n // unreachable server still exhausts attempts promptly.\n await Promise.all([\n new Promise((resolve) => setTimeout(resolve, sleepMs)),\n transport.awaitReady?.(\n sessionId,\n sleepMs + STREAM_TIMINGS.reconnectBaseMs * attempt,\n ),\n ])\n }\n }\n throw lastPushError\n }\n\n /**\n * Decorate the ack promise into a PushResult. `confirmed` is a\n * lazy getter — materialized on first access, so callers that\n * ignore it can't leak an unhandled rejection.\n */\n const withConfirmed = (ack: Promise<ContractEvent<D>[]>): PushResult<D> => {\n let confirmed: Promise<ContractEvent<D>[]> | undefined\n return Object.defineProperty(ack as PushResult<D>, 'confirmed', {\n get(): Promise<ContractEvent<D>[]> {\n confirmed ??= ack.then(\n (acked) =>\n new Promise<ContractEvent<D>[]>((resolve) => {\n const last = acked.at(-1)?.index ?? 0\n if (frontier >= last) {\n resolve(acked)\n return\n }\n confirmWatchers.push({\n index: last,\n resolve: () => resolve(acked),\n })\n }),\n )\n return confirmed\n },\n })\n }\n\n const push = (...events: AppendInput<D>[]): PushResult<D> => {\n touch(sessionId)\n return withConfirmed(\n (async () => {\n if (events.length === 0) {\n throw new TypeError('push requires at least one event')\n }\n const entries: Pending<D>[] = validated(events).map((e) => ({\n id: e.id,\n type: e.type,\n payload: e.payload,\n createdAt: new Date(),\n }))\n // Reserve before notifying: a subscriber may reenter push(),\n // and that work must follow the call whose state it observed.\n const waitForPriorPush = pushWireTail\n let releasePush!: () => void\n pushWireTail = new Promise<void>((resolve) => {\n releasePush = resolve\n })\n pending.push(...entries)\n inFlightPushes += 1\n\n try {\n notify() // optimistic — the view updates before the network moves\n await waitForPriorPush\n const acked = await post({\n sessionId,\n events: entries.map(({ id, type, payload }) => ({\n id,\n type,\n payload,\n })),\n })\n for (const event of acked) {\n const entry = pending.find((p) => p.id === event.id)\n if (entry) entry.acked = event // stream retires it at its index\n }\n notify()\n return acked\n } catch (err) {\n // Rollback: refold without the rejected events.\n const ids = new Set(entries.map((e) => e.id))\n pending = pending.filter((p) => !ids.has(p.id))\n notify()\n throw err\n } finally {\n releasePush()\n inFlightPushes -= 1\n if (inFlightPushes === 0 && deferredHydration) {\n const next = deferredHydration\n deferredHydration = undefined\n runtime.hydrate(next)\n }\n touch(sessionId)\n }\n })(),\n )\n }\n\n // ── backscroll ───────────────────────────────────────────────\n // Cold reads of the log below the frontier. Fetched events merge\n // into `serverEvents` only — never the fold, never the overlay.\n let historyLoads = 0\n /** Calls chain per session: each resolves its bounds after the\n * prior call's merge, so overlapping backscrolls page backward\n * instead of re-fetching the same range. */\n let historyTail: Promise<unknown> = Promise.resolve()\n\n const eventsBetween = (gte: number, lte: number): ContractEvent<D>[] =>\n serverEvents.filter((event) => event.index >= gte && event.index <= lte)\n\n /** The narrowest sub-range of [gte, lte] missing from the feed, or\n * null when the range is fully loaded (nothing to fetch). */\n const missingBetween = (\n gte: number,\n lte: number,\n ): { gte: number; lte: number } | null => {\n const present = new Set(eventsBetween(gte, lte).map((e) => e.index))\n let lo = gte\n while (lo <= lte && present.has(lo)) lo += 1\n if (lo > lte) return null\n let hi = lte\n while (hi > lo && present.has(hi)) hi -= 1\n return { gte: lo, lte: hi }\n }\n\n const loadHistory = (\n loadOptions?: LoadHistoryOptions,\n ): Promise<ContractEvent<D>[]> => {\n const readHistory = transport.history\n if (readHistory === undefined) {\n throw new TypeError(\n 'loadHistory requires an http api — the ws transport has no history lane; serve the session over handle() and pass its route url as the string or { type: \"http\" } api',\n )\n }\n const before = loadOptions?.before\n const limit = loadOptions?.limit\n if (\n before !== undefined &&\n (!Number.isSafeInteger(before) || before < 1)\n ) {\n throw new TypeError(\n 'loadHistory before must be a positive safe integer',\n )\n }\n if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 1)) {\n throw new TypeError('loadHistory limit must be a positive safe integer')\n }\n touch(sessionId)\n const run = historyTail.then(async () => {\n const upper = before ?? serverEvents[0]?.index ?? frontier + 1\n // Strictly at or below the frontier: the live stream owns\n // everything above it, and the optimistic overlay must never\n // collide with a backscrolled row.\n const lte = Math.min(upper - 1, frontier)\n const gte = Math.max(1, upper - (limit ?? DEFAULT_HISTORY_LIMIT))\n if (gte > lte) return []\n const missing = missingBetween(gte, lte)\n if (missing === null) return eventsBetween(gte, lte)\n historyLoads += 1\n notify()\n try {\n const rows = await readHistory({ sessionId, ...missing })\n mergeServerEvents(\n rows.map((row) => eventFromWire(row) as ContractEvent<D>),\n frontier,\n )\n return eventsBetween(gte, lte)\n } finally {\n historyLoads -= 1\n notify()\n touch(sessionId)\n }\n })\n // Failures reject the caller, never the chain.\n historyTail = run.catch(() => {})\n return run\n }\n\n // ── the live stream ──────────────────────────────────────────\n // A run-generation model instead of a terminal `closed` flag:\n // close() bumps the generation (the running loop notices and\n // exits), connect() starts a fresh one. Reentrant by design.\n let generation = 0\n let active = false\n let abort: AbortController | null = null\n\n const runStream = async (run: number): Promise<void> => {\n let backoff = STREAM_TIMINGS.reconnectBaseMs\n // oxlint-disable no-await-in-loop -- sequential reconnect loop\n // oxlint-disable-next-line no-unmodified-loop-condition -- close() bumps `generation`\n while (generation === run) {\n const controller = new AbortController()\n abort = controller\n // The stall watchdog: the server heartbeats the stream, so a\n // healthy connection is never silent for long. Silence past the\n // timeout means the connection is dead in a way the socket\n // won't report (an offline browser's established SSE socket\n // neither errors nor receives) — abort it and let the normal\n // reconnect path run. This is what keeps `status: 'live'`\n // honest.\n let stall: ReturnType<typeof setTimeout> | undefined\n let stalled = false\n const armStall = (): void => {\n clearTimeout(stall)\n stall = setTimeout(() => {\n stalled = true\n controller.abort()\n }, STREAM_TIMINGS.stallTimeoutMs)\n ;(stall as { unref?: () => void }).unref?.()\n }\n let wasLive = false\n try {\n const frames = transport.connect({\n sessionId,\n startAfter: frontier,\n signal: controller.signal,\n })\n for await (const frame of frames) {\n if (generation !== run) break\n // Every frame counts — heartbeats included. Liveness is\n // about the connection, not about events.\n armStall()\n if (!wasLive) {\n // The first frame is the connection signal: the transport\n // yields it the moment the stream is established.\n backoff = STREAM_TIMINGS.reconnectBaseMs\n wasLive = true\n status = 'live'\n lastError = null\n notify()\n if (presenceResend) {\n const values = presenceResend\n presenceResend = null\n queuePresenceSend(values)\n }\n }\n if (frame.kind === 'event') {\n ingest(eventFromWire(frame.event) as ContractEvent<D>)\n } else if (frame.kind === 'presence') {\n applyPresencePatch(presencePatchFromWire(frame.patch))\n } else if (frame.kind === 'presence-snapshot') {\n applyPresenceSnapshot(\n presenceSnapshotFromWire(frame.snapshot).snapshot,\n )\n }\n }\n // The transport ended the stream cleanly — reconnect, but it\n // isn't an error.\n if (generation === run) {\n lastError = null\n }\n } catch (err) {\n if (generation === run) {\n lastError = stalled\n ? new Error(\n `stream stalled: no data for ${STREAM_TIMINGS.stallTimeoutMs}ms`,\n )\n : err instanceof Error\n ? err\n : new Error(String(err))\n }\n } finally {\n clearTimeout(stall)\n }\n if (generation !== run) break\n if (wasLive) reconnects += 1\n status = 'connecting'\n notify()\n await new Promise<void>((resolve) => {\n let settled = false\n const finish = (): void => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n // oxlint-disable-next-line no-multiple-resolved -- guarded above\n resolve()\n }\n const timer = setTimeout(finish, backoff)\n ;(timer as { unref?: () => void }).unref?.()\n abort?.signal.addEventListener('abort', finish)\n })\n backoff = Math.min(backoff * 2, STREAM_TIMINGS.reconnectMaxMs)\n }\n // oxlint-enable no-await-in-loop\n }\n\n const runtime: SessionRuntime<D, S, P> = {\n sessionId,\n // Only on presence-declaring contracts — the type surface\n // promises no runtime-inert members (a2-api.md §12).\n ...(declaresPresence ? { setPresence } : {}),\n subscribe(listener) {\n touch(sessionId)\n listeners.add(listener)\n return () => {\n listeners.delete(listener)\n touch(sessionId)\n }\n },\n getSnapshot() {\n snapshot ??= buildSnapshot()\n return snapshot\n },\n push,\n loadHistory,\n connect() {\n touch(sessionId)\n if (active) return\n active = true\n status = 'connecting'\n notify()\n void runStream(generation)\n },\n close() {\n touch(sessionId)\n if (!active) return\n active = false\n generation += 1 // the running loop notices and exits\n abort?.abort()\n abort = null\n // A pending trailing send dies with the stream — losing one is\n // fine by definition; sets made while closed accumulate in\n // `presenceResend` and repaint after the next connect.\n clearTimeout(presenceTimer)\n presenceTimer = undefined\n presenceBuffer = null\n status = 'closed'\n notify()\n },\n hydrate(next) {\n touch(sessionId)\n if (!next) return\n participant ??= next.participant\n const nextIndex = next.initialIndex ?? 0\n if (nextIndex > frontier && inFlightPushes > 0) {\n if (\n !deferredHydration ||\n nextIndex > (deferredHydration.initialIndex ?? 0)\n ) {\n deferredHydration = next\n }\n return\n }\n\n const historyChanged = mergeServerEvents(\n next.initialEvents,\n Math.min(nextIndex, frontier),\n )\n if (next.initialState === undefined || nextIndex <= frontier) {\n if (historyChanged) notifyHydrated()\n return\n }\n\n // The server fold subsumes everything observed through the old\n // frontier. Keep later optimistic work overlaid, retiring acked\n // entries the new frontier proves are already durable.\n frontier = nextIndex\n foldedState = next.initialState\n mergeServerEvents(next.initialEvents, frontier)\n pending = pending.filter(\n (entry) => !entry.acked || entry.acked.index > frontier,\n )\n if (confirmWatchers.some((watcher) => watcher.index <= frontier)) {\n const due = confirmWatchers.filter(\n (watcher) => watcher.index <= frontier,\n )\n confirmWatchers = confirmWatchers.filter(\n (watcher) => watcher.index > frontier,\n )\n for (const watcher of due) watcher.resolve()\n }\n notifyHydrated()\n },\n canEvict() {\n return (\n listeners.size === 0 &&\n !active &&\n inFlightPushes === 0 &&\n historyLoads === 0 &&\n confirmWatchers.length === 0 &&\n presenceTimer === undefined\n )\n },\n } as SessionRuntime<D, S, P>\n\n return runtime\n }\n\n return {\n session(sessionId, sessionOptions) {\n const existing = sessions.get(sessionId)\n if (existing) {\n existing.runtime.hydrate(sessionOptions)\n touch(sessionId)\n return existing.runtime\n }\n\n const runtime = makeSession(sessionId, sessionOptions)\n sessions.set(sessionId, { runtime })\n touch(sessionId)\n return runtime\n },\n }\n}\n"],"mappings":"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"}
|
|
@@ -32,6 +32,25 @@ type Connection = {
|
|
|
32
32
|
type PushResult<D extends EventDefs> = Promise<ContractEvent<D>[]> & {
|
|
33
33
|
readonly confirmed: Promise<ContractEvent<D>[]>;
|
|
34
34
|
};
|
|
35
|
+
type LoadHistoryOptions = {
|
|
36
|
+
/**
|
|
37
|
+
* Exclusive upper bound for the fetch. Defaults to the oldest
|
|
38
|
+
* already-loaded index — successive calls page backward — or one
|
|
39
|
+
* past the hydration frontier while nothing is loaded yet.
|
|
40
|
+
*/
|
|
41
|
+
before?: number;
|
|
42
|
+
/** Page size; defaults to 50. */
|
|
43
|
+
limit?: number;
|
|
44
|
+
};
|
|
45
|
+
/** Backscroll progress, derived from the raw event feed. */
|
|
46
|
+
type HistoryState = {
|
|
47
|
+
/** A `loadHistory` fetch is in flight. */
|
|
48
|
+
loading: boolean;
|
|
49
|
+
/** The feed reaches index 1 — nothing older remains to fetch. */
|
|
50
|
+
complete: boolean;
|
|
51
|
+
/** The oldest loaded index; `null` while nothing is loaded. */
|
|
52
|
+
oldestLoaded: number | null;
|
|
53
|
+
};
|
|
35
54
|
/** One immutable view of the session — stable identity between changes. */
|
|
36
55
|
type SessionSnapshot<D extends EventDefs, S, P extends PresenceDefs = Record<never, never>> = WithPresence<P, {
|
|
37
56
|
/** The replicated presence map, this client included (local echo). */
|
|
@@ -39,12 +58,16 @@ type SessionSnapshot<D extends EventDefs, S, P extends PresenceDefs = Record<nev
|
|
|
39
58
|
}> & {
|
|
40
59
|
/** The live view: server events folded, optimistic pushes applied. */
|
|
41
60
|
state: S;
|
|
42
|
-
/** The
|
|
43
|
-
*
|
|
61
|
+
/** The raw event feed: server truth — observed, seeded, or
|
|
62
|
+
* backscrolled via `loadHistory` — plus pending optimistic events
|
|
63
|
+
* (provisional indexes past the frontier). `state` never folds
|
|
64
|
+
* backscrolled events; they are display data below the frontier. */
|
|
44
65
|
events: ContractEvent<D>[];
|
|
45
66
|
/** The stream frontier: the last server-confirmed index. This is the
|
|
46
67
|
* `lastSeenIndex` cancellation wants. */
|
|
47
68
|
index: number;
|
|
69
|
+
/** Backscroll progress — `loadHistory` moves it. */
|
|
70
|
+
history: HistoryState;
|
|
48
71
|
connection: Connection;
|
|
49
72
|
};
|
|
50
73
|
/**
|
|
@@ -78,6 +101,17 @@ type SessionClient<D extends EventDefs, S, P extends PresenceDefs = Record<never
|
|
|
78
101
|
* their optimistic folds.
|
|
79
102
|
*/
|
|
80
103
|
push(...events: AppendInput<D>[]): PushResult<D>;
|
|
104
|
+
/**
|
|
105
|
+
* Backscroll: fetch a bounded slice of events from below the
|
|
106
|
+
* frontier and merge it into `events` — deduped, ordered, shared by
|
|
107
|
+
* every handle of this session. Folded state and the optimistic
|
|
108
|
+
* overlay are never touched. Calls serialize per session and skip
|
|
109
|
+
* already-loaded ranges, so overlapping backscrolls never fetch the
|
|
110
|
+
* same range twice. Resolves with the events in the requested range.
|
|
111
|
+
* Requires an http api; the `ws` transport has no history lane and
|
|
112
|
+
* this throws a TypeError there.
|
|
113
|
+
*/
|
|
114
|
+
loadHistory(options?: LoadHistoryOptions): Promise<ContractEvent<D>[]>;
|
|
81
115
|
/** Open the live stream (idempotent while open). Reconnects with
|
|
82
116
|
* backoff and resumes from the frontier until `close()`. */
|
|
83
117
|
connect(): void;
|
|
@@ -107,7 +141,8 @@ type A2Client<D extends EventDefs, S, P extends PresenceDefs = Record<never, nev
|
|
|
107
141
|
* The wire the client rides. The string form is one route serving GET
|
|
108
142
|
* (SSE stream) + POST (push). `http` splits the verbs across two
|
|
109
143
|
* routes, for when platform duration limits differ per verb. `ws`
|
|
110
|
-
* rides everything over one
|
|
144
|
+
* rides everything — every session of this client — over one
|
|
145
|
+
* multiplexed WebSocket, served by `sessionsSocket`.
|
|
111
146
|
*/
|
|
112
147
|
type ClientApi = string | {
|
|
113
148
|
type: "http";
|
|
@@ -152,4 +187,5 @@ type CreateClientOptions<D extends EventDefs, S, P extends PresenceDefs = Record
|
|
|
152
187
|
};
|
|
153
188
|
declare function createClient<D extends EventDefs, S, P extends PresenceDefs = Record<never, never>>(options: CreateClientOptions<D, S, P>): A2Client<D, S, P>;
|
|
154
189
|
//#endregion
|
|
155
|
-
export { Connection as a,
|
|
190
|
+
export { Connection as a, HistoryState as c, SessionClient as d, SessionClientPresence as f, createClient as h, ClientWebSocketConstructor as i, LoadHistoryOptions as l, SessionSnapshot as m, ClientApi as n, ConnectionStatus as o, SessionOptions as p, ClientWebSocket as r, CreateClientOptions as s, A2Client as t, PushResult as u };
|
|
191
|
+
//# sourceMappingURL=client-D7mvIXrF.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-D7mvIXrF.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";;KAoDY;;;;;;;KAQA;EACN;;EAEA;;EAEA;;EAEA,OAAO;;EAEP;EAAgB;;EAChB;;;;;;;;;;KAUM,WAAW,UAAU,aAAa,QAAQ,cAAc;WACzD,WAAW,QAAQ,cAAc;;KAGhC;;;;;;EAMV;;EAEA;;;KAIU;;EAEV;;EAEA;;EAEA;;;KAIU,gBACV,UAAU,WACV,GACA,UAAU,eAAe,wBACvB,aACF;;EAGE,UAAU,YAAY;;;EAIxB,OAAO;;;;;EAKP,QAAQ,cAAc;;;EAGtB;;EAEA,SAAS;EACT,YAAY;;;;;;;KAQF,sBAAsB,UAAU;;;;;;;;;;EAU1C,YAAY,QAAQ,cAAc;;KAGxB,cACV,UAAU,WACV,GACA,UAAU,eAAe,wBACvB,aAAa,GAAG,sBAAsB;WAC/B;EACT,UAAU;EACV,eAAe,gBAAgB,GAAG,GAAG;;;;;;;;;;EAUrC,QAAQ,QAAQ,YAAY,OAAO,WAAW;;;;;;;;;;;EAW9C,YAAY,UAAU,qBAAqB,QAAQ,cAAc;;;EAGjE;;;;;;EAMA;;KAGU,eAAe,UAAU,WAAW;EAC9C,eAAe;EACf;;EAEA,gBAAgB,cAAc;;;;;;EAM9B;;KAGU,SACV,UAAU,WACV,GACA,UAAU,eAAe;EAEzB,QACE,mBACA,UAAU,eAAe,GAAG,KAC3B,cAAc,GAAG,GAAG;;;;;;;;;KAUb;EAEN;EAAc;EAAc;;EAC5B;EAAY;;;;;;;KAON;WACD;EACT,KAAK;EACL,MAAM,eAAe;EACrB,iBAAiB,cAAc;EAC/B,iBACE,iBACA,WAAW;IAAS;;EAEtB,iBACE,eACA,WAAW;IAAS;IAAc;;EAEpC,iBAAiB,eAAe;;KAGtB,kCAAkC,gBAAgB;KAElD,oBACV,UAAU,WACV,GACA,UAAU,eAAe;EAEzB,SAAS,QAAQ,GAAG,GAAG;;EAEvB,KAAK;;EAEL,eAAe,WAAW;;;EAG1B,YAAY;;;EAGZ;;iBAirBc,aACd,UAAU,WACV,GACA,UAAU,eAAe,sBACzB,SAAS,oBAAoB,GAAG,GAAG,KAAK,SAAS,GAAG,GAAG"}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as Connection, c as
|
|
2
|
-
export { A2Client, ClientApi, ClientWebSocket, ClientWebSocketConstructor, Connection, ConnectionStatus, CreateClientOptions, PushResult, SessionClient, SessionClientPresence, SessionOptions, SessionSnapshot, createClient };
|
|
1
|
+
import { a as Connection, c as HistoryState, d as SessionClient, f as SessionClientPresence, h as createClient, i as ClientWebSocketConstructor, l as LoadHistoryOptions, m as SessionSnapshot, n as ClientApi, o as ConnectionStatus, p as SessionOptions, r as ClientWebSocket, s as CreateClientOptions, t as A2Client, u as PushResult } from "./client-D7mvIXrF.js";
|
|
2
|
+
export { A2Client, ClientApi, ClientWebSocket, ClientWebSocketConstructor, Connection, ConnectionStatus, CreateClientOptions, HistoryState, LoadHistoryOptions, PushResult, SessionClient, SessionClientPresence, SessionOptions, SessionSnapshot, createClient };
|
package/dist/client.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createClient } from "./client-
|
|
1
|
+
import { t as createClient } from "./client-BKlyLiOU.js";
|
|
2
2
|
export { createClient };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-48bUMgcL.js","names":[],"sources":["../src/reducer.ts","../src/contract.ts"],"sourcesContent":["import type { ContractEvent, EventDefs, PresenceDefs } from './contract.ts'\nimport type { StandardSchemaV1 } from './standard-schema.ts'\nimport { validateSync } from './validate.ts'\n\n/**\n * A pure fold over a session's events — `(state, event) => state` —\n * derived from a contract via `contract.reducer(options).fold(fn)`.\n *\n * The name is the reducer's identity. Cached folds (server snapshots,\n * the browser cache) are keyed by it, which makes it the invalidation\n * knob too: changed the fold's logic? Change the name. Snapshots under\n * the old name are ignored and the next read refolds from raw events.\n * See docs/concepts/04-state.mdx.\n */\nexport type Reducer<\n D extends EventDefs = EventDefs,\n S = unknown,\n P extends PresenceDefs = Record<never, never>,\n> = {\n /** Identity and cache key. Rename when the fold's logic changes. */\n readonly name: string\n /** The contract's vocabulary — types every event in the fold. */\n readonly events: Readonly<D>\n /**\n * The contract's presence vocabulary — carried because the reducer\n * is the client's typed handle on the contract. The fold never sees\n * presence.\n */\n readonly presence: Readonly<P>\n readonly fold: (state: S, event: ContractEvent<D>) => S\n readonly initialState: S\n /**\n * When present: `initialState` was validated against it at\n * definition, and any cached fold a2 rehydrates (server snapshots,\n * the browser cache) is checked against it — failing snapshots are\n * discarded and refolded, catching shape drift a stale name can't.\n */\n readonly stateSchema: StandardSchemaV1<unknown, S> | undefined\n}\n\n/** The second step of `contract.reducer` — see the method's doc. */\nexport type ReducerBuilder<\n D extends EventDefs,\n S,\n P extends PresenceDefs = Record<never, never>,\n> = {\n fold(fold: (state: S, event: ContractEvent<D>) => S): Reducer<D, S, P>\n}\n\n/** Internal — reducers are created through `contract.reducer(...).fold(...)`. */\nexport function makeReducerBuilder<\n D extends EventDefs,\n P extends PresenceDefs,\n Seed,\n S,\n>(\n events: Readonly<D>,\n presence: Readonly<P>,\n options: {\n name: string\n initialState: Seed\n stateSchema?: StandardSchemaV1<Seed, S>\n },\n): ReducerBuilder<D, S, P> {\n const { name } = options\n if (typeof name !== 'string' || name.length === 0) {\n throw new TypeError(\n 'reducer name must be a non-empty string — it identifies the reducer and keys cached snapshots',\n )\n }\n\n // Without a schema, S = Seed (the type parameter default), so the\n // seed is the state. With one, the seed is the schema's input and the\n // validated output (defaults applied) becomes the fold's actual seed.\n let initialState = options.initialState as unknown as S\n if (options.stateSchema) {\n const result = validateSync(\n options.stateSchema,\n options.initialState,\n 'the stateSchema',\n )\n if (result.issues) {\n throw new TypeError(\n `reducer initialState does not match its stateSchema: ${result.issues\n .map((issue) => issue.message)\n .join('; ')}`,\n )\n }\n initialState = result.value as S\n }\n\n return {\n fold(fold) {\n if (typeof fold !== 'function') {\n throw new TypeError('reducer fold must be a function')\n }\n return {\n name,\n events,\n presence,\n fold,\n initialState,\n stateSchema: options.stateSchema as\n StandardSchemaV1<unknown, S> | undefined,\n }\n },\n }\n}\n","/**\n * The contract — a2's primary noun. A named event vocabulary, shared by\n * everything: the server implements it (experimental-a2/server's `createServer`),\n * reducers derive from it (`contract.reducer`), the browser types its\n * pushes off it. Isomorphic by construction: this module imports\n * nothing but schema plumbing.\n *\n * The contract says what can be *said*; the server says how it's\n * *reacted to*; a reducer says how it's *seen*.\n */\n\nimport { makeReducerBuilder, type ReducerBuilder } from './reducer.ts'\nimport type { StandardSchemaV1 } from './standard-schema.ts'\nimport { assertSyncSchema } from './validate.ts'\n\n/** The event vocabulary shape: event name → payload schema. */\nexport type EventDefs = Record<string, StandardSchemaV1>\n\n/** The presence vocabulary shape: field name → value schema. */\nexport type PresenceDefs = Record<string, StandardSchemaV1>\n\n/** One event of a contract, narrowed to type `K`. */\nexport type ContractEvent<\n D extends EventDefs,\n K extends keyof D & string = keyof D & string,\n> = {\n [T in keyof D & string]: {\n id: string\n type: T\n payload: StandardSchemaV1.InferOutput<D[T]>\n index: number\n sessionId: string\n createdAt: Date\n }\n}[K]\n\n/** Input to `append`/`push`, typed against the contract's schemas. */\nexport type AppendInput<D extends EventDefs> = {\n [T in keyof D & string]: {\n type: T\n payload: StandardSchemaV1.InferInput<D[T]>\n id?: string\n }\n}[keyof D & string]\n\nexport type EventBatchFactory<D extends EventDefs> = <\n const E extends readonly AppendInput<D>[],\n>(\n ...events: E\n) => E\n\n/**\n * One presence update: a participant's changed fields (`null` marks a\n * cleared one), stamped with the sender's stream frontier (`seen`).\n * This is what `stream({ presence: true })` interleaves with events.\n */\nexport type PresencePatch<P extends PresenceDefs = PresenceDefs> = {\n participant: string\n values: {\n [F in keyof P & string]?: StandardSchemaV1.InferOutput<P[F]> | null\n }\n seen: number\n at: Date\n}\n\n/**\n * The replicated ephemeral map: participant → field → latest value,\n * last-writer-wins per field. Never stored in the log; how long a\n * value stays painted is view logic, decided at render time against\n * `at`.\n */\nexport type PresenceMap<P extends PresenceDefs = PresenceDefs> = {\n [participant: string]: {\n [F in keyof P & string]?: {\n value: StandardSchemaV1.InferOutput<P[F]>\n seen: number\n at: Date\n }\n }\n}\n\n/**\n * The one item a presence stream yields before live patches: the\n * current pruned map, per-field stamps exact. Discriminate\n * structurally (`'snapshot' in item`).\n */\nexport type PresenceSnapshot<P extends PresenceDefs = PresenceDefs> = {\n snapshot: PresenceMap<P>\n}\n\n/**\n * Intersect into a surface type to add `Members` only when the\n * contract declares presence fields — on presence-less contracts the\n * members do not exist, a type error rather than a runtime throw.\n */\nexport type WithPresence<\n P extends PresenceDefs,\n Members,\n> = keyof P extends never ? unknown : Members\n\nexport type ReducerOptions<Seed, S> = {\n /**\n * Identity and snapshot cache key. Rename it when the fold's logic\n * changes — snapshots under the old name are ignored and refolded.\n */\n name: string\n /**\n * The seed the fold starts from. With a `stateSchema`, it's typed as\n * the schema's *input*: validated at definition, and the validated\n * output (defaults applied) is what the fold actually starts from.\n */\n initialState: Seed\n /**\n * Optional: declares the state's shape (the fold's state type becomes\n * the schema's output) and guards cached folds — server snapshots and\n * the browser cache reject mismatching shapes and refold.\n */\n stateSchema?: StandardSchemaV1<Seed, S>\n}\n\nexport type Contract<\n D extends EventDefs = EventDefs,\n P extends PresenceDefs = Record<never, never>,\n> = {\n /** Identity — prefixes storage keys and queue messages. */\n readonly name: string\n /** The vocabulary. */\n readonly events: Readonly<D>\n /** The presence vocabulary — empty unless declared. */\n readonly presence: Readonly<P>\n /** Preserve literal types for a handler-returned event batch. */\n readonly batch: EventBatchFactory<D>\n /**\n * Derive a view from this contract. The reducer's `name` is its\n * identity and snapshot cache key: rename it when the fold's logic\n * changes. Two steps by design: this call fixes the state and event\n * types, so `.fold()` receives fully concrete types — literal unions\n * in your state survive, with zero annotations. (A single call can't\n * do this: TypeScript never contextually types the returns of a\n * context-sensitive argument against generics inferred in the same\n * call.)\n */\n reducer<Seed, S = Seed>(\n options: ReducerOptions<Seed, S>,\n ): ReducerBuilder<D, S, P>\n}\n\n/**\n * Define a contract: a name plus the events it understands. Validators\n * must be synchronous — async ones are rejected here, at definition\n * time. The result is a plain, importable, isomorphic value.\n */\nexport function contract<\n D extends EventDefs,\n P extends PresenceDefs = Record<never, never>,\n>(options: {\n /** Identity — prefixes storage keys and queue messages. */\n name: string\n /** The vocabulary: event name → payload schema (Standard Schema). */\n events: D\n /**\n * Optional second vocabulary: presence field → value schema.\n * Per-participant ephemeral state, replicated last-writer-wins and\n * never stored in the log — reducers cannot see it.\n */\n presence?: P\n}): Contract<D, P> {\n const { name } = options\n if (typeof name !== 'string' || name.length === 0) {\n throw new TypeError('contract name must be a non-empty string')\n }\n if (name.includes('\\u001f')) {\n throw new TypeError('contract name contains a reserved control character')\n }\n const defs = options.events\n for (const [type, schema] of Object.entries(defs)) {\n if (typeof schema?.['~standard']?.validate !== 'function') {\n throw new TypeError(\n `contract '${name}': the value for '${type}' is not a Standard Schema (expected an object with '~standard')`,\n )\n }\n assertSyncSchema(schema, `event '${type}'`)\n }\n const events = Object.freeze({ ...defs })\n\n const presenceDefs = options.presence ?? ({} as P)\n for (const [field, schema] of Object.entries(presenceDefs)) {\n if (typeof schema?.['~standard']?.validate !== 'function') {\n throw new TypeError(\n `contract '${name}': the value for presence field '${field}' is not a Standard Schema (expected an object with '~standard')`,\n )\n }\n assertSyncSchema(schema, `presence field '${field}'`)\n }\n const presence = Object.freeze({ ...presenceDefs })\n\n return {\n name,\n events,\n presence,\n batch: (...items) => items,\n reducer(reducerOptions) {\n return makeReducerBuilder(events, presence, reducerOptions)\n },\n }\n}\n"],"mappings":";;;AAkDA,SAAgB,mBAMd,QACA,UACA,SAKyB;CACzB,MAAM,EAAE,SAAS;CACjB,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAC9C,MAAM,IAAI,UACR,+FACF;CAMF,IAAI,eAAe,QAAQ;CAC3B,IAAI,QAAQ,aAAa;EACvB,MAAM,SAAS,aACb,QAAQ,aACR,QAAQ,cACR,iBACF;EACA,IAAI,OAAO,QACT,MAAM,IAAI,UACR,wDAAwD,OAAO,OAC5D,KAAK,UAAU,MAAM,OAAO,CAAC,CAC7B,KAAK,IAAI,GACd;EAEF,eAAe,OAAO;CACxB;CAEA,OAAO,EACL,KAAK,MAAM;EACT,IAAI,OAAO,SAAS,YAClB,MAAM,IAAI,UAAU,iCAAiC;EAEvD,OAAO;GACL;GACA;GACA;GACA;GACA;GACA,aAAa,QAAQ;EAEvB;CACF,EACF;AACF;;;;;;;;;;;;;;;;;;AC6CA,SAAgB,SAGd,SAWiB;CACjB,MAAM,EAAE,SAAS;CACjB,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAC9C,MAAM,IAAI,UAAU,0CAA0C;CAEhE,IAAI,KAAK,SAAS,GAAQ,GACxB,MAAM,IAAI,UAAU,qDAAqD;CAE3E,MAAM,OAAO,QAAQ;CACrB,KAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,IAAI,GAAG;EACjD,IAAI,OAAO,SAAS,YAAY,EAAE,aAAa,YAC7C,MAAM,IAAI,UACR,aAAa,KAAK,oBAAoB,KAAK,iEAC7C;EAEF,iBAAiB,QAAQ,UAAU,KAAK,EAAE;CAC5C;CACA,MAAM,SAAS,OAAO,OAAO,EAAE,GAAG,KAAK,CAAC;CAExC,MAAM,eAAe,QAAQ,YAAa,CAAC;CAC3C,KAAK,MAAM,CAAC,OAAO,WAAW,OAAO,QAAQ,YAAY,GAAG;EAC1D,IAAI,OAAO,SAAS,YAAY,EAAE,aAAa,YAC7C,MAAM,IAAI,UACR,aAAa,KAAK,mCAAmC,MAAM,iEAC7D;EAEF,iBAAiB,QAAQ,mBAAmB,MAAM,EAAE;CACtD;CACA,MAAM,WAAW,OAAO,OAAO,EAAE,GAAG,aAAa,CAAC;CAElD,OAAO;EACL;EACA;EACA;EACA,QAAQ,GAAG,UAAU;EACrB,QAAQ,gBAAgB;GACtB,OAAO,mBAAmB,QAAQ,UAAU,cAAc;EAC5D;CACF;AACF"}
|
|
@@ -215,4 +215,5 @@ declare function contract<D extends EventDefs, P extends PresenceDefs = Record<n
|
|
|
215
215
|
presence?: P;
|
|
216
216
|
}): Contract<D, P>;
|
|
217
217
|
//#endregion
|
|
218
|
-
export { EventDefs as a, PresencePatch as c, WithPresence as d, contract as f, StandardSchemaV1 as h, EventBatchFactory as i, PresenceSnapshot as l, ReducerBuilder as m, Contract as n, PresenceDefs as o, Reducer as p, ContractEvent as r, PresenceMap as s, AppendInput as t, ReducerOptions as u };
|
|
218
|
+
export { EventDefs as a, PresencePatch as c, WithPresence as d, contract as f, StandardSchemaV1 as h, EventBatchFactory as i, PresenceSnapshot as l, ReducerBuilder as m, Contract as n, PresenceDefs as o, Reducer as p, ContractEvent as r, PresenceMap as s, AppendInput as t, ReducerOptions as u };
|
|
219
|
+
//# sourceMappingURL=contract-jIfaR085.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-jIfaR085.d.ts","names":[],"sources":["../src/standard-schema.ts","../src/reducer.ts","../src/contract.ts"],"mappings":";;;;;;;;UAQiB,iBAAiB,iBAAiB,SAAS;;WAEjD,aAAa,iBAAiB,MAAM,OAAO;;kBAG7B;;mBAEN,MAAM,iBAAiB,SAAS;;aAEtC;;aAEA;;aAEA,WACP,mBACG,OAAO,UAAU,QAAQ,OAAO;;aAE5B,QAAQ,MAAM,OAAO;;;cAIpB,OAAO,UAAU,cAAc,UAAU;;mBAGpC,cAAc;;aAEpB,OAAO;;aAEP;;;mBAIM;;aAEN,QAAQ,cAAc;;;mBAIhB;;aAEN;;aAEA,OAAO,cAAc,cAAc;;;mBAI7B;;aAEN,KAAK;;;mBAIC,MAAM,iBAAiB,SAAS;;aAEtC,OAAO;;aAEP,QAAQ;;;cAIP,WAAW,eAAe,oBAAoB,YACxD;;cAIU,YAAY,eAAe,oBAAoB,YACzD;;;;;;;;;;;;;;KC5DQ,QACV,UAAU,YAAY,WACtB,aACA,UAAU,eAAe;;WAGhB;;WAEA,QAAQ,SAAS;;;;;;WAMjB,UAAU,SAAS;WACnB,OAAO,OAAO,GAAG,OAAO,cAAc,OAAO;WAC7C,cAAc;;;;;;;WAOd,aAAa,0BAA0B;;;KAItC,eACV,UAAU,WACV,GACA,UAAU,eAAe;EAEzB,KAAK,OAAO,OAAO,GAAG,OAAO,cAAc,OAAO,IAAI,QAAQ,GAAG,GAAG;;;;;KC9B1D,YAAY,eAAe;;KAG3B,eAAe,eAAe;;KAG9B,cACV,UAAU,WACV,gBAAgB,mBAAmB,iBAElC,WAAW;EACV;EACA,MAAM;EACN,SAAS,iBAAiB,YAAY,EAAE;EACxC;EACA;EACA,WAAW;KAEb;;KAGU,YAAY,UAAU,gBAC/B,WAAW;EACV,MAAM;EACN,SAAS,iBAAiB,WAAW,EAAE;EACvC;WAEI;KAEI,kBAAkB,UAAU,oBAChC,mBAAmB,YAAY,SAElC,QAAQ,MACR;;;;;;KAOO,cAAc,UAAU,eAAe;EACjD;EACA,WACG,WAAW,cAAc,iBAAiB,YAAY,EAAE;EAE3D;EACA,IAAI;;;;;;;;KASM,YAAY,UAAU,eAAe;GAE5C,yBAAA,WAAW;IACV,OAAO,iBAAiB,YAAY,EAAE;IACtC;IACA,IAAI;;;;;;;;KAUE,iBAAiB,UAAU,eAAe;EACpD,UAAU,YAAY;;;;;;;KAQZ,aACV,UAAU,cACV,iBACQ,4BAA4B;KAE1B,eAAe,MAAM;;;;;EAK/B;;;;;;EAMA,cAAc;;;;;;EAMd,cAAc,iBAAiB,MAAM;;KAG3B,SACV,UAAU,YAAY,WACtB,UAAU,eAAe;;WAGhB;;WAEA,QAAQ,SAAS;;WAEjB,UAAU,SAAS;;WAEnB,OAAO,kBAAkB;;;;;;;;;;;EAWlC,QAAQ,MAAM,IAAI,MAChB,SAAS,eAAe,MAAM,KAC7B,eAAe,GAAG,GAAG;;;;;;;iBAQV,SACd,UAAU,WACV,UAAU,eAAe,sBACzB;;EAEA;;EAEA,QAAQ;;;;;;EAMR,WAAW;IACT,SAAS,GAAG"}
|
|
@@ -148,4 +148,5 @@ declare function encodeDevtoolsCapture(detail: DevtoolsSessionDetail, options?:
|
|
|
148
148
|
declare function parseDevtoolsCapture(value: unknown): DevtoolsCapture;
|
|
149
149
|
declare function verifyDevtoolsCapture(capture: DevtoolsCapture): Promise<void>;
|
|
150
150
|
//#endregion
|
|
151
|
-
export { parseDevtoolsCapture as C, encodeDevtoolsCapture as S, DevtoolsSessionsResponse as _, DEVTOOLS_SESSION_PAGE_LIMIT as a, DevtoolsWireSnapshot as b, DevtoolsCapture as c, DevtoolsCaptureManifest as d, DevtoolsCaptureRecord as f, DevtoolsSessionPage as g, DevtoolsSessionDetail as h, DEVTOOLS_PROTOCOL_VERSION as i, DevtoolsCaptureEvent as l, DevtoolsContractsResponse as m, DEVTOOLS_CAPTURE_MEDIA_TYPE as n, DEVTOOLS_SESSION_PAGE_MAX_LIMIT as o, DevtoolsCaptureSnapshot as p, DEVTOOLS_CAPTURE_VERSION as r, DevtoolsCapabilities as s, DEVTOOLS_CAPABILITIES as t, DevtoolsCaptureFooter as u, DevtoolsWireEvent as v, verifyDevtoolsCapture as w, devtoolsSessionRevision as x, DevtoolsWireSessionSummary as y };
|
|
151
|
+
export { parseDevtoolsCapture as C, encodeDevtoolsCapture as S, DevtoolsSessionsResponse as _, DEVTOOLS_SESSION_PAGE_LIMIT as a, DevtoolsWireSnapshot as b, DevtoolsCapture as c, DevtoolsCaptureManifest as d, DevtoolsCaptureRecord as f, DevtoolsSessionPage as g, DevtoolsSessionDetail as h, DEVTOOLS_PROTOCOL_VERSION as i, DevtoolsCaptureEvent as l, DevtoolsContractsResponse as m, DEVTOOLS_CAPTURE_MEDIA_TYPE as n, DEVTOOLS_SESSION_PAGE_MAX_LIMIT as o, DevtoolsCaptureSnapshot as p, DEVTOOLS_CAPTURE_VERSION as r, DevtoolsCapabilities as s, DEVTOOLS_CAPABILITIES as t, DevtoolsCaptureFooter as u, DevtoolsWireEvent as v, verifyDevtoolsCapture as w, devtoolsSessionRevision as x, DevtoolsWireSessionSummary as y };
|
|
152
|
+
//# sourceMappingURL=devtools-J_jZ2vQf.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools-J_jZ2vQf.d.ts","names":[],"sources":["../src/devtools.ts"],"mappings":";;;;;;;;;cASa;cACA;cACA;cACA;cACA;KAED;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;IAAS;IAAe;IAAiB;;EACzC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA;;KAGU;EACV,wBAAwB;EACxB,WAAW;IAAQ;;;KAGT;EACV,wBAAwB;EACxB;EACA,UAAU;;KAGA;EACV,wBAAwB;EACxB;EACA;;EAEA;EACA,QAAQ;EACR,WAAW;;KAGD;EACV,wBAAwB;EACxB;EACA;;EAEA;;EAEA;EACA,QAAQ;;EAER,WAAW;;KAGD;EACV,wBAAwB;EACxB,WAAW;EACX;IACE,qBAAqB;IACrB,iBAAiB;;EAEnB;IACE,gBAAgB;IAChB,kBAAkB;;;cAIT,uBAAuB;KAaxB;EACV;EACA,uBAAuB;EACvB,wBAAwB;EACxB;EACA;EACA;EACA;EACA;;;KAIU,uBAAuB;EAAsB;;;KAG7C,0BAA0B;EACpC;;KAGU;EACV;EACA,uBAAuB;EACvB;EACA;EACA;EACA;;KAGU,wBACR,0BACA,uBACA,0BACA;KAEQ;EACV;EACA;EACA;EACA;EACA,UAAU;EACV,QAAQ;EACR,WAAW;EACX,QAAQ;EACR,SAAS;;;cA2BE,0BAA2B;EACtC,iBAAiB;EACjB,oBAAoB;MAClB;iBAiDkB,sBACpB,QAAQ,uBACR;EAAW,sBAAsB;IAChC;iBA+Sa,qBAAqB,iBAAiB;iBAmFhC,sBACpB,SAAS,kBACR"}
|
|
@@ -336,3 +336,5 @@ async function verifyDevtoolsCapture(capture) {
|
|
|
336
336
|
}
|
|
337
337
|
//#endregion
|
|
338
338
|
export { DEVTOOLS_SESSION_PAGE_LIMIT as a, encodeDevtoolsCapture as c, DEVTOOLS_PROTOCOL_VERSION as i, parseDevtoolsCapture as l, DEVTOOLS_CAPTURE_MEDIA_TYPE as n, DEVTOOLS_SESSION_PAGE_MAX_LIMIT as o, DEVTOOLS_CAPTURE_VERSION as r, devtoolsSessionRevision as s, DEVTOOLS_CAPABILITIES as t, verifyDevtoolsCapture as u };
|
|
339
|
+
|
|
340
|
+
//# sourceMappingURL=devtools-kJJaORn-.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools-kJJaORn-.js","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["/**\n * Shared wire protocol for A2's read-only devtools surfaces.\n *\n * The HTTP dashboard and non-interactive consumers use the same response\n * shapes. Captures are newline-delimited JSON so they can be streamed,\n * inspected with ordinary command-line tools, and verified without access to\n * an A2 server.\n */\n\nexport const DEVTOOLS_PROTOCOL_VERSION = 1 as const\nexport const DEVTOOLS_CAPTURE_VERSION = 1 as const\nexport const DEVTOOLS_CAPTURE_MEDIA_TYPE = 'application/x-ndjson' as const\nexport const DEVTOOLS_SESSION_PAGE_LIMIT = 250 as const\nexport const DEVTOOLS_SESSION_PAGE_MAX_LIMIT = 1_000 as const\n\nexport type DevtoolsWireSessionSummary = {\n sessionId: string\n eventCount: number\n pendingCount: number\n failedCount: number\n attemptCount: number\n failureCount: number\n firstEventAt: string\n updatedAt: string\n}\n\nexport type DevtoolsWireEvent = {\n id: string\n type: string\n payload: unknown\n index: number\n sessionId: string\n createdAt: string\n cause: { index: number; attempt: number; batchSize?: number } | null\n lane: string | null\n processedAt: string | null\n processedByAttempt: number | null\n returnedEventIds: string[] | null\n firstClaimedAt: string | null\n lastClaimedAt: string | null\n attemptCount: number\n claimHolder: string | null\n claimExpiresAt: string | null\n failureCount: number\n lastFailedAt: string | null\n lastFailedAttempt: number | null\n lastError: string | null\n failedAt: string | null\n}\n\nexport type DevtoolsWireSnapshot = {\n reducerName: string\n index: number\n updatedAt: string | null\n}\n\nexport type DevtoolsContractsResponse = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n contracts: Array<{ name: string }>\n}\n\nexport type DevtoolsSessionsResponse = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n cursor: string | null\n sessions: DevtoolsWireSessionSummary[]\n}\n\nexport type DevtoolsSessionDetail = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n contract: string\n sessionId: string\n /** An invalidation token, not a content-integrity digest. */\n revision: string\n events: DevtoolsWireEvent[]\n snapshots: DevtoolsWireSnapshot[]\n}\n\nexport type DevtoolsSessionPage = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n contract: string\n sessionId: string\n /** Inclusive log frontier frozen by the first page. */\n throughIndex: number\n /** Opaque continuation token; null means this frozen read is complete. */\n cursor: string | null\n events: DevtoolsWireEvent[]\n /** Snapshot metadata is present only on the first page. */\n snapshots: DevtoolsWireSnapshot[]\n}\n\nexport type DevtoolsCapabilities = {\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n resources: Array<'contracts' | 'sessions' | 'session' | 'watch' | 'export'>\n sessionPagination: {\n defaultLimit: typeof DEVTOOLS_SESSION_PAGE_LIMIT\n maxLimit: typeof DEVTOOLS_SESSION_PAGE_MAX_LIMIT\n }\n capture: {\n version: typeof DEVTOOLS_CAPTURE_VERSION\n mediaType: typeof DEVTOOLS_CAPTURE_MEDIA_TYPE\n }\n}\n\nexport const DEVTOOLS_CAPABILITIES: DevtoolsCapabilities = {\n protocolVersion: DEVTOOLS_PROTOCOL_VERSION,\n resources: ['contracts', 'sessions', 'session', 'watch', 'export'],\n sessionPagination: {\n defaultLimit: DEVTOOLS_SESSION_PAGE_LIMIT,\n maxLimit: DEVTOOLS_SESSION_PAGE_MAX_LIMIT,\n },\n capture: {\n version: DEVTOOLS_CAPTURE_VERSION,\n mediaType: DEVTOOLS_CAPTURE_MEDIA_TYPE,\n },\n}\n\nexport type DevtoolsCaptureManifest = {\n kind: 'a2.capture'\n captureVersion: typeof DEVTOOLS_CAPTURE_VERSION\n protocolVersion: typeof DEVTOOLS_PROTOCOL_VERSION\n contract: string\n sessionId: string\n revision: string\n capturedAt: string\n fidelity: 'exact'\n}\n\n/** One exact durable event, with the record discriminator added. */\nexport type DevtoolsCaptureEvent = DevtoolsWireEvent & { kind: 'event' }\n\n/** Snapshot metadata only. Cached reducer state is never exported. */\nexport type DevtoolsCaptureSnapshot = DevtoolsWireSnapshot & {\n kind: 'snapshot'\n}\n\nexport type DevtoolsCaptureFooter = {\n kind: 'end'\n captureVersion: typeof DEVTOOLS_CAPTURE_VERSION\n eventCount: number\n snapshotCount: number\n maxIndex: number | null\n sha256: string\n}\n\nexport type DevtoolsCaptureRecord =\n | DevtoolsCaptureManifest\n | DevtoolsCaptureEvent\n | DevtoolsCaptureSnapshot\n | DevtoolsCaptureFooter\n\nexport type DevtoolsCapture = {\n contract: string\n sessionId: string\n revision: string\n capturedAt: string\n manifest: DevtoolsCaptureManifest\n events: DevtoolsCaptureEvent[]\n snapshots: DevtoolsCaptureSnapshot[]\n footer: DevtoolsCaptureFooter\n records: DevtoolsCaptureRecord[]\n}\n\nconst encoder = new TextEncoder()\nconst decoder = new TextDecoder('utf-8', { fatal: true })\n\nconst objectValue = (value: unknown): Record<string, unknown> | null =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null\n\nclass DevtoolsCaptureError extends TypeError {}\n\nconst captureError = (message: string): DevtoolsCaptureError =>\n new DevtoolsCaptureError(`invalid A2 devtools capture: ${message}`)\n\nconst sha256 = async (value: string): Promise<string> => {\n const digest = await crypto.subtle.digest('SHA-256', encoder.encode(value))\n return [...new Uint8Array(digest)]\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('')\n}\n\nconst timestampMs = (value: string | null): number | null =>\n value === null ? null : new Date(value).getTime()\n\n/** Compute the server-compatible lifecycle invalidation token. */\nexport const devtoolsSessionRevision = (detail: {\n events: readonly DevtoolsWireEvent[]\n snapshots: readonly DevtoolsWireSnapshot[]\n}): Promise<string> =>\n sha256(\n JSON.stringify({\n events: detail.events.map((event) => [\n event.id,\n event.index,\n event.cause?.index ?? null,\n event.cause?.attempt ?? null,\n timestampMs(event.processedAt),\n event.processedByAttempt,\n timestampMs(event.firstClaimedAt),\n timestampMs(event.lastClaimedAt),\n event.attemptCount,\n event.failureCount,\n timestampMs(event.lastFailedAt),\n event.lastFailedAttempt,\n event.lastError,\n timestampMs(event.failedAt),\n ]),\n snapshots: detail.snapshots.map((snapshot) => [\n snapshot.reducerName,\n snapshot.index,\n snapshot.updatedAt === null\n ? 0\n : new Date(snapshot.updatedAt).getTime(),\n ]),\n }),\n )\n\nconst encodeRecords = (records: readonly unknown[]): string =>\n records.map((record) => JSON.stringify(record)).join('\\n') + '\\n'\n\nconst asCapturedAt = (value: string | Date | undefined): string => {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) {\n throw new TypeError('capturedAt must be a canonical ISO date')\n }\n return value.toISOString()\n }\n if (value === undefined) return new Date().toISOString()\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== value) {\n throw new TypeError(\n 'capturedAt must be a canonical ISO date string or Date',\n )\n }\n return value\n}\n\nexport async function encodeDevtoolsCapture(\n detail: DevtoolsSessionDetail,\n options: { capturedAt?: string | Date } = {},\n): Promise<string> {\n const manifest: DevtoolsCaptureManifest = {\n kind: 'a2.capture',\n captureVersion: DEVTOOLS_CAPTURE_VERSION,\n protocolVersion: DEVTOOLS_PROTOCOL_VERSION,\n contract: detail.contract,\n sessionId: detail.sessionId,\n revision: detail.revision,\n capturedAt: asCapturedAt(options.capturedAt),\n fidelity: 'exact',\n }\n const events = detail.events.map((event): DevtoolsCaptureEvent => ({\n ...event,\n kind: 'event',\n }))\n const snapshots = detail.snapshots.map(\n (snapshot): DevtoolsCaptureSnapshot => ({\n ...snapshot,\n kind: 'snapshot',\n }),\n )\n const signedRecords = [manifest, ...events, ...snapshots]\n validateCanonicalJsonValue(signedRecords, 'capture')\n const signed = encodeRecords(signedRecords)\n const footer: DevtoolsCaptureFooter = {\n kind: 'end',\n captureVersion: DEVTOOLS_CAPTURE_VERSION,\n eventCount: events.length,\n snapshotCount: snapshots.length,\n maxIndex: events.at(-1)?.index ?? null,\n sha256: `sha256:${await sha256(signed)}`,\n }\n return `${signed}${JSON.stringify(footer)}\\n`\n}\n\nconst recordsFromText = (text: string): unknown[] => {\n if (text.length === 0) throw captureError('capture is empty')\n const lines = text.split('\\n')\n if (lines.pop() !== '') {\n throw captureError('capture must end with a newline')\n }\n const records = lines.map((line, index) => {\n if (line.length === 0) {\n throw captureError(`record ${index + 1} is empty`)\n }\n try {\n return JSON.parse(line) as unknown\n } catch {\n throw captureError(`record ${index + 1} is not valid JSON`)\n }\n })\n if (encodeRecords(records) !== text) {\n throw captureError('capture must use canonical UTF-8 NDJSON encoding')\n }\n return records\n}\n\nconst recordsFromValues = (records: readonly unknown[]): unknown[] => {\n try {\n validateCanonicalJsonValue(records, 'capture.records')\n return recordsFromText(encodeRecords(records))\n } catch (error) {\n if (error instanceof DevtoolsCaptureError) throw error\n throw captureError('capture records are not JSON-serializable')\n }\n}\n\nconst captureRecordsFrom = (value: unknown): unknown[] => {\n if (typeof value === 'string') return recordsFromText(value)\n if (value instanceof Uint8Array) {\n try {\n return recordsFromText(decoder.decode(value))\n } catch (error) {\n if (error instanceof DevtoolsCaptureError) throw error\n throw captureError('capture is not valid UTF-8')\n }\n }\n if (Array.isArray(value)) return recordsFromValues(value)\n const capture = objectValue(value)\n if (capture) {\n if (Array.isArray(capture['records'])) {\n return recordsFromValues(capture['records'])\n }\n const manifest = capture['manifest']\n const events = capture['events']\n const snapshots = capture['snapshots']\n const footer = capture['footer']\n if (Array.isArray(events) && Array.isArray(snapshots)) {\n return recordsFromValues([manifest, ...events, ...snapshots, footer])\n }\n }\n throw captureError('expected NDJSON text, bytes, records, or a capture')\n}\n\nconst stringField = (\n value: Record<string, unknown>,\n field: string,\n record: string,\n): string => {\n const candidate = value[field]\n if (typeof candidate !== 'string' || candidate.length === 0) {\n throw captureError(`${record}.${field} must be a non-empty string`)\n }\n return candidate\n}\n\nconst integerField = (\n value: Record<string, unknown>,\n field: string,\n record: string,\n): number => {\n const candidate = value[field]\n if (!Number.isSafeInteger(candidate) || (candidate as number) < 0) {\n throw captureError(`${record}.${field} must be a non-negative integer`)\n }\n return candidate as number\n}\n\nconst positiveInteger = (value: unknown): value is number =>\n Number.isSafeInteger(value) && (value as number) > 0\n\nconst nonNegativeInteger = (value: unknown): value is number =>\n Number.isSafeInteger(value) && (value as number) >= 0\n\nconst canonicalDate = (value: unknown): value is string => {\n if (typeof value !== 'string') return false\n const parsed = new Date(value)\n return !Number.isNaN(parsed.getTime()) && parsed.toISOString() === value\n}\n\nconst optionalDate = (value: unknown): value is string | null =>\n value === null || canonicalDate(value)\n\nconst nullableString = (value: unknown): value is string | null =>\n value === null || typeof value === 'string'\n\nconst nullablePositiveInteger = (value: unknown): value is number | null =>\n value === null || positiveInteger(value)\n\nconst validateDate = (\n value: unknown,\n field: string,\n record: string,\n nullable: boolean,\n): void => {\n if (nullable ? optionalDate(value) : canonicalDate(value)) return\n throw captureError(\n `${record}.${field} has an invalid timestamp; expected a canonical ISO date${nullable ? ' or null' : ''}`,\n )\n}\n\nconst validateCause = (value: unknown, record: string): void => {\n if (value === null) return\n const cause = objectValue(value)\n if (\n cause === null ||\n !positiveInteger(cause['index']) ||\n !positiveInteger(cause['attempt']) ||\n (cause['batchSize'] !== undefined && !positiveInteger(cause['batchSize']))\n ) {\n throw captureError(`${record}.cause is invalid`)\n }\n}\n\nconst validateEventRecord = (\n record: Record<string, unknown>,\n label: string,\n): void => {\n stringField(record, 'id', label)\n stringField(record, 'type', label)\n stringField(record, 'sessionId', label)\n if (!Object.hasOwn(record, 'payload')) {\n throw captureError(`${label}.payload is required`)\n }\n if (!positiveInteger(record['index'])) {\n throw captureError(`${label}.index must be a positive integer`)\n }\n validateDate(record['createdAt'], 'createdAt', label, false)\n validateCause(record['cause'], label)\n if (!nullableString(record['lane'])) {\n throw captureError(`${label}.lane must be a string or null`)\n }\n validateDate(record['processedAt'], 'processedAt', label, true)\n if (!nullablePositiveInteger(record['processedByAttempt'])) {\n throw captureError(\n `${label}.processedByAttempt must be a positive integer or null`,\n )\n }\n validateDate(record['firstClaimedAt'], 'firstClaimedAt', label, true)\n validateDate(record['lastClaimedAt'], 'lastClaimedAt', label, true)\n if (!nonNegativeInteger(record['attemptCount'])) {\n throw captureError(`${label}.attemptCount must be a non-negative integer`)\n }\n if (!nullableString(record['claimHolder'])) {\n throw captureError(`${label}.claimHolder must be a string or null`)\n }\n validateDate(record['claimExpiresAt'], 'claimExpiresAt', label, true)\n if (!nonNegativeInteger(record['failureCount'])) {\n throw captureError(`${label}.failureCount must be a non-negative integer`)\n }\n validateDate(record['lastFailedAt'], 'lastFailedAt', label, true)\n if (!nullablePositiveInteger(record['lastFailedAttempt'])) {\n throw captureError(\n `${label}.lastFailedAttempt must be a positive integer or null`,\n )\n }\n if (!nullableString(record['lastError'])) {\n throw captureError(`${label}.lastError must be a string or null`)\n }\n validateDate(record['failedAt'], 'failedAt', label, true)\n const returned = record['returnedEventIds']\n if (\n returned !== null &&\n (!Array.isArray(returned) ||\n returned.some((id) => typeof id !== 'string' || id.length === 0))\n ) {\n throw captureError(`${label}.returnedEventIds is invalid`)\n }\n}\n\nconst validateSnapshotRecord = (\n record: Record<string, unknown>,\n label: string,\n): void => {\n stringField(record, 'reducerName', label)\n if (!nonNegativeInteger(record['index'])) {\n throw captureError(`${label}.index must be a non-negative integer`)\n }\n validateDate(record['updatedAt'], 'updatedAt', label, true)\n}\n\nfunction validateCanonicalJsonValue(\n value: unknown,\n path: string,\n ancestors = new WeakSet<object>(),\n): void {\n if (\n value === null ||\n typeof value === 'string' ||\n typeof value === 'boolean'\n ) {\n return\n }\n if (typeof value === 'number') {\n if (!Number.isFinite(value) || Object.is(value, -0)) {\n throw captureError(`${path} is not a canonical JSON number`)\n }\n return\n }\n if (Array.isArray(value)) {\n if (ancestors.has(value)) {\n throw captureError(`${path} is not a canonical JSON tree`)\n }\n ancestors.add(value)\n try {\n const keys = Object.keys(value)\n const ownKeys = Reflect.ownKeys(value)\n if (\n keys.length !== value.length ||\n ownKeys.length !== keys.length + 1 ||\n !ownKeys.includes('length') ||\n keys.some((key, index) => key !== String(index))\n ) {\n throw captureError(`${path} is not a canonical JSON array`)\n }\n value.forEach((entry, index) =>\n validateCanonicalJsonValue(entry, `${path}[${index}]`, ancestors),\n )\n } finally {\n ancestors.delete(value)\n }\n return\n }\n const object = objectValue(value)\n if (\n object === null ||\n (Object.getPrototypeOf(object) !== Object.prototype &&\n Object.getPrototypeOf(object) !== null)\n ) {\n throw captureError(`${path} is not a canonical JSON value`)\n }\n if (ancestors.has(object)) {\n throw captureError(`${path} is not a canonical JSON tree`)\n }\n ancestors.add(object)\n try {\n const keys = Object.keys(object)\n if (\n Reflect.ownKeys(object).length !== keys.length ||\n keys.some(\n (key) => !Object.prototype.propertyIsEnumerable.call(object, key),\n )\n ) {\n throw captureError(`${path} is not a canonical JSON object`)\n }\n for (const key of keys) {\n validateCanonicalJsonValue(object[key], `${path}.${key}`, ancestors)\n }\n } finally {\n ancestors.delete(object)\n }\n}\n\nexport function parseDevtoolsCapture(value: unknown): DevtoolsCapture {\n const records = captureRecordsFrom(value)\n if (records.length < 2) {\n throw captureError('capture needs a manifest and footer')\n }\n const manifestValue = objectValue(records[0])\n if (manifestValue?.['kind'] !== 'a2.capture') {\n throw captureError(\"first record must have kind 'a2.capture'\")\n }\n if (manifestValue['captureVersion'] !== DEVTOOLS_CAPTURE_VERSION) {\n throw captureError(\n `unsupported capture version '${String(manifestValue['captureVersion'])}'`,\n )\n }\n if (manifestValue['protocolVersion'] !== DEVTOOLS_PROTOCOL_VERSION) {\n throw captureError(\n `unsupported protocol version '${String(manifestValue['protocolVersion'])}'`,\n )\n }\n if (manifestValue['fidelity'] !== 'exact') {\n throw captureError(\"manifest.fidelity must be 'exact'\")\n }\n const manifest = manifestValue as DevtoolsCaptureManifest\n const contract = stringField(manifestValue, 'contract', 'manifest')\n const sessionId = stringField(manifestValue, 'sessionId', 'manifest')\n const revision = stringField(manifestValue, 'revision', 'manifest')\n const capturedAt = stringField(manifestValue, 'capturedAt', 'manifest')\n if (!canonicalDate(capturedAt)) {\n throw captureError('manifest.capturedAt must be a canonical ISO date')\n }\n\n const footerValue = objectValue(records.at(-1))\n if (footerValue?.['kind'] !== 'end') {\n throw captureError(\"last record must have kind 'end'\")\n }\n if (footerValue['captureVersion'] !== DEVTOOLS_CAPTURE_VERSION) {\n throw captureError('footer capture version does not match')\n }\n const footer = footerValue as DevtoolsCaptureFooter\n integerField(footerValue, 'eventCount', 'footer')\n integerField(footerValue, 'snapshotCount', 'footer')\n stringField(footerValue, 'sha256', 'footer')\n if (\n footerValue['maxIndex'] !== null &&\n (!Number.isSafeInteger(footerValue['maxIndex']) ||\n (footerValue['maxIndex'] as number) < 1)\n ) {\n throw captureError('footer.maxIndex must be null or a positive integer')\n }\n\n const events: DevtoolsCaptureEvent[] = []\n const snapshots: DevtoolsCaptureSnapshot[] = []\n let sawSnapshot = false\n for (let index = 1; index < records.length - 1; index += 1) {\n const record = objectValue(records[index])\n if (record?.['kind'] === 'event') {\n if (sawSnapshot) {\n throw captureError('event records must precede snapshot records')\n }\n validateEventRecord(record, `record ${index + 1}`)\n events.push(record as DevtoolsCaptureEvent)\n } else if (record?.['kind'] === 'snapshot') {\n sawSnapshot = true\n validateSnapshotRecord(record, `record ${index + 1}`)\n snapshots.push(record as DevtoolsCaptureSnapshot)\n } else {\n throw captureError(`record ${index + 1} has an unknown kind`)\n }\n }\n\n return {\n contract,\n sessionId,\n revision,\n capturedAt,\n manifest,\n events,\n snapshots,\n footer,\n records: records as DevtoolsCaptureRecord[],\n }\n}\n\nexport async function verifyDevtoolsCapture(\n capture: DevtoolsCapture,\n): Promise<void> {\n validateCanonicalJsonValue(capture.records, 'capture.records')\n parseDevtoolsCapture(capture.records)\n const expectedRecords: DevtoolsCaptureRecord[] = [\n capture.manifest,\n ...capture.events,\n ...capture.snapshots,\n capture.footer,\n ]\n if (\n capture.records.length !== expectedRecords.length ||\n capture.records.some((record, index) => record !== expectedRecords[index])\n ) {\n throw captureError('record sequence does not match the capture')\n }\n if (\n capture.contract !== capture.manifest.contract ||\n capture.sessionId !== capture.manifest.sessionId ||\n capture.revision !== capture.manifest.revision ||\n capture.capturedAt !== capture.manifest.capturedAt\n ) {\n throw captureError('capture identity does not match its manifest')\n }\n if (!canonicalDate(capture.capturedAt)) {\n throw captureError('manifest.capturedAt must be a canonical ISO date')\n }\n if (capture.footer.eventCount !== capture.events.length) {\n throw captureError('footer event count does not match')\n }\n if (capture.footer.snapshotCount !== capture.snapshots.length) {\n throw captureError('footer snapshot count does not match')\n }\n\n let previousIndex = 0\n const ids = new Set<string>()\n for (const event of capture.events) {\n if (event.kind !== 'event') throw captureError('invalid event record kind')\n validateEventRecord(\n event as unknown as Record<string, unknown>,\n `event #${event.index}`,\n )\n if (\n !Number.isSafeInteger(event.index) ||\n event.index !== previousIndex + 1\n ) {\n throw captureError('event indexes must be contiguous from 1')\n }\n previousIndex = event.index\n if (event.sessionId !== capture.sessionId) {\n throw captureError(`event #${event.index} belongs to another session`)\n }\n if (ids.has(event.id)) {\n throw captureError(`event #${event.index} repeats id '${event.id}'`)\n }\n ids.add(event.id)\n }\n\n for (const snapshot of capture.snapshots) {\n if (snapshot.kind !== 'snapshot') {\n throw captureError('invalid snapshot record kind')\n }\n validateSnapshotRecord(\n snapshot as unknown as Record<string, unknown>,\n `snapshot '${snapshot.reducerName}'`,\n )\n if (\n !Number.isSafeInteger(snapshot.index) ||\n snapshot.index > previousIndex\n ) {\n throw captureError(`snapshot '${snapshot.reducerName}' is invalid`)\n }\n }\n\n const maxIndex = capture.events.at(-1)?.index ?? null\n if (capture.footer.maxIndex !== maxIndex) {\n throw captureError('footer max index does not match')\n }\n if (!/^sha256:[\\da-f]{64}$/.test(capture.footer.sha256)) {\n throw captureError('footer.sha256 is malformed')\n }\n const expected = `sha256:${await sha256(\n encodeRecords(capture.records.slice(0, -1)),\n )}`\n if (capture.footer.sha256 !== expected) {\n throw captureError('capture digest does not match')\n }\n}\n"],"mappings":"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"}
|
|
@@ -2,3 +2,5 @@
|
|
|
2
2
|
/** experimental-a2/devtools/server owns store access and is server-only. */
|
|
3
3
|
throw new Error("experimental-a2/devtools/server is server-only — mount its handler in a server route and open that URL in the browser.");
|
|
4
4
|
//#endregion
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=devtools-server.browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools-server.browser.js","names":[],"sources":["../src/devtools-server.browser.ts"],"sourcesContent":["/** experimental-a2/devtools/server owns store access and is server-only. */\nthrow new Error(\n 'experimental-a2/devtools/server is server-only — mount its handler in a server route and open that URL in the browser.',\n)\nexport {}\n"],"mappings":";;AACA,MAAM,IAAI,MACR,wHACF"}
|
|
@@ -19,4 +19,5 @@ type Devtools = {
|
|
|
19
19
|
};
|
|
20
20
|
declare function createDevtools(options: DevtoolsOptions): Devtools;
|
|
21
21
|
//#endregion
|
|
22
|
-
export { Devtools, DevtoolsAuthorization, DevtoolsOptions, DevtoolsServer, createDevtools };
|
|
22
|
+
export { Devtools, DevtoolsAuthorization, DevtoolsOptions, DevtoolsServer, createDevtools };
|
|
23
|
+
//# sourceMappingURL=devtools-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools-server.d.ts","names":[],"sources":["../src/devtools-server.ts"],"mappings":";KAsCY;WACD;aAAqB;;;KAGpB,yBACV,SAAS,sBACI,WAAW,kBAAkB;KAEhC;EACV,kBAAkB;;;;;;EAMlB,YAAY;;KAGF;EACV,YAAY,SAAS,YAAY,QAAQ;;iBAgY3B,eAAe,SAAS,kBAAkB"}
|