@ai-matrx/summon 0.1.4 → 0.1.5

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.
@@ -128,6 +128,15 @@ function createDataPersistence(options) {
128
128
  if (!row) return;
129
129
  await (0, import_db.remove)(db, table, row.id, { overwrite: true });
130
130
  },
131
+ async loadAll() {
132
+ const userId = await personId();
133
+ const rows = await (0, import_db.listAll)(db, table, (q) => q.eq("user_id", userId).eq("feature", feature), {
134
+ columns: ["surface_key", "state", "version"]
135
+ });
136
+ const values = /* @__PURE__ */ new Map();
137
+ for (const row of rows) values.set(row.surface_key, { value: row.state, version: row.version });
138
+ return values;
139
+ },
131
140
  async list(prefix) {
132
141
  const userId = await personId();
133
142
  const rows = await (0, import_db.listAll)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence-data.ts","../src/persistence/data/createDataPersistence.ts","../src/persistence/errors.ts"],"sourcesContent":["// @ai-matrx/summon/persistence/data — the @ai-matrx/data adapter over users.user_surface_state\n// (feature 'summon'; surface_key 'workspace' and 'state:<instanceKey>'; optimistic versions).\nexport {\n createDataPersistence,\n userSurfaceStateTable,\n SUMMON_SURFACE_FEATURE,\n type DataPersistenceOptions,\n type DataOverlayPersistence,\n type UserSurfaceStateRow,\n type UserSurfaceStateInsert,\n type UserSurfaceStateUpdate,\n} from \"./persistence/data/createDataPersistence\";\nexport { PersistenceVersionConflictError, isVersionConflict } from \"./persistence/errors\";\n","/**\n * createDataPersistence — the OverlayPersistencePort over `@ai-matrx/data`, backed by the\n * canonical per-person working-data table `users.user_surface_state` (design R4):\n *\n * feature = 'summon', surface_key = <key>, state = <value>, version = optimistic version.\n * Keys: `workspace` (the person's open windows/layout), `state:<overlayId>:<instanceId>`.\n *\n * Every call goes through the data package's doors (listAll / insert / update / remove) — the\n * descriptor's rules apply: the table is an `entity` with `org.rule = \"binding\"`, so an insert\n * takes the organization from `createDb({ organization })` (the host's active organization) and\n * the door REFUSES when there is none; this adapter adds no fallback of its own. Reads are scoped\n * to the signed-in person (`user_id`), never to an organization.\n *\n * The row is unique per (user_id, feature, surface_key) including soft-deleted rows, so `remove`\n * soft-deletes and a later `save` revives the same row instead of inserting a duplicate.\n */\nimport { DatabaseError, insert, listAll, remove as removeRow, update, type Db, type TableDescriptor } from \"@ai-matrx/data/db\";\nimport type { OverlayPersistencePort, StoredValue } from \"@ai-matrx/summon\";\nimport { PersistenceVersionConflictError } from \"../errors\";\n\nexport const SUMMON_SURFACE_FEATURE = \"summon\";\n\ntype Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[];\n\n/** The columns of users.user_surface_state this adapter reads and writes. */\nexport interface UserSurfaceStateRow {\n id: string;\n user_id: string;\n feature: string;\n surface_key: string;\n state: Json;\n organization_id: string;\n version: number;\n deleted_at: string | null;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n metadata: Json;\n custom_fields: Json;\n}\nexport interface UserSurfaceStateInsert {\n user_id: string;\n feature: string;\n surface_key?: string;\n state?: Json;\n organization_id?: string;\n deleted_at?: string | null;\n}\nexport type UserSurfaceStateUpdate = Partial<UserSurfaceStateInsert>;\n\n/**\n * The generated descriptor for users.user_surface_state (`matrx-data generate --schemas users`,\n * contract 1436b3e225ff). A host may pass its own freshly generated one instead.\n */\nexport const userSurfaceStateTable = {\n schema: \"users\",\n name: \"user_surface_state\",\n kind: \"table\",\n type: \"entity\",\n pk: [\"id\"],\n org: { rule: \"binding\" },\n versioned: true,\n softDelete: true,\n archive: null,\n readOnly: false,\n deletesRefused: false,\n readOnlyColumns: [],\n listScope: \"mine\",\n realtime: false,\n createdBy: true,\n} as const satisfies TableDescriptor<UserSurfaceStateRow, UserSurfaceStateInsert, UserSurfaceStateUpdate> as unknown as TableDescriptor<\n UserSurfaceStateRow,\n UserSurfaceStateInsert,\n UserSurfaceStateUpdate\n>;\n\nexport interface DataPersistenceOptions {\n readonly db: Db;\n /** The signed-in person's id. Default: the session on `db.client.auth`. */\n readonly userId?: () => string | null | undefined;\n /** Override the descriptor (a host's own generated `userSurfaceState`). */\n readonly table?: TableDescriptor<UserSurfaceStateRow, UserSurfaceStateInsert, UserSurfaceStateUpdate>;\n /** Default \"summon\". */\n readonly feature?: string;\n}\n\nexport interface DataOverlayPersistence extends OverlayPersistencePort {\n readonly perPerson: true;\n list(prefix: string): Promise<readonly string[]>;\n}\n\nconst COLUMNS = [\"id\", \"surface_key\", \"state\", \"version\", \"deleted_at\"] as const;\ntype Found = Pick<UserSurfaceStateRow, (typeof COLUMNS)[number]>;\n\nconst likeEscape = (value: string) => value.replace(/[\\\\%_]/g, (ch) => `\\\\${ch}`);\nconst isUniqueViolation = (error: unknown) => error instanceof DatabaseError && error.cause.code === \"23505\";\n\nexport function createDataPersistence(options: DataPersistenceOptions): DataOverlayPersistence {\n const { db } = options;\n const table = options.table ?? userSurfaceStateTable;\n const feature = options.feature ?? SUMMON_SURFACE_FEATURE;\n\n const personId = async (): Promise<string> => {\n const explicit = options.userId?.();\n if (explicit) return explicit;\n const session = db.client.auth ? await db.client.auth.getSession() : null;\n const id = session?.data.session?.user?.id;\n if (!id) throw new Error(\"[overlays/persistence/data] no signed-in person: overlay state is saved per person\");\n return id;\n };\n\n const find = async (key: string, includeDeleted: boolean): Promise<Found | null> => {\n const userId = await personId();\n const rows = await listAll(db, table, (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature).eq(\"surface_key\", key), {\n columns: COLUMNS,\n includeDeleted,\n });\n // Unique per (user_id, feature, surface_key); a live row beats a soft-deleted one.\n return rows.find((row) => row.deleted_at === null) ?? rows[0] ?? null;\n };\n\n const conflict = (key: string, expected: number, current: number) => new PersistenceVersionConflictError(key, expected, current);\n\n return {\n perPerson: true,\n\n async load<T = unknown>(key: string): Promise<StoredValue<T> | null> {\n const row = await find(key, false);\n return row ? { value: row.state as T, version: row.version } : null;\n },\n\n async save<T = unknown>(key: string, value: T, saveOptions?: { readonly expectedVersion?: number }) {\n const expected = saveOptions?.expectedVersion;\n const state = value as unknown as Json;\n const row = await find(key, true);\n if (row && row.deleted_at === null) {\n if (expected === 0) throw conflict(key, 0, row.version);\n const result = await update(db, table, row.id, { state }, expected === undefined ? { overwrite: true } : { expectedVersion: expected });\n if (result.status === \"saved\") return { version: result.row.version };\n if (result.status === \"conflict\") throw conflict(key, expected ?? row.version, result.currentVersion);\n throw conflict(key, expected ?? row.version, 0); // vanished between read and write\n }\n if (expected !== undefined && expected !== 0) throw conflict(key, expected, 0);\n if (row) {\n // Revive the soft-deleted row (the unique key includes deleted rows).\n const result = await update(db, table, row.id, { state, deleted_at: null }, { expectedVersion: row.version });\n if (result.status === \"saved\") return { version: result.row.version };\n throw conflict(key, 0, result.status === \"conflict\" ? result.currentVersion : 0);\n }\n try {\n const created = await insert(db, table, { user_id: await personId(), feature, surface_key: key, state });\n return { version: created.version };\n } catch (error) {\n // Another device created it first.\n if (isUniqueViolation(error)) throw conflict(key, 0, (await find(key, true))?.version ?? 1);\n throw error;\n }\n },\n\n async remove(key: string) {\n const row = await find(key, false);\n if (!row) return;\n await removeRow(db, table, row.id, { overwrite: true });\n },\n\n async list(prefix: string) {\n const userId = await personId();\n const rows = await listAll(\n db,\n table,\n (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature).like(\"surface_key\", `${likeEscape(prefix)}%`),\n { columns: [\"surface_key\"] },\n );\n return rows.map((row) => row.surface_key).filter((key) => key.startsWith(prefix)).sort();\n },\n };\n}\n","/**\n * Errors every persistence store throws in the same shape. Matched by `name`, never `instanceof`:\n * each section entry may carry its own copy of this module in a built consumer.\n */\nimport type { StoredValue } from \"@ai-matrx/summon\";\n\nexport const VERSION_CONFLICT_ERROR = \"OverlayPersistenceVersionConflict\";\nexport const LOAD_TIMEOUT_ERROR = \"OverlayPersistenceLoadTimeout\";\n\n/** A save whose `expectedVersion` no longer matches the stored version. */\nexport class PersistenceVersionConflictError extends Error {\n readonly key: string;\n readonly expectedVersion: number;\n readonly currentVersion: number;\n constructor(key: string, expectedVersion: number, currentVersion: number) {\n super(`[overlays/persistence] \"${key}\" is at version ${currentVersion}, not the expected ${expectedVersion}`);\n this.name = VERSION_CONFLICT_ERROR;\n this.key = key;\n this.expectedVersion = expectedVersion;\n this.currentVersion = currentVersion;\n }\n}\n\nexport function isVersionConflict(error: unknown): error is PersistenceVersionConflictError {\n return error instanceof Error && error.name === VERSION_CONFLICT_ERROR;\n}\n\n/**\n * The fast tier missed and the slow tier did not answer within the read budget. `pending` still\n * resolves the authoritative answer: a caller keeps every automatic writer gated until it does,\n * so an unknown stored value is never overwritten.\n */\nexport class PersistenceLoadTimeoutError extends Error {\n readonly key: string;\n readonly pending: Promise<StoredValue | null>;\n constructor(key: string, budgetMs: number, pending: Promise<StoredValue | null>) {\n super(`[overlays/persistence] \"${key}\" did not load within ${budgetMs}ms`);\n this.name = LOAD_TIMEOUT_ERROR;\n this.key = key;\n this.pending = pending;\n }\n}\n\nexport function isLoadTimeout(error: unknown): error is PersistenceLoadTimeoutError {\n return error instanceof Error && error.name === LOAD_TIMEOUT_ERROR;\n}\n"],"mappings":"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","names":["removeRow"]}
1
+ {"version":3,"sources":["../src/persistence-data.ts","../src/persistence/data/createDataPersistence.ts","../src/persistence/errors.ts"],"sourcesContent":["// @ai-matrx/summon/persistence/data — the @ai-matrx/data adapter over users.user_surface_state\n// (feature 'summon'; surface_key 'workspace' and 'state:<instanceKey>'; optimistic versions).\nexport {\n createDataPersistence,\n userSurfaceStateTable,\n SUMMON_SURFACE_FEATURE,\n type DataPersistenceOptions,\n type DataOverlayPersistence,\n type UserSurfaceStateRow,\n type UserSurfaceStateInsert,\n type UserSurfaceStateUpdate,\n} from \"./persistence/data/createDataPersistence\";\nexport { PersistenceVersionConflictError, isVersionConflict } from \"./persistence/errors\";\n","/**\n * createDataPersistence — the OverlayPersistencePort over `@ai-matrx/data`, backed by the\n * canonical per-person working-data table `users.user_surface_state` (design R4):\n *\n * feature = 'summon', surface_key = <key>, state = <value>, version = optimistic version.\n * Keys: `workspace` (the person's open windows/layout), `state:<overlayId>:<instanceId>`.\n *\n * Every call goes through the data package's doors (listAll / insert / update / remove) — the\n * descriptor's rules apply: the table is an `entity` with `org.rule = \"binding\"`, so an insert\n * takes the organization from `createDb({ organization })` (the host's active organization) and\n * the door REFUSES when there is none; this adapter adds no fallback of its own. Reads are scoped\n * to the signed-in person (`user_id`), never to an organization.\n *\n * The row is unique per (user_id, feature, surface_key) including soft-deleted rows, so `remove`\n * soft-deletes and a later `save` revives the same row instead of inserting a duplicate.\n */\nimport { DatabaseError, insert, listAll, remove as removeRow, update, type Db, type TableDescriptor } from \"@ai-matrx/data/db\";\nimport type { OverlayPersistencePort, StoredValue } from \"@ai-matrx/summon\";\nimport { PersistenceVersionConflictError } from \"../errors\";\n\nexport const SUMMON_SURFACE_FEATURE = \"summon\";\n\ntype Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[];\n\n/** The columns of users.user_surface_state this adapter reads and writes. */\nexport interface UserSurfaceStateRow {\n id: string;\n user_id: string;\n feature: string;\n surface_key: string;\n state: Json;\n organization_id: string;\n version: number;\n deleted_at: string | null;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n metadata: Json;\n custom_fields: Json;\n}\nexport interface UserSurfaceStateInsert {\n user_id: string;\n feature: string;\n surface_key?: string;\n state?: Json;\n organization_id?: string;\n deleted_at?: string | null;\n}\nexport type UserSurfaceStateUpdate = Partial<UserSurfaceStateInsert>;\n\n/**\n * The generated descriptor for users.user_surface_state (`matrx-data generate --schemas users`,\n * contract 1436b3e225ff). A host may pass its own freshly generated one instead.\n */\nexport const userSurfaceStateTable = {\n schema: \"users\",\n name: \"user_surface_state\",\n kind: \"table\",\n type: \"entity\",\n pk: [\"id\"],\n org: { rule: \"binding\" },\n versioned: true,\n softDelete: true,\n archive: null,\n readOnly: false,\n deletesRefused: false,\n readOnlyColumns: [],\n listScope: \"mine\",\n realtime: false,\n createdBy: true,\n} as const satisfies TableDescriptor<UserSurfaceStateRow, UserSurfaceStateInsert, UserSurfaceStateUpdate> as unknown as TableDescriptor<\n UserSurfaceStateRow,\n UserSurfaceStateInsert,\n UserSurfaceStateUpdate\n>;\n\nexport interface DataPersistenceOptions {\n readonly db: Db;\n /** The signed-in person's id. Default: the session on `db.client.auth`. */\n readonly userId?: () => string | null | undefined;\n /** Override the descriptor (a host's own generated `userSurfaceState`). */\n readonly table?: TableDescriptor<UserSurfaceStateRow, UserSurfaceStateInsert, UserSurfaceStateUpdate>;\n /** Default \"summon\". */\n readonly feature?: string;\n}\n\nexport interface DataOverlayPersistence extends OverlayPersistencePort {\n readonly perPerson: true;\n list(prefix: string): Promise<readonly string[]>;\n loadAll(): Promise<ReadonlyMap<string, StoredValue>>;\n}\n\nconst COLUMNS = [\"id\", \"surface_key\", \"state\", \"version\", \"deleted_at\"] as const;\ntype Found = Pick<UserSurfaceStateRow, (typeof COLUMNS)[number]>;\n\nconst likeEscape = (value: string) => value.replace(/[\\\\%_]/g, (ch) => `\\\\${ch}`);\nconst isUniqueViolation = (error: unknown) => error instanceof DatabaseError && error.cause.code === \"23505\";\n\nexport function createDataPersistence(options: DataPersistenceOptions): DataOverlayPersistence {\n const { db } = options;\n const table = options.table ?? userSurfaceStateTable;\n const feature = options.feature ?? SUMMON_SURFACE_FEATURE;\n\n const personId = async (): Promise<string> => {\n const explicit = options.userId?.();\n if (explicit) return explicit;\n const session = db.client.auth ? await db.client.auth.getSession() : null;\n const id = session?.data.session?.user?.id;\n if (!id) throw new Error(\"[overlays/persistence/data] no signed-in person: overlay state is saved per person\");\n return id;\n };\n\n const find = async (key: string, includeDeleted: boolean): Promise<Found | null> => {\n const userId = await personId();\n const rows = await listAll(db, table, (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature).eq(\"surface_key\", key), {\n columns: COLUMNS,\n includeDeleted,\n });\n // Unique per (user_id, feature, surface_key); a live row beats a soft-deleted one.\n return rows.find((row) => row.deleted_at === null) ?? rows[0] ?? null;\n };\n\n const conflict = (key: string, expected: number, current: number) => new PersistenceVersionConflictError(key, expected, current);\n\n return {\n perPerson: true,\n\n async load<T = unknown>(key: string): Promise<StoredValue<T> | null> {\n const row = await find(key, false);\n return row ? { value: row.state as T, version: row.version } : null;\n },\n\n async save<T = unknown>(key: string, value: T, saveOptions?: { readonly expectedVersion?: number }) {\n const expected = saveOptions?.expectedVersion;\n const state = value as unknown as Json;\n const row = await find(key, true);\n if (row && row.deleted_at === null) {\n if (expected === 0) throw conflict(key, 0, row.version);\n const result = await update(db, table, row.id, { state }, expected === undefined ? { overwrite: true } : { expectedVersion: expected });\n if (result.status === \"saved\") return { version: result.row.version };\n if (result.status === \"conflict\") throw conflict(key, expected ?? row.version, result.currentVersion);\n throw conflict(key, expected ?? row.version, 0); // vanished between read and write\n }\n if (expected !== undefined && expected !== 0) throw conflict(key, expected, 0);\n if (row) {\n // Revive the soft-deleted row (the unique key includes deleted rows).\n const result = await update(db, table, row.id, { state, deleted_at: null }, { expectedVersion: row.version });\n if (result.status === \"saved\") return { version: result.row.version };\n throw conflict(key, 0, result.status === \"conflict\" ? result.currentVersion : 0);\n }\n try {\n const created = await insert(db, table, { user_id: await personId(), feature, surface_key: key, state });\n return { version: created.version };\n } catch (error) {\n // Another device created it first.\n if (isUniqueViolation(error)) throw conflict(key, 0, (await find(key, true))?.version ?? 1);\n throw error;\n }\n },\n\n async remove(key: string) {\n const row = await find(key, false);\n if (!row) return;\n await removeRow(db, table, row.id, { overwrite: true });\n },\n\n async loadAll() {\n // The person's whole summon surface in one read: the workspace row and every saved\n // component state (rows are small; boot needs all of them anyway).\n const userId = await personId();\n const rows = await listAll(db, table, (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature), {\n columns: [\"surface_key\", \"state\", \"version\"],\n });\n const values = new Map<string, StoredValue>();\n for (const row of rows) values.set(row.surface_key, { value: row.state, version: row.version });\n return values;\n },\n\n async list(prefix: string) {\n const userId = await personId();\n const rows = await listAll(\n db,\n table,\n (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature).like(\"surface_key\", `${likeEscape(prefix)}%`),\n { columns: [\"surface_key\"] },\n );\n return rows.map((row) => row.surface_key).filter((key) => key.startsWith(prefix)).sort();\n },\n };\n}\n","/**\n * Errors every persistence store throws in the same shape. Matched by `name`, never `instanceof`:\n * each section entry may carry its own copy of this module in a built consumer.\n */\nimport type { StoredValue } from \"@ai-matrx/summon\";\n\nexport const VERSION_CONFLICT_ERROR = \"OverlayPersistenceVersionConflict\";\nexport const LOAD_TIMEOUT_ERROR = \"OverlayPersistenceLoadTimeout\";\n\n/** A save whose `expectedVersion` no longer matches the stored version. */\nexport class PersistenceVersionConflictError extends Error {\n readonly key: string;\n readonly expectedVersion: number;\n readonly currentVersion: number;\n constructor(key: string, expectedVersion: number, currentVersion: number) {\n super(`[overlays/persistence] \"${key}\" is at version ${currentVersion}, not the expected ${expectedVersion}`);\n this.name = VERSION_CONFLICT_ERROR;\n this.key = key;\n this.expectedVersion = expectedVersion;\n this.currentVersion = currentVersion;\n }\n}\n\nexport function isVersionConflict(error: unknown): error is PersistenceVersionConflictError {\n return error instanceof Error && error.name === VERSION_CONFLICT_ERROR;\n}\n\n/**\n * The fast tier missed and the slow tier did not answer within the read budget. `pending` still\n * resolves the authoritative answer: a caller keeps every automatic writer gated until it does,\n * so an unknown stored value is never overwritten.\n */\nexport class PersistenceLoadTimeoutError extends Error {\n readonly key: string;\n readonly pending: Promise<StoredValue | null>;\n constructor(key: string, budgetMs: number, pending: Promise<StoredValue | null>) {\n super(`[overlays/persistence] \"${key}\" did not load within ${budgetMs}ms`);\n this.name = LOAD_TIMEOUT_ERROR;\n this.key = key;\n this.pending = pending;\n }\n}\n\nexport function isLoadTimeout(error: unknown): error is PersistenceLoadTimeoutError {\n return error instanceof Error && error.name === LOAD_TIMEOUT_ERROR;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACgBA,gBAA2G;;;ACVpG,IAAM,yBAAyB;AAI/B,IAAM,kCAAN,cAA8C,MAAM;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAY,KAAa,iBAAyB,gBAAwB;AACxE,UAAM,2BAA2B,GAAG,mBAAmB,cAAc,sBAAsB,eAAe,EAAE;AAC5G,SAAK,OAAO;AACZ,SAAK,MAAM;AACX,SAAK,kBAAkB;AACvB,SAAK,iBAAiB;AAAA,EACxB;AACF;AAEO,SAAS,kBAAkB,OAA0D;AAC1F,SAAO,iBAAiB,SAAS,MAAM,SAAS;AAClD;;;ADLO,IAAM,yBAAyB;AAmC/B,IAAM,wBAAwB;AAAA,EACnC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,IAAI,CAAC,IAAI;AAAA,EACT,KAAK,EAAE,MAAM,UAAU;AAAA,EACvB,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,gBAAgB;AAAA,EAChB,iBAAiB,CAAC;AAAA,EAClB,WAAW;AAAA,EACX,UAAU;AAAA,EACV,WAAW;AACb;AAsBA,IAAM,UAAU,CAAC,MAAM,eAAe,SAAS,WAAW,YAAY;AAGtE,IAAM,aAAa,CAAC,UAAkB,MAAM,QAAQ,WAAW,CAAC,OAAO,KAAK,EAAE,EAAE;AAChF,IAAM,oBAAoB,CAAC,UAAmB,iBAAiB,2BAAiB,MAAM,MAAM,SAAS;AAE9F,SAAS,sBAAsB,SAAyD;AAC7F,QAAM,EAAE,GAAG,IAAI;AACf,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,UAAU,QAAQ,WAAW;AAEnC,QAAM,WAAW,YAA6B;AAC5C,UAAM,WAAW,QAAQ,SAAS;AAClC,QAAI,SAAU,QAAO;AACrB,UAAM,UAAU,GAAG,OAAO,OAAO,MAAM,GAAG,OAAO,KAAK,WAAW,IAAI;AACrE,UAAM,KAAK,SAAS,KAAK,SAAS,MAAM;AACxC,QAAI,CAAC,GAAI,OAAM,IAAI,MAAM,oFAAoF;AAC7G,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,OAAO,KAAa,mBAAmD;AAClF,UAAM,SAAS,MAAM,SAAS;AAC9B,UAAM,OAAO,UAAM,mBAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,WAAW,OAAO,EAAE,GAAG,eAAe,GAAG,GAAG;AAAA,MAClH,SAAS;AAAA,MACT;AAAA,IACF,CAAC;AAED,WAAO,KAAK,KAAK,CAAC,QAAQ,IAAI,eAAe,IAAI,KAAK,KAAK,CAAC,KAAK;AAAA,EACnE;AAEA,QAAM,WAAW,CAAC,KAAa,UAAkB,YAAoB,IAAI,gCAAgC,KAAK,UAAU,OAAO;AAE/H,SAAO;AAAA,IACL,WAAW;AAAA,IAEX,MAAM,KAAkB,KAA6C;AACnE,YAAM,MAAM,MAAM,KAAK,KAAK,KAAK;AACjC,aAAO,MAAM,EAAE,OAAO,IAAI,OAAY,SAAS,IAAI,QAAQ,IAAI;AAAA,IACjE;AAAA,IAEA,MAAM,KAAkB,KAAa,OAAU,aAAqD;AAClG,YAAM,WAAW,aAAa;AAC9B,YAAM,QAAQ;AACd,YAAM,MAAM,MAAM,KAAK,KAAK,IAAI;AAChC,UAAI,OAAO,IAAI,eAAe,MAAM;AAClC,YAAI,aAAa,EAAG,OAAM,SAAS,KAAK,GAAG,IAAI,OAAO;AACtD,cAAM,SAAS,UAAM,kBAAO,IAAI,OAAO,IAAI,IAAI,EAAE,MAAM,GAAG,aAAa,SAAY,EAAE,WAAW,KAAK,IAAI,EAAE,iBAAiB,SAAS,CAAC;AACtI,YAAI,OAAO,WAAW,QAAS,QAAO,EAAE,SAAS,OAAO,IAAI,QAAQ;AACpE,YAAI,OAAO,WAAW,WAAY,OAAM,SAAS,KAAK,YAAY,IAAI,SAAS,OAAO,cAAc;AACpG,cAAM,SAAS,KAAK,YAAY,IAAI,SAAS,CAAC;AAAA,MAChD;AACA,UAAI,aAAa,UAAa,aAAa,EAAG,OAAM,SAAS,KAAK,UAAU,CAAC;AAC7E,UAAI,KAAK;AAEP,cAAM,SAAS,UAAM,kBAAO,IAAI,OAAO,IAAI,IAAI,EAAE,OAAO,YAAY,KAAK,GAAG,EAAE,iBAAiB,IAAI,QAAQ,CAAC;AAC5G,YAAI,OAAO,WAAW,QAAS,QAAO,EAAE,SAAS,OAAO,IAAI,QAAQ;AACpE,cAAM,SAAS,KAAK,GAAG,OAAO,WAAW,aAAa,OAAO,iBAAiB,CAAC;AAAA,MACjF;AACA,UAAI;AACF,cAAM,UAAU,UAAM,kBAAO,IAAI,OAAO,EAAE,SAAS,MAAM,SAAS,GAAG,SAAS,aAAa,KAAK,MAAM,CAAC;AACvG,eAAO,EAAE,SAAS,QAAQ,QAAQ;AAAA,MACpC,SAAS,OAAO;AAEd,YAAI,kBAAkB,KAAK,EAAG,OAAM,SAAS,KAAK,IAAI,MAAM,KAAK,KAAK,IAAI,IAAI,WAAW,CAAC;AAC1F,cAAM;AAAA,MACR;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,KAAa;AACxB,YAAM,MAAM,MAAM,KAAK,KAAK,KAAK;AACjC,UAAI,CAAC,IAAK;AACV,gBAAM,UAAAA,QAAU,IAAI,OAAO,IAAI,IAAI,EAAE,WAAW,KAAK,CAAC;AAAA,IACxD;AAAA,IAEA,MAAM,UAAU;AAGd,YAAM,SAAS,MAAM,SAAS;AAC9B,YAAM,OAAO,UAAM,mBAAQ,IAAI,OAAO,CAAC,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,WAAW,OAAO,GAAG;AAAA,QAC3F,SAAS,CAAC,eAAe,SAAS,SAAS;AAAA,MAC7C,CAAC;AACD,YAAM,SAAS,oBAAI,IAAyB;AAC5C,iBAAW,OAAO,KAAM,QAAO,IAAI,IAAI,aAAa,EAAE,OAAO,IAAI,OAAO,SAAS,IAAI,QAAQ,CAAC;AAC9F,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,KAAK,QAAgB;AACzB,YAAM,SAAS,MAAM,SAAS;AAC9B,YAAM,OAAO,UAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA,CAAC,MAAM,EAAE,GAAG,WAAW,MAAM,EAAE,GAAG,WAAW,OAAO,EAAE,KAAK,eAAe,GAAG,WAAW,MAAM,CAAC,GAAG;AAAA,QAClG,EAAE,SAAS,CAAC,aAAa,EAAE;AAAA,MAC7B;AACA,aAAO,KAAK,IAAI,CAAC,QAAQ,IAAI,WAAW,EAAE,OAAO,CAAC,QAAQ,IAAI,WAAW,MAAM,CAAC,EAAE,KAAK;AAAA,IACzF;AAAA,EACF;AACF;","names":["removeRow"]}
@@ -1,5 +1,5 @@
1
1
  import { Db, TableDescriptor } from '@ai-matrx/data/db';
2
- import { OverlayPersistencePort } from '@ai-matrx/summon';
2
+ import { OverlayPersistencePort, StoredValue } from '@ai-matrx/summon';
3
3
  export { P as PersistenceVersionConflictError, i as isVersionConflict } from './errors-eJrKXq33.cjs';
4
4
 
5
5
  /**
@@ -66,6 +66,7 @@ interface DataPersistenceOptions {
66
66
  interface DataOverlayPersistence extends OverlayPersistencePort {
67
67
  readonly perPerson: true;
68
68
  list(prefix: string): Promise<readonly string[]>;
69
+ loadAll(): Promise<ReadonlyMap<string, StoredValue>>;
69
70
  }
70
71
  declare function createDataPersistence(options: DataPersistenceOptions): DataOverlayPersistence;
71
72
 
@@ -1,5 +1,5 @@
1
1
  import { Db, TableDescriptor } from '@ai-matrx/data/db';
2
- import { OverlayPersistencePort } from '@ai-matrx/summon';
2
+ import { OverlayPersistencePort, StoredValue } from '@ai-matrx/summon';
3
3
  export { P as PersistenceVersionConflictError, i as isVersionConflict } from './errors-eJrKXq33.js';
4
4
 
5
5
  /**
@@ -66,6 +66,7 @@ interface DataPersistenceOptions {
66
66
  interface DataOverlayPersistence extends OverlayPersistencePort {
67
67
  readonly perPerson: true;
68
68
  list(prefix: string): Promise<readonly string[]>;
69
+ loadAll(): Promise<ReadonlyMap<string, StoredValue>>;
69
70
  }
70
71
  declare function createDataPersistence(options: DataPersistenceOptions): DataOverlayPersistence;
71
72
 
@@ -98,6 +98,15 @@ function createDataPersistence(options) {
98
98
  if (!row) return;
99
99
  await removeRow(db, table, row.id, { overwrite: true });
100
100
  },
101
+ async loadAll() {
102
+ const userId = await personId();
103
+ const rows = await listAll(db, table, (q) => q.eq("user_id", userId).eq("feature", feature), {
104
+ columns: ["surface_key", "state", "version"]
105
+ });
106
+ const values = /* @__PURE__ */ new Map();
107
+ for (const row of rows) values.set(row.surface_key, { value: row.state, version: row.version });
108
+ return values;
109
+ },
101
110
  async list(prefix) {
102
111
  const userId = await personId();
103
112
  const rows = await listAll(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/persistence/data/createDataPersistence.ts","../src/persistence/errors.ts"],"sourcesContent":["/**\n * createDataPersistence — the OverlayPersistencePort over `@ai-matrx/data`, backed by the\n * canonical per-person working-data table `users.user_surface_state` (design R4):\n *\n * feature = 'summon', surface_key = <key>, state = <value>, version = optimistic version.\n * Keys: `workspace` (the person's open windows/layout), `state:<overlayId>:<instanceId>`.\n *\n * Every call goes through the data package's doors (listAll / insert / update / remove) — the\n * descriptor's rules apply: the table is an `entity` with `org.rule = \"binding\"`, so an insert\n * takes the organization from `createDb({ organization })` (the host's active organization) and\n * the door REFUSES when there is none; this adapter adds no fallback of its own. Reads are scoped\n * to the signed-in person (`user_id`), never to an organization.\n *\n * The row is unique per (user_id, feature, surface_key) including soft-deleted rows, so `remove`\n * soft-deletes and a later `save` revives the same row instead of inserting a duplicate.\n */\nimport { DatabaseError, insert, listAll, remove as removeRow, update, type Db, type TableDescriptor } from \"@ai-matrx/data/db\";\nimport type { OverlayPersistencePort, StoredValue } from \"@ai-matrx/summon\";\nimport { PersistenceVersionConflictError } from \"../errors\";\n\nexport const SUMMON_SURFACE_FEATURE = \"summon\";\n\ntype Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[];\n\n/** The columns of users.user_surface_state this adapter reads and writes. */\nexport interface UserSurfaceStateRow {\n id: string;\n user_id: string;\n feature: string;\n surface_key: string;\n state: Json;\n organization_id: string;\n version: number;\n deleted_at: string | null;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n metadata: Json;\n custom_fields: Json;\n}\nexport interface UserSurfaceStateInsert {\n user_id: string;\n feature: string;\n surface_key?: string;\n state?: Json;\n organization_id?: string;\n deleted_at?: string | null;\n}\nexport type UserSurfaceStateUpdate = Partial<UserSurfaceStateInsert>;\n\n/**\n * The generated descriptor for users.user_surface_state (`matrx-data generate --schemas users`,\n * contract 1436b3e225ff). A host may pass its own freshly generated one instead.\n */\nexport const userSurfaceStateTable = {\n schema: \"users\",\n name: \"user_surface_state\",\n kind: \"table\",\n type: \"entity\",\n pk: [\"id\"],\n org: { rule: \"binding\" },\n versioned: true,\n softDelete: true,\n archive: null,\n readOnly: false,\n deletesRefused: false,\n readOnlyColumns: [],\n listScope: \"mine\",\n realtime: false,\n createdBy: true,\n} as const satisfies TableDescriptor<UserSurfaceStateRow, UserSurfaceStateInsert, UserSurfaceStateUpdate> as unknown as TableDescriptor<\n UserSurfaceStateRow,\n UserSurfaceStateInsert,\n UserSurfaceStateUpdate\n>;\n\nexport interface DataPersistenceOptions {\n readonly db: Db;\n /** The signed-in person's id. Default: the session on `db.client.auth`. */\n readonly userId?: () => string | null | undefined;\n /** Override the descriptor (a host's own generated `userSurfaceState`). */\n readonly table?: TableDescriptor<UserSurfaceStateRow, UserSurfaceStateInsert, UserSurfaceStateUpdate>;\n /** Default \"summon\". */\n readonly feature?: string;\n}\n\nexport interface DataOverlayPersistence extends OverlayPersistencePort {\n readonly perPerson: true;\n list(prefix: string): Promise<readonly string[]>;\n}\n\nconst COLUMNS = [\"id\", \"surface_key\", \"state\", \"version\", \"deleted_at\"] as const;\ntype Found = Pick<UserSurfaceStateRow, (typeof COLUMNS)[number]>;\n\nconst likeEscape = (value: string) => value.replace(/[\\\\%_]/g, (ch) => `\\\\${ch}`);\nconst isUniqueViolation = (error: unknown) => error instanceof DatabaseError && error.cause.code === \"23505\";\n\nexport function createDataPersistence(options: DataPersistenceOptions): DataOverlayPersistence {\n const { db } = options;\n const table = options.table ?? userSurfaceStateTable;\n const feature = options.feature ?? SUMMON_SURFACE_FEATURE;\n\n const personId = async (): Promise<string> => {\n const explicit = options.userId?.();\n if (explicit) return explicit;\n const session = db.client.auth ? await db.client.auth.getSession() : null;\n const id = session?.data.session?.user?.id;\n if (!id) throw new Error(\"[overlays/persistence/data] no signed-in person: overlay state is saved per person\");\n return id;\n };\n\n const find = async (key: string, includeDeleted: boolean): Promise<Found | null> => {\n const userId = await personId();\n const rows = await listAll(db, table, (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature).eq(\"surface_key\", key), {\n columns: COLUMNS,\n includeDeleted,\n });\n // Unique per (user_id, feature, surface_key); a live row beats a soft-deleted one.\n return rows.find((row) => row.deleted_at === null) ?? rows[0] ?? null;\n };\n\n const conflict = (key: string, expected: number, current: number) => new PersistenceVersionConflictError(key, expected, current);\n\n return {\n perPerson: true,\n\n async load<T = unknown>(key: string): Promise<StoredValue<T> | null> {\n const row = await find(key, false);\n return row ? { value: row.state as T, version: row.version } : null;\n },\n\n async save<T = unknown>(key: string, value: T, saveOptions?: { readonly expectedVersion?: number }) {\n const expected = saveOptions?.expectedVersion;\n const state = value as unknown as Json;\n const row = await find(key, true);\n if (row && row.deleted_at === null) {\n if (expected === 0) throw conflict(key, 0, row.version);\n const result = await update(db, table, row.id, { state }, expected === undefined ? { overwrite: true } : { expectedVersion: expected });\n if (result.status === \"saved\") return { version: result.row.version };\n if (result.status === \"conflict\") throw conflict(key, expected ?? row.version, result.currentVersion);\n throw conflict(key, expected ?? row.version, 0); // vanished between read and write\n }\n if (expected !== undefined && expected !== 0) throw conflict(key, expected, 0);\n if (row) {\n // Revive the soft-deleted row (the unique key includes deleted rows).\n const result = await update(db, table, row.id, { state, deleted_at: null }, { expectedVersion: row.version });\n if (result.status === \"saved\") return { version: result.row.version };\n throw conflict(key, 0, result.status === \"conflict\" ? result.currentVersion : 0);\n }\n try {\n const created = await insert(db, table, { user_id: await personId(), feature, surface_key: key, state });\n return { version: created.version };\n } catch (error) {\n // Another device created it first.\n if (isUniqueViolation(error)) throw conflict(key, 0, (await find(key, true))?.version ?? 1);\n throw error;\n }\n },\n\n async remove(key: string) {\n const row = await find(key, false);\n if (!row) return;\n await removeRow(db, table, row.id, { overwrite: true });\n },\n\n async list(prefix: string) {\n const userId = await personId();\n const rows = await listAll(\n db,\n table,\n (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature).like(\"surface_key\", `${likeEscape(prefix)}%`),\n { columns: [\"surface_key\"] },\n );\n return rows.map((row) => row.surface_key).filter((key) => key.startsWith(prefix)).sort();\n },\n };\n}\n","/**\n * Errors every persistence store throws in the same shape. Matched by `name`, never `instanceof`:\n * each section entry may carry its own copy of this module in a built consumer.\n */\nimport type { StoredValue } from \"@ai-matrx/summon\";\n\nexport const VERSION_CONFLICT_ERROR = \"OverlayPersistenceVersionConflict\";\nexport const LOAD_TIMEOUT_ERROR = \"OverlayPersistenceLoadTimeout\";\n\n/** A save whose `expectedVersion` no longer matches the stored version. */\nexport class PersistenceVersionConflictError extends Error {\n readonly key: string;\n readonly expectedVersion: number;\n readonly currentVersion: number;\n constructor(key: string, expectedVersion: number, currentVersion: number) {\n super(`[overlays/persistence] \"${key}\" is at version ${currentVersion}, not the expected ${expectedVersion}`);\n this.name = VERSION_CONFLICT_ERROR;\n this.key = key;\n this.expectedVersion = expectedVersion;\n this.currentVersion = currentVersion;\n }\n}\n\nexport function isVersionConflict(error: unknown): error is PersistenceVersionConflictError {\n return error instanceof Error && error.name === VERSION_CONFLICT_ERROR;\n}\n\n/**\n * The fast tier missed and the slow tier did not answer within the read budget. `pending` still\n * resolves the authoritative answer: a caller keeps every automatic writer gated until it does,\n * so an unknown stored value is never overwritten.\n */\nexport class PersistenceLoadTimeoutError extends Error {\n readonly key: string;\n readonly pending: Promise<StoredValue | null>;\n constructor(key: string, budgetMs: number, pending: Promise<StoredValue | null>) {\n super(`[overlays/persistence] \"${key}\" did not load within ${budgetMs}ms`);\n this.name = LOAD_TIMEOUT_ERROR;\n this.key = key;\n this.pending = pending;\n }\n}\n\nexport function isLoadTimeout(error: unknown): error is PersistenceLoadTimeoutError {\n return error instanceof Error && error.name === LOAD_TIMEOUT_ERROR;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/persistence/data/createDataPersistence.ts","../src/persistence/errors.ts"],"sourcesContent":["/**\n * createDataPersistence — the OverlayPersistencePort over `@ai-matrx/data`, backed by the\n * canonical per-person working-data table `users.user_surface_state` (design R4):\n *\n * feature = 'summon', surface_key = <key>, state = <value>, version = optimistic version.\n * Keys: `workspace` (the person's open windows/layout), `state:<overlayId>:<instanceId>`.\n *\n * Every call goes through the data package's doors (listAll / insert / update / remove) — the\n * descriptor's rules apply: the table is an `entity` with `org.rule = \"binding\"`, so an insert\n * takes the organization from `createDb({ organization })` (the host's active organization) and\n * the door REFUSES when there is none; this adapter adds no fallback of its own. Reads are scoped\n * to the signed-in person (`user_id`), never to an organization.\n *\n * The row is unique per (user_id, feature, surface_key) including soft-deleted rows, so `remove`\n * soft-deletes and a later `save` revives the same row instead of inserting a duplicate.\n */\nimport { DatabaseError, insert, listAll, remove as removeRow, update, type Db, type TableDescriptor } from \"@ai-matrx/data/db\";\nimport type { OverlayPersistencePort, StoredValue } from \"@ai-matrx/summon\";\nimport { PersistenceVersionConflictError } from \"../errors\";\n\nexport const SUMMON_SURFACE_FEATURE = \"summon\";\n\ntype Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[];\n\n/** The columns of users.user_surface_state this adapter reads and writes. */\nexport interface UserSurfaceStateRow {\n id: string;\n user_id: string;\n feature: string;\n surface_key: string;\n state: Json;\n organization_id: string;\n version: number;\n deleted_at: string | null;\n created_at: string;\n updated_at: string;\n created_by: string | null;\n updated_by: string | null;\n metadata: Json;\n custom_fields: Json;\n}\nexport interface UserSurfaceStateInsert {\n user_id: string;\n feature: string;\n surface_key?: string;\n state?: Json;\n organization_id?: string;\n deleted_at?: string | null;\n}\nexport type UserSurfaceStateUpdate = Partial<UserSurfaceStateInsert>;\n\n/**\n * The generated descriptor for users.user_surface_state (`matrx-data generate --schemas users`,\n * contract 1436b3e225ff). A host may pass its own freshly generated one instead.\n */\nexport const userSurfaceStateTable = {\n schema: \"users\",\n name: \"user_surface_state\",\n kind: \"table\",\n type: \"entity\",\n pk: [\"id\"],\n org: { rule: \"binding\" },\n versioned: true,\n softDelete: true,\n archive: null,\n readOnly: false,\n deletesRefused: false,\n readOnlyColumns: [],\n listScope: \"mine\",\n realtime: false,\n createdBy: true,\n} as const satisfies TableDescriptor<UserSurfaceStateRow, UserSurfaceStateInsert, UserSurfaceStateUpdate> as unknown as TableDescriptor<\n UserSurfaceStateRow,\n UserSurfaceStateInsert,\n UserSurfaceStateUpdate\n>;\n\nexport interface DataPersistenceOptions {\n readonly db: Db;\n /** The signed-in person's id. Default: the session on `db.client.auth`. */\n readonly userId?: () => string | null | undefined;\n /** Override the descriptor (a host's own generated `userSurfaceState`). */\n readonly table?: TableDescriptor<UserSurfaceStateRow, UserSurfaceStateInsert, UserSurfaceStateUpdate>;\n /** Default \"summon\". */\n readonly feature?: string;\n}\n\nexport interface DataOverlayPersistence extends OverlayPersistencePort {\n readonly perPerson: true;\n list(prefix: string): Promise<readonly string[]>;\n loadAll(): Promise<ReadonlyMap<string, StoredValue>>;\n}\n\nconst COLUMNS = [\"id\", \"surface_key\", \"state\", \"version\", \"deleted_at\"] as const;\ntype Found = Pick<UserSurfaceStateRow, (typeof COLUMNS)[number]>;\n\nconst likeEscape = (value: string) => value.replace(/[\\\\%_]/g, (ch) => `\\\\${ch}`);\nconst isUniqueViolation = (error: unknown) => error instanceof DatabaseError && error.cause.code === \"23505\";\n\nexport function createDataPersistence(options: DataPersistenceOptions): DataOverlayPersistence {\n const { db } = options;\n const table = options.table ?? userSurfaceStateTable;\n const feature = options.feature ?? SUMMON_SURFACE_FEATURE;\n\n const personId = async (): Promise<string> => {\n const explicit = options.userId?.();\n if (explicit) return explicit;\n const session = db.client.auth ? await db.client.auth.getSession() : null;\n const id = session?.data.session?.user?.id;\n if (!id) throw new Error(\"[overlays/persistence/data] no signed-in person: overlay state is saved per person\");\n return id;\n };\n\n const find = async (key: string, includeDeleted: boolean): Promise<Found | null> => {\n const userId = await personId();\n const rows = await listAll(db, table, (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature).eq(\"surface_key\", key), {\n columns: COLUMNS,\n includeDeleted,\n });\n // Unique per (user_id, feature, surface_key); a live row beats a soft-deleted one.\n return rows.find((row) => row.deleted_at === null) ?? rows[0] ?? null;\n };\n\n const conflict = (key: string, expected: number, current: number) => new PersistenceVersionConflictError(key, expected, current);\n\n return {\n perPerson: true,\n\n async load<T = unknown>(key: string): Promise<StoredValue<T> | null> {\n const row = await find(key, false);\n return row ? { value: row.state as T, version: row.version } : null;\n },\n\n async save<T = unknown>(key: string, value: T, saveOptions?: { readonly expectedVersion?: number }) {\n const expected = saveOptions?.expectedVersion;\n const state = value as unknown as Json;\n const row = await find(key, true);\n if (row && row.deleted_at === null) {\n if (expected === 0) throw conflict(key, 0, row.version);\n const result = await update(db, table, row.id, { state }, expected === undefined ? { overwrite: true } : { expectedVersion: expected });\n if (result.status === \"saved\") return { version: result.row.version };\n if (result.status === \"conflict\") throw conflict(key, expected ?? row.version, result.currentVersion);\n throw conflict(key, expected ?? row.version, 0); // vanished between read and write\n }\n if (expected !== undefined && expected !== 0) throw conflict(key, expected, 0);\n if (row) {\n // Revive the soft-deleted row (the unique key includes deleted rows).\n const result = await update(db, table, row.id, { state, deleted_at: null }, { expectedVersion: row.version });\n if (result.status === \"saved\") return { version: result.row.version };\n throw conflict(key, 0, result.status === \"conflict\" ? result.currentVersion : 0);\n }\n try {\n const created = await insert(db, table, { user_id: await personId(), feature, surface_key: key, state });\n return { version: created.version };\n } catch (error) {\n // Another device created it first.\n if (isUniqueViolation(error)) throw conflict(key, 0, (await find(key, true))?.version ?? 1);\n throw error;\n }\n },\n\n async remove(key: string) {\n const row = await find(key, false);\n if (!row) return;\n await removeRow(db, table, row.id, { overwrite: true });\n },\n\n async loadAll() {\n // The person's whole summon surface in one read: the workspace row and every saved\n // component state (rows are small; boot needs all of them anyway).\n const userId = await personId();\n const rows = await listAll(db, table, (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature), {\n columns: [\"surface_key\", \"state\", \"version\"],\n });\n const values = new Map<string, StoredValue>();\n for (const row of rows) values.set(row.surface_key, { value: row.state, version: row.version });\n return values;\n },\n\n async list(prefix: string) {\n const userId = await personId();\n const rows = await listAll(\n db,\n table,\n (q) => q.eq(\"user_id\", userId).eq(\"feature\", feature).like(\"surface_key\", `${likeEscape(prefix)}%`),\n { columns: [\"surface_key\"] },\n );\n return rows.map((row) => row.surface_key).filter((key) => key.startsWith(prefix)).sort();\n },\n };\n}\n","/**\n * Errors every persistence store throws in the same shape. Matched by `name`, never `instanceof`:\n * each section entry may carry its own copy of this module in a built consumer.\n */\nimport type { StoredValue } from \"@ai-matrx/summon\";\n\nexport const VERSION_CONFLICT_ERROR = \"OverlayPersistenceVersionConflict\";\nexport const LOAD_TIMEOUT_ERROR = \"OverlayPersistenceLoadTimeout\";\n\n/** A save whose `expectedVersion` no longer matches the stored version. */\nexport class PersistenceVersionConflictError extends Error {\n readonly key: string;\n readonly expectedVersion: number;\n readonly currentVersion: number;\n constructor(key: string, expectedVersion: number, currentVersion: number) {\n super(`[overlays/persistence] \"${key}\" is at version ${currentVersion}, not the expected ${expectedVersion}`);\n this.name = VERSION_CONFLICT_ERROR;\n this.key = key;\n this.expectedVersion = expectedVersion;\n this.currentVersion = currentVersion;\n }\n}\n\nexport function isVersionConflict(error: unknown): error is PersistenceVersionConflictError {\n return error instanceof Error && error.name === VERSION_CONFLICT_ERROR;\n}\n\n/**\n * The fast tier missed and the slow tier did not answer within the read budget. `pending` still\n * resolves the authoritative answer: a caller keeps every automatic writer gated until it does,\n * so an unknown stored value is never overwritten.\n */\nexport class PersistenceLoadTimeoutError extends Error {\n readonly key: string;\n readonly pending: Promise<StoredValue | null>;\n constructor(key: string, budgetMs: number, pending: Promise<StoredValue | null>) {\n super(`[overlays/persistence] \"${key}\" did not load within ${budgetMs}ms`);\n this.name = LOAD_TIMEOUT_ERROR;\n this.key = key;\n this.pending = pending;\n }\n}\n\nexport function isLoadTimeout(error: unknown): error is PersistenceLoadTimeoutError {\n return error instanceof Error && error.name === LOAD_TIMEOUT_ERROR;\n}\n"],"mappings":"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","names":[]}
@@ -882,19 +882,21 @@ function createOverlayPersistenceController(store, ports, options = {}) {
882
882
  const gen = generation;
883
883
  if (scope === "tab" && local) workspaceId ??= local.workspaces.currentId();
884
884
  else workspaceId = null;
885
+ const all = port.loadAll ? await port.loadAll() : null;
886
+ const bootValue = async (k) => all ? all.get(k) ?? null : loadValue(k);
885
887
  const ownKey = scoped(workspaceKey());
886
- let stored = await loadValue(ownKey);
888
+ let stored = await bootValue(ownKey);
887
889
  let expectedWorkspaceId = scope === "tab" && workspaceId ? workspaceId : void 0;
888
890
  if (stored) versions.set(ownKey, stored.version);
889
891
  let storedUnderOwnKey = stored !== null;
890
892
  if (!stored && scope === "tab" && seedFromPerson) {
891
893
  storedUnderOwnKey = false;
892
894
  const personKey = scoped(PERSON_WORKSPACE_KEY);
893
- stored = await loadValue(personKey);
895
+ stored = await bootValue(personKey);
894
896
  if (stored) versions.set(personKey, stored.version);
895
897
  expectedWorkspaceId = void 0;
896
898
  }
897
- const stateKeys = port.list ? await port.list(scoped(STATE_KEY_PREFIX)) : null;
899
+ const stateKeys = all ? [...all.keys()].sort() : port.list ? await port.list(scoped(STATE_KEY_PREFIX)) : null;
898
900
  if (gen !== generation || identityKey !== key) return;
899
901
  const viewport = options.viewport?.() ?? (() => {
900
902
  const { vw, vh } = (0, import_summon2.safeViewportDims)();
@@ -908,7 +910,7 @@ function createOverlayPersistenceController(store, ports, options = {}) {
908
910
  const prefix = scoped(STATE_KEY_PREFIX);
909
911
  const candidates = stateKeys ? stateKeys.filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length)) : sessions.map((s) => s.sessionKey);
910
912
  const loadedStates = await Promise.all(
911
- candidates.map(async (instance) => [instance, await loadValue(scoped(stateKey(instance))).catch(() => null)])
913
+ candidates.map(async (instance) => [instance, await bootValue(scoped(stateKey(instance))).catch(() => null)])
912
914
  );
913
915
  if (gen !== generation || identityKey !== key) return;
914
916
  const before = store.getState();