@tearleads/client-sdk 0.1.113 → 0.1.114

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.
Files changed (37) hide show
  1. package/data/persistence/container-contents/containerContentsPersistence.js +3 -8
  2. package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
  3. package/data/persistence/container-contents/containerHydrationPersistence.js +15 -5
  4. package/data/persistence/container-contents/containerHydrationPersistence.js.map +1 -1
  5. package/data/persistence/container-contents/containerMetadataMutationPersistence.js +8 -2
  6. package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
  7. package/data/persistence/container-contents/containerMetadataRows.js +34 -2
  8. package/data/persistence/container-contents/containerMetadataRows.js.map +1 -1
  9. package/data/persistence/container-contents/containerPersistence.d.ts +231 -0
  10. package/data/persistence/container-contents/containerPersistence.js +20 -16
  11. package/data/persistence/container-contents/containerPersistence.js.map +1 -1
  12. package/data/persistence/container-contents/containerRecoveryPersistence.js +15 -13
  13. package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
  14. package/data/persistence/container-contents/documentContainerProjectionPersistence.js +10 -2
  15. package/data/persistence/container-contents/documentContainerProjectionPersistence.js.map +1 -1
  16. package/data/persistence/container-contents/documentMoveIntentPersistence.js +29 -5
  17. package/data/persistence/container-contents/documentMoveIntentPersistence.js.map +1 -1
  18. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js +1 -1
  19. package/data/persistence/container-contents/documentPlacementIntentEnqueue.js.map +1 -1
  20. package/data/persistence/documents/internal/documentMutationPersistence.js +15 -15
  21. package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
  22. package/data/persistence/documents/internal/documentRows.js +26 -10
  23. package/data/persistence/documents/internal/documentRows.js.map +1 -1
  24. package/data/persistence/documents/internal/rowQueryPersistence.js +52 -27
  25. package/data/persistence/documents/internal/rowQueryPersistence.js.map +1 -1
  26. package/data/persistence/documents/records/documentHistoryPersistence.js +17 -11
  27. package/data/persistence/documents/records/documentHistoryPersistence.js.map +1 -1
  28. package/package.json +1 -1
  29. package/workflows/container-contents/documentQueries/index.js +21 -6
  30. package/workflows/container-contents/documentQueries/index.js.map +1 -1
  31. package/workflows/documents/documentMutationCommit.js +11 -12
  32. package/workflows/documents/documentMutationCommit.js.map +1 -1
  33. package/workflows/documents/documentMutationPreparation.d.ts +1 -0
  34. package/workflows/documents/documentMutationPreparation.js +1 -0
  35. package/workflows/documents/documentMutationPreparation.js.map +1 -1
  36. package/workflows/documents/documentPersistGuards.js +24 -0
  37. package/workflows/documents/documentPersistGuards.js.map +1 -1
@@ -19,11 +19,241 @@ export interface ContainerRecord {
19
19
  updatedAt?: string | null;
20
20
  }
21
21
  export declare function ensureContainerTables(execSql: ExecSql): Promise<void>;
22
+ interface SelectedContainerRecord {
23
+ effectiveAccessLevel: string | null;
24
+ id: string | null;
25
+ organizationId: string;
26
+ parentId: string | null;
27
+ metadataDocumentId: string | null;
28
+ systemSlot: string | null;
29
+ name: string;
30
+ icon: string | null;
31
+ localCreatedAt: string;
32
+ localUpdatedAt: string;
33
+ serverCreatedAt: string | null;
34
+ serverUpdatedAt: string | null;
35
+ }
36
+ export declare function mapSelectedContainerRecord(row: SelectedContainerRecord): ContainerRecord;
22
37
  export declare function saveContainerRows(input: {
23
38
  tx: ClientSQLiteTransactionScope;
24
39
  record: ContainerRecord;
25
40
  localUpdatedAt: string;
26
41
  }): Promise<ContainerRecord>;
42
+ /** The ONE select shape for a container record list. */
43
+ export declare const containerRecordSelection: {
44
+ effectiveAccessLevel: import("drizzle-orm/sqlite-core").SQLiteColumn<{
45
+ name: "effective_access_level";
46
+ tableName: "containers";
47
+ dataType: "string";
48
+ columnType: "SQLiteText";
49
+ data: string;
50
+ driverParam: string;
51
+ notNull: true;
52
+ hasDefault: true;
53
+ isPrimaryKey: false;
54
+ isAutoincrement: false;
55
+ hasRuntimeDefault: false;
56
+ enumValues: [string, ...string[]];
57
+ baseColumn: never;
58
+ identity: undefined;
59
+ generated: undefined;
60
+ }, {}, {
61
+ length: number | undefined;
62
+ }>;
63
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
64
+ name: "id";
65
+ tableName: "containers";
66
+ dataType: "string";
67
+ columnType: "SQLiteText";
68
+ data: string;
69
+ driverParam: string;
70
+ notNull: false;
71
+ hasDefault: false;
72
+ isPrimaryKey: false;
73
+ isAutoincrement: false;
74
+ hasRuntimeDefault: false;
75
+ enumValues: [string, ...string[]];
76
+ baseColumn: never;
77
+ identity: undefined;
78
+ generated: undefined;
79
+ }, {}, {
80
+ length: number | undefined;
81
+ }>;
82
+ organizationId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
83
+ name: "organization_id";
84
+ tableName: "containers";
85
+ dataType: "string";
86
+ columnType: "SQLiteText";
87
+ data: string;
88
+ driverParam: string;
89
+ notNull: true;
90
+ hasDefault: false;
91
+ isPrimaryKey: false;
92
+ isAutoincrement: false;
93
+ hasRuntimeDefault: false;
94
+ enumValues: [string, ...string[]];
95
+ baseColumn: never;
96
+ identity: undefined;
97
+ generated: undefined;
98
+ }, {}, {
99
+ length: number | undefined;
100
+ }>;
101
+ parentId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
102
+ name: "parent_id";
103
+ tableName: "containers";
104
+ dataType: "string";
105
+ columnType: "SQLiteText";
106
+ data: string;
107
+ driverParam: string;
108
+ notNull: false;
109
+ hasDefault: false;
110
+ isPrimaryKey: false;
111
+ isAutoincrement: false;
112
+ hasRuntimeDefault: false;
113
+ enumValues: [string, ...string[]];
114
+ baseColumn: never;
115
+ identity: undefined;
116
+ generated: undefined;
117
+ }, {}, {
118
+ length: number | undefined;
119
+ }>;
120
+ metadataDocumentId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
121
+ name: "metadata_document_id";
122
+ tableName: "containers";
123
+ dataType: "string";
124
+ columnType: "SQLiteText";
125
+ data: string;
126
+ driverParam: string;
127
+ notNull: false;
128
+ hasDefault: false;
129
+ isPrimaryKey: false;
130
+ isAutoincrement: false;
131
+ hasRuntimeDefault: false;
132
+ enumValues: [string, ...string[]];
133
+ baseColumn: never;
134
+ identity: undefined;
135
+ generated: undefined;
136
+ }, {}, {
137
+ length: number | undefined;
138
+ }>;
139
+ systemSlot: import("drizzle-orm/sqlite-core").SQLiteColumn<{
140
+ name: "system_slot";
141
+ tableName: "containers";
142
+ dataType: "string";
143
+ columnType: "SQLiteText";
144
+ data: string;
145
+ driverParam: string;
146
+ notNull: false;
147
+ hasDefault: false;
148
+ isPrimaryKey: false;
149
+ isAutoincrement: false;
150
+ hasRuntimeDefault: false;
151
+ enumValues: [string, ...string[]];
152
+ baseColumn: never;
153
+ identity: undefined;
154
+ generated: undefined;
155
+ }, {}, {
156
+ length: number | undefined;
157
+ }>;
158
+ name: import("drizzle-orm").SQL<string>;
159
+ icon: import("drizzle-orm/sqlite-core").SQLiteColumn<{
160
+ name: "icon";
161
+ tableName: "containers";
162
+ dataType: "string";
163
+ columnType: "SQLiteText";
164
+ data: string;
165
+ driverParam: string;
166
+ notNull: false;
167
+ hasDefault: false;
168
+ isPrimaryKey: false;
169
+ isAutoincrement: false;
170
+ hasRuntimeDefault: false;
171
+ enumValues: [string, ...string[]];
172
+ baseColumn: never;
173
+ identity: undefined;
174
+ generated: undefined;
175
+ }, {}, {
176
+ length: number | undefined;
177
+ }>;
178
+ localCreatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
179
+ name: "local_created_at";
180
+ tableName: "containers";
181
+ dataType: "string";
182
+ columnType: "SQLiteText";
183
+ data: string;
184
+ driverParam: string;
185
+ notNull: true;
186
+ hasDefault: false;
187
+ isPrimaryKey: false;
188
+ isAutoincrement: false;
189
+ hasRuntimeDefault: false;
190
+ enumValues: [string, ...string[]];
191
+ baseColumn: never;
192
+ identity: undefined;
193
+ generated: undefined;
194
+ }, {}, {
195
+ length: number | undefined;
196
+ }>;
197
+ localUpdatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
198
+ name: "local_updated_at";
199
+ tableName: "containers";
200
+ dataType: "string";
201
+ columnType: "SQLiteText";
202
+ data: string;
203
+ driverParam: string;
204
+ notNull: true;
205
+ hasDefault: false;
206
+ isPrimaryKey: false;
207
+ isAutoincrement: false;
208
+ hasRuntimeDefault: false;
209
+ enumValues: [string, ...string[]];
210
+ baseColumn: never;
211
+ identity: undefined;
212
+ generated: undefined;
213
+ }, {}, {
214
+ length: number | undefined;
215
+ }>;
216
+ serverCreatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
217
+ name: "server_created_at";
218
+ tableName: "containers";
219
+ dataType: "string";
220
+ columnType: "SQLiteText";
221
+ data: string;
222
+ driverParam: string;
223
+ notNull: false;
224
+ hasDefault: false;
225
+ isPrimaryKey: false;
226
+ isAutoincrement: false;
227
+ hasRuntimeDefault: false;
228
+ enumValues: [string, ...string[]];
229
+ baseColumn: never;
230
+ identity: undefined;
231
+ generated: undefined;
232
+ }, {}, {
233
+ length: number | undefined;
234
+ }>;
235
+ serverUpdatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
236
+ name: "server_updated_at";
237
+ tableName: "containers";
238
+ dataType: "string";
239
+ columnType: "SQLiteText";
240
+ data: string;
241
+ driverParam: string;
242
+ notNull: false;
243
+ hasDefault: false;
244
+ isPrimaryKey: false;
245
+ isAutoincrement: false;
246
+ hasRuntimeDefault: false;
247
+ enumValues: [string, ...string[]];
248
+ baseColumn: never;
249
+ identity: undefined;
250
+ generated: undefined;
251
+ }, {}, {
252
+ length: number | undefined;
253
+ }>;
254
+ };
255
+ /** Container lists sort by display name, case-insensitively. */
256
+ export declare const containerListOrder: import("drizzle-orm").SQL<unknown>;
27
257
  export declare function loadContainers(execSql: ExecSql): Promise<ContainerRecord[]>;
