statewire 0.23.0 → 0.24.1
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/dist/client/core.d.ts +4 -3
- package/dist/client/core.d.ts.map +1 -1
- package/dist/client/core.js +56 -45
- package/dist/client/core.js.map +1 -1
- package/dist/client/session.d.ts +56 -12
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +632 -260
- package/dist/client/session.js.map +1 -1
- package/dist/client/sessionStore.d.ts +1 -0
- package/dist/client/sessionStore.d.ts.map +1 -1
- package/dist/client/sessionStore.js +1 -0
- package/dist/client/sessionStore.js.map +1 -1
- package/dist/host/host.d.ts +6 -5
- package/dist/host/host.d.ts.map +1 -1
- package/dist/host/host.js.map +1 -1
- package/dist/indexedDb.d.ts +15 -0
- package/dist/indexedDb.d.ts.map +1 -0
- package/dist/indexedDb.js +171 -0
- package/dist/indexedDb.js.map +1 -0
- package/dist/statewire.d.ts +117 -59
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js +13 -25
- package/dist/statewire.js.map +1 -1
- package/dist/storage.d.ts +42 -0
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +387 -125
- package/dist/storage.js.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/transports/remote.js +7 -9
- package/dist/transports/remote.js.map +1 -1
- package/dist/types.d.ts +6 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -1
- package/src/client/core.ts +81 -64
- package/src/client/session.ts +886 -424
- package/src/client/sessionStore.ts +2 -0
- package/src/host/host.ts +10 -3
- package/src/indexedDb.ts +167 -0
- package/src/statewire.ts +133 -86
- package/src/storage.ts +548 -112
- package/src/transports/remote.ts +13 -14
- package/src/types.ts +6 -7
|
@@ -11,6 +11,7 @@ export declare const LinkContext: import("react").Context<{
|
|
|
11
11
|
isNew?: boolean | undefined;
|
|
12
12
|
context?: Readonly<Record<string, Statewire.Context>> | undefined;
|
|
13
13
|
canOvertake?: Readonly<Record<string, StatewireClient.CanOvertake>> | undefined;
|
|
14
|
+
onSettled?: StatewireClient.OnSettled | undefined;
|
|
14
15
|
}>;
|
|
15
16
|
export declare const DocumentStorageContext: import("react").Context<StatewireDocuments.Storage | undefined>;
|
|
16
17
|
//# sourceMappingURL=sessionStore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionStore.d.ts","sourceRoot":"","sources":["../../src/client/sessionStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,6BAA0B;AAE5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,4BAAyB;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAiB;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAqB;AAEpD,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,qEAEtB,CAAC;AAEb,eAAO,MAAM,wBAAwB,QAAO,eAAe,CAAC,
|
|
1
|
+
{"version":3,"file":"sessionStore.d.ts","sourceRoot":"","sources":["../../src/client/sessionStore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,6BAA0B;AAE5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,4BAAyB;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAiB;AAC1C,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAqB;AAEpD,kFAAkF;AAClF,eAAO,MAAM,qBAAqB,qEAEtB,CAAC;AAEb,eAAO,MAAM,wBAAwB,QAAO,eAAe,CAAC,cAS3D,CAAC;AAEF,eAAO,MAAM,eAAe,oDAA+C,CAAC;AAE5E,+EAA+E;AAC/E,eAAO,MAAM,WAAW;YACd,OAAO,GAAG,SAAS;cACjB,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS;kBAE7D,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC,GACrD,SAAS;gBACD,eAAe,CAAC,SAAS,GAAG,SAAS;EAC7C,CAAC;AAEP,eAAO,MAAM,sBAAsB,iEAEvB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionStore.js","names":["StatewireDocuments","createContext","StatewireProtocol","Statewire","StatewireClient","SessionStorageContext","SessionStorage","undefined","createMemorySessionStore","record","SessionRecord","load","save","next","ProtocolContext","Options","LinkContext","isNew","context","Readonly","Record","Context","canOvertake","CanOvertake","DocumentStorageContext","Storage"],"sources":["../../src/client/sessionStore.ts"],"sourcesContent":["import type { StatewireDocuments } from \"../wire/documents\";\nimport { createContext } from \"react\";\nimport type { StatewireProtocol } from \"../wire/protocol\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\n\n/** The session storage `useStatewire` mounts, read by the transport's session. */\nexport const SessionStorageContext = createContext<\n StatewireClient.SessionStorage | undefined\n>(undefined);\n\nexport const createMemorySessionStore = (): StatewireClient.SessionStorage => {\n let record: StatewireClient.SessionRecord | null = null;\n return {\n load: () => record,\n save: (next) => {\n record = next;\n },\n };\n};\n\nexport const ProtocolContext = createContext<StatewireProtocol.Options>({});\n\n/** Link options `useStatewire` hands down to whichever transport it mounts. */\nexport const LinkContext = createContext<{\n isNew?: boolean | undefined;\n context?: Readonly<Record<string, Statewire.Context>> | undefined;\n canOvertake?:\n | Readonly<Record<string, StatewireClient.CanOvertake>>\n | undefined;\n}>({});\n\nexport const DocumentStorageContext = createContext<\n StatewireDocuments.Storage | undefined\n>(undefined);\n"],"mappings":";;;AAOA,MAAaK,wBAAwBJ,cAEnCM,KAAAA,CAAS;AAEX,MAAaC,iCAAiE;CAC5E,IAAIC,SAA+C;CACnD,OAAO;EACLE,
|
|
1
|
+
{"version":3,"file":"sessionStore.js","names":["StatewireDocuments","createContext","StatewireProtocol","Statewire","StatewireClient","SessionStorageContext","SessionStorage","undefined","createMemorySessionStore","record","SessionRecord","durable","load","save","next","ProtocolContext","Options","LinkContext","isNew","context","Readonly","Record","Context","canOvertake","CanOvertake","onSettled","OnSettled","DocumentStorageContext","Storage"],"sources":["../../src/client/sessionStore.ts"],"sourcesContent":["import type { StatewireDocuments } from \"../wire/documents\";\nimport { createContext } from \"react\";\nimport type { StatewireProtocol } from \"../wire/protocol\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireClient } from \"../statewire\";\n\n/** The session storage `useStatewire` mounts, read by the transport's session. */\nexport const SessionStorageContext = createContext<\n StatewireClient.SessionStorage | undefined\n>(undefined);\n\nexport const createMemorySessionStore = (): StatewireClient.SessionStorage => {\n let record: StatewireClient.SessionRecord | null = null;\n return {\n durable: false,\n load: () => record,\n save: (next) => {\n record = next;\n },\n };\n};\n\nexport const ProtocolContext = createContext<StatewireProtocol.Options>({});\n\n/** Link options `useStatewire` hands down to whichever transport it mounts. */\nexport const LinkContext = createContext<{\n isNew?: boolean | undefined;\n context?: Readonly<Record<string, Statewire.Context>> | undefined;\n canOvertake?:\n | Readonly<Record<string, StatewireClient.CanOvertake>>\n | undefined;\n onSettled?: StatewireClient.OnSettled | undefined;\n}>({});\n\nexport const DocumentStorageContext = createContext<\n StatewireDocuments.Storage | undefined\n>(undefined);\n"],"mappings":";;;AAOA,MAAaK,wBAAwBJ,cAEnCM,KAAAA,CAAS;AAEX,MAAaC,iCAAiE;CAC5E,IAAIC,SAA+C;CACnD,OAAO;EACLE,SAAS;EACTC,YAAYH;EACZI,OAAOC,SAAS;GACdL,SAASK;EACX;CACF;AACF;AAEA,MAAaC,kBAAkBd,cAAyC,CAAC,CAAC;;AAG1E,MAAagB,cAAchB,cAOxB,CAAC,CAAC;AAEL,MAAa0B,yBAAyB1B,cAEpCM,KAAAA,CAAS"}
|
package/dist/host/host.d.ts
CHANGED
|
@@ -14,9 +14,9 @@ export type CommandsInvocation = {
|
|
|
14
14
|
readonly id?: string;
|
|
15
15
|
readonly clientId?: string;
|
|
16
16
|
/** Present while the queued command is held. */
|
|
17
|
-
readonly
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
readonly held?: true;
|
|
18
|
+
/** Client-only data persisted with an outbox entry. */
|
|
19
|
+
readonly localMeta?: unknown;
|
|
20
20
|
};
|
|
21
21
|
export type CommandsOutcome = {
|
|
22
22
|
type: "accepted";
|
|
@@ -28,12 +28,13 @@ export type CommandsOutcome = {
|
|
|
28
28
|
export type CommandsListener = {
|
|
29
29
|
onStart: (inv: CommandsInvocation) => void;
|
|
30
30
|
onPosted?: (inv: CommandsInvocation) => void;
|
|
31
|
-
/** The invocation's seq or
|
|
31
|
+
/** The invocation's seq, hold or local meta changed in place. */
|
|
32
32
|
onChange?: (inv: CommandsInvocation) => void;
|
|
33
33
|
onApplied: (inv: CommandsInvocation) => void;
|
|
34
34
|
/** Canonical state now covers the invocation's effect — drop any overlay. */
|
|
35
35
|
onCovered?: (inv: CommandsInvocation) => void;
|
|
36
|
-
|
|
36
|
+
/** `durable`: the session persisted the settled update and delivers it itself. */
|
|
37
|
+
onSettle: (inv: CommandsInvocation, outcome: CommandsOutcome, durable?: boolean) => void;
|
|
37
38
|
};
|
|
38
39
|
export type CommandsSubmission = {
|
|
39
40
|
method: string;
|
package/dist/host/host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/host/host.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAiB;AAC1C,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAgB;AAS7C,eAAO,MAAM,gBAAgB,GAAI,UAAU,MAAM,EAAE,MAAM,SAAS,MAAM,EAAE,WACjC,CAAC;AAE1C,mHAAmH;AACnH,eAAO,MAAM,YAAY,GACvB,OAAO,MAAM,KACZ,aAAa,CAAC,SAAS,GAAG,SAAkC,CAAC;AAehE,eAAO,MAAM,iBAAiB,GAAI,MAAM,EACtC,MAAM,MAAM,MAAM,KACjB,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,CA6FlD,CAAC;AAQF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,gDAAgD;IAChD,QAAQ,CAAC,IAAI,CAAC,EAAE
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/host/host.ts"],"names":[],"mappings":"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"}
|
package/dist/host/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.js","names":["captureContext","useEffect","useState","applyOps","sanitizeValue","StatewirePath","StatewireReject","createStateDraft","createMethodProxy","StateDraft","StatewireSendError","Statewire","StatewireHost","isDev","process","env","stateHosts","WeakMap","StateHost","documentIdentity","protocol","key","JSON","stringify","getStateHost","state","get","StateCell","current","draft","changed","documents","Map","Document","onOp","op","Op","onChange","onDocuments","ctl","StateCtl","host","bump","useStatewireState","init","TState","setVersion","cell","commit","c","flush","v","apply","ops","result","invalidate","push","changedPaths","snapshot","setOnOp","listener","setOnChange","takeChanged","mount","value","options","main","undefined","id","doc","Error","set","unmount","delete","setOnDocuments","record","changedPath","proxy","const","Settled","type","error","CommandsInvocation","method","params","ts","seq","clientId","hold","meta","CommandsOutcome","payload","CommandsListener","onStart","inv","onPosted","onApplied","onCovered","onSettle","outcome","CommandsSubmission","caller","Caller","context","Context","attach","Attach","CommandsHost","setListener","submit","call","Promise","methods","abort","commandsHosts","getCommandsHost","commands","bindCommandsHost","QueuedCall","resolve","reject","origin","ActiveInvocation","applied","controller","AbortController","settled","CommandsCell","handlers","Record","args","queue","active","running","unmounted","stallTimer","ReturnType","setTimeout","STALL_WARN_MS","LOCAL_CALLER","Object","freeze","auth","isConnected","toSendError","console","fate","cause","message","code","applyInvocation","settleInvocation","abortInvocations","clear","isThenable","PromiseLike","then","handlerOf","prototype","propertyIsEnumerable","handler","dispatch","ctx","Ctx","signal","this","resolved","pump","watchStall","shift","length","warn","useStatewireCommands","Commands","THandlers","ThisType","CommandsOptions","CommandsProxy","HandledCommands","built","Date","now","keys","filter","clearTimeout","queueMicrotask","queued","startsWith"],"sources":["../../src/host/host.ts"],"sourcesContent":["import { captureContext } from \"../wire/context\";\nimport { useEffect, useState } from \"react\";\nimport { applyOps, sanitizeValue } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireReject } from \"./engine\";\nimport { createStateDraft } from \"./draft\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StateDraft } from \"./draft\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\n\nconst isDev = () =>\n typeof process === \"undefined\" || process.env[\"NODE_ENV\"] !== \"production\";\n\n// ─── state ──────────────────────────────────────────────\n\nconst stateHosts = new WeakMap<object, StatewireHost.StateHost>();\n\nexport const documentIdentity = (protocol: string, key?: readonly string[]) =>\n JSON.stringify([protocol, key ?? null]);\n\n/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */\nexport const getStateHost = (\n state: object,\n): StatewireHost.StateHost | undefined => stateHosts.get(state);\n\ntype StateCell = {\n current: unknown;\n draft: StateDraft<object> | null;\n changed: StatewirePath[];\n documents: Map<string, StatewireHost.Document>;\n onOp: ((op: Statewire.Op) => void) | null;\n onChange: (() => void) | null;\n onDocuments: (() => void) | null;\n ctl: StatewireHost.StateCtl;\n host: StatewireHost.StateHost;\n bump: () => void;\n};\n\nexport const useStatewireState = <TState>(\n init: () => TState,\n): readonly [TState, StatewireHost.StateCtl<TState>] => {\n const [, setVersion] = useState(0);\n const [cell] = useState((): StateCell => {\n const commit = () => {\n if (c.draft) c.current = c.draft.flush();\n return c.current;\n };\n const c: StateCell = {\n current: sanitizeValue(init()),\n draft: null,\n changed: [[]],\n documents: new Map(),\n onOp: null,\n onChange: null,\n onDocuments: null,\n bump: () => setVersion((v) => v + 1),\n ctl: {\n apply: (ops) => {\n const result = applyOps(commit(), ops);\n c.current = result.state;\n c.draft?.invalidate();\n c.changed.push(...result.changedPaths);\n if (c.onOp) for (const op of ops) c.onOp(op);\n c.bump();\n c.onChange?.();\n },\n snapshot: commit,\n },\n host: {\n snapshot: commit,\n setOnOp: (listener) => {\n c.onOp = listener;\n },\n setOnChange: (listener) => {\n c.onChange = listener;\n },\n takeChanged: () => {\n const changed = c.changed;\n c.changed = [];\n return changed;\n },\n mount: (protocol, value, options) => {\n const key = options?.key;\n const main = options?.main ?? key === undefined;\n if (main) {\n for (const [id, doc] of c.documents) {\n if (\n doc.protocol === protocol &&\n doc.main &&\n id !== documentIdentity(protocol, key)\n )\n throw new Error(\n `statewire: ${protocol} already has a main document`,\n );\n }\n }\n c.documents.set(documentIdentity(protocol, key), {\n protocol,\n key: key === undefined ? null : [...key],\n main,\n value: sanitizeValue(value),\n });\n c.onDocuments?.();\n },\n unmount: (protocol, key) => {\n if (!c.documents.delete(documentIdentity(protocol, key)))\n throw new Error(\"statewire: document is not mounted\");\n c.onDocuments?.();\n },\n documents: () => c.documents,\n setOnDocuments: (listener) => {\n c.onDocuments = listener;\n },\n },\n };\n return c;\n });\n\n let value = cell.ctl.snapshot();\n if (typeof value === \"object\" && value !== null) {\n cell.draft ??= createStateDraft<object>({\n snapshot: () => cell.current,\n record: (op, changedPath) => {\n cell.changed.push(changedPath);\n cell.onOp?.(op);\n cell.bump();\n cell.onChange?.();\n },\n });\n stateHosts.set(cell.draft.proxy, cell.host);\n value = cell.draft.proxy;\n }\n return [value as TState, cell.ctl as StatewireHost.StateCtl<TState>] as const;\n};\n\n// ─── commands ───────────────────────────────────────────\n\ntype Settled =\n | { type: \"resolved\"; value: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsInvocation = {\n readonly protocol?: string;\n readonly method: string;\n readonly params: readonly unknown[];\n readonly ts: number;\n /** Absent while the command waits in a client outbox. */\n readonly seq?: number;\n readonly id?: string;\n readonly clientId?: string;\n /** Present while the queued command is held. */\n readonly hold?: { meta?: unknown };\n};\n\nexport type CommandsOutcome =\n | { type: \"accepted\"; payload?: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsListener = {\n onStart: (inv: CommandsInvocation) => void;\n onPosted?: (inv: CommandsInvocation) => void;\n /** The invocation's seq or hold changed in place. */\n onChange?: (inv: CommandsInvocation) => void;\n onApplied: (inv: CommandsInvocation) => void;\n /** Canonical state now covers the invocation's effect — drop any overlay. */\n onCovered?: (inv: CommandsInvocation) => void;\n onSettle: (inv: CommandsInvocation, outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsSubmission = {\n method: string;\n params: readonly unknown[];\n clientId?: string;\n caller?: StatewireHost.Caller;\n context?: Statewire.Context;\n attach?: StatewireHost.Attach;\n onApplied?: () => void;\n onSettle?: (outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsHost = {\n setListener: (listener: CommandsListener | null) => void;\n submit?: (call: CommandsSubmission) => Promise<unknown>;\n /** Registered command names, read live. */\n methods?: () => string[];\n abort?: () => void;\n};\n\nconst commandsHosts = new WeakMap<object, CommandsHost>();\n\nexport const getCommandsHost = (commands: object): CommandsHost | undefined =>\n commandsHosts.get(commands);\n\nexport const bindCommandsHost = (commands: object, host: CommandsHost) => {\n commandsHosts.set(commands, host);\n};\n\ntype QueuedCall = {\n context?: Statewire.Context;\n method: string;\n params: readonly unknown[];\n ts: number;\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n origin?: CommandsSubmission;\n};\n\ntype ActiveInvocation = CommandsInvocation & {\n applied: boolean;\n controller: AbortController;\n settled: boolean;\n origin?: CommandsSubmission;\n};\n\ntype CommandsCell = {\n context: (() => Statewire.Context) | undefined;\n handlers: Record<string, (...args: never[]) => unknown> | null;\n queue: QueuedCall[];\n active: Map<ActiveInvocation, QueuedCall>;\n running: boolean;\n listener: CommandsListener | null;\n seq: number;\n unmounted: boolean;\n stallTimer: ReturnType<typeof setTimeout> | null;\n};\n\nconst STALL_WARN_MS = 5000;\n\n// In-process dispatch has no wire client: trivially attached.\nconst LOCAL_CALLER: StatewireHost.Caller = Object.freeze({\n get clientId(): string {\n throw new Error(\"statewire: in-process dispatch has no client id\");\n },\n auth: undefined,\n isConnected: true,\n});\n\nconst toSendError = (error: unknown): StatewireSendError => {\n if (\n !(error instanceof StatewireSendError) &&\n !(error instanceof StatewireReject)\n )\n console.error(\"statewire command failed\", error);\n return error instanceof StatewireSendError\n ? error\n : error instanceof StatewireReject\n ? new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n message: error.message,\n ...(error.code !== undefined && { code: error.code }),\n ...(error.payload !== undefined && { payload: error.payload }),\n })\n : new StatewireSendError({\n fate: \"failed\",\n cause: \"server\",\n message: \"the command failed\",\n });\n};\n\nconst applyInvocation = (cell: CommandsCell, inv: ActiveInvocation) => {\n if (inv.applied) return;\n inv.applied = true;\n cell.listener?.onApplied(inv);\n cell.listener?.onCovered?.(inv);\n inv.origin?.onApplied?.();\n};\n\nconst settleInvocation = (\n cell: CommandsCell,\n inv: ActiveInvocation,\n call: QueuedCall,\n settled: Settled,\n) => {\n if (inv.settled) return;\n inv.settled = true;\n cell.active.delete(inv);\n if (settled.type === \"resolved\") {\n applyInvocation(cell, inv);\n const outcome: CommandsOutcome =\n settled.value === undefined\n ? { type: \"accepted\" }\n : { type: \"accepted\", payload: sanitizeValue(settled.value) };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.resolve(outcome.payload);\n } else {\n cell.listener?.onCovered?.(inv);\n const error = toSendError(settled.error);\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n};\n\nconst abortInvocations = (cell: CommandsCell) => {\n for (const [inv, call] of cell.active) {\n inv.settled = true;\n inv.controller.abort();\n const error = new StatewireSendError({ fate: \"crashed\", cause: \"server\" });\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n cell.active.clear();\n};\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\";\n\n// A command is an own, enumerable, function-valued property — exactly what\n// `Object.keys` shows, so dispatch, `statewire/commands`, and the reserved-\n// namespace guard share one view; prototype-chain names are not commands.\nconst handlerOf = (\n handlers: Record<string, (...args: never[]) => unknown>,\n method: string,\n): ((...args: never[]) => unknown) | undefined => {\n if (!Object.prototype.propertyIsEnumerable.call(handlers, method)) {\n return undefined;\n }\n const handler = handlers[method];\n return typeof handler === \"function\" ? handler : undefined;\n};\n\nconst dispatch = (cell: CommandsCell, call: QueuedCall) => {\n const handler = handlerOf(cell.handlers!, call.method);\n if (handler === undefined) {\n const error = new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n code: \"unknown-command\",\n });\n call.origin?.onSettle?.({ type: \"rejected\", error });\n call.reject(error);\n return;\n }\n const inv: ActiveInvocation = {\n method: call.method,\n params: call.params,\n ts: call.ts,\n seq: ++cell.seq,\n applied: false,\n controller: new AbortController(),\n settled: false,\n ...(call.origin !== undefined && { origin: call.origin }),\n ...(call.origin?.clientId !== undefined && {\n clientId: call.origin.clientId,\n }),\n };\n cell.active.set(inv, call);\n cell.listener?.onStart(inv);\n cell.listener?.onPosted?.(inv);\n const attach = call.origin?.attach;\n const ctx: StatewireHost.Ctx = {\n caller: call.origin?.caller ?? LOCAL_CALLER,\n context: call.context ?? call.origin?.context ?? Object.freeze({}),\n get attach() {\n if (attach === undefined)\n throw new Error(\"statewire: in-process dispatch has no attach\");\n return attach;\n },\n signal: inv.controller.signal,\n applied: () => {\n if (!inv.settled) applyInvocation(cell, inv);\n },\n };\n try {\n const value = (\n handler as (\n this: StatewireHost.Ctx,\n ...args: readonly unknown[]\n ) => unknown\n ).call(ctx, ...call.params);\n if (isThenable(value)) {\n value.then(\n (resolved) =>\n settleInvocation(cell, inv, call, {\n type: \"resolved\",\n value: resolved,\n }),\n (error: unknown) =>\n settleInvocation(cell, inv, call, { type: \"rejected\", error }),\n );\n } else {\n settleInvocation(cell, inv, call, { type: \"resolved\", value });\n }\n } catch (error) {\n settleInvocation(cell, inv, call, { type: \"rejected\", error });\n }\n};\n\n// Prefix-only advancement: each handler runs synchronously to its first\n// await, then the pump moves on; settlement rides the returned promise.\nconst pump = (cell: CommandsCell) => {\n if (cell.running || !cell.handlers) {\n watchStall(cell);\n return;\n }\n cell.running = true;\n try {\n while (cell.handlers) {\n const call = cell.queue.shift();\n if (!call) break;\n dispatch(cell, call);\n }\n } finally {\n cell.running = false;\n }\n};\n\nconst watchStall = (cell: CommandsCell) => {\n if (!isDev() || cell.stallTimer !== null || cell.queue.length === 0) return;\n if (cell.handlers) return;\n cell.stallTimer = setTimeout(() => {\n cell.stallTimer = null;\n if (cell.handlers === null && cell.queue.length > 0 && !cell.unmounted) {\n console.warn(\n `statewire: ${cell.queue.length} buffered command(s) waiting over ${STALL_WARN_MS}ms — did the host forget to arm useStatewireCommands?`,\n );\n }\n }, STALL_WARN_MS);\n};\n\nexport const useStatewireCommands = <THandlers extends Statewire.Commands>(\n commands:\n | (THandlers & ThisType<StatewireHost.Ctx>)\n | undefined\n | null\n | false,\n options?: StatewireHost.CommandsOptions,\n): Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>> => {\n const [built] = useState(() => {\n const cell: CommandsCell = {\n context: undefined,\n handlers: null,\n queue: [],\n active: new Map(),\n running: false,\n listener: null,\n seq: 0,\n unmounted: false,\n stallTimer: null,\n };\n const proxy = createMethodProxy<\n Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>>\n >(\n (method, params) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n context: captureContext(cell.context?.() ?? {}),\n method,\n params,\n ts: Date.now(),\n resolve,\n reject,\n });\n pump(cell);\n }),\n );\n commandsHosts.set(proxy, {\n abort: () => abortInvocations(cell),\n setListener: (listener) => {\n cell.listener = listener;\n },\n methods: () => {\n const handlers = cell.handlers;\n if (!handlers) return [];\n return Object.keys(handlers).filter(\n (method) => handlerOf(handlers, method) !== undefined,\n );\n },\n submit: (origin) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method: origin.method,\n params: origin.params,\n ts: Date.now(),\n resolve,\n reject,\n origin,\n });\n pump(cell);\n }),\n });\n return { cell, proxy };\n });\n\n built.cell.context = options?.context;\n useEffect(() => {\n const { cell } = built;\n cell.unmounted = false;\n return () => {\n cell.unmounted = true;\n cell.handlers = null;\n if (cell.stallTimer !== null) {\n clearTimeout(cell.stallTimer);\n cell.stallTimer = null;\n }\n queueMicrotask(() => {\n if (!cell.unmounted) return;\n abortInvocations(cell);\n const queued = cell.queue;\n cell.queue = [];\n for (const call of queued) {\n call.reject(new Error(\"statewire: unmounted\"));\n }\n });\n };\n }, [built]);\n\n useEffect(() => {\n if (commands) {\n for (const method of Object.keys(commands)) {\n if (method.startsWith(\"statewire/\")) {\n throw new Error(\n `statewire: cannot register \"${method}\": the \"statewire/\" method namespace is reserved`,\n );\n }\n }\n }\n built.cell.handlers = commands || null;\n pump(built.cell);\n });\n\n return built.proxy;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"host.js","names":["captureContext","useEffect","useState","applyOps","sanitizeValue","StatewirePath","StatewireReject","createStateDraft","createMethodProxy","StateDraft","StatewireSendError","Statewire","StatewireHost","isDev","process","env","stateHosts","WeakMap","StateHost","documentIdentity","protocol","key","JSON","stringify","getStateHost","state","get","StateCell","current","draft","changed","documents","Map","Document","onOp","op","Op","onChange","onDocuments","ctl","StateCtl","host","bump","useStatewireState","init","TState","setVersion","cell","commit","c","flush","v","apply","ops","result","invalidate","push","changedPaths","snapshot","setOnOp","listener","setOnChange","takeChanged","mount","value","options","main","undefined","id","doc","Error","set","unmount","delete","setOnDocuments","record","changedPath","proxy","const","Settled","type","error","CommandsInvocation","method","params","ts","seq","clientId","held","localMeta","CommandsOutcome","payload","CommandsListener","onStart","inv","onPosted","onApplied","onCovered","onSettle","outcome","durable","CommandsSubmission","caller","Caller","context","Context","attach","Attach","CommandsHost","setListener","submit","call","Promise","methods","abort","commandsHosts","getCommandsHost","commands","bindCommandsHost","QueuedCall","resolve","reject","origin","ActiveInvocation","applied","controller","AbortController","settled","CommandsCell","handlers","Record","args","queue","active","running","unmounted","stallTimer","ReturnType","setTimeout","STALL_WARN_MS","LOCAL_CALLER","Object","freeze","auth","isConnected","toSendError","console","fate","cause","message","code","applyInvocation","settleInvocation","abortInvocations","clear","isThenable","PromiseLike","then","handlerOf","prototype","propertyIsEnumerable","handler","dispatch","ctx","Ctx","signal","this","resolved","pump","watchStall","shift","length","warn","useStatewireCommands","Commands","THandlers","ThisType","CommandsOptions","CommandsProxy","HandledCommands","built","Date","now","keys","filter","clearTimeout","queueMicrotask","queued","startsWith"],"sources":["../../src/host/host.ts"],"sourcesContent":["import { captureContext } from \"../wire/context\";\nimport { useEffect, useState } from \"react\";\nimport { applyOps, sanitizeValue } from \"../wire/ops\";\nimport type { StatewirePath } from \"../wire/ops\";\nimport { StatewireReject } from \"./engine\";\nimport { createStateDraft } from \"./draft\";\nimport { createMethodProxy } from \"../util/methodProxy\";\nimport type { StateDraft } from \"./draft\";\nimport { StatewireSendError } from \"../types\";\nimport type { Statewire } from \"../types\";\nimport type { StatewireHost } from \"../host\";\n\nconst isDev = () =>\n typeof process === \"undefined\" || process.env[\"NODE_ENV\"] !== \"production\";\n\n// ─── state ──────────────────────────────────────────────\n\nconst stateHosts = new WeakMap<object, StatewireHost.StateHost>();\n\nexport const documentIdentity = (protocol: string, key?: readonly string[]) =>\n JSON.stringify([protocol, key ?? null]);\n\n/** Host-side accessor for a `useStatewireState` state object's source (`snapshot()` reads the committed state). */\nexport const getStateHost = (\n state: object,\n): StatewireHost.StateHost | undefined => stateHosts.get(state);\n\ntype StateCell = {\n current: unknown;\n draft: StateDraft<object> | null;\n changed: StatewirePath[];\n documents: Map<string, StatewireHost.Document>;\n onOp: ((op: Statewire.Op) => void) | null;\n onChange: (() => void) | null;\n onDocuments: (() => void) | null;\n ctl: StatewireHost.StateCtl;\n host: StatewireHost.StateHost;\n bump: () => void;\n};\n\nexport const useStatewireState = <TState>(\n init: () => TState,\n): readonly [TState, StatewireHost.StateCtl<TState>] => {\n const [, setVersion] = useState(0);\n const [cell] = useState((): StateCell => {\n const commit = () => {\n if (c.draft) c.current = c.draft.flush();\n return c.current;\n };\n const c: StateCell = {\n current: sanitizeValue(init()),\n draft: null,\n changed: [[]],\n documents: new Map(),\n onOp: null,\n onChange: null,\n onDocuments: null,\n bump: () => setVersion((v) => v + 1),\n ctl: {\n apply: (ops) => {\n const result = applyOps(commit(), ops);\n c.current = result.state;\n c.draft?.invalidate();\n c.changed.push(...result.changedPaths);\n if (c.onOp) for (const op of ops) c.onOp(op);\n c.bump();\n c.onChange?.();\n },\n snapshot: commit,\n },\n host: {\n snapshot: commit,\n setOnOp: (listener) => {\n c.onOp = listener;\n },\n setOnChange: (listener) => {\n c.onChange = listener;\n },\n takeChanged: () => {\n const changed = c.changed;\n c.changed = [];\n return changed;\n },\n mount: (protocol, value, options) => {\n const key = options?.key;\n const main = options?.main ?? key === undefined;\n if (main) {\n for (const [id, doc] of c.documents) {\n if (\n doc.protocol === protocol &&\n doc.main &&\n id !== documentIdentity(protocol, key)\n )\n throw new Error(\n `statewire: ${protocol} already has a main document`,\n );\n }\n }\n c.documents.set(documentIdentity(protocol, key), {\n protocol,\n key: key === undefined ? null : [...key],\n main,\n value: sanitizeValue(value),\n });\n c.onDocuments?.();\n },\n unmount: (protocol, key) => {\n if (!c.documents.delete(documentIdentity(protocol, key)))\n throw new Error(\"statewire: document is not mounted\");\n c.onDocuments?.();\n },\n documents: () => c.documents,\n setOnDocuments: (listener) => {\n c.onDocuments = listener;\n },\n },\n };\n return c;\n });\n\n let value = cell.ctl.snapshot();\n if (typeof value === \"object\" && value !== null) {\n cell.draft ??= createStateDraft<object>({\n snapshot: () => cell.current,\n record: (op, changedPath) => {\n cell.changed.push(changedPath);\n cell.onOp?.(op);\n cell.bump();\n cell.onChange?.();\n },\n });\n stateHosts.set(cell.draft.proxy, cell.host);\n value = cell.draft.proxy;\n }\n return [value as TState, cell.ctl as StatewireHost.StateCtl<TState>] as const;\n};\n\n// ─── commands ───────────────────────────────────────────\n\ntype Settled =\n | { type: \"resolved\"; value: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsInvocation = {\n readonly protocol?: string;\n readonly method: string;\n readonly params: readonly unknown[];\n readonly ts: number;\n /** Absent while the command waits in a client outbox. */\n readonly seq?: number;\n readonly id?: string;\n readonly clientId?: string;\n /** Present while the queued command is held. */\n readonly held?: true;\n /** Client-only data persisted with an outbox entry. */\n readonly localMeta?: unknown;\n};\n\nexport type CommandsOutcome =\n | { type: \"accepted\"; payload?: unknown }\n | { type: \"rejected\"; error: unknown };\n\nexport type CommandsListener = {\n onStart: (inv: CommandsInvocation) => void;\n onPosted?: (inv: CommandsInvocation) => void;\n /** The invocation's seq, hold or local meta changed in place. */\n onChange?: (inv: CommandsInvocation) => void;\n onApplied: (inv: CommandsInvocation) => void;\n /** Canonical state now covers the invocation's effect — drop any overlay. */\n onCovered?: (inv: CommandsInvocation) => void;\n /** `durable`: the session persisted the settled update and delivers it itself. */\n onSettle: (\n inv: CommandsInvocation,\n outcome: CommandsOutcome,\n durable?: boolean,\n ) => void;\n};\n\nexport type CommandsSubmission = {\n method: string;\n params: readonly unknown[];\n clientId?: string;\n caller?: StatewireHost.Caller;\n context?: Statewire.Context;\n attach?: StatewireHost.Attach;\n onApplied?: () => void;\n onSettle?: (outcome: CommandsOutcome) => void;\n};\n\nexport type CommandsHost = {\n setListener: (listener: CommandsListener | null) => void;\n submit?: (call: CommandsSubmission) => Promise<unknown>;\n /** Registered command names, read live. */\n methods?: () => string[];\n abort?: () => void;\n};\n\nconst commandsHosts = new WeakMap<object, CommandsHost>();\n\nexport const getCommandsHost = (commands: object): CommandsHost | undefined =>\n commandsHosts.get(commands);\n\nexport const bindCommandsHost = (commands: object, host: CommandsHost) => {\n commandsHosts.set(commands, host);\n};\n\ntype QueuedCall = {\n context?: Statewire.Context;\n method: string;\n params: readonly unknown[];\n ts: number;\n resolve: (value: unknown) => void;\n reject: (error: unknown) => void;\n origin?: CommandsSubmission;\n};\n\ntype ActiveInvocation = CommandsInvocation & {\n applied: boolean;\n controller: AbortController;\n settled: boolean;\n origin?: CommandsSubmission;\n};\n\ntype CommandsCell = {\n context: (() => Statewire.Context) | undefined;\n handlers: Record<string, (...args: never[]) => unknown> | null;\n queue: QueuedCall[];\n active: Map<ActiveInvocation, QueuedCall>;\n running: boolean;\n listener: CommandsListener | null;\n seq: number;\n unmounted: boolean;\n stallTimer: ReturnType<typeof setTimeout> | null;\n};\n\nconst STALL_WARN_MS = 5000;\n\n// In-process dispatch has no wire client: trivially attached.\nconst LOCAL_CALLER: StatewireHost.Caller = Object.freeze({\n get clientId(): string {\n throw new Error(\"statewire: in-process dispatch has no client id\");\n },\n auth: undefined,\n isConnected: true,\n});\n\nconst toSendError = (error: unknown): StatewireSendError => {\n if (\n !(error instanceof StatewireSendError) &&\n !(error instanceof StatewireReject)\n )\n console.error(\"statewire command failed\", error);\n return error instanceof StatewireSendError\n ? error\n : error instanceof StatewireReject\n ? new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n message: error.message,\n ...(error.code !== undefined && { code: error.code }),\n ...(error.payload !== undefined && { payload: error.payload }),\n })\n : new StatewireSendError({\n fate: \"failed\",\n cause: \"server\",\n message: \"the command failed\",\n });\n};\n\nconst applyInvocation = (cell: CommandsCell, inv: ActiveInvocation) => {\n if (inv.applied) return;\n inv.applied = true;\n cell.listener?.onApplied(inv);\n cell.listener?.onCovered?.(inv);\n inv.origin?.onApplied?.();\n};\n\nconst settleInvocation = (\n cell: CommandsCell,\n inv: ActiveInvocation,\n call: QueuedCall,\n settled: Settled,\n) => {\n if (inv.settled) return;\n inv.settled = true;\n cell.active.delete(inv);\n if (settled.type === \"resolved\") {\n applyInvocation(cell, inv);\n const outcome: CommandsOutcome =\n settled.value === undefined\n ? { type: \"accepted\" }\n : { type: \"accepted\", payload: sanitizeValue(settled.value) };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.resolve(outcome.payload);\n } else {\n cell.listener?.onCovered?.(inv);\n const error = toSendError(settled.error);\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n};\n\nconst abortInvocations = (cell: CommandsCell) => {\n for (const [inv, call] of cell.active) {\n inv.settled = true;\n inv.controller.abort();\n const error = new StatewireSendError({ fate: \"crashed\", cause: \"server\" });\n const outcome: CommandsOutcome = { type: \"rejected\", error };\n cell.listener?.onSettle(inv, outcome);\n inv.origin?.onSettle?.(outcome);\n call.reject(error);\n }\n cell.active.clear();\n};\n\nconst isThenable = (value: unknown): value is PromiseLike<unknown> =>\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { then?: unknown }).then === \"function\";\n\n// A command is an own, enumerable, function-valued property — exactly what\n// `Object.keys` shows, so dispatch, `statewire/commands`, and the reserved-\n// namespace guard share one view; prototype-chain names are not commands.\nconst handlerOf = (\n handlers: Record<string, (...args: never[]) => unknown>,\n method: string,\n): ((...args: never[]) => unknown) | undefined => {\n if (!Object.prototype.propertyIsEnumerable.call(handlers, method)) {\n return undefined;\n }\n const handler = handlers[method];\n return typeof handler === \"function\" ? handler : undefined;\n};\n\nconst dispatch = (cell: CommandsCell, call: QueuedCall) => {\n const handler = handlerOf(cell.handlers!, call.method);\n if (handler === undefined) {\n const error = new StatewireSendError({\n fate: \"rejected\",\n cause: \"server\",\n code: \"unknown-command\",\n });\n call.origin?.onSettle?.({ type: \"rejected\", error });\n call.reject(error);\n return;\n }\n const inv: ActiveInvocation = {\n method: call.method,\n params: call.params,\n ts: call.ts,\n seq: ++cell.seq,\n applied: false,\n controller: new AbortController(),\n settled: false,\n ...(call.origin !== undefined && { origin: call.origin }),\n ...(call.origin?.clientId !== undefined && {\n clientId: call.origin.clientId,\n }),\n };\n cell.active.set(inv, call);\n cell.listener?.onStart(inv);\n cell.listener?.onPosted?.(inv);\n const attach = call.origin?.attach;\n const ctx: StatewireHost.Ctx = {\n caller: call.origin?.caller ?? LOCAL_CALLER,\n context: call.context ?? call.origin?.context ?? Object.freeze({}),\n get attach() {\n if (attach === undefined)\n throw new Error(\"statewire: in-process dispatch has no attach\");\n return attach;\n },\n signal: inv.controller.signal,\n applied: () => {\n if (!inv.settled) applyInvocation(cell, inv);\n },\n };\n try {\n const value = (\n handler as (\n this: StatewireHost.Ctx,\n ...args: readonly unknown[]\n ) => unknown\n ).call(ctx, ...call.params);\n if (isThenable(value)) {\n value.then(\n (resolved) =>\n settleInvocation(cell, inv, call, {\n type: \"resolved\",\n value: resolved,\n }),\n (error: unknown) =>\n settleInvocation(cell, inv, call, { type: \"rejected\", error }),\n );\n } else {\n settleInvocation(cell, inv, call, { type: \"resolved\", value });\n }\n } catch (error) {\n settleInvocation(cell, inv, call, { type: \"rejected\", error });\n }\n};\n\n// Prefix-only advancement: each handler runs synchronously to its first\n// await, then the pump moves on; settlement rides the returned promise.\nconst pump = (cell: CommandsCell) => {\n if (cell.running || !cell.handlers) {\n watchStall(cell);\n return;\n }\n cell.running = true;\n try {\n while (cell.handlers) {\n const call = cell.queue.shift();\n if (!call) break;\n dispatch(cell, call);\n }\n } finally {\n cell.running = false;\n }\n};\n\nconst watchStall = (cell: CommandsCell) => {\n if (!isDev() || cell.stallTimer !== null || cell.queue.length === 0) return;\n if (cell.handlers) return;\n cell.stallTimer = setTimeout(() => {\n cell.stallTimer = null;\n if (cell.handlers === null && cell.queue.length > 0 && !cell.unmounted) {\n console.warn(\n `statewire: ${cell.queue.length} buffered command(s) waiting over ${STALL_WARN_MS}ms — did the host forget to arm useStatewireCommands?`,\n );\n }\n }, STALL_WARN_MS);\n};\n\nexport const useStatewireCommands = <THandlers extends Statewire.Commands>(\n commands:\n | (THandlers & ThisType<StatewireHost.Ctx>)\n | undefined\n | null\n | false,\n options?: StatewireHost.CommandsOptions,\n): Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>> => {\n const [built] = useState(() => {\n const cell: CommandsCell = {\n context: undefined,\n handlers: null,\n queue: [],\n active: new Map(),\n running: false,\n listener: null,\n seq: 0,\n unmounted: false,\n stallTimer: null,\n };\n const proxy = createMethodProxy<\n Statewire.CommandsProxy<StatewireHost.HandledCommands<THandlers>>\n >(\n (method, params) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n context: captureContext(cell.context?.() ?? {}),\n method,\n params,\n ts: Date.now(),\n resolve,\n reject,\n });\n pump(cell);\n }),\n );\n commandsHosts.set(proxy, {\n abort: () => abortInvocations(cell),\n setListener: (listener) => {\n cell.listener = listener;\n },\n methods: () => {\n const handlers = cell.handlers;\n if (!handlers) return [];\n return Object.keys(handlers).filter(\n (method) => handlerOf(handlers, method) !== undefined,\n );\n },\n submit: (origin) =>\n new Promise((resolve, reject) => {\n if (cell.unmounted) {\n reject(new Error(\"statewire: commands called after unmount\"));\n return;\n }\n cell.queue.push({\n method: origin.method,\n params: origin.params,\n ts: Date.now(),\n resolve,\n reject,\n origin,\n });\n pump(cell);\n }),\n });\n return { cell, proxy };\n });\n\n built.cell.context = options?.context;\n useEffect(() => {\n const { cell } = built;\n cell.unmounted = false;\n return () => {\n cell.unmounted = true;\n cell.handlers = null;\n if (cell.stallTimer !== null) {\n clearTimeout(cell.stallTimer);\n cell.stallTimer = null;\n }\n queueMicrotask(() => {\n if (!cell.unmounted) return;\n abortInvocations(cell);\n const queued = cell.queue;\n cell.queue = [];\n for (const call of queued) {\n call.reject(new Error(\"statewire: unmounted\"));\n }\n });\n };\n }, [built]);\n\n useEffect(() => {\n if (commands) {\n for (const method of Object.keys(commands)) {\n if (method.startsWith(\"statewire/\")) {\n throw new Error(\n `statewire: cannot register \"${method}\": the \"statewire/\" method namespace is reserved`,\n );\n }\n }\n }\n built.cell.handlers = commands || null;\n pump(built.cell);\n });\n\n return built.proxy;\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { KeyValueSessionStorage } from "./storage.js";
|
|
2
|
+
import type { StatewireClient } from "./statewire.js";
|
|
3
|
+
/** A key-value backend over one IndexedDB database; writes broadcast to other runtimes. */
|
|
4
|
+
export declare const indexedDbBackend: (database: string) => KeyValueSessionStorage.Backend;
|
|
5
|
+
/** Shared session storage on IndexedDB: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */
|
|
6
|
+
export declare const IndexedDbSession: import("@assistant-ui/tap").Resource<StatewireClient.SessionStorage, [IndexedDbSession.Options]>;
|
|
7
|
+
export declare namespace IndexedDbSession {
|
|
8
|
+
type Options = Omit<KeyValueSessionStorage.Options, "backend" | "namespace"> & {
|
|
9
|
+
/** IndexedDB database name; default "statewire". */
|
|
10
|
+
database?: string;
|
|
11
|
+
/** Default "statewire-session:". */
|
|
12
|
+
namespace?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=indexedDb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexedDb.d.ts","sourceRoot":"","sources":["../src/indexedDb.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,qBAAkB;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAoB;AAanD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,KACf,sBAAsB,CAAC,OAmEzB,CAAC;AAmEF,4HAA4H;AAC5H,eAAO,MAAM,gBAAgB,kGAAgC,CAAC;AAE9D,yBAAiB,gBAAgB,CAAC;IAChC,KAAY,OAAO,GAAG,IAAI,CACxB,sBAAsB,CAAC,OAAO,EAC9B,SAAS,GAAG,WAAW,CACxB,GAAG;QACF,oDAAoD;QACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,oCAAoC;QACpC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { randomId } from "./util/randomId.js";
|
|
2
|
+
import { KeyValueSessionStorage } from "./storage.js";
|
|
3
|
+
import { useEffect, useState } from "@assistant-ui/tap/react-shim";
|
|
4
|
+
import { resource, useResource } from "@assistant-ui/tap";
|
|
5
|
+
import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
|
|
6
|
+
// src/indexedDb.ts
|
|
7
|
+
const STORE = "sessions";
|
|
8
|
+
const request = (req) => new Promise((resolve, reject) => {
|
|
9
|
+
req.onsuccess = () => resolve(req.result);
|
|
10
|
+
req.onerror = () => reject(req.error);
|
|
11
|
+
});
|
|
12
|
+
const backends = /* @__PURE__ */ new Map();
|
|
13
|
+
/** A key-value backend over one IndexedDB database; writes broadcast to other runtimes. */
|
|
14
|
+
const indexedDbBackend = (database) => {
|
|
15
|
+
const existing = backends.get(database);
|
|
16
|
+
if (existing) return existing;
|
|
17
|
+
if (typeof indexedDB === "undefined") throw new Error("statewire: IndexedDbSession requires indexedDB");
|
|
18
|
+
let opened;
|
|
19
|
+
const db = () => opened ??= (() => {
|
|
20
|
+
const open = indexedDB.open(database, 1);
|
|
21
|
+
open.onupgradeneeded = () => open.result.createObjectStore(STORE);
|
|
22
|
+
return request(open);
|
|
23
|
+
})();
|
|
24
|
+
const run = async (mode, fn) => {
|
|
25
|
+
const tx = (await db()).transaction(STORE, mode);
|
|
26
|
+
const committed = mode === "readonly" ? void 0 : new Promise((resolve, reject) => {
|
|
27
|
+
tx.oncomplete = () => resolve();
|
|
28
|
+
tx.onerror = () => reject(tx.error);
|
|
29
|
+
tx.onabort = () => reject(tx.error);
|
|
30
|
+
});
|
|
31
|
+
const result = await request(fn(tx.objectStore(STORE)));
|
|
32
|
+
await committed;
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
const channel = new BroadcastChannel(`statewire-idb:${database}`);
|
|
36
|
+
const announce = (key) => channel.postMessage(key);
|
|
37
|
+
const backend = {
|
|
38
|
+
getItem: async (key) => await run("readonly", (s) => s.get(key)) ?? null,
|
|
39
|
+
setItem: async (key, value) => {
|
|
40
|
+
await run("readwrite", (s) => s.put(value, key));
|
|
41
|
+
announce(key);
|
|
42
|
+
},
|
|
43
|
+
removeItem: async (key) => {
|
|
44
|
+
await run("readwrite", (s) => s.delete(key));
|
|
45
|
+
announce(key);
|
|
46
|
+
},
|
|
47
|
+
getAllKeys: async () => await run("readonly", (s) => s.getAllKeys()),
|
|
48
|
+
withUnused: (key, action) => navigator.locks.request(`statewire-active:${database}:${key}`, { ifAvailable: true }, (lock) => lock === null ? void 0 : navigator.locks.request(`statewire-session:${database}:${key}`, { ifAvailable: true }, (inner) => inner === null ? void 0 : action())),
|
|
49
|
+
subscribe: (listener) => {
|
|
50
|
+
const own = new BroadcastChannel(`statewire-idb:${database}`);
|
|
51
|
+
own.onmessage = (event) => listener(event.data);
|
|
52
|
+
return () => own.close();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
backends.set(database, backend);
|
|
56
|
+
return backend;
|
|
57
|
+
};
|
|
58
|
+
const holdLock = (name, mode = "exclusive") => {
|
|
59
|
+
let release;
|
|
60
|
+
let disposed = false;
|
|
61
|
+
navigator.locks.request(name, { mode }, () => {
|
|
62
|
+
if (disposed) return;
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
release = resolve;
|
|
65
|
+
});
|
|
66
|
+
}).catch(() => {});
|
|
67
|
+
return () => {
|
|
68
|
+
disposed = true;
|
|
69
|
+
release?.();
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
const useIndexedDbSession = (t0) => {
|
|
73
|
+
const $ = c(22);
|
|
74
|
+
const { database: t1, key, namespace: t2, ...options } = t0;
|
|
75
|
+
const database = t1 === void 0 ? "statewire" : t1;
|
|
76
|
+
const namespace = t2 === void 0 ? "statewire-session:" : t2;
|
|
77
|
+
if (typeof navigator === "undefined" || navigator.locks === void 0) throw new Error("statewire: IndexedDbSession requires the Web Locks API (navigator.locks)");
|
|
78
|
+
const backend = indexedDbBackend(database);
|
|
79
|
+
const storageKey = namespace + key;
|
|
80
|
+
const [tabId] = useState(randomId);
|
|
81
|
+
let t3;
|
|
82
|
+
let t4;
|
|
83
|
+
if ($[0] !== tabId) {
|
|
84
|
+
t3 = () => holdLock(`statewire-tab:${tabId}`);
|
|
85
|
+
t4 = [tabId];
|
|
86
|
+
$[0] = tabId;
|
|
87
|
+
$[1] = t3;
|
|
88
|
+
$[2] = t4;
|
|
89
|
+
} else {
|
|
90
|
+
t3 = $[1];
|
|
91
|
+
t4 = $[2];
|
|
92
|
+
}
|
|
93
|
+
useEffect(t3, t4);
|
|
94
|
+
let t5;
|
|
95
|
+
let t6;
|
|
96
|
+
if ($[3] !== database || $[4] !== storageKey) {
|
|
97
|
+
t5 = () => holdLock(`statewire-active:${database}:${storageKey}`, "shared");
|
|
98
|
+
t6 = [database, storageKey];
|
|
99
|
+
$[3] = database;
|
|
100
|
+
$[4] = storageKey;
|
|
101
|
+
$[5] = t5;
|
|
102
|
+
$[6] = t6;
|
|
103
|
+
} else {
|
|
104
|
+
t5 = $[5];
|
|
105
|
+
t6 = $[6];
|
|
106
|
+
}
|
|
107
|
+
useEffect(t5, t6);
|
|
108
|
+
const storage = useResource(KeyValueSessionStorage({
|
|
109
|
+
...options,
|
|
110
|
+
backend,
|
|
111
|
+
key,
|
|
112
|
+
namespace
|
|
113
|
+
}));
|
|
114
|
+
let t7;
|
|
115
|
+
let t8;
|
|
116
|
+
if ($[7] !== database || $[8] !== storageKey) {
|
|
117
|
+
t7 = (fn) => navigator.locks.request(`statewire-session:${database}:${storageKey}`, async () => fn());
|
|
118
|
+
t8 = (fn_0) => navigator.locks.request(`statewire-notify:${database}:${storageKey}`, fn_0);
|
|
119
|
+
$[7] = database;
|
|
120
|
+
$[8] = storageKey;
|
|
121
|
+
$[9] = t7;
|
|
122
|
+
$[10] = t8;
|
|
123
|
+
} else {
|
|
124
|
+
t7 = $[9];
|
|
125
|
+
t8 = $[10];
|
|
126
|
+
}
|
|
127
|
+
let t9;
|
|
128
|
+
if ($[11] !== tabId) {
|
|
129
|
+
t9 = {
|
|
130
|
+
id: tabId,
|
|
131
|
+
whenGone: _temp2
|
|
132
|
+
};
|
|
133
|
+
$[11] = tabId;
|
|
134
|
+
$[12] = t9;
|
|
135
|
+
} else t9 = $[12];
|
|
136
|
+
let t10;
|
|
137
|
+
if ($[13] !== backend || $[14] !== storageKey) {
|
|
138
|
+
t10 = (listener) => backend.subscribe((changed) => {
|
|
139
|
+
if (changed === storageKey) listener();
|
|
140
|
+
});
|
|
141
|
+
$[13] = backend;
|
|
142
|
+
$[14] = storageKey;
|
|
143
|
+
$[15] = t10;
|
|
144
|
+
} else t10 = $[15];
|
|
145
|
+
let t11;
|
|
146
|
+
if ($[16] !== storage || $[17] !== t10 || $[18] !== t7 || $[19] !== t8 || $[20] !== t9) {
|
|
147
|
+
t11 = {
|
|
148
|
+
...storage,
|
|
149
|
+
lock: t7,
|
|
150
|
+
notifyLock: t8,
|
|
151
|
+
owner: t9,
|
|
152
|
+
subscribe: t10
|
|
153
|
+
};
|
|
154
|
+
$[16] = storage;
|
|
155
|
+
$[17] = t10;
|
|
156
|
+
$[18] = t7;
|
|
157
|
+
$[19] = t8;
|
|
158
|
+
$[20] = t9;
|
|
159
|
+
$[21] = t11;
|
|
160
|
+
} else t11 = $[21];
|
|
161
|
+
return t11;
|
|
162
|
+
};
|
|
163
|
+
/** Shared session storage on IndexedDB: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */
|
|
164
|
+
const IndexedDbSession = resource(useIndexedDbSession);
|
|
165
|
+
function _temp() {}
|
|
166
|
+
function _temp2(id, signal) {
|
|
167
|
+
return navigator.locks.request(`statewire-tab:${id}`, { signal }, _temp);
|
|
168
|
+
}
|
|
169
|
+
export { IndexedDbSession, indexedDbBackend };
|
|
170
|
+
|
|
171
|
+
//# sourceMappingURL=indexedDb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexedDb.js","names":["useEffect","useMemo","useState","resource","useResource","KeyValueSessionStorage","StatewireClient","randomId","STORE","request","req","IDBRequest","T","Promise","resolve","reject","onsuccess","result","onerror","error","backends","Map","Backend","indexedDbBackend","database","existing","get","indexedDB","Error","opened","IDBDatabase","db","open","onupgradeneeded","createObjectStore","run","mode","IDBTransactionMode","fn","store","IDBObjectStore","tx","transaction","committed","undefined","oncomplete","onabort","objectStore","channel","BroadcastChannel","announce","key","postMessage","backend","getItem","s","setItem","value","put","removeItem","delete","getAllKeys","withUnused","action","navigator","locks","ifAvailable","lock","inner","subscribe","listener","own","onmessage","event","MessageEvent","data","close","set","holdLock","name","LockMode","release","disposed","catch","useIndexedDbSession","t0","$","_c","t1","namespace","t2","options","storageKey","tabId","t3","t4","t5","t6","storage","t7","t8","fn_0","t9","id","whenGone","_temp2","t10","changed","t11","notifyLock","owner","IndexedDbSession","Options","Omit","_temp","signal","_c"],"sources":["../src/indexedDb.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport { resource, useResource } from \"@assistant-ui/tap\";\nimport { KeyValueSessionStorage } from \"./storage\";\nimport type { StatewireClient } from \"./statewire\";\nimport { randomId } from \"./util/randomId\";\n\nconst STORE = \"sessions\";\n\nconst request = <T>(req: IDBRequest<T>) =>\n new Promise<T>((resolve, reject) => {\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n\nconst backends = new Map<string, KeyValueSessionStorage.Backend>();\n\n/** A key-value backend over one IndexedDB database; writes broadcast to other runtimes. */\nexport const indexedDbBackend = (\n database: string,\n): KeyValueSessionStorage.Backend => {\n const existing = backends.get(database);\n if (existing) return existing;\n if (typeof indexedDB === \"undefined\")\n throw new Error(\"statewire: IndexedDbSession requires indexedDB\");\n let opened: Promise<IDBDatabase> | undefined;\n const db = () =>\n (opened ??= (() => {\n const open = indexedDB.open(database, 1);\n open.onupgradeneeded = () => open.result.createObjectStore(STORE);\n return request(open);\n })());\n // Writes resolve once their transaction commits.\n const run = async <T>(\n mode: IDBTransactionMode,\n fn: (store: IDBObjectStore) => IDBRequest<T>,\n ) => {\n const tx = (await db()).transaction(STORE, mode);\n const committed =\n mode === \"readonly\"\n ? undefined\n : new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error);\n });\n const result = await request(fn(tx.objectStore(STORE)));\n await committed;\n return result;\n };\n const channel = new BroadcastChannel(`statewire-idb:${database}`);\n const announce = (key: string) => channel.postMessage(key);\n const backend: KeyValueSessionStorage.Backend = {\n getItem: async (key) =>\n ((await run(\"readonly\", (s) => s.get(key))) as string | undefined) ??\n null,\n setItem: async (key, value) => {\n await run(\"readwrite\", (s) => s.put(value, key));\n announce(key);\n },\n removeItem: async (key) => {\n await run(\"readwrite\", (s) => s.delete(key));\n announce(key);\n },\n getAllKeys: async () =>\n (await run(\"readonly\", (s) => s.getAllKeys())) as string[],\n withUnused: (key, action) =>\n navigator.locks.request(\n `statewire-active:${database}:${key}`,\n { ifAvailable: true },\n (lock) =>\n lock === null\n ? undefined\n : navigator.locks.request(\n `statewire-session:${database}:${key}`,\n { ifAvailable: true },\n (inner) => (inner === null ? undefined : action()),\n ),\n ),\n subscribe: (listener) => {\n const own = new BroadcastChannel(`statewire-idb:${database}`);\n own.onmessage = (event: MessageEvent<string>) => listener(event.data);\n return () => own.close();\n },\n };\n backends.set(database, backend);\n return backend;\n};\n\nconst holdLock = (name: string, mode: LockMode = \"exclusive\") => {\n let release: (() => void) | undefined;\n let disposed = false;\n void navigator.locks\n .request(name, { mode }, () => {\n if (disposed) return;\n return new Promise<void>((resolve) => {\n release = resolve;\n });\n })\n .catch(() => {});\n return () => {\n disposed = true;\n release?.();\n };\n};\n\nconst useIndexedDbSession = ({\n database = \"statewire\",\n key,\n namespace = \"statewire-session:\",\n ...options\n}: IndexedDbSession.Options): StatewireClient.SessionStorage => {\n if (typeof navigator === \"undefined\" || navigator.locks === undefined)\n throw new Error(\n \"statewire: IndexedDbSession requires the Web Locks API (navigator.locks)\",\n );\n const backend = indexedDbBackend(database);\n const storageKey = namespace + key;\n const [tabId] = useState(randomId);\n useEffect(() => holdLock(`statewire-tab:${tabId}`), [tabId]);\n useEffect(\n () => holdLock(`statewire-active:${database}:${storageKey}`, \"shared\"),\n [database, storageKey],\n );\n const storage = useResource(\n KeyValueSessionStorage({ ...options, backend, key, namespace }),\n );\n return useMemo(\n (): StatewireClient.SessionStorage => ({\n ...storage,\n lock: (fn) =>\n navigator.locks.request(\n `statewire-session:${database}:${storageKey}`,\n async () => fn(),\n ),\n notifyLock: (fn) =>\n navigator.locks.request(\n `statewire-notify:${database}:${storageKey}`,\n fn,\n ),\n owner: {\n id: tabId,\n whenGone: (id, signal) =>\n navigator.locks.request(`statewire-tab:${id}`, { signal }, () => {}),\n },\n subscribe: (listener) =>\n backend.subscribe!((changed) => {\n if (changed === storageKey) listener();\n }),\n }),\n [storage, backend, database, storageKey, tabId],\n );\n};\n\n/** Shared session storage on IndexedDB: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */\nexport const IndexedDbSession = resource(useIndexedDbSession);\n\nexport namespace IndexedDbSession {\n export type Options = Omit<\n KeyValueSessionStorage.Options,\n \"backend\" | \"namespace\"\n > & {\n /** IndexedDB database name; default \"statewire\". */\n database?: string;\n /** Default \"statewire-session:\". */\n namespace?: string;\n };\n}\n"],"mappings":"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"}
|