statewire 0.17.1 → 0.18.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/dist/client/connection.d.ts +2 -3
- package/dist/client/connection.d.ts.map +1 -1
- package/dist/client/connection.js +61 -25
- package/dist/client/connection.js.map +1 -1
- package/dist/client/session.d.ts +6 -2
- package/dist/client/session.d.ts.map +1 -1
- package/dist/client/session.js +189 -40
- package/dist/client/session.js.map +1 -1
- package/dist/client/storageError.d.ts +6 -0
- package/dist/client/storageError.d.ts.map +1 -0
- package/dist/client/storageError.js +12 -0
- package/dist/client/storageError.js.map +1 -0
- package/dist/statewire.d.ts +25 -11
- package/dist/statewire.d.ts.map +1 -1
- package/dist/statewire.js.map +1 -1
- package/dist/storage.d.ts +33 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +371 -0
- package/dist/storage.js.map +1 -0
- package/dist/transports/sse.d.ts +2 -0
- package/dist/transports/sse.d.ts.map +1 -1
- package/dist/transports/sse.js +3 -0
- package/dist/transports/sse.js.map +1 -1
- package/package.json +5 -1
- package/src/client/connection.ts +57 -28
- package/src/client/session.ts +262 -72
- package/src/client/storageError.ts +9 -0
- package/src/statewire.ts +31 -21
- package/src/storage.ts +601 -0
- package/src/transports/sse.ts +5 -0
package/dist/statewire.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statewire.js","names":["StatewireDocuments","StatewireProtocol","StatewireHost","useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","CONNECTED_CONNECTION","useStatewireCore","Statewire","TERMINAL","DocumentStorageContext","ProtocolContext","SessionStorageContext","useSessionTransport","transport","session","documents","protocol","StatewireClient","SessionStorage","Storage","Options","SessionTransport","useStatewire","Commands","options","TState","TCommands","Helpers","latest","current","storage","undefined","wireVersion","applicationProtocols","keyPrefixes","value","core","subscribe","listener","state","getState","isBusy","getBusy","failed","getFailed","connection","failures","NonNullable","Error","selection","invoke","method","params","updateContext","ops","commands","CommandsProxy","setContext","contextValue","Record","retryFailed","entry","FailedCommand","retry","discardFailed","discard","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Source","Selection","Promise","dispose","unmount","Readonly","Range","Offer","initialState","optimistic","Optimistic","materialize","Materialize","onStateChange","ctx","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","onError","error","ConnectionError","failureCovered","ConnectionState","status","reason","message","payload","attempt","cause","code","nextRetryAt","lastError","reconnect","degraded","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","predecessorLost","OnSessionLost","pending","ReadonlyArray","produced","SessionCommand","meta","failure","wire","protocols","SessionRecord","nextSeq","ctxNextSeq","context","verdicts","SessionVerdict","type","load","save","record","migrate","lock","fn","T","fin","Fin","HeadersOption","HeadersInit","staleAuth","HttpOptions","sessionStore","url","isNew","headers","fetch","globalThis","onSessionLost","onChange","onDone","WebsocketOptions","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import type { StatewireDocuments } from \"./wire/documents\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport type { StatewireHost } from \"./host\";\nimport { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useContextProvider,\n useResource,\n useResources,\n withKey,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { CONNECTED_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { TERMINAL } from \"./client/connection\";\nimport {\n DocumentStorageContext,\n ProtocolContext,\n SessionStorageContext,\n} from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n documents,\n protocol,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n documents: StatewireDocuments.Storage | undefined;\n protocol: StatewireProtocol.Options;\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(DocumentStorageContext, documents, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(ProtocolContext, protocol, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n ),\n ),\n );\n\n// The transport owns the session; it reads the storage off the context this\n// wrapper's own fiber provides, so plain-React callers get a provider too.\nconst SessionTransport = resource(useSessionTransport);\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const session = useResources(\n options.storage?.session !== undefined\n ? [withKey(\"session\", options.storage.session)]\n : [],\n )[0];\n const documents = useResources(\n options.storage?.documents === undefined\n ? []\n : [withKey(\"documents\", options.storage.documents)],\n )[0];\n const protocol: StatewireProtocol.Options = {\n ...(options.protocol !== undefined && { protocol: options.protocol }),\n ...(options.wireVersion !== undefined && {\n wireVersion: options.wireVersion,\n }),\n ...(options.applicationProtocols !== undefined && {\n applicationProtocols: options.applicationProtocols,\n }),\n ...(options.keyPrefixes !== undefined && {\n keyPrefixes: options.keyPrefixes,\n }),\n };\n const value = useResource(\n SessionTransport({\n transport: options.transport,\n session,\n documents,\n protocol,\n }),\n );\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);\n const failed = useSyncExternalStore(\n subscribe,\n core.getFailed,\n core.getFailed,\n );\n const connection = value.connection ?? CONNECTED_CONNECTION;\n\n return useMemo(() => {\n const failures = (): NonNullable<typeof value.failures> => {\n if (!value.failures) {\n throw new Error(\"statewire: this transport has no failed-send lane\");\n }\n return value.failures;\n };\n return {\n state,\n documents: value.documents,\n selection: value.selection,\n invoke: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => {\n if (!value.invoke)\n throw new Error(\n \"statewire: transport does not support protocol commands\",\n );\n return value.invoke(protocol, method, params);\n },\n updateContext: (ops: readonly unknown[]) => {\n if (!value.updateContext)\n throw new Error(\n \"statewire: transport does not support context documents\",\n );\n value.updateContext(ops);\n },\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n isBusy,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n value.setContext(contextValue);\n },\n failed,\n retryFailed: (entry: StatewireClient.FailedCommand) =>\n failures().retry(entry),\n discardFailed: (entry: StatewireClient.FailedCommand) =>\n failures().discard(entry),\n };\n }, [state, isBusy, failed, connection, core, value]);\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands>) {\n const ClientRoot = () => useStatewire<TState, TCommands>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n get documents(): StatewireDocuments.Source | undefined {\n return this.root.getValue().documents;\n }\n get selection(): StatewireProtocol.Selection | undefined {\n return this.root.getValue().selection;\n }\n invoke(\n protocol: string,\n method: string,\n params: readonly unknown[],\n ): Promise<unknown> {\n return this.root.getValue().invoke(protocol, method, params);\n }\n updateContext(ops: readonly unknown[]): void {\n this.root.getValue().updateContext(ops);\n }\n\n get isBusy(): boolean {\n return this.root.getValue().isBusy;\n }\n\n setContext(value: Record<string, unknown>): void {\n this.root.getValue().setContext(value);\n }\n\n get failed(): readonly StatewireClient.FailedCommand[] {\n return this.root.getValue().failed;\n }\n\n retryFailed(entry: StatewireClient.FailedCommand): Promise<unknown> {\n return this.root.getValue().retryFailed(entry);\n }\n\n discardFailed(entry: StatewireClient.FailedCommand): void {\n this.root.getValue().discardFailed(entry);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n protocol?: string;\n keyPrefixes?: Readonly<Record<string, readonly string[]>>;\n wireVersion?: StatewireProtocol.Range;\n applicationProtocols?: readonly StatewireProtocol.Offer[] | \"*\";\n transport: ResourceElement<Statewire<TState, TCommands>>;\n /** Durable session: the mounted storage holds this client's lane identity and its un-durable commands, which resend on the next attach. */\n storage?:\n | {\n session?: ResourceElement<SessionStorage> | undefined;\n documents?: ResourceElement<StatewireDocuments.Storage> | undefined;\n }\n | undefined;\n /** Served as the canonical state until the attach snapshot; own commands fold over it. */\n initialState?: TState;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n materialize?: Materialize<NonNullable<TState>>;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n onConnectionChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */\n failureCovered?: (\n state: NonNullable<TState>,\n entry: FailedCommand,\n ) => boolean;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n documents: StatewireDocuments.Source | undefined;\n selection: StatewireProtocol.Selection | undefined;\n invoke: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => Promise<unknown>;\n updateContext: (ops: readonly unknown[]) => void;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Negation of the server's broadcast idle level (a transport without idle is always busy). */\n isBusy: boolean;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => void;\n /** Non-accepted sends retained in the session record; reload with it. */\n failed: readonly FailedCommand[];\n /** Re-enqueues the entry's command on fresh clocks and removes the entry. */\n retryFailed: (entry: FailedCommand) => Promise<unknown>;\n /** Removes the entry. */\n discardFailed: (entry: FailedCommand) => void;\n };\n\n /** `cause`/`nextRetryAt`/`lastError` are present only while waiting out the backoff timer; a dial in flight carries `attempt` alone. */\n export type ConnectionState =\n | {\n status: \"disconnected\";\n reason:\n | \"deferred\"\n | \"idle\"\n | \"gone\"\n | \"forbidden\"\n | \"protocol-error\"\n | \"unauthorized\";\n message?: string;\n payload?: unknown;\n }\n | {\n status: \"connecting\";\n /** 0 = the episode's first dial, >0 = retry. */\n attempt: number;\n cause?: \"dropped\" | \"reconnect\" | \"error\" | \"unauthorized\";\n code?: string;\n nextRetryAt?: number;\n lastError?: string;\n message?: string;\n }\n | { status: \"connected\" };\n export type Connection = ConnectionState & {\n error?: ConnectionError;\n reconnect: () => void;\n /** True while retrying (`connecting` with `attempt > 0`) or parked `gone`. */\n degraded: boolean;\n };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * Mutate the draft to make server-accepted, still-uncommitted intents in\n * the state concrete (e.g. dispatch records into messages). Runs after the\n * optimistic folds on every published state.\n */\n export type Materialize<TState> = (draft: TState) => void;\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the server refused it\n * (`code` \"unknown-command\" | \"invalid-params\" for the static rejects,\n * code-less for handler rejects), \"lost\" = sent but no verdict will ever\n * arrive — the record is gone (a proven seq gap) or the channel died: it\n * may or may not have run, \"failed\" = the handler ran and threw,\n * \"crashed\" = the handler died involuntarily, \"expired\" = settled —\n * result, rejected, failed, or crashed — but the verdict outlived the\n * server's retention.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | {\n fate: \"rejected\";\n cause: \"server\";\n code?: string;\n message?: string;\n payload?: unknown;\n }\n | { fate: \"failed\"; cause: \"server\"; message?: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | { fate: \"expired\"; cause: \"server\" }\n | {\n fate: \"aborted\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n };\n\n /** Held commands a proven record gap settled `{fate: \"lost\", cause: \"session-lost\"}`; return \"requeue\" to re-enqueue them on the reset clocks. */\n export type OnSessionLost = (\n pending: ReadonlyArray<{\n method: string;\n params: readonly unknown[];\n ts: number;\n seq: number;\n clientId?: string;\n }>,\n ) => \"requeue\" | \"drop\";\n\n /** A non-accepted send retained for the user: the wire statement, its failure, and its original clocks. */\n export type FailedCommand = {\n readonly protocol: string;\n readonly produced?: SessionCommand[\"produced\"];\n readonly command: string;\n readonly params: readonly unknown[];\n readonly meta?: unknown;\n readonly failure: SendFailure;\n readonly ts: number;\n readonly seq: number;\n };\n\n /** A command as persisted while un-durable: its lane seq plus the wire statement. */\n export type SessionCommand = {\n seq: number;\n protocol: string;\n produced?: {\n wire: StatewireProtocol.Range;\n protocols: readonly StatewireProtocol.Offer[] | \"*\";\n };\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** The persisted session: the lane identity, the lane clocks, and the commands the server has not marked durable. */\n export type SessionRecord = {\n clientId: string;\n nextSeq: number;\n ctxNextSeq: number;\n context?: { ops: unknown[]; pending: { seq: number; ops: unknown[] }[] };\n commands: readonly SessionCommand[];\n /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */\n verdicts?: readonly SessionVerdict[];\n /** Non-accepted sends retained for the user; survive record rebases and resets. */\n failed?: readonly FailedCommand[];\n };\n\n /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */\n export type SessionVerdict = {\n seq: number;\n type: (typeof TERMINAL)[number];\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n };\n\n /** Session storage seam; `save` must persist synchronously in the caller's flow. */\n export type SessionStorage = {\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\n /** Upgrade incompatible stored commands; null discards the old record and starts a new client identity. */\n migrate?: (\n record: SessionRecord,\n current: NonNullable<SessionCommand[\"produced\"]>,\n ) => SessionRecord | null;\n /** Serializes record mutations across sharers; presence marks the storage shared. */\n lock?: <T>(fn: () => T | Promise<T>) => Promise<T>;\n /** Notifies of record saves by other sharers; returns unsubscribe. Required alongside `lock`. */\n subscribe?: (listener: () => void) => () => void;\n };\n\n /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */\n export type ConnectionError = Error & { fin: StatewireHost.Fin };\n\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type HttpOptions = StatewireProtocol.Options & {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n\n export type WebsocketOptions = StatewireProtocol.Options & {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"statewire.js","names":["StatewireDocuments","StatewireProtocol","StatewireHost","useMemo","useRef","useState","useSyncExternalStore","createTapRoot","flushTapSync","resource","useContextProvider","useResource","useResources","withKey","ResourceElement","CONNECTED_CONNECTION","useStatewireCore","Statewire","TERMINAL","DocumentStorageContext","ProtocolContext","SessionStorageContext","useSessionTransport","transport","session","documents","protocol","StatewireClient","SessionStorage","Storage","Options","SessionTransport","useStatewire","Commands","options","TState","TCommands","Helpers","latest","current","storage","undefined","wireVersion","applicationProtocols","keyPrefixes","value","core","subscribe","listener","state","getState","isBusy","getBusy","failed","getFailed","connection","failures","NonNullable","Error","selection","invoke","method","params","updateContext","ops","commands","CommandsProxy","setContext","contextValue","Record","retryFailed","entry","FailedCommand","retry","discardFailed","discard","StatewireResource","root","disposed","constructor","ClientRoot","getValue","Connection","Source","Selection","Promise","dispose","unmount","Readonly","Range","Offer","initialState","optimistic","Optimistic","materialize","Materialize","onStateChange","ctx","changes","Changes","onCommandChange","update","CommandUpdate","onConnectionChange","onError","error","ConnectionError","failureCovered","ConnectionState","status","reason","degraded","attempt","cause","code","nextRetryAt","lastError","message","payload","reconnect","ConnectionStatus","draft","command","Command","OptimisticContext","ts","seq","clientId","SendFailure","fate","predecessorLost","OnSessionLost","pending","ReadonlyArray","produced","SessionCommand","meta","failure","wire","protocols","SessionRecord","nextSeq","ctxNextSeq","context","recover","verdicts","SessionVerdict","type","ready","flush","load","save","record","migrate","lock","fn","T","fin","Fin","HeadersOption","HeadersInit","staleAuth","HttpOptions","sessionStore","url","isNew","headers","fetch","globalThis","onSessionLost","onChange","onDone","WebsocketOptions","query","webSocket","WebSocket"],"sources":["../src/statewire.ts"],"sourcesContent":["import type { StatewireDocuments } from \"./wire/documents\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport type { StatewireHost } from \"./host\";\nimport { useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport {\n createTapRoot,\n flushTapSync,\n resource,\n useContextProvider,\n useResource,\n useResources,\n withKey,\n} from \"@assistant-ui/tap\";\nimport type { ResourceElement } from \"@assistant-ui/tap\";\nimport { CONNECTED_CONNECTION, useStatewireCore } from \"./client/core\";\nimport type { Statewire } from \"./types\";\nimport type { TERMINAL } from \"./client/connection\";\nimport {\n DocumentStorageContext,\n ProtocolContext,\n SessionStorageContext,\n} from \"./client/sessionStore\";\n\nconst useSessionTransport = ({\n transport,\n session,\n documents,\n protocol,\n}: {\n transport: ResourceElement<Statewire<any, any>>;\n session: StatewireClient.SessionStorage | undefined;\n documents: StatewireDocuments.Storage | undefined;\n protocol: StatewireProtocol.Options;\n}): Statewire<unknown> =>\n useContextProvider(SessionStorageContext, session, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(DocumentStorageContext, documents, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useContextProvider(ProtocolContext, protocol, () =>\n // oxlint-disable-next-line react-hooks/rules-of-hooks\n useResource(transport),\n ),\n ),\n );\n\n// The transport owns the session; it reads the storage off the context this\n// wrapper's own fiber provides, so plain-React callers get a provider too.\nconst SessionTransport = resource(useSessionTransport);\n\nexport const useStatewire = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): StatewireClient.Helpers<TState, TCommands> => {\n const latest = useRef(options);\n latest.current = options;\n\n const session = useResources(\n options.storage?.session !== undefined\n ? [withKey(\"session\", options.storage.session)]\n : [],\n )[0];\n const documents = useResources(\n options.storage?.documents === undefined\n ? []\n : [withKey(\"documents\", options.storage.documents)],\n )[0];\n const protocol: StatewireProtocol.Options = {\n ...(options.protocol !== undefined && { protocol: options.protocol }),\n ...(options.wireVersion !== undefined && {\n wireVersion: options.wireVersion,\n }),\n ...(options.applicationProtocols !== undefined && {\n applicationProtocols: options.applicationProtocols,\n }),\n ...(options.keyPrefixes !== undefined && {\n keyPrefixes: options.keyPrefixes,\n }),\n };\n const value = useResource(\n SessionTransport({\n transport: options.transport,\n session,\n documents,\n protocol,\n }),\n );\n const core = useStatewireCore(() => latest.current, value);\n const [subscribe] = useState(\n () => (listener: () => void) =>\n core.subscribe(() => flushTapSync(listener)),\n );\n\n const state = useSyncExternalStore(\n subscribe,\n core.getState,\n core.getState,\n ) as TState;\n const isBusy = useSyncExternalStore(subscribe, core.getBusy, core.getBusy);\n const failed = useSyncExternalStore(\n subscribe,\n core.getFailed,\n core.getFailed,\n );\n const connection = value.connection ?? CONNECTED_CONNECTION;\n\n return useMemo(() => {\n const failures = (): NonNullable<typeof value.failures> => {\n if (!value.failures) {\n throw new Error(\"statewire: this transport has no failed-send lane\");\n }\n return value.failures;\n };\n return {\n state,\n documents: value.documents,\n selection: value.selection,\n invoke: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => {\n if (!value.invoke)\n throw new Error(\n \"statewire: transport does not support protocol commands\",\n );\n return value.invoke(protocol, method, params);\n },\n updateContext: (ops: readonly unknown[]) => {\n if (!value.updateContext)\n throw new Error(\n \"statewire: transport does not support context documents\",\n );\n value.updateContext(ops);\n },\n getState: core.getState as () => TState,\n connection,\n commands: core.commands as Statewire.CommandsProxy<TCommands>,\n isBusy,\n setContext: (contextValue: Record<string, unknown>) => {\n if (!value.setContext) {\n throw new Error(\n \"statewire: this transport has no client context channel\",\n );\n }\n value.setContext(contextValue);\n },\n failed,\n retryFailed: (entry: StatewireClient.FailedCommand) =>\n failures().retry(entry),\n discardFailed: (entry: StatewireClient.FailedCommand) =>\n failures().discard(entry),\n };\n }, [state, isBusy, failed, connection, core, value]);\n};\n\nexport const StatewireResource = <\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n>(\n options: StatewireClient.Options<TState, TCommands>,\n): ResourceElement<StatewireClient.Helpers<TState, TCommands>> =>\n resource(useStatewire<TState, TCommands>)(options);\n\nexport class StatewireClient<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n> {\n private readonly root;\n private disposed = false;\n\n constructor(options: StatewireClient.Options<TState, TCommands>) {\n const ClientRoot = () => useStatewire<TState, TCommands>(options);\n this.root = createTapRoot(ClientRoot);\n }\n\n get state(): TState {\n return this.root.getValue().getState();\n }\n\n get connection(): StatewireClient.Connection {\n return this.root.getValue().connection;\n }\n\n get commands(): Statewire.CommandsProxy<TCommands> {\n return this.root.getValue().commands;\n }\n\n get documents(): StatewireDocuments.Source | undefined {\n return this.root.getValue().documents;\n }\n get selection(): StatewireProtocol.Selection | undefined {\n return this.root.getValue().selection;\n }\n invoke(\n protocol: string,\n method: string,\n params: readonly unknown[],\n ): Promise<unknown> {\n return this.root.getValue().invoke(protocol, method, params);\n }\n updateContext(ops: readonly unknown[]): void {\n this.root.getValue().updateContext(ops);\n }\n\n get isBusy(): boolean {\n return this.root.getValue().isBusy;\n }\n\n setContext(value: Record<string, unknown>): void {\n this.root.getValue().setContext(value);\n }\n\n get failed(): readonly StatewireClient.FailedCommand[] {\n return this.root.getValue().failed;\n }\n\n retryFailed(entry: StatewireClient.FailedCommand): Promise<unknown> {\n return this.root.getValue().retryFailed(entry);\n }\n\n discardFailed(entry: StatewireClient.FailedCommand): void {\n this.root.getValue().discardFailed(entry);\n }\n\n subscribe(listener: () => void): () => void {\n return this.root.subscribe(listener);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.root.unmount();\n }\n}\n\nexport namespace StatewireClient {\n export type Options<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n protocol?: string;\n keyPrefixes?: Readonly<Record<string, readonly string[]>>;\n wireVersion?: StatewireProtocol.Range;\n applicationProtocols?: readonly StatewireProtocol.Offer[] | \"*\";\n transport: ResourceElement<Statewire<TState, TCommands>>;\n /** Durable session: the mounted storage holds this client's lane identity and its un-durable commands, which resend on the next attach. */\n storage?:\n | {\n session?: ResourceElement<SessionStorage> | undefined;\n documents?: ResourceElement<StatewireDocuments.Storage> | undefined;\n }\n | undefined;\n /** Served as the canonical state until the attach snapshot; own commands fold over it. */\n initialState?: TState;\n optimistic?: Optimistic<NonNullable<TState>, TCommands>;\n materialize?: Materialize<NonNullable<TState>>;\n onStateChange?: (\n state: NonNullable<TState>,\n ctx: { changes: Statewire.Changes },\n ) => void;\n onCommandChange?: (\n update: Statewire.CommandUpdate<TCommands>,\n ctx: { state: NonNullable<TState> },\n ) => void;\n onConnectionChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** Drops a retained failed entry once a canonical snapshot shows its effect landed; checked against each new snapshot. */\n failureCovered?: (\n state: NonNullable<TState>,\n entry: FailedCommand,\n ) => boolean;\n };\n\n export type Helpers<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = {\n /** The statewire's state type — remote statewires include `undefined` until the first attach snapshot applies. */\n state: TState;\n documents: StatewireDocuments.Source | undefined;\n selection: StatewireProtocol.Selection | undefined;\n invoke: (\n protocol: string,\n method: string,\n params: readonly unknown[],\n ) => Promise<unknown>;\n updateContext: (ops: readonly unknown[]) => void;\n /** Identity-stable across renders; reads the current canonical state without subscribing the caller. */\n getState: () => TState;\n connection: Connection;\n commands: Statewire.CommandsProxy<TCommands>;\n /** Negation of the server's broadcast idle level (a transport without idle is always busy). */\n isBusy: boolean;\n /** Replace this client's ambient context on the server; throws on transports without a client context channel. */\n setContext: (value: Record<string, unknown>) => void;\n /** Non-accepted sends retained in the session record; reload with it. */\n failed: readonly FailedCommand[];\n /** Re-enqueues the entry's command on fresh clocks and removes the entry. */\n retryFailed: (entry: FailedCommand) => Promise<unknown>;\n /** Removes the entry. */\n discardFailed: (entry: FailedCommand) => void;\n };\n\n /** Connection lifecycle and whether its current state warrants a problem indicator. */\n export type ConnectionState =\n | { status: \"standby\"; reason: \"deferred\" | \"idle\"; degraded: false }\n | { status: \"connecting\"; degraded: false }\n | { status: \"connected\"; degraded: false }\n | {\n status: \"retrying\";\n degraded: true;\n attempt: number;\n /** Backoff details are absent while the retry dial is in flight. */\n cause?: \"dropped\" | \"reconnect\" | \"error\" | \"unauthorized\";\n code?: string;\n nextRetryAt?: number;\n lastError?: string;\n message?: string;\n }\n | {\n status: \"stopped\";\n degraded: true;\n reason:\n | \"gone\"\n | \"forbidden\"\n | \"protocol-error\"\n | \"unauthorized\"\n | \"storage-error\";\n message?: string;\n payload?: unknown;\n };\n export type Connection = ConnectionState & {\n error?: ConnectionError;\n reconnect: () => void;\n };\n export type ConnectionStatus = Connection[\"status\"];\n\n /**\n * Mutate the draft in place; the client records the next state plus the exact\n * changed paths. Re-run against the new base on every rebase.\n */\n export type Optimistic<\n TState,\n TCommands extends Statewire.Commands = Statewire.Commands,\n > = (\n draft: TState,\n command: Statewire.Command<TCommands>,\n ctx: OptimisticContext,\n ) => void;\n\n export type OptimisticContext = {\n ts: number;\n seq: number;\n clientId?: string;\n };\n\n /**\n * Mutate the draft to make server-accepted, still-uncommitted intents in\n * the state concrete (e.g. dispatch records into messages). Runs after the\n * optimistic folds on every published state.\n */\n export type Materialize<TState> = (draft: TState) => void;\n\n /**\n * fate: \"aborted\" = never sent, \"rejected\" = the server refused it\n * (`code` \"unknown-command\" | \"invalid-params\" for the static rejects,\n * code-less for handler rejects), \"lost\" = sent but no verdict will ever\n * arrive — the record is gone (a proven seq gap) or the channel died: it\n * may or may not have run, \"failed\" = the handler ran and threw,\n * \"crashed\" = the handler died involuntarily, \"expired\" = settled —\n * result, rejected, failed, or crashed — but the verdict outlived the\n * server's retention.\n * cause is which channel settled it: \"server\" = the server's answer;\n * the rest are channel deaths the client settled locally.\n */\n export type SendFailure =\n | {\n fate: \"rejected\";\n cause: \"server\";\n code?: string;\n message?: string;\n payload?: unknown;\n }\n | { fate: \"failed\"; cause: \"server\"; message?: string }\n | { fate: \"crashed\"; cause: \"server\" }\n | { fate: \"expired\"; cause: \"server\" }\n | {\n fate: \"aborted\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n predecessorLost?: true;\n }\n | {\n fate: \"lost\";\n cause: \"unmount\" | \"stream-gone\" | \"session-lost\";\n };\n\n /** Held commands a proven record gap settled `{fate: \"lost\", cause: \"session-lost\"}`; return \"requeue\" to re-enqueue them on the reset clocks. */\n export type OnSessionLost = (\n pending: ReadonlyArray<{\n method: string;\n params: readonly unknown[];\n ts: number;\n seq: number;\n clientId?: string;\n }>,\n ) => \"requeue\" | \"drop\";\n\n /** A non-accepted send retained for the user: the wire statement, its failure, and its original clocks. */\n export type FailedCommand = {\n readonly protocol: string;\n readonly produced?: SessionCommand[\"produced\"];\n readonly command: string;\n readonly params: readonly unknown[];\n readonly meta?: unknown;\n readonly failure: SendFailure;\n readonly ts: number;\n readonly seq: number;\n };\n\n /** A command as persisted while un-durable: its lane seq plus the wire statement. */\n export type SessionCommand = {\n seq: number;\n protocol: string;\n produced?: {\n wire: StatewireProtocol.Range;\n protocols: readonly StatewireProtocol.Offer[] | \"*\";\n };\n method: string;\n params: unknown[];\n meta?: unknown;\n };\n\n /** The persisted session: the lane identity, the lane clocks, and the commands the server has not marked durable. */\n export type SessionRecord = {\n clientId: string;\n nextSeq: number;\n ctxNextSeq: number;\n context?: {\n ops: unknown[];\n pending: { seq: number; ops: unknown[] }[];\n /** Recover the original wire's unpersisted single-document context after the next hello. */\n recover?: \"version-1\";\n };\n commands: readonly SessionCommand[];\n /** Terminal verdicts parked for the sharer that minted the seq (shared storage only). */\n verdicts?: readonly SessionVerdict[];\n /** Non-accepted sends retained for the user; survive record rebases and resets. */\n failed?: readonly FailedCommand[];\n };\n\n /** A terminal verdict one sharer's connection carried, parked for the sharer awaiting the seq. */\n export type SessionVerdict = {\n seq: number;\n type: (typeof TERMINAL)[number];\n code?: string;\n message?: string;\n payload?: unknown;\n meta?: unknown;\n };\n\n /** Session storage seam with optional asynchronous hydration and persistence. */\n export type SessionStorage = {\n /** Resolves when a synchronous load can return the hydrated record. */\n ready?: Promise<void>;\n /** Resolves after all queued saves are persisted; awaited before sending statements. */\n flush?: () => void | Promise<void>;\n load: () => SessionRecord | null;\n save: (record: SessionRecord) => void;\n /** Upgrade incompatible stored commands; null discards the old record and starts a new client identity. */\n migrate?: (\n record: SessionRecord,\n current: NonNullable<SessionCommand[\"produced\"]>,\n ) => SessionRecord | null;\n /** Serializes record mutations across sharers; presence marks the storage shared. */\n lock?: <T>(fn: () => T | Promise<T>) => Promise<T>;\n /** Notifies of record saves by other sharers; returns unsubscribe. Required alongside `lock`. */\n subscribe?: (listener: () => void) => () => void;\n };\n\n /** Static headers, or a resolver invoked per request/attach; `staleAuth` is true right after an auth rejection. */\n export type ConnectionError = Error & { fin?: StatewireHost.Fin };\n\n export type HeadersOption =\n | HeadersInit\n | ((ctx: { staleAuth: boolean }) => HeadersInit | Promise<HeadersInit>);\n\n export type HttpOptions = StatewireProtocol.Options & {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n fetch?: typeof globalThis.fetch;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n\n export type WebsocketOptions = StatewireProtocol.Options & {\n sessionStore?: SessionStorage | undefined;\n url: string;\n /** The session does not exist on the server yet: the first attach waits for the first command send (or an explicit `reconnect()`). */\n isNew?: boolean;\n headers?: HeadersOption;\n protocols?: string | string[];\n query?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n webSocket?: new (url: string, protocols?: string | string[]) => WebSocket;\n onSessionLost?: OnSessionLost;\n onChange?: (connection: Connection) => void;\n onError?: (error: ConnectionError) => void;\n /** The broadcast idle flag's rising edge: the instance went quiet. */\n onDone?: () => void;\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { StatewireClient } from "./statewire.js";
|
|
2
|
+
//#region src/storage.d.ts
|
|
3
|
+
/** Persist sessions through a synchronous or asynchronous string storage backend. */
|
|
4
|
+
declare const KeyValueSessionStorage: import("@assistant-ui/tap").Resource<StatewireClient.SessionStorage, [options: KeyValueSessionStorage.Options]>;
|
|
5
|
+
declare namespace KeyValueSessionStorage {
|
|
6
|
+
type Backend = {
|
|
7
|
+
getItem: (key: string) => string | null | Promise<string | null>;
|
|
8
|
+
setItem: (key: string, value: string) => void | Promise<void>;
|
|
9
|
+
removeItem: (key: string) => void | Promise<void>;
|
|
10
|
+
getAllKeys?: () => readonly string[] | Promise<readonly string[]>;
|
|
11
|
+
/** Runs cleanup under exclusive access, skipping keys in use by another runtime. */
|
|
12
|
+
withUnused?: (key: string, action: () => Promise<void>) => Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
type Options = {
|
|
15
|
+
backend: Backend;
|
|
16
|
+
key: string;
|
|
17
|
+
namespace?: string;
|
|
18
|
+
/** The application format used by sessions saved before version negotiation. */
|
|
19
|
+
legacyProtocol?: {
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
};
|
|
23
|
+
migrate?: StatewireClient.SessionStorage["migrate"];
|
|
24
|
+
retention?: {
|
|
25
|
+
maxAgeMs?: number;
|
|
26
|
+
maxEntries?: number;
|
|
27
|
+
};
|
|
28
|
+
onError?: (error: unknown) => void;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { KeyValueSessionStorage };
|
|
33
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","names":[],"sources":["../src/storage.ts"],"mappings":";;;cAmkBa,oDAAsB,SAAA,gBAAA,iBAAA,SAAA,uBAAA;kBAElB;OACH;IACV,UAAU,gCAAgC;IAC1C,UAAU,aAAa,yBAAyB;IAChD,aAAa,uBAAuB;IACpC,uCAAuC;;IAEvC,cAAc,aAAa,cAAc,kBAAkB;;OAEjD;IACV,SAAS;IACT;IACA;;IAEA;MAAmB;MAAc;;IACjC,UAAU,gBAAgB;IAC1B;MAAc;MAAmB;;IACjC,WAAW"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { negotiationHeaders, protocolToken } from "./wire/protocol.js";
|
|
2
|
+
import { applyDocumentOps } from "./wire/documents.js";
|
|
3
|
+
import { wrapStorageError } from "./client/storageError.js";
|
|
4
|
+
import { useEffect, useMemo, useRef } from "@assistant-ui/tap/react-shim";
|
|
5
|
+
import { resource } from "@assistant-ui/tap";
|
|
6
|
+
//#region src/storage.ts
|
|
7
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
const isOptionalString = (value) => value === void 0 || typeof value === "string";
|
|
9
|
+
const isPositiveSafeInteger = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
10
|
+
const isSessionCommand = (value) => isRecord(value) && isPositiveSafeInteger(value["seq"]) && isProtocol(value["protocol"]) && isProducer(value["produced"]) && typeof value["method"] === "string" && Array.isArray(value["params"]);
|
|
11
|
+
const isSessionVerdict = (value) => isRecord(value) && isPositiveSafeInteger(value["seq"]) && (value["type"] === "result" || value["type"] === "rejected" || value["type"] === "failed" || value["type"] === "crashed" || value["type"] === "expired") && isOptionalString(value["code"]) && isOptionalString(value["message"]);
|
|
12
|
+
const isSendFailure = (value) => {
|
|
13
|
+
if (!isRecord(value)) return false;
|
|
14
|
+
switch (value["fate"]) {
|
|
15
|
+
case "rejected": return value["cause"] === "server" && isOptionalString(value["code"]) && isOptionalString(value["message"]);
|
|
16
|
+
case "failed": return value["cause"] === "server" && isOptionalString(value["message"]);
|
|
17
|
+
case "crashed":
|
|
18
|
+
case "expired": return value["cause"] === "server";
|
|
19
|
+
case "aborted": return (value["cause"] === "unmount" || value["cause"] === "stream-gone" || value["cause"] === "session-lost") && (value["predecessorLost"] === void 0 || value["predecessorLost"] === true);
|
|
20
|
+
case "lost": return value["cause"] === "unmount" || value["cause"] === "stream-gone" || value["cause"] === "session-lost";
|
|
21
|
+
default: return false;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const isFailedCommand = (value) => isRecord(value) && isProtocol(value["protocol"]) && isProducer(value["produced"]) && typeof value["command"] === "string" && Array.isArray(value["params"]) && isSendFailure(value["failure"]) && typeof value["ts"] === "number" && Number.isFinite(value["ts"]) && isPositiveSafeInteger(value["seq"]);
|
|
25
|
+
const isSessionRecord = (value) => isRecord(value) && typeof value["clientId"] === "string" && value["clientId"].length > 0 && isContext(value["context"]) && isPositiveSafeInteger(value["nextSeq"]) && isPositiveSafeInteger(value["ctxNextSeq"]) && Array.isArray(value["commands"]) && value["commands"].every(isSessionCommand) && (value["verdicts"] === void 0 || Array.isArray(value["verdicts"]) && value["verdicts"].every(isSessionVerdict)) && (value["failed"] === void 0 || Array.isArray(value["failed"]) && value["failed"].every(isFailedCommand));
|
|
26
|
+
const isProtocol = (value) => value === void 0 || typeof value === "string" && value !== "*" && protocolToken.test(value);
|
|
27
|
+
const isProducer = (value) => {
|
|
28
|
+
if (value === void 0) return true;
|
|
29
|
+
if (!isRecord(value) || !isRecord(value["wire"])) return false;
|
|
30
|
+
try {
|
|
31
|
+
negotiationHeaders({
|
|
32
|
+
wireVersion: value["wire"],
|
|
33
|
+
applicationProtocols: value["protocols"]
|
|
34
|
+
});
|
|
35
|
+
return value["protocols"] !== void 0;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const isContextOperation = (value) => {
|
|
41
|
+
if (!isRecord(value)) return false;
|
|
42
|
+
if (value["doc"] !== void 0 && (typeof value["doc"] !== "number" || !Number.isSafeInteger(value["doc"]) || value["doc"] < 0)) return false;
|
|
43
|
+
if (value["op"] === "mount") try {
|
|
44
|
+
applyDocumentOps({
|
|
45
|
+
mode: "mounted",
|
|
46
|
+
documents: /* @__PURE__ */ new Map()
|
|
47
|
+
}, [value], void 0, true);
|
|
48
|
+
return true;
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (value["op"] === "unmount") return true;
|
|
53
|
+
if (![
|
|
54
|
+
"add",
|
|
55
|
+
"replace",
|
|
56
|
+
"remove"
|
|
57
|
+
].includes(value["op"])) return false;
|
|
58
|
+
return Array.isArray(value["path"]) && value["path"].every((part) => typeof part === "string" || typeof part === "number" && Number.isSafeInteger(part) && part >= 0) && (value["op"] === "remove" || "value" in value);
|
|
59
|
+
};
|
|
60
|
+
const isContext = (value) => {
|
|
61
|
+
if (value === void 0) return true;
|
|
62
|
+
if (!isRecord(value) || !Array.isArray(value["ops"]) || !Array.isArray(value["pending"])) return false;
|
|
63
|
+
if (value["recover"] !== void 0 && value["recover"] !== "version-1") return false;
|
|
64
|
+
try {
|
|
65
|
+
const documents = applyDocumentOps({
|
|
66
|
+
mode: "mounted",
|
|
67
|
+
documents: /* @__PURE__ */ new Map()
|
|
68
|
+
}, value["ops"], void 0, true);
|
|
69
|
+
for (const entry of value["pending"]) {
|
|
70
|
+
if (!isRecord(entry) || !isPositiveSafeInteger(entry["seq"]) || !Array.isArray(entry["ops"])) return false;
|
|
71
|
+
if (!entry["ops"].every(isContextOperation)) return false;
|
|
72
|
+
}
|
|
73
|
+
return documents.mode === "mounted";
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const decode = (raw, legacyProtocol) => {
|
|
79
|
+
if (raw === null) return null;
|
|
80
|
+
const parsed = JSON.parse(raw);
|
|
81
|
+
if (!isSessionRecord(parsed)) throw new Error("statewire: invalid stored session record");
|
|
82
|
+
const { statewireStorageVersion: format, savedAt: _savedAt, ...value } = parsed;
|
|
83
|
+
if (!isSessionRecord(value)) throw new Error("statewire: invalid stored session record");
|
|
84
|
+
if (format !== void 0 && format !== 1) throw new Error("statewire: unsupported session storage version");
|
|
85
|
+
if (legacyProtocol === void 0 || format !== void 0) return value;
|
|
86
|
+
const produced = {
|
|
87
|
+
wire: { version: "2026-09-07" },
|
|
88
|
+
protocols: [legacyProtocol]
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
...value,
|
|
92
|
+
...value.context === void 0 && value.ctxNextSeq > 1 ? { context: {
|
|
93
|
+
ops: [{
|
|
94
|
+
op: "mount",
|
|
95
|
+
protocol: legacyProtocol.name,
|
|
96
|
+
main: true,
|
|
97
|
+
value: {}
|
|
98
|
+
}],
|
|
99
|
+
pending: [],
|
|
100
|
+
recover: "version-1"
|
|
101
|
+
} } : {},
|
|
102
|
+
commands: value.commands.map((command) => command.produced === void 0 ? {
|
|
103
|
+
...command,
|
|
104
|
+
protocol: command.protocol ?? legacyProtocol.name,
|
|
105
|
+
produced
|
|
106
|
+
} : command),
|
|
107
|
+
...value.failed === void 0 ? {} : { failed: value.failed.map((entry) => ({
|
|
108
|
+
...entry,
|
|
109
|
+
protocol: entry.protocol ?? legacyProtocol.name,
|
|
110
|
+
produced: entry.produced ?? produced
|
|
111
|
+
})) }
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
const active = /* @__PURE__ */ new WeakMap();
|
|
115
|
+
const sweep = async (options, currentKey, quota = false) => {
|
|
116
|
+
const { backend, retention, namespace = "" } = options;
|
|
117
|
+
if (!retention || !backend.getAllKeys) return;
|
|
118
|
+
const entries = [];
|
|
119
|
+
const candidate = (raw) => {
|
|
120
|
+
if (raw === null) return void 0;
|
|
121
|
+
let value;
|
|
122
|
+
try {
|
|
123
|
+
value = JSON.parse(raw);
|
|
124
|
+
} catch {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const format = isRecord(value) ? value["statewireStorageVersion"] : void 0;
|
|
128
|
+
if (format !== void 0 && format !== 1) return void 0;
|
|
129
|
+
if (!isSessionRecord(value) || value.commands.length || value.failed?.length || value.context?.pending.length) return void 0;
|
|
130
|
+
const savedAt = value["savedAt"] ?? value["updatedAt"];
|
|
131
|
+
return typeof savedAt === "number" && Number.isFinite(savedAt) ? savedAt : void 0;
|
|
132
|
+
};
|
|
133
|
+
for (const key of await backend.getAllKeys()) {
|
|
134
|
+
if (!key.startsWith(namespace) || key === currentKey || active.get(backend)?.has(key)) continue;
|
|
135
|
+
const savedAt = candidate(await backend.getItem(key));
|
|
136
|
+
if (savedAt !== void 0) entries.push({
|
|
137
|
+
key,
|
|
138
|
+
savedAt
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
entries.sort((a, b) => b.savedAt - a.savedAt);
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
let retained = 1;
|
|
144
|
+
const quotaStart = Math.floor(entries.length / 2);
|
|
145
|
+
for (const [index, entry] of entries.entries()) {
|
|
146
|
+
const expired = retention.maxAgeMs !== void 0 && now - entry.savedAt > retention.maxAgeMs;
|
|
147
|
+
const overflow = retention.maxEntries !== void 0 && retained >= retention.maxEntries;
|
|
148
|
+
if (expired || overflow || quota && index >= quotaStart) {
|
|
149
|
+
const remove = async () => {
|
|
150
|
+
if (active.get(backend)?.has(entry.key)) return;
|
|
151
|
+
if (candidate(await backend.getItem(entry.key)) !== entry.savedAt) return;
|
|
152
|
+
if (active.get(backend)?.has(entry.key)) return;
|
|
153
|
+
await backend.removeItem(entry.key);
|
|
154
|
+
};
|
|
155
|
+
if (backend.withUnused) await backend.withUnused(entry.key, remove);
|
|
156
|
+
else await remove();
|
|
157
|
+
} else retained++;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const isQuotaError = (error) => error instanceof Error && error.name === "QuotaExceededError";
|
|
161
|
+
const useKeyValueSessionStorage = (options) => {
|
|
162
|
+
const { backend, key, namespace = "", legacyProtocol: suppliedLegacyProtocol, migrate, retention: suppliedRetention, onError } = options;
|
|
163
|
+
const latestErrorHandler = useRef(onError);
|
|
164
|
+
latestErrorHandler.current = onError;
|
|
165
|
+
const reportError = useMemo(() => (error) => latestErrorHandler.current?.(error), []);
|
|
166
|
+
const legacyName = suppliedLegacyProtocol?.name;
|
|
167
|
+
const legacyVersion = suppliedLegacyProtocol?.version;
|
|
168
|
+
const legacyProtocol = useMemo(() => legacyName === void 0 ? void 0 : {
|
|
169
|
+
name: legacyName,
|
|
170
|
+
version: legacyVersion
|
|
171
|
+
}, [legacyName, legacyVersion]);
|
|
172
|
+
const retain = suppliedRetention !== void 0;
|
|
173
|
+
const maxAgeMs = suppliedRetention?.maxAgeMs;
|
|
174
|
+
const maxEntries = suppliedRetention?.maxEntries;
|
|
175
|
+
const retention = useMemo(() => retain ? {
|
|
176
|
+
...maxAgeMs === void 0 ? {} : { maxAgeMs },
|
|
177
|
+
...maxEntries === void 0 ? {} : { maxEntries }
|
|
178
|
+
} : void 0, [
|
|
179
|
+
retain,
|
|
180
|
+
maxAgeMs,
|
|
181
|
+
maxEntries
|
|
182
|
+
]);
|
|
183
|
+
const configured = useMemo(() => ({
|
|
184
|
+
backend,
|
|
185
|
+
key,
|
|
186
|
+
namespace,
|
|
187
|
+
...retention === void 0 ? {} : { retention }
|
|
188
|
+
}), [
|
|
189
|
+
backend,
|
|
190
|
+
key,
|
|
191
|
+
namespace,
|
|
192
|
+
retention
|
|
193
|
+
]);
|
|
194
|
+
const storageKey = namespace + key;
|
|
195
|
+
if (legacyProtocol !== void 0) negotiationHeaders({ applicationProtocols: [legacyProtocol] });
|
|
196
|
+
if (retention !== void 0) {
|
|
197
|
+
if (!namespace) throw new Error("statewire: session retention requires a namespace");
|
|
198
|
+
if (!backend.getAllKeys) throw new Error("statewire: session retention requires getAllKeys");
|
|
199
|
+
if (retention.maxAgeMs !== void 0 && (!Number.isFinite(retention.maxAgeMs) || retention.maxAgeMs < 0)) throw new Error("statewire: invalid session retention maxAgeMs");
|
|
200
|
+
if (retention.maxEntries !== void 0 && (!Number.isSafeInteger(retention.maxEntries) || retention.maxEntries < 1)) throw new Error("statewire: invalid session retention maxEntries");
|
|
201
|
+
}
|
|
202
|
+
const state_0 = useMemo(() => {
|
|
203
|
+
let raw;
|
|
204
|
+
try {
|
|
205
|
+
raw = backend.getItem(storageKey);
|
|
206
|
+
} catch (cause) {
|
|
207
|
+
const error_0 = wrapStorageError(cause);
|
|
208
|
+
reportError(error_0);
|
|
209
|
+
throw error_0;
|
|
210
|
+
}
|
|
211
|
+
const state = {
|
|
212
|
+
record: null,
|
|
213
|
+
ready: void 0,
|
|
214
|
+
queue: Promise.resolve(),
|
|
215
|
+
pending: 0,
|
|
216
|
+
failure: void 0,
|
|
217
|
+
asynchronous: typeof raw !== "string" && raw !== null,
|
|
218
|
+
hydrated: typeof raw === "string" || raw === null
|
|
219
|
+
};
|
|
220
|
+
if (state.asynchronous) {
|
|
221
|
+
state.ready = Promise.resolve(raw).then((value) => {
|
|
222
|
+
state.record = decode(value, legacyProtocol);
|
|
223
|
+
state.hydrated = true;
|
|
224
|
+
}).catch((cause_0) => {
|
|
225
|
+
const error_1 = wrapStorageError(cause_0);
|
|
226
|
+
if (state.failure !== error_1) reportError(error_1);
|
|
227
|
+
state.failure = error_1;
|
|
228
|
+
throw error_1;
|
|
229
|
+
});
|
|
230
|
+
state.ready.catch(() => {});
|
|
231
|
+
} else try {
|
|
232
|
+
state.record = decode(raw, legacyProtocol);
|
|
233
|
+
} catch (cause_1) {
|
|
234
|
+
const error_2 = wrapStorageError(cause_1);
|
|
235
|
+
reportError(error_2);
|
|
236
|
+
throw error_2;
|
|
237
|
+
}
|
|
238
|
+
return state;
|
|
239
|
+
}, [
|
|
240
|
+
backend,
|
|
241
|
+
storageKey,
|
|
242
|
+
legacyProtocol,
|
|
243
|
+
reportError
|
|
244
|
+
]);
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
const entries = active.get(backend) ?? /* @__PURE__ */ new Map();
|
|
247
|
+
active.set(backend, entries);
|
|
248
|
+
entries.set(storageKey, (entries.get(storageKey) ?? 0) + 1);
|
|
249
|
+
return () => {
|
|
250
|
+
const count = entries.get(storageKey) - 1;
|
|
251
|
+
if (count === 0) entries.delete(storageKey);
|
|
252
|
+
else entries.set(storageKey, count);
|
|
253
|
+
};
|
|
254
|
+
}, [backend, storageKey]);
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (retention !== void 0) sweep(configured, storageKey).catch((cause_2) => {
|
|
257
|
+
const error_3 = wrapStorageError(cause_2);
|
|
258
|
+
state_0.failure = error_3;
|
|
259
|
+
reportError(error_3);
|
|
260
|
+
});
|
|
261
|
+
}, [
|
|
262
|
+
state_0,
|
|
263
|
+
configured,
|
|
264
|
+
storageKey,
|
|
265
|
+
retention,
|
|
266
|
+
reportError
|
|
267
|
+
]);
|
|
268
|
+
return useMemo(() => ({
|
|
269
|
+
...state_0.ready === void 0 ? {} : { ready: state_0.ready },
|
|
270
|
+
load: () => {
|
|
271
|
+
if (state_0.failure !== void 0) throw state_0.failure;
|
|
272
|
+
if (!state_0.hydrated) throw new Error("statewire: await session storage readiness before loading");
|
|
273
|
+
if (state_0.asynchronous || state_0.pending > 0) return state_0.record;
|
|
274
|
+
let raw_0;
|
|
275
|
+
try {
|
|
276
|
+
raw_0 = backend.getItem(storageKey);
|
|
277
|
+
} catch (cause_3) {
|
|
278
|
+
const error_4 = wrapStorageError(cause_3);
|
|
279
|
+
reportError(error_4);
|
|
280
|
+
throw error_4;
|
|
281
|
+
}
|
|
282
|
+
if (typeof raw_0 !== "string" && raw_0 !== null) throw new Error("statewire: storage backend changed from synchronous to asynchronous");
|
|
283
|
+
try {
|
|
284
|
+
return decode(raw_0, legacyProtocol);
|
|
285
|
+
} catch (cause_4) {
|
|
286
|
+
const error_5 = wrapStorageError(cause_4);
|
|
287
|
+
reportError(error_5);
|
|
288
|
+
throw error_5;
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
save: (record) => {
|
|
292
|
+
if (state_0.failure !== void 0) throw state_0.failure;
|
|
293
|
+
if (!state_0.hydrated) throw new Error("statewire: await session storage readiness before saving");
|
|
294
|
+
if (!isSessionRecord(record)) throw new Error("statewire: invalid session record");
|
|
295
|
+
const encoded = JSON.stringify({
|
|
296
|
+
...record,
|
|
297
|
+
statewireStorageVersion: 1,
|
|
298
|
+
savedAt: Date.now()
|
|
299
|
+
});
|
|
300
|
+
state_0.record = record;
|
|
301
|
+
const recoverQuota = async (error_6) => {
|
|
302
|
+
if (!isQuotaError(error_6) || retention === void 0) throw error_6;
|
|
303
|
+
await sweep(configured, storageKey, true);
|
|
304
|
+
await backend.setItem(storageKey, encoded);
|
|
305
|
+
};
|
|
306
|
+
const write = async () => {
|
|
307
|
+
try {
|
|
308
|
+
await backend.setItem(storageKey, encoded);
|
|
309
|
+
} catch (error_7) {
|
|
310
|
+
await recoverQuota(error_7);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
if (!state_0.asynchronous && state_0.pending === 0) {
|
|
314
|
+
let result;
|
|
315
|
+
try {
|
|
316
|
+
result = backend.setItem(storageKey, encoded);
|
|
317
|
+
} catch (error_8) {
|
|
318
|
+
if (!isQuotaError(error_8) || retention === void 0) {
|
|
319
|
+
state_0.failure = wrapStorageError(error_8);
|
|
320
|
+
reportError(state_0.failure);
|
|
321
|
+
throw state_0.failure;
|
|
322
|
+
}
|
|
323
|
+
result = recoverQuota(error_8);
|
|
324
|
+
}
|
|
325
|
+
if (result === void 0) return;
|
|
326
|
+
state_0.pending++;
|
|
327
|
+
state_0.queue = result;
|
|
328
|
+
} else {
|
|
329
|
+
state_0.pending++;
|
|
330
|
+
state_0.queue = state_0.queue.then(() => state_0.ready).then(write);
|
|
331
|
+
}
|
|
332
|
+
state_0.queue = state_0.queue.catch((cause_5) => {
|
|
333
|
+
const error_9 = wrapStorageError(cause_5);
|
|
334
|
+
if (state_0.failure !== error_9) reportError(error_9);
|
|
335
|
+
state_0.failure = error_9;
|
|
336
|
+
throw error_9;
|
|
337
|
+
}).finally(() => {
|
|
338
|
+
state_0.pending--;
|
|
339
|
+
});
|
|
340
|
+
state_0.queue.catch(() => {});
|
|
341
|
+
},
|
|
342
|
+
flush: () => {
|
|
343
|
+
if (state_0.failure !== void 0) throw state_0.failure;
|
|
344
|
+
if (!state_0.asynchronous && state_0.pending === 0) return;
|
|
345
|
+
return (async () => {
|
|
346
|
+
await state_0.ready;
|
|
347
|
+
for (;;) {
|
|
348
|
+
const queue = state_0.queue;
|
|
349
|
+
await queue;
|
|
350
|
+
if (queue === state_0.queue) return;
|
|
351
|
+
}
|
|
352
|
+
})();
|
|
353
|
+
},
|
|
354
|
+
...migrate === void 0 ? {} : { migrate }
|
|
355
|
+
}), [
|
|
356
|
+
state_0,
|
|
357
|
+
backend,
|
|
358
|
+
storageKey,
|
|
359
|
+
legacyProtocol,
|
|
360
|
+
migrate,
|
|
361
|
+
retention,
|
|
362
|
+
configured,
|
|
363
|
+
reportError
|
|
364
|
+
]);
|
|
365
|
+
};
|
|
366
|
+
/** Persist sessions through a synchronous or asynchronous string storage backend. */
|
|
367
|
+
const KeyValueSessionStorage = resource(useKeyValueSessionStorage);
|
|
368
|
+
//#endregion
|
|
369
|
+
export { KeyValueSessionStorage };
|
|
370
|
+
|
|
371
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","names":["useEffect","useMemo","useRef","wrapStorageError","resource","StatewireClient","StatewireProtocol","negotiationHeaders","protocolToken","applyDocumentOps","isRecord","value","Record","Array","isArray","isOptionalString","undefined","isPositiveSafeInteger","Number","isSafeInteger","isSessionCommand","SessionCommand","isProtocol","isProducer","isSessionVerdict","SessionVerdict","isSendFailure","SendFailure","isFailedCommand","FailedCommand","isFinite","isSessionRecord","SessionRecord","length","isContext","every","test","wireVersion","Range","applicationProtocols","Offer","isContextOperation","mode","documents","Map","includes","part","entry","decode","raw","legacyProtocol","KeyValueSessionStorage","Options","parsed","JSON","parse","Error","statewireStorageVersion","format","savedAt","_savedAt","produced","wire","version","protocols","context","ctxNextSeq","ops","op","protocol","name","main","pending","recover","commands","map","command","failed","active","WeakMap","Backend","sweep","options","currentKey","quota","backend","retention","namespace","getAllKeys","entries","key","candidate","startsWith","get","has","getItem","push","sort","a","b","now","Date","retained","quotaStart","Math","floor","index","expired","maxAgeMs","overflow","maxEntries","remove","removeItem","withUnused","isQuotaError","error","useKeyValueSessionStorage","SessionStorage","suppliedLegacyProtocol","migrate","suppliedRetention","onError","latestErrorHandler","current","reportError","legacyName","legacyVersion","retain","configured","storageKey","state","Promise","cause","record","ready","queue","resolve","failure","asynchronous","hydrated","then","catch","set","count","delete","load","save","encoded","stringify","recoverQuota","setItem","write","result","finally","flush","action"],"sources":["../src/storage.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from \"react\";\nimport { wrapStorageError } from \"./client/storageError\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { StatewireClient } from \"./statewire\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport { negotiationHeaders, protocolToken } from \"./wire/protocol\";\nimport { applyDocumentOps } from \"./wire/documents\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isOptionalString = (value: unknown): boolean =>\n value === undefined || typeof value === \"string\";\n\nconst isPositiveSafeInteger = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isSafeInteger(value) && value > 0;\n\nconst isSessionCommand = (\n value: unknown,\n): value is StatewireClient.SessionCommand =>\n isRecord(value) &&\n isPositiveSafeInteger(value[\"seq\"]) &&\n isProtocol(value[\"protocol\"]) &&\n isProducer(value[\"produced\"]) &&\n typeof value[\"method\"] === \"string\" &&\n Array.isArray(value[\"params\"]);\n\nconst isSessionVerdict = (\n value: unknown,\n): value is StatewireClient.SessionVerdict =>\n isRecord(value) &&\n isPositiveSafeInteger(value[\"seq\"]) &&\n (value[\"type\"] === \"result\" ||\n value[\"type\"] === \"rejected\" ||\n value[\"type\"] === \"failed\" ||\n value[\"type\"] === \"crashed\" ||\n value[\"type\"] === \"expired\") &&\n isOptionalString(value[\"code\"]) &&\n isOptionalString(value[\"message\"]);\n\nconst isSendFailure = (\n value: unknown,\n): value is StatewireClient.SendFailure => {\n if (!isRecord(value)) return false;\n switch (value[\"fate\"]) {\n case \"rejected\":\n return (\n value[\"cause\"] === \"server\" &&\n isOptionalString(value[\"code\"]) &&\n isOptionalString(value[\"message\"])\n );\n case \"failed\":\n return value[\"cause\"] === \"server\" && isOptionalString(value[\"message\"]);\n case \"crashed\":\n case \"expired\":\n return value[\"cause\"] === \"server\";\n case \"aborted\":\n return (\n (value[\"cause\"] === \"unmount\" ||\n value[\"cause\"] === \"stream-gone\" ||\n value[\"cause\"] === \"session-lost\") &&\n (value[\"predecessorLost\"] === undefined ||\n value[\"predecessorLost\"] === true)\n );\n case \"lost\":\n return (\n value[\"cause\"] === \"unmount\" ||\n value[\"cause\"] === \"stream-gone\" ||\n value[\"cause\"] === \"session-lost\"\n );\n default:\n return false;\n }\n};\n\nconst isFailedCommand = (\n value: unknown,\n): value is StatewireClient.FailedCommand =>\n isRecord(value) &&\n isProtocol(value[\"protocol\"]) &&\n isProducer(value[\"produced\"]) &&\n typeof value[\"command\"] === \"string\" &&\n Array.isArray(value[\"params\"]) &&\n isSendFailure(value[\"failure\"]) &&\n typeof value[\"ts\"] === \"number\" &&\n Number.isFinite(value[\"ts\"]) &&\n isPositiveSafeInteger(value[\"seq\"]);\n\nconst isSessionRecord = (\n value: unknown,\n): value is StatewireClient.SessionRecord =>\n isRecord(value) &&\n typeof value[\"clientId\"] === \"string\" &&\n value[\"clientId\"].length > 0 &&\n isContext(value[\"context\"]) &&\n isPositiveSafeInteger(value[\"nextSeq\"]) &&\n isPositiveSafeInteger(value[\"ctxNextSeq\"]) &&\n Array.isArray(value[\"commands\"]) &&\n value[\"commands\"].every(isSessionCommand) &&\n (value[\"verdicts\"] === undefined ||\n (Array.isArray(value[\"verdicts\"]) &&\n value[\"verdicts\"].every(isSessionVerdict))) &&\n (value[\"failed\"] === undefined ||\n (Array.isArray(value[\"failed\"]) && value[\"failed\"].every(isFailedCommand)));\n\nconst isProtocol = (value: unknown) =>\n value === undefined ||\n (typeof value === \"string\" && value !== \"*\" && protocolToken.test(value));\n\nconst isProducer = (value: unknown) => {\n if (value === undefined) return true;\n if (!isRecord(value) || !isRecord(value[\"wire\"])) return false;\n try {\n negotiationHeaders({\n wireVersion: value[\"wire\"] as StatewireProtocol.Range,\n applicationProtocols: value[\"protocols\"] as\n | readonly StatewireProtocol.Offer[]\n | \"*\",\n });\n return value[\"protocols\"] !== undefined;\n } catch {\n return false;\n }\n};\n\nconst isContextOperation = (value: unknown) => {\n if (!isRecord(value)) return false;\n if (\n value[\"doc\"] !== undefined &&\n (typeof value[\"doc\"] !== \"number\" ||\n !Number.isSafeInteger(value[\"doc\"]) ||\n value[\"doc\"] < 0)\n )\n return false;\n if (value[\"op\"] === \"mount\") {\n try {\n applyDocumentOps(\n { mode: \"mounted\", documents: new Map() },\n [value],\n undefined,\n true,\n );\n return true;\n } catch {\n return false;\n }\n }\n if (value[\"op\"] === \"unmount\") return true;\n if (![\"add\", \"replace\", \"remove\"].includes(value[\"op\"] as string))\n return false;\n return (\n Array.isArray(value[\"path\"]) &&\n value[\"path\"].every(\n (part) =>\n typeof part === \"string\" ||\n (typeof part === \"number\" && Number.isSafeInteger(part) && part >= 0),\n ) &&\n (value[\"op\"] === \"remove\" || \"value\" in value)\n );\n};\n\nconst isContext = (value: unknown) => {\n if (value === undefined) return true;\n if (\n !isRecord(value) ||\n !Array.isArray(value[\"ops\"]) ||\n !Array.isArray(value[\"pending\"])\n )\n return false;\n if (value[\"recover\"] !== undefined && value[\"recover\"] !== \"version-1\")\n return false;\n try {\n const documents = applyDocumentOps(\n { mode: \"mounted\", documents: new Map() },\n value[\"ops\"],\n undefined,\n true,\n );\n for (const entry of value[\"pending\"]) {\n if (\n !isRecord(entry) ||\n !isPositiveSafeInteger(entry[\"seq\"]) ||\n !Array.isArray(entry[\"ops\"])\n )\n return false;\n if (!entry[\"ops\"].every(isContextOperation)) return false;\n }\n return documents.mode === \"mounted\";\n } catch {\n return false;\n }\n};\n\nconst decode = (\n raw: string | null,\n legacyProtocol: KeyValueSessionStorage.Options[\"legacyProtocol\"],\n): StatewireClient.SessionRecord | null => {\n if (raw === null) return null;\n const parsed: unknown = JSON.parse(raw);\n if (!isSessionRecord(parsed))\n throw new Error(\"statewire: invalid stored session record\");\n const {\n statewireStorageVersion: format,\n savedAt: _savedAt,\n ...value\n } = parsed as StatewireClient.SessionRecord & {\n statewireStorageVersion?: unknown;\n savedAt?: unknown;\n };\n if (!isSessionRecord(value))\n throw new Error(\"statewire: invalid stored session record\");\n if (format !== undefined && format !== 1)\n throw new Error(\"statewire: unsupported session storage version\");\n if (legacyProtocol === undefined || format !== undefined) return value;\n const produced = {\n wire: { version: \"2026-09-07\" },\n protocols: [legacyProtocol],\n };\n return {\n ...value,\n ...(value.context === undefined && value.ctxNextSeq > 1\n ? {\n context: {\n ops: [\n {\n op: \"mount\",\n protocol: legacyProtocol.name,\n main: true,\n value: {},\n },\n ],\n pending: [],\n recover: \"version-1\",\n },\n }\n : {}),\n commands: value.commands.map((command) =>\n command.produced === undefined\n ? {\n ...command,\n protocol: command.protocol ?? legacyProtocol.name,\n produced,\n }\n : command,\n ),\n ...(value.failed === undefined\n ? {}\n : {\n failed: value.failed.map((entry) => ({\n ...entry,\n protocol: entry.protocol ?? legacyProtocol.name,\n produced: entry.produced ?? produced,\n })),\n }),\n };\n};\n\nconst active = new WeakMap<\n KeyValueSessionStorage.Backend,\n Map<string, number>\n>();\n\nconst sweep = async (\n options: KeyValueSessionStorage.Options,\n currentKey: string,\n quota = false,\n) => {\n const { backend, retention, namespace = \"\" } = options;\n if (!retention || !backend.getAllKeys) return;\n const entries: { key: string; savedAt: number }[] = [];\n const candidate = (raw: string | null) => {\n if (raw === null) return undefined;\n let value: unknown;\n try {\n value = JSON.parse(raw);\n } catch {\n return undefined;\n }\n const format = isRecord(value)\n ? value[\"statewireStorageVersion\"]\n : undefined;\n if (format !== undefined && format !== 1) return undefined;\n if (\n !isSessionRecord(value) ||\n value.commands.length ||\n value.failed?.length ||\n value.context?.pending.length\n )\n return undefined;\n const savedAt =\n (value as Record<string, unknown>)[\"savedAt\"] ??\n (value as Record<string, unknown>)[\"updatedAt\"];\n return typeof savedAt === \"number\" && Number.isFinite(savedAt)\n ? savedAt\n : undefined;\n };\n for (const key of await backend.getAllKeys()) {\n if (\n !key.startsWith(namespace) ||\n key === currentKey ||\n active.get(backend)?.has(key)\n )\n continue;\n const savedAt = candidate(await backend.getItem(key));\n if (savedAt !== undefined) entries.push({ key, savedAt });\n }\n entries.sort((a, b) => b.savedAt - a.savedAt);\n const now = Date.now();\n let retained = 1;\n const quotaStart = Math.floor(entries.length / 2);\n for (const [index, entry] of entries.entries()) {\n const expired =\n retention.maxAgeMs !== undefined &&\n now - entry.savedAt > retention.maxAgeMs;\n const overflow =\n retention.maxEntries !== undefined && retained >= retention.maxEntries;\n if (expired || overflow || (quota && index >= quotaStart)) {\n const remove = async () => {\n if (active.get(backend)?.has(entry.key)) return;\n if (candidate(await backend.getItem(entry.key)) !== entry.savedAt)\n return;\n if (active.get(backend)?.has(entry.key)) return;\n await backend.removeItem(entry.key);\n };\n if (backend.withUnused) await backend.withUnused(entry.key, remove);\n else await remove();\n } else retained++;\n }\n};\n\nconst isQuotaError = (error: unknown) =>\n error instanceof Error && error.name === \"QuotaExceededError\";\n\nconst useKeyValueSessionStorage = (\n options: KeyValueSessionStorage.Options,\n): StatewireClient.SessionStorage => {\n const {\n backend,\n key,\n namespace = \"\",\n legacyProtocol: suppliedLegacyProtocol,\n migrate,\n retention: suppliedRetention,\n onError,\n } = options;\n const latestErrorHandler = useRef(onError);\n latestErrorHandler.current = onError;\n const reportError = useMemo(\n () => (error: unknown) => latestErrorHandler.current?.(error),\n [],\n );\n const legacyName = suppliedLegacyProtocol?.name;\n const legacyVersion = suppliedLegacyProtocol?.version;\n const legacyProtocol = useMemo(\n () =>\n legacyName === undefined\n ? undefined\n : { name: legacyName, version: legacyVersion! },\n [legacyName, legacyVersion],\n );\n const retain = suppliedRetention !== undefined;\n const maxAgeMs = suppliedRetention?.maxAgeMs;\n const maxEntries = suppliedRetention?.maxEntries;\n const retention = useMemo(\n () =>\n retain\n ? {\n ...(maxAgeMs === undefined ? {} : { maxAgeMs }),\n ...(maxEntries === undefined ? {} : { maxEntries }),\n }\n : undefined,\n [retain, maxAgeMs, maxEntries],\n );\n const configured = useMemo(\n () => ({\n backend,\n key,\n namespace,\n ...(retention === undefined ? {} : { retention }),\n }),\n [backend, key, namespace, retention],\n );\n const storageKey = namespace + key;\n if (legacyProtocol !== undefined)\n negotiationHeaders({ applicationProtocols: [legacyProtocol] });\n if (retention !== undefined) {\n if (!namespace)\n throw new Error(\"statewire: session retention requires a namespace\");\n if (!backend.getAllKeys)\n throw new Error(\"statewire: session retention requires getAllKeys\");\n if (\n retention.maxAgeMs !== undefined &&\n (!Number.isFinite(retention.maxAgeMs) || retention.maxAgeMs < 0)\n )\n throw new Error(\"statewire: invalid session retention maxAgeMs\");\n if (\n retention.maxEntries !== undefined &&\n (!Number.isSafeInteger(retention.maxEntries) || retention.maxEntries < 1)\n )\n throw new Error(\"statewire: invalid session retention maxEntries\");\n }\n const state = useMemo(() => {\n let raw: string | null | Promise<string | null>;\n try {\n raw = backend.getItem(storageKey);\n } catch (cause) {\n const error = wrapStorageError(cause);\n reportError(error);\n throw error;\n }\n const state = {\n record: null as StatewireClient.SessionRecord | null,\n ready: undefined as Promise<void> | undefined,\n queue: Promise.resolve(),\n pending: 0,\n failure: undefined as unknown,\n asynchronous: typeof raw !== \"string\" && raw !== null,\n hydrated: typeof raw === \"string\" || raw === null,\n };\n if (state.asynchronous) {\n state.ready = Promise.resolve(raw)\n .then((value) => {\n state.record = decode(value, legacyProtocol);\n state.hydrated = true;\n })\n .catch((cause: unknown) => {\n const error = wrapStorageError(cause);\n if (state.failure !== error) reportError(error);\n state.failure = error;\n throw error;\n });\n state.ready.catch(() => {});\n } else {\n try {\n state.record = decode(raw as string | null, legacyProtocol);\n } catch (cause) {\n const error = wrapStorageError(cause);\n reportError(error);\n throw error;\n }\n }\n return state;\n }, [backend, storageKey, legacyProtocol, reportError]);\n\n useEffect(() => {\n const entries = active.get(backend) ?? new Map<string, number>();\n active.set(backend, entries);\n entries.set(storageKey, (entries.get(storageKey) ?? 0) + 1);\n return () => {\n const count = entries.get(storageKey)! - 1;\n if (count === 0) entries.delete(storageKey);\n else entries.set(storageKey, count);\n };\n }, [backend, storageKey]);\n\n useEffect(() => {\n if (retention !== undefined)\n void sweep(configured, storageKey).catch((cause: unknown) => {\n const error = wrapStorageError(cause);\n state.failure = error;\n reportError(error);\n });\n }, [state, configured, storageKey, retention, reportError]);\n\n return useMemo(\n () => ({\n ...(state.ready === undefined ? {} : { ready: state.ready }),\n load: () => {\n if (state.failure !== undefined) throw state.failure;\n if (!state.hydrated)\n throw new Error(\n \"statewire: await session storage readiness before loading\",\n );\n if (state.asynchronous || state.pending > 0) return state.record;\n let raw: string | null | Promise<string | null>;\n try {\n raw = backend.getItem(storageKey);\n } catch (cause) {\n const error = wrapStorageError(cause);\n reportError(error);\n throw error;\n }\n if (typeof raw !== \"string\" && raw !== null)\n throw new Error(\n \"statewire: storage backend changed from synchronous to asynchronous\",\n );\n try {\n return decode(raw, legacyProtocol);\n } catch (cause) {\n const error = wrapStorageError(cause);\n reportError(error);\n throw error;\n }\n },\n save: (record: StatewireClient.SessionRecord) => {\n if (state.failure !== undefined) throw state.failure;\n if (!state.hydrated)\n throw new Error(\n \"statewire: await session storage readiness before saving\",\n );\n if (!isSessionRecord(record))\n throw new Error(\"statewire: invalid session record\");\n const encoded = JSON.stringify({\n ...record,\n statewireStorageVersion: 1,\n savedAt: Date.now(),\n });\n state.record = record;\n const recoverQuota = async (error: unknown) => {\n if (!isQuotaError(error) || retention === undefined) throw error;\n await sweep(configured, storageKey, true);\n await backend.setItem(storageKey, encoded);\n };\n const write = async () => {\n try {\n await backend.setItem(storageKey, encoded);\n } catch (error) {\n await recoverQuota(error);\n }\n };\n if (!state.asynchronous && state.pending === 0) {\n let result: void | Promise<void>;\n try {\n result = backend.setItem(storageKey, encoded);\n } catch (error) {\n if (!isQuotaError(error) || retention === undefined) {\n state.failure = wrapStorageError(error);\n reportError(state.failure);\n throw state.failure;\n }\n result = recoverQuota(error);\n }\n if (result === undefined) return;\n state.pending++;\n state.queue = result;\n } else {\n state.pending++;\n state.queue = state.queue.then(() => state.ready).then(write);\n }\n state.queue = state.queue\n .catch((cause: unknown) => {\n const error = wrapStorageError(cause);\n if (state.failure !== error) reportError(error);\n state.failure = error;\n throw error;\n })\n .finally(() => {\n state.pending--;\n });\n state.queue.catch(() => {});\n },\n flush: () => {\n if (state.failure !== undefined) throw state.failure;\n if (!state.asynchronous && state.pending === 0) return;\n return (async () => {\n await state.ready;\n for (;;) {\n const queue = state.queue;\n await queue;\n if (queue === state.queue) return;\n }\n })();\n },\n ...(migrate === undefined ? {} : { migrate }),\n }),\n [\n state,\n backend,\n storageKey,\n legacyProtocol,\n migrate,\n retention,\n configured,\n reportError,\n ],\n );\n};\n\n/** Persist sessions through a synchronous or asynchronous string storage backend. */\nexport const KeyValueSessionStorage = resource(useKeyValueSessionStorage);\n\nexport namespace KeyValueSessionStorage {\n export type Backend = {\n getItem: (key: string) => string | null | Promise<string | null>;\n setItem: (key: string, value: string) => void | Promise<void>;\n removeItem: (key: string) => void | Promise<void>;\n getAllKeys?: () => readonly string[] | Promise<readonly string[]>;\n /** Runs cleanup under exclusive access, skipping keys in use by another runtime. */\n withUnused?: (key: string, action: () => Promise<void>) => Promise<void>;\n };\n export type Options = {\n backend: Backend;\n key: string;\n namespace?: string;\n /** The application format used by sessions saved before version negotiation. */\n legacyProtocol?: { name: string; version: string };\n migrate?: StatewireClient.SessionStorage[\"migrate\"];\n retention?: { maxAgeMs?: number; maxEntries?: number };\n onError?: (error: unknown) => void;\n };\n}\n"],"mappings":";;;;;;AAQA,MAAMU,YAAYC,UAChB,OAAOA,UAAU,YAAYA,UAAU,QAAQ,CAACE,MAAMC,QAAQH,KAAK;AAErE,MAAMI,oBAAoBJ,UACxBA,UAAUK,KAAAA,KAAa,OAAOL,UAAU;AAE1C,MAAMM,yBAAyBN,UAC7B,OAAOA,UAAU,YAAYO,OAAOC,cAAcR,KAAK,KAAKA,QAAQ;AAEtE,MAAMS,oBACJT,UAEAD,SAASC,KAAK,KACdM,sBAAsBN,MAAM,MAAM,KAClCW,WAAWX,MAAM,WAAW,KAC5BY,WAAWZ,MAAM,WAAW,KAC5B,OAAOA,MAAM,cAAc,YAC3BE,MAAMC,QAAQH,MAAM,SAAS;AAE/B,MAAMa,oBACJb,UAEAD,SAASC,KAAK,KACdM,sBAAsBN,MAAM,MAAM,MACjCA,MAAM,YAAY,YACjBA,MAAM,YAAY,cAClBA,MAAM,YAAY,YAClBA,MAAM,YAAY,aAClBA,MAAM,YAAY,cACpBI,iBAAiBJ,MAAM,OAAO,KAC9BI,iBAAiBJ,MAAM,UAAU;AAEnC,MAAMe,iBACJf,UACyC;CACzC,IAAI,CAACD,SAASC,KAAK,GAAG,OAAO;CAC7B,QAAQA,MAAM,SAAd;EACE,KAAK,YACH,OACEA,MAAM,aAAa,YACnBI,iBAAiBJ,MAAM,OAAO,KAC9BI,iBAAiBJ,MAAM,UAAU;EAErC,KAAK,UACH,OAAOA,MAAM,aAAa,YAAYI,iBAAiBJ,MAAM,UAAU;EACzE,KAAK;EACL,KAAK,WACH,OAAOA,MAAM,aAAa;EAC5B,KAAK,WACH,QACGA,MAAM,aAAa,aAClBA,MAAM,aAAa,iBACnBA,MAAM,aAAa,oBACpBA,MAAM,uBAAuBK,KAAAA,KAC5BL,MAAM,uBAAuB;EAEnC,KAAK,QACH,OACEA,MAAM,aAAa,aACnBA,MAAM,aAAa,iBACnBA,MAAM,aAAa;EAEvB,SACE,OAAO;CACX;AACF;AAEA,MAAMiB,mBACJjB,UAEAD,SAASC,KAAK,KACdW,WAAWX,MAAM,WAAW,KAC5BY,WAAWZ,MAAM,WAAW,KAC5B,OAAOA,MAAM,eAAe,YAC5BE,MAAMC,QAAQH,MAAM,SAAS,KAC7Be,cAAcf,MAAM,UAAU,KAC9B,OAAOA,MAAM,UAAU,YACvBO,OAAOY,SAASnB,MAAM,KAAK,KAC3BM,sBAAsBN,MAAM,MAAM;AAEpC,MAAMoB,mBACJpB,UAEAD,SAASC,KAAK,KACd,OAAOA,MAAM,gBAAgB,YAC7BA,MAAM,WAAW,CAACsB,SAAS,KAC3BC,UAAUvB,MAAM,UAAU,KAC1BM,sBAAsBN,MAAM,UAAU,KACtCM,sBAAsBN,MAAM,aAAa,KACzCE,MAAMC,QAAQH,MAAM,WAAW,KAC/BA,MAAM,WAAW,CAACwB,MAAMf,gBAAgB,MACvCT,MAAM,gBAAgBK,KAAAA,KACpBH,MAAMC,QAAQH,MAAM,WAAW,KAC9BA,MAAM,WAAW,CAACwB,MAAMX,gBAAgB,OAC3Cb,MAAM,cAAcK,KAAAA,KAClBH,MAAMC,QAAQH,MAAM,SAAS,KAAKA,MAAM,SAAS,CAACwB,MAAMP,eAAe;AAE5E,MAAMN,cAAcX,UAClBA,UAAUK,KAAAA,KACT,OAAOL,UAAU,YAAYA,UAAU,OAAOH,cAAc4B,KAAKzB,KAAK;AAEzE,MAAMY,cAAcZ,UAAmB;CACrC,IAAIA,UAAUK,KAAAA,GAAW,OAAO;CAChC,IAAI,CAACN,SAASC,KAAK,KAAK,CAACD,SAASC,MAAM,OAAO,GAAG,OAAO;CACzD,IAAI;EACFJ,mBAAmB;GACjB8B,aAAa1B,MAAM;GACnB4B,sBAAsB5B,MAAM;EAG9B,CAAC;EACD,OAAOA,MAAM,iBAAiBK,KAAAA;CAChC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAMyB,sBAAsB9B,UAAmB;CAC7C,IAAI,CAACD,SAASC,KAAK,GAAG,OAAO;CAC7B,IACEA,MAAM,WAAWK,KAAAA,MAChB,OAAOL,MAAM,WAAW,YACvB,CAACO,OAAOC,cAAcR,MAAM,MAAM,KAClCA,MAAM,SAAS,IAEjB,OAAO;CACT,IAAIA,MAAM,UAAU,SAClB,IAAI;EACFF,iBACE;GAAEiC,MAAM;GAAWC,2BAAW,IAAIC,IAAI;EAAE,GACxC,CAACjC,KAAK,GACNK,KAAAA,GACA,IACF;EACA,OAAO;CACT,QAAQ;EACN,OAAO;CACT;CAEF,IAAIL,MAAM,UAAU,WAAW,OAAO;CACtC,IAAI,CAAC;EAAC;EAAO;EAAW;CAAQ,CAAC,CAACkC,SAASlC,MAAM,KAAe,GAC9D,OAAO;CACT,OACEE,MAAMC,QAAQH,MAAM,OAAO,KAC3BA,MAAM,OAAO,CAACwB,OACXW,SACC,OAAOA,SAAS,YACf,OAAOA,SAAS,YAAY5B,OAAOC,cAAc2B,IAAI,KAAKA,QAAQ,CACvE,MACCnC,MAAM,UAAU,YAAY,WAAWA;AAE5C;AAEA,MAAMuB,aAAavB,UAAmB;CACpC,IAAIA,UAAUK,KAAAA,GAAW,OAAO;CAChC,IACE,CAACN,SAASC,KAAK,KACf,CAACE,MAAMC,QAAQH,MAAM,MAAM,KAC3B,CAACE,MAAMC,QAAQH,MAAM,UAAU,GAE/B,OAAO;CACT,IAAIA,MAAM,eAAeK,KAAAA,KAAaL,MAAM,eAAe,aACzD,OAAO;CACT,IAAI;EACF,MAAMgC,YAAYlC,iBAChB;GAAEiC,MAAM;GAAWC,2BAAW,IAAIC,IAAI;EAAE,GACxCjC,MAAM,QACNK,KAAAA,GACA,IACF;EACA,KAAK,MAAM+B,SAASpC,MAAM,YAAY;GACpC,IACE,CAACD,SAASqC,KAAK,KACf,CAAC9B,sBAAsB8B,MAAM,MAAM,KACnC,CAAClC,MAAMC,QAAQiC,MAAM,MAAM,GAE3B,OAAO;GACT,IAAI,CAACA,MAAM,MAAM,CAACZ,MAAMM,kBAAkB,GAAG,OAAO;EACtD;EACA,OAAOE,UAAUD,SAAS;CAC5B,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAMM,UACJC,KACAC,mBACyC;CACzC,IAAID,QAAQ,MAAM,OAAO;CACzB,MAAMI,SAAkBC,KAAKC,MAAMN,GAAG;CACtC,IAAI,CAAClB,gBAAgBsB,MAAM,GACzB,MAAM,IAAIG,MAAM,0CAA0C;CAC5D,MAAM,EACJC,yBAAyBC,QACzBC,SAASC,UACT,GAAGjD,UACD0C;CAIJ,IAAI,CAACtB,gBAAgBpB,KAAK,GACxB,MAAM,IAAI6C,MAAM,0CAA0C;CAC5D,IAAIE,WAAW1C,KAAAA,KAAa0C,WAAW,GACrC,MAAM,IAAIF,MAAM,gDAAgD;CAClE,IAAIN,mBAAmBlC,KAAAA,KAAa0C,WAAW1C,KAAAA,GAAW,OAAOL;CACjE,MAAMkD,WAAW;EACfC,MAAM,EAAEC,SAAS,aAAa;EAC9BC,WAAW,CAACd,cAAc;CAC5B;CACA,OAAO;EACL,GAAGvC;EACH,GAAIA,MAAMsD,YAAYjD,KAAAA,KAAaL,MAAMuD,aAAa,IAClD,EACED,SAAS;GACPE,KAAK,CACH;IACEC,IAAI;IACJC,UAAUnB,eAAeoB;IACzBC,MAAM;IACN5D,OAAO,CAAC;GACV,CAAC;GAEH6D,SAAS,CAAA;GACTC,SAAS;EACX,EACF,IACA,CAAC;EACLC,UAAU/D,MAAM+D,SAASC,KAAKC,YAC5BA,QAAQf,aAAa7C,KAAAA,IACjB;GACE,GAAG4D;GACHP,UAAUO,QAAQP,YAAYnB,eAAeoB;GAC7CT;EACF,IACAe,OACN;EACA,GAAIjE,MAAMkE,WAAW7D,KAAAA,IACjB,CAAC,IACD,EACE6D,QAAQlE,MAAMkE,OAAOF,KAAK5B,WAAW;GACnC,GAAGA;GACHsB,UAAUtB,MAAMsB,YAAYnB,eAAeoB;GAC3CT,UAAUd,MAAMc,YAAYA;EAC9B,EAAE,EACJ;CACN;AACF;AAEA,MAAMiB,yBAAS,IAAIC,QAGjB;AAEF,MAAME,QAAQ,OACZC,SACAC,YACAC,QAAQ,UACL;CACH,MAAM,EAAEC,SAASC,WAAWC,YAAY,OAAOL;CAC/C,IAAI,CAACI,aAAa,CAACD,QAAQG,YAAY;CACvC,MAAMC,UAA8C,CAAA;CACpD,MAAME,aAAa1C,QAAuB;EACxC,IAAIA,QAAQ,MAAM,OAAOjC,KAAAA;EACzB,IAAIL;EACJ,IAAI;GACFA,QAAQ2C,KAAKC,MAAMN,GAAG;EACxB,QAAQ;GACN;EACF;EACA,MAAMS,SAAShD,SAASC,KAAK,IACzBA,MAAM,6BACNK,KAAAA;EACJ,IAAI0C,WAAW1C,KAAAA,KAAa0C,WAAW,GAAG,OAAO1C,KAAAA;EACjD,IACE,CAACe,gBAAgBpB,KAAK,KACtBA,MAAM+D,SAASzC,UACftB,MAAMkE,QAAQ5C,UACdtB,MAAMsD,SAASO,QAAQvC,QAEvB,OAAOjB,KAAAA;EACT,MAAM2C,UACHhD,MAAkC,cAClCA,MAAkC;EACrC,OAAO,OAAOgD,YAAY,YAAYzC,OAAOY,SAAS6B,OAAO,IACzDA,UACA3C,KAAAA;CACN;CACA,KAAK,MAAM0E,OAAO,MAAML,QAAQG,WAAW,GAAG;EAC5C,IACE,CAACE,IAAIE,WAAWL,SAAS,KACzBG,QAAQP,cACRL,OAAOe,IAAIR,OAAO,CAAC,EAAES,IAAIJ,GAAG,GAE5B;EACF,MAAM/B,UAAUgC,UAAU,MAAMN,QAAQU,QAAQL,GAAG,CAAC;EACpD,IAAI/B,YAAY3C,KAAAA,GAAWyE,QAAQO,KAAK;GAAEN;GAAK/B;EAAQ,CAAC;CAC1D;CACA8B,QAAQQ,MAAMC,GAAGC,MAAMA,EAAExC,UAAUuC,EAAEvC,OAAO;CAC5C,MAAMyC,MAAMC,KAAKD,IAAI;CACrB,IAAIE,WAAW;CACf,MAAMC,aAAaC,KAAKC,MAAMhB,QAAQxD,SAAS,CAAC;CAChD,KAAK,MAAM,CAACyE,OAAO3D,UAAU0C,QAAQA,QAAQ,GAAG;EAC9C,MAAMkB,UACJrB,UAAUsB,aAAa5F,KAAAA,KACvBoF,MAAMrD,MAAMY,UAAU2B,UAAUsB;EAClC,MAAMC,WACJvB,UAAUwB,eAAe9F,KAAAA,KAAasF,YAAYhB,UAAUwB;EAC9D,IAAIH,WAAWE,YAAazB,SAASsB,SAASH,YAAa;GACzD,MAAMQ,SAAS,YAAY;IACzB,IAAIjC,OAAOe,IAAIR,OAAO,CAAC,EAAES,IAAI/C,MAAM2C,GAAG,GAAG;IACzC,IAAIC,UAAU,MAAMN,QAAQU,QAAQhD,MAAM2C,GAAG,CAAC,MAAM3C,MAAMY,SACxD;IACF,IAAImB,OAAOe,IAAIR,OAAO,CAAC,EAAES,IAAI/C,MAAM2C,GAAG,GAAG;IACzC,MAAML,QAAQ2B,WAAWjE,MAAM2C,GAAG;GACpC;GACA,IAAIL,QAAQ4B,YAAY,MAAM5B,QAAQ4B,WAAWlE,MAAM2C,KAAKqB,MAAM;QAC7D,MAAMA,OAAO;EACpB,OAAOT;CACT;AACF;AAEA,MAAMY,gBAAgBC,UACpBA,iBAAiB3D,SAAS2D,MAAM7C,SAAS;AAE3C,MAAM8C,6BACJlC,YACmC;CACnC,MAAM,EACJG,SACAK,KACAH,YAAY,IACZrC,gBAAgBoE,wBAChBC,SACAjC,WAAWkC,mBACXC,YACEvC;CACJ,MAAMwC,qBAAqBxH,OAAOuH,OAAO;CACzCC,mBAAmBC,UAAUF;CAC7B,MAAMG,cAAc3H,eACXkH,UAAmBO,mBAAmBC,UAAUR,KAAK,GAC5D,CAAA,CACF;CACA,MAAMU,aAAaP,wBAAwBhD;CAC3C,MAAMwD,gBAAgBR,wBAAwBvD;CAC9C,MAAMb,iBAAiBjD,cAEnB4H,eAAe7G,KAAAA,IACXA,KAAAA,IACA;EAAEsD,MAAMuD;EAAY9D,SAAS+D;CAAe,GAClD,CAACD,YAAYC,aAAa,CAC5B;CACA,MAAMC,SAASP,sBAAsBxG,KAAAA;CACrC,MAAM4F,WAAWY,mBAAmBZ;CACpC,MAAME,aAAaU,mBAAmBV;CACtC,MAAMxB,YAAYrF,cAEd8H,SACI;EACE,GAAInB,aAAa5F,KAAAA,IAAY,CAAC,IAAI,EAAE4F,SAAS;EAC7C,GAAIE,eAAe9F,KAAAA,IAAY,CAAC,IAAI,EAAE8F,WAAW;CACnD,IACA9F,KAAAA,GACN;EAAC+G;EAAQnB;EAAUE;CAAU,CAC/B;CACA,MAAMkB,aAAa/H,eACV;EACLoF;EACAK;EACAH;EACA,GAAID,cAActE,KAAAA,IAAY,CAAC,IAAI,EAAEsE,UAAU;CACjD,IACA;EAACD;EAASK;EAAKH;EAAWD;CAAS,CACrC;CACA,MAAM2C,aAAa1C,YAAYG;CAC/B,IAAIxC,mBAAmBlC,KAAAA,GACrBT,mBAAmB,EAAEgC,sBAAsB,CAACW,cAAc,EAAE,CAAC;CAC/D,IAAIoC,cAActE,KAAAA,GAAW;EAC3B,IAAI,CAACuE,WACH,MAAM,IAAI/B,MAAM,mDAAmD;EACrE,IAAI,CAAC6B,QAAQG,YACX,MAAM,IAAIhC,MAAM,kDAAkD;EACpE,IACE8B,UAAUsB,aAAa5F,KAAAA,MACtB,CAACE,OAAOY,SAASwD,UAAUsB,QAAQ,KAAKtB,UAAUsB,WAAW,IAE9D,MAAM,IAAIpD,MAAM,+CAA+C;EACjE,IACE8B,UAAUwB,eAAe9F,KAAAA,MACxB,CAACE,OAAOC,cAAcmE,UAAUwB,UAAU,KAAKxB,UAAUwB,aAAa,IAEvE,MAAM,IAAItD,MAAM,iDAAiD;CACrE;CACA,MAAM0E,UAAQjI,cAAc;EAC1B,IAAIgD;EACJ,IAAI;GACFA,MAAMoC,QAAQU,QAAQkC,UAAU;EAClC,SAASG,OAAO;GACd,MAAMjB,UAAQhH,iBAAiBiI,KAAK;GACpCR,YAAYT,OAAK;GACjB,MAAMA;EACR;EACA,MAAMe,QAAQ;GACZG,QAAQ;GACRC,OAAOtH,KAAAA;GACPuH,OAAOJ,QAAQK,QAAQ;GACvBhE,SAAS;GACTiE,SAASzH,KAAAA;GACT0H,cAAc,OAAOzF,QAAQ,YAAYA,QAAQ;GACjD0F,UAAU,OAAO1F,QAAQ,YAAYA,QAAQ;EAC/C;EACA,IAAIiF,MAAMQ,cAAc;GACtBR,MAAMI,QAAQH,QAAQK,QAAQvF,GAAG,CAAC,CAC/B2F,MAAMjI,UAAU;IACfuH,MAAMG,SAASrF,OAAOrC,OAAOuC,cAAc;IAC3CgF,MAAMS,WAAW;GACnB,CAAC,CAAC,CACDE,OAAOT,YAAmB;IACzB,MAAMjB,UAAQhH,iBAAiBiI,OAAK;IACpC,IAAIF,MAAMO,YAAYtB,SAAOS,YAAYT,OAAK;IAC9Ce,MAAMO,UAAUtB;IAChB,MAAMA;GACR,CAAC;GACHe,MAAMI,MAAMO,YAAY,CAAC,CAAC;EAC5B,OACE,IAAI;GACFX,MAAMG,SAASrF,OAAOC,KAAsBC,cAAc;EAC5D,SAASkF,SAAO;GACd,MAAMjB,UAAQhH,iBAAiBiI,OAAK;GACpCR,YAAYT,OAAK;GACjB,MAAMA;EACR;EAEF,OAAOe;CACT,GAAG;EAAC7C;EAAS4C;EAAY/E;EAAgB0E;CAAW,CAAC;CAErD5H,gBAAgB;EACd,MAAMyF,UAAUX,OAAOe,IAAIR,OAAO,qBAAK,IAAIzC,IAAoB;EAC/DkC,OAAOgE,IAAIzD,SAASI,OAAO;EAC3BA,QAAQqD,IAAIb,aAAaxC,QAAQI,IAAIoC,UAAU,KAAK,KAAK,CAAC;EAC1D,aAAa;GACX,MAAMc,QAAQtD,QAAQI,IAAIoC,UAAU,IAAK;GACzC,IAAIc,UAAU,GAAGtD,QAAQuD,OAAOf,UAAU;QACrCxC,QAAQqD,IAAIb,YAAYc,KAAK;EACpC;CACF,GAAG,CAAC1D,SAAS4C,UAAU,CAAC;CAExBjI,gBAAgB;EACd,IAAIsF,cAActE,KAAAA,GAChB,MAAWgH,YAAYC,UAAU,CAAC,CAACY,OAAOT,YAAmB;GAC3D,MAAMjB,UAAQhH,iBAAiBiI,OAAK;GACpCF,QAAMO,UAAUtB;GAChBS,YAAYT,OAAK;EACnB,CAAC;CACL,GAAG;EAACe;EAAOF;EAAYC;EAAY3C;EAAWsC;CAAW,CAAC;CAE1D,OAAO3H,eACE;EACL,GAAIiI,QAAMI,UAAUtH,KAAAA,IAAY,CAAC,IAAI,EAAEsH,OAAOJ,QAAMI,MAAM;EAC1DW,YAAY;GACV,IAAIf,QAAMO,YAAYzH,KAAAA,GAAW,MAAMkH,QAAMO;GAC7C,IAAI,CAACP,QAAMS,UACT,MAAM,IAAInF,MACR,2DACF;GACF,IAAI0E,QAAMQ,gBAAgBR,QAAM1D,UAAU,GAAG,OAAO0D,QAAMG;GAC1D,IAAIpF;GACJ,IAAI;IACFA,QAAMoC,QAAQU,QAAQkC,UAAU;GAClC,SAASG,SAAO;IACd,MAAMjB,UAAQhH,iBAAiBiI,OAAK;IACpCR,YAAYT,OAAK;IACjB,MAAMA;GACR;GACA,IAAI,OAAOlE,UAAQ,YAAYA,UAAQ,MACrC,MAAM,IAAIO,MACR,qEACF;GACF,IAAI;IACF,OAAOR,OAAOC,OAAKC,cAAc;GACnC,SAASkF,SAAO;IACd,MAAMjB,UAAQhH,iBAAiBiI,OAAK;IACpCR,YAAYT,OAAK;IACjB,MAAMA;GACR;EACF;EACA+B,OAAOb,WAA0C;GAC/C,IAAIH,QAAMO,YAAYzH,KAAAA,GAAW,MAAMkH,QAAMO;GAC7C,IAAI,CAACP,QAAMS,UACT,MAAM,IAAInF,MACR,0DACF;GACF,IAAI,CAACzB,gBAAgBsG,MAAM,GACzB,MAAM,IAAI7E,MAAM,mCAAmC;GACrD,MAAM2F,UAAU7F,KAAK8F,UAAU;IAC7B,GAAGf;IACH5E,yBAAyB;IACzBE,SAAS0C,KAAKD,IAAI;GACpB,CAAC;GACD8B,QAAMG,SAASA;GACf,MAAMgB,eAAe,OAAOlC,YAAmB;IAC7C,IAAI,CAACD,aAAaC,OAAK,KAAK7B,cAActE,KAAAA,GAAW,MAAMmG;IAC3D,MAAMlC,MAAM+C,YAAYC,YAAY,IAAI;IACxC,MAAM5C,QAAQiE,QAAQrB,YAAYkB,OAAO;GAC3C;GACA,MAAMI,QAAQ,YAAY;IACxB,IAAI;KACF,MAAMlE,QAAQiE,QAAQrB,YAAYkB,OAAO;IAC3C,SAAShC,SAAO;KACd,MAAMkC,aAAalC,OAAK;IAC1B;GACF;GACA,IAAI,CAACe,QAAMQ,gBAAgBR,QAAM1D,YAAY,GAAG;IAC9C,IAAIgF;IACJ,IAAI;KACFA,SAASnE,QAAQiE,QAAQrB,YAAYkB,OAAO;IAC9C,SAAShC,SAAO;KACd,IAAI,CAACD,aAAaC,OAAK,KAAK7B,cAActE,KAAAA,GAAW;MACnDkH,QAAMO,UAAUtI,iBAAiBgH,OAAK;MACtCS,YAAYM,QAAMO,OAAO;MACzB,MAAMP,QAAMO;KACd;KACAe,SAASH,aAAalC,OAAK;IAC7B;IACA,IAAIqC,WAAWxI,KAAAA,GAAW;IAC1BkH,QAAM1D;IACN0D,QAAMK,QAAQiB;GAChB,OAAO;IACLtB,QAAM1D;IACN0D,QAAMK,QAAQL,QAAMK,MAAMK,WAAWV,QAAMI,KAAK,CAAC,CAACM,KAAKW,KAAK;GAC9D;GACArB,QAAMK,QAAQL,QAAMK,MACjBM,OAAOT,YAAmB;IACzB,MAAMjB,UAAQhH,iBAAiBiI,OAAK;IACpC,IAAIF,QAAMO,YAAYtB,SAAOS,YAAYT,OAAK;IAC9Ce,QAAMO,UAAUtB;IAChB,MAAMA;GACR,CAAC,CAAC,CACDsC,cAAc;IACbvB,QAAM1D;GACR,CAAC;GACH0D,QAAMK,MAAMM,YAAY,CAAC,CAAC;EAC5B;EACAa,aAAa;GACX,IAAIxB,QAAMO,YAAYzH,KAAAA,GAAW,MAAMkH,QAAMO;GAC7C,IAAI,CAACP,QAAMQ,gBAAgBR,QAAM1D,YAAY,GAAG;GAChD,QAAQ,YAAY;IAClB,MAAM0D,QAAMI;IACZ,SAAS;KACP,MAAMC,QAAQL,QAAMK;KACpB,MAAMA;KACN,IAAIA,UAAUL,QAAMK,OAAO;IAC7B;GACF,EAAA,CAAG;EACL;EACA,GAAIhB,YAAYvG,KAAAA,IAAY,CAAC,IAAI,EAAEuG,QAAQ;CAC7C,IACA;EACEW;EACA7C;EACA4C;EACA/E;EACAqE;EACAjC;EACA0C;EACAJ;CAAW,CAEf;AACF;;AAGA,MAAazE,yBAAyB/C,SAASgH,yBAAyB"}
|
package/dist/transports/sse.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ type StatewireFrame = {
|
|
|
18
18
|
}[];
|
|
19
19
|
};
|
|
20
20
|
type StatewireRemoteProps = {
|
|
21
|
+
beforeAttach?: () => void | Promise<void>;
|
|
21
22
|
clientId: string;
|
|
22
23
|
/** Lanes to ride the attach frame (no-lease supersession only); records the included seqs. */
|
|
23
24
|
takeAttachLanes: () => StatewireFrame | null;
|
|
@@ -48,6 +49,7 @@ type StatewireSubmitResult = {
|
|
|
48
49
|
type ConnectionState = StatewireClient.ConnectionState;
|
|
49
50
|
/** The live wire a remote session drives: connection status plus frame submission. */
|
|
50
51
|
type RemoteLink = {
|
|
52
|
+
storageFailed: (error: Error) => void;
|
|
51
53
|
connection: StatewireClient.Connection;
|
|
52
54
|
/** A deferred first attach still waiting; `reconnect()` releases it. */
|
|
53
55
|
parked: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;;;KA0BY;EACV;IAAQ;IAAgB;;EACxB;IAAQ;IAAgB;IAAmB;IAAgB;;;KAGjD;EACV;;EAEA,uBAAuB;EACvB,UAAU;IAAQ;IAAgB,KAAK;;EACvC,QAAQ;IAAQ;IAAgB,MAAM;;EACtC,SAAS,MAAM;EACf,YAAY,KAAK;EACjB,SAAS;EACT;EACA,WAAW,MAAM,cAAc;;KAGrB;EACN;;EACA;EAAiB,KAAK;;EACtB;;EACA;;KAEM,kBAAkB,gBAAgB;;KAGlC;EACV,YAAY,gBAAgB;;EAE5B;EACA,OAAO,OAAO,mBAAmB,QAAQ;;
|
|
1
|
+
{"version":3,"file":"sse.d.ts","names":[],"sources":["../../src/transports/sse.ts"],"mappings":";;;;;;;KA0BY;EACV;IAAQ;IAAgB;;EACxB;IAAQ;IAAgB;IAAmB;IAAgB;;;KAGjD;EACV,4BAA4B;EAC5B;;EAEA,uBAAuB;EACvB,UAAU;IAAQ;IAAgB,KAAK;;EACvC,QAAQ;IAAQ;IAAgB,MAAM;;EACtC,SAAS,MAAM;EACf,YAAY,KAAK;EACjB,SAAS;EACT;EACA,WAAW,MAAM,cAAc;;KAGrB;EACN;;EACA;EAAiB,KAAK;;EACtB;;EACA;;KAEM,kBAAkB,gBAAgB;;KAGlC;EACV,gBAAgB,OAAO;EACvB,YAAY,gBAAgB;;EAE5B;EACA,OAAO,OAAO,mBAAmB,QAAQ;;cAoP9B,gBACX,cACA,kBAAkB,UAAU,WAAW,UAAU,UAEjD,SAAS,gBAAgB,gBACtB,gBAAgB,UAAU,oBAAoB"}
|