@earendil-works/pi-durable 1.4.0 → 1.5.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/CHANGELOG.md +204 -0
- package/README.md +195 -85
- package/dist/codemode/codemode.d.ts +6 -5
- package/dist/codemode/codemode.d.ts.map +1 -1
- package/dist/codemode/codemode.js +10 -49
- package/dist/codemode/codemode.js.map +1 -1
- package/dist/codemode/models.d.ts +2 -1
- package/dist/codemode/models.d.ts.map +1 -1
- package/dist/codemode/models.js +4 -4
- package/dist/codemode/models.js.map +1 -1
- package/dist/documents.d.ts +1 -1
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +1 -1
- package/dist/documents.js.map +1 -1
- package/dist/entries.d.ts +1 -1
- package/dist/entries.d.ts.map +1 -1
- package/dist/entries.js.map +1 -1
- package/dist/env/index.d.ts +13 -11
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js.map +1 -1
- package/dist/env/node-watch.d.ts +4 -4
- package/dist/env/node-watch.d.ts.map +1 -1
- package/dist/env/node-watch.js +18 -16
- package/dist/env/node-watch.js.map +1 -1
- package/dist/env/node.d.ts +3 -3
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +1 -1
- package/dist/env/node.js.map +1 -1
- package/dist/errors.d.ts +18 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +26 -3
- package/dist/errors.js.map +1 -1
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +37 -3
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.d.ts +8 -3
- package/dist/harness/compaction.d.ts.map +1 -1
- package/dist/harness/compaction.js +40 -11
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +24 -8
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +71 -25
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/define.d.ts +8 -4
- package/dist/harness/define.d.ts.map +1 -1
- package/dist/harness/define.js +4 -0
- package/dist/harness/define.js.map +1 -1
- package/dist/harness/generation.d.ts +39 -6
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +451 -100
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts +3 -1
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +87 -62
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +7 -2
- package/dist/harness/inbox.d.ts.map +1 -1
- package/dist/harness/inbox.js +5 -2
- package/dist/harness/inbox.js.map +1 -1
- package/dist/harness/json.d.ts +21 -5
- package/dist/harness/json.d.ts.map +1 -1
- package/dist/harness/json.js +23 -5
- package/dist/harness/json.js.map +1 -1
- package/dist/harness/live.d.ts +124 -17
- package/dist/harness/live.d.ts.map +1 -1
- package/dist/harness/live.js +93 -12
- package/dist/harness/live.js.map +1 -1
- package/dist/harness/partial-changes.d.ts +54 -0
- package/dist/harness/partial-changes.d.ts.map +1 -0
- package/dist/harness/partial-changes.js +163 -0
- package/dist/harness/partial-changes.js.map +1 -0
- package/dist/harness/prompt.d.ts +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/provider.d.ts +1 -1
- package/dist/harness/provider.d.ts.map +1 -1
- package/dist/harness/provider.js.map +1 -1
- package/dist/harness/registry-reader.d.ts +11 -0
- package/dist/harness/registry-reader.d.ts.map +1 -0
- package/dist/harness/registry-reader.js +25 -0
- package/dist/harness/registry-reader.js.map +1 -0
- package/dist/harness/scheduler.d.ts +48 -20
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +1006 -380
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +18 -11
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +63 -26
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +14 -9
- package/dist/harness/task-graph.d.ts.map +1 -1
- package/dist/harness/task-graph.js +38 -28
- package/dist/harness/task-graph.js.map +1 -1
- package/dist/harness/telemetry.d.ts +11 -11
- package/dist/harness/telemetry.d.ts.map +1 -1
- package/dist/harness/telemetry.js +7 -7
- package/dist/harness/telemetry.js.map +1 -1
- package/dist/harness/tool.d.ts +3 -1
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +172 -56
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/transcript.d.ts +72 -0
- package/dist/harness/transcript.d.ts.map +1 -0
- package/dist/harness/transcript.js +264 -0
- package/dist/harness/transcript.js.map +1 -0
- package/dist/harness/types.d.ts +237 -61
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage.d.ts +2 -1
- package/dist/harness/usage.d.ts.map +1 -1
- package/dist/harness/usage.js +1 -1
- package/dist/harness/usage.js.map +1 -1
- package/dist/harness/util.d.ts +4 -2
- package/dist/harness/util.d.ts.map +1 -1
- package/dist/harness/util.js +9 -0
- package/dist/harness/util.js.map +1 -1
- package/dist/harness/view.d.ts +44 -22
- package/dist/harness/view.d.ts.map +1 -1
- package/dist/harness/view.js +130 -71
- package/dist/harness/view.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/search.d.ts +19 -3
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +107 -44
- package/dist/search.js.map +1 -1
- package/dist/session/cached-storage.d.ts +72 -0
- package/dist/session/cached-storage.d.ts.map +1 -0
- package/dist/session/cached-storage.js +269 -0
- package/dist/session/cached-storage.js.map +1 -0
- package/dist/session/conversation-tails.d.ts +33 -0
- package/dist/session/conversation-tails.d.ts.map +1 -0
- package/dist/session/conversation-tails.js +101 -0
- package/dist/session/conversation-tails.js.map +1 -0
- package/dist/session/forks.d.ts +1 -1
- package/dist/session/forks.d.ts.map +1 -1
- package/dist/session/forks.js.map +1 -1
- package/dist/session/keys.d.ts +69 -0
- package/dist/session/keys.d.ts.map +1 -0
- package/dist/session/keys.js +161 -0
- package/dist/session/keys.js.map +1 -0
- package/dist/session/session.d.ts +169 -26
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +1029 -171
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +84 -5
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +227 -40
- package/dist/session/transaction.js.map +1 -1
- package/dist/session/writer.d.ts +74 -0
- package/dist/session/writer.d.ts.map +1 -0
- package/dist/session/writer.js +294 -0
- package/dist/session/writer.js.map +1 -0
- package/dist/storage/indexeddb.d.ts +12 -3
- package/dist/storage/indexeddb.d.ts.map +1 -1
- package/dist/storage/indexeddb.js +91 -41
- package/dist/storage/indexeddb.js.map +1 -1
- package/dist/storage/jsonl/index.d.ts +1 -1
- package/dist/storage/jsonl/index.d.ts.map +1 -1
- package/dist/storage/jsonl/index.js +1 -1
- package/dist/storage/jsonl/index.js.map +1 -1
- package/dist/storage/jsonl/node.d.ts +3 -3
- package/dist/storage/jsonl/node.d.ts.map +1 -1
- package/dist/storage/jsonl/node.js +2 -2
- package/dist/storage/jsonl/node.js.map +1 -1
- package/dist/storage/jsonl/storage.d.ts +46 -5
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/jsonl/storage.js +158 -43
- package/dist/storage/jsonl/storage.js.map +1 -1
- package/dist/storage/memory.d.ts +9 -3
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +28 -2
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/commit.d.ts +52 -0
- package/dist/storage/sqlite/commit.d.ts.map +1 -0
- package/dist/storage/sqlite/commit.js +371 -0
- package/dist/storage/sqlite/commit.js.map +1 -0
- package/dist/storage/sqlite/rows.d.ts +33 -0
- package/dist/storage/sqlite/rows.d.ts.map +1 -0
- package/dist/storage/sqlite/rows.js +53 -0
- package/dist/storage/sqlite/rows.js.map +1 -0
- package/dist/storage/sqlite/statements.d.ts +13 -0
- package/dist/storage/sqlite/statements.d.ts.map +1 -0
- package/dist/storage/sqlite/statements.js +88 -0
- package/dist/storage/sqlite/statements.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +23 -10
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +120 -261
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/storage/store/commit.d.ts +33 -0
- package/dist/storage/store/commit.d.ts.map +1 -0
- package/dist/storage/store/commit.js +286 -0
- package/dist/storage/store/commit.js.map +1 -0
- package/dist/storage/store/documents.d.ts +43 -0
- package/dist/storage/store/documents.d.ts.map +1 -0
- package/dist/storage/store/documents.js +102 -0
- package/dist/storage/store/documents.js.map +1 -0
- package/dist/storage/store/index.d.ts +2 -0
- package/dist/storage/store/index.d.ts.map +1 -0
- package/dist/storage/store/index.js +2 -0
- package/dist/storage/store/index.js.map +1 -0
- package/dist/storage/store/queries.d.ts +49 -0
- package/dist/storage/store/queries.d.ts.map +1 -0
- package/dist/storage/store/queries.js +93 -0
- package/dist/storage/store/queries.js.map +1 -0
- package/dist/storage/store/storage.d.ts +60 -0
- package/dist/storage/store/storage.d.ts.map +1 -0
- package/dist/storage/store/storage.js +438 -0
- package/dist/storage/store/storage.js.map +1 -0
- package/dist/storage/store/tables.d.ts +54 -0
- package/dist/storage/store/tables.d.ts.map +1 -0
- package/dist/storage/store/tables.js +108 -0
- package/dist/storage/store/tables.js.map +1 -0
- package/dist/testing/commit-writes.d.ts +5 -0
- package/dist/testing/commit-writes.d.ts.map +1 -0
- package/dist/testing/commit-writes.js +12 -0
- package/dist/testing/commit-writes.js.map +1 -0
- package/dist/testing/env-conformance.js +7 -7
- package/dist/testing/env-conformance.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/storage-benchmark.d.ts.map +1 -1
- package/dist/testing/storage-benchmark.js +18 -17
- package/dist/testing/storage-benchmark.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +354 -69
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/tool-search/tool-search.d.ts +8 -14
- package/dist/tool-search/tool-search.d.ts.map +1 -1
- package/dist/tool-search/tool-search.js +25 -13
- package/dist/tool-search/tool-search.js.map +1 -1
- package/dist/tools/bash.d.ts +13 -13
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +16 -15
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +7 -7
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +9 -9
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/file-mutation-queue.d.ts +1 -1
- package/dist/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/tools/file-mutation-queue.js.map +1 -1
- package/dist/tools/index.d.ts +32 -32
- package/dist/tools/path-utils.d.ts +1 -1
- package/dist/tools/path-utils.d.ts.map +1 -1
- package/dist/tools/path-utils.js.map +1 -1
- package/dist/tools/read.d.ts +6 -6
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +19 -8
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +4 -4
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +5 -5
- package/dist/tools/write.js.map +1 -1
- package/dist/types.d.ts +228 -78
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +19 -12
- package/dist/harness/events.d.ts +0 -186
- package/dist/harness/events.d.ts.map +0 -1
- package/dist/harness/events.js +0 -373
- package/dist/harness/events.js.map +0 -1
- package/dist/session/observation.d.ts +0 -42
- package/dist/session/observation.d.ts.map +0 -1
- package/dist/session/observation.js +0 -281
- package/dist/session/observation.js.map +0 -1
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// One `SqliteStorage.commit()` call in a few statements whatever its number of batches: the check state of all its
|
|
2
|
+
// batches read up front (`CommitState`), each batch checked in memory against what the batches before it left, and the
|
|
3
|
+
// accepted batches written per table (`PendingWrites`, through `statements.ts`). Over a remote SQLite every statement
|
|
4
|
+
// is a round trip while the write lock is held; a group of commits must not cost one round trip per write.
|
|
5
|
+
import { addressColumnsKey, addressKey, addressParts, documentRow, encodeIndexedString, encodeJson, isCurrentOnly, parseJson, } from "./rows.js";
|
|
6
|
+
import { insertRows, readRows, runForEach } from "./statements.js";
|
|
7
|
+
/** The record ID a write claims and its table, if it claims one. */
|
|
8
|
+
export const claimOf = (write) => {
|
|
9
|
+
switch (write.type) {
|
|
10
|
+
case "conversation":
|
|
11
|
+
case "entry":
|
|
12
|
+
case "task":
|
|
13
|
+
case "submission":
|
|
14
|
+
return { id: write.value.id, table: write.type };
|
|
15
|
+
case "document.create":
|
|
16
|
+
case "document.copy":
|
|
17
|
+
return { id: write.record.id, table: "document" };
|
|
18
|
+
case "document.change":
|
|
19
|
+
case "document.retire":
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
/** One batch's writes per document; throws when a document gets two content commands or two retirements. */
|
|
24
|
+
export function prepareDocumentActions(writes) {
|
|
25
|
+
const actions = new Map();
|
|
26
|
+
for (const write of writes) {
|
|
27
|
+
if (write.type !== "document.create" &&
|
|
28
|
+
write.type !== "document.copy" &&
|
|
29
|
+
write.type !== "document.change" &&
|
|
30
|
+
write.type !== "document.retire") {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const id = write.type === "document.create" || write.type === "document.copy" ? write.record.id : write.id;
|
|
34
|
+
let action = actions.get(id);
|
|
35
|
+
if (action === undefined) {
|
|
36
|
+
action = { retire: false };
|
|
37
|
+
actions.set(id, action);
|
|
38
|
+
}
|
|
39
|
+
switch (write.type) {
|
|
40
|
+
case "document.create":
|
|
41
|
+
if (action.create !== undefined || action.content !== undefined || action.copy !== undefined) {
|
|
42
|
+
throw new Error(`Document ${id} has more than one content command`);
|
|
43
|
+
}
|
|
44
|
+
action.create = write.record;
|
|
45
|
+
action.content = write.content;
|
|
46
|
+
break;
|
|
47
|
+
case "document.copy":
|
|
48
|
+
if (action.create !== undefined || action.content !== undefined || action.copy !== undefined) {
|
|
49
|
+
throw new Error(`Document ${id} has more than one content command`);
|
|
50
|
+
}
|
|
51
|
+
action.create = write.record;
|
|
52
|
+
action.copy = write.source;
|
|
53
|
+
break;
|
|
54
|
+
case "document.change":
|
|
55
|
+
if (action.content !== undefined || action.copy !== undefined) {
|
|
56
|
+
throw new Error(`Document ${id} has more than one content command`);
|
|
57
|
+
}
|
|
58
|
+
action.content = write.content;
|
|
59
|
+
break;
|
|
60
|
+
case "document.retire":
|
|
61
|
+
if (action.retire)
|
|
62
|
+
throw new Error(`Document ${id} is retired more than once`);
|
|
63
|
+
action.retire = true;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return actions;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* What the checks of one call's batches read: the stored state of every ID, document and address the batches touch,
|
|
71
|
+
* read in a few statements when the call starts, then advanced in memory by each accepted batch.
|
|
72
|
+
*/
|
|
73
|
+
export class CommitState {
|
|
74
|
+
/** The table of each claimed ID; absent: unclaimed. */
|
|
75
|
+
#recordIds;
|
|
76
|
+
/** Each touched document's record; absent: no such document. */
|
|
77
|
+
#documents;
|
|
78
|
+
/** The version of each delta-changed document's latest revision; absent: no revision. */
|
|
79
|
+
#versions;
|
|
80
|
+
/** The current incarnation of each created address (`addressKey`); `undefined`: none. */
|
|
81
|
+
#current;
|
|
82
|
+
constructor(recordIds, documents, versions, current) {
|
|
83
|
+
this.#recordIds = recordIds;
|
|
84
|
+
this.#documents = documents;
|
|
85
|
+
this.#versions = versions;
|
|
86
|
+
this.#current = current;
|
|
87
|
+
}
|
|
88
|
+
/** Reads the stored state the checks of `batches` need: one statement per kind of state (split only by size). */
|
|
89
|
+
static async read(executor, tables, batches) {
|
|
90
|
+
const ids = new Set();
|
|
91
|
+
const documentIds = new Set();
|
|
92
|
+
const deltaIds = new Set();
|
|
93
|
+
const addresses = new Map();
|
|
94
|
+
for (const writes of batches) {
|
|
95
|
+
for (const write of writes) {
|
|
96
|
+
const claim = claimOf(write);
|
|
97
|
+
if (claim !== undefined)
|
|
98
|
+
ids.add(claim.id);
|
|
99
|
+
switch (write.type) {
|
|
100
|
+
case "document.create":
|
|
101
|
+
case "document.copy":
|
|
102
|
+
documentIds.add(write.record.id);
|
|
103
|
+
addresses.set(addressKey(write.record), addressParts(write.record));
|
|
104
|
+
break;
|
|
105
|
+
case "document.change":
|
|
106
|
+
documentIds.add(write.id);
|
|
107
|
+
if (write.content.kind === "delta")
|
|
108
|
+
deltaIds.add(write.id);
|
|
109
|
+
break;
|
|
110
|
+
case "document.retire":
|
|
111
|
+
documentIds.add(write.id);
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const recordIds = new Map();
|
|
119
|
+
for (const row of await readRows(executor, `SELECT id, record_type FROM ${tables.recordIds} WHERE id IN (SELECT value FROM json_each(?))`, [...ids])) {
|
|
120
|
+
recordIds.set(row.id, row.record_type);
|
|
121
|
+
}
|
|
122
|
+
const documents = new Map();
|
|
123
|
+
for (const row of await readRows(executor, `SELECT record FROM ${tables.documents} WHERE id IN (SELECT value FROM json_each(?))`, [...documentIds])) {
|
|
124
|
+
const record = parseJson(row.record);
|
|
125
|
+
documents.set(record.id, record);
|
|
126
|
+
}
|
|
127
|
+
const versions = new Map();
|
|
128
|
+
// Driven by the requested IDs: each latest revision is one descending probe of the primary key.
|
|
129
|
+
for (const row of await readRows(executor, `SELECT requested.value AS document_id, (
|
|
130
|
+
SELECT version FROM ${tables.documentRevisions} WHERE document_id = requested.value ORDER BY seq DESC LIMIT 1
|
|
131
|
+
) AS version FROM json_each(?) AS requested`, [...deltaIds])) {
|
|
132
|
+
if (row.version !== null)
|
|
133
|
+
versions.set(row.document_id, row.version);
|
|
134
|
+
}
|
|
135
|
+
return new CommitState(recordIds, documents, versions, await readCurrent(executor, tables, addresses));
|
|
136
|
+
}
|
|
137
|
+
/** Durable's own checks of one batch that need no copy source: throws the refusal. */
|
|
138
|
+
check(writes, actions) {
|
|
139
|
+
this.#checkIds(writes);
|
|
140
|
+
this.#checkDocuments(actions);
|
|
141
|
+
}
|
|
142
|
+
/** Advances the state past an accepted batch stored at `seq`, its copies resolved to their content. */
|
|
143
|
+
apply(writes, actions, seq) {
|
|
144
|
+
for (const write of writes) {
|
|
145
|
+
// An accepted batch claims an ID only for its stored table, if it has one (`#checkIds`).
|
|
146
|
+
const claim = claimOf(write);
|
|
147
|
+
if (claim !== undefined)
|
|
148
|
+
this.#recordIds.set(claim.id, claim.table);
|
|
149
|
+
}
|
|
150
|
+
for (const [id, action] of actions) {
|
|
151
|
+
if (action.create !== undefined) {
|
|
152
|
+
const record = { ...action.create, createdAt: seq, ...(action.retire ? { retiredAt: seq } : {}) };
|
|
153
|
+
this.#documents.set(id, record);
|
|
154
|
+
if (!action.retire)
|
|
155
|
+
this.#current.set(addressKey(record), id);
|
|
156
|
+
}
|
|
157
|
+
else if (action.retire) {
|
|
158
|
+
const record = { ...this.#documents.get(id), retiredAt: seq };
|
|
159
|
+
this.#documents.set(id, record);
|
|
160
|
+
// The batch may have created the address's next incarnation already: only this one leaves.
|
|
161
|
+
const key = addressKey(record);
|
|
162
|
+
if (this.#current.get(key) === id)
|
|
163
|
+
this.#current.set(key, undefined);
|
|
164
|
+
}
|
|
165
|
+
// A retired document's versions are never read again: every later action on it is refused first.
|
|
166
|
+
if (action.content !== undefined)
|
|
167
|
+
this.#versions.set(id, action.content.version);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** A document's record as the accepted batches left it. */
|
|
171
|
+
document(id) {
|
|
172
|
+
return this.#documents.get(id);
|
|
173
|
+
}
|
|
174
|
+
#checkIds(writes) {
|
|
175
|
+
const claimed = new Map();
|
|
176
|
+
for (const write of writes) {
|
|
177
|
+
const claim = claimOf(write);
|
|
178
|
+
if (claim === undefined)
|
|
179
|
+
continue;
|
|
180
|
+
const { id, table } = claim;
|
|
181
|
+
const existing = this.#recordIds.get(id);
|
|
182
|
+
const earlier = claimed.get(id);
|
|
183
|
+
if (table === "conversation" || table === "entry" || table === "document") {
|
|
184
|
+
if (existing !== undefined)
|
|
185
|
+
throw new Error(`ID ${id} already belongs to ${existing}`);
|
|
186
|
+
if (earlier !== undefined)
|
|
187
|
+
throw new Error(`ID ${id} is written more than once`);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
if (existing !== undefined && existing !== table)
|
|
191
|
+
throw new Error(`ID ${id} already belongs to ${existing}`);
|
|
192
|
+
if (earlier !== undefined && earlier !== table)
|
|
193
|
+
throw new Error(`ID ${id} is written as two record types`);
|
|
194
|
+
}
|
|
195
|
+
claimed.set(id, table);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
#checkDocuments(actions) {
|
|
199
|
+
const liveCounts = new Map();
|
|
200
|
+
for (const [id, action] of actions) {
|
|
201
|
+
if (action.copy !== undefined && actions.has(action.copy.id)) {
|
|
202
|
+
throw new Error(`Document copy ${id} source is changed in the copy batch`);
|
|
203
|
+
}
|
|
204
|
+
const existing = this.#documents.get(id);
|
|
205
|
+
if (action.create === undefined && existing === undefined)
|
|
206
|
+
throw new Error(`Unknown document: ${id}`);
|
|
207
|
+
if (action.create !== undefined && existing !== undefined)
|
|
208
|
+
throw new Error(`Document ${id} already exists`);
|
|
209
|
+
if (existing?.retiredAt !== undefined)
|
|
210
|
+
throw new Error(`Document ${id} is retired`);
|
|
211
|
+
if (action.content?.kind === "delta") {
|
|
212
|
+
const previous = this.#versions.get(id);
|
|
213
|
+
if (previous === undefined)
|
|
214
|
+
throw new Error(`Document ${id} delta has no base`);
|
|
215
|
+
if (previous !== action.content.version)
|
|
216
|
+
throw new Error(`Document ${id} version transition requires a base`);
|
|
217
|
+
}
|
|
218
|
+
// A plain change keeps its address's live count: only creations and retirements are counted.
|
|
219
|
+
if (action.create === undefined && !action.retire)
|
|
220
|
+
continue;
|
|
221
|
+
const key = addressKey(action.create ?? existing);
|
|
222
|
+
let live = liveCounts.get(key);
|
|
223
|
+
// Only created addresses were read: an address no batch creates only loses incarnations, never exceeds one.
|
|
224
|
+
if (live === undefined)
|
|
225
|
+
live = this.#current.get(key) === undefined ? 0 : 1;
|
|
226
|
+
if (action.retire && existing !== undefined)
|
|
227
|
+
live--;
|
|
228
|
+
if (action.create !== undefined && !action.retire)
|
|
229
|
+
live++;
|
|
230
|
+
liveCounts.set(key, live);
|
|
231
|
+
}
|
|
232
|
+
for (const live of liveCounts.values()) {
|
|
233
|
+
if (live > 1)
|
|
234
|
+
throw new Error("Document address already has a current incarnation");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/** The current incarnation of each of `addresses`, `undefined` for one without. */
|
|
239
|
+
async function readCurrent(executor, tables, addresses) {
|
|
240
|
+
const current = new Map();
|
|
241
|
+
for (const key of addresses.keys())
|
|
242
|
+
current.set(key, undefined);
|
|
243
|
+
const rows = await readRows(executor, `SELECT document.id, document.kind, document.scope_kind, document.owner_id, document.family, document.key_value
|
|
244
|
+
FROM json_each(?) AS address JOIN ${tables.documents} AS document
|
|
245
|
+
ON document.kind = json_extract(address.value, '$[0]') AND document.scope_kind = json_extract(address.value, '$[1]')
|
|
246
|
+
AND document.owner_id = json_extract(address.value, '$[2]') AND document.family = json_extract(address.value, '$[3]')
|
|
247
|
+
AND document.key_value = json_extract(address.value, '$[4]')
|
|
248
|
+
WHERE document.retired_at IS NULL`, [...addresses.values()].map((part) => [part.kind, part.scopeKind, part.ownerId, part.family, part.keyValue]));
|
|
249
|
+
for (const row of rows) {
|
|
250
|
+
current.set(addressColumnsKey(row.kind, row.scope_kind, row.owner_id, row.family, row.key_value), row.id);
|
|
251
|
+
}
|
|
252
|
+
return current;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* The writes of the accepted batches not yet written, per table, as their net effect: a task or submission written
|
|
256
|
+
* twice is written once, as last written; a current-only document's revisions before its latest base or retirement
|
|
257
|
+
* are dropped (and its stored ones deleted).
|
|
258
|
+
*/
|
|
259
|
+
export class PendingWrites {
|
|
260
|
+
#recordIds = new Map();
|
|
261
|
+
#conversations = [];
|
|
262
|
+
#entries = [];
|
|
263
|
+
#tasks = new Map();
|
|
264
|
+
#submissions = new Map();
|
|
265
|
+
/** Each created or retired document's record. */
|
|
266
|
+
#documents = new Map();
|
|
267
|
+
/** The documents whose stored revisions go. */
|
|
268
|
+
#cleared = new Set();
|
|
269
|
+
/** Each document's new revisions, in seq order. */
|
|
270
|
+
#revisions = new Map();
|
|
271
|
+
/** Adds a batch stored at `seq`, after `state` applied it. */
|
|
272
|
+
add(writes, actions, seq, state) {
|
|
273
|
+
for (const write of writes)
|
|
274
|
+
this.#addWrite(write, seq);
|
|
275
|
+
for (const [id, action] of actions)
|
|
276
|
+
this.#addDocument(id, action, seq, state.document(id));
|
|
277
|
+
}
|
|
278
|
+
#addWrite(write, seq) {
|
|
279
|
+
const claim = claimOf(write);
|
|
280
|
+
if (claim !== undefined)
|
|
281
|
+
this.#recordIds.set(claim.id, claim.table);
|
|
282
|
+
switch (write.type) {
|
|
283
|
+
case "conversation":
|
|
284
|
+
this.#conversations.push([
|
|
285
|
+
write.value.id,
|
|
286
|
+
write.value.owner?.conversationId ?? null,
|
|
287
|
+
write.value.owner?.taskId ?? null,
|
|
288
|
+
encodeJson(write.value),
|
|
289
|
+
]);
|
|
290
|
+
break;
|
|
291
|
+
case "entry":
|
|
292
|
+
this.#entries.push([
|
|
293
|
+
write.value.id,
|
|
294
|
+
write.value.conversationId,
|
|
295
|
+
write.value.head ?? null,
|
|
296
|
+
seq,
|
|
297
|
+
encodeJson(write.value),
|
|
298
|
+
]);
|
|
299
|
+
break;
|
|
300
|
+
case "task":
|
|
301
|
+
this.#tasks.set(write.value.id, [
|
|
302
|
+
write.value.id,
|
|
303
|
+
write.value.conversationId,
|
|
304
|
+
encodeIndexedString(write.value.kind),
|
|
305
|
+
write.value.state.status,
|
|
306
|
+
write.value.abortRequested ? 1 : 0,
|
|
307
|
+
write.value.background ? 1 : 0,
|
|
308
|
+
encodeJson(write.value),
|
|
309
|
+
]);
|
|
310
|
+
break;
|
|
311
|
+
case "submission":
|
|
312
|
+
this.#submissions.set(write.value.id, [
|
|
313
|
+
write.value.id,
|
|
314
|
+
write.value.conversationId,
|
|
315
|
+
write.value.requestId === undefined ? null : encodeIndexedString(write.value.requestId),
|
|
316
|
+
write.value.status,
|
|
317
|
+
encodeJson(write.value),
|
|
318
|
+
]);
|
|
319
|
+
break;
|
|
320
|
+
default:
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
#addDocument(id, action, seq, record) {
|
|
325
|
+
if (action.create !== undefined || action.retire)
|
|
326
|
+
this.#documents.set(id, record);
|
|
327
|
+
const content = action.content;
|
|
328
|
+
if (content !== undefined) {
|
|
329
|
+
if (content.kind === "base" && isCurrentOnly(record))
|
|
330
|
+
this.#clear(id);
|
|
331
|
+
const encoded = content.kind === "base" ? encodeJson(content.value) : encodeJson(content.ops);
|
|
332
|
+
let revisions = this.#revisions.get(id);
|
|
333
|
+
if (revisions === undefined) {
|
|
334
|
+
revisions = [];
|
|
335
|
+
this.#revisions.set(id, revisions);
|
|
336
|
+
}
|
|
337
|
+
revisions.push([id, seq, content.kind, content.version, encoded]);
|
|
338
|
+
}
|
|
339
|
+
if (action.retire && isCurrentOnly(record))
|
|
340
|
+
this.#clear(id);
|
|
341
|
+
}
|
|
342
|
+
/** Drops a document's revisions: the pending ones, and the stored ones when written. */
|
|
343
|
+
#clear(id) {
|
|
344
|
+
this.#cleared.add(id);
|
|
345
|
+
this.#revisions.delete(id);
|
|
346
|
+
}
|
|
347
|
+
/** Writes everything pending, a few statements per table (`insertRows`), and forgets it. */
|
|
348
|
+
async flush(executor, tables) {
|
|
349
|
+
await insertRows(executor, tables.recordIds, ["id", "record_type"], [...this.#recordIds].map(([id, table]) => [id, table]), "ON CONFLICT(id) DO NOTHING");
|
|
350
|
+
await insertRows(executor, tables.conversations, ["id", "owner_conversation_id", "owner_task_id", "record"], this.#conversations);
|
|
351
|
+
await insertRows(executor, tables.entries, ["id", "conversation_id", "head", "commit_seq", "record"], this.#entries);
|
|
352
|
+
await insertRows(executor, tables.tasks, ["id", "conversation_id", "kind", "status", "abort_requested", "background", "record"], [...this.#tasks.values()], `ON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id, kind = excluded.kind,
|
|
353
|
+
status = excluded.status, abort_requested = excluded.abort_requested,
|
|
354
|
+
background = excluded.background, record = excluded.record`);
|
|
355
|
+
await insertRows(executor, tables.submissions, ["id", "conversation_id", "request_id", "status", "record"], [...this.#submissions.values()], `ON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id,
|
|
356
|
+
request_id = excluded.request_id, status = excluded.status, record = excluded.record`);
|
|
357
|
+
// A created document is inserted, a stored one retired: one upsert writes both.
|
|
358
|
+
await insertRows(executor, tables.documents, ["id", "kind", "family", "key_value", "scope_kind", "owner_id", "created_at", "retired_at", "record"], [...this.#documents.values()].map(documentRow), "ON CONFLICT(id) DO UPDATE SET retired_at = excluded.retired_at, record = excluded.record");
|
|
359
|
+
await runForEach(executor, `DELETE FROM ${tables.documentRevisions} WHERE document_id IN (SELECT value FROM json_each(?))`, [...this.#cleared]);
|
|
360
|
+
await insertRows(executor, tables.documentRevisions, ["document_id", "seq", "kind", "version", "content"], [...this.#revisions.values()].flat());
|
|
361
|
+
this.#recordIds.clear();
|
|
362
|
+
this.#conversations.length = 0;
|
|
363
|
+
this.#entries.length = 0;
|
|
364
|
+
this.#tasks.clear();
|
|
365
|
+
this.#submissions.clear();
|
|
366
|
+
this.#documents.clear();
|
|
367
|
+
this.#cleared.clear();
|
|
368
|
+
this.#revisions.clear();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/storage/sqlite/commit.ts"],"names":[],"mappings":"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","sourcesContent":["// One `SqliteStorage.commit()` call in a few statements whatever its number of batches: the check state of all its\n// batches read up front (`CommitState`), each batch checked in memory against what the batches before it left, and the\n// accepted batches written per table (`PendingWrites`, through `statements.ts`). Over a remote SQLite every statement\n// is a round trip while the write lock is held; a group of commits must not cost one round trip per write.\n\nimport type { SqliteExecutor } from \"@earendil-works/chord/sqlite\";\nimport type {\n\tDocumentContent,\n\tDocumentCreate,\n\tDocumentId,\n\tDocumentRecord,\n\tId,\n\tSeq,\n\tStorageWrite,\n} from \"../../types.ts\";\nimport {\n\taddressColumnsKey,\n\taddressKey,\n\taddressParts,\n\ttype Cell,\n\tdocumentRow,\n\tencodeIndexedString,\n\tencodeJson,\n\tisCurrentOnly,\n\tparseJson,\n} from \"./rows.ts\";\nimport type { SqliteSchema } from \"./schema.ts\";\nimport { insertRows, readRows, runForEach } from \"./statements.ts\";\n\n/** The table a record ID belongs to, as `record_ids` stores it. */\nexport type TableName = \"conversation\" | \"entry\" | \"task\" | \"submission\" | \"document\";\n\n/** What one batch does to one document: its creation or copy, its new content, its retirement. */\nexport type DocumentAction = {\n\tcreate?: DocumentCreate;\n\tcopy?: Extract<StorageWrite, { readonly type: \"document.copy\" }>[\"source\"];\n\tcontent?: DocumentContent;\n\tretire: boolean;\n};\n\ntype Tables = SqliteSchema[\"tables\"];\n\n/** The record ID a write claims and its table, if it claims one. */\nexport const claimOf = (write: StorageWrite): { readonly id: Id<string>; readonly table: TableName } | 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 { id: write.value.id, table: write.type };\n\t\tcase \"document.create\":\n\t\tcase \"document.copy\":\n\t\t\treturn { id: write.record.id, table: \"document\" };\n\t\tcase \"document.change\":\n\t\tcase \"document.retire\":\n\t\t\treturn undefined;\n\t}\n};\n\n/** One batch's writes per document; throws when a document gets two content commands or two retirements. */\nexport function prepareDocumentActions(writes: readonly StorageWrite[]): Map<DocumentId, DocumentAction> {\n\tconst actions = new Map<DocumentId, DocumentAction>();\n\tfor (const write of writes) {\n\t\tif (\n\t\t\twrite.type !== \"document.create\" &&\n\t\t\twrite.type !== \"document.copy\" &&\n\t\t\twrite.type !== \"document.change\" &&\n\t\t\twrite.type !== \"document.retire\"\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst id = write.type === \"document.create\" || write.type === \"document.copy\" ? write.record.id : write.id;\n\t\tlet action = actions.get(id);\n\t\tif (action === undefined) {\n\t\t\taction = { retire: false };\n\t\t\tactions.set(id, action);\n\t\t}\n\t\tswitch (write.type) {\n\t\t\tcase \"document.create\":\n\t\t\t\tif (action.create !== undefined || action.content !== undefined || action.copy !== undefined) {\n\t\t\t\t\tthrow new Error(`Document ${id} has more than one content command`);\n\t\t\t\t}\n\t\t\t\taction.create = write.record;\n\t\t\t\taction.content = write.content;\n\t\t\t\tbreak;\n\t\t\tcase \"document.copy\":\n\t\t\t\tif (action.create !== undefined || action.content !== undefined || action.copy !== undefined) {\n\t\t\t\t\tthrow new Error(`Document ${id} has more than one content command`);\n\t\t\t\t}\n\t\t\t\taction.create = write.record;\n\t\t\t\taction.copy = write.source;\n\t\t\t\tbreak;\n\t\t\tcase \"document.change\":\n\t\t\t\tif (action.content !== undefined || action.copy !== undefined) {\n\t\t\t\t\tthrow new Error(`Document ${id} has more than one content command`);\n\t\t\t\t}\n\t\t\t\taction.content = write.content;\n\t\t\t\tbreak;\n\t\t\tcase \"document.retire\":\n\t\t\t\tif (action.retire) throw new Error(`Document ${id} is retired more than once`);\n\t\t\t\taction.retire = true;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn actions;\n}\n\ntype RecordIdRow = { readonly id: number; readonly record_type: TableName };\ntype DocumentRow = { readonly record: string };\ntype VersionRow = { readonly document_id: number; readonly version: number | null };\ntype AddressRow = {\n\treadonly id: number;\n\treadonly kind: string;\n\treadonly scope_kind: string;\n\treadonly owner_id: number;\n\treadonly family: number;\n\treadonly key_value: string;\n};\n\n/**\n * What the checks of one call's batches read: the stored state of every ID, document and address the batches touch,\n * read in a few statements when the call starts, then advanced in memory by each accepted batch.\n */\nexport class CommitState {\n\t/** The table of each claimed ID; absent: unclaimed. */\n\treadonly #recordIds: Map<number, TableName>;\n\t/** Each touched document's record; absent: no such document. */\n\treadonly #documents: Map<number, DocumentRecord>;\n\t/** The version of each delta-changed document's latest revision; absent: no revision. */\n\treadonly #versions: Map<number, number>;\n\t/** The current incarnation of each created address (`addressKey`); `undefined`: none. */\n\treadonly #current: Map<string, DocumentId | undefined>;\n\n\tprivate constructor(\n\t\trecordIds: Map<number, TableName>,\n\t\tdocuments: Map<number, DocumentRecord>,\n\t\tversions: Map<number, number>,\n\t\tcurrent: Map<string, DocumentId | undefined>,\n\t) {\n\t\tthis.#recordIds = recordIds;\n\t\tthis.#documents = documents;\n\t\tthis.#versions = versions;\n\t\tthis.#current = current;\n\t}\n\n\t/** Reads the stored state the checks of `batches` need: one statement per kind of state (split only by size). */\n\tstatic async read(\n\t\texecutor: SqliteExecutor,\n\t\ttables: Tables,\n\t\tbatches: readonly (readonly StorageWrite[])[],\n\t): Promise<CommitState> {\n\t\tconst ids = new Set<number>();\n\t\tconst documentIds = new Set<number>();\n\t\tconst deltaIds = new Set<number>();\n\t\tconst addresses = new Map<string, ReturnType<typeof addressParts>>();\n\t\tfor (const writes of batches) {\n\t\t\tfor (const write of writes) {\n\t\t\t\tconst claim = claimOf(write);\n\t\t\t\tif (claim !== undefined) ids.add(claim.id);\n\t\t\t\tswitch (write.type) {\n\t\t\t\t\tcase \"document.create\":\n\t\t\t\t\tcase \"document.copy\":\n\t\t\t\t\t\tdocumentIds.add(write.record.id);\n\t\t\t\t\t\taddresses.set(addressKey(write.record), addressParts(write.record));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"document.change\":\n\t\t\t\t\t\tdocumentIds.add(write.id);\n\t\t\t\t\t\tif (write.content.kind === \"delta\") deltaIds.add(write.id);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"document.retire\":\n\t\t\t\t\t\tdocumentIds.add(write.id);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst recordIds = new Map<number, TableName>();\n\t\tfor (const row of await readRows<RecordIdRow>(\n\t\t\texecutor,\n\t\t\t`SELECT id, record_type FROM ${tables.recordIds} WHERE id IN (SELECT value FROM json_each(?))`,\n\t\t\t[...ids],\n\t\t)) {\n\t\t\trecordIds.set(row.id, row.record_type);\n\t\t}\n\t\tconst documents = new Map<number, DocumentRecord>();\n\t\tfor (const row of await readRows<DocumentRow>(\n\t\t\texecutor,\n\t\t\t`SELECT record FROM ${tables.documents} WHERE id IN (SELECT value FROM json_each(?))`,\n\t\t\t[...documentIds],\n\t\t)) {\n\t\t\tconst record = parseJson<DocumentRecord>(row.record);\n\t\t\tdocuments.set(record.id, record);\n\t\t}\n\t\tconst versions = new Map<number, number>();\n\t\t// Driven by the requested IDs: each latest revision is one descending probe of the primary key.\n\t\tfor (const row of await readRows<VersionRow>(\n\t\t\texecutor,\n\t\t\t`SELECT requested.value AS document_id, (\n\t\t\t\tSELECT version FROM ${tables.documentRevisions} WHERE document_id = requested.value ORDER BY seq DESC LIMIT 1\n\t\t\t) AS version FROM json_each(?) AS requested`,\n\t\t\t[...deltaIds],\n\t\t)) {\n\t\t\tif (row.version !== null) versions.set(row.document_id, row.version);\n\t\t}\n\t\treturn new CommitState(recordIds, documents, versions, await readCurrent(executor, tables, addresses));\n\t}\n\n\t/** Durable's own checks of one batch that need no copy source: throws the refusal. */\n\tcheck(writes: readonly StorageWrite[], actions: ReadonlyMap<DocumentId, DocumentAction>): void {\n\t\tthis.#checkIds(writes);\n\t\tthis.#checkDocuments(actions);\n\t}\n\n\t/** Advances the state past an accepted batch stored at `seq`, its copies resolved to their content. */\n\tapply(writes: readonly StorageWrite[], actions: ReadonlyMap<DocumentId, DocumentAction>, seq: Seq): void {\n\t\tfor (const write of writes) {\n\t\t\t// An accepted batch claims an ID only for its stored table, if it has one (`#checkIds`).\n\t\t\tconst claim = claimOf(write);\n\t\t\tif (claim !== undefined) this.#recordIds.set(claim.id, claim.table);\n\t\t}\n\t\tfor (const [id, action] of actions) {\n\t\t\tif (action.create !== undefined) {\n\t\t\t\tconst record = { ...action.create, createdAt: seq, ...(action.retire ? { retiredAt: seq } : {}) };\n\t\t\t\tthis.#documents.set(id, record);\n\t\t\t\tif (!action.retire) this.#current.set(addressKey(record), id);\n\t\t\t} else if (action.retire) {\n\t\t\t\tconst record = { ...this.#documents.get(id)!, retiredAt: seq };\n\t\t\t\tthis.#documents.set(id, record);\n\t\t\t\t// The batch may have created the address's next incarnation already: only this one leaves.\n\t\t\t\tconst key = addressKey(record);\n\t\t\t\tif (this.#current.get(key) === id) this.#current.set(key, undefined);\n\t\t\t}\n\t\t\t// A retired document's versions are never read again: every later action on it is refused first.\n\t\t\tif (action.content !== undefined) this.#versions.set(id, action.content.version);\n\t\t}\n\t}\n\n\t/** A document's record as the accepted batches left it. */\n\tdocument(id: DocumentId): DocumentRecord | undefined {\n\t\treturn this.#documents.get(id);\n\t}\n\n\t#checkIds(writes: readonly StorageWrite[]): void {\n\t\tconst claimed = new Map<Id<string>, TableName>();\n\t\tfor (const write of writes) {\n\t\t\tconst claim = claimOf(write);\n\t\t\tif (claim === undefined) continue;\n\t\t\tconst { id, table } = claim;\n\t\t\tconst existing = this.#recordIds.get(id);\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\t#checkDocuments(actions: ReadonlyMap<DocumentId, DocumentAction>): void {\n\t\tconst liveCounts = new Map<string, number>();\n\t\tfor (const [id, action] of actions) {\n\t\t\tif (action.copy !== undefined && actions.has(action.copy.id)) {\n\t\t\t\tthrow new Error(`Document copy ${id} source is changed in the copy batch`);\n\t\t\t}\n\t\t\tconst existing = this.#documents.get(id);\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.content?.kind === \"delta\") {\n\t\t\t\tconst previous = this.#versions.get(id);\n\t\t\t\tif (previous === undefined) throw new Error(`Document ${id} delta has no base`);\n\t\t\t\tif (previous !== action.content.version)\n\t\t\t\t\tthrow new Error(`Document ${id} version transition requires a base`);\n\t\t\t}\n\t\t\t// A plain change keeps its address's live count: only creations and retirements are counted.\n\t\t\tif (action.create === undefined && !action.retire) continue;\n\t\t\tconst key = addressKey(action.create ?? existing!);\n\t\t\tlet live = liveCounts.get(key);\n\t\t\t// Only created addresses were read: an address no batch creates only loses incarnations, never exceeds one.\n\t\t\tif (live === undefined) live = this.#current.get(key) === 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\n/** The current incarnation of each of `addresses`, `undefined` for one without. */\nasync function readCurrent(\n\texecutor: SqliteExecutor,\n\ttables: Tables,\n\taddresses: ReadonlyMap<string, ReturnType<typeof addressParts>>,\n): Promise<Map<string, DocumentId | undefined>> {\n\tconst current = new Map<string, DocumentId | undefined>();\n\tfor (const key of addresses.keys()) current.set(key, undefined);\n\tconst rows = await readRows<AddressRow>(\n\t\texecutor,\n\t\t`SELECT document.id, document.kind, document.scope_kind, document.owner_id, document.family, document.key_value\n\t\t\tFROM json_each(?) AS address JOIN ${tables.documents} AS document\n\t\t\tON document.kind = json_extract(address.value, '$[0]') AND document.scope_kind = json_extract(address.value, '$[1]')\n\t\t\tAND document.owner_id = json_extract(address.value, '$[2]') AND document.family = json_extract(address.value, '$[3]')\n\t\t\tAND document.key_value = json_extract(address.value, '$[4]')\n\t\t\tWHERE document.retired_at IS NULL`,\n\t\t[...addresses.values()].map((part) => [part.kind, part.scopeKind, part.ownerId, part.family, part.keyValue]),\n\t);\n\tfor (const row of rows) {\n\t\tcurrent.set(\n\t\t\taddressColumnsKey(row.kind, row.scope_kind, row.owner_id, row.family, row.key_value),\n\t\t\trow.id as DocumentId,\n\t\t);\n\t}\n\treturn current;\n}\n\n/**\n * The writes of the accepted batches not yet written, per table, as their net effect: a task or submission written\n * twice is written once, as last written; a current-only document's revisions before its latest base or retirement\n * are dropped (and its stored ones deleted).\n */\nexport class PendingWrites {\n\treadonly #recordIds = new Map<number, TableName>();\n\treadonly #conversations: Cell[][] = [];\n\treadonly #entries: Cell[][] = [];\n\treadonly #tasks = new Map<number, Cell[]>();\n\treadonly #submissions = new Map<number, Cell[]>();\n\t/** Each created or retired document's record. */\n\treadonly #documents = new Map<number, DocumentRecord>();\n\t/** The documents whose stored revisions go. */\n\treadonly #cleared = new Set<number>();\n\t/** Each document's new revisions, in seq order. */\n\treadonly #revisions = new Map<number, Cell[][]>();\n\n\t/** Adds a batch stored at `seq`, after `state` applied it. */\n\tadd(\n\t\twrites: readonly StorageWrite[],\n\t\tactions: ReadonlyMap<DocumentId, DocumentAction>,\n\t\tseq: Seq,\n\t\tstate: CommitState,\n\t): void {\n\t\tfor (const write of writes) this.#addWrite(write, seq);\n\t\tfor (const [id, action] of actions) this.#addDocument(id, action, seq, state.document(id)!);\n\t}\n\n\t#addWrite(write: StorageWrite, seq: Seq): void {\n\t\tconst claim = claimOf(write);\n\t\tif (claim !== undefined) this.#recordIds.set(claim.id, claim.table);\n\t\tswitch (write.type) {\n\t\t\tcase \"conversation\":\n\t\t\t\tthis.#conversations.push([\n\t\t\t\t\twrite.value.id,\n\t\t\t\t\twrite.value.owner?.conversationId ?? null,\n\t\t\t\t\twrite.value.owner?.taskId ?? null,\n\t\t\t\t\tencodeJson(write.value),\n\t\t\t\t]);\n\t\t\t\tbreak;\n\t\t\tcase \"entry\":\n\t\t\t\tthis.#entries.push([\n\t\t\t\t\twrite.value.id,\n\t\t\t\t\twrite.value.conversationId,\n\t\t\t\t\twrite.value.head ?? null,\n\t\t\t\t\tseq,\n\t\t\t\t\tencodeJson(write.value),\n\t\t\t\t]);\n\t\t\t\tbreak;\n\t\t\tcase \"task\":\n\t\t\t\tthis.#tasks.set(write.value.id, [\n\t\t\t\t\twrite.value.id,\n\t\t\t\t\twrite.value.conversationId,\n\t\t\t\t\tencodeIndexedString(write.value.kind),\n\t\t\t\t\twrite.value.state.status,\n\t\t\t\t\twrite.value.abortRequested ? 1 : 0,\n\t\t\t\t\twrite.value.background ? 1 : 0,\n\t\t\t\t\tencodeJson(write.value),\n\t\t\t\t]);\n\t\t\t\tbreak;\n\t\t\tcase \"submission\":\n\t\t\t\tthis.#submissions.set(write.value.id, [\n\t\t\t\t\twrite.value.id,\n\t\t\t\t\twrite.value.conversationId,\n\t\t\t\t\twrite.value.requestId === undefined ? null : encodeIndexedString(write.value.requestId),\n\t\t\t\t\twrite.value.status,\n\t\t\t\t\tencodeJson(write.value),\n\t\t\t\t]);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t#addDocument(id: DocumentId, action: DocumentAction, seq: Seq, record: DocumentRecord): void {\n\t\tif (action.create !== undefined || action.retire) this.#documents.set(id, record);\n\t\tconst content = action.content;\n\t\tif (content !== undefined) {\n\t\t\tif (content.kind === \"base\" && isCurrentOnly(record)) this.#clear(id);\n\t\t\tconst encoded = content.kind === \"base\" ? encodeJson(content.value) : encodeJson(content.ops);\n\t\t\tlet revisions = this.#revisions.get(id);\n\t\t\tif (revisions === undefined) {\n\t\t\t\trevisions = [];\n\t\t\t\tthis.#revisions.set(id, revisions);\n\t\t\t}\n\t\t\trevisions.push([id, seq, content.kind, content.version, encoded]);\n\t\t}\n\t\tif (action.retire && isCurrentOnly(record)) this.#clear(id);\n\t}\n\n\t/** Drops a document's revisions: the pending ones, and the stored ones when written. */\n\t#clear(id: number): void {\n\t\tthis.#cleared.add(id);\n\t\tthis.#revisions.delete(id);\n\t}\n\n\t/** Writes everything pending, a few statements per table (`insertRows`), and forgets it. */\n\tasync flush(executor: SqliteExecutor, tables: Tables): Promise<void> {\n\t\tawait insertRows(\n\t\t\texecutor,\n\t\t\ttables.recordIds,\n\t\t\t[\"id\", \"record_type\"],\n\t\t\t[...this.#recordIds].map(([id, table]) => [id, table]),\n\t\t\t\"ON CONFLICT(id) DO NOTHING\",\n\t\t);\n\t\tawait insertRows(\n\t\t\texecutor,\n\t\t\ttables.conversations,\n\t\t\t[\"id\", \"owner_conversation_id\", \"owner_task_id\", \"record\"],\n\t\t\tthis.#conversations,\n\t\t);\n\t\tawait insertRows(\n\t\t\texecutor,\n\t\t\ttables.entries,\n\t\t\t[\"id\", \"conversation_id\", \"head\", \"commit_seq\", \"record\"],\n\t\t\tthis.#entries,\n\t\t);\n\t\tawait insertRows(\n\t\t\texecutor,\n\t\t\ttables.tasks,\n\t\t\t[\"id\", \"conversation_id\", \"kind\", \"status\", \"abort_requested\", \"background\", \"record\"],\n\t\t\t[...this.#tasks.values()],\n\t\t\t`ON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id, kind = excluded.kind,\n\t\t\t\tstatus = excluded.status, abort_requested = excluded.abort_requested,\n\t\t\t\tbackground = excluded.background, record = excluded.record`,\n\t\t);\n\t\tawait insertRows(\n\t\t\texecutor,\n\t\t\ttables.submissions,\n\t\t\t[\"id\", \"conversation_id\", \"request_id\", \"status\", \"record\"],\n\t\t\t[...this.#submissions.values()],\n\t\t\t`ON CONFLICT(id) DO UPDATE SET conversation_id = excluded.conversation_id,\n\t\t\t\trequest_id = excluded.request_id, status = excluded.status, record = excluded.record`,\n\t\t);\n\t\t// A created document is inserted, a stored one retired: one upsert writes both.\n\t\tawait insertRows(\n\t\t\texecutor,\n\t\t\ttables.documents,\n\t\t\t[\"id\", \"kind\", \"family\", \"key_value\", \"scope_kind\", \"owner_id\", \"created_at\", \"retired_at\", \"record\"],\n\t\t\t[...this.#documents.values()].map(documentRow),\n\t\t\t\"ON CONFLICT(id) DO UPDATE SET retired_at = excluded.retired_at, record = excluded.record\",\n\t\t);\n\t\tawait runForEach(\n\t\t\texecutor,\n\t\t\t`DELETE FROM ${tables.documentRevisions} WHERE document_id IN (SELECT value FROM json_each(?))`,\n\t\t\t[...this.#cleared],\n\t\t);\n\t\tawait insertRows(\n\t\t\texecutor,\n\t\t\ttables.documentRevisions,\n\t\t\t[\"document_id\", \"seq\", \"kind\", \"version\", \"content\"],\n\t\t\t[...this.#revisions.values()].flat(),\n\t\t);\n\t\tthis.#recordIds.clear();\n\t\tthis.#conversations.length = 0;\n\t\tthis.#entries.length = 0;\n\t\tthis.#tasks.clear();\n\t\tthis.#submissions.clear();\n\t\tthis.#documents.clear();\n\t\tthis.#cleared.clear();\n\t\tthis.#revisions.clear();\n\t}\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DocumentAddress, DocumentCreate, DocumentRecord } from "../../types.ts";
|
|
2
|
+
/** A value of a stored cell, as Durable's rows hold them. */
|
|
3
|
+
export type Cell = string | number | null;
|
|
4
|
+
/** A document's scope as its `scope_kind` and `owner_id` columns. */
|
|
5
|
+
export type ScopeColumns = {
|
|
6
|
+
readonly scopeKind: DocumentRecord["scope"]["kind"];
|
|
7
|
+
readonly ownerId: number;
|
|
8
|
+
};
|
|
9
|
+
/** Parses a stored JSON column. */
|
|
10
|
+
export declare const parseJson: <T>(value: string) => T;
|
|
11
|
+
/** Encodes a value as a stored JSON column. */
|
|
12
|
+
export declare const encodeJson: (value: unknown) => string;
|
|
13
|
+
/** Encodes an indexed string column. Some SQLite bindings replace lone UTF-16 surrogates; JSON keeps it lossless. */
|
|
14
|
+
export declare const encodeIndexedString: (value: string) => string;
|
|
15
|
+
/** A document scope's columns. */
|
|
16
|
+
export declare const scopeColumns: (scope: DocumentRecord["scope"]) => ScopeColumns;
|
|
17
|
+
/** A document address's columns, as `documents` stores and indexes them. */
|
|
18
|
+
export declare const addressParts: (address: DocumentAddress | DocumentCreate | DocumentRecord) => {
|
|
19
|
+
scopeKind: DocumentRecord["scope"]["kind"];
|
|
20
|
+
ownerId: number;
|
|
21
|
+
kind: string;
|
|
22
|
+
family: number;
|
|
23
|
+
keyValue: string;
|
|
24
|
+
};
|
|
25
|
+
/** One string per document address, from its stored columns. */
|
|
26
|
+
export declare const addressColumnsKey: (kind: string, scopeKind: string, ownerId: number, family: number, keyValue: string) => string;
|
|
27
|
+
/** One string per document address. */
|
|
28
|
+
export declare const addressKey: (address: DocumentAddress | DocumentCreate | DocumentRecord) => string;
|
|
29
|
+
/** Whether a document keeps only its current state: its revisions go at each new base and at retirement. */
|
|
30
|
+
export declare const isCurrentOnly: (record: DocumentRecord) => boolean;
|
|
31
|
+
/** A document record's `documents` row: id, kind, family, key, scope, owner, created and retired seqs, record. */
|
|
32
|
+
export declare function documentRow(record: DocumentRecord): Cell[];
|
|
33
|
+
//# sourceMappingURL=rows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/rows.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEtF,6DAA6D;AAC7D,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAE1C,qEAAqE;AACrE,MAAM,MAAM,YAAY,GAAG;IAC1B,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,mCAAmC;AACnC,eAAO,MAAM,SAAS,GAAI,CAAC,SAAS,MAAM,KAAG,CAA2B,CAAC;AAEzE,+CAA+C;AAC/C,eAAO,MAAM,UAAU,UAAW,OAAO,KAAG,MAAyC,CAAC;AAEtF,qHAAqH;AACrH,eAAO,MAAM,mBAAmB,UAAW,MAAM,KAAG,MAA+B,CAAC;AAEpF,kCAAkC;AAClC,eAAO,MAAM,YAAY,UAAW,cAAc,CAAC,OAAO,CAAC,KAAG,YAS7D,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,YAAY,YAAa,eAAe,GAAG,cAAc,GAAG,cAAc;eA1BlE,cAAc,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;aACjC,MAAM;;;;CAiCxB,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,iBAAiB,SACvB,MAAM,aACD,MAAM,WACR,MAAM,UACP,MAAM,YACJ,MAAM,KACd,MAAsE,CAAC;AAE1E,uCAAuC;AACvC,eAAO,MAAM,UAAU,YAAa,eAAe,GAAG,cAAc,GAAG,cAAc,KAAG,MAGvF,CAAC;AAEF,4GAA4G;AAC5G,eAAO,MAAM,aAAa,WAAY,cAAc,KAAG,OACa,CAAC;AAErE,kHAAkH;AAClH,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,EAAE,CAa1D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// How Durable's records map to SQLite: JSON text, indexed strings, and a document's scope and address columns.
|
|
2
|
+
/** Parses a stored JSON column. */
|
|
3
|
+
export const parseJson = (value) => JSON.parse(value);
|
|
4
|
+
/** Encodes a value as a stored JSON column. */
|
|
5
|
+
export const encodeJson = (value) => JSON.stringify(value);
|
|
6
|
+
/** Encodes an indexed string column. Some SQLite bindings replace lone UTF-16 surrogates; JSON keeps it lossless. */
|
|
7
|
+
export const encodeIndexedString = (value) => JSON.stringify(value);
|
|
8
|
+
/** A document scope's columns. */
|
|
9
|
+
export const scopeColumns = (scope) => {
|
|
10
|
+
switch (scope.kind) {
|
|
11
|
+
case "session":
|
|
12
|
+
return { scopeKind: "session", ownerId: 0 };
|
|
13
|
+
case "conversation":
|
|
14
|
+
return { scopeKind: "conversation", ownerId: scope.conversationId };
|
|
15
|
+
case "task":
|
|
16
|
+
return { scopeKind: "task", ownerId: scope.taskId };
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/** A document address's columns, as `documents` stores and indexes them. */
|
|
20
|
+
export const addressParts = (address) => {
|
|
21
|
+
const scope = scopeColumns(address.scope);
|
|
22
|
+
return {
|
|
23
|
+
kind: encodeIndexedString(address.kind),
|
|
24
|
+
...scope,
|
|
25
|
+
family: address.key === undefined ? 0 : 1,
|
|
26
|
+
keyValue: encodeIndexedString(address.key ?? ""),
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
/** One string per document address, from its stored columns. */
|
|
30
|
+
export const addressColumnsKey = (kind, scopeKind, ownerId, family, keyValue) => JSON.stringify([kind, scopeKind, ownerId, family, keyValue]);
|
|
31
|
+
/** One string per document address. */
|
|
32
|
+
export const addressKey = (address) => {
|
|
33
|
+
const parts = addressParts(address);
|
|
34
|
+
return addressColumnsKey(parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue);
|
|
35
|
+
};
|
|
36
|
+
/** Whether a document keeps only its current state: its revisions go at each new base and at retirement. */
|
|
37
|
+
export const isCurrentOnly = (record) => record.scope.kind !== "conversation" || record.history === "latest";
|
|
38
|
+
/** A document record's `documents` row: id, kind, family, key, scope, owner, created and retired seqs, record. */
|
|
39
|
+
export function documentRow(record) {
|
|
40
|
+
const parts = addressParts(record);
|
|
41
|
+
return [
|
|
42
|
+
record.id,
|
|
43
|
+
parts.kind,
|
|
44
|
+
parts.family,
|
|
45
|
+
parts.keyValue,
|
|
46
|
+
parts.scopeKind,
|
|
47
|
+
parts.ownerId,
|
|
48
|
+
record.createdAt,
|
|
49
|
+
record.retiredAt ?? null,
|
|
50
|
+
encodeJson(record),
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.js","sourceRoot":"","sources":["../../../src/storage/sqlite/rows.ts"],"names":[],"mappings":"AAAA,+GAA+G;AAa/G,mCAAmC;AACnC,MAAM,CAAC,MAAM,SAAS,GAAG,CAAI,KAAa,EAAK,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAM,CAAC;AAEzE,+CAA+C;AAC/C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,KAAc,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAW,CAAC;AAEtF,qHAAqH;AACrH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAEpF,kCAAkC;AAClC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAA8B,EAAgB,EAAE;IAC5E,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;AACF,CAAC,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAA0D,EAAE,EAAE;IAC1F,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;AACH,CAAC,CAAC;AAEF,gEAAgE;AAChE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAChC,IAAY,EACZ,SAAiB,EACjB,OAAe,EACf,MAAc,EACd,QAAgB,EACP,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAE1E,uCAAuC;AACvC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAA0D,EAAU,EAAE;IAChG,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACpC,OAAO,iBAAiB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;AACpG,CAAC,CAAC;AAEF,4GAA4G;AAC5G,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,MAAsB,EAAW,EAAE,CAChE,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC;AAErE,kHAAkH;AAClH,MAAM,UAAU,WAAW,CAAC,MAAsB;IACjD,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,OAAO;QACN,MAAM,CAAC,EAAE;QACT,KAAK,CAAC,IAAI;QACV,KAAK,CAAC,MAAM;QACZ,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,SAAS;QACf,KAAK,CAAC,OAAO;QACb,MAAM,CAAC,SAAS;QAChB,MAAM,CAAC,SAAS,IAAI,IAAI;QACxB,UAAU,CAAC,MAAM,CAAC;KAClB,CAAC;AACH,CAAC","sourcesContent":["// How Durable's records map to SQLite: JSON text, indexed strings, and a document's scope and address columns.\n\nimport type { DocumentAddress, DocumentCreate, DocumentRecord } from \"../../types.ts\";\n\n/** A value of a stored cell, as Durable's rows hold them. */\nexport type Cell = string | number | null;\n\n/** A document's scope as its `scope_kind` and `owner_id` columns. */\nexport type ScopeColumns = {\n\treadonly scopeKind: DocumentRecord[\"scope\"][\"kind\"];\n\treadonly ownerId: number;\n};\n\n/** Parses a stored JSON column. */\nexport const parseJson = <T>(value: string): T => JSON.parse(value) as T;\n\n/** Encodes a value as a stored JSON column. */\nexport const encodeJson = (value: unknown): string => JSON.stringify(value) as string;\n\n/** Encodes an indexed string column. Some SQLite bindings replace lone UTF-16 surrogates; JSON keeps it lossless. */\nexport const encodeIndexedString = (value: string): string => JSON.stringify(value);\n\n/** A document scope's columns. */\nexport const 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\n/** A document address's columns, as `documents` stores and indexes them. */\nexport const 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\n/** One string per document address, from its stored columns. */\nexport const addressColumnsKey = (\n\tkind: string,\n\tscopeKind: string,\n\townerId: number,\n\tfamily: number,\n\tkeyValue: string,\n): string => JSON.stringify([kind, scopeKind, ownerId, family, keyValue]);\n\n/** One string per document address. */\nexport const addressKey = (address: DocumentAddress | DocumentCreate | DocumentRecord): string => {\n\tconst parts = addressParts(address);\n\treturn addressColumnsKey(parts.kind, parts.scopeKind, parts.ownerId, parts.family, parts.keyValue);\n};\n\n/** Whether a document keeps only its current state: its revisions go at each new base and at retirement. */\nexport const isCurrentOnly = (record: DocumentRecord): boolean =>\n\trecord.scope.kind !== \"conversation\" || record.history === \"latest\";\n\n/** A document record's `documents` row: id, kind, family, key, scope, owner, created and retired seqs, record. */\nexport function documentRow(record: DocumentRecord): Cell[] {\n\tconst parts = addressParts(record);\n\treturn [\n\t\trecord.id,\n\t\tparts.kind,\n\t\tparts.family,\n\t\tparts.keyValue,\n\t\tparts.scopeKind,\n\t\tparts.ownerId,\n\t\trecord.createdAt,\n\t\trecord.retiredAt ?? null,\n\t\tencodeJson(record),\n\t];\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SqliteExecutor } from "@earendil-works/chord/sqlite";
|
|
2
|
+
import type { Cell } from "./rows.ts";
|
|
3
|
+
/** The rows `sql` reads for `values`, given as its one JSON array parameter (`json_each(?)`): one statement per chunk. */
|
|
4
|
+
export declare function readRows<T extends object>(executor: SqliteExecutor, sql: string, values: readonly (Cell | readonly Cell[])[]): Promise<T[]>;
|
|
5
|
+
/** Runs `sql` (a deletion) for `values`, given as its one JSON array parameter: one statement per chunk. */
|
|
6
|
+
export declare function runForEach(executor: SqliteExecutor, sql: string, values: readonly Cell[]): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Inserts `rows` (one cell per column, in order) into `table`, then `conflict` (an upsert's clause): the rows of little
|
|
9
|
+
* text from one JSON array parameter per chunk, the same SQL text whatever their number; the large ones bound column by
|
|
10
|
+
* column.
|
|
11
|
+
*/
|
|
12
|
+
export declare function insertRows(executor: SqliteExecutor, table: string, columns: readonly string[], rows: readonly (readonly Cell[])[], conflict?: string): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=statements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statements.d.ts","sourceRoot":"","sources":["../../../src/storage/sqlite/statements.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,0HAA0H;AAC1H,wBAAsB,QAAQ,CAAC,CAAC,SAAS,MAAM,EAC9C,QAAQ,EAAE,cAAc,EACxB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,SAAS,CAAC,IAAI,GAAG,SAAS,IAAI,EAAE,CAAC,EAAE,GACzC,OAAO,CAAC,CAAC,EAAE,CAAC,CAMd;AAED,4GAA4G;AAC5G,wBAAsB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9G;AAMD;;;;GAIG;AACH,wBAAsB,UAAU,CAC/B,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,IAAI,EAAE,SAAS,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,EAClC,QAAQ,SAAK,GACX,OAAO,CAAC,IAAI,CAAC,CAiBf"}
|