28
258
  export declare function loadContainerById(execSql: ExecSql, containerId: string): Promise<ContainerRecord | null>;
29
259
  export declare function loadContainerDisplayNamesByIds(execSql: ExecSql, ids: ReadonlyArray<string>): Promise<Map<string, string>>;
@@ -33,3 +263,4 @@ export declare function saveContainer(execSql: ExecSql, record: ContainerRecord)
33
263
  * can compose it inside one atomic transaction.
34
264
  */
35
265
  export declare function deleteContainerRowsInTransaction(tx: ClientSQLiteTransactionScope, ids: ReadonlyArray<string>): Promise<void>;
266
+ export {};
@@ -38,7 +38,7 @@ function applyContainerTimestampDefaults(record, localUpdatedAt) {
38
38
  updatedAt: serverUpdatedAt ?? localUpdatedAt,
39
39
  };
40
40
  }
41
- function mapSelectedContainerRecord(row) {
41
+ export function mapSelectedContainerRecord(row) {
42
42
  const systemSlot = isContainerSystemSlot(row.systemSlot)
43
43
  ? row.systemSlot
44
44
  : null;
@@ -99,25 +99,29 @@ export async function saveContainerRows(input) {
99
99
  .run();
100
100
  return nextRecord;
101
101
  }
102
+ /** The ONE select shape for a container record list. */
103
+ export const containerRecordSelection = {
104
+ effectiveAccessLevel: containers.effectiveAccessLevel,
105
+ id: containers.id,
106
+ organizationId: containers.organizationId,
107
+ parentId: containers.parentId,
108
+ metadataDocumentId: containers.metadataDocumentId,
109
+ systemSlot: containers.systemSlot,
110
+ name: containerDisplayName,
111
+ icon: containers.icon,
112
+ localCreatedAt: containers.localCreatedAt,
113
+ localUpdatedAt: containers.localUpdatedAt,
114
+ serverCreatedAt: containers.serverCreatedAt,
115
+ serverUpdatedAt: containers.serverUpdatedAt,
116
+ };
117
+ /** Container lists sort by display name, case-insensitively. */
118
+ export const containerListOrder = asc(sql `${containerDisplayName} COLLATE NOCASE`);
102
119
  export async function loadContainers(execSql) {
103
120
  const { db } = getClientSQLitePersistenceRuntime(execSql);
104
121
  const rows = await db
105
- .select({
106
- effectiveAccessLevel: containers.effectiveAccessLevel,
107
- id: containers.id,
108
- organizationId: containers.organizationId,
109
- parentId: containers.parentId,
110
- metadataDocumentId: containers.metadataDocumentId,
111
- systemSlot: containers.systemSlot,
112
- name: containerDisplayName,
113
- icon: containers.icon,
114
- localCreatedAt: containers.localCreatedAt,
115
- localUpdatedAt: containers.localUpdatedAt,
116
- serverCreatedAt: containers.serverCreatedAt,
117
- serverUpdatedAt: containers.serverUpdatedAt,
118
- })
122
+ .select(containerRecordSelection)
119
123
  .from(containers)
120
- .orderBy(asc(sql `${containerDisplayName} COLLATE NOCASE`));
124
+ .orderBy(containerListOrder);
121
125
  return rows.map((row) => mapSelectedContainerRecord(row));
122
126
  }
123
127
  export async function loadContainerById(execSql, containerId) {
@@ -1 +1 @@
1
- {"version":3,"file":"containerPersistence.js","sourceRoot":"","sources":["containerPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContainerAccessLevel } from \"@tearleads/crypto\";\nimport {\n type ContainerSystemSlot,\n isContainerSystemSlot,\n} from \"@tearleads/validators/containerSystemSlot\";\nimport { asc, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n DEFAULT_EFFECTIVE_ACCESS_LEVEL,\n normalizeEffectiveAccessLevel,\n} from \"../../keying/accessLevel\";\nimport { containers, containerTables } from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\n\nexport interface ContainerRecord {\n createdAt?: string | null;\n effectiveAccessLevel?: ContainerAccessLevel | null;\n id: string;\n organizationId: string;\n parentId: string | null;\n metadataDocumentId: string | null;\n systemSlot?: ContainerSystemSlot | null;\n name: string;\n icon: string | null;\n localCreatedAt?: string | null;\n localUpdatedAt?: string | null;\n serverCreatedAt?: string | null;\n serverUpdatedAt?: string | null;\n updatedAt?: string | null;\n}\n\ninterface ContainerRecordWithTimestampDefaults extends ContainerRecord {\n createdAt: string | null;\n localCreatedAt: string;\n localUpdatedAt: string;\n serverCreatedAt: string | null;\n serverUpdatedAt: string | null;\n updatedAt: string | null;\n}\n\nexport function ensureContainerTables(execSql: ExecSql): Promise<void> {\n return ensureSqlTables(execSql, containerTables);\n}\n\n// The ONE display-name fallback: a decrypted metadata name names the container;\n// without one the root reads '/' and everything else 'Untitled'.\nconst containerDisplayName = sql<string>`COALESCE(\n ${containers.displayName},\n CASE WHEN ${containers.parentId} IS NULL THEN '/' ELSE 'Untitled' END\n )`;\n\ninterface SelectedContainerRecord {\n effectiveAccessLevel: string | null;\n id: string | null;\n organizationId: string;\n parentId: string | null;\n metadataDocumentId: string | null;\n systemSlot: string | null;\n name: string;\n icon: string | null;\n localCreatedAt: string;\n localUpdatedAt: string;\n serverCreatedAt: string | null;\n serverUpdatedAt: string | null;\n}\n\nfunction toDisplayContainerRecord(record: ContainerRecord): ContainerRecord {\n return {\n ...record,\n createdAt: record.serverCreatedAt ?? record.localCreatedAt ?? null,\n updatedAt: record.serverUpdatedAt ?? record.localUpdatedAt ?? null,\n };\n}\n\nfunction applyContainerTimestampDefaults(\n record: ContainerRecord,\n localUpdatedAt: string,\n): ContainerRecordWithTimestampDefaults {\n const hasServerTimestamps =\n record.serverCreatedAt !== undefined ||\n record.serverUpdatedAt !== undefined;\n const localCreatedAt =\n record.localCreatedAt ??\n (!hasServerTimestamps ? record.createdAt : null) ??\n localUpdatedAt;\n const serverCreatedAt = record.serverCreatedAt ?? null;\n const serverUpdatedAt = record.serverUpdatedAt ?? null;\n\n return {\n ...record,\n createdAt: serverCreatedAt ?? localCreatedAt,\n localCreatedAt,\n localUpdatedAt,\n serverCreatedAt,\n serverUpdatedAt,\n updatedAt: serverUpdatedAt ?? localUpdatedAt,\n };\n}\n\nfunction mapSelectedContainerRecord(\n row: SelectedContainerRecord,\n): ContainerRecord {\n const systemSlot = isContainerSystemSlot(row.systemSlot)\n ? row.systemSlot\n : null;\n\n return toDisplayContainerRecord({\n effectiveAccessLevel: normalizeEffectiveAccessLevel(\n row.effectiveAccessLevel,\n ),\n id: String(row.id ?? \"\"),\n organizationId: row.organizationId,\n parentId: row.parentId,\n metadataDocumentId: row.metadataDocumentId,\n systemSlot,\n name: row.name,\n icon: row.icon && row.icon.length > 0 ? row.icon : null,\n localCreatedAt: row.localCreatedAt,\n localUpdatedAt: row.localUpdatedAt,\n serverCreatedAt: row.serverCreatedAt,\n serverUpdatedAt: row.serverUpdatedAt,\n });\n}\n\nexport async function saveContainerRows(input: {\n tx: ClientSQLiteTransactionScope;\n record: ContainerRecord;\n localUpdatedAt: string;\n}): Promise<ContainerRecord> {\n const { record, tx, localUpdatedAt } = input;\n const nextRecord = applyContainerTimestampDefaults(record, localUpdatedAt);\n const containerRow = {\n effectiveAccessLevel:\n nextRecord.effectiveAccessLevel ?? DEFAULT_EFFECTIVE_ACCESS_LEVEL,\n id: nextRecord.id,\n organizationId: nextRecord.organizationId,\n parentId: nextRecord.parentId,\n metadataDocumentId: nextRecord.metadataDocumentId,\n systemSlot: nextRecord.systemSlot ?? null,\n displayName: nextRecord.name,\n icon: nextRecord.icon,\n localCreatedAt: nextRecord.localCreatedAt,\n localUpdatedAt,\n serverCreatedAt: nextRecord.serverCreatedAt,\n serverUpdatedAt: nextRecord.serverUpdatedAt,\n };\n await tx\n .insert(containers)\n .values(containerRow)\n .onConflictDoUpdate({\n target: containers.id,\n set: {\n organizationId: containerRow.organizationId,\n parentId: containerRow.parentId,\n metadataDocumentId: containerRow.metadataDocumentId,\n systemSlot: containerRow.systemSlot,\n displayName: containerRow.displayName,\n icon: containerRow.icon,\n effectiveAccessLevel: containerRow.effectiveAccessLevel,\n localUpdatedAt: containerRow.localUpdatedAt,\n serverCreatedAt:\n record.serverCreatedAt === undefined\n ? sql`${containers.serverCreatedAt}`\n : containerRow.serverCreatedAt,\n serverUpdatedAt:\n record.serverUpdatedAt === undefined\n ? sql`${containers.serverUpdatedAt}`\n : containerRow.serverUpdatedAt,\n },\n })\n .run();\n\n return nextRecord;\n}\n\nexport async function loadContainers(\n execSql: ExecSql,\n): Promise<ContainerRecord[]> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n effectiveAccessLevel: containers.effectiveAccessLevel,\n id: containers.id,\n organizationId: containers.organizationId,\n parentId: containers.parentId,\n metadataDocumentId: containers.metadataDocumentId,\n systemSlot: containers.systemSlot,\n name: containerDisplayName,\n icon: containers.icon,\n localCreatedAt: containers.localCreatedAt,\n localUpdatedAt: containers.localUpdatedAt,\n serverCreatedAt: containers.serverCreatedAt,\n serverUpdatedAt: containers.serverUpdatedAt,\n })\n .from(containers)\n .orderBy(asc(sql`${containerDisplayName} COLLATE NOCASE`));\n\n return rows.map((row) => mapSelectedContainerRecord(row));\n}\n\nexport async function loadContainerById(\n execSql: ExecSql,\n containerId: string,\n): Promise<ContainerRecord | null> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n effectiveAccessLevel: containers.effectiveAccessLevel,\n id: containers.id,\n organizationId: containers.organizationId,\n parentId: containers.parentId,\n metadataDocumentId: containers.metadataDocumentId,\n systemSlot: containers.systemSlot,\n name: containerDisplayName,\n icon: containers.icon,\n localCreatedAt: containers.localCreatedAt,\n localUpdatedAt: containers.localUpdatedAt,\n serverCreatedAt: containers.serverCreatedAt,\n serverUpdatedAt: containers.serverUpdatedAt,\n })\n .from(containers)\n .where(eq(containers.id, containerId))\n .limit(1);\n\n return rows[0] ? mapSelectedContainerRecord(rows[0]) : null;\n}\n\nexport async function loadContainerDisplayNamesByIds(\n execSql: ExecSql,\n ids: ReadonlyArray<string>,\n): Promise<Map<string, string>> {\n const uniqueIds = Array.from(new Set(ids));\n if (uniqueIds.length === 0) {\n return new Map();\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n id: containers.id,\n name: containerDisplayName,\n })\n .from(containers)\n .where(inArray(containers.id, uniqueIds));\n\n return new Map(\n rows.flatMap((row) => {\n const displayName = row.name.trim();\n if (!row.id || displayName.length === 0) {\n return [];\n }\n\n return [[row.id, displayName]];\n }),\n );\n}\n\nexport async function saveContainer(\n execSql: ExecSql,\n record: ContainerRecord,\n): Promise<void> {\n const localUpdatedAt = new Date().toISOString();\n\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n await saveContainerRows({\n record,\n tx,\n localUpdatedAt,\n });\n });\n}\n\n/**\n * The container-row half of the delete cascade, statement-only so callers\n * can compose it inside one atomic transaction.\n */\nexport async function deleteContainerRowsInTransaction(\n tx: ClientSQLiteTransactionScope,\n ids: ReadonlyArray<string>,\n): Promise<void> {\n const uniqueIds = Array.from(new Set(ids));\n if (uniqueIds.length === 0) {\n return;\n }\n await tx.delete(containers).where(inArray(containers.id, uniqueIds)).run();\n}\n"]}
1
+ {"version":3,"file":"containerPersistence.js","sourceRoot":"","sources":["containerPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContainerAccessLevel } from \"@tearleads/crypto\";\nimport {\n type ContainerSystemSlot,\n isContainerSystemSlot,\n} from \"@tearleads/validators/containerSystemSlot\";\nimport { asc, eq, inArray, sql } from \"drizzle-orm\";\nimport {\n DEFAULT_EFFECTIVE_ACCESS_LEVEL,\n normalizeEffectiveAccessLevel,\n} from \"../../keying/accessLevel\";\nimport { containers, containerTables } from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\n\nexport interface ContainerRecord {\n createdAt?: string | null;\n effectiveAccessLevel?: ContainerAccessLevel | null;\n id: string;\n organizationId: string;\n parentId: string | null;\n metadataDocumentId: string | null;\n systemSlot?: ContainerSystemSlot | null;\n name: string;\n icon: string | null;\n localCreatedAt?: string | null;\n localUpdatedAt?: string | null;\n serverCreatedAt?: string | null;\n serverUpdatedAt?: string | null;\n updatedAt?: string | null;\n}\n\ninterface ContainerRecordWithTimestampDefaults extends ContainerRecord {\n createdAt: string | null;\n localCreatedAt: string;\n localUpdatedAt: string;\n serverCreatedAt: string | null;\n serverUpdatedAt: string | null;\n updatedAt: string | null;\n}\n\nexport function ensureContainerTables(execSql: ExecSql): Promise<void> {\n return ensureSqlTables(execSql, containerTables);\n}\n\n// The ONE display-name fallback: a decrypted metadata name names the container;\n// without one the root reads '/' and everything else 'Untitled'.\nconst containerDisplayName = sql<string>`COALESCE(\n ${containers.displayName},\n CASE WHEN ${containers.parentId} IS NULL THEN '/' ELSE 'Untitled' END\n )`;\n\ninterface SelectedContainerRecord {\n effectiveAccessLevel: string | null;\n id: string | null;\n organizationId: string;\n parentId: string | null;\n metadataDocumentId: string | null;\n systemSlot: string | null;\n name: string;\n icon: string | null;\n localCreatedAt: string;\n localUpdatedAt: string;\n serverCreatedAt: string | null;\n serverUpdatedAt: string | null;\n}\n\nfunction toDisplayContainerRecord(record: ContainerRecord): ContainerRecord {\n return {\n ...record,\n createdAt: record.serverCreatedAt ?? record.localCreatedAt ?? null,\n updatedAt: record.serverUpdatedAt ?? record.localUpdatedAt ?? null,\n };\n}\n\nfunction applyContainerTimestampDefaults(\n record: ContainerRecord,\n localUpdatedAt: string,\n): ContainerRecordWithTimestampDefaults {\n const hasServerTimestamps =\n record.serverCreatedAt !== undefined ||\n record.serverUpdatedAt !== undefined;\n const localCreatedAt =\n record.localCreatedAt ??\n (!hasServerTimestamps ? record.createdAt : null) ??\n localUpdatedAt;\n const serverCreatedAt = record.serverCreatedAt ?? null;\n const serverUpdatedAt = record.serverUpdatedAt ?? null;\n\n return {\n ...record,\n createdAt: serverCreatedAt ?? localCreatedAt,\n localCreatedAt,\n localUpdatedAt,\n serverCreatedAt,\n serverUpdatedAt,\n updatedAt: serverUpdatedAt ?? localUpdatedAt,\n };\n}\n\nexport function mapSelectedContainerRecord(\n row: SelectedContainerRecord,\n): ContainerRecord {\n const systemSlot = isContainerSystemSlot(row.systemSlot)\n ? row.systemSlot\n : null;\n\n return toDisplayContainerRecord({\n effectiveAccessLevel: normalizeEffectiveAccessLevel(\n row.effectiveAccessLevel,\n ),\n id: String(row.id ?? \"\"),\n organizationId: row.organizationId,\n parentId: row.parentId,\n metadataDocumentId: row.metadataDocumentId,\n systemSlot,\n name: row.name,\n icon: row.icon && row.icon.length > 0 ? row.icon : null,\n localCreatedAt: row.localCreatedAt,\n localUpdatedAt: row.localUpdatedAt,\n serverCreatedAt: row.serverCreatedAt,\n serverUpdatedAt: row.serverUpdatedAt,\n });\n}\n\nexport async function saveContainerRows(input: {\n tx: ClientSQLiteTransactionScope;\n record: ContainerRecord;\n localUpdatedAt: string;\n}): Promise<ContainerRecord> {\n const { record, tx, localUpdatedAt } = input;\n const nextRecord = applyContainerTimestampDefaults(record, localUpdatedAt);\n const containerRow = {\n effectiveAccessLevel:\n nextRecord.effectiveAccessLevel ?? DEFAULT_EFFECTIVE_ACCESS_LEVEL,\n id: nextRecord.id,\n organizationId: nextRecord.organizationId,\n parentId: nextRecord.parentId,\n metadataDocumentId: nextRecord.metadataDocumentId,\n systemSlot: nextRecord.systemSlot ?? null,\n displayName: nextRecord.name,\n icon: nextRecord.icon,\n localCreatedAt: nextRecord.localCreatedAt,\n localUpdatedAt,\n serverCreatedAt: nextRecord.serverCreatedAt,\n serverUpdatedAt: nextRecord.serverUpdatedAt,\n };\n await tx\n .insert(containers)\n .values(containerRow)\n .onConflictDoUpdate({\n target: containers.id,\n set: {\n organizationId: containerRow.organizationId,\n parentId: containerRow.parentId,\n metadataDocumentId: containerRow.metadataDocumentId,\n systemSlot: containerRow.systemSlot,\n displayName: containerRow.displayName,\n icon: containerRow.icon,\n effectiveAccessLevel: containerRow.effectiveAccessLevel,\n localUpdatedAt: containerRow.localUpdatedAt,\n serverCreatedAt:\n record.serverCreatedAt === undefined\n ? sql`${containers.serverCreatedAt}`\n : containerRow.serverCreatedAt,\n serverUpdatedAt:\n record.serverUpdatedAt === undefined\n ? sql`${containers.serverUpdatedAt}`\n : containerRow.serverUpdatedAt,\n },\n })\n .run();\n\n return nextRecord;\n}\n\n/** The ONE select shape for a container record list. */\nexport const containerRecordSelection = {\n effectiveAccessLevel: containers.effectiveAccessLevel,\n id: containers.id,\n organizationId: containers.organizationId,\n parentId: containers.parentId,\n metadataDocumentId: containers.metadataDocumentId,\n systemSlot: containers.systemSlot,\n name: containerDisplayName,\n icon: containers.icon,\n localCreatedAt: containers.localCreatedAt,\n localUpdatedAt: containers.localUpdatedAt,\n serverCreatedAt: containers.serverCreatedAt,\n serverUpdatedAt: containers.serverUpdatedAt,\n};\n\n/** Container lists sort by display name, case-insensitively. */\nexport const containerListOrder = asc(\n sql`${containerDisplayName} COLLATE NOCASE`,\n);\n\nexport async function loadContainers(\n execSql: ExecSql,\n): Promise<ContainerRecord[]> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select(containerRecordSelection)\n .from(containers)\n .orderBy(containerListOrder);\n\n return rows.map((row) => mapSelectedContainerRecord(row));\n}\n\nexport async function loadContainerById(\n execSql: ExecSql,\n containerId: string,\n): Promise<ContainerRecord | null> {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n effectiveAccessLevel: containers.effectiveAccessLevel,\n id: containers.id,\n organizationId: containers.organizationId,\n parentId: containers.parentId,\n metadataDocumentId: containers.metadataDocumentId,\n systemSlot: containers.systemSlot,\n name: containerDisplayName,\n icon: containers.icon,\n localCreatedAt: containers.localCreatedAt,\n localUpdatedAt: containers.localUpdatedAt,\n serverCreatedAt: containers.serverCreatedAt,\n serverUpdatedAt: containers.serverUpdatedAt,\n })\n .from(containers)\n .where(eq(containers.id, containerId))\n .limit(1);\n\n return rows[0] ? mapSelectedContainerRecord(rows[0]) : null;\n}\n\nexport async function loadContainerDisplayNamesByIds(\n execSql: ExecSql,\n ids: ReadonlyArray<string>,\n): Promise<Map<string, string>> {\n const uniqueIds = Array.from(new Set(ids));\n if (uniqueIds.length === 0) {\n return new Map();\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n id: containers.id,\n name: containerDisplayName,\n })\n .from(containers)\n .where(inArray(containers.id, uniqueIds));\n\n return new Map(\n rows.flatMap((row) => {\n const displayName = row.name.trim();\n if (!row.id || displayName.length === 0) {\n return [];\n }\n\n return [[row.id, displayName]];\n }),\n );\n}\n\nexport async function saveContainer(\n execSql: ExecSql,\n record: ContainerRecord,\n): Promise<void> {\n const localUpdatedAt = new Date().toISOString();\n\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n await saveContainerRows({\n record,\n tx,\n localUpdatedAt,\n });\n });\n}\n\n/**\n * The container-row half of the delete cascade, statement-only so callers\n * can compose it inside one atomic transaction.\n */\nexport async function deleteContainerRowsInTransaction(\n tx: ClientSQLiteTransactionScope,\n ids: ReadonlyArray<string>,\n): Promise<void> {\n const uniqueIds = Array.from(new Set(ids));\n if (uniqueIds.length === 0) {\n return;\n }\n await tx.delete(containers).where(inArray(containers.id, uniqueIds)).run();\n}\n"]}
@@ -116,24 +116,26 @@ export async function discardRetainedContainerMetadata(execSql, input) {
116
116
  return true;
117
117
  }, { behavior: "immediate" }));
118
118
  }
