@earendil-works/pi-durable 0.87.0 → 0.87.1
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/README.md +29 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{memory-storage.d.ts → storage/memory.d.ts} +12 -6
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/{memory-storage.js → storage/memory.js} +236 -24
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/sqlite/database.d.ts +26 -0
- package/dist/storage/sqlite/database.d.ts.map +1 -0
- package/dist/storage/sqlite/database.js +2 -0
- package/dist/storage/sqlite/database.js.map +1 -0
- package/dist/storage/sqlite/index.d.ts +4 -0
- package/dist/storage/sqlite/index.d.ts.map +1 -0
- package/dist/storage/sqlite/index.js +3 -0
- package/dist/storage/sqlite/index.js.map +1 -0
- package/dist/storage/sqlite/migrations.d.ts +11 -0
- package/dist/storage/sqlite/migrations.d.ts.map +1 -0
- package/dist/storage/sqlite/migrations.js +104 -0
- package/dist/storage/sqlite/migrations.js.map +1 -0
- package/dist/storage/sqlite/node.d.ts +25 -0
- package/dist/storage/sqlite/node.d.ts.map +1 -0
- package/dist/storage/sqlite/node.js +97 -0
- package/dist/storage/sqlite/node.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +45 -0
- package/dist/storage/sqlite/storage.d.ts.map +1 -0
- package/dist/storage/sqlite/storage.js +524 -0
- package/dist/storage/sqlite/storage.js.map +1 -0
- package/dist/types.d.ts +113 -34
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/pico-v5-chord-usage.md +64 -63
- package/docs/pico-v5-handoff.md +92 -57
- package/docs/pico-v5.md +519 -350
- package/package.json +20 -3
- package/dist/memory-storage.d.ts.map +0 -1
- package/dist/memory-storage.js.map +0 -1
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { applyImmutable } from "@earendil-works/chord/delta";
|
|
2
|
+
import { applySqliteMigrations } from "./migrations.js";
|
|
3
|
+
const parseJson = (value) => JSON.parse(value);
|
|
4
|
+
const encodeJson = (value) => JSON.stringify(value);
|
|
5
|
+
// Some SQLite bindings replace lone UTF-16 surrogates. JSON encoding keeps indexed identities lossless.
|
|
6
|
+
const encodeIndexedString = (value) => JSON.stringify(value);
|
|
7
|
+
const getRow = (statement, ...params) => statement.get(...params);
|
|
8
|
+
const allRows = (statement, ...params) => statement.all(...params);
|
|
9
|
+
const cursorId = (cursor) => cursor?.after;
|
|
10
|
+
const page = (values, limit) => {
|
|
11
|
+
const items = values.slice(0, limit);
|
|
12
|
+
if (values.length <= limit)
|
|
13
|
+
return { items };
|
|
14
|
+
return { items, next: { after: items.at(-1).id } };
|
|
15
|
+
};
|
|
16
|
+
const scopeColumns = (scope) => {
|
|
17
|
+
switch (scope.kind) {
|
|
18
|
+
case "session":
|
|
19
|
+
return { scopeKind: "session", ownerId: 0 };
|
|
20
|
+
case "conversation":
|
|
21
|
+
return { scopeKind: "conversation", ownerId: scope.conversationId };
|
|
22
|
+
case "task":
|
|
23
|
+
return { scopeKind: "task", ownerId: scope.taskId };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const addressParts = (address) => {
|
|
27
|
+
const scope = scopeColumns(address.scope);
|
|
28
|
+
return {
|
|
29
|
+
kind: encodeIndexedString(address.kind),
|
|
30
|
+
...scope,
|
|
31
|
+
family: address.key === undefined ? 0 : 1,
|
|
32
|
+
keyValue: encodeIndexedString(address.key ?? ""),
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const addressKey = (address) => {
|
|
36
|
+
const parts = addressParts(address);
|
|
37
|
+
return JSON.stringify([parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue]);
|
|
38
|
+
};
|
|
39
|
+
const isAliveAt = (record, at) => {
|
|
40
|
+
if (at === "current")
|
|
41
|
+
return record.retiredAt === undefined;
|
|
42
|
+
return record.createdAt <= at && (record.retiredAt === undefined || at < record.retiredAt);
|
|
43
|
+
};
|
|
44
|
+
const isCurrentOnly = (record) => record.scope.kind !== "conversation" || record.history === "latest";
|
|
45
|
+
const writeId = (write) => {
|
|
46
|
+
switch (write.type) {
|
|
47
|
+
case "conversation":
|
|
48
|
+
case "entry":
|
|
49
|
+
case "task":
|
|
50
|
+
case "submission":
|
|
51
|
+
return write.value.id;
|
|
52
|
+
case "document.create":
|
|
53
|
+
return write.record.id;
|
|
54
|
+
case "document.change":
|
|
55
|
+
case "document.retire":
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
class StatementCachingDatabase {
|
|
60
|
+
database;
|
|
61
|
+
statements = new Map();
|
|
62
|
+
constructor(database) {
|
|
63
|
+
this.database = database;
|
|
64
|
+
}
|
|
65
|
+
exec(sql) {
|
|
66
|
+
this.database.exec(sql);
|
|
67
|
+
}
|
|
68
|
+
prepare(sql) {
|
|
69
|
+
let statement = this.statements.get(sql);
|
|
70
|
+
if (statement === undefined) {
|
|
71
|
+
statement = this.database.prepare(sql);
|
|
72
|
+
this.statements.set(sql, statement);
|
|
73
|
+
}
|
|
74
|
+
return statement;
|
|
75
|
+
}
|
|
76
|
+
transaction(callback) {
|
|
77
|
+
return this.database.transaction(callback);
|
|
78
|
+
}
|
|
79
|
+
close() {
|
|
80
|
+
this.statements.clear();
|
|
81
|
+
return this.database.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Portable SQLite implementation of the Pico storage contract. */
|
|
85
|
+
export class SqliteStorage {
|
|
86
|
+
db;
|
|
87
|
+
nextId;
|
|
88
|
+
closed = false;
|
|
89
|
+
constructor(db, nextId) {
|
|
90
|
+
this.db = new StatementCachingDatabase(db);
|
|
91
|
+
this.nextId = nextId;
|
|
92
|
+
}
|
|
93
|
+
/** Initialize storage over an owned SQLite database facade. */
|
|
94
|
+
static async open(db) {
|
|
95
|
+
try {
|
|
96
|
+
await applySqliteMigrations(db);
|
|
97
|
+
const metadata = getRow(db.prepare("SELECT next_id, next_seq FROM durable_metadata WHERE singleton = 1"));
|
|
98
|
+
if (metadata === undefined)
|
|
99
|
+
throw new Error("Durable SQLite metadata is missing");
|
|
100
|
+
return new SqliteStorage(db, Number(metadata.next_id));
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
try {
|
|
104
|
+
await db.close();
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Preserve the initialization failure.
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async commit(writes, _context) {
|
|
113
|
+
this.assertOpen();
|
|
114
|
+
const documentActions = this.prepareDocumentActions(writes);
|
|
115
|
+
const candidateNextId = this.candidateNextId(writes);
|
|
116
|
+
const seq = await this.db.transaction(() => {
|
|
117
|
+
const metadata = getRow(this.db.prepare("SELECT next_id, next_seq FROM durable_metadata WHERE singleton = 1"));
|
|
118
|
+
if (metadata === undefined)
|
|
119
|
+
throw new Error("Durable SQLite metadata is missing");
|
|
120
|
+
const committedSeq = metadata.next_seq;
|
|
121
|
+
this.checkGlobalIds(writes);
|
|
122
|
+
this.checkDocumentActions(documentActions);
|
|
123
|
+
for (const write of writes)
|
|
124
|
+
this.applyTableWrite(write, committedSeq);
|
|
125
|
+
this.applyDocumentActions(documentActions, committedSeq);
|
|
126
|
+
this.db
|
|
127
|
+
.prepare("UPDATE durable_metadata SET next_id = ?, next_seq = ? WHERE singleton = 1")
|
|
128
|
+
.run(String(Math.max(Number(metadata.next_id), candidateNextId)), committedSeq + 1);
|
|
129
|
+
return committedSeq;
|
|
130
|
+
});
|
|
131
|
+
this.nextId = Math.max(this.nextId, candidateNextId);
|
|
132
|
+
return seq;
|
|
133
|
+
}
|
|
134
|
+
async mintId() {
|
|
135
|
+
this.assertOpen();
|
|
136
|
+
if (!Number.isSafeInteger(this.nextId))
|
|
137
|
+
throw new Error("ID space is exhausted");
|
|
138
|
+
return this.nextId++;
|
|
139
|
+
}
|
|
140
|
+
async conversation(id, _context) {
|
|
141
|
+
this.assertOpen();
|
|
142
|
+
const row = getRow(this.db.prepare("SELECT record FROM conversations WHERE id = ?"), id);
|
|
143
|
+
return row === undefined ? undefined : parseJson(row.record);
|
|
144
|
+
}
|
|
145
|
+
async scanConversations(cursor, limit, _context) {
|
|
146
|
+
this.assertOpen();
|
|
147
|
+
const after = cursorId(cursor) ?? -1;
|
|
148
|
+
const rows = allRows(this.db.prepare("SELECT record FROM conversations WHERE id > ? ORDER BY id LIMIT ?"), after, limit + 1);
|
|
149
|
+
return page(rows.map((row) => parseJson(row.record)), limit);
|
|
150
|
+
}
|
|
151
|
+
async entry(id, _context) {
|
|
152
|
+
this.assertOpen();
|
|
153
|
+
const row = getRow(this.db.prepare("SELECT record, commit_seq FROM entries WHERE id = ?"), id);
|
|
154
|
+
return row === undefined ? undefined : { entry: parseJson(row.record), commitSeq: row.commit_seq };
|
|
155
|
+
}
|
|
156
|
+
async findLatestHeadMarker(conversationId, atOrBeforeEntryId, _context) {
|
|
157
|
+
this.assertOpen();
|
|
158
|
+
let conversation = this.readConversation(conversationId);
|
|
159
|
+
if (conversation === undefined)
|
|
160
|
+
throw new Error(`Unknown conversation: ${conversationId}`);
|
|
161
|
+
let upper = atOrBeforeEntryId;
|
|
162
|
+
while (true) {
|
|
163
|
+
const row = upper === undefined
|
|
164
|
+
? getRow(this.db.prepare("SELECT record FROM entries WHERE conversation_id = ? AND head IS NOT NULL ORDER BY id DESC LIMIT 1"), conversation.id)
|
|
165
|
+
: getRow(this.db.prepare("SELECT record FROM entries WHERE conversation_id = ? AND head IS NOT NULL AND id <= ? ORDER BY id DESC LIMIT 1"), conversation.id, upper);
|
|
166
|
+
if (row !== undefined)
|
|
167
|
+
return parseJson(row.record);
|
|
168
|
+
if (conversation.parent === undefined)
|
|
169
|
+
return undefined;
|
|
170
|
+
upper = upper === undefined ? conversation.parent.at : Math.min(upper, conversation.parent.at);
|
|
171
|
+
conversation = this.readConversation(conversation.parent.conversationId);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async scanEntries(query, cursor, limit, _context) {
|
|
175
|
+
this.assertOpen();
|
|
176
|
+
let conversation = this.readConversation(query.conversationId);
|
|
177
|
+
if (conversation === undefined)
|
|
178
|
+
throw new Error(`Unknown conversation: ${query.conversationId}`);
|
|
179
|
+
const after = cursorId(cursor);
|
|
180
|
+
let upper = query.maxEntryId;
|
|
181
|
+
if (after !== undefined)
|
|
182
|
+
upper = Math.min(upper ?? Number.MAX_SAFE_INTEGER, after - 1);
|
|
183
|
+
const values = [];
|
|
184
|
+
while (true) {
|
|
185
|
+
const clauses = ["conversation_id = ?"];
|
|
186
|
+
const params = [conversation.id];
|
|
187
|
+
if (query.minEntryId !== undefined) {
|
|
188
|
+
clauses.push("id >= ?");
|
|
189
|
+
params.push(query.minEntryId);
|
|
190
|
+
}
|
|
191
|
+
if (upper !== undefined) {
|
|
192
|
+
clauses.push("id <= ?");
|
|
193
|
+
params.push(upper);
|
|
194
|
+
}
|
|
195
|
+
params.push(limit + 1 - values.length);
|
|
196
|
+
const rows = allRows(this.db.prepare(`SELECT record FROM entries WHERE ${clauses.join(" AND ")} ORDER BY id DESC LIMIT ?`), ...params);
|
|
197
|
+
values.push(...rows.map((row) => parseJson(row.record)));
|
|
198
|
+
if (values.length > limit || conversation.parent === undefined)
|
|
199
|
+
break;
|
|
200
|
+
upper = upper === undefined ? conversation.parent.at : Math.min(upper, conversation.parent.at);
|
|
201
|
+
if (query.minEntryId !== undefined && upper < query.minEntryId)
|
|
202
|
+
break;
|
|
203
|
+
conversation = this.readConversation(conversation.parent.conversationId);
|
|
204
|
+
}
|
|
205
|
+
return page(values, limit);
|
|
206
|
+
}
|
|
207
|
+
async task(id, _context) {
|
|
208
|
+
this.assertOpen();
|
|
209
|
+
const row = getRow(this.db.prepare("SELECT record FROM tasks WHERE id = ?"), id);
|
|
210
|
+
return row === undefined ? undefined : parseJson(row.record);
|
|
211
|
+
}
|
|
212
|
+
async scanTasks(query, cursor, limit, _context) {
|
|
213
|
+
this.assertOpen();
|
|
214
|
+
const clauses = ["id > ?"];
|
|
215
|
+
const params = [cursorId(cursor) ?? -1];
|
|
216
|
+
if (query.conversationId !== undefined) {
|
|
217
|
+
clauses.push("conversation_id = ?");
|
|
218
|
+
params.push(query.conversationId);
|
|
219
|
+
}
|
|
220
|
+
if (query.kind !== undefined) {
|
|
221
|
+
clauses.push("kind = ?");
|
|
222
|
+
params.push(encodeIndexedString(query.kind));
|
|
223
|
+
}
|
|
224
|
+
if (query.status !== undefined) {
|
|
225
|
+
clauses.push("status = ?");
|
|
226
|
+
params.push(query.status);
|
|
227
|
+
}
|
|
228
|
+
if (query.abortRequested !== undefined) {
|
|
229
|
+
clauses.push("abort_requested = ?");
|
|
230
|
+
params.push(query.abortRequested ? 1 : 0);
|
|
231
|
+
}
|
|
232
|
+
if (query.background !== undefined) {
|
|
233
|
+
clauses.push("background = ?");
|
|
234
|
+
params.push(query.background ? 1 : 0);
|
|
235
|
+
}
|
|
236
|
+
params.push(limit + 1);
|
|
237
|
+
const rows = allRows(this.db.prepare(`SELECT record FROM tasks WHERE ${clauses.join(" AND ")} ORDER BY id LIMIT ?`), ...params);
|
|
238
|
+
return page(rows.map((row) => parseJson(row.record)), limit);
|
|
239
|
+
}
|
|
240
|
+
async submission(id, _context) {
|
|
241
|
+
this.assertOpen();
|
|
242
|
+
const row = getRow(this.db.prepare("SELECT record FROM submissions WHERE id = ?"), id);
|
|
243
|
+
return row === undefined ? undefined : parseJson(row.record);
|
|
244
|
+
}
|
|
245
|
+
async submissionByRequest(conversationId, requestId, _context) {
|
|
246
|
+
this.assertOpen();
|
|
247
|
+
const row = getRow(this.db.prepare("SELECT record FROM submissions WHERE conversation_id = ? AND request_id = ?"), conversationId, encodeIndexedString(requestId));
|
|
248
|
+
return row === undefined ? undefined : parseJson(row.record);
|
|
249
|
+
}
|
|
250
|
+
async findDocument(address, at, _context) {
|
|
251
|
+
this.assertOpen();
|
|
252
|
+
const parts = addressParts(address);
|
|
253
|
+
const statement = at === "current"
|
|
254
|
+
? this.db.prepare(`SELECT record FROM documents
|
|
255
|
+
WHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ?
|
|
256
|
+
AND retired_at IS NULL ORDER BY created_at DESC LIMIT 1`)
|
|
257
|
+
: this.db.prepare(`SELECT record FROM documents
|
|
258
|
+
WHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ?
|
|
259
|
+
AND created_at <= ? AND (retired_at IS NULL OR retired_at > ?)
|
|
260
|
+
ORDER BY created_at DESC LIMIT 1`);
|
|
261
|
+
const params = [parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue];
|
|
262
|
+
if (at !== "current")
|
|
263
|
+
params.push(at, at);
|
|
264
|
+
const row = getRow(statement, ...params);
|
|
265
|
+
return row === undefined ? undefined : parseJson(row.record);
|
|
266
|
+
}
|
|
267
|
+
async document(id, at, _context) {
|
|
268
|
+
this.assertOpen();
|
|
269
|
+
const row = getRow(this.db.prepare("SELECT record FROM documents WHERE id = ?"), id);
|
|
270
|
+
if (row === undefined)
|
|
271
|
+
return undefined;
|
|
272
|
+
const record = parseJson(row.record);
|
|
273
|
+
if (at !== "current" && isCurrentOnly(record)) {
|
|
274
|
+
throw new Error(`Document ${id} does not retain historical content`);
|
|
275
|
+
}
|
|
276
|
+
if (!isAliveAt(record, at))
|
|
277
|
+
return undefined;
|
|
278
|
+
const upper = at === "current" ? Number.MAX_SAFE_INTEGER : at;
|
|
279
|
+
const base = getRow(this.db.prepare(`SELECT seq, kind, version, content FROM document_revisions
|
|
280
|
+
WHERE document_id = ? AND kind = 'base' AND seq <= ? ORDER BY seq DESC LIMIT 1`), id, upper);
|
|
281
|
+
if (base === undefined)
|
|
282
|
+
throw new Error(`Document ${id} is missing a required base`);
|
|
283
|
+
let value = parseJson(base.content);
|
|
284
|
+
const tail = allRows(this.db.prepare(`SELECT seq, kind, version, content FROM document_revisions
|
|
285
|
+
WHERE document_id = ? AND seq > ? AND seq <= ? ORDER BY seq`), id, base.seq, upper);
|
|
286
|
+
for (const revision of tail) {
|
|
287
|
+
if (revision.kind !== "delta" || revision.version !== base.version) {
|
|
288
|
+
throw new Error(`Document ${id} crosses a stored version boundary without a base`);
|
|
289
|
+
}
|
|
290
|
+
value = applyImmutable(value, parseJson(revision.content));
|
|
291
|
+
}
|
|
292
|
+
return { record, version: base.version, value };
|
|
293
|
+
}
|
|
294
|
+
async scanDocuments(query, cursor, limit, _context) {
|
|
295
|
+
this.assertOpen();
|
|
296
|
+
const scope = scopeColumns(query.scope);
|
|
297
|
+
const clauses = ["scope_kind = ?", "owner_id = ?", "id > ?"];
|
|
298
|
+
const params = [scope.scopeKind, scope.ownerId, cursorId(cursor) ?? -1];
|
|
299
|
+
if (query.kind !== undefined) {
|
|
300
|
+
clauses.push("kind = ?");
|
|
301
|
+
params.push(encodeIndexedString(query.kind));
|
|
302
|
+
}
|
|
303
|
+
if (query.at === "current") {
|
|
304
|
+
clauses.push("retired_at IS NULL");
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
clauses.push("created_at <= ?", "(retired_at IS NULL OR retired_at > ?)");
|
|
308
|
+
params.push(query.at, query.at);
|
|
309
|
+
}
|
|
310
|
+
params.push(limit + 1);
|
|
311
|
+
const rows = allRows(this.db.prepare(`SELECT record FROM documents WHERE ${clauses.join(" AND ")} ORDER BY id LIMIT ?`), ...params);
|
|
312
|
+
return page(rows.map((row) => parseJson(row.record)), limit);
|
|
313
|
+
}
|
|
314
|
+
async close(_context) {
|
|
315
|
+
if (this.closed)
|
|
316
|
+
return;
|
|
317
|
+
this.closed = true;
|
|
318
|
+
await this.db.close();
|
|
319
|
+
}
|
|
320
|
+
readConversation(id) {
|
|
321
|
+
const row = getRow(this.db.prepare("SELECT record FROM conversations WHERE id = ?"), id);
|
|
322
|
+
return row === undefined ? undefined : parseJson(row.record);
|
|
323
|
+
}
|
|
324
|
+
candidateNextId(writes) {
|
|
325
|
+
let nextId = this.nextId;
|
|
326
|
+
for (const write of writes) {
|
|
327
|
+
const id = writeId(write);
|
|
328
|
+
if (id !== undefined)
|
|
329
|
+
nextId = Math.max(nextId, id + 1);
|
|
330
|
+
}
|
|
331
|
+
return nextId;
|
|
332
|
+
}
|
|
333
|
+
checkGlobalIds(writes) {
|
|
334
|
+
const claimed = new Map();
|
|
335
|
+
const lookup = this.db.prepare("SELECT record_type FROM record_ids WHERE id = ?");
|
|
336
|
+
for (const write of writes) {
|
|
337
|
+
if (write.type === "document.change" || write.type === "document.retire")
|
|
338
|
+
continue;
|
|
339
|
+
const table = write.type === "document.create" ? "document" : write.type;
|
|
340
|
+
const id = write.type === "document.create" ? write.record.id : write.value.id;
|
|
341
|
+
const existing = getRow(lookup, id)?.record_type;
|
|
342
|
+
const earlier = claimed.get(id);
|
|
343
|
+
if (table === "conversation" || table === "entry" || table === "document") {
|
|
344
|
+
if (existing !== undefined)
|
|
345
|
+
throw new Error(`ID ${id} already belongs to ${existing}`);
|
|
346
|
+
if (earlier !== undefined)
|
|
347
|
+
throw new Error(`ID ${id} is written more than once`);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
if (existing !== undefined && existing !== table)
|
|
351
|
+
throw new Error(`ID ${id} already belongs to ${existing}`);
|
|
352
|
+
if (earlier !== undefined && earlier !== table)
|
|
353
|
+
throw new Error(`ID ${id} is written as two record types`);
|
|
354
|
+
}
|
|
355
|
+
claimed.set(id, table);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
prepareDocumentActions(writes) {
|
|
359
|
+
const actions = new Map();
|
|
360
|
+
for (const write of writes) {
|
|
361
|
+
if (write.type !== "document.create" && write.type !== "document.change" && write.type !== "document.retire") {
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const id = write.type === "document.create" ? write.record.id : write.id;
|
|
365
|
+
let action = actions.get(id);
|
|
366
|
+
if (action === undefined) {
|
|
367
|
+
action = { retire: false };
|
|
368
|
+
actions.set(id, action);
|
|
369
|
+
}
|
|
370
|
+
switch (write.type) {
|
|
371
|
+
case "document.create":
|
|
372
|
+
if (action.create !== undefined || action.content !== undefined) {
|
|
373
|
+
throw new Error(`Document ${id} has more than one content command`);
|
|
374
|
+
}
|
|
375
|
+
action.create = write.record;
|
|
376
|
+
action.content = write.content;
|
|
377
|
+
break;
|
|
378
|
+
case "document.change":
|
|
379
|
+
if (action.content !== undefined)
|
|
380
|
+
throw new Error(`Document ${id} has more than one content command`);
|
|
381
|
+
action.content = write.content;
|
|
382
|
+
break;
|
|
383
|
+
case "document.retire":
|
|
384
|
+
if (action.retire)
|
|
385
|
+
throw new Error(`Document ${id} is retired more than once`);
|
|
386
|
+
action.retire = true;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return actions;
|
|
391
|
+
}
|
|
392
|
+
checkDocumentActions(actions) {
|
|
393
|
+
const liveCounts = new Map();
|
|
394
|
+
for (const [id, action] of actions) {
|
|
395
|
+
const row = getRow(this.db.prepare("SELECT record FROM documents WHERE id = ?"), id);
|
|
396
|
+
const existing = row === undefined ? undefined : parseJson(row.record);
|
|
397
|
+
if (action.create === undefined && existing === undefined)
|
|
398
|
+
throw new Error(`Unknown document: ${id}`);
|
|
399
|
+
if (action.create !== undefined && existing !== undefined)
|
|
400
|
+
throw new Error(`Document ${id} already exists`);
|
|
401
|
+
if (existing?.retiredAt !== undefined)
|
|
402
|
+
throw new Error(`Document ${id} is retired`);
|
|
403
|
+
if (action.create !== undefined && action.content?.kind !== "base") {
|
|
404
|
+
throw new Error(`Document ${id} creation requires a base`);
|
|
405
|
+
}
|
|
406
|
+
if (action.content?.kind === "delta") {
|
|
407
|
+
const previous = getRow(this.db.prepare("SELECT version FROM document_revisions WHERE document_id = ? ORDER BY seq DESC LIMIT 1"), id);
|
|
408
|
+
if (previous === undefined)
|
|
409
|
+
throw new Error(`Document ${id} delta has no base`);
|
|
410
|
+
if (previous.version !== action.content.version) {
|
|
411
|
+
throw new Error(`Document ${id} version transition requires a base`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const record = action.create ?? existing;
|
|
415
|
+
const key = addressKey(record);
|
|
416
|
+
let live = liveCounts.get(key);
|
|
417
|
+
if (live === undefined)
|
|
418
|
+
live = this.currentDocumentId(record) === undefined ? 0 : 1;
|
|
419
|
+
if (action.retire && existing !== undefined)
|
|
420
|
+
live--;
|
|
421
|
+
if (action.create !== undefined && !action.retire)
|
|
422
|
+
live++;
|
|
423
|
+
liveCounts.set(key, live);
|
|
424
|
+
}
|
|
425
|
+
for (const live of liveCounts.values()) {
|
|
426
|
+
if (live > 1)
|
|
427
|
+
throw new Error("Document address already has a current incarnation");
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
currentDocumentId(address) {
|
|
431
|
+
const parts = addressParts(address);
|
|
432
|
+
return getRow(this.db.prepare(`SELECT id FROM documents
|
|
433
|
+
WHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ? AND retired_at IS NULL
|
|
434
|
+
LIMIT 1`), parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue)?.id;
|
|
435
|
+
}
|
|
436
|
+
applyTableWrite(write, seq) {
|
|
437
|
+
switch (write.type) {
|
|
438
|
+
case "conversation":
|
|
439
|
+
this.claimId(write.value.id, "conversation");
|
|
440
|
+
this.db
|
|
441
|
+
.prepare("INSERT INTO conversations (id, record) VALUES (?, ?)")
|
|
442
|
+
.run(write.value.id, encodeJson(write.value));
|
|
443
|
+
break;
|
|
444
|
+
case "entry":
|
|
445
|
+
this.claimId(write.value.id, "entry");
|
|
446
|
+
this.db
|
|
447
|
+
.prepare("INSERT INTO entries (id, conversation_id, head, commit_seq, record) VALUES (?, ?, ?, ?, ?)")
|
|
448
|
+
.run(write.value.id, write.value.conversationId, write.value.head ?? null, seq, encodeJson(write.value));
|
|
449
|
+
break;
|
|
450
|
+
case "task":
|
|
451
|
+
this.claimId(write.value.id, "task");
|
|
452
|
+
this.db
|
|
453
|
+
.prepare(`INSERT INTO tasks (id, conversation_id, kind, status, abort_requested, background, record)
|
|
454
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
455
|
+
ON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id, kind = excluded.kind,
|
|
456
|
+
status = excluded.status, abort_requested = excluded.abort_requested,
|
|
457
|
+
background = excluded.background, record = excluded.record`)
|
|
458
|
+
.run(write.value.id, write.value.conversationId, encodeIndexedString(write.value.kind), write.value.state.status, write.value.abortRequested ? 1 : 0, write.value.background ? 1 : 0, encodeJson(write.value));
|
|
459
|
+
break;
|
|
460
|
+
case "submission":
|
|
461
|
+
this.claimId(write.value.id, "submission");
|
|
462
|
+
this.db
|
|
463
|
+
.prepare(`INSERT INTO submissions (id, conversation_id, request_id, record) VALUES (?, ?, ?, ?)
|
|
464
|
+
ON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id,
|
|
465
|
+
request_id = excluded.request_id, record = excluded.record`)
|
|
466
|
+
.run(write.value.id, write.value.conversationId, write.value.requestId === undefined ? null : encodeIndexedString(write.value.requestId), encodeJson(write.value));
|
|
467
|
+
break;
|
|
468
|
+
case "document.create":
|
|
469
|
+
case "document.change":
|
|
470
|
+
case "document.retire":
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
claimId(id, table) {
|
|
475
|
+
this.db.prepare("INSERT OR IGNORE INTO record_ids (id, record_type) VALUES (?, ?)").run(id, table);
|
|
476
|
+
}
|
|
477
|
+
applyDocumentActions(actions, seq) {
|
|
478
|
+
for (const [id, action] of actions) {
|
|
479
|
+
let record;
|
|
480
|
+
if (action.create !== undefined) {
|
|
481
|
+
record = {
|
|
482
|
+
...action.create,
|
|
483
|
+
createdAt: seq,
|
|
484
|
+
...(action.retire ? { retiredAt: seq } : {}),
|
|
485
|
+
};
|
|
486
|
+
const parts = addressParts(record);
|
|
487
|
+
this.claimId(id, "document");
|
|
488
|
+
this.db
|
|
489
|
+
.prepare(`INSERT INTO documents
|
|
490
|
+
(id, kind, family, key_value, scope_kind, owner_id, created_at, retired_at, record)
|
|
491
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
492
|
+
.run(id, parts.kind, parts.family, parts.keyValue, parts.scopeKind, parts.ownerId, seq, action.retire ? seq : null, encodeJson(record));
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
const row = getRow(this.db.prepare("SELECT record FROM documents WHERE id = ?"), id);
|
|
496
|
+
record = parseJson(row.record);
|
|
497
|
+
}
|
|
498
|
+
if (action.content !== undefined) {
|
|
499
|
+
if (action.content.kind === "base" && isCurrentOnly(record)) {
|
|
500
|
+
this.db.prepare("DELETE FROM document_revisions WHERE document_id = ?").run(id);
|
|
501
|
+
}
|
|
502
|
+
const encodedContent = action.content.kind === "base" ? encodeJson(action.content.value) : encodeJson(action.content.ops);
|
|
503
|
+
this.db
|
|
504
|
+
.prepare("INSERT INTO document_revisions (document_id, seq, kind, version, content) VALUES (?, ?, ?, ?, ?)")
|
|
505
|
+
.run(id, seq, action.content.kind, action.content.version, encodedContent);
|
|
506
|
+
}
|
|
507
|
+
if (action.retire) {
|
|
508
|
+
if (action.create === undefined) {
|
|
509
|
+
record = { ...record, retiredAt: seq };
|
|
510
|
+
this.db
|
|
511
|
+
.prepare("UPDATE documents SET retired_at = ?, record = ? WHERE id = ?")
|
|
512
|
+
.run(seq, encodeJson(record), id);
|
|
513
|
+
}
|
|
514
|
+
if (isCurrentOnly(record))
|
|
515
|
+
this.db.prepare("DELETE FROM document_revisions WHERE document_id = ?").run(id);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
assertOpen() {
|
|
520
|
+
if (this.closed)
|
|
521
|
+
throw new Error("SqliteStorage is closed");
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/storage/sqlite/storage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAW,MAAM,6BAA6B,CAAC;AAwBtE,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAyBxD,MAAM,SAAS,GAAG,CAAI,KAAa,EAAK,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAM,CAAC;AAClE,MAAM,UAAU,GAAG,CAAC,KAAc,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAW,CAAC;AAC/E,wGAAwG;AACxG,MAAM,mBAAmB,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAE7E,MAAM,MAAM,GAAG,CAAmB,SAA0B,EAAE,GAAG,MAAqB,EAAiB,EAAE,CACxG,SAAS,CAAC,GAAG,CAAI,GAAG,MAAM,CAAC,CAAC;AAE7B,MAAM,OAAO,GAAG,CAAmB,SAA0B,EAAE,GAAG,MAAqB,EAAO,EAAE,CAC/F,SAAS,CAAC,GAAG,CAAI,GAAG,MAAM,CAAC,CAAC;AAE7B,MAAM,QAAQ,GAAG,CAAC,MAA0B,EAAkB,EAAE,CAAC,MAAM,EAAE,KAAuB,CAAC;AAEjG,MAAM,IAAI,GAAG,CAAgC,MAAoB,EAAE,KAAa,EAAmB,EAAE,CAAC;IACrG,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACrC,IAAI,MAAM,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC7C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,EAAE,EAAE,EAAE,CAAC;AAAA,CACpD,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,KAA8B,EAAgB,EAAE,CAAC;IACtE,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,SAAS;YACb,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;QAC7C,KAAK,cAAc;YAClB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC;QACrE,KAAK,MAAM;YACV,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IACtD,CAAC;AAAA,CACD,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,OAA0D,EAAE,EAAE,CAAC;IACpF,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1C,OAAO;QACN,IAAI,EAAE,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC;QACvC,GAAG,KAAK;QACR,MAAM,EAAE,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC;KAChD,CAAC;AAAA,CACF,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,OAA0D,EAAU,EAAE,CAAC;IAC1F,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;AAAA,CAClG,CAAC;AAEF,MAAM,SAAS,GAAG,CAAC,MAAsB,EAAE,EAAiB,EAAW,EAAE,CAAC;IACzE,IAAI,EAAE,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC;IAC5D,OAAO,MAAM,CAAC,SAAS,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;AAAA,CAC3F,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,MAAsB,EAAW,EAAE,CACzD,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC;AAErE,MAAM,OAAO,GAAG,CAAC,KAAmB,EAAkB,EAAE,CAAC;IACxD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,cAAc,CAAC;QACpB,KAAK,OAAO,CAAC;QACb,KAAK,MAAM,CAAC;QACZ,KAAK,YAAY;YAChB,OAAO,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,KAAK,iBAAiB;YACrB,OAAO,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,KAAK,iBAAiB,CAAC;QACvB,KAAK,iBAAiB;YACrB,OAAO,SAAS,CAAC;IACnB,CAAC;AAAA,CACD,CAAC;AAEF,MAAM,wBAAwB;IACZ,QAAQ,CAAiB;IACzB,UAAU,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEjE,YAAY,QAAwB,EAAE;QACrC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAAA,CACzB;IAED,IAAI,CAAC,GAAW,EAAQ;QACvB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAAA,CACxB;IAED,OAAO,CAAC,GAAW,EAAmB;QACrC,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC7B,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACvC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACrC,CAAC;QACD,OAAO,SAAS,CAAC;IAAA,CACjB;IAED,WAAW,CAAI,QAAiB,EAAkB;QACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAAA,CAC3C;IAED,KAAK,GAAyB;QAC7B,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IAAA,CAC7B;CACD;AAED,mEAAmE;AACnE,MAAM,OAAO,aAAa;IACR,EAAE,CAAiB;IAC5B,MAAM,CAAK;IACX,MAAM,GAAG,KAAK,CAAC;IAEvB,YAAoB,EAAkB,EAAE,MAAU,EAAE;QACnD,IAAI,CAAC,EAAE,GAAG,IAAI,wBAAwB,CAAC,EAAE,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAAA,CACrB;IAED,+DAA+D;IAC/D,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAkB,EAA0B;QAC7D,IAAI,CAAC;YACJ,MAAM,qBAAqB,CAAC,EAAE,CAAC,CAAC;YAChC,MAAM,QAAQ,GAAG,MAAM,CACtB,EAAE,CAAC,OAAO,CAAC,oEAAoE,CAAC,CAChF,CAAC;YACF,IAAI,QAAQ,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YAClF,OAAO,IAAI,aAAa,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC;gBACJ,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;YAClB,CAAC;YAAC,MAAM,CAAC;gBACR,uCAAuC;YACxC,CAAC;YACD,MAAM,KAAK,CAAC;QACb,CAAC;IAAA,CACD;IAED,KAAK,CAAC,MAAM,CAAC,MAA+B,EAAE,QAAiB,EAAgB;QAC9E,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,eAAe,GAAG,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAC5D,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,MAAM,CACtB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,oEAAoE,CAAC,CACrF,CAAC;YACF,IAAI,QAAQ,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YAClF,MAAM,YAAY,GAAG,QAAQ,CAAC,QAAQ,CAAC;YACvC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;YAC5B,IAAI,CAAC,oBAAoB,CAAC,eAAe,CAAC,CAAC;YAC3C,KAAK,MAAM,KAAK,IAAI,MAAM;gBAAE,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;YACtE,IAAI,CAAC,oBAAoB,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;YACzD,IAAI,CAAC,EAAE;iBACL,OAAO,CAAC,2EAA2E,CAAC;iBACpF,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC;YACrF,OAAO,YAAY,CAAC;QAAA,CACpB,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;QACrD,OAAO,GAAG,CAAC;IAAA,CACX;IAED,KAAK,CAAC,MAAM,GAAgB;QAC3B,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACjF,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;IAAA,CACrB;IAED,KAAK,CAAC,YAAY,CAAC,EAAM,EAAE,QAAiB,EAA2C;QACtF,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,CAAU,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,+CAA+C,CAAC,EAAE,EAAE,CAAC,CAAC;QAClG,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAqB,GAAG,CAAC,MAAM,CAAC,CAAC;IAAA,CACjF;IAED,KAAK,CAAC,iBAAiB,CACtB,MAA0B,EAC1B,KAAa,EACb,QAAiB,EAC2B;QAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,OAAO,CACnB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,mEAAmE,CAAC,EACpF,KAAK,EACL,KAAK,GAAG,CAAC,CACT,CAAC;QACF,OAAO,IAAI,CACV,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAqB,GAAG,CAAC,MAAM,CAAC,CAAC,EAC5D,KAAK,CACL,CAAC;IAAA,CACF;IAED,KAAK,CAAC,KAAK,CACV,EAAM,EACN,QAAiB,EAC+D;QAChF,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,CAAe,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,qDAAqD,CAAC,EAAE,EAAE,CAAC,CAAC;QAC7G,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAc,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;IAAA,CAChH;IAED,KAAK,CAAC,oBAAoB,CACzB,cAAkB,EAClB,iBAAiC,EACjC,QAAiB,EAC4C;QAC7D,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;QACzD,IAAI,YAAY,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,cAAc,EAAE,CAAC,CAAC;QAC3F,IAAI,KAAK,GAAG,iBAAiB,CAAC;QAC9B,OAAO,IAAI,EAAE,CAAC;YACb,MAAM,GAAG,GACR,KAAK,KAAK,SAAS;gBAClB,CAAC,CAAC,MAAM,CACN,IAAI,CAAC,EAAE,CAAC,OAAO,CACd,oGAAoG,CACpG,EACD,YAAY,CAAC,EAAE,CACf;gBACF,CAAC,CAAC,MAAM,CACN,IAAI,CAAC,EAAE,CAAC,OAAO,CACd,gHAAgH,CAChH,EACD,YAAY,CAAC,EAAE,EACf,KAAK,CACL,CAAC;YACL,IAAI,GAAG,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAsC,GAAG,CAAC,MAAM,CAAC,CAAC;YACzF,IAAI,YAAY,CAAC,MAAM,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAC;YACxD,KAAK,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC/F,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,YAAY,CAAC,MAAM,CAAC,cAAc,CAAE,CAAC;QAC3E,CAAC;IAAA,CACD;IAED,KAAK,CAAC,WAAW,CAChB,KAAiB,EACjB,MAA0B,EAC1B,KAAa,EACb,QAAiB,EACoB;QACrC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAC/D,IAAI,YAAY,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC;QACjG,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/B,IAAI,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC;QAC7B,IAAI,KAAK,KAAK,SAAS;YAAE,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC,gBAAgB,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QACvF,MAAM,MAAM,GAAkB,EAAE,CAAC;QACjC,OAAO,IAAI,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,CAAC,qBAAqB,CAAC,CAAC;YACxC,MAAM,MAAM,GAAkB,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;YAChD,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;gBACpC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/B,CAAC;YACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,OAAO,CACnB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,oCAAoC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,2BAA2B,CAAC,EACrG,GAAG,MAAM,CACT,CAAC;YACF,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAc,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACtE,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,IAAI,YAAY,CAAC,MAAM,KAAK,SAAS;gBAAE,MAAM;YACtE,KAAK,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC/F,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,IAAI,KAAK,GAAG,KAAK,CAAC,UAAU;gBAAE,MAAM;YACtE,YAAY,GAAG,IAAI,CAAC,gBAAgB,CAAC,YAAY,CAAC,MAAM,CAAC,cAAc,CAAE,CAAC;QAC3E,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAAA,CAC3B;IAED,KAAK,CAAC,IAAI,CAAC,EAAM,EAAE,QAAiB,EAAmC;QACtE,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,CAAU,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,uCAAuC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1F,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAa,GAAG,CAAC,MAAM,CAAC,CAAC;IAAA,CACzE;IAED,KAAK,CAAC,SAAS,CACd,KAAgB,EAChB,MAA0B,EAC1B,KAAa,EACb,QAAiB,EACmB;QACpC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC3B,MAAM,MAAM,GAAkB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvD,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACpC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACvC,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACvB,MAAM,IAAI,GAAG,OAAO,CACnB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kCAAkC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,CAAC,EAC9F,GAAG,MAAM,CACT,CAAC;QACF,OAAO,IAAI,CACV,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAa,GAAG,CAAC,MAAM,CAAC,CAAC,EACpD,KAAK,CACL,CAAC;IAAA,CACF;IAED,KAAK,CAAC,UAAU,CAAC,EAAM,EAAE,QAAiB,EAAyC;QAClF,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,CAAU,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,6CAA6C,CAAC,EAAE,EAAE,CAAC,CAAC;QAChG,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAmB,GAAG,CAAC,MAAM,CAAC,CAAC;IAAA,CAC/E;IAED,KAAK,CAAC,mBAAmB,CACxB,cAAkB,EAClB,SAAiB,EACjB,QAAiB,EACuB;QACxC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,CACjB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,6EAA6E,CAAC,EAC9F,cAAc,EACd,mBAAmB,CAAC,SAAS,CAAC,CAC9B,CAAC;QACF,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAmB,GAAG,CAAC,MAAM,CAAC,CAAC;IAAA,CAC/E;IAED,KAAK,CAAC,YAAY,CACjB,OAAwB,EACxB,EAAiB,EACjB,QAAiB,EACqB;QACtC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,MAAM,SAAS,GACd,EAAE,KAAK,SAAS;YACf,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;6DAEuC,CAAC;YAC1D,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;sCAGgB,CAAC,CAAC;QACtC,MAAM,MAAM,GAAkB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACzG,IAAI,EAAE,KAAK,SAAS;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,MAAM,CAAU,SAAS,EAAE,GAAG,MAAM,CAAC,CAAC;QAClD,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAiB,GAAG,CAAC,MAAM,CAAC,CAAC;IAAA,CAC7E;IAED,KAAK,CAAC,QAAQ,CAAC,EAAM,EAAE,EAAiB,EAAE,QAAiB,EAAuC;QACjG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,CAAU,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9F,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QACxC,MAAM,MAAM,GAAG,SAAS,CAAiB,GAAG,CAAC,MAAM,CAAC,CAAC;QACrD,IAAI,EAAE,KAAK,SAAS,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,qCAAqC,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;YAAE,OAAO,SAAS,CAAC;QAC7C,MAAM,KAAK,GAAG,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,GAAG,MAAM,CAClB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;mFACgE,CAAC,EACjF,EAAE,EACF,KAAK,CACL,CAAC;QACF,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,6BAA6B,CAAC,CAAC;QACrF,IAAI,KAAK,GAAG,SAAS,CAAa,IAAI,CAAC,OAAO,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,OAAO,CACnB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;gEAC6C,CAAC,EAC9D,EAAE,EACF,IAAI,CAAC,GAAG,EACR,KAAK,CACL,CAAC;QACF,KAAK,MAAM,QAAQ,IAAI,IAAI,EAAE,CAAC;YAC7B,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,IAAI,QAAQ,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;gBACpE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,mDAAmD,CAAC,CAAC;YACpF,CAAC;YACD,KAAK,GAAG,cAAc,CAAC,KAAK,EAAE,SAAS,CAAgB,QAAQ,CAAC,OAAO,CAAC,CAAe,CAAC;QACzF,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;IAAA,CAChD;IAED,KAAK,CAAC,aAAa,CAClB,KAAoB,EACpB,MAA0B,EAC1B,KAAa,EACb,QAAiB,EACuB;QACxC,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,CAAC,gBAAgB,EAAE,cAAc,EAAE,QAAQ,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAkB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvF,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,KAAK,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,iBAAiB,EAAE,wCAAwC,CAAC,CAAC;YAC1E,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QACvB,MAAM,IAAI,GAAG,OAAO,CACnB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,sCAAsC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,sBAAsB,CAAC,EAClG,GAAG,MAAM,CACT,CAAC;QACF,OAAO,IAAI,CACV,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAiB,GAAG,CAAC,MAAM,CAAC,CAAC,EACxD,KAAK,CACL,CAAC;IAAA,CACF;IAED,KAAK,CAAC,KAAK,CAAC,QAAiB,EAAiB;QAC7C,IAAI,IAAI,CAAC,MAAM;YAAE,OAAO;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,MAAM,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAAA,CACtB;IAEO,gBAAgB,CAAC,EAAM,EAAkC;QAChE,MAAM,GAAG,GAAG,MAAM,CAAU,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,+CAA+C,CAAC,EAAE,EAAE,CAAC,CAAC;QAClG,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAqB,GAAG,CAAC,MAAM,CAAC,CAAC;IAAA,CACjF;IAEO,eAAe,CAAC,MAA+B,EAAM;QAC5D,IAAI,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QACzB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,EAAE,KAAK,SAAS;gBAAE,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,MAAM,CAAC;IAAA,CACd;IAEO,cAAc,CAAC,MAA+B,EAAQ;QAC7D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC;QAClF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB;gBAAE,SAAS;YACnF,MAAM,KAAK,GAAc,KAAK,CAAC,IAAI,KAAK,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;YACpF,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,KAAK,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/E,MAAM,QAAQ,GAAG,MAAM,CAAc,MAAM,EAAE,EAAE,CAAC,EAAE,WAAW,CAAC;YAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChC,IAAI,KAAK,KAAK,cAAc,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,UAAU,EAAE,CAAC;gBAC3E,IAAI,QAAQ,KAAK,SAAS;oBAAE,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,uBAAuB,QAAQ,EAAE,CAAC,CAAC;gBACvF,IAAI,OAAO,KAAK,SAAS;oBAAE,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAC;YAClF,CAAC;iBAAM,CAAC;gBACP,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,KAAK;oBAC/C,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,uBAAuB,QAAQ,EAAE,CAAC,CAAC;gBAC5D,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK;oBAAE,MAAM,IAAI,KAAK,CAAC,MAAM,EAAE,iCAAiC,CAAC,CAAC;YAC5G,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACxB,CAAC;IAAA,CACD;IAEO,sBAAsB,CAAC,MAA+B,EAA2B;QACxF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAC;QAC9C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,IAAI,KAAK,CAAC,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBAC9G,SAAS;YACV,CAAC;YACD,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,KAAK,iBAAiB,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;YACzE,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC7B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;gBAC3B,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;YACzB,CAAC;YACD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;gBACpB,KAAK,iBAAiB;oBACrB,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;wBACjE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,oCAAoC,CAAC,CAAC;oBACrE,CAAC;oBACD,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;oBAC7B,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;oBAC/B,MAAM;gBACP,KAAK,iBAAiB;oBACrB,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;wBAAE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,oCAAoC,CAAC,CAAC;oBACtG,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;oBAC/B,MAAM;gBACP,KAAK,iBAAiB;oBACrB,IAAI,MAAM,CAAC,MAAM;wBAAE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,4BAA4B,CAAC,CAAC;oBAC/E,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC;oBACrB,MAAM;YACR,CAAC;QACF,CAAC;QACD,OAAO,OAAO,CAAC;IAAA,CACf;IAEO,oBAAoB,CAAC,OAAwC,EAAQ;QAC5E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC7C,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACpC,MAAM,GAAG,GAAG,MAAM,CAAU,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9F,MAAM,QAAQ,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAiB,GAAG,CAAC,MAAM,CAAC,CAAC;YACvF,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAC;YACtG,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC;YAC5G,IAAI,QAAQ,EAAE,SAAS,KAAK,SAAS;gBAAE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;YACpF,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,2BAA2B,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,MAAM,CAAC,OAAO,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;gBACtC,MAAM,QAAQ,GAAG,MAAM,CACtB,IAAI,CAAC,EAAE,CAAC,OAAO,CACd,wFAAwF,CACxF,EACD,EAAE,CACF,CAAC;gBACF,IAAI,QAAQ,KAAK,SAAS;oBAAE,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,oBAAoB,CAAC,CAAC;gBAChF,IAAI,QAAQ,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;oBACjD,MAAM,IAAI,KAAK,CAAC,YAAY,EAAE,qCAAqC,CAAC,CAAC;gBACtE,CAAC;YACF,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,QAAS,CAAC;YAC1C,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;YAC/B,IAAI,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,IAAI,KAAK,SAAS;gBAAE,IAAI,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACpF,IAAI,MAAM,CAAC,MAAM,IAAI,QAAQ,KAAK,SAAS;gBAAE,IAAI,EAAE,CAAC;YACpD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,MAAM;gBAAE,IAAI,EAAE,CAAC;YAC1D,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;YACxC,IAAI,IAAI,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACrF,CAAC;IAAA,CACD;IAEO,iBAAiB,CAAC,OAA0D,EAAkB;QACrG,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;QACpC,OAAO,MAAM,CACZ,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;YAEP,CAAC,EACV,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,QAAQ,CACd,EAAE,EAAE,CAAC;IAAA,CACN;IAEO,eAAe,CAAC,KAAmB,EAAE,GAAQ,EAAQ;QAC5D,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACpB,KAAK,cAAc;gBAClB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,cAAc,CAAC,CAAC;gBAC7C,IAAI,CAAC,EAAE;qBACL,OAAO,CAAC,sDAAsD,CAAC;qBAC/D,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC/C,MAAM;YACP,KAAK,OAAO;gBACX,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;gBACtC,IAAI,CAAC,EAAE;qBACL,OAAO,CAAC,4FAA4F,CAAC;qBACrG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;gBAC1G,MAAM;YACP,KAAK,MAAM;gBACV,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;gBACrC,IAAI,CAAC,EAAE;qBACL,OAAO,CAAC;;;;iEAImD,CAAC;qBAC5D,GAAG,CACH,KAAK,CAAC,KAAK,CAAC,EAAE,EACd,KAAK,CAAC,KAAK,CAAC,cAAc,EAC1B,mBAAmB,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EACrC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EACxB,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAClC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAC9B,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CACvB,CAAC;gBACH,MAAM;YACP,KAAK,YAAY;gBAChB,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;gBAC3C,IAAI,CAAC,EAAE;qBACL,OAAO,CAAC;;iEAEmD,CAAC;qBAC5D,GAAG,CACH,KAAK,CAAC,KAAK,CAAC,EAAE,EACd,KAAK,CAAC,KAAK,CAAC,cAAc,EAC1B,KAAK,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,EACvF,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CACvB,CAAC;gBACH,MAAM;YACP,KAAK,iBAAiB,CAAC;YACvB,KAAK,iBAAiB,CAAC;YACvB,KAAK,iBAAiB;gBACrB,MAAM;QACR,CAAC;IAAA,CACD;IAEO,OAAO,CAAC,EAAM,EAAE,KAAgB,EAAQ;QAC/C,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kEAAkE,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAAA,CACnG;IAEO,oBAAoB,CAAC,OAAwC,EAAE,GAAQ,EAAQ;QACtF,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACpC,IAAI,MAAsB,CAAC;YAC3B,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBACjC,MAAM,GAAG;oBACR,GAAG,MAAM,CAAC,MAAM;oBAChB,SAAS,EAAE,GAAG;oBACd,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC5C,CAAC;gBACF,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;gBACnC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC;gBAC7B,IAAI,CAAC,EAAE;qBACL,OAAO,CAAC;;yCAE2B,CAAC;qBACpC,GAAG,CACH,EAAE,EACF,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,OAAO,EACb,GAAG,EACH,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAC1B,UAAU,CAAC,MAAM,CAAC,CAClB,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACP,MAAM,GAAG,GAAG,MAAM,CAAU,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,2CAA2C,CAAC,EAAE,EAAE,CAAE,CAAC;gBAC/F,MAAM,GAAG,SAAS,CAAiB,GAAG,CAAC,MAAM,CAAC,CAAC;YAChD,CAAC;YAED,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBAClC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC7D,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACjF,CAAC;gBACD,MAAM,cAAc,GACnB,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACpG,IAAI,CAAC,EAAE;qBACL,OAAO,CACP,kGAAkG,CAClG;qBACA,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;YAC7E,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBACnB,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;oBACjC,MAAM,GAAG,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;oBACvC,IAAI,CAAC,EAAE;yBACL,OAAO,CAAC,8DAA8D,CAAC;yBACvE,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;gBACpC,CAAC;gBACD,IAAI,aAAa,CAAC,MAAM,CAAC;oBAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC5G,CAAC;QACF,CAAC;IAAA,CACD;IAEO,UAAU,GAAS;QAC1B,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAAA,CAC5D;CACD","sourcesContent":["import type { Context, JsonValue } from \"@earendil-works/chord\";\nimport { applyImmutable, type Op } from \"@earendil-works/chord/delta\";\nimport type {\n\tConversationRecord,\n\tCursor,\n\tDocumentAddress,\n\tDocumentContent,\n\tDocumentCreate,\n\tDocumentPoint,\n\tDocumentQuery,\n\tDocumentRecord,\n\tEntryQuery,\n\tEntryRecord,\n\tId,\n\tJsonObject,\n\tPage,\n\tSeq,\n\tStorage,\n\tStorageWrite,\n\tStoredDocument,\n\tSubmissionRecord,\n\tTaskQuery,\n\tTaskRecord,\n} from \"../../types.ts\";\nimport type { SqliteDatabase, SqliteStatement, SqliteValue } from \"./database.ts\";\nimport { applySqliteMigrations } from \"./migrations.ts\";\n\ntype StoredTask = TaskRecord<JsonValue, JsonValue, JsonValue>;\ntype TableName = \"conversation\" | \"entry\" | \"task\" | \"submission\" | \"document\";\ntype RecordIdRow = { readonly record_type: TableName };\ntype JsonRow = { readonly record: string };\ntype EntryJsonRow = { readonly record: string; readonly commit_seq: number };\ntype RevisionRow = {\n\treadonly seq: number;\n\treadonly kind: DocumentContent[\"kind\"];\n\treadonly version: number;\n\treadonly content: string;\n};\ntype IdRow = { readonly id: number };\ntype MetadataRow = { readonly next_id: string; readonly next_seq: number };\ntype DocumentAction = {\n\tcreate?: DocumentCreate;\n\tcontent?: DocumentContent;\n\tretire: boolean;\n};\ntype ScopeColumns = {\n\treadonly scopeKind: DocumentRecord[\"scope\"][\"kind\"];\n\treadonly ownerId: number;\n};\n\nconst parseJson = <T>(value: string): T => JSON.parse(value) as T;\nconst encodeJson = (value: unknown): string => JSON.stringify(value) as string;\n// Some SQLite bindings replace lone UTF-16 surrogates. JSON encoding keeps indexed identities lossless.\nconst encodeIndexedString = (value: string): string => JSON.stringify(value);\n\nconst getRow = <T extends object>(statement: SqliteStatement, ...params: SqliteValue[]): T | undefined =>\n\tstatement.get<T>(...params);\n\nconst allRows = <T extends object>(statement: SqliteStatement, ...params: SqliteValue[]): T[] =>\n\tstatement.all<T>(...params);\n\nconst cursorId = (cursor: Cursor | undefined): Id | undefined => cursor?.after as Id | undefined;\n\nconst page = <T extends { readonly id: Id }>(values: readonly T[], limit: number): Page<T, Cursor> => {\n\tconst items = values.slice(0, limit);\n\tif (values.length <= limit) return { items };\n\treturn { items, next: { after: items.at(-1)!.id } };\n};\n\nconst scopeColumns = (scope: DocumentRecord[\"scope\"]): ScopeColumns => {\n\tswitch (scope.kind) {\n\t\tcase \"session\":\n\t\t\treturn { scopeKind: \"session\", ownerId: 0 };\n\t\tcase \"conversation\":\n\t\t\treturn { scopeKind: \"conversation\", ownerId: scope.conversationId };\n\t\tcase \"task\":\n\t\t\treturn { scopeKind: \"task\", ownerId: scope.taskId };\n\t}\n};\n\nconst addressParts = (address: DocumentAddress | DocumentCreate | DocumentRecord) => {\n\tconst scope = scopeColumns(address.scope);\n\treturn {\n\t\tkind: encodeIndexedString(address.kind),\n\t\t...scope,\n\t\tfamily: address.key === undefined ? 0 : 1,\n\t\tkeyValue: encodeIndexedString(address.key ?? \"\"),\n\t};\n};\n\nconst addressKey = (address: DocumentAddress | DocumentCreate | DocumentRecord): string => {\n\tconst parts = addressParts(address);\n\treturn JSON.stringify([parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue]);\n};\n\nconst isAliveAt = (record: DocumentRecord, at: DocumentPoint): boolean => {\n\tif (at === \"current\") return record.retiredAt === undefined;\n\treturn record.createdAt <= at && (record.retiredAt === undefined || at < record.retiredAt);\n};\n\nconst isCurrentOnly = (record: DocumentRecord): boolean =>\n\trecord.scope.kind !== \"conversation\" || record.history === \"latest\";\n\nconst writeId = (write: StorageWrite): Id | undefined => {\n\tswitch (write.type) {\n\t\tcase \"conversation\":\n\t\tcase \"entry\":\n\t\tcase \"task\":\n\t\tcase \"submission\":\n\t\t\treturn write.value.id;\n\t\tcase \"document.create\":\n\t\t\treturn write.record.id;\n\t\tcase \"document.change\":\n\t\tcase \"document.retire\":\n\t\t\treturn undefined;\n\t}\n};\n\nclass StatementCachingDatabase implements SqliteDatabase {\n\tprivate readonly database: SqliteDatabase;\n\tprivate readonly statements = new Map<string, SqliteStatement>();\n\n\tconstructor(database: SqliteDatabase) {\n\t\tthis.database = database;\n\t}\n\n\texec(sql: string): void {\n\t\tthis.database.exec(sql);\n\t}\n\n\tprepare(sql: string): SqliteStatement {\n\t\tlet statement = this.statements.get(sql);\n\t\tif (statement === undefined) {\n\t\t\tstatement = this.database.prepare(sql);\n\t\t\tthis.statements.set(sql, statement);\n\t\t}\n\t\treturn statement;\n\t}\n\n\ttransaction<T>(callback: () => T): T | Promise<T> {\n\t\treturn this.database.transaction(callback);\n\t}\n\n\tclose(): void | Promise<void> {\n\t\tthis.statements.clear();\n\t\treturn this.database.close();\n\t}\n}\n\n/** Portable SQLite implementation of the Pico storage contract. */\nexport class SqliteStorage implements Storage {\n\tprivate readonly db: SqliteDatabase;\n\tprivate nextId: Id;\n\tprivate closed = false;\n\n\tprivate constructor(db: SqliteDatabase, nextId: Id) {\n\t\tthis.db = new StatementCachingDatabase(db);\n\t\tthis.nextId = nextId;\n\t}\n\n\t/** Initialize storage over an owned SQLite database facade. */\n\tstatic async open(db: SqliteDatabase): Promise<SqliteStorage> {\n\t\ttry {\n\t\t\tawait applySqliteMigrations(db);\n\t\t\tconst metadata = getRow<MetadataRow>(\n\t\t\t\tdb.prepare(\"SELECT next_id, next_seq FROM durable_metadata WHERE singleton = 1\"),\n\t\t\t);\n\t\t\tif (metadata === undefined) throw new Error(\"Durable SQLite metadata is missing\");\n\t\t\treturn new SqliteStorage(db, Number(metadata.next_id));\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tawait db.close();\n\t\t\t} catch {\n\t\t\t\t// Preserve the initialization failure.\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync commit(writes: readonly StorageWrite[], _context: Context): Promise<Seq> {\n\t\tthis.assertOpen();\n\t\tconst documentActions = this.prepareDocumentActions(writes);\n\t\tconst candidateNextId = this.candidateNextId(writes);\n\t\tconst seq = await this.db.transaction(() => {\n\t\t\tconst metadata = getRow<MetadataRow>(\n\t\t\t\tthis.db.prepare(\"SELECT next_id, next_seq FROM durable_metadata WHERE singleton = 1\"),\n\t\t\t);\n\t\t\tif (metadata === undefined) throw new Error(\"Durable SQLite metadata is missing\");\n\t\t\tconst committedSeq = metadata.next_seq;\n\t\t\tthis.checkGlobalIds(writes);\n\t\t\tthis.checkDocumentActions(documentActions);\n\t\t\tfor (const write of writes) this.applyTableWrite(write, committedSeq);\n\t\t\tthis.applyDocumentActions(documentActions, committedSeq);\n\t\t\tthis.db\n\t\t\t\t.prepare(\"UPDATE durable_metadata SET next_id = ?, next_seq = ? WHERE singleton = 1\")\n\t\t\t\t.run(String(Math.max(Number(metadata.next_id), candidateNextId)), committedSeq + 1);\n\t\t\treturn committedSeq;\n\t\t});\n\t\tthis.nextId = Math.max(this.nextId, candidateNextId);\n\t\treturn seq;\n\t}\n\n\tasync mintId(): Promise<Id> {\n\t\tthis.assertOpen();\n\t\tif (!Number.isSafeInteger(this.nextId)) throw new Error(\"ID space is exhausted\");\n\t\treturn this.nextId++;\n\t}\n\n\tasync conversation(id: Id, _context: Context): Promise<ConversationRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM conversations WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : parseJson<ConversationRecord>(row.record);\n\t}\n\n\tasync scanConversations(\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<ConversationRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor) ?? -1;\n\t\tconst rows = allRows<JsonRow>(\n\t\t\tthis.db.prepare(\"SELECT record FROM conversations WHERE id > ? ORDER BY id LIMIT ?\"),\n\t\t\tafter,\n\t\t\tlimit + 1,\n\t\t);\n\t\treturn page(\n\t\t\trows.map((row) => parseJson<ConversationRecord>(row.record)),\n\t\t\tlimit,\n\t\t);\n\t}\n\n\tasync entry(\n\t\tid: Id,\n\t\t_context: Context,\n\t): Promise<{ readonly entry: EntryRecord; readonly commitSeq: Seq } | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<EntryJsonRow>(this.db.prepare(\"SELECT record, commit_seq FROM entries WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : { entry: parseJson<EntryRecord>(row.record), commitSeq: row.commit_seq };\n\t}\n\n\tasync findLatestHeadMarker(\n\t\tconversationId: Id,\n\t\tatOrBeforeEntryId: Id | undefined,\n\t\t_context: Context,\n\t): Promise<(EntryRecord & { readonly head: Id }) | undefined> {\n\t\tthis.assertOpen();\n\t\tlet conversation = this.readConversation(conversationId);\n\t\tif (conversation === undefined) throw new Error(`Unknown conversation: ${conversationId}`);\n\t\tlet upper = atOrBeforeEntryId;\n\t\twhile (true) {\n\t\t\tconst row =\n\t\t\t\tupper === undefined\n\t\t\t\t\t? getRow<JsonRow>(\n\t\t\t\t\t\t\tthis.db.prepare(\n\t\t\t\t\t\t\t\t\"SELECT record FROM entries WHERE conversation_id = ? AND head IS NOT NULL ORDER BY id DESC LIMIT 1\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tconversation.id,\n\t\t\t\t\t\t)\n\t\t\t\t\t: getRow<JsonRow>(\n\t\t\t\t\t\t\tthis.db.prepare(\n\t\t\t\t\t\t\t\t\"SELECT record FROM entries WHERE conversation_id = ? AND head IS NOT NULL AND id <= ? ORDER BY id DESC LIMIT 1\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tconversation.id,\n\t\t\t\t\t\t\tupper,\n\t\t\t\t\t\t);\n\t\t\tif (row !== undefined) return parseJson<EntryRecord & { readonly head: Id }>(row.record);\n\t\t\tif (conversation.parent === undefined) return undefined;\n\t\t\tupper = upper === undefined ? conversation.parent.at : Math.min(upper, conversation.parent.at);\n\t\t\tconversation = this.readConversation(conversation.parent.conversationId)!;\n\t\t}\n\t}\n\n\tasync scanEntries(\n\t\tquery: EntryQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<EntryRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tlet conversation = this.readConversation(query.conversationId);\n\t\tif (conversation === undefined) throw new Error(`Unknown conversation: ${query.conversationId}`);\n\t\tconst after = cursorId(cursor);\n\t\tlet upper = query.maxEntryId;\n\t\tif (after !== undefined) upper = Math.min(upper ?? Number.MAX_SAFE_INTEGER, after - 1);\n\t\tconst values: EntryRecord[] = [];\n\t\twhile (true) {\n\t\t\tconst clauses = [\"conversation_id = ?\"];\n\t\t\tconst params: SqliteValue[] = [conversation.id];\n\t\t\tif (query.minEntryId !== undefined) {\n\t\t\t\tclauses.push(\"id >= ?\");\n\t\t\t\tparams.push(query.minEntryId);\n\t\t\t}\n\t\t\tif (upper !== undefined) {\n\t\t\t\tclauses.push(\"id <= ?\");\n\t\t\t\tparams.push(upper);\n\t\t\t}\n\t\t\tparams.push(limit + 1 - values.length);\n\t\t\tconst rows = allRows<JsonRow>(\n\t\t\t\tthis.db.prepare(`SELECT record FROM entries WHERE ${clauses.join(\" AND \")} ORDER BY id DESC LIMIT ?`),\n\t\t\t\t...params,\n\t\t\t);\n\t\t\tvalues.push(...rows.map((row) => parseJson<EntryRecord>(row.record)));\n\t\t\tif (values.length > limit || conversation.parent === undefined) break;\n\t\t\tupper = upper === undefined ? conversation.parent.at : Math.min(upper, conversation.parent.at);\n\t\t\tif (query.minEntryId !== undefined && upper < query.minEntryId) break;\n\t\t\tconversation = this.readConversation(conversation.parent.conversationId)!;\n\t\t}\n\t\treturn page(values, limit);\n\t}\n\n\tasync task(id: Id, _context: Context): Promise<StoredTask | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM tasks WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : parseJson<StoredTask>(row.record);\n\t}\n\n\tasync scanTasks(\n\t\tquery: TaskQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<StoredTask, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst clauses = [\"id > ?\"];\n\t\tconst params: SqliteValue[] = [cursorId(cursor) ?? -1];\n\t\tif (query.conversationId !== undefined) {\n\t\t\tclauses.push(\"conversation_id = ?\");\n\t\t\tparams.push(query.conversationId);\n\t\t}\n\t\tif (query.kind !== undefined) {\n\t\t\tclauses.push(\"kind = ?\");\n\t\t\tparams.push(encodeIndexedString(query.kind));\n\t\t}\n\t\tif (query.status !== undefined) {\n\t\t\tclauses.push(\"status = ?\");\n\t\t\tparams.push(query.status);\n\t\t}\n\t\tif (query.abortRequested !== undefined) {\n\t\t\tclauses.push(\"abort_requested = ?\");\n\t\t\tparams.push(query.abortRequested ? 1 : 0);\n\t\t}\n\t\tif (query.background !== undefined) {\n\t\t\tclauses.push(\"background = ?\");\n\t\t\tparams.push(query.background ? 1 : 0);\n\t\t}\n\t\tparams.push(limit + 1);\n\t\tconst rows = allRows<JsonRow>(\n\t\t\tthis.db.prepare(`SELECT record FROM tasks WHERE ${clauses.join(\" AND \")} ORDER BY id LIMIT ?`),\n\t\t\t...params,\n\t\t);\n\t\treturn page(\n\t\t\trows.map((row) => parseJson<StoredTask>(row.record)),\n\t\t\tlimit,\n\t\t);\n\t}\n\n\tasync submission(id: Id, _context: Context): Promise<SubmissionRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM submissions WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : parseJson<SubmissionRecord>(row.record);\n\t}\n\n\tasync submissionByRequest(\n\t\tconversationId: Id,\n\t\trequestId: string,\n\t\t_context: Context,\n\t): Promise<SubmissionRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(\n\t\t\tthis.db.prepare(\"SELECT record FROM submissions WHERE conversation_id = ? AND request_id = ?\"),\n\t\t\tconversationId,\n\t\t\tencodeIndexedString(requestId),\n\t\t);\n\t\treturn row === undefined ? undefined : parseJson<SubmissionRecord>(row.record);\n\t}\n\n\tasync findDocument(\n\t\taddress: DocumentAddress,\n\t\tat: DocumentPoint,\n\t\t_context: Context,\n\t): Promise<DocumentRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst parts = addressParts(address);\n\t\tconst statement =\n\t\t\tat === \"current\"\n\t\t\t\t? this.db.prepare(`SELECT record FROM documents\n\t\t\t\t\tWHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ?\n\t\t\t\t\tAND retired_at IS NULL ORDER BY created_at DESC LIMIT 1`)\n\t\t\t\t: this.db.prepare(`SELECT record FROM documents\n\t\t\t\t\tWHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ?\n\t\t\t\t\tAND created_at <= ? AND (retired_at IS NULL OR retired_at > ?)\n\t\t\t\t\tORDER BY created_at DESC LIMIT 1`);\n\t\tconst params: SqliteValue[] = [parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue];\n\t\tif (at !== \"current\") params.push(at, at);\n\t\tconst row = getRow<JsonRow>(statement, ...params);\n\t\treturn row === undefined ? undefined : parseJson<DocumentRecord>(row.record);\n\t}\n\n\tasync document(id: Id, at: DocumentPoint, _context: Context): Promise<StoredDocument | undefined> {\n\t\tthis.assertOpen();\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM documents WHERE id = ?\"), id);\n\t\tif (row === undefined) return undefined;\n\t\tconst record = parseJson<DocumentRecord>(row.record);\n\t\tif (at !== \"current\" && isCurrentOnly(record)) {\n\t\t\tthrow new Error(`Document ${id} does not retain historical content`);\n\t\t}\n\t\tif (!isAliveAt(record, at)) return undefined;\n\t\tconst upper = at === \"current\" ? Number.MAX_SAFE_INTEGER : at;\n\t\tconst base = getRow<RevisionRow>(\n\t\t\tthis.db.prepare(`SELECT seq, kind, version, content FROM document_revisions\n\t\t\t\tWHERE document_id = ? AND kind = 'base' AND seq <= ? ORDER BY seq DESC LIMIT 1`),\n\t\t\tid,\n\t\t\tupper,\n\t\t);\n\t\tif (base === undefined) throw new Error(`Document ${id} is missing a required base`);\n\t\tlet value = parseJson<JsonObject>(base.content);\n\t\tconst tail = allRows<RevisionRow>(\n\t\t\tthis.db.prepare(`SELECT seq, kind, version, content FROM document_revisions\n\t\t\t\tWHERE document_id = ? AND seq > ? AND seq <= ? ORDER BY seq`),\n\t\t\tid,\n\t\t\tbase.seq,\n\t\t\tupper,\n\t\t);\n\t\tfor (const revision of tail) {\n\t\t\tif (revision.kind !== \"delta\" || revision.version !== base.version) {\n\t\t\t\tthrow new Error(`Document ${id} crosses a stored version boundary without a base`);\n\t\t\t}\n\t\t\tvalue = applyImmutable(value, parseJson<readonly Op[]>(revision.content)) as JsonObject;\n\t\t}\n\t\treturn { record, version: base.version, value };\n\t}\n\n\tasync scanDocuments(\n\t\tquery: DocumentQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<DocumentRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst scope = scopeColumns(query.scope);\n\t\tconst clauses = [\"scope_kind = ?\", \"owner_id = ?\", \"id > ?\"];\n\t\tconst params: SqliteValue[] = [scope.scopeKind, scope.ownerId, cursorId(cursor) ?? -1];\n\t\tif (query.kind !== undefined) {\n\t\t\tclauses.push(\"kind = ?\");\n\t\t\tparams.push(encodeIndexedString(query.kind));\n\t\t}\n\t\tif (query.at === \"current\") {\n\t\t\tclauses.push(\"retired_at IS NULL\");\n\t\t} else {\n\t\t\tclauses.push(\"created_at <= ?\", \"(retired_at IS NULL OR retired_at > ?)\");\n\t\t\tparams.push(query.at, query.at);\n\t\t}\n\t\tparams.push(limit + 1);\n\t\tconst rows = allRows<JsonRow>(\n\t\t\tthis.db.prepare(`SELECT record FROM documents WHERE ${clauses.join(\" AND \")} ORDER BY id LIMIT ?`),\n\t\t\t...params,\n\t\t);\n\t\treturn page(\n\t\t\trows.map((row) => parseJson<DocumentRecord>(row.record)),\n\t\t\tlimit,\n\t\t);\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tawait this.db.close();\n\t}\n\n\tprivate readConversation(id: Id): ConversationRecord | undefined {\n\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM conversations WHERE id = ?\"), id);\n\t\treturn row === undefined ? undefined : parseJson<ConversationRecord>(row.record);\n\t}\n\n\tprivate candidateNextId(writes: readonly StorageWrite[]): Id {\n\t\tlet nextId = this.nextId;\n\t\tfor (const write of writes) {\n\t\t\tconst id = writeId(write);\n\t\t\tif (id !== undefined) nextId = Math.max(nextId, id + 1);\n\t\t}\n\t\treturn nextId;\n\t}\n\n\tprivate checkGlobalIds(writes: readonly StorageWrite[]): void {\n\t\tconst claimed = new Map<Id, TableName>();\n\t\tconst lookup = this.db.prepare(\"SELECT record_type FROM record_ids WHERE id = ?\");\n\t\tfor (const write of writes) {\n\t\t\tif (write.type === \"document.change\" || write.type === \"document.retire\") continue;\n\t\t\tconst table: TableName = write.type === \"document.create\" ? \"document\" : write.type;\n\t\t\tconst id = write.type === \"document.create\" ? write.record.id : write.value.id;\n\t\t\tconst existing = getRow<RecordIdRow>(lookup, id)?.record_type;\n\t\t\tconst earlier = claimed.get(id);\n\t\t\tif (table === \"conversation\" || table === \"entry\" || table === \"document\") {\n\t\t\t\tif (existing !== undefined) throw new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\tif (earlier !== undefined) throw new Error(`ID ${id} is written more than once`);\n\t\t\t} else {\n\t\t\t\tif (existing !== undefined && existing !== table)\n\t\t\t\t\tthrow new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\tif (earlier !== undefined && earlier !== table) throw new Error(`ID ${id} is written as two record types`);\n\t\t\t}\n\t\t\tclaimed.set(id, table);\n\t\t}\n\t}\n\n\tprivate prepareDocumentActions(writes: readonly StorageWrite[]): Map<Id, DocumentAction> {\n\t\tconst actions = new Map<Id, DocumentAction>();\n\t\tfor (const write of writes) {\n\t\t\tif (write.type !== \"document.create\" && write.type !== \"document.change\" && write.type !== \"document.retire\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst id = write.type === \"document.create\" ? write.record.id : write.id;\n\t\t\tlet action = actions.get(id);\n\t\t\tif (action === undefined) {\n\t\t\t\taction = { retire: false };\n\t\t\t\tactions.set(id, action);\n\t\t\t}\n\t\t\tswitch (write.type) {\n\t\t\t\tcase \"document.create\":\n\t\t\t\t\tif (action.create !== undefined || action.content !== undefined) {\n\t\t\t\t\t\tthrow new Error(`Document ${id} has more than one content command`);\n\t\t\t\t\t}\n\t\t\t\t\taction.create = write.record;\n\t\t\t\t\taction.content = write.content;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"document.change\":\n\t\t\t\t\tif (action.content !== undefined) throw new Error(`Document ${id} has more than one content command`);\n\t\t\t\t\taction.content = write.content;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"document.retire\":\n\t\t\t\t\tif (action.retire) throw new Error(`Document ${id} is retired more than once`);\n\t\t\t\t\taction.retire = true;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn actions;\n\t}\n\n\tprivate checkDocumentActions(actions: ReadonlyMap<Id, DocumentAction>): void {\n\t\tconst liveCounts = new Map<string, number>();\n\t\tfor (const [id, action] of actions) {\n\t\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM documents WHERE id = ?\"), id);\n\t\t\tconst existing = row === undefined ? undefined : parseJson<DocumentRecord>(row.record);\n\t\t\tif (action.create === undefined && existing === undefined) throw new Error(`Unknown document: ${id}`);\n\t\t\tif (action.create !== undefined && existing !== undefined) throw new Error(`Document ${id} already exists`);\n\t\t\tif (existing?.retiredAt !== undefined) throw new Error(`Document ${id} is retired`);\n\t\t\tif (action.create !== undefined && action.content?.kind !== \"base\") {\n\t\t\t\tthrow new Error(`Document ${id} creation requires a base`);\n\t\t\t}\n\t\t\tif (action.content?.kind === \"delta\") {\n\t\t\t\tconst previous = getRow<{ readonly version: number }>(\n\t\t\t\t\tthis.db.prepare(\n\t\t\t\t\t\t\"SELECT version FROM document_revisions WHERE document_id = ? ORDER BY seq DESC LIMIT 1\",\n\t\t\t\t\t),\n\t\t\t\t\tid,\n\t\t\t\t);\n\t\t\t\tif (previous === undefined) throw new Error(`Document ${id} delta has no base`);\n\t\t\t\tif (previous.version !== action.content.version) {\n\t\t\t\t\tthrow new Error(`Document ${id} version transition requires a base`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst record = action.create ?? existing!;\n\t\t\tconst key = addressKey(record);\n\t\t\tlet live = liveCounts.get(key);\n\t\t\tif (live === undefined) live = this.currentDocumentId(record) === undefined ? 0 : 1;\n\t\t\tif (action.retire && existing !== undefined) live--;\n\t\t\tif (action.create !== undefined && !action.retire) live++;\n\t\t\tliveCounts.set(key, live);\n\t\t}\n\t\tfor (const live of liveCounts.values()) {\n\t\t\tif (live > 1) throw new Error(\"Document address already has a current incarnation\");\n\t\t}\n\t}\n\n\tprivate currentDocumentId(address: DocumentAddress | DocumentCreate | DocumentRecord): Id | undefined {\n\t\tconst parts = addressParts(address);\n\t\treturn getRow<IdRow>(\n\t\t\tthis.db.prepare(`SELECT id FROM documents\n\t\t\t\tWHERE kind = ? AND scope_kind = ? AND owner_id = ? AND family = ? AND key_value = ? AND retired_at IS NULL\n\t\t\t\tLIMIT 1`),\n\t\t\tparts.kind,\n\t\t\tparts.scopeKind,\n\t\t\tparts.ownerId,\n\t\t\tparts.family,\n\t\t\tparts.keyValue,\n\t\t)?.id;\n\t}\n\n\tprivate applyTableWrite(write: StorageWrite, seq: Seq): void {\n\t\tswitch (write.type) {\n\t\t\tcase \"conversation\":\n\t\t\t\tthis.claimId(write.value.id, \"conversation\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"INSERT INTO conversations (id, record) VALUES (?, ?)\")\n\t\t\t\t\t.run(write.value.id, encodeJson(write.value));\n\t\t\t\tbreak;\n\t\t\tcase \"entry\":\n\t\t\t\tthis.claimId(write.value.id, \"entry\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\"INSERT INTO entries (id, conversation_id, head, commit_seq, record) VALUES (?, ?, ?, ?, ?)\")\n\t\t\t\t\t.run(write.value.id, write.value.conversationId, write.value.head ?? null, seq, encodeJson(write.value));\n\t\t\t\tbreak;\n\t\t\tcase \"task\":\n\t\t\t\tthis.claimId(write.value.id, \"task\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`INSERT INTO tasks (id, conversation_id, kind, status, abort_requested, background, record)\n\t\t\t\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?)\n\t\t\t\t\t\tON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id, kind = excluded.kind,\n\t\t\t\t\t\tstatus = excluded.status, abort_requested = excluded.abort_requested,\n\t\t\t\t\t\tbackground = excluded.background, record = excluded.record`)\n\t\t\t\t\t.run(\n\t\t\t\t\t\twrite.value.id,\n\t\t\t\t\t\twrite.value.conversationId,\n\t\t\t\t\t\tencodeIndexedString(write.value.kind),\n\t\t\t\t\t\twrite.value.state.status,\n\t\t\t\t\t\twrite.value.abortRequested ? 1 : 0,\n\t\t\t\t\t\twrite.value.background ? 1 : 0,\n\t\t\t\t\t\tencodeJson(write.value),\n\t\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"submission\":\n\t\t\t\tthis.claimId(write.value.id, \"submission\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`INSERT INTO submissions (id, conversation_id, request_id, record) VALUES (?, ?, ?, ?)\n\t\t\t\t\t\tON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id,\n\t\t\t\t\t\trequest_id = excluded.request_id, record = excluded.record`)\n\t\t\t\t\t.run(\n\t\t\t\t\t\twrite.value.id,\n\t\t\t\t\t\twrite.value.conversationId,\n\t\t\t\t\t\twrite.value.requestId === undefined ? null : encodeIndexedString(write.value.requestId),\n\t\t\t\t\t\tencodeJson(write.value),\n\t\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tcase \"document.create\":\n\t\t\tcase \"document.change\":\n\t\t\tcase \"document.retire\":\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate claimId(id: Id, table: TableName): void {\n\t\tthis.db.prepare(\"INSERT OR IGNORE INTO record_ids (id, record_type) VALUES (?, ?)\").run(id, table);\n\t}\n\n\tprivate applyDocumentActions(actions: ReadonlyMap<Id, DocumentAction>, seq: Seq): void {\n\t\tfor (const [id, action] of actions) {\n\t\t\tlet record: DocumentRecord;\n\t\t\tif (action.create !== undefined) {\n\t\t\t\trecord = {\n\t\t\t\t\t...action.create,\n\t\t\t\t\tcreatedAt: seq,\n\t\t\t\t\t...(action.retire ? { retiredAt: seq } : {}),\n\t\t\t\t};\n\t\t\t\tconst parts = addressParts(record);\n\t\t\t\tthis.claimId(id, \"document\");\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(`INSERT INTO documents\n\t\t\t\t\t\t(id, kind, family, key_value, scope_kind, owner_id, created_at, retired_at, record)\n\t\t\t\t\t\tVALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)\n\t\t\t\t\t.run(\n\t\t\t\t\t\tid,\n\t\t\t\t\t\tparts.kind,\n\t\t\t\t\t\tparts.family,\n\t\t\t\t\t\tparts.keyValue,\n\t\t\t\t\t\tparts.scopeKind,\n\t\t\t\t\t\tparts.ownerId,\n\t\t\t\t\t\tseq,\n\t\t\t\t\t\taction.retire ? seq : null,\n\t\t\t\t\t\tencodeJson(record),\n\t\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tconst row = getRow<JsonRow>(this.db.prepare(\"SELECT record FROM documents WHERE id = ?\"), id)!;\n\t\t\t\trecord = parseJson<DocumentRecord>(row.record);\n\t\t\t}\n\n\t\t\tif (action.content !== undefined) {\n\t\t\t\tif (action.content.kind === \"base\" && isCurrentOnly(record)) {\n\t\t\t\t\tthis.db.prepare(\"DELETE FROM document_revisions WHERE document_id = ?\").run(id);\n\t\t\t\t}\n\t\t\t\tconst encodedContent =\n\t\t\t\t\taction.content.kind === \"base\" ? encodeJson(action.content.value) : encodeJson(action.content.ops);\n\t\t\t\tthis.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t\"INSERT INTO document_revisions (document_id, seq, kind, version, content) VALUES (?, ?, ?, ?, ?)\",\n\t\t\t\t\t)\n\t\t\t\t\t.run(id, seq, action.content.kind, action.content.version, encodedContent);\n\t\t\t}\n\n\t\t\tif (action.retire) {\n\t\t\t\tif (action.create === undefined) {\n\t\t\t\t\trecord = { ...record, retiredAt: seq };\n\t\t\t\t\tthis.db\n\t\t\t\t\t\t.prepare(\"UPDATE documents SET retired_at = ?, record = ? WHERE id = ?\")\n\t\t\t\t\t\t.run(seq, encodeJson(record), id);\n\t\t\t\t}\n\t\t\t\tif (isCurrentOnly(record)) this.db.prepare(\"DELETE FROM document_revisions WHERE document_id = ?\").run(id);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (this.closed) throw new Error(\"SqliteStorage is closed\");\n\t}\n}\n"]}
|