@tearleads/client-sdk 0.1.113 → 0.1.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/persistence/container-contents/containerContentsPersistence.js +3 -8
- package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerHydrationPersistence.js +15 -5
- package/data/persistence/container-contents/containerHydrationPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js +8 -2
- package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerMetadataRows.js +34 -2
- package/data/persistence/container-contents/containerMetadataRows.js.map +1 -1
- package/data/persistence/container-contents/containerPersistence.d.ts +231 -0
- package/data/persistence/container-contents/containerPersistence.js +20 -16
- package/data/persistence/container-contents/containerPersistence.js.map +1 -1
- package/data/persistence/container-contents/containerRecoveryPersistence.js +167 -51
- package/data/persistence/container-contents/containerRecoveryPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentContainerProjectionPersistence.js +56 -19
- package/data/persistence/container-contents/documentContainerProjectionPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentMoveIntentPersistence.js +29 -5
- package/data/persistence/container-contents/documentMoveIntentPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentPlacement.js +24 -5
- package/data/persistence/container-contents/documentPlacement.js.map +1 -1
- package/data/persistence/container-contents/documentPlacementIntentEnqueue.js +1 -1
- package/data/persistence/container-contents/documentPlacementIntentEnqueue.js.map +1 -1
- package/data/persistence/documents/containerDocumentTombstoneHoldsPersistence.js +1 -1
- package/data/persistence/documents/containerDocumentTombstoneHoldsPersistence.js.map +1 -1
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js +43 -35
- package/data/persistence/documents/documentDiscoveryEvidencePersistence.js.map +1 -1
- package/data/persistence/documents/documentsPersistence.js +4 -58
- package/data/persistence/documents/documentsPersistence.js.map +1 -1
- package/data/persistence/documents/internal/containerDocumentTombstoneHolds.js +15 -11
- package/data/persistence/documents/internal/containerDocumentTombstoneHolds.js.map +1 -1
- package/data/persistence/documents/internal/containerDocumentTombstones.js +87 -35
- package/data/persistence/documents/internal/containerDocumentTombstones.js.map +1 -1
- package/data/persistence/documents/internal/discoveredDocumentPlacement.js +5 -2
- package/data/persistence/documents/internal/discoveredDocumentPlacement.js.map +1 -1
- package/data/persistence/documents/internal/discoveredDocumentUpsert.js +174 -0
- package/data/persistence/documents/internal/discoveredDocumentUpsert.js.map +1 -0
- package/data/persistence/documents/internal/documentMutationPersistence.js +15 -15
- package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
- package/data/persistence/documents/internal/documentRows.js +122 -11
- package/data/persistence/documents/internal/documentRows.js.map +1 -1
- package/data/persistence/documents/internal/rowQueryPersistence.js +70 -27
- package/data/persistence/documents/internal/rowQueryPersistence.js.map +1 -1
- package/data/persistence/documents/records/documentHistoryPersistence.js +17 -11
- package/data/persistence/documents/records/documentHistoryPersistence.js.map +1 -1
- package/data/persistence/documents/records/documentRecordPersistence.d.ts +5 -0
- package/data/persistence/documents/records/documentRecordPersistence.js +41 -6
- package/data/persistence/documents/records/documentRecordPersistence.js.map +1 -1
- package/data/persistence/documents/records/index.d.ts +1 -1
- package/data/persistence/documents/records/index.js +1 -1
- package/data/persistence/documents/records/index.js.map +1 -1
- package/data/persistence/keyingCheckpoints/documentPurgeCheckpointPersistence.d.ts +2 -0
- package/data/persistence/keyingCheckpoints/documentPurgeCheckpointPersistence.js +17 -1
- package/data/persistence/keyingCheckpoints/documentPurgeCheckpointPersistence.js.map +1 -1
- package/package.json +1 -1
- package/workflows/container-contents/documentQueries/index.js +21 -6
- package/workflows/container-contents/documentQueries/index.js.map +1 -1
- package/workflows/container-contents/remoteReset/containerRemoteReset.js +12 -7
- package/workflows/container-contents/remoteReset/containerRemoteReset.js.map +1 -1
- package/workflows/documents/documentMutationCommit.js +11 -12
- package/workflows/documents/documentMutationCommit.js.map +1 -1
- package/workflows/documents/documentMutationPreparation.d.ts +1 -0
- package/workflows/documents/documentMutationPreparation.js +1 -0
- package/workflows/documents/documentMutationPreparation.js.map +1 -1
- package/workflows/documents/documentPersistGuards.js +24 -0
- package/workflows/documents/documentPersistGuards.js.map +1 -1
- package/workflows/documents/remoteReset/documentRemoteReset.js +31 -18
- package/workflows/documents/remoteReset/documentRemoteReset.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(
|
|
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":"AACA,OAAO,EAEL,qBAAqB,GACtB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,8BAA8B,EAC9B,6BAA6B,GAC9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAEL,iCAAiC,GAClC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAgB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA4BvE,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO,eAAe,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AACnD,CAAC;AAED,gFAAgF;AAChF,iEAAiE;AACjE,MAAM,oBAAoB,GAAG,GAAG,CAAQ;MAClC,UAAU,CAAC,WAAW;gBACZ,UAAU,CAAC,QAAQ;IAC/B,CAAC;AAiBL,SAAS,wBAAwB,CAAC,MAAuB;IACvD,OAAO;QACL,GAAG,MAAM;QACT,SAAS,EAAE,MAAM,CAAC,eAAe,IAAI,MAAM,CAAC,cAAc,IAAI,IAAI;QAClE,SAAS,EAAE,MAAM,CAAC,eAAe,IAAI,MAAM,CAAC,cAAc,IAAI,IAAI;KACnE,CAAC;AACJ,CAAC;AAED,SAAS,+BAA+B,CACtC,MAAuB,EACvB,cAAsB;IAEtB,MAAM,mBAAmB,GACvB,MAAM,CAAC,eAAe,KAAK,SAAS;QACpC,MAAM,CAAC,eAAe,KAAK,SAAS,CAAC;IACvC,MAAM,cAAc,GAClB,MAAM,CAAC,cAAc;QACrB,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;QAChD,cAAc,CAAC;IACjB,MAAM,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,IAAI,CAAC;IACvD,MAAM,eAAe,GAAG,MAAM,CAAC,eAAe,IAAI,IAAI,CAAC;IAEvD,OAAO;QACL,GAAG,MAAM;QACT,SAAS,EAAE,eAAe,IAAI,cAAc;QAC5C,cAAc;QACd,cAAc;QACd,eAAe;QACf,eAAe;QACf,SAAS,EAAE,eAAe,IAAI,cAAc;KAC7C,CAAC;AACJ,CAAC;AAED,SAAS,0BAA0B,CACjC,GAA4B;IAE5B,MAAM,UAAU,GAAG,qBAAqB,CAAC,GAAG,CAAC,UAAU,CAAC;QACtD,CAAC,CAAC,GAAG,CAAC,UAAU;QAChB,CAAC,CAAC,IAAI,CAAC;IAET,OAAO,wBAAwB,CAAC;QAC9B,oBAAoB,EAAE,6BAA6B,CACjD,GAAG,CAAC,oBAAoB,CACzB;QACD,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;QACxB,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;QAC1C,UAAU;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;QACvD,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,eAAe,EAAE,GAAG,CAAC,eAAe;QACpC,eAAe,EAAE,GAAG,CAAC,eAAe;KACrC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAIvC;IACC,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,GAAG,KAAK,CAAC;IAC7C,MAAM,UAAU,GAAG,+BAA+B,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC3E,MAAM,YAAY,GAAG;QACnB,oBAAoB,EAClB,UAAU,CAAC,oBAAoB,IAAI,8BAA8B;QACnE,EAAE,EAAE,UAAU,CAAC,EAAE;QACjB,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,kBAAkB,EAAE,UAAU,CAAC,kBAAkB;QACjD,UAAU,EAAE,UAAU,CAAC,UAAU,IAAI,IAAI;QACzC,WAAW,EAAE,UAAU,CAAC,IAAI;QAC5B,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,cAAc;QACd,eAAe,EAAE,UAAU,CAAC,eAAe;QAC3C,eAAe,EAAE,UAAU,CAAC,eAAe;KAC5C,CAAC;IACF,MAAM,EAAE;SACL,MAAM,CAAC,UAAU,CAAC;SAClB,MAAM,CAAC,YAAY,CAAC;SACpB,kBAAkB,CAAC;QAClB,MAAM,EAAE,UAAU,CAAC,EAAE;QACrB,GAAG,EAAE;YACH,cAAc,EAAE,YAAY,CAAC,cAAc;YAC3C,QAAQ,EAAE,YAAY,CAAC,QAAQ;YAC/B,kBAAkB,EAAE,YAAY,CAAC,kBAAkB;YACnD,UAAU,EAAE,YAAY,CAAC,UAAU;YACnC,WAAW,EAAE,YAAY,CAAC,WAAW;YACrC,IAAI,EAAE,YAAY,CAAC,IAAI;YACvB,oBAAoB,EAAE,YAAY,CAAC,oBAAoB;YACvD,cAAc,EAAE,YAAY,CAAC,cAAc;YAC3C,eAAe,EACb,MAAM,CAAC,eAAe,KAAK,SAAS;gBAClC,CAAC,CAAC,GAAG,CAAA,GAAG,UAAU,CAAC,eAAe,EAAE;gBACpC,CAAC,CAAC,YAAY,CAAC,eAAe;YAClC,eAAe,EACb,MAAM,CAAC,eAAe,KAAK,SAAS;gBAClC,CAAC,CAAC,GAAG,CAAA,GAAG,UAAU,CAAC,eAAe,EAAE;gBACpC,CAAC,CAAC,YAAY,CAAC,eAAe;SACnC;KACF,CAAC;SACD,GAAG,EAAE,CAAC;IAET,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAgB;IAEhB,MAAM,EAAE,EAAE,EAAE,GAAG,iCAAiC,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,CAAC;QACN,oBAAoB,EAAE,UAAU,CAAC,oBAAoB;QACrD,EAAE,EAAE,UAAU,CAAC,EAAE;QACjB,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,kBAAkB,EAAE,UAAU,CAAC,kBAAkB;QACjD,UAAU,EAAE,UAAU,CAAC,UAAU;QACjC,IAAI,EAAE,oBAAoB;QAC1B,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,eAAe,EAAE,UAAU,CAAC,eAAe;QAC3C,eAAe,EAAE,UAAU,CAAC,eAAe;KAC5C,CAAC;SACD,IAAI,CAAC,UAAU,CAAC;SAChB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAA,GAAG,oBAAoB,iBAAiB,CAAC,CAAC,CAAC;IAE7D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,0BAA0B,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,OAAgB,EAChB,WAAmB;IAEnB,MAAM,EAAE,EAAE,EAAE,GAAG,iCAAiC,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,CAAC;QACN,oBAAoB,EAAE,UAAU,CAAC,oBAAoB;QACrD,EAAE,EAAE,UAAU,CAAC,EAAE;QACjB,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,kBAAkB,EAAE,UAAU,CAAC,kBAAkB;QACjD,UAAU,EAAE,UAAU,CAAC,UAAU;QACjC,IAAI,EAAE,oBAAoB;QAC1B,IAAI,EAAE,UAAU,CAAC,IAAI;QACrB,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,cAAc,EAAE,UAAU,CAAC,cAAc;QACzC,eAAe,EAAE,UAAU,CAAC,eAAe;QAC3C,eAAe,EAAE,UAAU,CAAC,eAAe;KAC5C,CAAC;SACD,IAAI,CAAC,UAAU,CAAC;SAChB,KAAK,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;SACrC,KAAK,CAAC,CAAC,CAAC,CAAC;IAEZ,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,OAAgB,EAChB,GAA0B;IAE1B,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;IAED,MAAM,EAAE,EAAE,EAAE,GAAG,iCAAiC,CAAC,OAAO,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,MAAM,EAAE;SAClB,MAAM,CAAC;QACN,EAAE,EAAE,UAAU,CAAC,EAAE;QACjB,IAAI,EAAE,oBAAoB;KAC3B,CAAC;SACD,IAAI,CAAC,UAAU,CAAC;SAChB,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;IAE5C,OAAO,IAAI,GAAG,CACZ,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,MAAM,WAAW,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACpC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxC,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CACH,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAgB,EAChB,MAAuB;IAEvB,MAAM,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAEhD,MAAM,iCAAiC,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QACxE,MAAM,iBAAiB,CAAC;YACtB,MAAM;YACN,EAAE;YACF,cAAc;SACf,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,EAAgC,EAChC,GAA0B;IAE1B,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,MAAM,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC7E,CAAC","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"]}
|
|
@@ -8,8 +8,32 @@ import { recordContainerHydrationTombstones } from "./containerHydrationPersiste
|
|
|
8
8
|
import { repairDocumentsForRemovedContainersInTransaction } from "./containerStructuralRepair.js";
|
|
9
9
|
import { discardLinkIntentsForRemovedContainers } from "./documentLinkRemovalRepair.js";
|
|
10
10
|
import { deleteContainerMetadataDocumentRowsInTransaction } from "./dormantContainerMetadata.js";
|
|
11
|
+
const RECOVERY_METADATA_TABLES = [
|
|
12
|
+
[
|
|
13
|
+
"documents",
|
|
14
|
+
"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version",
|
|
15
|
+
],
|
|
16
|
+
["document_history_checkpoints", "snapshot, end_version_vector"],
|
|
17
|
+
[
|
|
18
|
+
"document_history_updates",
|
|
19
|
+
"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector",
|
|
20
|
+
],
|
|
21
|
+
];
|
|
22
|
+
const CREATE_COLUMNS = "id, subject_id, target_container_id, intent_type, remote_container_id, remote_metadata_document_id, remote_metadata_access_state_hash";
|
|
23
|
+
const MOVE_COLUMNS = "id, subject_id, target_container_id, source_container_id, intent_type";
|
|
24
|
+
const LINK_COLUMNS = "id, local_id, subject_id, target_container_id, source_container_id, replace_linked_containers, intent_type";
|
|
25
|
+
const TARGET_COLUMNS = "intent_id, operation, container_id";
|
|
11
26
|
// The token covers all local work the user is agreeing to discard, including
|
|
12
27
|
// shared link intents. A second pane's edit invalidates the confirmation.
|
|
28
|
+
async function summarizeRecoverySnapshot(input) {
|
|
29
|
+
const { creates, dormant, links, metadata, moves, targets } = input;
|
|
30
|
+
const revision = await toFingerprint(new TextEncoder().encode(JSON.stringify([dormant, metadata, creates, moves, links, targets])));
|
|
31
|
+
return {
|
|
32
|
+
revision,
|
|
33
|
+
pendingUpdateCount: (metadata[2] ?? []).filter((row) => Reflect.get(row, "update_id") !== null).length,
|
|
34
|
+
hasStructuralIntent: creates.length + moves.length + links.length > 0,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
13
37
|
async function recoverySnapshot(execSql, containerId, organizationId) {
|
|
14
38
|
const [dormant] = await execSql(`SELECT container_id, organization_id FROM dormant_container_metadata
|
|
15
39
|
WHERE container_id = ? AND organization_id = ?
|
|
@@ -17,21 +41,11 @@ async function recoverySnapshot(execSql, containerId, organizationId) {
|
|
|
17
41
|
if (!dormant)
|
|
18
42
|
return null;
|
|
19
43
|
const metadata = [];
|
|
20
|
-
for (const [table, columns] of
|
|
21
|
-
[
|
|
22
|
-
"documents",
|
|
23
|
-
"document_id, recovery_document_id, recovery_generation, snapshot_end_version, pending_base_version",
|
|
24
|
-
],
|
|
25
|
-
["document_history_checkpoints", "snapshot, end_version_vector"],
|
|
26
|
-
[
|
|
27
|
-
"document_history_updates",
|
|
28
|
-
"id, update_data, origin, update_id, partial_start_version_vector, partial_end_version_vector, source_version_vector",
|
|
29
|
-
],
|
|
30
|
-
]) {
|
|
44
|
+
for (const [table, columns] of RECOVERY_METADATA_TABLES) {
|
|
31
45
|
metadata.push(await execSql(`SELECT ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id = ? ORDER BY rowid`, [containerId]));
|
|
32
46
|
}
|
|
33
|
-
const creates = await execSql(
|
|
34
|
-
const moves = await execSql(
|
|
47
|
+
const creates = await execSql(`SELECT ${CREATE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id = ? AND sync_status = 'pending' ORDER BY id`, [LOCAL_INTENT_LANES.containerCreate, containerId]);
|
|
48
|
+
const moves = await execSql(`SELECT ${MOVE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id = ? ORDER BY id`, [LOCAL_INTENT_LANES.containerMove, containerId]);
|
|
35
49
|
const placementWhere = `lane = ? AND (target_container_id = ? OR id IN
|
|
36
50
|
(SELECT intent_id FROM document_intent_link_targets WHERE container_id = ?))`;
|
|
37
51
|
const placementBind = [
|
|
@@ -39,40 +53,140 @@ async function recoverySnapshot(execSql, containerId, organizationId) {
|
|
|
39
53
|
containerId,
|
|
40
54
|
containerId,
|
|
41
55
|
];
|
|
42
|
-
const links = await execSql(`SELECT
|
|
43
|
-
const targets = await execSql(`SELECT
|
|
56
|
+
const links = await execSql(`SELECT ${LINK_COLUMNS} FROM local_intents WHERE ${placementWhere} ORDER BY id`, placementBind);
|
|
57
|
+
const targets = await execSql(`SELECT ${TARGET_COLUMNS} FROM document_intent_link_targets WHERE intent_id IN
|
|
44
58
|
(SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`, placementBind);
|
|
45
|
-
|
|
59
|
+
return summarizeRecoverySnapshot({
|
|
60
|
+
creates,
|
|
61
|
+
dormant,
|
|
62
|
+
links,
|
|
63
|
+
metadata,
|
|
64
|
+
moves,
|
|
65
|
+
targets,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Rows grouped by one column's value, dropping that column when asked. */
|
|
69
|
+
function groupRows(rows, column, drop) {
|
|
70
|
+
const groups = new Map();
|
|
71
|
+
for (const row of rows) {
|
|
72
|
+
const key = String(Reflect.get(row, column));
|
|
73
|
+
const kept = drop
|
|
74
|
+
? Object.fromEntries(Object.entries(row).filter(([name]) => name !== column))
|
|
75
|
+
: row;
|
|
76
|
+
const group = groups.get(key);
|
|
77
|
+
if (group)
|
|
78
|
+
group.push(kept);
|
|
79
|
+
else
|
|
80
|
+
groups.set(key, [kept]);
|
|
81
|
+
}
|
|
82
|
+
return groups;
|
|
83
|
+
}
|
|
84
|
+
/** Keeps SQL `IN` lists, bound twice by the placement reads, under the budget. */
|
|
85
|
+
const RECOVERY_CONTAINER_BATCH_SIZE = 400;
|
|
86
|
+
/** Every row the recovery snapshots of a batch of folders read. */
|
|
87
|
+
async function readRecoveryBatchRows(execSql, ids) {
|
|
88
|
+
const list = ids.map(() => "?").join(", ");
|
|
89
|
+
const metadata = [];
|
|
90
|
+
for (const [table, columns] of RECOVERY_METADATA_TABLES) {
|
|
91
|
+
metadata.push(groupRows(await execSql(`SELECT local_id, ${columns} FROM ${table} WHERE app_kind = 'container-metadata' AND local_id IN (${list}) ORDER BY local_id, rowid`, ids), "local_id", true));
|
|
92
|
+
}
|
|
93
|
+
const creates = groupRows(await execSql(`SELECT ${CREATE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id IN (${list}) AND sync_status = 'pending' ORDER BY subject_id, id`, [LOCAL_INTENT_LANES.containerCreate, ...ids]), "subject_id", false);
|
|
94
|
+
const moves = groupRows(await execSql(`SELECT ${MOVE_COLUMNS} FROM local_intents WHERE lane = ? AND subject_id IN (${list}) ORDER BY subject_id, id`, [LOCAL_INTENT_LANES.containerMove, ...ids]), "subject_id", false);
|
|
95
|
+
const placementWhere = `lane = ? AND (target_container_id IN (${list}) OR id IN
|
|
96
|
+
(SELECT intent_id FROM document_intent_link_targets WHERE container_id IN (${list})))`;
|
|
97
|
+
const placementBind = [LOCAL_INTENT_LANES.documentPlacement, ...ids, ...ids];
|
|
98
|
+
// Every placement intent any folder in the batch reaches, and ALL link
|
|
99
|
+
// targets of those intents: a folder's share is then the intents that
|
|
100
|
+
// target it or carry a link target in it, as the per-folder read selects.
|
|
101
|
+
const links = await execSql(`SELECT ${LINK_COLUMNS} FROM local_intents WHERE ${placementWhere} ORDER BY id`, placementBind);
|
|
102
|
+
const targets = await execSql(`SELECT ${TARGET_COLUMNS} FROM document_intent_link_targets WHERE intent_id IN
|
|
103
|
+
(SELECT id FROM local_intents WHERE ${placementWhere}) ORDER BY intent_id, container_id, operation`, placementBind);
|
|
104
|
+
return { creates, links, metadata, moves, targets };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Each folder's share of a batch's placement rows, indexed in one pass: the
|
|
108
|
+
* intents that target the folder or carry a link target in it (in intent id
|
|
109
|
+
* order), and all link targets of those intents (in the per-folder order).
|
|
110
|
+
*/
|
|
111
|
+
function indexPlacementRows(rows) {
|
|
112
|
+
const targetsByIntent = groupRows(rows.targets, "intent_id", false);
|
|
113
|
+
const linksByFolder = new Map();
|
|
114
|
+
for (const link of rows.links) {
|
|
115
|
+
const intentId = String(Reflect.get(link, "id"));
|
|
116
|
+
const folders = new Set([
|
|
117
|
+
String(Reflect.get(link, "target_container_id")),
|
|
118
|
+
...(targetsByIntent.get(intentId) ?? []).map((target) => String(Reflect.get(target, "container_id"))),
|
|
119
|
+
]);
|
|
120
|
+
for (const folder of folders) {
|
|
121
|
+
const folderLinks = linksByFolder.get(folder);
|
|
122
|
+
if (folderLinks)
|
|
123
|
+
folderLinks.push(link);
|
|
124
|
+
else
|
|
125
|
+
linksByFolder.set(folder, [link]);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { linksByFolder, targetsByIntent };
|
|
129
|
+
}
|
|
130
|
+
/** One folder's snapshot from its batch's rows, in the per-folder shape. */
|
|
131
|
+
async function folderRecoverySnapshot(rows, placements, containerId, dormant) {
|
|
132
|
+
const links = placements.linksByFolder.get(containerId) ?? [];
|
|
133
|
+
// Links are in intent id order and targets in (intent id, container,
|
|
134
|
+
// operation) order, so concatenating per link keeps the per-folder order.
|
|
135
|
+
const targets = links.flatMap((link) => placements.targetsByIntent.get(String(Reflect.get(link, "id"))) ?? []);
|
|
136
|
+
const metadata = rows.metadata.map((byFolder) => byFolder.get(containerId) ?? []);
|
|
137
|
+
// The metadata record's updates are its checkpoint snapshot, read above; a
|
|
138
|
+
// folder with no record has none.
|
|
139
|
+
const snapshotRow = (metadata[1] ?? [])[0];
|
|
46
140
|
return {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
141
|
+
containerId,
|
|
142
|
+
metadataUpdates: (metadata[0] ?? []).length > 0 && snapshotRow
|
|
143
|
+
? String(Reflect.get(snapshotRow, "snapshot") ?? "")
|
|
144
|
+
: "",
|
|
145
|
+
snapshot: await summarizeRecoverySnapshot({
|
|
146
|
+
creates: rows.creates.get(containerId) ?? [],
|
|
147
|
+
dormant,
|
|
148
|
+
links,
|
|
149
|
+
metadata,
|
|
150
|
+
moves: rows.moves.get(containerId) ?? [],
|
|
151
|
+
targets,
|
|
152
|
+
}),
|
|
50
153
|
};
|
|
51
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* `recoverySnapshot` for every retained folder of an organization, one read
|
|
157
|
+
* per table for each batch of folders. Each folder's rows are grouped in the
|
|
158
|
+
* per-folder order (`local_id, rowid` for metadata; `subject_id, id` for
|
|
159
|
+
* folder intents), so its revision is byte-identical to `recoverySnapshot`'s,
|
|
160
|
+
* which a later discard compares.
|
|
161
|
+
*/
|
|
162
|
+
async function recoverySnapshots(execSql, organizationId) {
|
|
163
|
+
const dormantRows = await execSql(`SELECT container_id, organization_id FROM dormant_container_metadata d
|
|
164
|
+
WHERE organization_id = ?
|
|
165
|
+
AND NOT EXISTS (SELECT 1 FROM containers WHERE id = d.container_id)
|
|
166
|
+
ORDER BY container_id`, [organizationId]);
|
|
167
|
+
const result = [];
|
|
168
|
+
for (let offset = 0; offset < dormantRows.length; offset += RECOVERY_CONTAINER_BATCH_SIZE) {
|
|
169
|
+
const batch = dormantRows.slice(offset, offset + RECOVERY_CONTAINER_BATCH_SIZE);
|
|
170
|
+
const ids = batch.map((row) => String(Reflect.get(row, "container_id")));
|
|
171
|
+
const rows = await readRecoveryBatchRows(execSql, ids);
|
|
172
|
+
const placements = indexPlacementRows(rows);
|
|
173
|
+
for (const [index, dormant] of batch.entries()) {
|
|
174
|
+
result.push(await folderRecoverySnapshot(rows, placements, ids[index] ?? "", dormant));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
52
179
|
export async function listRetainedContainerMetadata(execSql, organizationId) {
|
|
53
180
|
if (!organizationId)
|
|
54
181
|
return [];
|
|
55
182
|
await sqlContainerContentsPersistence.ensureSchema(execSql);
|
|
56
183
|
await ensureSqlTables(execSql, documentPlacementIntentTables);
|
|
57
|
-
return runSerializedSqlMutation(execSql, async (locked) => getClientSQLitePersistenceRuntime(locked).transaction(async () => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const snapshot = await recoverySnapshot(locked, containerId, organizationId);
|
|
64
|
-
if (!snapshot)
|
|
65
|
-
continue;
|
|
66
|
-
const record = await sqlContainerContentsPersistence.loadContainerMetadataRecord(locked, containerId);
|
|
67
|
-
result.push({
|
|
68
|
-
containerId,
|
|
69
|
-
organizationId,
|
|
70
|
-
...snapshot,
|
|
71
|
-
metadataUpdates: record?.metadataUpdates ?? "",
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
return result;
|
|
75
|
-
}));
|
|
184
|
+
return runSerializedSqlMutation(execSql, async (locked) => getClientSQLitePersistenceRuntime(locked).transaction(async () => (await recoverySnapshots(locked, organizationId)).map(({ containerId, metadataUpdates, snapshot }) => ({
|
|
185
|
+
containerId,
|
|
186
|
+
organizationId,
|
|
187
|
+
...snapshot,
|
|
188
|
+
metadataUpdates,
|
|
189
|
+
}))));
|
|
76
190
|
}
|
|
77
191
|
/** Explicit local discard, never called by remote discovery. */
|
|
78
192
|
export async function discardRetainedContainerMetadata(execSql, input) {
|
|
@@ -116,24 +230,26 @@ export async function discardRetainedContainerMetadata(execSql, input) {
|
|
|
116
230
|
return true;
|
|
117
231
|
}, { behavior: "immediate" }));
|
|
118
232
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
233
|
+
/** Retained metadata of an organization's absent folders; binds the org. */
|
|
234
|
+
export const RETAINED_CONTAINER_METADATA_SQL = `SELECT 1 FROM dormant_container_metadata d
|
|
235
|
+
WHERE d.organization_id = ? AND NOT EXISTS (SELECT 1 FROM containers c WHERE c.id = d.container_id)`;
|
|
236
|
+
/**
|
|
237
|
+
* An organization's local folders whose queued move points at an unavailable
|
|
238
|
+
* parent; binds the move lane, then the organization.
|
|
239
|
+
*/
|
|
240
|
+
export const RECOVERY_FOLDER_MOVES_SQL = `SELECT c.id FROM containers c JOIN local_intents i
|
|
241
|
+
ON i.lane = ? AND i.subject_id = c.id AND i.target_container_id = c.parent_id
|
|
242
|
+
WHERE c.organization_id = ? AND i.sync_status IN ('pending', 'blocked')
|
|
243
|
+
AND NOT EXISTS (SELECT 1 FROM containers p WHERE p.id = c.parent_id)`;
|
|
127
244
|
/** Existing local folders whose queued move points at an unavailable parent. */
|
|
128
245
|
export async function listRecoveryFolderMoveIds(execSql, organizationId) {
|
|
129
246
|
if (!organizationId)
|
|
130
247
|
return [];
|
|
131
248
|
await sqlContainerContentsPersistence.ensureSchema(execSql);
|
|
132
|
-
const rows = await execSql(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
ORDER BY c.id`, [LOCAL_INTENT_LANES.containerMove, organizationId]);
|
|
249
|
+
const rows = await execSql(`${RECOVERY_FOLDER_MOVES_SQL} ORDER BY c.id`, [
|
|
250
|
+
LOCAL_INTENT_LANES.containerMove,
|
|
251
|
+
organizationId,
|
|
252
|
+
]);
|
|
137
253
|
return rows.map((row) => String(Reflect.get(row, "id")));
|
|
138
254
|
}
|
|
139
255
|
//# sourceMappingURL=containerRecoveryPersistence.js.map
|