119
- export async function hasRetainedContainerMetadata(execSql, organizationId) {
120
- if (!organizationId)
121
- return false;
122
- await sqlContainerContentsPersistence.ensureSchema(execSql);
123
- const rows = await execSql(`SELECT 1 FROM dormant_container_metadata d
124
- WHERE d.organization_id = ? AND NOT EXISTS (SELECT 1 FROM containers c WHERE c.id = d.container_id) LIMIT 1`, [organizationId]);
125
- return rows.length > 0;
126
- }
119
+ /** Retained metadata of an organization's absent folders; binds the org. */
120
+ export const RETAINED_CONTAINER_METADATA_SQL = `SELECT 1 FROM dormant_container_metadata d
121
+ WHERE d.organization_id = ? AND NOT EXISTS (SELECT 1 FROM containers c WHERE c.id = d.container_id)`;
122
+ /**
123
+ * An organization's local folders whose queued move points at an unavailable
124
+ * parent; binds the move lane, then the organization.
125
+ */
126
+ export const RECOVERY_FOLDER_MOVES_SQL = `SELECT c.id FROM containers c JOIN local_intents i
127
+ ON i.lane = ? AND i.subject_id = c.id AND i.target_container_id = c.parent_id
128
+ WHERE c.organization_id = ? AND i.sync_status IN ('pending', 'blocked')
129
+ AND NOT EXISTS (SELECT 1 FROM containers p WHERE p.id = c.parent_id)`;
127
130
  /** Existing local folders whose queued move points at an unavailable parent. */
