@earendil-works/pi-durable 1.3.0 → 1.5.0
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 +227 -0
- package/README.md +287 -82
- package/dist/codemode/codemode.d.ts +4 -4
- package/dist/codemode/codemode.d.ts.map +1 -1
- package/dist/codemode/codemode.js +21 -16
- package/dist/codemode/codemode.js.map +1 -1
- package/dist/codemode/models.d.ts.map +1 -1
- package/dist/codemode/models.js +6 -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.map +1 -1
- package/dist/env/index.d.ts +12 -10
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js.map +1 -1
- package/dist/env/node-watch.d.ts +3 -3
- 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 +2 -2
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +12 -4
- 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 +13 -2
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +48 -6
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.d.ts +3 -1
- package/dist/harness/compaction.d.ts.map +1 -1
- package/dist/harness/compaction.js +38 -6
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +23 -7
- 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 +7 -3
- 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 +38 -5
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +497 -108
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts +7 -2
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +147 -57
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +7 -3
- package/dist/harness/inbox.d.ts.map +1 -1
- package/dist/harness/inbox.js +7 -4
- package/dist/harness/inbox.js.map +1 -1
- package/dist/harness/json.d.ts +20 -4
- 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 +122 -16
- 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 +162 -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 +1 -1
- package/dist/harness/prompt.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 +86 -14
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +1459 -369
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +23 -11
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +104 -36
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +28 -8
- package/dist/harness/task-graph.d.ts.map +1 -1
- package/dist/harness/task-graph.js +85 -36
- package/dist/harness/task-graph.js.map +1 -1
- package/dist/harness/telemetry.d.ts +307 -0
- package/dist/harness/telemetry.d.ts.map +1 -0
- package/dist/harness/telemetry.js +136 -0
- package/dist/harness/telemetry.js.map +1 -0
- package/dist/harness/tool.d.ts +2 -0
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +223 -61
- 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 +302 -48
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/util.d.ts +7 -1
- package/dist/harness/util.d.ts.map +1 -1
- package/dist/harness/util.js +13 -0
- package/dist/harness/util.js.map +1 -1
- package/dist/harness/view.d.ts +43 -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 +13 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- 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 +71 -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/keys.d.ts +68 -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 +181 -29
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +1073 -183
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +86 -4
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +250 -38
- 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 +68 -0
- package/dist/storage/indexeddb.d.ts.map +1 -0
- package/dist/storage/indexeddb.js +881 -0
- package/dist/storage/indexeddb.js.map +1 -0
- package/dist/storage/jsonl/storage.d.ts +30 -2
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/jsonl/storage.js +88 -24
- package/dist/storage/jsonl/storage.js.map +1 -1
- package/dist/storage/memory.d.ts +7 -2
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +29 -3
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/cloudflare.d.ts +2 -51
- package/dist/storage/sqlite/cloudflare.d.ts.map +1 -1
- package/dist/storage/sqlite/cloudflare.js +2 -133
- package/dist/storage/sqlite/cloudflare.js.map +1 -1
- package/dist/storage/sqlite/database.d.ts +1 -36
- package/dist/storage/sqlite/database.d.ts.map +1 -1
- package/dist/storage/sqlite/database.js.map +1 -1
- package/dist/storage/sqlite/index.d.ts +1 -1
- package/dist/storage/sqlite/index.d.ts.map +1 -1
- package/dist/storage/sqlite/index.js.map +1 -1
- package/dist/storage/sqlite/migrations.d.ts +2 -5
- package/dist/storage/sqlite/migrations.d.ts.map +1 -1
- package/dist/storage/sqlite/migrations.js +28 -28
- package/dist/storage/sqlite/migrations.js.map +1 -1
- package/dist/storage/sqlite/node.d.ts +2 -37
- package/dist/storage/sqlite/node.d.ts.map +1 -1
- package/dist/storage/sqlite/node.js +3 -174
- package/dist/storage/sqlite/node.js.map +1 -1
- package/dist/storage/sqlite/schema.d.ts +1 -3
- package/dist/storage/sqlite/schema.d.ts.map +1 -1
- package/dist/storage/sqlite/schema.js +2 -9
- package/dist/storage/sqlite/schema.js.map +1 -1
- package/dist/storage/sqlite/storage.d.ts +15 -3
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +110 -36
- 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 +59 -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 +369 -67
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/timers.d.ts +21 -0
- package/dist/timers.d.ts.map +1 -0
- package/dist/timers.js +72 -0
- package/dist/timers.js.map +1 -0
- package/dist/tool-search/tool-search.d.ts +6 -12
- package/dist/tool-search/tool-search.d.ts.map +1 -1
- package/dist/tool-search/tool-search.js +7 -7
- package/dist/tool-search/tool-search.js.map +1 -1
- package/dist/tools/bash.d.ts +12 -12
- 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/index.d.ts +32 -32
- 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 +331 -64
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +27 -10
- 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,286 @@
|
|
|
1
|
+
// One Durable commit as one commit of the store's series (slice 6b §2.3): staged before the queue (rows encoded,
|
|
2
|
+
// contents serialized, explicit ids raising the store's allocator), then inside the series' transaction what
|
|
3
|
+
// `SqliteStorage` does inside its own, with the step's reader: Durable's checks of ids and documents and the copies
|
|
4
|
+
// resolved from their sources (its messages, a refusal), then its one composite write: the table puts, and the
|
|
5
|
+
// documents in two passes, existing ones first (their content, then their retirement, which frees the addresses this
|
|
6
|
+
// commit's creations take), then the creations and copies.
|
|
7
|
+
import { findDocument, insertDocument, readDocument, retireRecord, writeContent, } from "@earendil-works/chord/next";
|
|
8
|
+
import { StorageRequestError } from "../../errors.js";
|
|
9
|
+
import { addressOf, durableRecord, engineRecord, readAt } from "./documents.js";
|
|
10
|
+
import { conversationRow, entryRow, submissionRow, taskRow } from "./tables.js";
|
|
11
|
+
/** A Durable commit staged for the store's series: its checks, then itself as its one write. */
|
|
12
|
+
export class DurableCommit {
|
|
13
|
+
order = "write";
|
|
14
|
+
writes;
|
|
15
|
+
#owner;
|
|
16
|
+
#tables;
|
|
17
|
+
#context;
|
|
18
|
+
/** Every id a write claims, in write order (documents created or copied included), for the id checks. */
|
|
19
|
+
#claims = [];
|
|
20
|
+
#rows = [];
|
|
21
|
+
#actions = new Map();
|
|
22
|
+
/** What `check` found for `apply`, in the same step: the stored types of the claimed ids, the changed documents. */
|
|
23
|
+
#checked;
|
|
24
|
+
/**
|
|
25
|
+
* Stages `writes` (rule 8: rows and contents encoded here, before the queue); `raise` makes room above each explicit
|
|
26
|
+
* id. A malformed batch (two content commands for one document, a second retirement) throws Durable's message.
|
|
27
|
+
*/
|
|
28
|
+
constructor(writes, owner, tables, raise, context) {
|
|
29
|
+
this.#owner = owner;
|
|
30
|
+
this.#tables = tables;
|
|
31
|
+
this.#context = context;
|
|
32
|
+
for (const write of writes)
|
|
33
|
+
this.#stage(write, raise);
|
|
34
|
+
this.writes = [this];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Durable's checks against the state the series' earlier commits left, writing nothing: the ids, the documents and
|
|
38
|
+
* the copies; a throw refuses the commit.
|
|
39
|
+
*/
|
|
40
|
+
async check(step) {
|
|
41
|
+
const claimed = await this.#checkIds(step);
|
|
42
|
+
const existing = await this.#checkDocuments(step);
|
|
43
|
+
await this.#resolveCopies(step);
|
|
44
|
+
this.#checked = { claimed, existing };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the copies (S11): each source read at its point and checked against the copy's record, entered as a
|
|
48
|
+
* creation carrying the source's value as its base.
|
|
49
|
+
*/
|
|
50
|
+
async #resolveCopies(step) {
|
|
51
|
+
for (const [id, action] of this.#actions) {
|
|
52
|
+
const source = action.copy;
|
|
53
|
+
if (source === undefined)
|
|
54
|
+
continue;
|
|
55
|
+
if (this.#actions.has(source.id))
|
|
56
|
+
throw new Error(`Document copy ${id} source is changed in the copy batch`);
|
|
57
|
+
try {
|
|
58
|
+
const read = await readAt(step, source.id, source.at, this.#owner);
|
|
59
|
+
if (read.kind === "current_only") {
|
|
60
|
+
throw new StorageRequestError(`Document ${read.id} does not retain historical content`);
|
|
61
|
+
}
|
|
62
|
+
const stored = read.document;
|
|
63
|
+
if (stored === undefined)
|
|
64
|
+
throw new Error(`Fork source document ${source.id} cannot be read`);
|
|
65
|
+
const create = action.create;
|
|
66
|
+
if (stored.record.scope.kind !== "conversation" ||
|
|
67
|
+
create.scope.kind !== "conversation" ||
|
|
68
|
+
stored.record.kind !== create.kind ||
|
|
69
|
+
stored.record.key !== create.key ||
|
|
70
|
+
stored.record.history !== create.history ||
|
|
71
|
+
stored.record.fork !== create.fork) {
|
|
72
|
+
throw new Error(`Fork source document ${source.id} does not match the copied record`);
|
|
73
|
+
}
|
|
74
|
+
action.base = { version: stored.version, value: stored.value, text: JSON.stringify(stored.value) };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new Error(`Document copy ${id} was rejected`, { cause: error });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** The commit's writes, after its `check` in the same step. */
|
|
82
|
+
async apply(step) {
|
|
83
|
+
const recorded = [];
|
|
84
|
+
const { claimed, existing } = this.#checked;
|
|
85
|
+
for (const write of this.#rows) {
|
|
86
|
+
if (claimed.get(write.id) === undefined)
|
|
87
|
+
recorded.push(await this.#claim(step, write.id, write.type));
|
|
88
|
+
claimed.set(write.id, write.type);
|
|
89
|
+
const stamp = write.table === this.#tables.entries ? "commit_seq" : undefined;
|
|
90
|
+
recorded.push(await write.table.put(step, write.row, stamp));
|
|
91
|
+
}
|
|
92
|
+
// Existing documents first: a retirement frees its address before a creation of this commit takes it.
|
|
93
|
+
for (const [id, record] of existing)
|
|
94
|
+
recorded.push(...(await this.#change(step, record, this.#actions.get(id))));
|
|
95
|
+
for (const [id, action] of this.#actions) {
|
|
96
|
+
if (action.create === undefined)
|
|
97
|
+
continue;
|
|
98
|
+
recorded.push(await this.#claim(step, id, "document"));
|
|
99
|
+
recorded.push(await this.#create(step, action.create, action.base, action.retire));
|
|
100
|
+
}
|
|
101
|
+
return recorded;
|
|
102
|
+
}
|
|
103
|
+
/** Stages one write: a table row encoded, a document command into its action. */
|
|
104
|
+
#stage(write, raise) {
|
|
105
|
+
const tables = this.#tables;
|
|
106
|
+
switch (write.type) {
|
|
107
|
+
case "conversation":
|
|
108
|
+
this.#row(write.value.id, write.type, tables.conversations, conversationRow(write.value), raise);
|
|
109
|
+
return;
|
|
110
|
+
case "entry": {
|
|
111
|
+
const { id, conversationId, head } = write.value;
|
|
112
|
+
this.#row(id, write.type, tables.entries, entryRow(write.value), raise);
|
|
113
|
+
// Its claim is the entry's: `apply` claims an id once.
|
|
114
|
+
if (head !== undefined) {
|
|
115
|
+
const row = tables.entryHeads.encode({ id, conversation_id: conversationId });
|
|
116
|
+
this.#rows.push({ id, type: write.type, table: tables.entryHeads, row });
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
case "task":
|
|
121
|
+
this.#row(write.value.id, write.type, tables.tasks, taskRow(write.value), raise);
|
|
122
|
+
return;
|
|
123
|
+
case "submission":
|
|
124
|
+
this.#row(write.value.id, write.type, tables.submissions, submissionRow(write.value), raise);
|
|
125
|
+
return;
|
|
126
|
+
case "document.create":
|
|
127
|
+
case "document.copy": {
|
|
128
|
+
const { id } = write.record;
|
|
129
|
+
const action = this.#action(id);
|
|
130
|
+
if (action.create !== undefined || action.content !== undefined || action.copy !== undefined) {
|
|
131
|
+
throw new Error(`Document ${id} has more than one content command`);
|
|
132
|
+
}
|
|
133
|
+
action.create = write.record;
|
|
134
|
+
if (write.type === "document.copy")
|
|
135
|
+
action.copy = write.source;
|
|
136
|
+
else
|
|
137
|
+
action.base = { ...write.content, text: JSON.stringify(write.content.value) };
|
|
138
|
+
this.#claims.push({ id, type: "document" });
|
|
139
|
+
raise(id);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
case "document.change": {
|
|
143
|
+
const action = this.#action(write.id);
|
|
144
|
+
// A creation's base is its content command (`SqliteStorage` counts it so too), in either order.
|
|
145
|
+
if (action.create !== undefined || action.content !== undefined || action.copy !== undefined) {
|
|
146
|
+
throw new Error(`Document ${write.id} has more than one content command`);
|
|
147
|
+
}
|
|
148
|
+
const { content } = write;
|
|
149
|
+
action.content = { content, text: JSON.stringify(content.kind === "base" ? content.value : content.ops) };
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
case "document.retire": {
|
|
153
|
+
const action = this.#action(write.id);
|
|
154
|
+
if (action.retire)
|
|
155
|
+
throw new Error(`Document ${write.id} is retired more than once`);
|
|
156
|
+
action.retire = true;
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
#row(id, type, table, row, raise) {
|
|
162
|
+
this.#claims.push({ id, type });
|
|
163
|
+
this.#rows.push({ id, type, table, row: table.encode(row) });
|
|
164
|
+
raise(id);
|
|
165
|
+
}
|
|
166
|
+
#action(id) {
|
|
167
|
+
let action = this.#actions.get(id);
|
|
168
|
+
if (action === undefined) {
|
|
169
|
+
action = { retire: false };
|
|
170
|
+
this.#actions.set(id, action);
|
|
171
|
+
}
|
|
172
|
+
return action;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Durable's global id checks (`SqliteStorage.checkGlobalIds`): a conversation, entry or document id claimed by
|
|
176
|
+
* anything, or written twice; a task or submission id claimed by another type. Returns the stored types.
|
|
177
|
+
*/
|
|
178
|
+
async #checkIds(step) {
|
|
179
|
+
const stored = new Map();
|
|
180
|
+
const claimed = new Map();
|
|
181
|
+
for (const { id, type } of this.#claims) {
|
|
182
|
+
let existing = stored.get(id);
|
|
183
|
+
if (!stored.has(id)) {
|
|
184
|
+
const row = await this.#tables.recordIds.get(step, [id], this.#context);
|
|
185
|
+
existing = row?.record_type;
|
|
186
|
+
stored.set(id, existing);
|
|
187
|
+
}
|
|
188
|
+
const earlier = claimed.get(id);
|
|
189
|
+
if (type === "conversation" || type === "entry" || type === "document") {
|
|
190
|
+
if (existing !== undefined)
|
|
191
|
+
throw new Error(`ID ${id} already belongs to ${existing}`);
|
|
192
|
+
if (earlier !== undefined)
|
|
193
|
+
throw new Error(`ID ${id} is written more than once`);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
if (existing !== undefined && existing !== type)
|
|
197
|
+
throw new Error(`ID ${id} already belongs to ${existing}`);
|
|
198
|
+
if (earlier !== undefined && earlier !== type)
|
|
199
|
+
throw new Error(`ID ${id} is written as two record types`);
|
|
200
|
+
}
|
|
201
|
+
claimed.set(id, type);
|
|
202
|
+
}
|
|
203
|
+
const types = new Map();
|
|
204
|
+
for (const [id, type] of stored)
|
|
205
|
+
if (type !== undefined)
|
|
206
|
+
types.set(id, type);
|
|
207
|
+
return types;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Durable's document checks (`SqliteStorage.checkDocumentActions`): a change or retirement of an unknown or retired
|
|
211
|
+
* document, a delta on another version, more than one live incarnation per address after the commit. Returns the
|
|
212
|
+
* existing records the commit changes or retires, in action order.
|
|
213
|
+
*/
|
|
214
|
+
async #checkDocuments(step) {
|
|
215
|
+
const existing = new Map();
|
|
216
|
+
for (const [id, action] of this.#actions) {
|
|
217
|
+
if (action.create !== undefined)
|
|
218
|
+
continue;
|
|
219
|
+
const stored = await readDocument(step, id);
|
|
220
|
+
const record = stored === undefined ? undefined : durableRecord(stored, this.#owner);
|
|
221
|
+
if (record === undefined)
|
|
222
|
+
throw new Error(`Unknown document: ${id}`);
|
|
223
|
+
if (record.retiredAt !== undefined)
|
|
224
|
+
throw new Error(`Document ${id} is retired`);
|
|
225
|
+
const content = action.content?.content;
|
|
226
|
+
if (content?.kind === "delta" && content.version !== stored.version) {
|
|
227
|
+
throw new Error(`Document ${id} version transition requires a base`);
|
|
228
|
+
}
|
|
229
|
+
existing.set(id, stored);
|
|
230
|
+
}
|
|
231
|
+
/** Per address a live creation takes: its live incarnations after the commit so far. */
|
|
232
|
+
const live = new Map();
|
|
233
|
+
for (const action of this.#actions.values()) {
|
|
234
|
+
if (action.create === undefined || action.retire)
|
|
235
|
+
continue;
|
|
236
|
+
const address = addressOf(action.create, this.#owner);
|
|
237
|
+
const key = JSON.stringify([address.namespace, address.kind, address.key]);
|
|
238
|
+
let count = live.get(key);
|
|
239
|
+
if (count === undefined) {
|
|
240
|
+
// The incarnation live there now counts unless this commit retires it.
|
|
241
|
+
const current = await findDocument(step, address);
|
|
242
|
+
count = current === undefined || this.#actions.get(current.id)?.retire === true ? 0 : 1;
|
|
243
|
+
}
|
|
244
|
+
if (++count > 1)
|
|
245
|
+
throw new Error("Document address already has a current incarnation");
|
|
246
|
+
live.set(key, count);
|
|
247
|
+
}
|
|
248
|
+
return existing;
|
|
249
|
+
}
|
|
250
|
+
/** Puts `id`'s `record_ids` row: its record type. */
|
|
251
|
+
#claim(step, id, type) {
|
|
252
|
+
const table = this.#tables.recordIds;
|
|
253
|
+
return table.put(step, table.encode({ id, record_type: type }));
|
|
254
|
+
}
|
|
255
|
+
/** An existing document's content, then its retirement, as their writes. */
|
|
256
|
+
async #change(step, record, action) {
|
|
257
|
+
const writes = [];
|
|
258
|
+
let { revision } = record;
|
|
259
|
+
if (action.content !== undefined) {
|
|
260
|
+
const { content, text } = action.content;
|
|
261
|
+
const stored = engineContent(content);
|
|
262
|
+
await writeContent(step, record.id, record.history, revision, stored, text);
|
|
263
|
+
writes.push({ kind: "document.change", id: record.id, revision, content: stored });
|
|
264
|
+
revision++;
|
|
265
|
+
}
|
|
266
|
+
if (action.retire) {
|
|
267
|
+
await retireRecord(step, record);
|
|
268
|
+
writes.push({ kind: "document.retire", id: record.id, revision });
|
|
269
|
+
}
|
|
270
|
+
return writes;
|
|
271
|
+
}
|
|
272
|
+
/** A creation or a resolved copy (retired at once for a create-plus-retire, an empty lifetime), as its write. */
|
|
273
|
+
async #create(step, create, base, retired) {
|
|
274
|
+
const record = engineRecord(create, this.#owner, base.version, step.seq, retired);
|
|
275
|
+
await insertDocument(step, record, base.text);
|
|
276
|
+
const value = base.value;
|
|
277
|
+
return { kind: "document.create", record, base: { version: base.version, fingerprint: "", value } };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/** A Durable content as the engine stores it: its documents have no schema fingerprint. */
|
|
281
|
+
function engineContent(content) {
|
|
282
|
+
return content.kind === "base"
|
|
283
|
+
? { kind: "base", version: content.version, fingerprint: "", value: content.value }
|
|
284
|
+
: { kind: "delta", version: content.version, ops: content.ops };
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=commit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit.js","sourceRoot":"","sources":["../../../src/storage/store/commit.ts"],"names":[],"mappings":"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","sourcesContent":["// One Durable commit as one commit of the store's series (slice 6b §2.3): staged before the queue (rows encoded,\n// contents serialized, explicit ids raising the store's allocator), then inside the series' transaction what\n// `SqliteStorage` does inside its own, with the step's reader: Durable's checks of ids and documents and the copies\n// resolved from their sources (its messages, a refusal), then its one composite write: the table puts, and the\n// documents in two passes, existing ones first (their content, then their retirement, which frees the addresses this\n// commit's creations take), then the creations and copies.\n\nimport type { Context, JsonValue } from \"@earendil-works/chord\";\nimport {\n\ttype EncodedRow,\n\ttype DocumentContent as EngineContent,\n\ttype DocumentRecord as EngineRecord,\n\tfindDocument,\n\tinsertDocument,\n\ttype OwnerTable,\n\treadDocument,\n\tretireRecord,\n\ttype SeriesCommit,\n\ttype StagedWrite,\n\ttype Step,\n\ttype StoreWrite,\n\twriteContent,\n} from \"@earendil-works/chord/next\";\nimport { StorageRequestError } from \"../../errors.ts\";\nimport type {\n\tDocumentContent,\n\tDocumentCopySource,\n\tDocumentCreate,\n\tDocumentId,\n\tId,\n\tJsonObject,\n\tStorageWrite,\n} from \"../../types.ts\";\nimport { addressOf, durableRecord, engineRecord, readAt } from \"./documents.ts\";\nimport { conversationRow, entryRow, submissionRow, taskRow } from \"./tables.ts\";\n\n/** Durable's record types, as `record_ids` names them. */\nexport type RecordType = \"conversation\" | \"entry\" | \"task\" | \"submission\" | \"document\";\n\n/** The storage's tables, as a commit writes them. */\nexport type CommitTables = {\n\treadonly recordIds: OwnerTable;\n\treadonly conversations: OwnerTable;\n\treadonly entries: OwnerTable;\n\treadonly entryHeads: OwnerTable;\n\treadonly tasks: OwnerTable;\n\treadonly submissions: OwnerTable;\n};\n\n/** A table write of the commit: its record's id and type, its table and its row encoded at staging. */\ntype RowWrite = {\n\treadonly id: number;\n\treadonly type: Exclude<RecordType, \"document\">;\n\treadonly table: OwnerTable;\n\treadonly row: EncodedRow;\n};\n\n/** A base as the commit stores it: its version and value, and the value serialized. */\ntype StagedBase = { readonly version: number; readonly value: JsonObject; readonly text: string };\n\n/** What one commit does with one document, as `SqliteStorage`'s action map has it. */\ntype DocumentAction = {\n\tcreate?: DocumentCreate;\n\tcopy?: DocumentCopySource;\n\t/** A creation's base: the write's own, or a copy's once resolved. */\n\tbase?: StagedBase;\n\tcontent?: { readonly content: DocumentContent; readonly text: string };\n\tretire: boolean;\n};\n\n/** A Durable commit staged for the store's series: its checks, then itself as its one write. */\nexport class DurableCommit implements SeriesCommit, StagedWrite {\n\treadonly order = \"write\";\n\treadonly writes: readonly StagedWrite[];\n\treadonly #owner: string;\n\treadonly #tables: CommitTables;\n\treadonly #context: Context;\n\t/** Every id a write claims, in write order (documents created or copied included), for the id checks. */\n\treadonly #claims: { readonly id: number; readonly type: RecordType }[] = [];\n\treadonly #rows: RowWrite[] = [];\n\treadonly #actions = new Map<DocumentId, DocumentAction>();\n\t/** What `check` found for `apply`, in the same step: the stored types of the claimed ids, the changed documents. */\n\t#checked:\n\t\t| { readonly claimed: Map<number, RecordType>; readonly existing: Map<DocumentId, EngineRecord> }\n\t\t| undefined;\n\n\t/**\n\t * Stages `writes` (rule 8: rows and contents encoded here, before the queue); `raise` makes room above each explicit\n\t * id. A malformed batch (two content commands for one document, a second retirement) throws Durable's message.\n\t */\n\tconstructor(\n\t\twrites: readonly StorageWrite[],\n\t\towner: string,\n\t\ttables: CommitTables,\n\t\traise: (id: number) => void,\n\t\tcontext: Context,\n\t) {\n\t\tthis.#owner = owner;\n\t\tthis.#tables = tables;\n\t\tthis.#context = context;\n\t\tfor (const write of writes) this.#stage(write, raise);\n\t\tthis.writes = [this];\n\t}\n\n\t/**\n\t * Durable's checks against the state the series' earlier commits left, writing nothing: the ids, the documents and\n\t * the copies; a throw refuses the commit.\n\t */\n\tasync check(step: Step): Promise<void> {\n\t\tconst claimed = await this.#checkIds(step);\n\t\tconst existing = await this.#checkDocuments(step);\n\t\tawait this.#resolveCopies(step);\n\t\tthis.#checked = { claimed, existing };\n\t}\n\n\t/**\n\t * Resolves the copies (S11): each source read at its point and checked against the copy's record, entered as a\n\t * creation carrying the source's value as its base.\n\t */\n\tasync #resolveCopies(step: Step): Promise<void> {\n\t\tfor (const [id, action] of this.#actions) {\n\t\t\tconst source = action.copy;\n\t\t\tif (source === undefined) continue;\n\t\t\tif (this.#actions.has(source.id)) throw new Error(`Document copy ${id} source is changed in the copy batch`);\n\t\t\ttry {\n\t\t\t\tconst read = await readAt(step, source.id, source.at, this.#owner);\n\t\t\t\tif (read.kind === \"current_only\") {\n\t\t\t\t\tthrow new StorageRequestError(`Document ${read.id} does not retain historical content`);\n\t\t\t\t}\n\t\t\t\tconst stored = read.document;\n\t\t\t\tif (stored === undefined) throw new Error(`Fork source document ${source.id} cannot be read`);\n\t\t\t\tconst create = action.create!;\n\t\t\t\tif (\n\t\t\t\t\tstored.record.scope.kind !== \"conversation\" ||\n\t\t\t\t\tcreate.scope.kind !== \"conversation\" ||\n\t\t\t\t\tstored.record.kind !== create.kind ||\n\t\t\t\t\tstored.record.key !== create.key ||\n\t\t\t\t\tstored.record.history !== create.history ||\n\t\t\t\t\tstored.record.fork !== create.fork\n\t\t\t\t) {\n\t\t\t\t\tthrow new Error(`Fork source document ${source.id} does not match the copied record`);\n\t\t\t\t}\n\t\t\t\taction.base = { version: stored.version, value: stored.value, text: JSON.stringify(stored.value) };\n\t\t\t} catch (error) {\n\t\t\t\tthrow new Error(`Document copy ${id} was rejected`, { cause: error });\n\t\t\t}\n\t\t}\n\t}\n\n\t/** The commit's writes, after its `check` in the same step. */\n\tasync apply(step: Step): Promise<readonly StoreWrite[]> {\n\t\tconst recorded: StoreWrite[] = [];\n\t\tconst { claimed, existing } = this.#checked!;\n\t\tfor (const write of this.#rows) {\n\t\t\tif (claimed.get(write.id) === undefined) recorded.push(await this.#claim(step, write.id, write.type));\n\t\t\tclaimed.set(write.id, write.type);\n\t\t\tconst stamp = write.table === this.#tables.entries ? \"commit_seq\" : undefined;\n\t\t\trecorded.push(await write.table.put(step, write.row, stamp));\n\t\t}\n\t\t// Existing documents first: a retirement frees its address before a creation of this commit takes it.\n\t\tfor (const [id, record] of existing) recorded.push(...(await this.#change(step, record, this.#actions.get(id)!)));\n\t\tfor (const [id, action] of this.#actions) {\n\t\t\tif (action.create === undefined) continue;\n\t\t\trecorded.push(await this.#claim(step, id, \"document\"));\n\t\t\trecorded.push(await this.#create(step, action.create, action.base!, action.retire));\n\t\t}\n\t\treturn recorded;\n\t}\n\n\t/** Stages one write: a table row encoded, a document command into its action. */\n\t#stage(write: StorageWrite, raise: (id: number) => void): void {\n\t\tconst tables = this.#tables;\n\t\tswitch (write.type) {\n\t\t\tcase \"conversation\":\n\t\t\t\tthis.#row(write.value.id, write.type, tables.conversations, conversationRow(write.value), raise);\n\t\t\t\treturn;\n\t\t\tcase \"entry\": {\n\t\t\t\tconst { id, conversationId, head } = write.value;\n\t\t\t\tthis.#row(id, write.type, tables.entries, entryRow(write.value), raise);\n\t\t\t\t// Its claim is the entry's: `apply` claims an id once.\n\t\t\t\tif (head !== undefined) {\n\t\t\t\t\tconst row = tables.entryHeads.encode({ id, conversation_id: conversationId });\n\t\t\t\t\tthis.#rows.push({ id, type: write.type, table: tables.entryHeads, row });\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"task\":\n\t\t\t\tthis.#row(write.value.id, write.type, tables.tasks, taskRow(write.value), raise);\n\t\t\t\treturn;\n\t\t\tcase \"submission\":\n\t\t\t\tthis.#row(write.value.id, write.type, tables.submissions, submissionRow(write.value), raise);\n\t\t\t\treturn;\n\t\t\tcase \"document.create\":\n\t\t\tcase \"document.copy\": {\n\t\t\t\tconst { id } = write.record;\n\t\t\t\tconst action = this.#action(id);\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\tif (write.type === \"document.copy\") action.copy = write.source;\n\t\t\t\telse action.base = { ...write.content, text: JSON.stringify(write.content.value) };\n\t\t\t\tthis.#claims.push({ id, type: \"document\" });\n\t\t\t\traise(id);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"document.change\": {\n\t\t\t\tconst action = this.#action(write.id);\n\t\t\t\t// A creation's base is its content command (`SqliteStorage` counts it so too), in either order.\n\t\t\t\tif (action.create !== undefined || action.content !== undefined || action.copy !== undefined) {\n\t\t\t\t\tthrow new Error(`Document ${write.id} has more than one content command`);\n\t\t\t\t}\n\t\t\t\tconst { content } = write;\n\t\t\t\taction.content = { content, text: JSON.stringify(content.kind === \"base\" ? content.value : content.ops) };\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcase \"document.retire\": {\n\t\t\t\tconst action = this.#action(write.id);\n\t\t\t\tif (action.retire) throw new Error(`Document ${write.id} is retired more than once`);\n\t\t\t\taction.retire = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t#row(id: Id<string>, type: RowWrite[\"type\"], table: OwnerTable, row: object, raise: (id: number) => void): void {\n\t\tthis.#claims.push({ id, type });\n\t\tthis.#rows.push({ id, type, table, row: table.encode(row) });\n\t\traise(id);\n\t}\n\n\t#action(id: DocumentId): DocumentAction {\n\t\tlet action = this.#actions.get(id);\n\t\tif (action === undefined) {\n\t\t\taction = { retire: false };\n\t\t\tthis.#actions.set(id, action);\n\t\t}\n\t\treturn action;\n\t}\n\n\t/**\n\t * Durable's global id checks (`SqliteStorage.checkGlobalIds`): a conversation, entry or document id claimed by\n\t * anything, or written twice; a task or submission id claimed by another type. Returns the stored types.\n\t */\n\tasync #checkIds(step: Step): Promise<Map<number, RecordType>> {\n\t\tconst stored = new Map<number, RecordType | undefined>();\n\t\tconst claimed = new Map<number, RecordType>();\n\t\tfor (const { id, type } of this.#claims) {\n\t\t\tlet existing = stored.get(id);\n\t\t\tif (!stored.has(id)) {\n\t\t\t\tconst row = await this.#tables.recordIds.get(step, [id], this.#context);\n\t\t\t\texisting = row?.record_type as RecordType | undefined;\n\t\t\t\tstored.set(id, existing);\n\t\t\t}\n\t\t\tconst earlier = claimed.get(id);\n\t\t\tif (type === \"conversation\" || type === \"entry\" || type === \"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 !== type) throw new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\tif (earlier !== undefined && earlier !== type) throw new Error(`ID ${id} is written as two record types`);\n\t\t\t}\n\t\t\tclaimed.set(id, type);\n\t\t}\n\t\tconst types = new Map<number, RecordType>();\n\t\tfor (const [id, type] of stored) if (type !== undefined) types.set(id, type);\n\t\treturn types;\n\t}\n\n\t/**\n\t * Durable's document checks (`SqliteStorage.checkDocumentActions`): a change or retirement of an unknown or retired\n\t * document, a delta on another version, more than one live incarnation per address after the commit. Returns the\n\t * existing records the commit changes or retires, in action order.\n\t */\n\tasync #checkDocuments(step: Step): Promise<Map<DocumentId, EngineRecord>> {\n\t\tconst existing = new Map<DocumentId, EngineRecord>();\n\t\tfor (const [id, action] of this.#actions) {\n\t\t\tif (action.create !== undefined) continue;\n\t\t\tconst stored = await readDocument(step, id);\n\t\t\tconst record = stored === undefined ? undefined : durableRecord(stored, this.#owner);\n\t\t\tif (record === undefined) throw new Error(`Unknown document: ${id}`);\n\t\t\tif (record.retiredAt !== undefined) throw new Error(`Document ${id} is retired`);\n\t\t\tconst content = action.content?.content;\n\t\t\tif (content?.kind === \"delta\" && content.version !== stored!.version) {\n\t\t\t\tthrow new Error(`Document ${id} version transition requires a base`);\n\t\t\t}\n\t\t\texisting.set(id, stored!);\n\t\t}\n\t\t/** Per address a live creation takes: its live incarnations after the commit so far. */\n\t\tconst live = new Map<string, number>();\n\t\tfor (const action of this.#actions.values()) {\n\t\t\tif (action.create === undefined || action.retire) continue;\n\t\t\tconst address = addressOf(action.create, this.#owner);\n\t\t\tconst key = JSON.stringify([address.namespace, address.kind, address.key]);\n\t\t\tlet count = live.get(key);\n\t\t\tif (count === undefined) {\n\t\t\t\t// The incarnation live there now counts unless this commit retires it.\n\t\t\t\tconst current = await findDocument(step, address);\n\t\t\t\tcount = current === undefined || this.#actions.get(current.id as DocumentId)?.retire === true ? 0 : 1;\n\t\t\t}\n\t\t\tif (++count > 1) throw new Error(\"Document address already has a current incarnation\");\n\t\t\tlive.set(key, count);\n\t\t}\n\t\treturn existing;\n\t}\n\n\t/** Puts `id`'s `record_ids` row: its record type. */\n\t#claim(step: Step, id: number, type: RecordType): Promise<StoreWrite> {\n\t\tconst table = this.#tables.recordIds;\n\t\treturn table.put(step, table.encode({ id, record_type: type }));\n\t}\n\n\t/** An existing document's content, then its retirement, as their writes. */\n\tasync #change(step: Step, record: EngineRecord, action: DocumentAction): Promise<StoreWrite[]> {\n\t\tconst writes: StoreWrite[] = [];\n\t\tlet { revision } = record;\n\t\tif (action.content !== undefined) {\n\t\t\tconst { content, text } = action.content;\n\t\t\tconst stored = engineContent(content);\n\t\t\tawait writeContent(step, record.id, record.history, revision, stored, text);\n\t\t\twrites.push({ kind: \"document.change\", id: record.id, revision, content: stored });\n\t\t\trevision++;\n\t\t}\n\t\tif (action.retire) {\n\t\t\tawait retireRecord(step, record);\n\t\t\twrites.push({ kind: \"document.retire\", id: record.id, revision });\n\t\t}\n\t\treturn writes;\n\t}\n\n\t/** A creation or a resolved copy (retired at once for a create-plus-retire, an empty lifetime), as its write. */\n\tasync #create(step: Step, create: DocumentCreate, base: StagedBase, retired: boolean): Promise<StoreWrite> {\n\t\tconst record = engineRecord(create, this.#owner, base.version, step.seq, retired);\n\t\tawait insertDocument(step, record, base.text);\n\t\tconst value = base.value as JsonValue;\n\t\treturn { kind: \"document.create\", record, base: { version: base.version, fingerprint: \"\", value } };\n\t}\n}\n\n/** A Durable content as the engine stores it: its documents have no schema fingerprint. */\nfunction engineContent(content: DocumentContent): EngineContent {\n\treturn content.kind === \"base\"\n\t\t? { kind: \"base\", version: content.version, fingerprint: \"\", value: content.value }\n\t\t: { kind: \"delta\", version: content.version, ops: content.ops };\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type DocumentEntry, type DocumentHistory, type DocumentAddress as EngineAddress, type DocumentRecord as EngineRecord } from "@earendil-works/chord/next";
|
|
2
|
+
import type { SqliteExecutor } from "@earendil-works/chord/sqlite";
|
|
3
|
+
import type { DocumentAddress, DocumentCreate, DocumentId, DocumentPoint, DocumentRecord, StoredDocument } from "../../types.ts";
|
|
4
|
+
/** The namespaces a storage claims under its owner: Durable's scopes (Chord's namespaces never contain a `:`). */
|
|
5
|
+
export declare const SCOPE_NAMESPACES: readonly ["session", "conversation:", "task:"];
|
|
6
|
+
/** The engine namespace of a Durable scope: `session`, `conversation:<id>`, `task:<id>`. */
|
|
7
|
+
export declare function namespaceOf(scope: DocumentRecord["scope"]): string;
|
|
8
|
+
/** The engine address of a Durable address under `owner`. */
|
|
9
|
+
export declare function addressOf(address: DocumentAddress | DocumentCreate, owner: string): EngineAddress;
|
|
10
|
+
/** What a record of `scope` keeps: session and task documents and latest conversation ones their latest state. */
|
|
11
|
+
export declare function historyOf(record: DocumentCreate | DocumentRecord): DocumentHistory;
|
|
12
|
+
/**
|
|
13
|
+
* The engine record of a Durable creation at `seq` (retired at once for an empty lifetime): the Durable record itself,
|
|
14
|
+
* its creation seq included, in `meta`, so a read of a live record parses one JSON text from one column, as
|
|
15
|
+
* `SqliteStorage`'s does (a retirement later sets only the engine's `retired_at`).
|
|
16
|
+
*/
|
|
17
|
+
export declare function engineRecord(create: DocumentCreate, owner: string, version: number, seq: number, retired: boolean): EngineRecord;
|
|
18
|
+
/**
|
|
19
|
+
* The Durable record of an engine record of this storage's owner and scopes, `undefined` for anyone else's (a Chord
|
|
20
|
+
* document, another owner's): its id is unknown to this storage.
|
|
21
|
+
*/
|
|
22
|
+
export declare function durableRecord(record: EngineRecord, owner: string): DocumentRecord | undefined;
|
|
23
|
+
/** The Durable record of an engine entry read at one of this storage's addresses or scopes (the record in `meta`). */
|
|
24
|
+
export declare function recordOfEntry(entry: DocumentEntry): DocumentRecord;
|
|
25
|
+
/** Whether a document is alive at `at` (the half-open lifetime). */
|
|
26
|
+
export declare function aliveAt(record: DocumentRecord, at: DocumentPoint): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* A document read at a point, as a tagged result: request errors are told after the guarded read (slice 6b §1.4),
|
|
29
|
+
* never thrown inside it.
|
|
30
|
+
*/
|
|
31
|
+
export type DocumentRead = {
|
|
32
|
+
readonly kind: "found";
|
|
33
|
+
readonly document: StoredDocument | undefined;
|
|
34
|
+
} | {
|
|
35
|
+
readonly kind: "current_only";
|
|
36
|
+
readonly id: DocumentId;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Document `id` of `owner` at `at`: absent (unknown, someone else's, not alive at `at`), a record whose history does
|
|
40
|
+
* not reach back to `at`, or its value from the base at or below `at` and the deltas up to it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readAt(executor: Pick<SqliteExecutor, "get" | "all">, id: DocumentId, at: DocumentPoint, owner: string): Promise<DocumentRead>;
|
|
43
|
+
//# sourceMappingURL=documents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../../src/storage/store/documents.ts"],"names":[],"mappings":"AAKA,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,IAAI,aAAa,EAErC,KAAK,cAAc,IAAI,YAAY,EAGnC,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE,OAAO,KAAK,EACX,eAAe,EACf,cAAc,EACd,UAAU,EACV,aAAa,EACb,cAAc,EAEd,cAAc,EACd,MAAM,gBAAgB,CAAC;AAGxB,kHAAkH;AAClH,eAAO,MAAM,gBAAgB,YAAI,SAAS,EAAE,eAAe,EAAE,OAAO,CAAU,CAAC;AAE/E,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,GAAG,MAAM,CASlE;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,OAAO,EAAE,eAAe,GAAG,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CAGjG;AAED,kHAAkH;AAClH,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,GAAG,eAAe,CAElF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,GACd,YAAY,CAYd;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAI7F;AAED,sHAAsH;AACtH,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAKlE;AASD,oEAAoE;AACpE,wBAAgB,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,EAAE,aAAa,GAAG,OAAO,CAG1E;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,SAAS,CAAA;CAAE,GACzE;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAA;CAAE,CAAC;AAE9D;;;GAGG;AACH,wBAAsB,MAAM,CAC3B,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,CAAC,EAC7C,EAAE,EAAE,UAAU,EACd,EAAE,EAAE,aAAa,EACjB,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,CAgBvB"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Durable's documents in the store's documents engine (slice 6b §1.5, R1): a Durable address as an engine address
|
|
2
|
+
// under the storage's owner name (its scope as the namespace, its kind and key JSON-encoded as Durable's indexed
|
|
3
|
+
// strings), a Durable record as an engine record and back (the Durable record in `meta`), and a document read at a
|
|
4
|
+
// point.
|
|
5
|
+
import { loadContent, readDocumentEntry, } from "@earendil-works/chord/next";
|
|
6
|
+
import { seqFromNumber } from "../../ids.js";
|
|
7
|
+
import { encodeIndexed } from "./tables.js";
|
|
8
|
+
/** The namespaces a storage claims under its owner: Durable's scopes (Chord's namespaces never contain a `:`). */
|
|
9
|
+
export const SCOPE_NAMESPACES = ["session", "conversation:", "task:"];
|
|
10
|
+
/** The engine namespace of a Durable scope: `session`, `conversation:<id>`, `task:<id>`. */
|
|
11
|
+
export function namespaceOf(scope) {
|
|
12
|
+
switch (scope.kind) {
|
|
13
|
+
case "session":
|
|
14
|
+
return "session";
|
|
15
|
+
case "conversation":
|
|
16
|
+
return `conversation:${scope.conversationId}`;
|
|
17
|
+
case "task":
|
|
18
|
+
return `task:${scope.taskId}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** The engine address of a Durable address under `owner`. */
|
|
22
|
+
export function addressOf(address, owner) {
|
|
23
|
+
const at = { owner, namespace: namespaceOf(address.scope), kind: encodeIndexed(address.kind) };
|
|
24
|
+
return address.key === undefined ? at : { ...at, key: encodeIndexed(address.key) };
|
|
25
|
+
}
|
|
26
|
+
/** What a record of `scope` keeps: session and task documents and latest conversation ones their latest state. */
|
|
27
|
+
export function historyOf(record) {
|
|
28
|
+
return record.scope.kind === "conversation" && record.history === "rewindable" ? "rewindable" : "latest";
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The engine record of a Durable creation at `seq` (retired at once for an empty lifetime): the Durable record itself,
|
|
32
|
+
* its creation seq included, in `meta`, so a read of a live record parses one JSON text from one column, as
|
|
33
|
+
* `SqliteStorage`'s does (a retirement later sets only the engine's `retired_at`).
|
|
34
|
+
*/
|
|
35
|
+
export function engineRecord(create, owner, version, seq, retired) {
|
|
36
|
+
return {
|
|
37
|
+
id: create.id,
|
|
38
|
+
...addressOf(create, owner),
|
|
39
|
+
history: historyOf(create),
|
|
40
|
+
createdAt: seq,
|
|
41
|
+
...(retired ? { retiredAt: seq } : {}),
|
|
42
|
+
meta: JSON.stringify({ ...create, createdAt: seq }),
|
|
43
|
+
revision: 1,
|
|
44
|
+
version,
|
|
45
|
+
fingerprint: "",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The Durable record of an engine record of this storage's owner and scopes, `undefined` for anyone else's (a Chord
|
|
50
|
+
* document, another owner's): its id is unknown to this storage.
|
|
51
|
+
*/
|
|
52
|
+
export function durableRecord(record, owner) {
|
|
53
|
+
const { meta } = record;
|
|
54
|
+
if (meta === undefined || !ownedHere(record, owner))
|
|
55
|
+
return undefined;
|
|
56
|
+
return recordOfEntry({ meta, retired_at: record.retiredAt ?? null });
|
|
57
|
+
}
|
|
58
|
+
/** The Durable record of an engine entry read at one of this storage's addresses or scopes (the record in `meta`). */
|
|
59
|
+
export function recordOfEntry(entry) {
|
|
60
|
+
// Written by `engineRecord`, parsed fresh: a retirement since is set on it in place.
|
|
61
|
+
const record = JSON.parse(entry.meta);
|
|
62
|
+
if (entry.retired_at !== undefined && entry.retired_at !== null)
|
|
63
|
+
record.retiredAt = seqFromNumber(entry.retired_at);
|
|
64
|
+
return record;
|
|
65
|
+
}
|
|
66
|
+
/** Whether a record is this storage's: `owner`'s, in one of Durable's scopes (not a Chord document of the owner). */
|
|
67
|
+
function ownedHere(record, owner) {
|
|
68
|
+
const { namespace } = record;
|
|
69
|
+
if (record.owner !== owner)
|
|
70
|
+
return false;
|
|
71
|
+
return namespace === "session" || namespace.startsWith("conversation:") || namespace.startsWith("task:");
|
|
72
|
+
}
|
|
73
|
+
/** Whether a document is alive at `at` (the half-open lifetime). */
|
|
74
|
+
export function aliveAt(record, at) {
|
|
75
|
+
if (at === "current")
|
|
76
|
+
return record.retiredAt === undefined;
|
|
77
|
+
return record.createdAt <= at && (record.retiredAt === undefined || at < record.retiredAt);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Document `id` of `owner` at `at`: absent (unknown, someone else's, not alive at `at`), a record whose history does
|
|
81
|
+
* not reach back to `at`, or its value from the base at or below `at` and the deltas up to it.
|
|
82
|
+
*/
|
|
83
|
+
export async function readAt(executor, id, at, owner) {
|
|
84
|
+
const entry = await readDocumentEntry(executor, id);
|
|
85
|
+
if (entry === undefined || entry.meta === null || !ownedHere(entry, owner)) {
|
|
86
|
+
return { kind: "found", document: undefined };
|
|
87
|
+
}
|
|
88
|
+
const record = recordOfEntry(entry);
|
|
89
|
+
if (at !== "current" && entry.history !== "rewindable")
|
|
90
|
+
return { kind: "current_only", id };
|
|
91
|
+
if (!aliveAt(record, at))
|
|
92
|
+
return { kind: "found", document: undefined };
|
|
93
|
+
const point = at;
|
|
94
|
+
const loaded = await loadContent(executor, id, point);
|
|
95
|
+
// The engine stores what Durable's commits gave it: JSON objects.
|
|
96
|
+
const value = loaded.value;
|
|
97
|
+
return {
|
|
98
|
+
kind: "found",
|
|
99
|
+
document: { record, version: loaded.version, value, deltasSinceBase: loaded.deltasSinceBase },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=documents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documents.js","sourceRoot":"","sources":["../../../src/storage/store/documents.ts"],"names":[],"mappings":"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","sourcesContent":["// Durable's documents in the store's documents engine (slice 6b §1.5, R1): a Durable address as an engine address\n// under the storage's owner name (its scope as the namespace, its kind and key JSON-encoded as Durable's indexed\n// strings), a Durable record as an engine record and back (the Durable record in `meta`), and a document read at a\n// point.\n\nimport {\n\ttype DocumentEntry,\n\ttype DocumentHistory,\n\ttype DocumentAddress as EngineAddress,\n\ttype DocumentPoint as EnginePoint,\n\ttype DocumentRecord as EngineRecord,\n\tloadContent,\n\treadDocumentEntry,\n} from \"@earendil-works/chord/next\";\nimport type { SqliteExecutor } from \"@earendil-works/chord/sqlite\";\nimport { seqFromNumber } from \"../../ids.ts\";\nimport type {\n\tDocumentAddress,\n\tDocumentCreate,\n\tDocumentId,\n\tDocumentPoint,\n\tDocumentRecord,\n\tJsonObject,\n\tStoredDocument,\n} from \"../../types.ts\";\nimport { encodeIndexed } from \"./tables.ts\";\n\n/** The namespaces a storage claims under its owner: Durable's scopes (Chord's namespaces never contain a `:`). */\nexport const SCOPE_NAMESPACES = [\"session\", \"conversation:\", \"task:\"] as const;\n\n/** The engine namespace of a Durable scope: `session`, `conversation:<id>`, `task:<id>`. */\nexport function namespaceOf(scope: DocumentRecord[\"scope\"]): string {\n\tswitch (scope.kind) {\n\t\tcase \"session\":\n\t\t\treturn \"session\";\n\t\tcase \"conversation\":\n\t\t\treturn `conversation:${scope.conversationId}`;\n\t\tcase \"task\":\n\t\t\treturn `task:${scope.taskId}`;\n\t}\n}\n\n/** The engine address of a Durable address under `owner`. */\nexport function addressOf(address: DocumentAddress | DocumentCreate, owner: string): EngineAddress {\n\tconst at = { owner, namespace: namespaceOf(address.scope), kind: encodeIndexed(address.kind) };\n\treturn address.key === undefined ? at : { ...at, key: encodeIndexed(address.key) };\n}\n\n/** What a record of `scope` keeps: session and task documents and latest conversation ones their latest state. */\nexport function historyOf(record: DocumentCreate | DocumentRecord): DocumentHistory {\n\treturn record.scope.kind === \"conversation\" && record.history === \"rewindable\" ? \"rewindable\" : \"latest\";\n}\n\n/**\n * The engine record of a Durable creation at `seq` (retired at once for an empty lifetime): the Durable record itself,\n * its creation seq included, in `meta`, so a read of a live record parses one JSON text from one column, as\n * `SqliteStorage`'s does (a retirement later sets only the engine's `retired_at`).\n */\nexport function engineRecord(\n\tcreate: DocumentCreate,\n\towner: string,\n\tversion: number,\n\tseq: number,\n\tretired: boolean,\n): EngineRecord {\n\treturn {\n\t\tid: create.id,\n\t\t...addressOf(create, owner),\n\t\thistory: historyOf(create),\n\t\tcreatedAt: seq,\n\t\t...(retired ? { retiredAt: seq } : {}),\n\t\tmeta: JSON.stringify({ ...create, createdAt: seq }),\n\t\trevision: 1,\n\t\tversion,\n\t\tfingerprint: \"\",\n\t};\n}\n\n/**\n * The Durable record of an engine record of this storage's owner and scopes, `undefined` for anyone else's (a Chord\n * document, another owner's): its id is unknown to this storage.\n */\nexport function durableRecord(record: EngineRecord, owner: string): DocumentRecord | undefined {\n\tconst { meta } = record;\n\tif (meta === undefined || !ownedHere(record, owner)) return undefined;\n\treturn recordOfEntry({ meta, retired_at: record.retiredAt ?? null });\n}\n\n/** The Durable record of an engine entry read at one of this storage's addresses or scopes (the record in `meta`). */\nexport function recordOfEntry(entry: DocumentEntry): DocumentRecord {\n\t// Written by `engineRecord`, parsed fresh: a retirement since is set on it in place.\n\tconst record = JSON.parse(entry.meta!) as { -readonly [K in keyof DocumentRecord]: DocumentRecord[K] };\n\tif (entry.retired_at !== undefined && entry.retired_at !== null) record.retiredAt = seqFromNumber(entry.retired_at);\n\treturn record as DocumentRecord;\n}\n\n/** Whether a record is this storage's: `owner`'s, in one of Durable's scopes (not a Chord document of the owner). */\nfunction ownedHere(record: { readonly owner: string; readonly namespace: string }, owner: string): boolean {\n\tconst { namespace } = record;\n\tif (record.owner !== owner) return false;\n\treturn namespace === \"session\" || namespace.startsWith(\"conversation:\") || namespace.startsWith(\"task:\");\n}\n\n/** Whether a document is alive at `at` (the half-open lifetime). */\nexport function aliveAt(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\n/**\n * A document read at a point, as a tagged result: request errors are told after the guarded read (slice 6b §1.4),\n * never thrown inside it.\n */\nexport type DocumentRead =\n\t| { readonly kind: \"found\"; readonly document: StoredDocument | undefined }\n\t| { readonly kind: \"current_only\"; readonly id: DocumentId };\n\n/**\n * Document `id` of `owner` at `at`: absent (unknown, someone else's, not alive at `at`), a record whose history does\n * not reach back to `at`, or its value from the base at or below `at` and the deltas up to it.\n */\nexport async function readAt(\n\texecutor: Pick<SqliteExecutor, \"get\" | \"all\">,\n\tid: DocumentId,\n\tat: DocumentPoint,\n\towner: string,\n): Promise<DocumentRead> {\n\tconst entry = await readDocumentEntry(executor, id);\n\tif (entry === undefined || entry.meta === null || !ownedHere(entry, owner)) {\n\t\treturn { kind: \"found\", document: undefined };\n\t}\n\tconst record = recordOfEntry(entry);\n\tif (at !== \"current\" && entry.history !== \"rewindable\") return { kind: \"current_only\", id };\n\tif (!aliveAt(record, at)) return { kind: \"found\", document: undefined };\n\tconst point: EnginePoint = at;\n\tconst loaded = await loadContent(executor, id, point);\n\t// The engine stores what Durable's commits gave it: JSON objects.\n\tconst value = loaded.value as JsonObject;\n\treturn {\n\t\tkind: \"found\",\n\t\tdocument: { record, version: loaded.version, value, deltasSinceBase: loaded.deltasSinceBase },\n\t};\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/storage/store/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/storage/store/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC","sourcesContent":["export { StoreStorage } from \"./storage.ts\";\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Context } from "@earendil-works/chord";
|
|
2
|
+
import type { OwnerTable } from "@earendil-works/chord/next";
|
|
3
|
+
import type { SqliteExecutor, SqliteValue } from "@earendil-works/chord/sqlite";
|
|
4
|
+
import type { CommitTables } from "./commit.ts";
|
|
5
|
+
/**
|
|
6
|
+
* A stored row as a query selects it: the record cell, its version (selected only once the column has versions past
|
|
7
|
+
* 1), and an entry's commit seq.
|
|
8
|
+
*/
|
|
9
|
+
type RecordRow = {
|
|
10
|
+
readonly record: string;
|
|
11
|
+
readonly record$v?: number;
|
|
12
|
+
readonly commit_seq?: number;
|
|
13
|
+
};
|
|
14
|
+
/** One condition of a read: a column, how it compares, its value; a call site keeps one order of them. */
|
|
15
|
+
export type Condition = readonly [string, "eq" | "gt" | "lt" | "gte" | "lte", SqliteValue];
|
|
16
|
+
/**
|
|
17
|
+
* One table's compiled reads: by id (with the `byIdExtra` columns), and by shape (conditions in binding order, an
|
|
18
|
+
* order; the record only), each shape compiled at its first use.
|
|
19
|
+
*/
|
|
20
|
+
export declare class TableQueries {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(table: OwnerTable, byIdExtra?: readonly string[]);
|
|
23
|
+
/** The row with `id`, `undefined` when absent. */
|
|
24
|
+
byId(executor: Pick<SqliteExecutor, "get">, id: number): Promise<RecordRow | undefined>;
|
|
25
|
+
/** Up to `limit` rows whose `conditions` hold, by id in `direction`. */
|
|
26
|
+
select(executor: Pick<SqliteExecutor, "all">, conditions: readonly Condition[], direction: "ascending" | "descending", limit: number): Promise<RecordRow[]>;
|
|
27
|
+
/** The first row whose `conditions` hold, by id in `direction`; `undefined` when none does. */
|
|
28
|
+
first(executor: Pick<SqliteExecutor, "get">, conditions: readonly Condition[], direction: "ascending" | "descending"): Promise<RecordRow | undefined>;
|
|
29
|
+
/** A row's record, read as its column's (version checked, migrated on access). */
|
|
30
|
+
record<T>(row: RecordRow, context: Context): T;
|
|
31
|
+
/** Appends the records of `rows` to `into`, in order, each read as `record` reads it (one check per call). */
|
|
32
|
+
records<T>(rows: readonly RecordRow[], into: T[], context: Context): void;
|
|
33
|
+
}
|
|
34
|
+
/** The compiled reads of the storage's tables. */
|
|
35
|
+
export type StorageQueries = {
|
|
36
|
+
readonly conversations: TableQueries;
|
|
37
|
+
readonly entries: TableQueries;
|
|
38
|
+
/** The id of a conversation's newest entry with a head marker: any, or at or before an id (bound second). */
|
|
39
|
+
readonly latestHead: {
|
|
40
|
+
readonly any: string;
|
|
41
|
+
readonly atOrBefore: string;
|
|
42
|
+
};
|
|
43
|
+
readonly tasks: TableQueries;
|
|
44
|
+
readonly submissions: TableQueries;
|
|
45
|
+
};
|
|
46
|
+
/** The reads of `tables`. */
|
|
47
|
+
export declare function storageQueries(tables: CommitTables): StorageQueries;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=queries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../../src/storage/store/queries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAS,UAAU,EAAa,MAAM,4BAA4B,CAAC;AAC/E,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;GAGG;AACH,KAAK,SAAS,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvG,0GAA0G;AAC1G,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,EAAE,WAAW,CAAC,CAAC;AAW3F;;;GAGG;AACH,qBAAa,YAAY;;IAQxB,YAAY,KAAK,EAAE,UAAU,EAAE,SAAS,GAAE,SAAS,MAAM,EAAO,EAM/D;IAED,kDAAkD;IAClD,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAEtF;IAED,wEAAwE;IACxE,MAAM,CACL,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,EACrC,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,SAAS,EAAE,WAAW,GAAG,YAAY,EACrC,KAAK,EAAE,MAAM,GACX,OAAO,CAAC,SAAS,EAAE,CAAC,CAGtB;IAED,+FAA+F;IAC/F,KAAK,CACJ,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,EACrC,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,SAAS,EAAE,WAAW,GAAG,YAAY,GACnC,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAGhC;IAED,kFAAkF;IAClF,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,GAAG,CAAC,CAI7C;IAED,8GAA8G;IAC9G,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAOxE;CAmBD;AAED,kDAAkD;AAClD,MAAM,MAAM,cAAc,GAAG;IAC5B,QAAQ,CAAC,aAAa,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;IAC/B,6GAA6G;IAC7G,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3E,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC;CACnC,CAAC;AAEF,6BAA6B;AAC7B,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAgBnE"}
|