statewire 0.24.3 → 0.25.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.
@@ -2,7 +2,7 @@ import { KeyValueSessionStorage } from "./storage.js";
2
2
  import type { StatewireClient } from "./statewire.js";
3
3
  /** A key-value backend over one IndexedDB database; writes broadcast to other runtimes. */
4
4
  export declare const indexedDbBackend: (database: string) => KeyValueSessionStorage.Backend;
5
- /** Shared session storage on IndexedDB: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */
5
+ /** Shared session storage on IndexedDB, keyed per principal and key: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */
6
6
  export declare const IndexedDbSession: import("@assistant-ui/tap").Resource<StatewireClient.SessionStorage, [IndexedDbSession.Options]>;
7
7
  export declare namespace IndexedDbSession {
8
8
  type Options = Omit<KeyValueSessionStorage.Options, "backend" | "namespace"> & {
@@ -1 +1 @@
1
- {"version":3,"file":"indexedDb.d.ts","sourceRoot":"","sources":["../src/indexedDb.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,qBAAkB;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAoB;AAanD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,KACf,sBAAsB,CAAC,OAmEzB,CAAC;AAmEF,4HAA4H;AAC5H,eAAO,MAAM,gBAAgB,kGAAgC,CAAC;AAE9D,yBAAiB,gBAAgB,CAAC;IAChC,KAAY,OAAO,GAAG,IAAI,CACxB,sBAAsB,CAAC,OAAO,EAC9B,SAAS,GAAG,WAAW,CACxB,GAAG;QACF,oDAAoD;QACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,oCAAoC;QACpC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH"}
1
+ {"version":3,"file":"indexedDb.d.ts","sourceRoot":"","sources":["../src/indexedDb.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAoB,qBAAkB;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAoB;AAanD,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,GAC3B,UAAU,MAAM,KACf,sBAAsB,CAAC,OAmEzB,CAAC;AAoEF,yJAAyJ;AACzJ,eAAO,MAAM,gBAAgB,kGAAgC,CAAC;AAE9D,yBAAiB,gBAAgB,CAAC;IAChC,KAAY,OAAO,GAAG,IAAI,CACxB,sBAAsB,CAAC,OAAO,EAC9B,SAAS,GAAG,WAAW,CACxB,GAAG;QACF,oDAAoD;QACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,oCAAoC;QACpC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH"}
package/dist/indexedDb.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { randomId } from "./util/randomId.js";
2
- import { KeyValueSessionStorage } from "./storage.js";
2
+ import { KeyValueSessionStorage, sessionNamespace } from "./storage.js";
3
3
  import { useEffect, useState } from "@assistant-ui/tap/react-shim";
4
4
  import { resource, useResource } from "@assistant-ui/tap";
5
5
  import { c } from "@assistant-ui/tap/react-shim/compiler-runtime";
@@ -71,12 +71,12 @@ const holdLock = (name, mode = "exclusive") => {
71
71
  };
72
72
  const useIndexedDbSession = (t0) => {
73
73
  const $ = c(22);
74
- const { database: t1, key, namespace: t2, ...options } = t0;
74
+ const { database: t1, key, namespace: t2, principal, ...options } = t0;
75
75
  const database = t1 === void 0 ? "statewire" : t1;
76
76
  const namespace = t2 === void 0 ? "statewire-session:" : t2;
77
77
  if (typeof navigator === "undefined" || navigator.locks === void 0) throw new Error("statewire: IndexedDbSession requires the Web Locks API (navigator.locks)");
78
78
  const backend = indexedDbBackend(database);
79
- const storageKey = namespace + key;
79
+ const storageKey = sessionNamespace(namespace, principal) + key;
80
80
  const [tabId] = useState(randomId);
81
81
  let t3;
82
82
  let t4;
@@ -109,7 +109,8 @@ const useIndexedDbSession = (t0) => {
109
109
  ...options,
110
110
  backend,
111
111
  key,
112
- namespace
112
+ namespace,
113
+ principal
113
114
  }));
114
115
  let t7;
115
116
  let t8;
@@ -160,7 +161,7 @@ const useIndexedDbSession = (t0) => {
160
161
  } else t11 = $[21];
161
162
  return t11;
162
163
  };
163
- /** Shared session storage on IndexedDB: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */
164
+ /** Shared session storage on IndexedDB, keyed per principal and key: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */
164
165
  const IndexedDbSession = resource(useIndexedDbSession);
165
166
  function _temp() {}
166
167
  function _temp2(id, signal) {
@@ -1 +1 @@
1
- {"version":3,"file":"indexedDb.js","names":["useEffect","useMemo","useState","resource","useResource","KeyValueSessionStorage","StatewireClient","randomId","STORE","request","req","IDBRequest","T","Promise","resolve","reject","onsuccess","result","onerror","error","backends","Map","Backend","indexedDbBackend","database","existing","get","indexedDB","Error","opened","IDBDatabase","db","open","onupgradeneeded","createObjectStore","run","mode","IDBTransactionMode","fn","store","IDBObjectStore","tx","transaction","committed","undefined","oncomplete","onabort","objectStore","channel","BroadcastChannel","announce","key","postMessage","backend","getItem","s","setItem","value","put","removeItem","delete","getAllKeys","withUnused","action","navigator","locks","ifAvailable","lock","inner","subscribe","listener","own","onmessage","event","MessageEvent","data","close","set","holdLock","name","LockMode","release","disposed","catch","useIndexedDbSession","t0","$","_c","t1","namespace","t2","options","storageKey","tabId","t3","t4","t5","t6","storage","t7","t8","fn_0","t9","id","whenGone","_temp2","t10","changed","t11","notifyLock","owner","IndexedDbSession","Options","Omit","_temp","signal","_c"],"sources":["../src/indexedDb.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport { resource, useResource } from \"@assistant-ui/tap\";\nimport { KeyValueSessionStorage } from \"./storage\";\nimport type { StatewireClient } from \"./statewire\";\nimport { randomId } from \"./util/randomId\";\n\nconst STORE = \"sessions\";\n\nconst request = <T>(req: IDBRequest<T>) =>\n new Promise<T>((resolve, reject) => {\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n\nconst backends = new Map<string, KeyValueSessionStorage.Backend>();\n\n/** A key-value backend over one IndexedDB database; writes broadcast to other runtimes. */\nexport const indexedDbBackend = (\n database: string,\n): KeyValueSessionStorage.Backend => {\n const existing = backends.get(database);\n if (existing) return existing;\n if (typeof indexedDB === \"undefined\")\n throw new Error(\"statewire: IndexedDbSession requires indexedDB\");\n let opened: Promise<IDBDatabase> | undefined;\n const db = () =>\n (opened ??= (() => {\n const open = indexedDB.open(database, 1);\n open.onupgradeneeded = () => open.result.createObjectStore(STORE);\n return request(open);\n })());\n // Writes resolve once their transaction commits.\n const run = async <T>(\n mode: IDBTransactionMode,\n fn: (store: IDBObjectStore) => IDBRequest<T>,\n ) => {\n const tx = (await db()).transaction(STORE, mode);\n const committed =\n mode === \"readonly\"\n ? undefined\n : new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error);\n });\n const result = await request(fn(tx.objectStore(STORE)));\n await committed;\n return result;\n };\n const channel = new BroadcastChannel(`statewire-idb:${database}`);\n const announce = (key: string) => channel.postMessage(key);\n const backend: KeyValueSessionStorage.Backend = {\n getItem: async (key) =>\n ((await run(\"readonly\", (s) => s.get(key))) as string | undefined) ??\n null,\n setItem: async (key, value) => {\n await run(\"readwrite\", (s) => s.put(value, key));\n announce(key);\n },\n removeItem: async (key) => {\n await run(\"readwrite\", (s) => s.delete(key));\n announce(key);\n },\n getAllKeys: async () =>\n (await run(\"readonly\", (s) => s.getAllKeys())) as string[],\n withUnused: (key, action) =>\n navigator.locks.request(\n `statewire-active:${database}:${key}`,\n { ifAvailable: true },\n (lock) =>\n lock === null\n ? undefined\n : navigator.locks.request(\n `statewire-session:${database}:${key}`,\n { ifAvailable: true },\n (inner) => (inner === null ? undefined : action()),\n ),\n ),\n subscribe: (listener) => {\n const own = new BroadcastChannel(`statewire-idb:${database}`);\n own.onmessage = (event: MessageEvent<string>) => listener(event.data);\n return () => own.close();\n },\n };\n backends.set(database, backend);\n return backend;\n};\n\nconst holdLock = (name: string, mode: LockMode = \"exclusive\") => {\n let release: (() => void) | undefined;\n let disposed = false;\n void navigator.locks\n .request(name, { mode }, () => {\n if (disposed) return;\n return new Promise<void>((resolve) => {\n release = resolve;\n });\n })\n .catch(() => {});\n return () => {\n disposed = true;\n release?.();\n };\n};\n\nconst useIndexedDbSession = ({\n database = \"statewire\",\n key,\n namespace = \"statewire-session:\",\n ...options\n}: IndexedDbSession.Options): StatewireClient.SessionStorage => {\n if (typeof navigator === \"undefined\" || navigator.locks === undefined)\n throw new Error(\n \"statewire: IndexedDbSession requires the Web Locks API (navigator.locks)\",\n );\n const backend = indexedDbBackend(database);\n const storageKey = namespace + key;\n const [tabId] = useState(randomId);\n useEffect(() => holdLock(`statewire-tab:${tabId}`), [tabId]);\n useEffect(\n () => holdLock(`statewire-active:${database}:${storageKey}`, \"shared\"),\n [database, storageKey],\n );\n const storage = useResource(\n KeyValueSessionStorage({ ...options, backend, key, namespace }),\n );\n return useMemo(\n (): StatewireClient.SessionStorage => ({\n ...storage,\n lock: (fn) =>\n navigator.locks.request(\n `statewire-session:${database}:${storageKey}`,\n async () => fn(),\n ),\n notifyLock: (fn) =>\n navigator.locks.request(\n `statewire-notify:${database}:${storageKey}`,\n fn,\n ),\n owner: {\n id: tabId,\n whenGone: (id, signal) =>\n navigator.locks.request(`statewire-tab:${id}`, { signal }, () => {}),\n },\n subscribe: (listener) =>\n backend.subscribe!((changed) => {\n if (changed === storageKey) listener();\n }),\n }),\n [storage, backend, database, storageKey, tabId],\n );\n};\n\n/** Shared session storage on IndexedDB: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */\nexport const IndexedDbSession = resource(useIndexedDbSession);\n\nexport namespace IndexedDbSession {\n export type Options = Omit<\n KeyValueSessionStorage.Options,\n \"backend\" | \"namespace\"\n > & {\n /** IndexedDB database name; default \"statewire\". */\n database?: string;\n /** Default \"statewire-session:\". */\n namespace?: string;\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"indexedDb.js","names":["useEffect","useMemo","useState","resource","useResource","KeyValueSessionStorage","sessionNamespace","StatewireClient","randomId","STORE","request","req","IDBRequest","T","Promise","resolve","reject","onsuccess","result","onerror","error","backends","Map","Backend","indexedDbBackend","database","existing","get","indexedDB","Error","opened","IDBDatabase","db","open","onupgradeneeded","createObjectStore","run","mode","IDBTransactionMode","fn","store","IDBObjectStore","tx","transaction","committed","undefined","oncomplete","onabort","objectStore","channel","BroadcastChannel","announce","key","postMessage","backend","getItem","s","setItem","value","put","removeItem","delete","getAllKeys","withUnused","action","navigator","locks","ifAvailable","lock","inner","subscribe","listener","own","onmessage","event","MessageEvent","data","close","set","holdLock","name","LockMode","release","disposed","catch","useIndexedDbSession","t0","$","_c","t1","namespace","t2","principal","options","storageKey","tabId","t3","t4","t5","t6","storage","t7","t8","fn_0","t9","id","whenGone","_temp2","t10","changed","t11","notifyLock","owner","IndexedDbSession","Options","Omit","_temp","signal","_c"],"sources":["../src/indexedDb.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from \"react\";\nimport { resource, useResource } from \"@assistant-ui/tap\";\nimport { KeyValueSessionStorage, sessionNamespace } from \"./storage\";\nimport type { StatewireClient } from \"./statewire\";\nimport { randomId } from \"./util/randomId\";\n\nconst STORE = \"sessions\";\n\nconst request = <T>(req: IDBRequest<T>) =>\n new Promise<T>((resolve, reject) => {\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n\nconst backends = new Map<string, KeyValueSessionStorage.Backend>();\n\n/** A key-value backend over one IndexedDB database; writes broadcast to other runtimes. */\nexport const indexedDbBackend = (\n database: string,\n): KeyValueSessionStorage.Backend => {\n const existing = backends.get(database);\n if (existing) return existing;\n if (typeof indexedDB === \"undefined\")\n throw new Error(\"statewire: IndexedDbSession requires indexedDB\");\n let opened: Promise<IDBDatabase> | undefined;\n const db = () =>\n (opened ??= (() => {\n const open = indexedDB.open(database, 1);\n open.onupgradeneeded = () => open.result.createObjectStore(STORE);\n return request(open);\n })());\n // Writes resolve once their transaction commits.\n const run = async <T>(\n mode: IDBTransactionMode,\n fn: (store: IDBObjectStore) => IDBRequest<T>,\n ) => {\n const tx = (await db()).transaction(STORE, mode);\n const committed =\n mode === \"readonly\"\n ? undefined\n : new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () => reject(tx.error);\n tx.onabort = () => reject(tx.error);\n });\n const result = await request(fn(tx.objectStore(STORE)));\n await committed;\n return result;\n };\n const channel = new BroadcastChannel(`statewire-idb:${database}`);\n const announce = (key: string) => channel.postMessage(key);\n const backend: KeyValueSessionStorage.Backend = {\n getItem: async (key) =>\n ((await run(\"readonly\", (s) => s.get(key))) as string | undefined) ??\n null,\n setItem: async (key, value) => {\n await run(\"readwrite\", (s) => s.put(value, key));\n announce(key);\n },\n removeItem: async (key) => {\n await run(\"readwrite\", (s) => s.delete(key));\n announce(key);\n },\n getAllKeys: async () =>\n (await run(\"readonly\", (s) => s.getAllKeys())) as string[],\n withUnused: (key, action) =>\n navigator.locks.request(\n `statewire-active:${database}:${key}`,\n { ifAvailable: true },\n (lock) =>\n lock === null\n ? undefined\n : navigator.locks.request(\n `statewire-session:${database}:${key}`,\n { ifAvailable: true },\n (inner) => (inner === null ? undefined : action()),\n ),\n ),\n subscribe: (listener) => {\n const own = new BroadcastChannel(`statewire-idb:${database}`);\n own.onmessage = (event: MessageEvent<string>) => listener(event.data);\n return () => own.close();\n },\n };\n backends.set(database, backend);\n return backend;\n};\n\nconst holdLock = (name: string, mode: LockMode = \"exclusive\") => {\n let release: (() => void) | undefined;\n let disposed = false;\n void navigator.locks\n .request(name, { mode }, () => {\n if (disposed) return;\n return new Promise<void>((resolve) => {\n release = resolve;\n });\n })\n .catch(() => {});\n return () => {\n disposed = true;\n release?.();\n };\n};\n\nconst useIndexedDbSession = ({\n database = \"statewire\",\n key,\n namespace = \"statewire-session:\",\n principal,\n ...options\n}: IndexedDbSession.Options): StatewireClient.SessionStorage => {\n if (typeof navigator === \"undefined\" || navigator.locks === undefined)\n throw new Error(\n \"statewire: IndexedDbSession requires the Web Locks API (navigator.locks)\",\n );\n const backend = indexedDbBackend(database);\n const storageKey = sessionNamespace(namespace, principal) + key;\n const [tabId] = useState(randomId);\n useEffect(() => holdLock(`statewire-tab:${tabId}`), [tabId]);\n useEffect(\n () => holdLock(`statewire-active:${database}:${storageKey}`, \"shared\"),\n [database, storageKey],\n );\n const storage = useResource(\n KeyValueSessionStorage({ ...options, backend, key, namespace, principal }),\n );\n return useMemo(\n (): StatewireClient.SessionStorage => ({\n ...storage,\n lock: (fn) =>\n navigator.locks.request(\n `statewire-session:${database}:${storageKey}`,\n async () => fn(),\n ),\n notifyLock: (fn) =>\n navigator.locks.request(\n `statewire-notify:${database}:${storageKey}`,\n fn,\n ),\n owner: {\n id: tabId,\n whenGone: (id, signal) =>\n navigator.locks.request(`statewire-tab:${id}`, { signal }, () => {}),\n },\n subscribe: (listener) =>\n backend.subscribe!((changed) => {\n if (changed === storageKey) listener();\n }),\n }),\n [storage, backend, database, storageKey, tabId],\n );\n};\n\n/** Shared session storage on IndexedDB, keyed per principal and key: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */\nexport const IndexedDbSession = resource(useIndexedDbSession);\n\nexport namespace IndexedDbSession {\n export type Options = Omit<\n KeyValueSessionStorage.Options,\n \"backend\" | \"namespace\"\n > & {\n /** IndexedDB database name; default \"statewire\". */\n database?: string;\n /** Default \"statewire-session:\". */\n namespace?: string;\n };\n}\n"],"mappings":"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"}
package/dist/storage.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { StatewireClient } from "./statewire.js";
2
- export declare const STORAGE_FORMAT = 5;
2
+ /** The prefix of every record `KeyValueSessionStorage` keeps for `principal` under `namespace`. */
3
+ export declare const sessionNamespace: (namespace: string, principal: string) => string;
4
+ export declare const STORAGE_FORMAT = 6;
3
5
  /** Persist sessions through a synchronous or asynchronous string storage backend. */
4
6
  export declare const KeyValueSessionStorage: import("@assistant-ui/tap").Resource<StatewireClient.SessionStorage, [options: KeyValueSessionStorage.Options]>;
5
7
  export declare namespace KeyValueSessionStorage {
@@ -23,6 +25,8 @@ export declare namespace KeyValueSessionStorage {
23
25
  backend: Backend;
24
26
  key: string;
25
27
  namespace?: string;
28
+ /** The account whose session this storage holds; scopes the stored key, tags the record. */
29
+ principal: string;
26
30
  /** The lane that sessions saved in a single-lane format lift into; defaults to the client's default protocol. */
27
31
  protocol?: string;
28
32
  migrate?: StatewireClient.SessionStorage["migrate"];
@@ -42,20 +46,24 @@ export declare namespace KeyValueSessionStorage {
42
46
  onEvict?: (event: EvictEvent) => void;
43
47
  };
44
48
  }
45
- /** Session storage in page memory, keyed by scope: the record and client identity survive remounts, not reloads. */
49
+ /** Session storage in page memory, keyed by principal and scope: the record and client identity survive remounts, not reloads. */
46
50
  export declare const MemorySessionStorage: import("@assistant-ui/tap").Resource<StatewireClient.SessionStorage, [MemorySessionStorage.Options]>;
47
51
  export declare namespace MemorySessionStorage {
48
52
  type Options = {
53
+ /** The account whose session this storage holds. */
54
+ principal: string;
49
55
  scope: string;
50
56
  };
51
57
  }
52
- /** Reads the outboxes of every session under `namespace`, mounted or not. */
58
+ /** Reads the outboxes of every session `principal` keeps under `namespace`, mounted or not. */
53
59
  export declare const readOutboxes: (backend: KeyValueSessionStorage.Backend, options: ReadOutboxes.Options) => Promise<readonly ReadOutboxes.Outbox[]>;
54
- /** Calls `listener` with `readOutboxes` now and after every save under `namespace`; returns unsubscribe. */
60
+ /** Calls `listener` with `readOutboxes` now and after every save to `principal`'s sessions under `namespace`; returns unsubscribe. */
55
61
  export declare const subscribeOutboxes: (backend: KeyValueSessionStorage.Backend, options: ReadOutboxes.Options, listener: (outboxes: readonly ReadOutboxes.Outbox[]) => void, onError: (error: unknown) => void) => (() => void);
56
62
  export declare namespace ReadOutboxes {
57
63
  type Options = {
58
64
  namespace: string;
65
+ /** The account whose sessions to read. */
66
+ principal: string;
59
67
  codec?: KeyValueSessionStorage.Codec;
60
68
  /** The lane that single-lane formats lift into. */
61
69
  protocol?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,uBAAoB;AAqPnD,mGAAmG;AACnG,eAAO,MAAM,gBAAgB,GAAI,WAAW,MAAM,EAAE,WAAW,MAAM,WAIpE,CAAC;AA0IF,eAAO,MAAM,cAAc,IAAI,CAAC;AAshBhC,qFAAqF;AACrF,eAAO,MAAM,sBAAsB,iHAAsC,CAAC;AAE1E,yBAAiB,sBAAsB,CAAC;IACtC,KAAY,OAAO,GAAG;QACpB,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QACjE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAC9D,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAClD,UAAU,CAAC,EAAE,MAAM,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;QAClE,oFAAoF;QACpF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QACzE,kEAAkE;QAClE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;KAC7D,CAAC;IACF,oEAAoE;IACpE,KAAY,KAAK,GAAG;QAClB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;KAC7C,CAAC;IACF,KAAY,UAAU,GAAG,eAAe,CAAC,UAAU,CAAC;IACpD,KAAY,OAAO,GAAG;QACpB,OAAO,EAAE,OAAO,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,4FAA4F;QAC5F,SAAS,EAAE,MAAM,CAAC;QAClB,iHAAiH;QACjH,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,OAAO,CAAC,EAAE,eAAe,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QACpD,SAAS,CAAC,EAAE;YAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,UAAU,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACvD,yGAAyG;QACzG,MAAM,CAAC,EAAE;YACP,aAAa,CAAC,EAAE,MAAM,CAAC;YACvB,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,UAAU,CAAC,EAAE,MAAM,CAAC;SACrB,CAAC;QACF,KAAK,CAAC,EAAE,KAAK,CAAC;QACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;QACnC,2DAA2D;QAC3D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;KACvC,CAAC;CACH;AAsBD,kIAAkI;AAClI,eAAO,MAAM,oBAAoB,sGAAoC,CAAC;AAEtE,yBAAiB,oBAAoB,CAAC;IACpC,KAAY,OAAO,GAAG;QACpB,oDAAoD;QACpD,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAgCD,+FAA+F;AAC/F,eAAO,MAAM,YAAY,GACvB,SAAS,sBAAsB,CAAC,OAAO,EACvC,SAAS,YAAY,CAAC,OAAO,KAC5B,OAAO,CAAC,SAAS,YAAY,CAAC,MAAM,EAAE,CAgBxC,CAAC;AAEF,sIAAsI;AACtI,eAAO,MAAM,iBAAiB,GAC5B,SAAS,sBAAsB,CAAC,OAAO,EACvC,SAAS,YAAY,CAAC,OAAO,EAC7B,UAAU,CAAC,QAAQ,EAAE,SAAS,YAAY,CAAC,MAAM,EAAE,KAAK,IAAI,EAC5D,SAAS,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,KAChC,CAAC,MAAM,IAAI,CA4Bb,CAAC;AAEF,yBAAiB,YAAY,CAAC;IAC5B,KAAY,OAAO,GAAG;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,0CAA0C;QAC1C,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,sBAAsB,CAAC,KAAK,CAAC;QACrC,mDAAmD;QACnD,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,KAAY,MAAM,GAAG;QACnB,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,SAAS,eAAe,CAAC,WAAW,EAAE,CAAC;KACjD,CAAC;CACH;AACD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,uBAAoB"}
package/dist/storage.js CHANGED
@@ -70,6 +70,15 @@ const isProducer = (value) => {
70
70
  const invalid = () => {
71
71
  throw new Error("statewire: invalid stored session record");
72
72
  };
73
+ const isPrincipal = (value) => typeof value === "string" && value !== "";
74
+ /** The prefix of every record `KeyValueSessionStorage` keeps for `principal` under `namespace`. */
75
+ const sessionNamespace = (namespace, principal) => {
76
+ if (!isPrincipal(principal)) throw new Error("statewire: storage requires a non-empty principal");
77
+ return `${namespace}${encodeURIComponent(principal)}:`;
78
+ };
79
+ const usePrincipal = (principal) => {
80
+ if (useRef(principal).current !== principal) throw new Error("statewire: principal changed; remount the account's thread/session");
81
+ };
73
82
  const liftLegacy = (value, protocol) => {
74
83
  const { clientId, nextSeq, commands, verdicts, failed } = value;
75
84
  const lane = {
@@ -124,9 +133,15 @@ const upgrade = (record) => ({
124
133
  }))
125
134
  });
126
135
  const isLegacyLane = (lane) => isLaneWith(lane, isSessionCommand, true);
127
- const normalize = (parsed, protocol) => {
136
+ const normalize = (parsed, protocol, principal) => {
128
137
  if (!isRecord(parsed)) return invalid();
129
- const { statewireStorageVersion: format, savedAt: _savedAt, ...value } = parsed;
138
+ const { statewireStorageVersion: format, savedAt: _savedAt, principal: owner, ...value } = parsed;
139
+ if (format === 6) {
140
+ if (!isPrincipal(owner) || !isSessionRecord(value)) return invalid();
141
+ if (owner !== principal) throw new Error("statewire: stored session belongs to another principal");
142
+ return value;
143
+ }
144
+ if (owner !== void 0) return invalid();
130
145
  if (format === 5) return isSessionRecord(value) ? value : invalid();
131
146
  if (format === 4) return isRecordWith(value, isLegacyLane) ? upgrade(value) : invalid();
132
147
  if (format === 3) return isRecordWith(value, (lane) => isLaneWith(lane, isLegacyCommand, true)) ? upgrade(identify(value)) : invalid();
@@ -134,9 +149,9 @@ const normalize = (parsed, protocol) => {
134
149
  const { ctxNextSeq: _ctxNextSeq, context: _context, ...legacy } = value;
135
150
  return upgrade(liftLegacy(legacy, protocol));
136
151
  };
137
- const STORAGE_FORMAT = 5;
152
+ const STORAGE_FORMAT = 6;
138
153
  const parse = async (raw, codec) => raw === null ? null : JSON.parse(codec === void 0 ? raw : await codec.decode(raw));
139
- const decode = (raw, protocol) => raw === null ? null : normalize(JSON.parse(raw), protocol);
154
+ const decode = (raw, protocol, principal) => raw === null ? null : normalize(JSON.parse(raw), protocol, principal);
140
155
  const isUnsent = (lane) => lane.commands.length > 0 || (lane.outbox?.length ?? 0) > 0;
141
156
  const entryCount = (record) => record === null ? 0 : Object.values(record.lanes).reduce((sum, lane) => sum + lane.commands.length + (lane.outbox?.length ?? 0), 0);
142
157
  const noticesOf = (record) => Object.entries(record.lanes).flatMap(([protocol, lane]) => (lane.inbox ?? []).map((n) => ({
@@ -162,8 +177,9 @@ const sweep = (options, currentKey, quota = false) => {
162
177
  return run;
163
178
  };
164
179
  const sweepOnce = async (options, currentKey, quota = false) => {
165
- const { backend, retention, namespace = "", codec, onEvict } = options;
180
+ const { backend, retention, codec, onEvict, principal } = options;
166
181
  if (!retention || !backend.getAllKeys) return;
182
+ const namespace = sessionNamespace(options.namespace ?? "", principal);
167
183
  const candidate = async (raw) => {
168
184
  let value;
169
185
  try {
@@ -174,7 +190,7 @@ const sweepOnce = async (options, currentKey, quota = false) => {
174
190
  if (value === null) return void 0;
175
191
  let record;
176
192
  try {
177
- record = normalize(value, DEFAULT_PROTOCOL);
193
+ record = normalize(value, DEFAULT_PROTOCOL, principal);
178
194
  } catch {
179
195
  return;
180
196
  }
@@ -245,7 +261,9 @@ const validLimit = (value, name) => {
245
261
  if (value !== void 0 && (!Number.isSafeInteger(value) || value < 1)) throw new Error(`statewire: invalid session limit ${name}`);
246
262
  };
247
263
  const useKeyValueSessionStorage = (options) => {
248
- const { backend, key, namespace = "", protocol = DEFAULT_PROTOCOL, migrate, retention: suppliedRetention, limits: suppliedLimits, codec, onError, onEvict } = options;
264
+ const { backend, key, namespace = "", principal, protocol = DEFAULT_PROTOCOL, migrate, retention: suppliedRetention, limits: suppliedLimits, codec, onError, onEvict } = options;
265
+ const scope = sessionNamespace(namespace, principal);
266
+ usePrincipal(principal);
249
267
  const latest = useRef({
250
268
  onError,
251
269
  onEvict
@@ -278,6 +296,7 @@ const useKeyValueSessionStorage = (options) => {
278
296
  backend,
279
297
  key,
280
298
  namespace,
299
+ principal,
281
300
  onEvict: reportEvict,
282
301
  ...codec === void 0 ? {} : { codec },
283
302
  ...retention === void 0 ? {} : { retention }
@@ -285,11 +304,12 @@ const useKeyValueSessionStorage = (options) => {
285
304
  backend,
286
305
  key,
287
306
  namespace,
307
+ principal,
288
308
  retention,
289
309
  codec,
290
310
  reportEvict
291
311
  ]);
292
- const storageKey = namespace + key;
312
+ const storageKey = scope + key;
293
313
  if (retention !== void 0) {
294
314
  if (!namespace) throw new Error("statewire: session retention requires a namespace");
295
315
  if (!backend.getAllKeys) throw new Error("statewire: session retention requires getAllKeys");
@@ -324,7 +344,7 @@ const useKeyValueSessionStorage = (options) => {
324
344
  state.ready = Promise.resolve(raw).then(async (value) => {
325
345
  state.bytes = value?.length ?? 0;
326
346
  const parsed = await parse(value, codec);
327
- hydrate(parsed === null ? null : normalize(parsed, protocol));
347
+ hydrate(parsed === null ? null : normalize(parsed, protocol, principal));
328
348
  }).catch((cause_0) => {
329
349
  const error_1 = wrapStorageError(cause_0);
330
350
  if (state.failure !== error_1) reportError(error_1);
@@ -334,7 +354,7 @@ const useKeyValueSessionStorage = (options) => {
334
354
  state.ready.catch(() => {});
335
355
  } else try {
336
356
  state.bytes = raw?.length ?? 0;
337
- hydrate(decode(raw, protocol));
357
+ hydrate(decode(raw, protocol, principal));
338
358
  } catch (cause_1) {
339
359
  const error_2 = wrapStorageError(cause_1);
340
360
  reportError(error_2);
@@ -345,6 +365,7 @@ const useKeyValueSessionStorage = (options) => {
345
365
  backend,
346
366
  storageKey,
347
367
  protocol,
368
+ principal,
348
369
  codec,
349
370
  reportError
350
371
  ]);
@@ -373,13 +394,13 @@ const useKeyValueSessionStorage = (options) => {
373
394
  ]);
374
395
  useEffect(() => {
375
396
  if (totalBytes === void 0) return;
376
- otherBytes(backend, namespace, storageKey).then((bytes) => {
397
+ otherBytes(backend, scope, storageKey).then((bytes) => {
377
398
  state_0.others = bytes;
378
399
  }, () => {});
379
400
  }, [
380
401
  state_0,
381
402
  backend,
382
- namespace,
403
+ scope,
383
404
  storageKey,
384
405
  totalBytes
385
406
  ]);
@@ -412,7 +433,7 @@ const useKeyValueSessionStorage = (options) => {
412
433
  const raw_0 = await backend.getItem(storageKey);
413
434
  const parsed_0 = await parse(raw_0, codec);
414
435
  state_0.bytes = raw_0?.length ?? 0;
415
- state_0.record = parsed_0 === null ? null : normalize(parsed_0, protocol);
436
+ state_0.record = parsed_0 === null ? null : normalize(parsed_0, protocol, principal);
416
437
  return state_0.record;
417
438
  } catch (cause_3) {
418
439
  const error_5 = wrapStorageError(cause_3);
@@ -434,7 +455,7 @@ const useKeyValueSessionStorage = (options) => {
434
455
  }
435
456
  if (typeof raw_1 !== "string" && raw_1 !== null) throw new Error("statewire: storage backend changed from synchronous to asynchronous");
436
457
  try {
437
- state_0.record = decode(raw_1, protocol);
458
+ state_0.record = decode(raw_1, protocol, principal);
438
459
  return state_0.record;
439
460
  } catch (cause_5) {
440
461
  const error_7 = wrapStorageError(cause_5);
@@ -448,7 +469,8 @@ const useKeyValueSessionStorage = (options) => {
448
469
  if (!isSessionRecord(record_1)) throw new Error("statewire: invalid session record");
449
470
  const encoded_0 = JSON.stringify({
450
471
  ...record_1,
451
- statewireStorageVersion: 5,
472
+ principal,
473
+ statewireStorageVersion: 6,
452
474
  savedAt: Date.now()
453
475
  });
454
476
  checkLimits(record_1, encoded_0.length);
@@ -520,6 +542,7 @@ const useKeyValueSessionStorage = (options) => {
520
542
  backend,
521
543
  storageKey,
522
544
  protocol,
545
+ principal,
523
546
  migrate,
524
547
  retention,
525
548
  configured,
@@ -536,22 +559,24 @@ const KeyValueSessionStorage = resource(useKeyValueSessionStorage);
536
559
  const pages = /* @__PURE__ */ new Map();
537
560
  const useMemorySessionStorage = (t0) => {
538
561
  const $ = c(2);
539
- const { scope } = t0;
562
+ const { principal, scope } = t0;
563
+ const key = sessionNamespace("", principal) + scope;
564
+ usePrincipal(principal);
540
565
  let t1;
541
- if ($[0] !== scope) {
566
+ if ($[0] !== key) {
542
567
  t1 = {
543
568
  durable: false,
544
- load: () => pages.get(scope) ?? null,
569
+ load: () => pages.get(key) ?? null,
545
570
  save: (record) => {
546
- pages.set(scope, record);
571
+ pages.set(key, record);
547
572
  }
548
573
  };
549
- $[0] = scope;
574
+ $[0] = key;
550
575
  $[1] = t1;
551
576
  } else t1 = $[1];
552
577
  return t1;
553
578
  };
554
- /** Session storage in page memory, keyed by scope: the record and client identity survive remounts, not reloads. */
579
+ /** Session storage in page memory, keyed by principal and scope: the record and client identity survive remounts, not reloads. */
555
580
  const MemorySessionStorage = resource(useMemorySessionStorage);
556
581
  const entriesOf = (record) => Object.entries(record.lanes).flatMap(([protocol, lane]) => {
557
582
  const view = (c, state) => ({
@@ -568,17 +593,18 @@ const entriesOf = (record) => Object.entries(record.lanes).flatMap(([protocol, l
568
593
  });
569
594
  return [...[...lane.commands].sort((a, b) => a.seq - b.seq).map((c) => view(c, "sending")), ...(lane.outbox ?? []).map((c) => view(c, c.held ? "held" : "queued"))];
570
595
  });
571
- /** Reads the outboxes of every session under `namespace`, mounted or not. */
596
+ /** Reads the outboxes of every session `principal` keeps under `namespace`, mounted or not. */
572
597
  const readOutboxes = async (backend, options) => {
573
598
  if (!backend.getAllKeys) throw new Error("statewire: readOutboxes requires getAllKeys");
574
- const { namespace, codec, protocol = DEFAULT_PROTOCOL } = options;
599
+ const { namespace, principal, codec, protocol = DEFAULT_PROTOCOL } = options;
575
600
  if (!namespace) throw new Error("statewire: readOutboxes requires a namespace");
601
+ const scope = sessionNamespace(namespace, principal);
576
602
  const result = [];
577
603
  for (const key of await backend.getAllKeys()) {
578
- if (!key.startsWith(namespace)) continue;
604
+ if (!key.startsWith(scope)) continue;
579
605
  const parsed = await parse(await backend.getItem(key), codec);
580
606
  if (parsed === null) continue;
581
- const entries = entriesOf(normalize(parsed, protocol));
607
+ const entries = entriesOf(normalize(parsed, protocol, principal));
582
608
  if (entries.length > 0) result.push({
583
609
  key,
584
610
  entries
@@ -586,8 +612,9 @@ const readOutboxes = async (backend, options) => {
586
612
  }
587
613
  return result;
588
614
  };
589
- /** Calls `listener` with `readOutboxes` now and after every save under `namespace`; returns unsubscribe. */
615
+ /** Calls `listener` with `readOutboxes` now and after every save to `principal`'s sessions under `namespace`; returns unsubscribe. */
590
616
  const subscribeOutboxes = (backend, options, listener, onError) => {
617
+ const scope = sessionNamespace(options.namespace, options.principal);
591
618
  let closed = false;
592
619
  let reading = Promise.resolve();
593
620
  const refresh = () => {
@@ -602,7 +629,7 @@ const subscribeOutboxes = (backend, options, listener, onError) => {
602
629
  });
603
630
  };
604
631
  const onKey = (key) => {
605
- if (key.startsWith(options.namespace)) refresh();
632
+ if (key.startsWith(scope)) refresh();
606
633
  };
607
634
  const local = savers.get(backend) ?? /* @__PURE__ */ new Set();
608
635
  savers.set(backend, local);
@@ -615,6 +642,6 @@ const subscribeOutboxes = (backend, options, listener, onError) => {
615
642
  remote?.();
616
643
  };
617
644
  };
618
- export { IndexedDbSession, KeyValueSessionStorage, MemorySessionStorage, STORAGE_FORMAT, indexedDbBackend, readOutboxes, subscribeOutboxes };
645
+ export { IndexedDbSession, KeyValueSessionStorage, MemorySessionStorage, STORAGE_FORMAT, indexedDbBackend, readOutboxes, sessionNamespace, subscribeOutboxes };
619
646
 
620
647
  //# sourceMappingURL=storage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","names":["useEffect","useMemo","useRef","randomId","wrapStorageError","reviveValue","resource","StatewireClient","Statewire","StatewireProtocol","DEFAULT_PROTOCOL","MIN_SERVED_WIRE_VERSION","WIRE_VERSION","negotiationHeaders","protocolToken","isRecord","value","Record","Array","isArray","isOptionalString","undefined","isPositiveSafeInteger","Number","isSafeInteger","isStatement","isProtocol","isProducer","isIdentified","isFinite","isLegacyCommand","isSessionCommand","SessionCommand","isOutboxCommand","OutboxCommand","isLegacyOutboxCommand","isStoredSettled","StoredSettled","isSendFailure","isSessionVerdict","SessionVerdict","SendFailure","FailedCommand","protocol","produced","command","params","meta","failure","ts","seq","isFailedCommand","isArrayOf","is","item","every","isMark","mark","isLaneWith","isCommand","legacy","SessionLane","isSessionLane","isClientId","length","isRecordWith","isLane","lane","SessionRecord","Object","keys","values","isSessionRecord","identify","record","lanes","fromEntries","entries","map","commands","wire","version","id","Date","now","test","wireVersion","Range","applicationProtocols","Offer","invalid","Error","LegacyLane","outbox","Omit","hold","failed","LegacyRecord","clientId","liftLegacy","nextSeq","verdicts","legacyCommands","filter","_protocol","entry","target","upgrade","inbox","sort","a","b","f","method","held","const","localMeta","isLegacyLane","normalize","parsed","statewireStorageVersion","format","savedAt","_savedAt","ctxNextSeq","_ctxNextSeq","context","_context","STORAGE_FORMAT","parse","raw","codec","KeyValueSessionStorage","Codec","Promise","JSON","decode","isUnsent","entryCount","reduce","sum","noticesOf","CommandUpdate","flatMap","n","status","active","WeakMap","Backend","Map","savers","Set","key","announce","backend","listener","get","Candidate","sweeping","sweep","options","Options","currentKey","quota","run","resolve","catch","then","sweepOnce","set","retention","namespace","onEvict","getAllKeys","candidate","some","empty","noticed","startsWith","has","found","getItem","push","remove","reason","EvictEvent","again","removeItem","dropped","withUnused","expired","maxAgeMs","retained","quotaStart","Math","floor","index","overflow","maxEntries","isQuotaError","error","name","quotaError","limit","otherBytes","total","other","validLimit","useKeyValueSessionStorage","SessionStorage","migrate","suppliedRetention","limits","suppliedLimits","onError","latest","current","reportError","reportEvict","event","retain","outboxEntries","recordBytes","totalBytes","configured","storageKey","state","cause","bytes","others","ready","queue","pending","asynchronous","hydrated","hydrate","count","delete","checkLimits","persist","encoded","stored","encode","setItem","durable","read","load","save","stringify","finally","flush","action","subscribe","plain","pages","useMemorySessionStorage","t0","$","_c","scope","t1","MemorySessionStorage","entriesOf","OutboxEntry","view","c","OutboxState","structuredClone","readOutboxes","ReadOutboxes","Outbox","result","subscribeOutboxes","outboxes","closed","reading","refresh","onKey","local","add","remote","IndexedDbSession","indexedDbBackend","_c","reviveValue"],"sources":["../src/storage.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from \"react\";\nimport { randomId } from \"./util/randomId\";\nimport { wrapStorageError } from \"./client/storageError\";\nimport { reviveValue } from \"./wire/meta\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { StatewireClient } from \"./statewire\";\nimport type { Statewire } from \"./types\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport {\n DEFAULT_PROTOCOL,\n MIN_SERVED_WIRE_VERSION,\n WIRE_VERSION,\n negotiationHeaders,\n protocolToken,\n} from \"./wire/protocol\";\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 isStatement = (value: unknown): value is Record<string, unknown> =>\n isRecord(value) &&\n isProtocol(value[\"protocol\"]) &&\n isProducer(value[\"produced\"]) &&\n typeof value[\"method\"] === \"string\" &&\n Array.isArray(value[\"params\"]);\n\nconst isIdentified = (value: Record<string, unknown>) =>\n typeof value[\"id\"] === \"string\" &&\n value[\"id\"] !== \"\" &&\n typeof value[\"ts\"] === \"number\" &&\n Number.isFinite(value[\"ts\"]);\n\nconst isLegacyCommand = (value: unknown) =>\n isStatement(value) && isPositiveSafeInteger(value[\"seq\"]);\n\nconst isSessionCommand = (\n value: unknown,\n): value is StatewireClient.SessionCommand =>\n isLegacyCommand(value) &&\n isIdentified(value as Record<string, unknown>) &&\n isOptionalString((value as Record<string, unknown>)[\"owner\"]);\n\nconst isOutboxCommand = (\n value: unknown,\n): value is StatewireClient.OutboxCommand =>\n isStatement(value) &&\n isIdentified(value) &&\n (value[\"held\"] === undefined || value[\"held\"] === true) &&\n (value[\"released\"] === undefined || value[\"released\"] === true) &&\n isOptionalString(value[\"by\"]);\n\n// Format 4 outbox entries carried the hold as `hold: { meta }`.\nconst isLegacyOutboxCommand = (value: unknown) =>\n isStatement(value) &&\n isIdentified(value) &&\n (value[\"hold\"] === undefined || isRecord(value[\"hold\"])) &&\n (value[\"released\"] === undefined || value[\"released\"] === true) &&\n isOptionalString(value[\"by\"]);\n\nconst isStoredSettled = (\n value: unknown,\n): value is StatewireClient.StoredSettled =>\n isRecord(value) &&\n isIdentified(value) &&\n typeof value[\"method\"] === \"string\" &&\n Array.isArray(value[\"params\"]) &&\n (value[\"seq\"] === undefined || isPositiveSafeInteger(value[\"seq\"])) &&\n (value[\"failure\"] === undefined\n ? isRecord(value[\"result\"]) && Array.isArray(value[\"result\"][\"value\"])\n : value[\"result\"] === undefined && isSendFailure(value[\"failure\"]));\n\nconst isSessionVerdict = (\n value: unknown,\n): value is StatewireClient.SessionVerdict =>\n isRecord(value) &&\n isPositiveSafeInteger(value[\"seq\"]) &&\n isOptionalString(value[\"id\"]) &&\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[\"cause\"] === \"removed\" ||\n value[\"cause\"] === \"quota\") &&\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\ntype FailedCommand = {\n protocol: string;\n produced?: StatewireClient.SessionCommand[\"produced\"];\n command: string;\n params: unknown[];\n meta?: unknown;\n failure: StatewireClient.SendFailure;\n ts: number;\n seq: number;\n};\n\nconst isFailedCommand = (value: unknown): value is 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 isArrayOf = (value: unknown, is: (item: unknown) => boolean) =>\n value === undefined || (Array.isArray(value) && value.every(is));\n\nconst isMark = (mark: unknown) =>\n isRecord(mark) &&\n typeof mark[\"id\"] === \"string\" &&\n typeof mark[\"by\"] === \"string\" &&\n (mark[\"failure\"] === undefined || isSendFailure(mark[\"failure\"]));\n\nconst isLaneWith = (\n value: unknown,\n isCommand: (command: unknown) => boolean,\n legacy = false,\n): value is StatewireClient.SessionLane =>\n isRecord(value) &&\n isPositiveSafeInteger(value[\"nextSeq\"]) &&\n Array.isArray(value[\"commands\"]) &&\n value[\"commands\"].every(isCommand) &&\n isArrayOf(\n value[\"outbox\"],\n legacy ? isLegacyOutboxCommand : isOutboxCommand,\n ) &&\n isArrayOf(value[\"removed\"], isMark) &&\n isArrayOf(value[\"verdicts\"], isSessionVerdict) &&\n (legacy\n ? isArrayOf(value[\"failed\"], isFailedCommand)\n : value[\"failed\"] === undefined &&\n isArrayOf(value[\"inbox\"], isStoredSettled));\n\nconst isSessionLane = (value: unknown): value is StatewireClient.SessionLane =>\n isLaneWith(value, isSessionCommand);\n\nconst isClientId = (value: unknown): value is string =>\n typeof value === \"string\" && value.length > 0;\n\nconst isRecordWith = (\n value: unknown,\n isLane: (lane: unknown) => boolean,\n): value is StatewireClient.SessionRecord =>\n isRecord(value) &&\n isClientId(value[\"clientId\"]) &&\n isRecord(value[\"lanes\"]) &&\n Object.keys(value[\"lanes\"]).every(isProtocol) &&\n Object.values(value[\"lanes\"]).every(isLane);\n\nconst isSessionRecord = (\n value: unknown,\n): value is StatewireClient.SessionRecord => isRecordWith(value, isSessionLane);\n\n// Formats before 4 stamped commands without an id or an enqueue time.\nconst identify = (\n record: StatewireClient.SessionRecord,\n): StatewireClient.SessionRecord => ({\n ...record,\n lanes: Object.fromEntries(\n Object.entries(record.lanes).map(([protocol, lane]) => [\n protocol,\n {\n ...lane,\n commands: lane.commands.map((command) => ({\n ...command,\n // The statement shape is unchanged across the gap-fill contract.\n ...(command.produced?.wire.version === MIN_SERVED_WIRE_VERSION && {\n produced: { ...command.produced, wire: { version: WIRE_VERSION } },\n }),\n id: randomId(),\n ts: Date.now(),\n })),\n },\n ]),\n ),\n});\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 invalid = (): never => {\n throw new Error(\"statewire: invalid stored session record\");\n};\n\ntype LegacyLane = Record<string, unknown> & {\n commands: StatewireClient.SessionCommand[];\n outbox?: (Omit<StatewireClient.OutboxCommand, \"held\"> & {\n hold?: { meta?: unknown };\n })[];\n failed?: FailedCommand[];\n};\n\ntype LegacyRecord = { clientId: string; lanes: Record<string, LegacyLane> };\n\n// Formats 1 and 2 held one lane: it lifts under the primary protocol, failed\n// entries under their own.\nconst liftLegacy = (\n value: Record<string, unknown>,\n protocol: string,\n): LegacyRecord => {\n const { clientId, nextSeq, commands, verdicts, failed } = value;\n const lane = {\n nextSeq,\n commands: [],\n ...(verdicts !== undefined && { verdicts }),\n };\n if (!isClientId(clientId) || !isSessionLane(lane) || !Array.isArray(commands))\n return invalid();\n const legacyCommands = commands as (StatewireClient.SessionCommand & {\n protocol?: string;\n })[];\n if (!legacyCommands.every(isLegacyCommand)) return invalid();\n if (\n failed !== undefined &&\n (!Array.isArray(failed) || !failed.every(isFailedCommand))\n )\n return invalid();\n const lanes: Record<string, LegacyLane> = {\n [protocol]: {\n ...(lane as Record<string, unknown>),\n commands: legacyCommands\n .filter((command) => (command.protocol ?? protocol) === protocol)\n .map(({ protocol: _protocol, ...command }) => command),\n },\n };\n for (const entry of (failed ?? []) as FailedCommand[]) {\n const target = lanes[entry.protocol] ?? { nextSeq: 1, commands: [] };\n lanes[entry.protocol] = {\n ...target,\n failed: [...(target.failed ?? []), entry],\n };\n }\n return identify({ clientId, lanes } as never) as never;\n};\n\n// Format 5: holds are a flag with client-only localMeta; failed sends are\n// settled updates awaiting their onCommandChange ack.\nconst upgrade = (record: LegacyRecord): StatewireClient.SessionRecord => ({\n clientId: record.clientId,\n lanes: Object.fromEntries(\n Object.entries(record.lanes).map(([protocol, legacy]) => {\n const { outbox, failed, ...lane } = legacy;\n const inbox = [...(failed ?? [])]\n .sort((a, b) => a.ts - b.ts)\n .map((f): StatewireClient.StoredSettled => ({\n // Stable across re-migrations until the upgraded record is saved.\n id: `failed:${protocol}:${f.seq}:${f.ts}`,\n method: f.command,\n params: f.params,\n ...(f.meta !== undefined && { meta: f.meta }),\n ts: f.ts,\n failure: f.failure,\n }));\n return [\n protocol,\n {\n ...(lane as unknown as StatewireClient.SessionLane),\n ...(outbox?.length && {\n outbox: outbox.map(({ hold, ...command }) => ({\n ...command,\n ...(hold !== undefined && { held: true as const }),\n ...(hold?.meta !== undefined && { localMeta: hold.meta }),\n })),\n }),\n ...(inbox.length > 0 && { inbox }),\n },\n ];\n }),\n ),\n});\n\nconst isLegacyLane = (lane: unknown) =>\n isLaneWith(lane, isSessionCommand, true);\n\nconst normalize = (\n parsed: unknown,\n protocol: string,\n): StatewireClient.SessionRecord => {\n if (!isRecord(parsed)) return invalid();\n const {\n statewireStorageVersion: format,\n savedAt: _savedAt,\n ...value\n } = parsed;\n if (format === 5) return isSessionRecord(value) ? value : invalid();\n if (format === 4)\n return isRecordWith(value, isLegacyLane)\n ? upgrade(value as never)\n : invalid();\n if (format === 3)\n return isRecordWith(value, (lane) =>\n isLaneWith(lane, isLegacyCommand, true),\n )\n ? upgrade(identify(value) as never)\n : invalid();\n if (format !== undefined && format !== 1 && format !== 2)\n throw new Error(\"statewire: unsupported session storage version\");\n const { ctxNextSeq: _ctxNextSeq, context: _context, ...legacy } = value;\n return upgrade(liftLegacy(legacy, protocol));\n};\n\nexport const STORAGE_FORMAT = 5;\n\nconst parse = async (\n raw: string | null,\n codec: KeyValueSessionStorage.Codec | undefined,\n): Promise<unknown> =>\n raw === null\n ? null\n : JSON.parse(codec === undefined ? raw : await codec.decode(raw));\n\nconst decode = (\n raw: string | null,\n protocol: string,\n): StatewireClient.SessionRecord | null =>\n raw === null ? null : normalize(JSON.parse(raw), protocol);\n\nconst isUnsent = (lane: StatewireClient.SessionLane) =>\n lane.commands.length > 0 || (lane.outbox?.length ?? 0) > 0;\n\nconst entryCount = (record: StatewireClient.SessionRecord | null) =>\n record === null\n ? 0\n : Object.values(record.lanes).reduce(\n (sum, lane) => sum + lane.commands.length + (lane.outbox?.length ?? 0),\n 0,\n );\n\nconst noticesOf = (\n record: StatewireClient.SessionRecord,\n): Statewire.CommandUpdate[] =>\n Object.entries(record.lanes).flatMap(([protocol, lane]) =>\n (lane.inbox ?? []).map((n) => ({\n protocol,\n id: n.id,\n method: n.method,\n params: n.params as never[],\n ...(n.seq !== undefined && { seq: n.seq }),\n status: \"settled\" as const,\n ...(n.failure !== undefined && { failure: n.failure }),\n ...(n.localMeta !== undefined && { localMeta: n.localMeta }),\n })),\n );\n\nconst active = new WeakMap<\n KeyValueSessionStorage.Backend,\n Map<string, number>\n>();\n\n/** Same-runtime saves, per backend, for `subscribeOutboxes`. */\nconst savers = new WeakMap<\n KeyValueSessionStorage.Backend,\n Set<(key: string) => void>\n>();\n\nconst announce = (backend: KeyValueSessionStorage.Backend, key: string) => {\n for (const listener of savers.get(backend) ?? []) listener(key);\n};\n\ntype Candidate = {\n key: string;\n savedAt: number;\n record: StatewireClient.SessionRecord;\n};\n\nconst sweeping = new WeakMap<KeyValueSessionStorage.Backend, Promise<void>>();\n\n// Sweeps of one backend run one at a time.\nconst sweep = (\n options: KeyValueSessionStorage.Options,\n currentKey: string,\n quota = false,\n): Promise<void> => {\n const run = (sweeping.get(options.backend) ?? Promise.resolve())\n .catch(() => {})\n .then(() => sweepOnce(options, currentKey, quota));\n sweeping.set(options.backend, run);\n return run;\n};\n\n// Records with unsent commands are never evicted. Empty records go first;\n// records holding only failure notices go once past their age.\nconst sweepOnce = async (\n options: KeyValueSessionStorage.Options,\n currentKey: string,\n quota = false,\n) => {\n const { backend, retention, namespace = \"\", codec, onEvict } = options;\n if (!retention || !backend.getAllKeys) return;\n const candidate = async (raw: string | null) => {\n let value: unknown;\n try {\n value = await parse(raw, codec);\n } catch {\n return undefined;\n }\n if (value === null) return undefined;\n let record: StatewireClient.SessionRecord;\n try {\n record = normalize(value, DEFAULT_PROTOCOL);\n } catch {\n return undefined;\n }\n if (Object.values(record.lanes).some(isUnsent)) 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, record }\n : undefined;\n };\n const empty: Candidate[] = [];\n const noticed: Candidate[] = [];\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 found = await candidate(await backend.getItem(key));\n if (found === undefined) continue;\n (noticesOf(found.record).length > 0 ? noticed : empty).push({\n key,\n ...found,\n });\n }\n const remove = async (\n entry: Candidate,\n reason: KeyValueSessionStorage.EvictEvent[\"reason\"],\n ) => {\n const run = async () => {\n if (active.get(backend)?.has(entry.key)) return;\n const again = await candidate(await backend.getItem(entry.key));\n if (again?.savedAt !== entry.savedAt) return;\n if (active.get(backend)?.has(entry.key)) return;\n await backend.removeItem(entry.key);\n announce(backend, entry.key);\n onEvict?.({\n key: entry.key,\n reason,\n dropped: noticesOf(again.record),\n });\n };\n if (backend.withUnused) await backend.withUnused(entry.key, run);\n else await run();\n };\n const now = Date.now();\n const expired = (entry: Candidate) =>\n retention.maxAgeMs !== undefined &&\n now - entry.savedAt > retention.maxAgeMs;\n empty.sort((a, b) => b.savedAt - a.savedAt);\n let retained = 1;\n const quotaStart = Math.floor(empty.length / 2);\n for (const [index, entry] of empty.entries()) {\n const overflow =\n retention.maxEntries !== undefined && retained >= retention.maxEntries;\n if (expired(entry)) await remove(entry, \"expired\");\n else if (overflow) await remove(entry, \"overflow\");\n else if (quota && index >= quotaStart) await remove(entry, \"quota\");\n else retained++;\n }\n for (const entry of noticed)\n if (expired(entry)) await remove(entry, \"expired\");\n};\n\nconst isQuotaError = (error: unknown) =>\n error instanceof Error && error.name === \"QuotaExceededError\";\n\nconst quotaError = (limit: string) => {\n const error = new Error(`statewire: session ${limit} limit reached`);\n error.name = \"StatewireQuotaError\";\n return error;\n};\n\nconst otherBytes = async (\n backend: KeyValueSessionStorage.Backend,\n namespace: string,\n key: string,\n) => {\n let total = 0;\n for (const other of (await backend.getAllKeys?.()) ?? []) {\n if (other === key || !other.startsWith(namespace)) continue;\n total += (await backend.getItem(other))?.length ?? 0;\n }\n return total;\n};\n\nconst validLimit = (value: number | undefined, name: string) => {\n if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))\n throw new Error(`statewire: invalid session limit ${name}`);\n};\n\nconst useKeyValueSessionStorage = (\n options: KeyValueSessionStorage.Options,\n): StatewireClient.SessionStorage => {\n const {\n backend,\n key,\n namespace = \"\",\n protocol = DEFAULT_PROTOCOL,\n migrate,\n retention: suppliedRetention,\n limits: suppliedLimits,\n codec,\n onError,\n onEvict,\n } = options;\n const latest = useRef({ onError, onEvict });\n latest.current = { onError, onEvict };\n const reportError = useMemo(\n () => (error: unknown) => latest.current.onError?.(error),\n [],\n );\n const reportEvict = useMemo(\n () => (event: KeyValueSessionStorage.EvictEvent) =>\n latest.current.onEvict?.(event),\n [],\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 outboxEntries = suppliedLimits?.outboxEntries;\n const recordBytes = suppliedLimits?.recordBytes;\n const totalBytes = suppliedLimits?.totalBytes;\n validLimit(outboxEntries, \"outboxEntries\");\n validLimit(recordBytes, \"recordBytes\");\n validLimit(totalBytes, \"totalBytes\");\n if (totalBytes !== undefined && !backend.getAllKeys)\n throw new Error(\"statewire: a totalBytes limit requires getAllKeys\");\n const configured = useMemo(\n () => ({\n backend,\n key,\n namespace,\n onEvict: reportEvict,\n ...(codec === undefined ? {} : { codec }),\n ...(retention === undefined ? {} : { retention }),\n }),\n [backend, key, namespace, retention, codec, reportEvict],\n );\n const storageKey = namespace + key;\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 bytes: 0,\n others: undefined as number | undefined,\n ready: undefined as Promise<void> | undefined,\n queue: Promise.resolve(),\n pending: 0,\n failure: undefined as unknown,\n asynchronous:\n codec !== undefined || (typeof raw !== \"string\" && raw !== null),\n hydrated: false,\n };\n const hydrate = (record: StatewireClient.SessionRecord | null) => {\n state.record = record;\n state.hydrated = true;\n };\n if (state.asynchronous) {\n state.ready = Promise.resolve(raw)\n .then(async (value) => {\n state.bytes = value?.length ?? 0;\n const parsed = await parse(value, codec);\n hydrate(parsed === null ? null : normalize(parsed, protocol));\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.bytes = (raw as string | null)?.length ?? 0;\n hydrate(decode(raw as string | null, protocol));\n } catch (cause) {\n const error = wrapStorageError(cause);\n reportError(error);\n throw error;\n }\n }\n return state;\n }, [backend, storageKey, protocol, codec, 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 useEffect(() => {\n if (totalBytes === undefined) return;\n void otherBytes(backend, namespace, storageKey).then(\n (bytes) => {\n state.others = bytes;\n },\n () => {},\n );\n }, [state, backend, namespace, storageKey, totalBytes]);\n\n return useMemo(() => {\n const checkLimits = (\n record: StatewireClient.SessionRecord,\n bytes: number,\n ) => {\n // Limits gate writes that add an entry; settling and stamping always land.\n if (entryCount(record) <= entryCount(state.record)) return;\n if (outboxEntries !== undefined && entryCount(record) > outboxEntries)\n throw quotaError(\"outboxEntries\");\n if (recordBytes !== undefined && bytes > recordBytes)\n throw quotaError(\"recordBytes\");\n if (\n totalBytes !== undefined &&\n state.others !== undefined &&\n state.others + bytes > totalBytes\n )\n throw quotaError(\"totalBytes\");\n };\n const persist = async (encoded: string) => {\n const stored =\n codec === undefined ? encoded : await codec.encode(encoded);\n try {\n await backend.setItem(storageKey, stored);\n } catch (error) {\n if (!isQuotaError(error)) throw error;\n if (retention !== undefined) await sweep(configured, storageKey, true);\n await backend.setItem(storageKey, stored);\n }\n announce(backend, storageKey);\n };\n return {\n durable: true,\n ...(state.ready === undefined ? {} : { ready: state.ready }),\n read: async () => {\n if (state.failure !== undefined) throw state.failure;\n await state.ready;\n await state.queue.catch(() => {});\n try {\n const raw = await backend.getItem(storageKey);\n const parsed = await parse(raw, codec);\n state.bytes = raw?.length ?? 0;\n state.record = parsed === null ? null : normalize(parsed, protocol);\n return state.record;\n } catch (cause) {\n const error = wrapStorageError(cause);\n reportError(error);\n throw error;\n }\n },\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 state.record = decode(raw, protocol);\n return state.record;\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: STORAGE_FORMAT,\n savedAt: Date.now(),\n });\n checkLimits(record, encoded.length);\n if (!state.asynchronous && state.pending === 0) {\n try {\n backend.setItem(storageKey, encoded) as void;\n } catch (error) {\n if (!isQuotaError(error)) {\n state.failure = wrapStorageError(error);\n reportError(state.failure);\n throw state.failure;\n }\n // A write that adds an entry is refused; the rest retry after the sweep.\n if (entryCount(record) > entryCount(state.record)) {\n if (retention !== undefined)\n void sweep(configured, storageKey, true).catch(() => {});\n throw quotaError(\"backend\");\n }\n state.record = record;\n state.bytes = encoded.length;\n state.pending++;\n state.queue = persist(encoded).finally(() => {\n state.pending--;\n });\n state.queue.catch((cause: unknown) => {\n const failure = wrapStorageError(cause);\n if (state.failure !== failure) reportError(failure);\n state.failure = failure;\n });\n return;\n }\n state.record = record;\n state.bytes = encoded.length;\n announce(backend, storageKey);\n return;\n }\n state.record = record;\n state.bytes = encoded.length;\n state.pending++;\n state.queue = state.queue\n .then(() => state.ready)\n .then(() => persist(encoded))\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 onEvict: ({ reason, dropped }) =>\n reportEvict({ key: storageKey, reason, dropped }),\n ...(migrate === undefined ? {} : { migrate }),\n } satisfies StatewireClient.SessionStorage;\n }, [\n state,\n backend,\n storageKey,\n protocol,\n migrate,\n retention,\n configured,\n codec,\n outboxEntries,\n recordBytes,\n totalBytes,\n reportError,\n reportEvict,\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 /** Notifies of keys other runtimes wrote; returns unsubscribe. */\n subscribe?: (listener: (key: string) => void) => () => void;\n };\n /** Transforms the stored string, e.g. encryption or compression. */\n export type Codec = {\n encode: (plain: string) => Promise<string>;\n decode: (stored: string) => Promise<string>;\n };\n export type EvictEvent = StatewireClient.EvictEvent;\n export type Options = {\n backend: Backend;\n key: string;\n namespace?: string;\n /** The lane that sessions saved in a single-lane format lift into; defaults to the client's default protocol. */\n protocol?: string;\n migrate?: StatewireClient.SessionStorage[\"migrate\"];\n retention?: { maxAgeMs?: number; maxEntries?: number };\n /** Off by default; a write that adds an entry past one rejects `{ fate: \"aborted\", cause: \"quota\" }`. */\n limits?: {\n outboxEntries?: number;\n recordBytes?: number;\n totalBytes?: number;\n };\n codec?: Codec;\n onError?: (error: unknown) => void;\n /** Reports evicted records and dropped failure notices. */\n onEvict?: (event: EvictEvent) => void;\n };\n}\n\nconst pages = new Map<string, StatewireClient.SessionRecord | null>();\n\nconst useMemorySessionStorage = ({\n scope,\n}: MemorySessionStorage.Options): StatewireClient.SessionStorage =>\n useMemo(\n () => ({\n durable: false,\n load: () => pages.get(scope) ?? null,\n save: (record) => {\n pages.set(scope, record);\n },\n }),\n [scope],\n );\n\n/** Session storage in page memory, keyed by scope: the record and client identity survive remounts, not reloads. */\nexport const MemorySessionStorage = resource(useMemorySessionStorage);\n\nexport namespace MemorySessionStorage {\n export type Options = { scope: string };\n}\n\nconst entriesOf = (\n record: StatewireClient.SessionRecord,\n): StatewireClient.OutboxEntry[] =>\n Object.entries(record.lanes).flatMap(([protocol, lane]) => {\n const view = (\n c: StatewireClient.OutboxCommand | StatewireClient.SessionCommand,\n state: StatewireClient.OutboxState,\n ): StatewireClient.OutboxEntry => ({\n id: c.id,\n protocol,\n command: {\n method: c.method,\n params:\n c.meta === undefined\n ? c.params\n : (reviveValue(structuredClone(c.params), c.meta) as unknown[]),\n },\n ts: c.ts,\n state,\n localMeta: c.localMeta,\n durable: true,\n });\n return [\n ...[...lane.commands]\n .sort((a, b) => a.seq - b.seq)\n .map((c) => view(c, \"sending\")),\n ...(lane.outbox ?? []).map((c) => view(c, c.held ? \"held\" : \"queued\")),\n ];\n });\n\n/** Reads the outboxes of every session under `namespace`, mounted or not. */\nexport const readOutboxes = async (\n backend: KeyValueSessionStorage.Backend,\n options: ReadOutboxes.Options,\n): Promise<readonly ReadOutboxes.Outbox[]> => {\n if (!backend.getAllKeys)\n throw new Error(\"statewire: readOutboxes requires getAllKeys\");\n const { namespace, codec, protocol = DEFAULT_PROTOCOL } = options;\n if (!namespace)\n throw new Error(\"statewire: readOutboxes requires a namespace\");\n const result: ReadOutboxes.Outbox[] = [];\n for (const key of await backend.getAllKeys()) {\n if (!key.startsWith(namespace)) continue;\n const parsed = await parse(await backend.getItem(key), codec);\n if (parsed === null) continue;\n const entries = entriesOf(normalize(parsed, protocol));\n if (entries.length > 0) result.push({ key, entries });\n }\n return result;\n};\n\n/** Calls `listener` with `readOutboxes` now and after every save under `namespace`; returns unsubscribe. */\nexport const subscribeOutboxes = (\n backend: KeyValueSessionStorage.Backend,\n options: ReadOutboxes.Options,\n listener: (outboxes: readonly ReadOutboxes.Outbox[]) => void,\n onError: (error: unknown) => void,\n): (() => void) => {\n let closed = false;\n let reading = Promise.resolve();\n const refresh = () => {\n reading = reading.then(async () => {\n if (closed) return;\n try {\n const outboxes = await readOutboxes(backend, options);\n if (!closed) listener(outboxes);\n } catch (error) {\n onError(error);\n }\n });\n };\n const onKey = (key: string) => {\n if (key.startsWith(options.namespace)) refresh();\n };\n const local = savers.get(backend) ?? new Set();\n savers.set(backend, local);\n local.add(onKey);\n const remote = backend.subscribe?.(onKey);\n refresh();\n return () => {\n closed = true;\n local.delete(onKey);\n remote?.();\n };\n};\n\nexport namespace ReadOutboxes {\n export type Options = {\n namespace: string;\n codec?: KeyValueSessionStorage.Codec;\n /** The lane that single-lane formats lift into. */\n protocol?: string;\n };\n export type Outbox = {\n key: string;\n entries: readonly StatewireClient.OutboxEntry[];\n };\n}\nexport { IndexedDbSession, indexedDbBackend } from \"./indexedDb\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"storage.js","names":["useEffect","useMemo","useRef","randomId","wrapStorageError","reviveValue","resource","StatewireClient","Statewire","StatewireProtocol","DEFAULT_PROTOCOL","MIN_SERVED_WIRE_VERSION","WIRE_VERSION","negotiationHeaders","protocolToken","isRecord","value","Record","Array","isArray","isOptionalString","undefined","isPositiveSafeInteger","Number","isSafeInteger","isStatement","isProtocol","isProducer","isIdentified","isFinite","isLegacyCommand","isSessionCommand","SessionCommand","isOutboxCommand","OutboxCommand","isLegacyOutboxCommand","isStoredSettled","StoredSettled","isSendFailure","isSessionVerdict","SessionVerdict","SendFailure","FailedCommand","protocol","produced","command","params","meta","failure","ts","seq","isFailedCommand","isArrayOf","is","item","every","isMark","mark","isLaneWith","isCommand","legacy","SessionLane","isSessionLane","isClientId","length","isRecordWith","isLane","lane","SessionRecord","Object","keys","values","isSessionRecord","identify","record","lanes","fromEntries","entries","map","commands","wire","version","id","Date","now","test","wireVersion","Range","applicationProtocols","Offer","invalid","Error","isPrincipal","sessionNamespace","namespace","principal","encodeURIComponent","usePrincipal","initial","current","LegacyLane","outbox","Omit","hold","failed","LegacyRecord","clientId","liftLegacy","nextSeq","verdicts","legacyCommands","filter","_protocol","entry","target","upgrade","inbox","sort","a","b","f","method","held","const","localMeta","isLegacyLane","normalize","parsed","statewireStorageVersion","format","savedAt","_savedAt","owner","ctxNextSeq","_ctxNextSeq","context","_context","STORAGE_FORMAT","parse","raw","codec","KeyValueSessionStorage","Codec","Promise","JSON","decode","isUnsent","entryCount","reduce","sum","noticesOf","CommandUpdate","flatMap","n","status","active","WeakMap","Backend","Map","savers","Set","key","announce","backend","listener","get","Candidate","sweeping","sweep","options","Options","currentKey","quota","run","resolve","catch","then","sweepOnce","set","retention","onEvict","getAllKeys","candidate","some","empty","noticed","startsWith","has","found","getItem","push","remove","reason","EvictEvent","again","removeItem","dropped","withUnused","expired","maxAgeMs","retained","quotaStart","Math","floor","index","overflow","maxEntries","isQuotaError","error","name","quotaError","limit","otherBytes","total","other","validLimit","useKeyValueSessionStorage","SessionStorage","migrate","suppliedRetention","limits","suppliedLimits","onError","scope","latest","reportError","reportEvict","event","retain","outboxEntries","recordBytes","totalBytes","configured","storageKey","state","cause","bytes","others","ready","queue","pending","asynchronous","hydrated","hydrate","count","delete","checkLimits","persist","encoded","stored","encode","setItem","durable","read","load","save","stringify","finally","flush","action","subscribe","plain","pages","useMemorySessionStorage","t0","$","_c","t1","MemorySessionStorage","entriesOf","OutboxEntry","view","c","OutboxState","structuredClone","readOutboxes","ReadOutboxes","Outbox","result","subscribeOutboxes","outboxes","closed","reading","refresh","onKey","local","add","remote","IndexedDbSession","indexedDbBackend","_c","reviveValue"],"sources":["../src/storage.ts"],"sourcesContent":["import { useEffect, useMemo, useRef } from \"react\";\nimport { randomId } from \"./util/randomId\";\nimport { wrapStorageError } from \"./client/storageError\";\nimport { reviveValue } from \"./wire/meta\";\nimport { resource } from \"@assistant-ui/tap\";\nimport type { StatewireClient } from \"./statewire\";\nimport type { Statewire } from \"./types\";\nimport type { StatewireProtocol } from \"./wire/protocol\";\nimport {\n DEFAULT_PROTOCOL,\n MIN_SERVED_WIRE_VERSION,\n WIRE_VERSION,\n negotiationHeaders,\n protocolToken,\n} from \"./wire/protocol\";\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 isStatement = (value: unknown): value is Record<string, unknown> =>\n isRecord(value) &&\n isProtocol(value[\"protocol\"]) &&\n isProducer(value[\"produced\"]) &&\n typeof value[\"method\"] === \"string\" &&\n Array.isArray(value[\"params\"]);\n\nconst isIdentified = (value: Record<string, unknown>) =>\n typeof value[\"id\"] === \"string\" &&\n value[\"id\"] !== \"\" &&\n typeof value[\"ts\"] === \"number\" &&\n Number.isFinite(value[\"ts\"]);\n\nconst isLegacyCommand = (value: unknown) =>\n isStatement(value) && isPositiveSafeInteger(value[\"seq\"]);\n\nconst isSessionCommand = (\n value: unknown,\n): value is StatewireClient.SessionCommand =>\n isLegacyCommand(value) &&\n isIdentified(value as Record<string, unknown>) &&\n isOptionalString((value as Record<string, unknown>)[\"owner\"]);\n\nconst isOutboxCommand = (\n value: unknown,\n): value is StatewireClient.OutboxCommand =>\n isStatement(value) &&\n isIdentified(value) &&\n (value[\"held\"] === undefined || value[\"held\"] === true) &&\n (value[\"released\"] === undefined || value[\"released\"] === true) &&\n isOptionalString(value[\"by\"]);\n\n// Format 4 outbox entries carried the hold as `hold: { meta }`.\nconst isLegacyOutboxCommand = (value: unknown) =>\n isStatement(value) &&\n isIdentified(value) &&\n (value[\"hold\"] === undefined || isRecord(value[\"hold\"])) &&\n (value[\"released\"] === undefined || value[\"released\"] === true) &&\n isOptionalString(value[\"by\"]);\n\nconst isStoredSettled = (\n value: unknown,\n): value is StatewireClient.StoredSettled =>\n isRecord(value) &&\n isIdentified(value) &&\n typeof value[\"method\"] === \"string\" &&\n Array.isArray(value[\"params\"]) &&\n (value[\"seq\"] === undefined || isPositiveSafeInteger(value[\"seq\"])) &&\n (value[\"failure\"] === undefined\n ? isRecord(value[\"result\"]) && Array.isArray(value[\"result\"][\"value\"])\n : value[\"result\"] === undefined && isSendFailure(value[\"failure\"]));\n\nconst isSessionVerdict = (\n value: unknown,\n): value is StatewireClient.SessionVerdict =>\n isRecord(value) &&\n isPositiveSafeInteger(value[\"seq\"]) &&\n isOptionalString(value[\"id\"]) &&\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[\"cause\"] === \"removed\" ||\n value[\"cause\"] === \"quota\") &&\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\ntype FailedCommand = {\n protocol: string;\n produced?: StatewireClient.SessionCommand[\"produced\"];\n command: string;\n params: unknown[];\n meta?: unknown;\n failure: StatewireClient.SendFailure;\n ts: number;\n seq: number;\n};\n\nconst isFailedCommand = (value: unknown): value is 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 isArrayOf = (value: unknown, is: (item: unknown) => boolean) =>\n value === undefined || (Array.isArray(value) && value.every(is));\n\nconst isMark = (mark: unknown) =>\n isRecord(mark) &&\n typeof mark[\"id\"] === \"string\" &&\n typeof mark[\"by\"] === \"string\" &&\n (mark[\"failure\"] === undefined || isSendFailure(mark[\"failure\"]));\n\nconst isLaneWith = (\n value: unknown,\n isCommand: (command: unknown) => boolean,\n legacy = false,\n): value is StatewireClient.SessionLane =>\n isRecord(value) &&\n isPositiveSafeInteger(value[\"nextSeq\"]) &&\n Array.isArray(value[\"commands\"]) &&\n value[\"commands\"].every(isCommand) &&\n isArrayOf(\n value[\"outbox\"],\n legacy ? isLegacyOutboxCommand : isOutboxCommand,\n ) &&\n isArrayOf(value[\"removed\"], isMark) &&\n isArrayOf(value[\"verdicts\"], isSessionVerdict) &&\n (legacy\n ? isArrayOf(value[\"failed\"], isFailedCommand)\n : value[\"failed\"] === undefined &&\n isArrayOf(value[\"inbox\"], isStoredSettled));\n\nconst isSessionLane = (value: unknown): value is StatewireClient.SessionLane =>\n isLaneWith(value, isSessionCommand);\n\nconst isClientId = (value: unknown): value is string =>\n typeof value === \"string\" && value.length > 0;\n\nconst isRecordWith = (\n value: unknown,\n isLane: (lane: unknown) => boolean,\n): value is StatewireClient.SessionRecord =>\n isRecord(value) &&\n isClientId(value[\"clientId\"]) &&\n isRecord(value[\"lanes\"]) &&\n Object.keys(value[\"lanes\"]).every(isProtocol) &&\n Object.values(value[\"lanes\"]).every(isLane);\n\nconst isSessionRecord = (\n value: unknown,\n): value is StatewireClient.SessionRecord => isRecordWith(value, isSessionLane);\n\n// Formats before 4 stamped commands without an id or an enqueue time.\nconst identify = (\n record: StatewireClient.SessionRecord,\n): StatewireClient.SessionRecord => ({\n ...record,\n lanes: Object.fromEntries(\n Object.entries(record.lanes).map(([protocol, lane]) => [\n protocol,\n {\n ...lane,\n commands: lane.commands.map((command) => ({\n ...command,\n // The statement shape is unchanged across the gap-fill contract.\n ...(command.produced?.wire.version === MIN_SERVED_WIRE_VERSION && {\n produced: { ...command.produced, wire: { version: WIRE_VERSION } },\n }),\n id: randomId(),\n ts: Date.now(),\n })),\n },\n ]),\n ),\n});\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 invalid = (): never => {\n throw new Error(\"statewire: invalid stored session record\");\n};\n\nconst isPrincipal = (value: unknown): value is string =>\n typeof value === \"string\" && value !== \"\";\n\n/** The prefix of every record `KeyValueSessionStorage` keeps for `principal` under `namespace`. */\nexport const sessionNamespace = (namespace: string, principal: string) => {\n if (!isPrincipal(principal))\n throw new Error(\"statewire: storage requires a non-empty principal\");\n return `${namespace}${encodeURIComponent(principal)}:`;\n};\n\nconst usePrincipal = (principal: string) => {\n const initial = useRef(principal);\n if (initial.current !== principal)\n throw new Error(\n \"statewire: principal changed; remount the account's thread/session\",\n );\n};\n\ntype LegacyLane = Record<string, unknown> & {\n commands: StatewireClient.SessionCommand[];\n outbox?: (Omit<StatewireClient.OutboxCommand, \"held\"> & {\n hold?: { meta?: unknown };\n })[];\n failed?: FailedCommand[];\n};\n\ntype LegacyRecord = { clientId: string; lanes: Record<string, LegacyLane> };\n\n// Formats 1 and 2 held one lane: it lifts under the primary protocol, failed\n// entries under their own.\nconst liftLegacy = (\n value: Record<string, unknown>,\n protocol: string,\n): LegacyRecord => {\n const { clientId, nextSeq, commands, verdicts, failed } = value;\n const lane = {\n nextSeq,\n commands: [],\n ...(verdicts !== undefined && { verdicts }),\n };\n if (!isClientId(clientId) || !isSessionLane(lane) || !Array.isArray(commands))\n return invalid();\n const legacyCommands = commands as (StatewireClient.SessionCommand & {\n protocol?: string;\n })[];\n if (!legacyCommands.every(isLegacyCommand)) return invalid();\n if (\n failed !== undefined &&\n (!Array.isArray(failed) || !failed.every(isFailedCommand))\n )\n return invalid();\n const lanes: Record<string, LegacyLane> = {\n [protocol]: {\n ...(lane as Record<string, unknown>),\n commands: legacyCommands\n .filter((command) => (command.protocol ?? protocol) === protocol)\n .map(({ protocol: _protocol, ...command }) => command),\n },\n };\n for (const entry of (failed ?? []) as FailedCommand[]) {\n const target = lanes[entry.protocol] ?? { nextSeq: 1, commands: [] };\n lanes[entry.protocol] = {\n ...target,\n failed: [...(target.failed ?? []), entry],\n };\n }\n return identify({ clientId, lanes } as never) as never;\n};\n\n// Format 5: holds are a flag with client-only localMeta; failed sends are\n// settled updates awaiting their onCommandChange ack.\nconst upgrade = (record: LegacyRecord): StatewireClient.SessionRecord => ({\n clientId: record.clientId,\n lanes: Object.fromEntries(\n Object.entries(record.lanes).map(([protocol, legacy]) => {\n const { outbox, failed, ...lane } = legacy;\n const inbox = [...(failed ?? [])]\n .sort((a, b) => a.ts - b.ts)\n .map((f): StatewireClient.StoredSettled => ({\n // Stable across re-migrations until the upgraded record is saved.\n id: `failed:${protocol}:${f.seq}:${f.ts}`,\n method: f.command,\n params: f.params,\n ...(f.meta !== undefined && { meta: f.meta }),\n ts: f.ts,\n failure: f.failure,\n }));\n return [\n protocol,\n {\n ...(lane as unknown as StatewireClient.SessionLane),\n ...(outbox?.length && {\n outbox: outbox.map(({ hold, ...command }) => ({\n ...command,\n ...(hold !== undefined && { held: true as const }),\n ...(hold?.meta !== undefined && { localMeta: hold.meta }),\n })),\n }),\n ...(inbox.length > 0 && { inbox }),\n },\n ];\n }),\n ),\n});\n\nconst isLegacyLane = (lane: unknown) =>\n isLaneWith(lane, isSessionCommand, true);\n\n// Format 6 records name their principal; earlier formats sit under a key the\n// principal already scopes.\nconst normalize = (\n parsed: unknown,\n protocol: string,\n principal: string,\n): StatewireClient.SessionRecord => {\n if (!isRecord(parsed)) return invalid();\n const {\n statewireStorageVersion: format,\n savedAt: _savedAt,\n principal: owner,\n ...value\n } = parsed;\n if (format === 6) {\n if (!isPrincipal(owner) || !isSessionRecord(value)) return invalid();\n if (owner !== principal)\n throw new Error(\"statewire: stored session belongs to another principal\");\n return value;\n }\n if (owner !== undefined) return invalid();\n if (format === 5) return isSessionRecord(value) ? value : invalid();\n if (format === 4)\n return isRecordWith(value, isLegacyLane)\n ? upgrade(value as never)\n : invalid();\n if (format === 3)\n return isRecordWith(value, (lane) =>\n isLaneWith(lane, isLegacyCommand, true),\n )\n ? upgrade(identify(value) as never)\n : invalid();\n if (format !== undefined && format !== 1 && format !== 2)\n throw new Error(\"statewire: unsupported session storage version\");\n const { ctxNextSeq: _ctxNextSeq, context: _context, ...legacy } = value;\n return upgrade(liftLegacy(legacy, protocol));\n};\n\nexport const STORAGE_FORMAT = 6;\n\nconst parse = async (\n raw: string | null,\n codec: KeyValueSessionStorage.Codec | undefined,\n): Promise<unknown> =>\n raw === null\n ? null\n : JSON.parse(codec === undefined ? raw : await codec.decode(raw));\n\nconst decode = (\n raw: string | null,\n protocol: string,\n principal: string,\n): StatewireClient.SessionRecord | null =>\n raw === null ? null : normalize(JSON.parse(raw), protocol, principal);\n\nconst isUnsent = (lane: StatewireClient.SessionLane) =>\n lane.commands.length > 0 || (lane.outbox?.length ?? 0) > 0;\n\nconst entryCount = (record: StatewireClient.SessionRecord | null) =>\n record === null\n ? 0\n : Object.values(record.lanes).reduce(\n (sum, lane) => sum + lane.commands.length + (lane.outbox?.length ?? 0),\n 0,\n );\n\nconst noticesOf = (\n record: StatewireClient.SessionRecord,\n): Statewire.CommandUpdate[] =>\n Object.entries(record.lanes).flatMap(([protocol, lane]) =>\n (lane.inbox ?? []).map((n) => ({\n protocol,\n id: n.id,\n method: n.method,\n params: n.params as never[],\n ...(n.seq !== undefined && { seq: n.seq }),\n status: \"settled\" as const,\n ...(n.failure !== undefined && { failure: n.failure }),\n ...(n.localMeta !== undefined && { localMeta: n.localMeta }),\n })),\n );\n\nconst active = new WeakMap<\n KeyValueSessionStorage.Backend,\n Map<string, number>\n>();\n\n/** Same-runtime saves, per backend, for `subscribeOutboxes`. */\nconst savers = new WeakMap<\n KeyValueSessionStorage.Backend,\n Set<(key: string) => void>\n>();\n\nconst announce = (backend: KeyValueSessionStorage.Backend, key: string) => {\n for (const listener of savers.get(backend) ?? []) listener(key);\n};\n\ntype Candidate = {\n key: string;\n savedAt: number;\n record: StatewireClient.SessionRecord;\n};\n\nconst sweeping = new WeakMap<KeyValueSessionStorage.Backend, Promise<void>>();\n\n// Sweeps of one backend run one at a time.\nconst sweep = (\n options: KeyValueSessionStorage.Options,\n currentKey: string,\n quota = false,\n): Promise<void> => {\n const run = (sweeping.get(options.backend) ?? Promise.resolve())\n .catch(() => {})\n .then(() => sweepOnce(options, currentKey, quota));\n sweeping.set(options.backend, run);\n return run;\n};\n\n// Records with unsent commands are never evicted. Empty records go first;\n// records holding only failure notices go once past their age.\nconst sweepOnce = async (\n options: KeyValueSessionStorage.Options,\n currentKey: string,\n quota = false,\n) => {\n const { backend, retention, codec, onEvict, principal } = options;\n if (!retention || !backend.getAllKeys) return;\n const namespace = sessionNamespace(options.namespace ?? \"\", principal);\n const candidate = async (raw: string | null) => {\n let value: unknown;\n try {\n value = await parse(raw, codec);\n } catch {\n return undefined;\n }\n if (value === null) return undefined;\n let record: StatewireClient.SessionRecord;\n try {\n record = normalize(value, DEFAULT_PROTOCOL, principal);\n } catch {\n return undefined;\n }\n if (Object.values(record.lanes).some(isUnsent)) 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, record }\n : undefined;\n };\n const empty: Candidate[] = [];\n const noticed: Candidate[] = [];\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 found = await candidate(await backend.getItem(key));\n if (found === undefined) continue;\n (noticesOf(found.record).length > 0 ? noticed : empty).push({\n key,\n ...found,\n });\n }\n const remove = async (\n entry: Candidate,\n reason: KeyValueSessionStorage.EvictEvent[\"reason\"],\n ) => {\n const run = async () => {\n if (active.get(backend)?.has(entry.key)) return;\n const again = await candidate(await backend.getItem(entry.key));\n if (again?.savedAt !== entry.savedAt) return;\n if (active.get(backend)?.has(entry.key)) return;\n await backend.removeItem(entry.key);\n announce(backend, entry.key);\n onEvict?.({\n key: entry.key,\n reason,\n dropped: noticesOf(again.record),\n });\n };\n if (backend.withUnused) await backend.withUnused(entry.key, run);\n else await run();\n };\n const now = Date.now();\n const expired = (entry: Candidate) =>\n retention.maxAgeMs !== undefined &&\n now - entry.savedAt > retention.maxAgeMs;\n empty.sort((a, b) => b.savedAt - a.savedAt);\n let retained = 1;\n const quotaStart = Math.floor(empty.length / 2);\n for (const [index, entry] of empty.entries()) {\n const overflow =\n retention.maxEntries !== undefined && retained >= retention.maxEntries;\n if (expired(entry)) await remove(entry, \"expired\");\n else if (overflow) await remove(entry, \"overflow\");\n else if (quota && index >= quotaStart) await remove(entry, \"quota\");\n else retained++;\n }\n for (const entry of noticed)\n if (expired(entry)) await remove(entry, \"expired\");\n};\n\nconst isQuotaError = (error: unknown) =>\n error instanceof Error && error.name === \"QuotaExceededError\";\n\nconst quotaError = (limit: string) => {\n const error = new Error(`statewire: session ${limit} limit reached`);\n error.name = \"StatewireQuotaError\";\n return error;\n};\n\nconst otherBytes = async (\n backend: KeyValueSessionStorage.Backend,\n namespace: string,\n key: string,\n) => {\n let total = 0;\n for (const other of (await backend.getAllKeys?.()) ?? []) {\n if (other === key || !other.startsWith(namespace)) continue;\n total += (await backend.getItem(other))?.length ?? 0;\n }\n return total;\n};\n\nconst validLimit = (value: number | undefined, name: string) => {\n if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))\n throw new Error(`statewire: invalid session limit ${name}`);\n};\n\nconst useKeyValueSessionStorage = (\n options: KeyValueSessionStorage.Options,\n): StatewireClient.SessionStorage => {\n const {\n backend,\n key,\n namespace = \"\",\n principal,\n protocol = DEFAULT_PROTOCOL,\n migrate,\n retention: suppliedRetention,\n limits: suppliedLimits,\n codec,\n onError,\n onEvict,\n } = options;\n const scope = sessionNamespace(namespace, principal);\n usePrincipal(principal);\n const latest = useRef({ onError, onEvict });\n latest.current = { onError, onEvict };\n const reportError = useMemo(\n () => (error: unknown) => latest.current.onError?.(error),\n [],\n );\n const reportEvict = useMemo(\n () => (event: KeyValueSessionStorage.EvictEvent) =>\n latest.current.onEvict?.(event),\n [],\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 outboxEntries = suppliedLimits?.outboxEntries;\n const recordBytes = suppliedLimits?.recordBytes;\n const totalBytes = suppliedLimits?.totalBytes;\n validLimit(outboxEntries, \"outboxEntries\");\n validLimit(recordBytes, \"recordBytes\");\n validLimit(totalBytes, \"totalBytes\");\n if (totalBytes !== undefined && !backend.getAllKeys)\n throw new Error(\"statewire: a totalBytes limit requires getAllKeys\");\n const configured = useMemo(\n () => ({\n backend,\n key,\n namespace,\n principal,\n onEvict: reportEvict,\n ...(codec === undefined ? {} : { codec }),\n ...(retention === undefined ? {} : { retention }),\n }),\n [backend, key, namespace, principal, retention, codec, reportEvict],\n );\n const storageKey = scope + key;\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 bytes: 0,\n others: undefined as number | undefined,\n ready: undefined as Promise<void> | undefined,\n queue: Promise.resolve(),\n pending: 0,\n failure: undefined as unknown,\n asynchronous:\n codec !== undefined || (typeof raw !== \"string\" && raw !== null),\n hydrated: false,\n };\n const hydrate = (record: StatewireClient.SessionRecord | null) => {\n state.record = record;\n state.hydrated = true;\n };\n if (state.asynchronous) {\n state.ready = Promise.resolve(raw)\n .then(async (value) => {\n state.bytes = value?.length ?? 0;\n const parsed = await parse(value, codec);\n hydrate(\n parsed === null ? null : normalize(parsed, protocol, principal),\n );\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.bytes = (raw as string | null)?.length ?? 0;\n hydrate(decode(raw as string | null, protocol, principal));\n } catch (cause) {\n const error = wrapStorageError(cause);\n reportError(error);\n throw error;\n }\n }\n return state;\n }, [backend, storageKey, protocol, principal, codec, 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 useEffect(() => {\n if (totalBytes === undefined) return;\n void otherBytes(backend, scope, storageKey).then(\n (bytes) => {\n state.others = bytes;\n },\n () => {},\n );\n }, [state, backend, scope, storageKey, totalBytes]);\n\n return useMemo(() => {\n const checkLimits = (\n record: StatewireClient.SessionRecord,\n bytes: number,\n ) => {\n // Limits gate writes that add an entry; settling and stamping always land.\n if (entryCount(record) <= entryCount(state.record)) return;\n if (outboxEntries !== undefined && entryCount(record) > outboxEntries)\n throw quotaError(\"outboxEntries\");\n if (recordBytes !== undefined && bytes > recordBytes)\n throw quotaError(\"recordBytes\");\n if (\n totalBytes !== undefined &&\n state.others !== undefined &&\n state.others + bytes > totalBytes\n )\n throw quotaError(\"totalBytes\");\n };\n const persist = async (encoded: string) => {\n const stored =\n codec === undefined ? encoded : await codec.encode(encoded);\n try {\n await backend.setItem(storageKey, stored);\n } catch (error) {\n if (!isQuotaError(error)) throw error;\n if (retention !== undefined) await sweep(configured, storageKey, true);\n await backend.setItem(storageKey, stored);\n }\n announce(backend, storageKey);\n };\n return {\n durable: true,\n ...(state.ready === undefined ? {} : { ready: state.ready }),\n read: async () => {\n if (state.failure !== undefined) throw state.failure;\n await state.ready;\n await state.queue.catch(() => {});\n try {\n const raw = await backend.getItem(storageKey);\n const parsed = await parse(raw, codec);\n state.bytes = raw?.length ?? 0;\n state.record =\n parsed === null ? null : normalize(parsed, protocol, principal);\n return state.record;\n } catch (cause) {\n const error = wrapStorageError(cause);\n reportError(error);\n throw error;\n }\n },\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 state.record = decode(raw, protocol, principal);\n return state.record;\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 principal,\n statewireStorageVersion: STORAGE_FORMAT,\n savedAt: Date.now(),\n });\n checkLimits(record, encoded.length);\n if (!state.asynchronous && state.pending === 0) {\n try {\n backend.setItem(storageKey, encoded) as void;\n } catch (error) {\n if (!isQuotaError(error)) {\n state.failure = wrapStorageError(error);\n reportError(state.failure);\n throw state.failure;\n }\n // A write that adds an entry is refused; the rest retry after the sweep.\n if (entryCount(record) > entryCount(state.record)) {\n if (retention !== undefined)\n void sweep(configured, storageKey, true).catch(() => {});\n throw quotaError(\"backend\");\n }\n state.record = record;\n state.bytes = encoded.length;\n state.pending++;\n state.queue = persist(encoded).finally(() => {\n state.pending--;\n });\n state.queue.catch((cause: unknown) => {\n const failure = wrapStorageError(cause);\n if (state.failure !== failure) reportError(failure);\n state.failure = failure;\n });\n return;\n }\n state.record = record;\n state.bytes = encoded.length;\n announce(backend, storageKey);\n return;\n }\n state.record = record;\n state.bytes = encoded.length;\n state.pending++;\n state.queue = state.queue\n .then(() => state.ready)\n .then(() => persist(encoded))\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 onEvict: ({ reason, dropped }) =>\n reportEvict({ key: storageKey, reason, dropped }),\n ...(migrate === undefined ? {} : { migrate }),\n } satisfies StatewireClient.SessionStorage;\n }, [\n state,\n backend,\n storageKey,\n protocol,\n principal,\n migrate,\n retention,\n configured,\n codec,\n outboxEntries,\n recordBytes,\n totalBytes,\n reportError,\n reportEvict,\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 /** Notifies of keys other runtimes wrote; returns unsubscribe. */\n subscribe?: (listener: (key: string) => void) => () => void;\n };\n /** Transforms the stored string, e.g. encryption or compression. */\n export type Codec = {\n encode: (plain: string) => Promise<string>;\n decode: (stored: string) => Promise<string>;\n };\n export type EvictEvent = StatewireClient.EvictEvent;\n export type Options = {\n backend: Backend;\n key: string;\n namespace?: string;\n /** The account whose session this storage holds; scopes the stored key, tags the record. */\n principal: string;\n /** The lane that sessions saved in a single-lane format lift into; defaults to the client's default protocol. */\n protocol?: string;\n migrate?: StatewireClient.SessionStorage[\"migrate\"];\n retention?: { maxAgeMs?: number; maxEntries?: number };\n /** Off by default; a write that adds an entry past one rejects `{ fate: \"aborted\", cause: \"quota\" }`. */\n limits?: {\n outboxEntries?: number;\n recordBytes?: number;\n totalBytes?: number;\n };\n codec?: Codec;\n onError?: (error: unknown) => void;\n /** Reports evicted records and dropped failure notices. */\n onEvict?: (event: EvictEvent) => void;\n };\n}\n\nconst pages = new Map<string, StatewireClient.SessionRecord | null>();\n\nconst useMemorySessionStorage = ({\n principal,\n scope,\n}: MemorySessionStorage.Options): StatewireClient.SessionStorage => {\n const key = sessionNamespace(\"\", principal) + scope;\n usePrincipal(principal);\n return useMemo(\n () => ({\n durable: false,\n load: () => pages.get(key) ?? null,\n save: (record) => {\n pages.set(key, record);\n },\n }),\n [key],\n );\n};\n\n/** Session storage in page memory, keyed by principal and scope: the record and client identity survive remounts, not reloads. */\nexport const MemorySessionStorage = resource(useMemorySessionStorage);\n\nexport namespace MemorySessionStorage {\n export type Options = {\n /** The account whose session this storage holds. */\n principal: string;\n scope: string;\n };\n}\n\nconst entriesOf = (\n record: StatewireClient.SessionRecord,\n): StatewireClient.OutboxEntry[] =>\n Object.entries(record.lanes).flatMap(([protocol, lane]) => {\n const view = (\n c: StatewireClient.OutboxCommand | StatewireClient.SessionCommand,\n state: StatewireClient.OutboxState,\n ): StatewireClient.OutboxEntry => ({\n id: c.id,\n protocol,\n command: {\n method: c.method,\n params:\n c.meta === undefined\n ? c.params\n : (reviveValue(structuredClone(c.params), c.meta) as unknown[]),\n },\n ts: c.ts,\n state,\n localMeta: c.localMeta,\n durable: true,\n });\n return [\n ...[...lane.commands]\n .sort((a, b) => a.seq - b.seq)\n .map((c) => view(c, \"sending\")),\n ...(lane.outbox ?? []).map((c) => view(c, c.held ? \"held\" : \"queued\")),\n ];\n });\n\n/** Reads the outboxes of every session `principal` keeps under `namespace`, mounted or not. */\nexport const readOutboxes = async (\n backend: KeyValueSessionStorage.Backend,\n options: ReadOutboxes.Options,\n): Promise<readonly ReadOutboxes.Outbox[]> => {\n if (!backend.getAllKeys)\n throw new Error(\"statewire: readOutboxes requires getAllKeys\");\n const { namespace, principal, codec, protocol = DEFAULT_PROTOCOL } = options;\n if (!namespace)\n throw new Error(\"statewire: readOutboxes requires a namespace\");\n const scope = sessionNamespace(namespace, principal);\n const result: ReadOutboxes.Outbox[] = [];\n for (const key of await backend.getAllKeys()) {\n if (!key.startsWith(scope)) continue;\n const parsed = await parse(await backend.getItem(key), codec);\n if (parsed === null) continue;\n const entries = entriesOf(normalize(parsed, protocol, principal));\n if (entries.length > 0) result.push({ key, entries });\n }\n return result;\n};\n\n/** Calls `listener` with `readOutboxes` now and after every save to `principal`'s sessions under `namespace`; returns unsubscribe. */\nexport const subscribeOutboxes = (\n backend: KeyValueSessionStorage.Backend,\n options: ReadOutboxes.Options,\n listener: (outboxes: readonly ReadOutboxes.Outbox[]) => void,\n onError: (error: unknown) => void,\n): (() => void) => {\n const scope = sessionNamespace(options.namespace, options.principal);\n let closed = false;\n let reading = Promise.resolve();\n const refresh = () => {\n reading = reading.then(async () => {\n if (closed) return;\n try {\n const outboxes = await readOutboxes(backend, options);\n if (!closed) listener(outboxes);\n } catch (error) {\n onError(error);\n }\n });\n };\n const onKey = (key: string) => {\n if (key.startsWith(scope)) refresh();\n };\n const local = savers.get(backend) ?? new Set();\n savers.set(backend, local);\n local.add(onKey);\n const remote = backend.subscribe?.(onKey);\n refresh();\n return () => {\n closed = true;\n local.delete(onKey);\n remote?.();\n };\n};\n\nexport namespace ReadOutboxes {\n export type Options = {\n namespace: string;\n /** The account whose sessions to read. */\n principal: string;\n codec?: KeyValueSessionStorage.Codec;\n /** The lane that single-lane formats lift into. */\n protocol?: string;\n };\n export type Outbox = {\n key: string;\n entries: readonly StatewireClient.OutboxEntry[];\n };\n}\nexport { IndexedDbSession, indexedDbBackend } from \"./indexedDb\";\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statewire",
3
- "version": "0.24.3",
3
+ "version": "0.25.0",
4
4
  "description": "State over the wire: snapshot-first delta streaming and command submission for any state and any command",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/indexedDb.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useMemo, useState } from "react";
2
2
  import { resource, useResource } from "@assistant-ui/tap";
3
- import { KeyValueSessionStorage } from "./storage";
3
+ import { KeyValueSessionStorage, sessionNamespace } from "./storage";
4
4
  import type { StatewireClient } from "./statewire";
5
5
  import { randomId } from "./util/randomId";
6
6
 
@@ -107,6 +107,7 @@ const useIndexedDbSession = ({
107
107
  database = "statewire",
108
108
  key,
109
109
  namespace = "statewire-session:",
110
+ principal,
110
111
  ...options
111
112
  }: IndexedDbSession.Options): StatewireClient.SessionStorage => {
112
113
  if (typeof navigator === "undefined" || navigator.locks === undefined)
@@ -114,7 +115,7 @@ const useIndexedDbSession = ({
114
115
  "statewire: IndexedDbSession requires the Web Locks API (navigator.locks)",
115
116
  );
116
117
  const backend = indexedDbBackend(database);
117
- const storageKey = namespace + key;
118
+ const storageKey = sessionNamespace(namespace, principal) + key;
118
119
  const [tabId] = useState(randomId);
119
120
  useEffect(() => holdLock(`statewire-tab:${tabId}`), [tabId]);
120
121
  useEffect(
@@ -122,7 +123,7 @@ const useIndexedDbSession = ({
122
123
  [database, storageKey],
123
124
  );
124
125
  const storage = useResource(
125
- KeyValueSessionStorage({ ...options, backend, key, namespace }),
126
+ KeyValueSessionStorage({ ...options, backend, key, namespace, principal }),
126
127
  );
127
128
  return useMemo(
128
129
  (): StatewireClient.SessionStorage => ({
@@ -151,7 +152,7 @@ const useIndexedDbSession = ({
151
152
  );
152
153
  };
153
154
 
154
- /** Shared session storage on IndexedDB: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */
155
+ /** Shared session storage on IndexedDB, keyed per principal and key: sharers serialize through Web Locks and hear each other over a BroadcastChannel. */
155
156
  export const IndexedDbSession = resource(useIndexedDbSession);
156
157
 
157
158
  export namespace IndexedDbSession {
package/src/storage.ts CHANGED
@@ -245,6 +245,24 @@ const invalid = (): never => {
245
245
  throw new Error("statewire: invalid stored session record");
246
246
  };
247
247
 
248
+ const isPrincipal = (value: unknown): value is string =>
249
+ typeof value === "string" && value !== "";
250
+
251
+ /** The prefix of every record `KeyValueSessionStorage` keeps for `principal` under `namespace`. */
252
+ export const sessionNamespace = (namespace: string, principal: string) => {
253
+ if (!isPrincipal(principal))
254
+ throw new Error("statewire: storage requires a non-empty principal");
255
+ return `${namespace}${encodeURIComponent(principal)}:`;
256
+ };
257
+
258
+ const usePrincipal = (principal: string) => {
259
+ const initial = useRef(principal);
260
+ if (initial.current !== principal)
261
+ throw new Error(
262
+ "statewire: principal changed; remount the account's thread/session",
263
+ );
264
+ };
265
+
248
266
  type LegacyLane = Record<string, unknown> & {
249
267
  commands: StatewireClient.SessionCommand[];
250
268
  outbox?: (Omit<StatewireClient.OutboxCommand, "held"> & {
@@ -335,16 +353,27 @@ const upgrade = (record: LegacyRecord): StatewireClient.SessionRecord => ({
335
353
  const isLegacyLane = (lane: unknown) =>
336
354
  isLaneWith(lane, isSessionCommand, true);
337
355
 
356
+ // Format 6 records name their principal; earlier formats sit under a key the
357
+ // principal already scopes.
338
358
  const normalize = (
339
359
  parsed: unknown,
340
360
  protocol: string,
361
+ principal: string,
341
362
  ): StatewireClient.SessionRecord => {
342
363
  if (!isRecord(parsed)) return invalid();
343
364
  const {
344
365
  statewireStorageVersion: format,
345
366
  savedAt: _savedAt,
367
+ principal: owner,
346
368
  ...value
347
369
  } = parsed;
370
+ if (format === 6) {
371
+ if (!isPrincipal(owner) || !isSessionRecord(value)) return invalid();
372
+ if (owner !== principal)
373
+ throw new Error("statewire: stored session belongs to another principal");
374
+ return value;
375
+ }
376
+ if (owner !== undefined) return invalid();
348
377
  if (format === 5) return isSessionRecord(value) ? value : invalid();
349
378
  if (format === 4)
350
379
  return isRecordWith(value, isLegacyLane)
@@ -362,7 +391,7 @@ const normalize = (
362
391
  return upgrade(liftLegacy(legacy, protocol));
363
392
  };
364
393
 
365
- export const STORAGE_FORMAT = 5;
394
+ export const STORAGE_FORMAT = 6;
366
395
 
367
396
  const parse = async (
368
397
  raw: string | null,
@@ -375,8 +404,9 @@ const parse = async (
375
404
  const decode = (
376
405
  raw: string | null,
377
406
  protocol: string,
407
+ principal: string,
378
408
  ): StatewireClient.SessionRecord | null =>
379
- raw === null ? null : normalize(JSON.parse(raw), protocol);
409
+ raw === null ? null : normalize(JSON.parse(raw), protocol, principal);
380
410
 
381
411
  const isUnsent = (lane: StatewireClient.SessionLane) =>
382
412
  lane.commands.length > 0 || (lane.outbox?.length ?? 0) > 0;
@@ -448,8 +478,9 @@ const sweepOnce = async (
448
478
  currentKey: string,
449
479
  quota = false,
450
480
  ) => {
451
- const { backend, retention, namespace = "", codec, onEvict } = options;
481
+ const { backend, retention, codec, onEvict, principal } = options;
452
482
  if (!retention || !backend.getAllKeys) return;
483
+ const namespace = sessionNamespace(options.namespace ?? "", principal);
453
484
  const candidate = async (raw: string | null) => {
454
485
  let value: unknown;
455
486
  try {
@@ -460,7 +491,7 @@ const sweepOnce = async (
460
491
  if (value === null) return undefined;
461
492
  let record: StatewireClient.SessionRecord;
462
493
  try {
463
- record = normalize(value, DEFAULT_PROTOCOL);
494
+ record = normalize(value, DEFAULT_PROTOCOL, principal);
464
495
  } catch {
465
496
  return undefined;
466
497
  }
@@ -561,6 +592,7 @@ const useKeyValueSessionStorage = (
561
592
  backend,
562
593
  key,
563
594
  namespace = "",
595
+ principal,
564
596
  protocol = DEFAULT_PROTOCOL,
565
597
  migrate,
566
598
  retention: suppliedRetention,
@@ -569,6 +601,8 @@ const useKeyValueSessionStorage = (
569
601
  onError,
570
602
  onEvict,
571
603
  } = options;
604
+ const scope = sessionNamespace(namespace, principal);
605
+ usePrincipal(principal);
572
606
  const latest = useRef({ onError, onEvict });
573
607
  latest.current = { onError, onEvict };
574
608
  const reportError = useMemo(
@@ -606,13 +640,14 @@ const useKeyValueSessionStorage = (
606
640
  backend,
607
641
  key,
608
642
  namespace,
643
+ principal,
609
644
  onEvict: reportEvict,
610
645
  ...(codec === undefined ? {} : { codec }),
611
646
  ...(retention === undefined ? {} : { retention }),
612
647
  }),
613
- [backend, key, namespace, retention, codec, reportEvict],
648
+ [backend, key, namespace, principal, retention, codec, reportEvict],
614
649
  );
615
- const storageKey = namespace + key;
650
+ const storageKey = scope + key;
616
651
  if (retention !== undefined) {
617
652
  if (!namespace)
618
653
  throw new Error("statewire: session retention requires a namespace");
@@ -659,7 +694,9 @@ const useKeyValueSessionStorage = (
659
694
  .then(async (value) => {
660
695
  state.bytes = value?.length ?? 0;
661
696
  const parsed = await parse(value, codec);
662
- hydrate(parsed === null ? null : normalize(parsed, protocol));
697
+ hydrate(
698
+ parsed === null ? null : normalize(parsed, protocol, principal),
699
+ );
663
700
  })
664
701
  .catch((cause: unknown) => {
665
702
  const error = wrapStorageError(cause);
@@ -671,7 +708,7 @@ const useKeyValueSessionStorage = (
671
708
  } else {
672
709
  try {
673
710
  state.bytes = (raw as string | null)?.length ?? 0;
674
- hydrate(decode(raw as string | null, protocol));
711
+ hydrate(decode(raw as string | null, protocol, principal));
675
712
  } catch (cause) {
676
713
  const error = wrapStorageError(cause);
677
714
  reportError(error);
@@ -679,7 +716,7 @@ const useKeyValueSessionStorage = (
679
716
  }
680
717
  }
681
718
  return state;
682
- }, [backend, storageKey, protocol, codec, reportError]);
719
+ }, [backend, storageKey, protocol, principal, codec, reportError]);
683
720
 
684
721
  useEffect(() => {
685
722
  const entries = active.get(backend) ?? new Map<string, number>();
@@ -703,13 +740,13 @@ const useKeyValueSessionStorage = (
703
740
 
704
741
  useEffect(() => {
705
742
  if (totalBytes === undefined) return;
706
- void otherBytes(backend, namespace, storageKey).then(
743
+ void otherBytes(backend, scope, storageKey).then(
707
744
  (bytes) => {
708
745
  state.others = bytes;
709
746
  },
710
747
  () => {},
711
748
  );
712
- }, [state, backend, namespace, storageKey, totalBytes]);
749
+ }, [state, backend, scope, storageKey, totalBytes]);
713
750
 
714
751
  return useMemo(() => {
715
752
  const checkLimits = (
@@ -752,7 +789,8 @@ const useKeyValueSessionStorage = (
752
789
  const raw = await backend.getItem(storageKey);
753
790
  const parsed = await parse(raw, codec);
754
791
  state.bytes = raw?.length ?? 0;
755
- state.record = parsed === null ? null : normalize(parsed, protocol);
792
+ state.record =
793
+ parsed === null ? null : normalize(parsed, protocol, principal);
756
794
  return state.record;
757
795
  } catch (cause) {
758
796
  const error = wrapStorageError(cause);
@@ -780,7 +818,7 @@ const useKeyValueSessionStorage = (
780
818
  "statewire: storage backend changed from synchronous to asynchronous",
781
819
  );
782
820
  try {
783
- state.record = decode(raw, protocol);
821
+ state.record = decode(raw, protocol, principal);
784
822
  return state.record;
785
823
  } catch (cause) {
786
824
  const error = wrapStorageError(cause);
@@ -798,6 +836,7 @@ const useKeyValueSessionStorage = (
798
836
  throw new Error("statewire: invalid session record");
799
837
  const encoded = JSON.stringify({
800
838
  ...record,
839
+ principal,
801
840
  statewireStorageVersion: STORAGE_FORMAT,
802
841
  savedAt: Date.now(),
803
842
  });
@@ -873,6 +912,7 @@ const useKeyValueSessionStorage = (
873
912
  backend,
874
913
  storageKey,
875
914
  protocol,
915
+ principal,
876
916
  migrate,
877
917
  retention,
878
918
  configured,
@@ -909,6 +949,8 @@ export namespace KeyValueSessionStorage {
909
949
  backend: Backend;
910
950
  key: string;
911
951
  namespace?: string;
952
+ /** The account whose session this storage holds; scopes the stored key, tags the record. */
953
+ principal: string;
912
954
  /** The lane that sessions saved in a single-lane format lift into; defaults to the client's default protocol. */
913
955
  protocol?: string;
914
956
  migrate?: StatewireClient.SessionStorage["migrate"];
@@ -929,24 +971,32 @@ export namespace KeyValueSessionStorage {
929
971
  const pages = new Map<string, StatewireClient.SessionRecord | null>();
930
972
 
931
973
  const useMemorySessionStorage = ({
974
+ principal,
932
975
  scope,
933
- }: MemorySessionStorage.Options): StatewireClient.SessionStorage =>
934
- useMemo(
976
+ }: MemorySessionStorage.Options): StatewireClient.SessionStorage => {
977
+ const key = sessionNamespace("", principal) + scope;
978
+ usePrincipal(principal);
979
+ return useMemo(
935
980
  () => ({
936
981
  durable: false,
937
- load: () => pages.get(scope) ?? null,
982
+ load: () => pages.get(key) ?? null,
938
983
  save: (record) => {
939
- pages.set(scope, record);
984
+ pages.set(key, record);
940
985
  },
941
986
  }),
942
- [scope],
987
+ [key],
943
988
  );
989
+ };
944
990
 
945
- /** Session storage in page memory, keyed by scope: the record and client identity survive remounts, not reloads. */
991
+ /** Session storage in page memory, keyed by principal and scope: the record and client identity survive remounts, not reloads. */
946
992
  export const MemorySessionStorage = resource(useMemorySessionStorage);
947
993
 
948
994
  export namespace MemorySessionStorage {
949
- export type Options = { scope: string };
995
+ export type Options = {
996
+ /** The account whose session this storage holds. */
997
+ principal: string;
998
+ scope: string;
999
+ };
950
1000
  }
951
1001
 
952
1002
  const entriesOf = (
@@ -979,34 +1029,36 @@ const entriesOf = (
979
1029
  ];
980
1030
  });
981
1031
 
982
- /** Reads the outboxes of every session under `namespace`, mounted or not. */
1032
+ /** Reads the outboxes of every session `principal` keeps under `namespace`, mounted or not. */
983
1033
  export const readOutboxes = async (
984
1034
  backend: KeyValueSessionStorage.Backend,
985
1035
  options: ReadOutboxes.Options,
986
1036
  ): Promise<readonly ReadOutboxes.Outbox[]> => {
987
1037
  if (!backend.getAllKeys)
988
1038
  throw new Error("statewire: readOutboxes requires getAllKeys");
989
- const { namespace, codec, protocol = DEFAULT_PROTOCOL } = options;
1039
+ const { namespace, principal, codec, protocol = DEFAULT_PROTOCOL } = options;
990
1040
  if (!namespace)
991
1041
  throw new Error("statewire: readOutboxes requires a namespace");
1042
+ const scope = sessionNamespace(namespace, principal);
992
1043
  const result: ReadOutboxes.Outbox[] = [];
993
1044
  for (const key of await backend.getAllKeys()) {
994
- if (!key.startsWith(namespace)) continue;
1045
+ if (!key.startsWith(scope)) continue;
995
1046
  const parsed = await parse(await backend.getItem(key), codec);
996
1047
  if (parsed === null) continue;
997
- const entries = entriesOf(normalize(parsed, protocol));
1048
+ const entries = entriesOf(normalize(parsed, protocol, principal));
998
1049
  if (entries.length > 0) result.push({ key, entries });
999
1050
  }
1000
1051
  return result;
1001
1052
  };
1002
1053
 
1003
- /** Calls `listener` with `readOutboxes` now and after every save under `namespace`; returns unsubscribe. */
1054
+ /** Calls `listener` with `readOutboxes` now and after every save to `principal`'s sessions under `namespace`; returns unsubscribe. */
1004
1055
  export const subscribeOutboxes = (
1005
1056
  backend: KeyValueSessionStorage.Backend,
1006
1057
  options: ReadOutboxes.Options,
1007
1058
  listener: (outboxes: readonly ReadOutboxes.Outbox[]) => void,
1008
1059
  onError: (error: unknown) => void,
1009
1060
  ): (() => void) => {
1061
+ const scope = sessionNamespace(options.namespace, options.principal);
1010
1062
  let closed = false;
1011
1063
  let reading = Promise.resolve();
1012
1064
  const refresh = () => {
@@ -1021,7 +1073,7 @@ export const subscribeOutboxes = (
1021
1073
  });
1022
1074
  };
1023
1075
  const onKey = (key: string) => {
1024
- if (key.startsWith(options.namespace)) refresh();
1076
+ if (key.startsWith(scope)) refresh();
1025
1077
  };
1026
1078
  const local = savers.get(backend) ?? new Set();
1027
1079
  savers.set(backend, local);
@@ -1038,6 +1090,8 @@ export const subscribeOutboxes = (
1038
1090
  export namespace ReadOutboxes {
1039
1091
  export type Options = {
1040
1092
  namespace: string;
1093
+ /** The account whose sessions to read. */
1094
+ principal: string;
1041
1095
  codec?: KeyValueSessionStorage.Codec;
1042
1096
  /** The lane that single-lane formats lift into. */
1043
1097
  protocol?: string;