128
131
  export async function listRecoveryFolderMoveIds(execSql, organizationId) {
129
132
  if (!organizationId)
130
133
  return [];
131
134
  await sqlContainerContentsPersistence.ensureSchema(execSql);
132
- const rows = await execSql(`SELECT c.id FROM containers c JOIN local_intents i
133
- ON i.lane = ? AND i.subject_id = c.id AND i.target_container_id = c.parent_id
134
- WHERE c.organization_id = ? AND i.sync_status IN ('pending', 'blocked')
135
- AND NOT EXISTS (SELECT 1 FROM containers p WHERE p.id = c.parent_id)
136
- ORDER BY c.id`, [LOCAL_INTENT_LANES.containerMove, organizationId]);
135
+ const rows = await execSql(`${RECOVERY_FOLDER_MOVES_SQL} ORDER BY c.id`, [
136
+ LOCAL_INTENT_LANES.containerMove,
137
+ organizationId,
138
+ ]);
137
139
  return rows.map((row) => String(Reflect.get(row, "id")));
138
140
  }
139
141
  //# sourceMappingURL=containerRecoveryPersistence.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"containerRecoveryPersistence.js","sourceRoot":"","sources":["containerRecoveryPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,EACtB,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAEL,eAAe,EACf,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,+BAA+B,EAAE,MAAM,gCAAgC,CAAC;AACjF,OAAO,EAAE,kCAAkC,EAAE,MAAM,iCAAiC,CAAC;AACrF,OAAO,EAAE,gDAAgD,EAAE,MAAM,6BAA6B,CAAC;AAC/F,OAAO,EAAE,sCAAsC,EAAE,MAAM,6BAA6B,CAAC;AACrF,OAAO,EAAE,gDAAgD,EAAE,MAAM,4BAA4B,CAAC;AAE9F,6EAA6E;AAC7E,0EAA0E;AAC1E,KAAK,UAAU,gBAAgB,CAC7B,OAAgB,EAChB,WAAmB,EACnB,cAAsB;IAEtB,MAAM,CAAC,OAAO,CAAC,GAAG,MAAM,OAAO,CAC7B;;2DAEuD,EACvD,CAAC,WAAW,EAAE,cAAc,EAAE,WAAW,CAAC,CAC3C,CAAC;IACF,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI;QAC7B;YACE,WAAW;YACX,oGAAoG;SACrG;QACD,CAAC,8BAA8B,EAAE,8BAA8B,CAAC;QAChE;YACE,0BAA0B;YAC1B,qHAAqH;SACtH;KACF,EAAE,CAAC;QACF,QAAQ,CAAC,IAAI,CACX,MAAM,OAAO,CACX,UAAU,OAAO,SAAS,KAAK,wEAAwE,EACvG,CAAC,WAAW,CAAC,CACd,CACF,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAC3B,2OAA2O,EAC3O,CAAC,kBAAkB,CAAC,eAAe,EAAE,WAAW,CAAC,CAClD,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,OAAO,CACzB,+IAA+I,EAC/I,CAAC,kBAAkB,CAAC,aAAa,EAAE,WAAW,CAAC,CAChD,CAAC;IACF,MAAM,cAAc,GAAG;iFACwD,CAAC;IAChF,MAAM,aAAa,GAAG;QACpB,kBAAkB,CAAC,iBAAiB;QACpC,WAAW;QACX,WAAW;KACZ,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,OAAO,CACzB,8IAA8I,cAAc,cAAc,EAC1K,aAAa,CACd,CAAC;IACF,MAAM,OAAO,GAAG,MAAM,OAAO,CAC3B;0CACsC,cAAc,+CAA+C,EACnG,aAAa,CACd,CAAC;IACF,MAAM,QAAQ,GAAG,MAAM,aAAa,CAClC,IAAI,WAAW,EAAE,CAAC,MAAM,CACtB,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CACpE,CACF,CAAC;IACF,OAAO;QACL,QAAQ;QACR,kBAAkB,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAC5C,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,KAAK,IAAI,CAChD,CAAC,MAAM;QACR,mBAAmB,EAAE,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC;KACtE,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,OAAgB,EAChB,cAA6B;IAE7B,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,CAAC;IAC/B,MAAM,+BAA+B,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,eAAe,CAAC,OAAO,EAAE,6BAA6B,CAAC,CAAC;IAC9D,OAAO,wBAAwB,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CACxD,iCAAiC,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,KAAK,IAAI,EAAE;QAC/D,MAAM,IAAI,GAAG,MAAM,MAAM,CACvB;sDAC8C,EAC9C,CAAC,cAAc,CAAC,CACjB,CAAC;QACF,MAAM,MAAM,GAAG,EAAE,CAAC;QAClB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;YAC7D,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CACrC,MAAM,EACN,WAAW,EACX,cAAc,CACf,CAAC;YACF,IAAI,CAAC,QAAQ;gBAAE,SAAS;YACxB,MAAM,MAAM,GACV,MAAM,+BAA+B,CAAC,2BAA2B,CAC/D,MAAM,EACN,WAAW,CACZ,CAAC;YACJ,MAAM,CAAC,IAAI,CAAC;gBACV,WAAW;gBACX,cAAc;gBACd,GAAG,QAAQ;gBACX,eAAe,EAAE,MAAM,EAAE,eAAe,IAAI,EAAE;aAC/C,CAAC,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,OAAgB,EAChB,KAIC;IAED,MAAM,+BAA+B,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,eAAe,CAAC,OAAO,EAAE,6BAA6B,CAAC,CAAC;IAC9D,OAAO,wBAAwB,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CACxD,iCAAiC,CAAC,MAAM,CAAC,CAAC,WAAW,CACnD,KAAK,EAAE,EAAE,EAAE,EAAE;QACX,MAAM,OAAO,GAAG,MAAM,gBAAgB,CACpC,MAAM,EACN,KAAK,CAAC,WAAW,EACjB,KAAK,CAAC,cAAc,CACrB,CAAC;QACF,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAClE,MAAM,kCAAkC,CAAC;YACvC,EAAE;YACF,QAAQ,EAAE;gBACR;oBACE,WAAW,EAAE,KAAK,CAAC,WAAW;oBAC9B,MAAM,EAAE,SAAS;oBACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACpC;aACF;SACF,CAAC,CAAC;QACH,MAAM,gDAAgD,CAAC;YACrD,EAAE;YACF,QAAQ,EAAE;gBACR;oBACE,WAAW,EAAE,KAAK,CAAC,WAAW;oBAC9B,MAAM,EAAE,SAAS;oBACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACpC;aACF;SACF,CAAC,CAAC;QACH,MAAM,sCAAsC,CAAC;YAC3C,EAAE;YACF,YAAY,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC;SAClC,CAAC,CAAC;QACH,MAAM,EAAE;aACL,MAAM,CAAC,YAAY,CAAC;aACpB,KAAK,CACJ,GAAG,CACD,sBAAsB,EAAE,EACxB,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,CAC9C,CACF;aACA,GAAG,EAAE,CAAC;QACT,MAAM,gDAAgD,CAAC,EAAE,EAAE;YACzD,KAAK,CAAC,WAAW;SAClB,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC,EACD,EAAE,QAAQ,EAAE,WAAW,EAAE,CAC1B,CACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,OAAgB,EAChB,cAA6B;IAE7B,IAAI,CAAC,cAAc;QAAE,OAAO,KAAK,CAAC;IAClC,MAAM,+BAA+B,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB;gHAC4G,EAC5G,CAAC,cAAc,CAAC,CACjB,CAAC;IACF,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;AACzB,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAgB,EAChB,cAA6B;IAE7B,IAAI,CAAC,cAAc;QAAE,OAAO,EAAE,CAAC;IAC/B,MAAM,+BAA+B,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,OAAO,CACxB;;;;kBAIc,EACd,CAAC,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CACnD,CAAC;IACF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC","sourcesContent":["import { toFingerprint } from \"@tearleads/crypto\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n documentPlacementIntentTables,\n inContainerIntentLanes,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport { sqlContainerContentsPersistence } from \"./containerContentsPersistence\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport { discardLinkIntentsForRemovedContainers } from \"./documentLinkRemovalRepair\";\nimport { deleteContainerMetadataDocumentRowsInTransaction } from \"./dormantContainerMetadata\";\n\n// The token covers all local work the user is agreeing to discard, including\n// shared link intents. A second pane's edit invalidates the confirmation.\nasync function recoverySnapshot(\n execSql: ExecSql,\n containerId: string,\n organizationId: string,\n) {\n const [dormant] = await execSql(\n `SELECT container_id, organization_id FROM dormant_container_metadata\n WHERE container_id = ? AND organization_id = ?\n AND NOT EXISTS (SELECT 1 FROM containers WHERE id = ?)`,\n [containerId, organizationId, containerId],\n );\n if (!dormant) return null;\n const metadata = [];\n for (const [table, columns] of [\n [\n \"documents\",\n \"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version\",\n ],\n [\"document_history_checkpoints\", \"snapshot, end_version_vector\"],\n [\n \"document_history_updates\",\n \"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector\",\n ],\n ]) {\n metadata.push(\n await execSql(\n `SELECT ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id = ? ORDER BY rowid`,\n [containerId],\n ),\n );\n }\n const creates = await execSql(\n \"SELECT id, subject_id, target_container_id, intent_type, remote_container_id, remote_metadata_document_id, remote_metadata_access_state_hash FROM local_intents WHERE lane = ? AND subject_id = ? AND sync_status = 'pending' ORDER BY id\",\n [LOCAL_INTENT_LANES.containerCreate, containerId],\n );\n const moves = await execSql(\n \"SELECT id, subject_id, target_container_id, source_container_id, intent_type FROM local_intents WHERE lane = ? AND subject_id = ? ORDER BY id\",\n [LOCAL_INTENT_LANES.containerMove, containerId],\n );\n const placementWhere = `lane = ? AND (target_container_id = ? OR id IN\n (SELECT intent_id FROM document_intent_link_targets WHERE container_id = ?))`;\n const placementBind = [\n LOCAL_INTENT_LANES.documentPlacement,\n containerId,\n containerId,\n ];\n const links = await execSql(\n `SELECT id, local_id, subject_id, target_container_id, source_container_id, replace_linked_containers, intent_type FROM local_intents WHERE ${placementWhere} ORDER BY id`,\n placementBind,\n );\n const targets = await execSql(\n `SELECT intent_id, operation, container_id FROM document_intent_link_targets WHERE intent_id IN\n (SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`,\n placementBind,\n );\n const revision = await toFingerprint(\n new TextEncoder().encode(\n JSON.stringify([dormant, metadata, creates, moves, links, targets]),\n ),\n );\n return {\n revision,\n pendingUpdateCount: (metadata[2] ?? []).filter(\n (row) => Reflect.get(row, \"update_id\") !== null,\n ).length,\n hasStructuralIntent: creates.length + moves.length + links.length > 0,\n };\n}\n\nexport async function listRetainedContainerMetadata(\n execSql: ExecSql,\n organizationId: string | null,\n) {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(async () => {\n const rows = await locked(\n `SELECT container_id FROM dormant_container_metadata\n WHERE organization_id = ? ORDER BY container_id`,\n [organizationId],\n );\n const result = [];\n for (const row of rows) {\n const containerId = String(Reflect.get(row, \"container_id\"));\n const snapshot = await recoverySnapshot(\n locked,\n containerId,\n organizationId,\n );\n if (!snapshot) continue;\n const record =\n await sqlContainerContentsPersistence.loadContainerMetadataRecord(\n locked,\n containerId,\n );\n result.push({\n containerId,\n organizationId,\n ...snapshot,\n metadataUpdates: record?.metadataUpdates ?? \"\",\n });\n }\n return result;\n }),\n );\n}\n\n/** Explicit local discard, never called by remote discovery. */\nexport async function discardRetainedContainerMetadata(\n execSql: ExecSql,\n input: {\n containerId: string;\n organizationId: string;\n revision: string;\n },\n): Promise<boolean> {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(\n async (tx) => {\n const current = await recoverySnapshot(\n locked,\n input.containerId,\n input.organizationId,\n );\n if (!current || current.revision !== input.revision) return false;\n await recordContainerHydrationTombstones({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await repairDocumentsForRemovedContainersInTransaction({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await discardLinkIntentsForRemovedContainers({\n tx,\n containerIds: [input.containerId],\n });\n await tx\n .delete(localIntents)\n .where(\n and(\n inContainerIntentLanes(),\n eq(localIntents.subjectId, input.containerId),\n ),\n )\n .run();\n await deleteContainerMetadataDocumentRowsInTransaction(tx, [\n input.containerId,\n ]);\n return true;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n\nexport async function hasRetainedContainerMetadata(\n execSql: ExecSql,\n organizationId: string | null,\n): Promise<boolean> {\n if (!organizationId) return false;\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const rows = await execSql(\n `SELECT 1 FROM dormant_container_metadata d\n WHERE d.organization_id = ? AND NOT EXISTS (SELECT 1 FROM containers c WHERE c.id = d.container_id) LIMIT 1`,\n [organizationId],\n );\n return rows.length > 0;\n}\n\n/** Existing local folders whose queued move points at an unavailable parent. */\nexport async function listRecoveryFolderMoveIds(\n execSql: ExecSql,\n organizationId: string | null,\n): Promise<string[]> {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const rows = await execSql(\n `SELECT c.id FROM containers c JOIN local_intents i\n ON i.lane = ? AND i.subject_id = c.id AND i.target_container_id = c.parent_id\n WHERE c.organization_id = ? AND i.sync_status IN ('pending', 'blocked')\n AND NOT EXISTS (SELECT 1 FROM containers p WHERE p.id = c.parent_id)\n ORDER BY c.id`,\n [LOCAL_INTENT_LANES.containerMove, organizationId],\n );\n return rows.map((row) => String(Reflect.get(row, \"id\")));\n}\n"]}
1
+ {"version":3,"file":"containerRecoveryPersistence.js","sourceRoot":"","sources":["containerRecoveryPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { toFingerprint } from \"@tearleads/crypto\";\nimport { and, eq } from \"drizzle-orm\";\nimport {\n documentPlacementIntentTables,\n inContainerIntentLanes,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport { sqlContainerContentsPersistence } from \"./containerContentsPersistence\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport { discardLinkIntentsForRemovedContainers } from \"./documentLinkRemovalRepair\";\nimport { deleteContainerMetadataDocumentRowsInTransaction } from \"./dormantContainerMetadata\";\n\n// The token covers all local work the user is agreeing to discard, including\n// shared link intents. A second pane's edit invalidates the confirmation.\nasync function recoverySnapshot(\n execSql: ExecSql,\n containerId: string,\n organizationId: string,\n) {\n const [dormant] = await execSql(\n `SELECT container_id, organization_id FROM dormant_container_metadata\n WHERE container_id = ? AND organization_id = ?\n AND NOT EXISTS (SELECT 1 FROM containers WHERE id = ?)`,\n [containerId, organizationId, containerId],\n );\n if (!dormant) return null;\n const metadata = [];\n for (const [table, columns] of [\n [\n \"documents\",\n \"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version\",\n ],\n [\"document_history_checkpoints\", \"snapshot, end_version_vector\"],\n [\n \"document_history_updates\",\n \"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector\",\n ],\n ]) {\n metadata.push(\n await execSql(\n `SELECT ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id = ? ORDER BY rowid`,\n [containerId],\n ),\n );\n }\n const creates = await execSql(\n \"SELECT id, subject_id, target_container_id, intent_type, remote_container_id, remote_metadata_document_id, remote_metadata_access_state_hash FROM local_intents WHERE lane = ? AND subject_id = ? AND sync_status = 'pending' ORDER BY id\",\n [LOCAL_INTENT_LANES.containerCreate, containerId],\n );\n const moves = await execSql(\n \"SELECT id, subject_id, target_container_id, source_container_id, intent_type FROM local_intents WHERE lane = ? AND subject_id = ? ORDER BY id\",\n [LOCAL_INTENT_LANES.containerMove, containerId],\n );\n const placementWhere = `lane = ? AND (target_container_id = ? OR id IN\n (SELECT intent_id FROM document_intent_link_targets WHERE container_id = ?))`;\n const placementBind = [\n LOCAL_INTENT_LANES.documentPlacement,\n containerId,\n containerId,\n ];\n const links = await execSql(\n `SELECT id, local_id, subject_id, target_container_id, source_container_id, replace_linked_containers, intent_type FROM local_intents WHERE ${placementWhere} ORDER BY id`,\n placementBind,\n );\n const targets = await execSql(\n `SELECT intent_id, operation, container_id FROM document_intent_link_targets WHERE intent_id IN\n (SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`,\n placementBind,\n );\n const revision = await toFingerprint(\n new TextEncoder().encode(\n JSON.stringify([dormant, metadata, creates, moves, links, targets]),\n ),\n );\n return {\n revision,\n pendingUpdateCount: (metadata[2] ?? []).filter(\n (row) => Reflect.get(row, \"update_id\") !== null,\n ).length,\n hasStructuralIntent: creates.length + moves.length + links.length > 0,\n };\n}\n\nexport async function listRetainedContainerMetadata(\n execSql: ExecSql,\n organizationId: string | null,\n) {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(async () => {\n const rows = await locked(\n `SELECT container_id FROM dormant_container_metadata\n WHERE organization_id = ? ORDER BY container_id`,\n [organizationId],\n );\n const result = [];\n for (const row of rows) {\n const containerId = String(Reflect.get(row, \"container_id\"));\n const snapshot = await recoverySnapshot(\n locked,\n containerId,\n organizationId,\n );\n if (!snapshot) continue;\n const record =\n await sqlContainerContentsPersistence.loadContainerMetadataRecord(\n locked,\n containerId,\n );\n result.push({\n containerId,\n organizationId,\n ...snapshot,\n metadataUpdates: record?.metadataUpdates ?? \"\",\n });\n }\n return result;\n }),\n );\n}\n\n/** Explicit local discard, never called by remote discovery. */\nexport async function discardRetainedContainerMetadata(\n execSql: ExecSql,\n input: {\n containerId: string;\n organizationId: string;\n revision: string;\n },\n): Promise<boolean> {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n await ensureSqlTables(execSql, documentPlacementIntentTables);\n return runSerializedSqlMutation(execSql, async (locked) =>\n getClientSQLitePersistenceRuntime(locked).transaction(\n async (tx) => {\n const current = await recoverySnapshot(\n locked,\n input.containerId,\n input.organizationId,\n );\n if (!current || current.revision !== input.revision) return false;\n await recordContainerHydrationTombstones({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await repairDocumentsForRemovedContainersInTransaction({\n tx,\n removals: [\n {\n containerId: input.containerId,\n reason: \"deleted\",\n updatedAt: new Date().toISOString(),\n },\n ],\n });\n await discardLinkIntentsForRemovedContainers({\n tx,\n containerIds: [input.containerId],\n });\n await tx\n .delete(localIntents)\n .where(\n and(\n inContainerIntentLanes(),\n eq(localIntents.subjectId, input.containerId),\n ),\n )\n .run();\n await deleteContainerMetadataDocumentRowsInTransaction(tx, [\n input.containerId,\n ]);\n return true;\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n\n/** Retained metadata of an organization's absent folders; binds the org. */\nexport const RETAINED_CONTAINER_METADATA_SQL = `SELECT 1 FROM dormant_container_metadata d\n WHERE d.organization_id = ? AND NOT EXISTS (SELECT 1 FROM containers c WHERE c.id = d.container_id)`;\n\n/**\n * An organization's local folders whose queued move points at an unavailable\n * parent; binds the move lane, then the organization.\n */\nexport const RECOVERY_FOLDER_MOVES_SQL = `SELECT c.id FROM containers c JOIN local_intents i\n ON i.lane = ? AND i.subject_id = c.id AND i.target_container_id = c.parent_id\n WHERE c.organization_id = ? AND i.sync_status IN ('pending', 'blocked')\n AND NOT EXISTS (SELECT 1 FROM containers p WHERE p.id = c.parent_id)`;\n\n/** Existing local folders whose queued move points at an unavailable parent. */\nexport async function listRecoveryFolderMoveIds(\n execSql: ExecSql,\n organizationId: string | null,\n): Promise<string[]> {\n if (!organizationId) return [];\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const rows = await execSql(`${RECOVERY_FOLDER_MOVES_SQL} ORDER BY c.id`, [\n LOCAL_INTENT_LANES.containerMove,\n organizationId,\n ]);\n return rows.map((row) => String(Reflect.get(row, \"id\")));\n}\n"]}
@@ -1,10 +1,12 @@
1
1
  import { and, asc, eq, inArray, notInArray } from "drizzle-orm";
2
2
  import { documentContainerProjection, documentContainerProjectionTables, documentPlacementIntentTables, documentTables, inLocalIntentLane, LOCAL_INTENT_LANES, localIntents, } from "../../sqlite/schema/index.js";
3
3
  import { documentIntentLinkTargets } from "../../sqlite/schema/documentPlacementIntentSchema.js";
4
+ import { sqliteInsertBatchSize } from "../../sqlite/sqliteBindBudget.js";
4
5
  import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
5
6
  import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
6
7
  import { deleteContainerDocumentTombstoneHoldRowsForLinks, deleteContainerDocumentTombstoneHoldsForContainers, deleteContainerDocumentTombstoneHoldsForDocuments, } from "../documents/containerDocumentTombstoneHoldsPersistence.js";
7
8
  import { filterWritableDocumentPlacements, } from "./documentPlacement.js";
9
+ const LINK_INSERT_BATCH_SIZE = sqliteInsertBatchSize(documentContainerProjection);
8
10
  /** Link rows and their tombstone holds leave together on a remote reset. */
9
11
  export async function deleteDocumentPlacementRowsByContainerIds(tx, containerIds) {
10
12
  await tx
@@ -142,8 +144,14 @@ export const sqlDocumentContainerProjectionPersistence = {
142
144
  containerId,
143
145
  updatedAt,
144
146
  })));
145
- if (rows.length > 0)
146
- await tx.insert(documentContainerProjection).values(rows).run();
147
+ // A lane page of multi-link documents can carry more link rows than
148
+ // one statement may bind.
149
+ for (let index = 0; index < rows.length; index += LINK_INSERT_BATCH_SIZE) {
150
+ await tx
151
+ .insert(documentContainerProjection)
152
+ .values(rows.slice(index, index + LINK_INSERT_BATCH_SIZE))
153
+ .run();
154
+ }
147
155
  };
148
156
  if (options?.stillCurrent) {
149
157
  await runtime.guardedTransaction(replace, options.stillCurrent);
@@ -1 +1 @@
1
- {"version":3,"file":"documentContainerProjectionPersistence.js","sourceRoot":"","sources":["documentContainerProjectionPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, asc, eq, inArray, notInArray } from \"drizzle-orm\";\nimport {\n documentContainerProjection,\n documentContainerProjectionTables,\n documentPlacementIntentTables,\n documentTables,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { documentIntentLinkTargets } from \"../../sqlite/schema/documentPlacementIntentSchema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport {\n deleteContainerDocumentTombstoneHoldRowsForLinks,\n deleteContainerDocumentTombstoneHoldsForContainers,\n deleteContainerDocumentTombstoneHoldsForDocuments,\n} from \"../documents/containerDocumentTombstoneHoldsPersistence\";\n\nimport {\n type DocumentPlacementInput,\n type DocumentPlacementWriteOptions,\n filterWritableDocumentPlacements,\n} from \"./documentPlacement\";\n\ninterface DocumentContainerProjectionPersistence {\n ensureSchema: (execSql: ExecSql) => Promise<void>;\n listLinkedContainerIds: (\n execSql: ExecSql,\n documentId: string,\n ) => Promise<ReadonlyArray<string>>;\n listLinkedContainerIdsByDocumentIds: (\n execSql: ExecSql,\n documentIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyMap<string, ReadonlyArray<string>>>;\n listDocumentIdsByContainerIds: (\n execSql: ExecSql,\n containerIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyArray<string>>;\n replaceDocumentLinks: (\n execSql: ExecSql,\n documentId: string,\n containerIds: ReadonlyArray<string>,\n options?: DocumentPlacementWriteOptions,\n ) => Promise<void>;\n replaceDocumentLinksBatch: (\n execSql: ExecSql,\n inputs: ReadonlyArray<DocumentPlacementInput>,\n options?: DocumentPlacementWriteOptions,\n ) => Promise<void>;\n}\n\n/** Link rows and their tombstone holds leave together on a remote reset. */\nexport async function deleteDocumentPlacementRowsByContainerIds(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.containerId, containerIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForContainers(tx, containerIds);\n}\n\nexport async function deleteDocumentPlacementRowsByDocumentIds(\n tx: ClientSQLiteTransactionScope,\n documentIds: ReadonlyArray<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.documentId, documentIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForDocuments(tx, documentIds);\n}\n\n/** Removes one placement a verified tombstone retired. */\nexport async function deleteDocumentPlacementRowInTransaction(\n tx: ClientSQLiteTransactionScope,\n placement: { readonly containerId: string; readonly documentId: string },\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(\n and(\n eq(documentContainerProjection.documentId, placement.documentId),\n eq(documentContainerProjection.containerId, placement.containerId),\n ),\n )\n .run();\n}\n\n/** Removes a document's placements outside the containers its head links. */\nexport async function deleteUnlinkedDocumentPlacementRowsInTransaction(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n linkedContainerIds: ReadonlySet<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(\n linkedContainerIds.size === 0\n ? eq(documentContainerProjection.documentId, documentId)\n : and(\n eq(documentContainerProjection.documentId, documentId),\n notInArray(documentContainerProjection.containerId, [\n ...linkedContainerIds,\n ]),\n ),\n )\n .run();\n}\n\n/** Removes a deleted document's queued placement intents and link targets. */\nexport async function deleteDocumentPlacementIntentsInTransaction(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n): Promise<void> {\n const placementIntent = and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n eq(localIntents.subjectId, documentId),\n );\n await tx\n .delete(documentIntentLinkTargets)\n .where(\n inArray(\n documentIntentLinkTargets.intentId,\n tx\n .select({ id: localIntents.id })\n .from(localIntents)\n .where(placementIntent),\n ),\n )\n .run();\n await tx.delete(localIntents).where(placementIntent).run();\n}\n\nexport const sqlDocumentContainerProjectionPersistence: DocumentContainerProjectionPersistence =\n {\n async ensureSchema(execSql) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n },\n async listLinkedContainerIds(execSql, documentId) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ containerId: documentContainerProjection.containerId })\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, documentId))\n .orderBy(asc(documentContainerProjection.containerId));\n\n return rows.map((row) => row.containerId);\n },\n async listLinkedContainerIdsByDocumentIds(execSql, documentIds) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const uniqueDocumentIds = Array.from(new Set(documentIds));\n if (uniqueDocumentIds.length === 0) {\n return new Map();\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n documentId: documentContainerProjection.documentId,\n containerId: documentContainerProjection.containerId,\n })\n .from(documentContainerProjection)\n .where(\n inArray(documentContainerProjection.documentId, uniqueDocumentIds),\n )\n .orderBy(\n asc(documentContainerProjection.documentId),\n asc(documentContainerProjection.containerId),\n );\n\n const linkedContainerIdsByDocumentId = new Map<string, string[]>();\n for (const documentId of uniqueDocumentIds) {\n linkedContainerIdsByDocumentId.set(documentId, []);\n }\n\n for (const row of rows) {\n const linkedContainerIds =\n linkedContainerIdsByDocumentId.get(row.documentId) ?? [];\n linkedContainerIds.push(row.containerId);\n linkedContainerIdsByDocumentId.set(row.documentId, linkedContainerIds);\n }\n\n return linkedContainerIdsByDocumentId;\n },\n async listDocumentIdsByContainerIds(execSql, containerIds) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const uniqueContainerIds = Array.from(new Set(containerIds));\n if (uniqueContainerIds.length === 0) {\n return [];\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .selectDistinct({ documentId: documentContainerProjection.documentId })\n .from(documentContainerProjection)\n .where(\n inArray(documentContainerProjection.containerId, uniqueContainerIds),\n )\n .orderBy(asc(documentContainerProjection.documentId));\n\n return rows.map((row) => row.documentId);\n },\n async replaceDocumentLinks(execSql, documentId, containerIds, options) {\n await sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(\n execSql,\n [{ documentId, containerIds }],\n options,\n );\n },\n async replaceDocumentLinksBatch(execSql, inputs, options) {\n await ensureSqlTables(execSql, [\n ...documentContainerProjectionTables,\n ...documentPlacementIntentTables,\n ...documentTables,\n ]);\n const latest = new Map(inputs.map((input) => [input.documentId, input]));\n if (latest.size === 0) return;\n const runtime = getClientSQLitePersistenceRuntime(execSql);\n const replace = async (tx: ClientSQLiteTransactionScope) => {\n const writable = await filterWritableDocumentPlacements(\n tx,\n [...latest.values()],\n options,\n );\n if (writable.length === 0) return;\n // A placement the writer re-asserts is no longer tombstoned: a\n // listing that links it again, or a local move that owns it, releases\n // the hold so the document is not hidden until the next retry.\n if (options?.moveIntentId) {\n await deleteContainerDocumentTombstoneHoldsForDocuments(\n tx,\n writable.map((input) => input.documentId),\n );\n } else {\n await deleteContainerDocumentTombstoneHoldRowsForLinks(tx, writable);\n }\n await tx\n .delete(documentContainerProjection)\n .where(\n inArray(\n documentContainerProjection.documentId,\n writable.map((input) => input.documentId),\n ),\n )\n .run();\n const updatedAt = new Date().toISOString();\n const rows = writable.flatMap((input) =>\n Array.from(new Set(input.containerIds))\n .sort()\n .map((containerId) => ({\n documentId: input.documentId,\n containerId,\n updatedAt,\n })),\n );\n if (rows.length > 0)\n await tx.insert(documentContainerProjection).values(rows).run();\n };\n if (options?.stillCurrent) {\n await runtime.guardedTransaction(replace, options.stillCurrent);\n return;\n }\n await runtime.transaction(replace);\n },\n };\n"]}
1
+ {"version":3,"file":"documentContainerProjectionPersistence.js","sourceRoot":"","sources":["documentContainerProjectionPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAChE,OAAO,EACL,2BAA2B,EAC3B,iCAAiC,EACjC,6BAA6B,EAC7B,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,yBAAyB,EAAE,MAAM,mDAAmD,CAAC;AAC9F,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAEL,iCAAiC,GAClC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAgB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACvE,OAAO,EACL,gDAAgD,EAChD,kDAAkD,EAClD,iDAAiD,GAClD,MAAM,yDAAyD,CAAC;AAEjE,OAAO,EAGL,gCAAgC,GACjC,MAAM,qBAAqB,CAAC;AAE7B,MAAM,sBAAsB,GAAG,qBAAqB,CAClD,2BAA2B,CAC5B,CAAC;AA6BF,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,yCAAyC,CAC7D,EAAgC,EAChC,YAAmC;IAEnC,MAAM,EAAE;SACL,MAAM,CAAC,2BAA2B,CAAC;SACnC,KAAK,CAAC,OAAO,CAAC,2BAA2B,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;SACrE,GAAG,EAAE,CAAC;IACT,MAAM,kDAAkD,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wCAAwC,CAC5D,EAAgC,EAChC,WAAkC;IAElC,MAAM,EAAE;SACL,MAAM,CAAC,2BAA2B,CAAC;SACnC,KAAK,CAAC,OAAO,CAAC,2BAA2B,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;SACnE,GAAG,EAAE,CAAC;IACT,MAAM,iDAAiD,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;AAC3E,CAAC;AAED,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,uCAAuC,CAC3D,EAAgC,EAChC,SAAwE;IAExE,MAAM,EAAE;SACL,MAAM,CAAC,2BAA2B,CAAC;SACnC,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,2BAA2B,CAAC,UAAU,EAAE,SAAS,CAAC,UAAU,CAAC,EAChE,EAAE,CAAC,2BAA2B,CAAC,WAAW,EAAE,SAAS,CAAC,WAAW,CAAC,CACnE,CACF;SACA,GAAG,EAAE,CAAC;AACX,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,gDAAgD,CACpE,EAAgC,EAChC,UAAkB,EAClB,kBAAuC;IAEvC,MAAM,EAAE;SACL,MAAM,CAAC,2BAA2B,CAAC;SACnC,KAAK,CACJ,kBAAkB,CAAC,IAAI,KAAK,CAAC;QAC3B,CAAC,CAAC,EAAE,CAAC,2BAA2B,CAAC,UAAU,EAAE,UAAU,CAAC;QACxD,CAAC,CAAC,GAAG,CACD,EAAE,CAAC,2BAA2B,CAAC,UAAU,EAAE,UAAU,CAAC,EACtD,UAAU,CAAC,2BAA2B,CAAC,WAAW,EAAE;YAClD,GAAG,kBAAkB;SACtB,CAAC,CACH,CACN;SACA,GAAG,EAAE,CAAC;AACX,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,2CAA2C,CAC/D,EAAgC,EAChC,UAAkB;IAElB,MAAM,eAAe,GAAG,GAAG,CACzB,iBAAiB,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,EACvD,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,UAAU,CAAC,CACvC,CAAC;IACF,MAAM,EAAE;SACL,MAAM,CAAC,yBAAyB,CAAC;SACjC,KAAK,CACJ,OAAO,CACL,yBAAyB,CAAC,QAAQ,EAClC,EAAE;SACC,MAAM,CAAC,EAAE,EAAE,EAAE,YAAY,CAAC,EAAE,EAAE,CAAC;SAC/B,IAAI,CAAC,YAAY,CAAC;SAClB,KAAK,CAAC,eAAe,CAAC,CAC1B,CACF;SACA,GAAG,EAAE,CAAC;IACT,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,GAAG,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,CAAC,MAAM,yCAAyC,GACpD;IACE,KAAK,CAAC,YAAY,CAAC,OAAO;QACxB,MAAM,eAAe,CAAC,OAAO,EAAE,iCAAiC,CAAC,CAAC;IACpE,CAAC;IACD,KAAK,CAAC,sBAAsB,CAAC,OAAO,EAAE,UAAU;QAC9C,MAAM,eAAe,CAAC,OAAO,EAAE,iCAAiC,CAAC,CAAC;QAClE,MAAM,EAAE,EAAE,EAAE,GAAG,iCAAiC,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,EAAE;aAClB,MAAM,CAAC,EAAE,WAAW,EAAE,2BAA2B,CAAC,WAAW,EAAE,CAAC;aAChE,IAAI,CAAC,2BAA2B,CAAC;aACjC,KAAK,CAAC,EAAE,CAAC,2BAA2B,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;aAC7D,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,WAAW,CAAC,CAAC,CAAC;QAEzD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC5C,CAAC;IACD,KAAK,CAAC,mCAAmC,CAAC,OAAO,EAAE,WAAW;QAC5D,MAAM,eAAe,CAAC,OAAO,EAAE,iCAAiC,CAAC,CAAC;QAClE,MAAM,iBAAiB,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC;QAC3D,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACnC,OAAO,IAAI,GAAG,EAAE,CAAC;QACnB,CAAC;QAED,MAAM,EAAE,EAAE,EAAE,GAAG,iCAAiC,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,EAAE;aAClB,MAAM,CAAC;YACN,UAAU,EAAE,2BAA2B,CAAC,UAAU;YAClD,WAAW,EAAE,2BAA2B,CAAC,WAAW;SACrD,CAAC;aACD,IAAI,CAAC,2BAA2B,CAAC;aACjC,KAAK,CACJ,OAAO,CAAC,2BAA2B,CAAC,UAAU,EAAE,iBAAiB,CAAC,CACnE;aACA,OAAO,CACN,GAAG,CAAC,2BAA2B,CAAC,UAAU,CAAC,EAC3C,GAAG,CAAC,2BAA2B,CAAC,WAAW,CAAC,CAC7C,CAAC;QAEJ,MAAM,8BAA8B,GAAG,IAAI,GAAG,EAAoB,CAAC;QACnE,KAAK,MAAM,UAAU,IAAI,iBAAiB,EAAE,CAAC;YAC3C,8BAA8B,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,kBAAkB,GACtB,8BAA8B,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;YAC3D,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACzC,8BAA8B,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;QACzE,CAAC;QAED,OAAO,8BAA8B,CAAC;IACxC,CAAC;IACD,KAAK,CAAC,6BAA6B,CAAC,OAAO,EAAE,YAAY;QACvD,MAAM,eAAe,CAAC,OAAO,EAAE,iCAAiC,CAAC,CAAC;QAClE,MAAM,kBAAkB,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;QAC7D,IAAI,kBAAkB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,EAAE,EAAE,EAAE,GAAG,iCAAiC,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,EAAE;aAClB,cAAc,CAAC,EAAE,UAAU,EAAE,2BAA2B,CAAC,UAAU,EAAE,CAAC;aACtE,IAAI,CAAC,2BAA2B,CAAC;aACjC,KAAK,CACJ,OAAO,CAAC,2BAA2B,CAAC,WAAW,EAAE,kBAAkB,CAAC,CACrE;aACA,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,UAAU,CAAC,CAAC,CAAC;QAExD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC3C,CAAC;IACD,KAAK,CAAC,oBAAoB,CAAC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO;QACnE,MAAM,yCAAyC,CAAC,yBAAyB,CACvE,OAAO,EACP,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,EAC9B,OAAO,CACR,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO;QACtD,MAAM,eAAe,CAAC,OAAO,EAAE;YAC7B,GAAG,iCAAiC;YACpC,GAAG,6BAA6B;YAChC,GAAG,cAAc;SAClB,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;QACzE,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAC9B,MAAM,OAAO,GAAG,iCAAiC,CAAC,OAAO,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,KAAK,EAAE,EAAgC,EAAE,EAAE;YACzD,MAAM,QAAQ,GAAG,MAAM,gCAAgC,CACrD,EAAE,EACF,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EACpB,OAAO,CACR,CAAC;YACF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YAClC,+DAA+D;YAC/D,sEAAsE;YACtE,+DAA+D;YAC/D,IAAI,OAAO,EAAE,YAAY,EAAE,CAAC;gBAC1B,MAAM,iDAAiD,CACrD,EAAE,EACF,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAC1C,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,MAAM,gDAAgD,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;YACvE,CAAC;YACD,MAAM,EAAE;iBACL,MAAM,CAAC,2BAA2B,CAAC;iBACnC,KAAK,CACJ,OAAO,CACL,2BAA2B,CAAC,UAAU,EACtC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAC1C,CACF;iBACA,GAAG,EAAE,CAAC;YACT,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CACtC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;iBACpC,IAAI,EAAE;iBACN,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;gBACrB,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,WAAW;gBACX,SAAS;aACV,CAAC,CAAC,CACN,CAAC;YACF,oEAAoE;YACpE,0BAA0B;YAC1B,KACE,IAAI,KAAK,GAAG,CAAC,EACb,KAAK,GAAG,IAAI,CAAC,MAAM,EACnB,KAAK,IAAI,sBAAsB,EAC/B,CAAC;gBACD,MAAM,EAAE;qBACL,MAAM,CAAC,2BAA2B,CAAC;qBACnC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,sBAAsB,CAAC,CAAC;qBACzD,GAAG,EAAE,CAAC;YACX,CAAC;QACH,CAAC,CAAC;QACF,IAAI,OAAO,EAAE,YAAY,EAAE,CAAC;YAC1B,MAAM,OAAO,CAAC,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;YAChE,OAAO;QACT,CAAC;QACD,MAAM,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;CACF,CAAC","sourcesContent":["import { and, asc, eq, inArray, notInArray } from \"drizzle-orm\";\nimport {\n documentContainerProjection,\n documentContainerProjectionTables,\n documentPlacementIntentTables,\n documentTables,\n inLocalIntentLane,\n LOCAL_INTENT_LANES,\n localIntents,\n} from \"../../sqlite/schema\";\nimport { documentIntentLinkTargets } from \"../../sqlite/schema/documentPlacementIntentSchema\";\nimport { sqliteInsertBatchSize } from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport {\n deleteContainerDocumentTombstoneHoldRowsForLinks,\n deleteContainerDocumentTombstoneHoldsForContainers,\n deleteContainerDocumentTombstoneHoldsForDocuments,\n} from \"../documents/containerDocumentTombstoneHoldsPersistence\";\n\nimport {\n type DocumentPlacementInput,\n type DocumentPlacementWriteOptions,\n filterWritableDocumentPlacements,\n} from \"./documentPlacement\";\n\nconst LINK_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n documentContainerProjection,\n);\n\ninterface DocumentContainerProjectionPersistence {\n ensureSchema: (execSql: ExecSql) => Promise<void>;\n listLinkedContainerIds: (\n execSql: ExecSql,\n documentId: string,\n ) => Promise<ReadonlyArray<string>>;\n listLinkedContainerIdsByDocumentIds: (\n execSql: ExecSql,\n documentIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyMap<string, ReadonlyArray<string>>>;\n listDocumentIdsByContainerIds: (\n execSql: ExecSql,\n containerIds: ReadonlyArray<string>,\n ) => Promise<ReadonlyArray<string>>;\n replaceDocumentLinks: (\n execSql: ExecSql,\n documentId: string,\n containerIds: ReadonlyArray<string>,\n options?: DocumentPlacementWriteOptions,\n ) => Promise<void>;\n replaceDocumentLinksBatch: (\n execSql: ExecSql,\n inputs: ReadonlyArray<DocumentPlacementInput>,\n options?: DocumentPlacementWriteOptions,\n ) => Promise<void>;\n}\n\n/** Link rows and their tombstone holds leave together on a remote reset. */\nexport async function deleteDocumentPlacementRowsByContainerIds(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.containerId, containerIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForContainers(tx, containerIds);\n}\n\nexport async function deleteDocumentPlacementRowsByDocumentIds(\n tx: ClientSQLiteTransactionScope,\n documentIds: ReadonlyArray<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(inArray(documentContainerProjection.documentId, documentIds))\n .run();\n await deleteContainerDocumentTombstoneHoldsForDocuments(tx, documentIds);\n}\n\n/** Removes one placement a verified tombstone retired. */\nexport async function deleteDocumentPlacementRowInTransaction(\n tx: ClientSQLiteTransactionScope,\n placement: { readonly containerId: string; readonly documentId: string },\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(\n and(\n eq(documentContainerProjection.documentId, placement.documentId),\n eq(documentContainerProjection.containerId, placement.containerId),\n ),\n )\n .run();\n}\n\n/** Removes a document's placements outside the containers its head links. */\nexport async function deleteUnlinkedDocumentPlacementRowsInTransaction(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n linkedContainerIds: ReadonlySet<string>,\n): Promise<void> {\n await tx\n .delete(documentContainerProjection)\n .where(\n linkedContainerIds.size === 0\n ? eq(documentContainerProjection.documentId, documentId)\n : and(\n eq(documentContainerProjection.documentId, documentId),\n notInArray(documentContainerProjection.containerId, [\n ...linkedContainerIds,\n ]),\n ),\n )\n .run();\n}\n\n/** Removes a deleted document's queued placement intents and link targets. */\nexport async function deleteDocumentPlacementIntentsInTransaction(\n tx: ClientSQLiteTransactionScope,\n documentId: string,\n): Promise<void> {\n const placementIntent = and(\n inLocalIntentLane(LOCAL_INTENT_LANES.documentPlacement),\n eq(localIntents.subjectId, documentId),\n );\n await tx\n .delete(documentIntentLinkTargets)\n .where(\n inArray(\n documentIntentLinkTargets.intentId,\n tx\n .select({ id: localIntents.id })\n .from(localIntents)\n .where(placementIntent),\n ),\n )\n .run();\n await tx.delete(localIntents).where(placementIntent).run();\n}\n\nexport const sqlDocumentContainerProjectionPersistence: DocumentContainerProjectionPersistence =\n {\n async ensureSchema(execSql) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n },\n async listLinkedContainerIds(execSql, documentId) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ containerId: documentContainerProjection.containerId })\n .from(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, documentId))\n .orderBy(asc(documentContainerProjection.containerId));\n\n return rows.map((row) => row.containerId);\n },\n async listLinkedContainerIdsByDocumentIds(execSql, documentIds) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const uniqueDocumentIds = Array.from(new Set(documentIds));\n if (uniqueDocumentIds.length === 0) {\n return new Map();\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n documentId: documentContainerProjection.documentId,\n containerId: documentContainerProjection.containerId,\n })\n .from(documentContainerProjection)\n .where(\n inArray(documentContainerProjection.documentId, uniqueDocumentIds),\n )\n .orderBy(\n asc(documentContainerProjection.documentId),\n asc(documentContainerProjection.containerId),\n );\n\n const linkedContainerIdsByDocumentId = new Map<string, string[]>();\n for (const documentId of uniqueDocumentIds) {\n linkedContainerIdsByDocumentId.set(documentId, []);\n }\n\n for (const row of rows) {\n const linkedContainerIds =\n linkedContainerIdsByDocumentId.get(row.documentId) ?? [];\n linkedContainerIds.push(row.containerId);\n linkedContainerIdsByDocumentId.set(row.documentId, linkedContainerIds);\n }\n\n return linkedContainerIdsByDocumentId;\n },\n async listDocumentIdsByContainerIds(execSql, containerIds) {\n await ensureSqlTables(execSql, documentContainerProjectionTables);\n const uniqueContainerIds = Array.from(new Set(containerIds));\n if (uniqueContainerIds.length === 0) {\n return [];\n }\n\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .selectDistinct({ documentId: documentContainerProjection.documentId })\n .from(documentContainerProjection)\n .where(\n inArray(documentContainerProjection.containerId, uniqueContainerIds),\n )\n .orderBy(asc(documentContainerProjection.documentId));\n\n return rows.map((row) => row.documentId);\n },\n async replaceDocumentLinks(execSql, documentId, containerIds, options) {\n await sqlDocumentContainerProjectionPersistence.replaceDocumentLinksBatch(\n execSql,\n [{ documentId, containerIds }],\n options,\n );\n },\n async replaceDocumentLinksBatch(execSql, inputs, options) {\n await ensureSqlTables(execSql, [\n ...documentContainerProjectionTables,\n ...documentPlacementIntentTables,\n ...documentTables,\n ]);\n const latest = new Map(inputs.map((input) => [input.documentId, input]));\n if (latest.size === 0) return;\n const runtime = getClientSQLitePersistenceRuntime(execSql);\n const replace = async (tx: ClientSQLiteTransactionScope) => {\n const writable = await filterWritableDocumentPlacements(\n tx,\n [...latest.values()],\n options,\n );\n if (writable.length === 0) return;\n // A placement the writer re-asserts is no longer tombstoned: a\n // listing that links it again, or a local move that owns it, releases\n // the hold so the document is not hidden until the next retry.\n if (options?.moveIntentId) {\n await deleteContainerDocumentTombstoneHoldsForDocuments(\n tx,\n writable.map((input) => input.documentId),\n );\n } else {\n await deleteContainerDocumentTombstoneHoldRowsForLinks(tx, writable);\n }\n await tx\n .delete(documentContainerProjection)\n .where(\n inArray(\n documentContainerProjection.documentId,\n writable.map((input) => input.documentId),\n ),\n )\n .run();\n const updatedAt = new Date().toISOString();\n const rows = writable.flatMap((input) =>\n Array.from(new Set(input.containerIds))\n .sort()\n .map((containerId) => ({\n documentId: input.documentId,\n containerId,\n updatedAt,\n })),\n );\n // A lane page of multi-link documents can carry more link rows than\n // one statement may bind.\n for (\n let index = 0;\n index < rows.length;\n index += LINK_INSERT_BATCH_SIZE\n ) {\n await tx\n .insert(documentContainerProjection)\n .values(rows.slice(index, index + LINK_INSERT_BATCH_SIZE))\n .run();\n }\n };\n if (options?.stillCurrent) {\n await runtime.guardedTransaction(replace, options.stillCurrent);\n return;\n }\n await runtime.transaction(replace);\n },\n };\n"]}