@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,93 @@
|
|
|
1
|
+
// StoreStorage's reads as the store's queries (slice 6b D2, §2.4), compiled once from the store's query vocabulary
|
|
2
|
+
// into SQL over the declared tables: no SQL is rendered per call, and a read selects only the record cell (by id, an
|
|
3
|
+
// entry's commit seq too; each column returned costs) and parses one JSON text per record, as `SqliteStorage`'s do.
|
|
4
|
+
const ASCENDING = { columns: ["id"], direction: "asc" };
|
|
5
|
+
const DESCENDING = { columns: ["id"], direction: "desc" };
|
|
6
|
+
/** The columns of a read of ids only. */
|
|
7
|
+
const ID = ["id"];
|
|
8
|
+
/** A placeholder value: a query's shape binds its values at each call. */
|
|
9
|
+
const ANY = 0;
|
|
10
|
+
/**
|
|
11
|
+
* One table's compiled reads: by id (with the `byIdExtra` columns), and by shape (conditions in binding order, an
|
|
12
|
+
* order; the record only), each shape compiled at its first use.
|
|
13
|
+
*/
|
|
14
|
+
export class TableQueries {
|
|
15
|
+
#table;
|
|
16
|
+
/** The record column's read of a cell at a version past 1; `undefined` while the column is at version 1. */
|
|
17
|
+
#versioned;
|
|
18
|
+
#byId;
|
|
19
|
+
#shapes = new Map();
|
|
20
|
+
#columns;
|
|
21
|
+
constructor(table, byIdExtra = []) {
|
|
22
|
+
this.#table = table;
|
|
23
|
+
const versioned = table.jsonVersion("record") > 1;
|
|
24
|
+
this.#versioned = versioned ? table.reader("record") : undefined;
|
|
25
|
+
this.#columns = versioned ? ["record", "record$v"] : ["record"];
|
|
26
|
+
this.#byId = table.prepareGet([...this.#columns, ...byIdExtra]);
|
|
27
|
+
}
|
|
28
|
+
/** The row with `id`, `undefined` when absent. */
|
|
29
|
+
byId(executor, id) {
|
|
30
|
+
return executor.get(this.#byId, id);
|
|
31
|
+
}
|
|
32
|
+
/** Up to `limit` rows whose `conditions` hold, by id in `direction`. */
|
|
33
|
+
select(executor, conditions, direction, limit) {
|
|
34
|
+
const sql = this.#shape(conditions, direction, "many");
|
|
35
|
+
return executor.all(sql, ...conditions.map(([, , value]) => value), limit);
|
|
36
|
+
}
|
|
37
|
+
/** The first row whose `conditions` hold, by id in `direction`; `undefined` when none does. */
|
|
38
|
+
first(executor, conditions, direction) {
|
|
39
|
+
const sql = this.#shape(conditions, direction, "first");
|
|
40
|
+
return executor.get(sql, ...conditions.map(([, , value]) => value));
|
|
41
|
+
}
|
|
42
|
+
/** A row's record, read as its column's (version checked, migrated on access). */
|
|
43
|
+
record(row, context) {
|
|
44
|
+
const versioned = this.#versioned;
|
|
45
|
+
// The cell holds a record of this table the storage wrote; at version 1 it is that JSON (`OwnerTable.reader`).
|
|
46
|
+
return (versioned === undefined ? JSON.parse(row.record) : versioned(row.record, row.record$v, context));
|
|
47
|
+
}
|
|
48
|
+
/** Appends the records of `rows` to `into`, in order, each read as `record` reads it (one check per call). */
|
|
49
|
+
records(rows, into, context) {
|
|
50
|
+
const versioned = this.#versioned;
|
|
51
|
+
if (versioned === undefined) {
|
|
52
|
+
for (const row of rows)
|
|
53
|
+
into.push(JSON.parse(row.record));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
for (const row of rows)
|
|
57
|
+
into.push(versioned(row.record, row.record$v, context));
|
|
58
|
+
}
|
|
59
|
+
/** The compiled read of a shape: its conditions' columns and comparisons, the direction, one row or many. */
|
|
60
|
+
#shape(conditions, direction, rows) {
|
|
61
|
+
let key = `${rows} ${direction}`;
|
|
62
|
+
for (const [column, condition] of conditions)
|
|
63
|
+
key += ` ${column} ${condition}`;
|
|
64
|
+
let sql = this.#shapes.get(key);
|
|
65
|
+
if (sql !== undefined)
|
|
66
|
+
return sql;
|
|
67
|
+
// One comparison per condition: the vocabulary's node of that name, its value a placeholder.
|
|
68
|
+
const nodes = conditions.map(([column, condition]) => ({ [condition]: [column, ANY] }));
|
|
69
|
+
const where = nodes.length === 0 ? undefined : { and: nodes };
|
|
70
|
+
const order = direction === "ascending" ? ASCENDING : DESCENDING;
|
|
71
|
+
sql =
|
|
72
|
+
rows === "first"
|
|
73
|
+
? this.#table.prepareFirst(this.#columns, where, order)
|
|
74
|
+
: this.#table.prepare(this.#columns, where, order);
|
|
75
|
+
this.#shapes.set(key, sql);
|
|
76
|
+
return sql;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** The reads of `tables`. */
|
|
80
|
+
export function storageQueries(tables) {
|
|
81
|
+
const heads = tables.entryHeads;
|
|
82
|
+
return {
|
|
83
|
+
conversations: new TableQueries(tables.conversations),
|
|
84
|
+
entries: new TableQueries(tables.entries, ["commit_seq"]),
|
|
85
|
+
latestHead: {
|
|
86
|
+
any: heads.prepareFirst(ID, { eq: ["conversation_id", ANY] }, DESCENDING),
|
|
87
|
+
atOrBefore: heads.prepareFirst(ID, { and: [{ eq: ["conversation_id", ANY] }, { lte: ["id", ANY] }] }, DESCENDING),
|
|
88
|
+
},
|
|
89
|
+
tasks: new TableQueries(tables.tasks),
|
|
90
|
+
submissions: new TableQueries(tables.submissions),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=queries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../../src/storage/store/queries.ts"],"names":[],"mappings":"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","sourcesContent":["// StoreStorage's reads as the store's queries (slice 6b D2, §2.4), compiled once from the store's query vocabulary\n// into SQL over the declared tables: no SQL is rendered per call, and a read selects only the record cell (by id, an\n// entry's commit seq too; each column returned costs) and parses one JSON text per record, as `SqliteStorage`'s do.\n\nimport type { Context } from \"@earendil-works/chord\";\nimport type { Order, OwnerTable, WhereNode } from \"@earendil-works/chord/next\";\nimport type { SqliteExecutor, SqliteValue } from \"@earendil-works/chord/sqlite\";\nimport type { CommitTables } from \"./commit.ts\";\n\n/**\n * A stored row as a query selects it: the record cell, its version (selected only once the column has versions past\n * 1), and an entry's commit seq.\n */\ntype RecordRow = { readonly record: string; readonly record$v?: number; readonly commit_seq?: number };\n\n/** One condition of a read: a column, how it compares, its value; a call site keeps one order of them. */\nexport type Condition = readonly [string, \"eq\" | \"gt\" | \"lt\" | \"gte\" | \"lte\", SqliteValue];\n\nconst ASCENDING: Order = { columns: [\"id\"], direction: \"asc\" };\nconst DESCENDING: Order = { columns: [\"id\"], direction: \"desc\" };\n\n/** The columns of a read of ids only. */\nconst ID = [\"id\"];\n\n/** A placeholder value: a query's shape binds its values at each call. */\nconst ANY = 0;\n\n/**\n * One table's compiled reads: by id (with the `byIdExtra` columns), and by shape (conditions in binding order, an\n * order; the record only), each shape compiled at its first use.\n */\nexport class TableQueries {\n\treadonly #table: OwnerTable;\n\t/** The record column's read of a cell at a version past 1; `undefined` while the column is at version 1. */\n\treadonly #versioned: ((text: string, version: number, context: Context) => unknown) | undefined;\n\treadonly #byId: string;\n\treadonly #shapes = new Map<string, string>();\n\treadonly #columns: readonly string[];\n\n\tconstructor(table: OwnerTable, byIdExtra: readonly string[] = []) {\n\t\tthis.#table = table;\n\t\tconst versioned = table.jsonVersion(\"record\") > 1;\n\t\tthis.#versioned = versioned ? table.reader(\"record\") : undefined;\n\t\tthis.#columns = versioned ? [\"record\", \"record$v\"] : [\"record\"];\n\t\tthis.#byId = table.prepareGet([...this.#columns, ...byIdExtra]);\n\t}\n\n\t/** The row with `id`, `undefined` when absent. */\n\tbyId(executor: Pick<SqliteExecutor, \"get\">, id: number): Promise<RecordRow | undefined> {\n\t\treturn executor.get<RecordRow>(this.#byId, id);\n\t}\n\n\t/** Up to `limit` rows whose `conditions` hold, by id in `direction`. */\n\tselect(\n\t\texecutor: Pick<SqliteExecutor, \"all\">,\n\t\tconditions: readonly Condition[],\n\t\tdirection: \"ascending\" | \"descending\",\n\t\tlimit: number,\n\t): Promise<RecordRow[]> {\n\t\tconst sql = this.#shape(conditions, direction, \"many\");\n\t\treturn executor.all<RecordRow>(sql, ...conditions.map(([, , value]) => value), limit);\n\t}\n\n\t/** The first row whose `conditions` hold, by id in `direction`; `undefined` when none does. */\n\tfirst(\n\t\texecutor: Pick<SqliteExecutor, \"get\">,\n\t\tconditions: readonly Condition[],\n\t\tdirection: \"ascending\" | \"descending\",\n\t): Promise<RecordRow | undefined> {\n\t\tconst sql = this.#shape(conditions, direction, \"first\");\n\t\treturn executor.get<RecordRow>(sql, ...conditions.map(([, , value]) => value));\n\t}\n\n\t/** A row's record, read as its column's (version checked, migrated on access). */\n\trecord<T>(row: RecordRow, context: Context): T {\n\t\tconst versioned = this.#versioned;\n\t\t// The cell holds a record of this table the storage wrote; at version 1 it is that JSON (`OwnerTable.reader`).\n\t\treturn (versioned === undefined ? JSON.parse(row.record) : versioned(row.record, row.record$v!, context)) as T;\n\t}\n\n\t/** Appends the records of `rows` to `into`, in order, each read as `record` reads it (one check per call). */\n\trecords<T>(rows: readonly RecordRow[], into: T[], context: Context): void {\n\t\tconst versioned = this.#versioned;\n\t\tif (versioned === undefined) {\n\t\t\tfor (const row of rows) into.push(JSON.parse(row.record) as T);\n\t\t\treturn;\n\t\t}\n\t\tfor (const row of rows) into.push(versioned(row.record, row.record$v!, context) as T);\n\t}\n\n\t/** The compiled read of a shape: its conditions' columns and comparisons, the direction, one row or many. */\n\t#shape(conditions: readonly Condition[], direction: \"ascending\" | \"descending\", rows: \"first\" | \"many\"): string {\n\t\tlet key = `${rows} ${direction}`;\n\t\tfor (const [column, condition] of conditions) key += ` ${column} ${condition}`;\n\t\tlet sql = this.#shapes.get(key);\n\t\tif (sql !== undefined) return sql;\n\t\t// One comparison per condition: the vocabulary's node of that name, its value a placeholder.\n\t\tconst nodes = conditions.map(([column, condition]) => ({ [condition]: [column, ANY] }) as unknown as WhereNode);\n\t\tconst where: WhereNode | undefined = nodes.length === 0 ? undefined : { and: nodes };\n\t\tconst order = direction === \"ascending\" ? ASCENDING : DESCENDING;\n\t\tsql =\n\t\t\trows === \"first\"\n\t\t\t\t? this.#table.prepareFirst(this.#columns, where, order)\n\t\t\t\t: this.#table.prepare(this.#columns, where, order);\n\t\tthis.#shapes.set(key, sql);\n\t\treturn sql;\n\t}\n}\n\n/** The compiled reads of the storage's tables. */\nexport type StorageQueries = {\n\treadonly conversations: TableQueries;\n\treadonly entries: TableQueries;\n\t/** The id of a conversation's newest entry with a head marker: any, or at or before an id (bound second). */\n\treadonly latestHead: { readonly any: string; readonly atOrBefore: string };\n\treadonly tasks: TableQueries;\n\treadonly submissions: TableQueries;\n};\n\n/** The reads of `tables`. */\nexport function storageQueries(tables: CommitTables): StorageQueries {\n\tconst heads = tables.entryHeads;\n\treturn {\n\t\tconversations: new TableQueries(tables.conversations),\n\t\tentries: new TableQueries(tables.entries, [\"commit_seq\"]),\n\t\tlatestHead: {\n\t\t\tany: heads.prepareFirst(ID, { eq: [\"conversation_id\", ANY] }, DESCENDING),\n\t\t\tatOrBefore: heads.prepareFirst(\n\t\t\t\tID,\n\t\t\t\t{ and: [{ eq: [\"conversation_id\", ANY] }, { lte: [\"id\", ANY] }] },\n\t\t\t\tDESCENDING,\n\t\t\t),\n\t\t},\n\t\ttasks: new TableQueries(tables.tasks),\n\t\tsubmissions: new TableQueries(tables.submissions),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Context, JsonValue } from "@earendil-works/chord";
|
|
2
|
+
import { type Store } from "@earendil-works/chord/next";
|
|
3
|
+
import type { CommitResult, ConversationId, ConversationQuery, ConversationRecord, Cursor, DocumentAddress, DocumentId, DocumentPoint, DocumentQuery, DocumentRecord, EntryId, EntryQuery, EntryRecord, Id, Page, Seq, Storage, StorageWrite, StoredDocument, SubmissionId, SubmissionQuery, SubmissionRecord, TaskId, TaskQuery, TaskRecord } from "../../types.ts";
|
|
4
|
+
type StoredTask = TaskRecord<JsonValue, JsonValue, JsonValue>;
|
|
5
|
+
type StoredEntry = {
|
|
6
|
+
readonly entry: EntryRecord;
|
|
7
|
+
readonly commitSeq: Seq;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Durable's `Storage` on Chord's store (slice 6b D1, D2, §2): its records as declared tables under an owner name, its
|
|
11
|
+
* documents in the store's documents engine, its ids from the store's one allocator, each commit call as one series
|
|
12
|
+
* of the store's commit queue (one backend transaction, one seq per batch), published in the host's batch. Several may share a store under distinct owner names
|
|
13
|
+
* (claims); the app owns the store, and `close` leaves it open.
|
|
14
|
+
*/
|
|
15
|
+
export declare class StoreStorage implements Storage {
|
|
16
|
+
#private;
|
|
17
|
+
private constructor();
|
|
18
|
+
/**
|
|
19
|
+
* Opens Durable's storage in `store` under `owner` (a lowercase `[a-z0-9@/._~-]` name): claims its tables and its
|
|
20
|
+
* document namespaces (a second claim is refused `invalid/args`), creates or migrates its tables, and raises the
|
|
21
|
+
* store's allocator above every id it holds (one index read). A failure releases what it claimed.
|
|
22
|
+
*/
|
|
23
|
+
static open(store: Store, owner: string, context: Context): Promise<StoreStorage>;
|
|
24
|
+
/**
|
|
25
|
+
* Commit `batches` as one series of the store (`ChordStore.commitSeries`): one backend transaction, one seq per
|
|
26
|
+
* batch, each batch's checks (at staging, then against the state the earlier ones left) before its writes.
|
|
27
|
+
*/
|
|
28
|
+
commit(batches: readonly (readonly StorageWrite[])[], receive: (index: number, seq: Seq) => void, context: Context): Promise<CommitResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Run a commit's publication in the attached host's batch (`batch.within`: it joins a running batch, opens one, or
|
|
31
|
+
* runs next while one sends): Chord sources over Durable's views are marked in one batch per commit. Plainly without
|
|
32
|
+
* a host.
|
|
33
|
+
*/
|
|
34
|
+
publishing(publish: () => void, context: Context): void;
|
|
35
|
+
mintId<I extends Id<string>>(): Promise<I>;
|
|
36
|
+
conversation(id: ConversationId, context: Context): Promise<ConversationRecord | undefined>;
|
|
37
|
+
scanConversations(query: ConversationQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<ConversationRecord, Cursor>>;
|
|
38
|
+
entry(id: EntryId, context: Context): Promise<StoredEntry | undefined>;
|
|
39
|
+
entry(conversationId: ConversationId, id: EntryId, context: Context): Promise<StoredEntry | undefined>;
|
|
40
|
+
findLatestHeadMarker(conversationId: ConversationId, atOrBeforeEntryId: EntryId | undefined, context: Context): Promise<(EntryRecord & {
|
|
41
|
+
readonly head: EntryId;
|
|
42
|
+
}) | undefined>;
|
|
43
|
+
scanEntries(query: EntryQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<EntryRecord, Cursor>>;
|
|
44
|
+
task(id: TaskId, context: Context): Promise<StoredTask | undefined>;
|
|
45
|
+
scanTasks(query: TaskQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<StoredTask, Cursor>>;
|
|
46
|
+
submission(id: SubmissionId, context: Context): Promise<SubmissionRecord | undefined>;
|
|
47
|
+
scanSubmissions(query: SubmissionQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<SubmissionRecord, Cursor>>;
|
|
48
|
+
submissionByRequest(conversationId: ConversationId, requestId: string, context: Context): Promise<SubmissionRecord | undefined>;
|
|
49
|
+
findDocument(address: DocumentAddress, at: DocumentPoint, context: Context): Promise<DocumentRecord | undefined>;
|
|
50
|
+
document(id: DocumentId, at: DocumentPoint, context: Context): Promise<StoredDocument | undefined>;
|
|
51
|
+
scanDocuments(query: DocumentQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<DocumentRecord, Cursor>>;
|
|
52
|
+
/**
|
|
53
|
+
* Refuses new calls, waits for its commits underway, then releases its claims; the store stays open (the app's),
|
|
54
|
+
* and finishes the reads underway, which change nothing.
|
|
55
|
+
*/
|
|
56
|
+
close(_context: Context): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
59
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/storage/store/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
// Durable's `Storage` on Chord's store (slice 6b D1, D2, §2): opening under an owner name (claims, tables, the
|
|
2
|
+
// allocator), commits as one composite write of the store's commit step, and the reads, each one guarded read of the
|
|
3
|
+
// store (several queries at one state in a read transaction), its request errors in a tagged result told after it.
|
|
4
|
+
import { findDocumentEntry, listDocumentEntries, StoreOwnerTables, } from "@earendil-works/chord/next";
|
|
5
|
+
import { StorageRequestError } from "../../errors.js";
|
|
6
|
+
import { idFromNumber, seqFromNumber } from "../../ids.js";
|
|
7
|
+
import { nextCursor, scanStart } from "../scan.js";
|
|
8
|
+
import { DurableCommit } from "./commit.js";
|
|
9
|
+
import { addressOf, namespaceOf, readAt, recordOfEntry, SCOPE_NAMESPACES } from "./documents.js";
|
|
10
|
+
import { storageQueries } from "./queries.js";
|
|
11
|
+
import { Conversations, Entries, EntryHeads, encodeIndexed, RecordIds, Submissions, Tasks } from "./tables.js";
|
|
12
|
+
/** The id condition of a table scan from `start`. */
|
|
13
|
+
function scanFrom(start) {
|
|
14
|
+
return start.order === "ascending"
|
|
15
|
+
? ["id", "gt", start.after ?? -1]
|
|
16
|
+
: ["id", "lt", start.after ?? Number.MAX_SAFE_INTEGER];
|
|
17
|
+
}
|
|
18
|
+
function page(values, limit, order) {
|
|
19
|
+
const items = values.slice(0, limit);
|
|
20
|
+
if (values.length <= limit)
|
|
21
|
+
return { items };
|
|
22
|
+
return { items, next: nextCursor(items.at(-1).id, order) };
|
|
23
|
+
}
|
|
24
|
+
function cursorId(cursor) {
|
|
25
|
+
const after = cursor?.after;
|
|
26
|
+
if (after === undefined)
|
|
27
|
+
return undefined;
|
|
28
|
+
if (typeof after !== "number" || !Number.isSafeInteger(after))
|
|
29
|
+
throw new StorageRequestError("Invalid storage cursor");
|
|
30
|
+
return after;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Durable's `Storage` on Chord's store (slice 6b D1, D2, §2): its records as declared tables under an owner name, its
|
|
34
|
+
* documents in the store's documents engine, its ids from the store's one allocator, each commit call as one series
|
|
35
|
+
* of the store's commit queue (one backend transaction, one seq per batch), published in the host's batch. Several may share a store under distinct owner names
|
|
36
|
+
* (claims); the app owns the store, and `close` leaves it open.
|
|
37
|
+
*/
|
|
38
|
+
export class StoreStorage {
|
|
39
|
+
#store;
|
|
40
|
+
#owner;
|
|
41
|
+
#claimant;
|
|
42
|
+
#tables;
|
|
43
|
+
#queries;
|
|
44
|
+
#closed = false;
|
|
45
|
+
#closing;
|
|
46
|
+
/** Calls underway, which `close` waits for before it releases the claims (`#drained` once they reach 0). */
|
|
47
|
+
#underway = 0;
|
|
48
|
+
#drained;
|
|
49
|
+
constructor(store, owner, claimant, tables) {
|
|
50
|
+
this.#store = store;
|
|
51
|
+
this.#owner = owner;
|
|
52
|
+
this.#claimant = claimant;
|
|
53
|
+
this.#tables = tables;
|
|
54
|
+
this.#queries = storageQueries(tables);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Opens Durable's storage in `store` under `owner` (a lowercase `[a-z0-9@/._~-]` name): claims its tables and its
|
|
58
|
+
* document namespaces (a second claim is refused `invalid/args`), creates or migrates its tables, and raises the
|
|
59
|
+
* store's allocator above every id it holds (one index read). A failure releases what it claimed.
|
|
60
|
+
*/
|
|
61
|
+
static async open(store, owner, context) {
|
|
62
|
+
// Chord's own: `openStore` or `importStore` made it.
|
|
63
|
+
const chord = store;
|
|
64
|
+
const claimant = { label: `StoreStorage(${owner})` };
|
|
65
|
+
try {
|
|
66
|
+
const declared = new StoreOwnerTables(chord, owner, claimant);
|
|
67
|
+
const tables = {
|
|
68
|
+
recordIds: declared.declare(RecordIds),
|
|
69
|
+
conversations: declared.declare(Conversations),
|
|
70
|
+
entries: declared.declare(Entries),
|
|
71
|
+
entryHeads: declared.declare(EntryHeads),
|
|
72
|
+
tasks: declared.declare(Tasks),
|
|
73
|
+
submissions: declared.declare(Submissions),
|
|
74
|
+
};
|
|
75
|
+
for (const namespace of SCOPE_NAMESPACES)
|
|
76
|
+
chord.claims.documents(owner, namespace, claimant);
|
|
77
|
+
await declared.prepare(context);
|
|
78
|
+
const highest = await chord.read((executor) => executor.get(`SELECT max("id") AS id FROM "${tables.recordIds.sql.table}"`), context);
|
|
79
|
+
if (highest?.id != null)
|
|
80
|
+
chord.ids.raise(highest.id);
|
|
81
|
+
return new StoreStorage(chord, owner, claimant, tables);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
chord.claims.release(claimant);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Commit `batches` as one series of the store (`ChordStore.commitSeries`): one backend transaction, one seq per
|
|
90
|
+
* batch, each batch's checks (at staging, then against the state the earlier ones left) before its writes.
|
|
91
|
+
*/
|
|
92
|
+
commit(batches, receive, context) {
|
|
93
|
+
return this.#call(() => {
|
|
94
|
+
const store = this.#store;
|
|
95
|
+
const raise = (id) => store.ids.raise(id);
|
|
96
|
+
const commits = [];
|
|
97
|
+
for (const writes of batches) {
|
|
98
|
+
try {
|
|
99
|
+
commits.push(new DurableCommit(writes, this.#owner, this.#tables, raise, context));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
// Staging runs Durable's checks of the batch itself: a refusal in its turn, the batches before it stored.
|
|
103
|
+
commits.push({ check: () => Promise.reject(error), writes: [] });
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return store.commitSeries(commits, (index, seq) => receive(index, seqFromNumber(seq)), context);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Run a commit's publication in the attached host's batch (`batch.within`: it joins a running batch, opens one, or
|
|
112
|
+
* runs next while one sends): Chord sources over Durable's views are marked in one batch per commit. Plainly without
|
|
113
|
+
* a host.
|
|
114
|
+
*/
|
|
115
|
+
publishing(publish, context) {
|
|
116
|
+
const batch = this.#store.batch;
|
|
117
|
+
if (batch === undefined)
|
|
118
|
+
publish();
|
|
119
|
+
else
|
|
120
|
+
batch.within(publish, context);
|
|
121
|
+
}
|
|
122
|
+
mintId() {
|
|
123
|
+
return this.#call(async () => idFromNumber(this.#store.mintId()));
|
|
124
|
+
}
|
|
125
|
+
conversation(id, context) {
|
|
126
|
+
return this.#read((executor) => this.#conversation(executor, id, context), context);
|
|
127
|
+
}
|
|
128
|
+
scanConversations(query, limit, cursor, context) {
|
|
129
|
+
return this.#reading(() => {
|
|
130
|
+
const start = scanStart(query.order, cursor, "ascending");
|
|
131
|
+
const conditions = [scanFrom(start)];
|
|
132
|
+
if (query.ownerConversationId !== undefined) {
|
|
133
|
+
conditions.push(["owner_conversation_id", "eq", query.ownerConversationId]);
|
|
134
|
+
}
|
|
135
|
+
if (query.ownerTaskId !== undefined)
|
|
136
|
+
conditions.push(["owner_task_id", "eq", query.ownerTaskId]);
|
|
137
|
+
return this.#scan(this.#queries.conversations, conditions, start, limit, context);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
entry(idOrConversationId, idOrContext, maybeContext) {
|
|
141
|
+
if (maybeContext === undefined) {
|
|
142
|
+
const context = idOrContext;
|
|
143
|
+
return this.#read((executor) => this.#entry(executor, idOrConversationId, context), context);
|
|
144
|
+
}
|
|
145
|
+
const context = maybeContext;
|
|
146
|
+
const id = typeof idOrContext === "number" ? idFromNumber(idOrContext) : undefined;
|
|
147
|
+
return this.#reading(() => {
|
|
148
|
+
if (id === undefined)
|
|
149
|
+
throw new StorageRequestError("Storage.entry() requires an entry ID");
|
|
150
|
+
const conversationId = idOrConversationId;
|
|
151
|
+
return this.#store
|
|
152
|
+
.read((executor) => this.#visibleEntry(executor, conversationId, id, context), context)
|
|
153
|
+
.then(told);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
findLatestHeadMarker(conversationId, atOrBeforeEntryId, context) {
|
|
157
|
+
return this.#reading(() => this.#store
|
|
158
|
+
.read((executor) => this.#latestHeadMarker(executor, conversationId, atOrBeforeEntryId, context), context)
|
|
159
|
+
.then(told));
|
|
160
|
+
}
|
|
161
|
+
scanEntries(query, limit, cursor, context) {
|
|
162
|
+
return this.#reading(() => {
|
|
163
|
+
const { order, after } = scanStart(query.order, cursor, "descending");
|
|
164
|
+
const read = order === "ascending"
|
|
165
|
+
? (executor) => this.#entriesAscending(executor, query, limit, after, context)
|
|
166
|
+
: (executor) => this.#entriesDescending(executor, query, limit, after, context);
|
|
167
|
+
return this.#store.read(read, context).then(told);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
task(id, context) {
|
|
171
|
+
const tasks = this.#queries.tasks;
|
|
172
|
+
return this.#read(async (executor) => {
|
|
173
|
+
const row = await tasks.byId(executor, id);
|
|
174
|
+
return row === undefined ? undefined : tasks.record(row, context);
|
|
175
|
+
}, context);
|
|
176
|
+
}
|
|
177
|
+
scanTasks(query, limit, cursor, context) {
|
|
178
|
+
return this.#reading(() => {
|
|
179
|
+
const start = scanStart(query.order, cursor, "ascending");
|
|
180
|
+
const conditions = [scanFrom(start)];
|
|
181
|
+
if (query.conversationId !== undefined)
|
|
182
|
+
conditions.push(["conversation_id", "eq", query.conversationId]);
|
|
183
|
+
if (query.kind !== undefined)
|
|
184
|
+
conditions.push(["kind", "eq", encodeIndexed(query.kind)]);
|
|
185
|
+
if (query.status !== undefined)
|
|
186
|
+
conditions.push(["status", "eq", query.status]);
|
|
187
|
+
if (query.abortRequested !== undefined)
|
|
188
|
+
conditions.push(["abort_requested", "eq", query.abortRequested ? 1 : 0]);
|
|
189
|
+
if (query.background !== undefined)
|
|
190
|
+
conditions.push(["background", "eq", query.background ? 1 : 0]);
|
|
191
|
+
return this.#scan(this.#queries.tasks, conditions, start, limit, context);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
submission(id, context) {
|
|
195
|
+
const submissions = this.#queries.submissions;
|
|
196
|
+
return this.#read(async (executor) => {
|
|
197
|
+
const row = await submissions.byId(executor, id);
|
|
198
|
+
return row === undefined ? undefined : submissions.record(row, context);
|
|
199
|
+
}, context);
|
|
200
|
+
}
|
|
201
|
+
scanSubmissions(query, limit, cursor, context) {
|
|
202
|
+
return this.#reading(() => {
|
|
203
|
+
const start = scanStart(query.order, cursor, "ascending");
|
|
204
|
+
const conditions = [scanFrom(start)];
|
|
205
|
+
if (query.conversationId !== undefined)
|
|
206
|
+
conditions.push(["conversation_id", "eq", query.conversationId]);
|
|
207
|
+
if (query.status !== undefined)
|
|
208
|
+
conditions.push(["status", "eq", query.status]);
|
|
209
|
+
return this.#scan(this.#queries.submissions, conditions, start, limit, context);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
submissionByRequest(conversationId, requestId, context) {
|
|
213
|
+
const submissions = this.#queries.submissions;
|
|
214
|
+
const conditions = [
|
|
215
|
+
["conversation_id", "eq", conversationId],
|
|
216
|
+
["request_id", "eq", encodeIndexed(requestId)],
|
|
217
|
+
];
|
|
218
|
+
return this.#read(async (executor) => {
|
|
219
|
+
const row = await submissions.first(executor, conditions, "ascending");
|
|
220
|
+
return row === undefined ? undefined : submissions.record(row, context);
|
|
221
|
+
}, context);
|
|
222
|
+
}
|
|
223
|
+
findDocument(address, at, context) {
|
|
224
|
+
return this.#read(async (executor) => {
|
|
225
|
+
const entry = await findDocumentEntry(executor, addressOf(address, this.#owner), at);
|
|
226
|
+
return entry === undefined ? undefined : recordOfEntry(entry);
|
|
227
|
+
}, context);
|
|
228
|
+
}
|
|
229
|
+
document(id, at, context) {
|
|
230
|
+
// The record and its revisions must come from one committed state (S4).
|
|
231
|
+
return this.#reading(() => this.#store.readTransaction((executor) => readAt(executor, id, at, this.#owner), context).then(documentOf));
|
|
232
|
+
}
|
|
233
|
+
scanDocuments(query, limit, cursor, context) {
|
|
234
|
+
return this.#reading(() => {
|
|
235
|
+
const after = cursorId(cursor) ?? -1;
|
|
236
|
+
const scope = {
|
|
237
|
+
owner: this.#owner,
|
|
238
|
+
namespace: namespaceOf(query.scope),
|
|
239
|
+
...(query.kind === undefined ? {} : { kind: encodeIndexed(query.kind) }),
|
|
240
|
+
};
|
|
241
|
+
return this.#store
|
|
242
|
+
.read((executor) => listDocumentEntries(executor, scope, query.at, after, limit + 1), context)
|
|
243
|
+
.then((found) => page(found.map(recordOfEntry), limit, "ascending"));
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Refuses new calls, waits for its commits underway, then releases its claims; the store stays open (the app's),
|
|
248
|
+
* and finishes the reads underway, which change nothing.
|
|
249
|
+
*/
|
|
250
|
+
close(_context) {
|
|
251
|
+
if (this.#closing === undefined) {
|
|
252
|
+
this.#closed = true;
|
|
253
|
+
this.#closing = (async () => {
|
|
254
|
+
if (this.#underway > 0) {
|
|
255
|
+
await new Promise((resolve) => {
|
|
256
|
+
this.#drained = resolve;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
this.#store.claims.release(this.#claimant);
|
|
260
|
+
})();
|
|
261
|
+
}
|
|
262
|
+
return this.#closing;
|
|
263
|
+
}
|
|
264
|
+
/** Runs one commit or mint of this storage: refused once it closed, counted until it settles (`close` waits). */
|
|
265
|
+
async #call(run) {
|
|
266
|
+
if (this.#closed)
|
|
267
|
+
throw new Error("StoreStorage is closed");
|
|
268
|
+
this.#underway++;
|
|
269
|
+
try {
|
|
270
|
+
return await run();
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
if (--this.#underway === 0)
|
|
274
|
+
this.#drained?.();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* A call that is one guarded read of the store: refused once closed; not waited for by `close` (a read changes
|
|
279
|
+
* nothing, and the store, which stays open, finishes it).
|
|
280
|
+
*/
|
|
281
|
+
#read(read, context) {
|
|
282
|
+
return this.#reading(() => this.#store.read(read, context));
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* A read call: `read` prepares its guarded read (a request error thrown then, before it) and returns it. Refused
|
|
286
|
+
* once closed; one promise per call besides the guard's (no async wrapper).
|
|
287
|
+
*/
|
|
288
|
+
#reading(read) {
|
|
289
|
+
try {
|
|
290
|
+
if (this.#closed)
|
|
291
|
+
throw new Error("StoreStorage is closed");
|
|
292
|
+
return read();
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
return Promise.reject(error);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async #conversation(executor, id, context) {
|
|
299
|
+
const conversations = this.#queries.conversations;
|
|
300
|
+
const row = await conversations.byId(executor, id);
|
|
301
|
+
return row === undefined ? undefined : conversations.record(row, context);
|
|
302
|
+
}
|
|
303
|
+
#entry(executor, id, context) {
|
|
304
|
+
const entries = this.#queries.entries;
|
|
305
|
+
return entries.byId(executor, id).then((row) => {
|
|
306
|
+
if (row === undefined)
|
|
307
|
+
return undefined;
|
|
308
|
+
return { entry: entries.record(row, context), commitSeq: seqFromNumber(row.commit_seq) };
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
/** Entry `id` when visible through `conversationId`'s fork chain (`SqliteStorage.readEntry`). */
|
|
312
|
+
async #visibleEntry(executor, conversationId, id, context) {
|
|
313
|
+
let conversation = await this.#conversation(executor, conversationId, context);
|
|
314
|
+
if (conversation === undefined)
|
|
315
|
+
return unknownConversation(conversationId);
|
|
316
|
+
const stored = await this.#entry(executor, id, context);
|
|
317
|
+
if (stored === undefined)
|
|
318
|
+
return NOTHING_FOUND;
|
|
319
|
+
let upper = Number.POSITIVE_INFINITY;
|
|
320
|
+
while (conversation.id !== stored.entry.conversationId) {
|
|
321
|
+
if (conversation.parent === undefined)
|
|
322
|
+
return NOTHING_FOUND;
|
|
323
|
+
upper = Math.min(upper, conversation.parent.at);
|
|
324
|
+
conversation = (await this.#conversation(executor, conversation.parent.conversationId, context));
|
|
325
|
+
}
|
|
326
|
+
return stored.entry.id > upper ? NOTHING_FOUND : { kind: "found", value: stored };
|
|
327
|
+
}
|
|
328
|
+
async #latestHeadMarker(executor, conversationId, atOrBeforeEntryId, context) {
|
|
329
|
+
let conversation = await this.#conversation(executor, conversationId, context);
|
|
330
|
+
if (conversation === undefined)
|
|
331
|
+
return unknownConversation(conversationId);
|
|
332
|
+
let upper = atOrBeforeEntryId;
|
|
333
|
+
while (true) {
|
|
334
|
+
const { latestHead } = this.#queries;
|
|
335
|
+
const head = upper === undefined
|
|
336
|
+
? await executor.get(latestHead.any, conversation.id)
|
|
337
|
+
: await executor.get(latestHead.atOrBefore, conversation.id, upper);
|
|
338
|
+
if (head !== undefined) {
|
|
339
|
+
// Written with its entry, in the same commit.
|
|
340
|
+
const row = (await this.#queries.entries.byId(executor, head.id));
|
|
341
|
+
const value = this.#queries.entries.record(row, context);
|
|
342
|
+
return { kind: "found", value };
|
|
343
|
+
}
|
|
344
|
+
if (conversation.parent === undefined)
|
|
345
|
+
return NOTHING_FOUND;
|
|
346
|
+
upper = upper === undefined ? conversation.parent.at : Math.min(upper, conversation.parent.at);
|
|
347
|
+
conversation = (await this.#conversation(executor, conversation.parent.conversationId, context));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/** Newest first: each conversation of the fork chain up to its fork point (`SqliteStorage.readEntries`). */
|
|
351
|
+
async #entriesDescending(executor, query, limit, after, context) {
|
|
352
|
+
let conversation = await this.#conversation(executor, query.conversationId, context);
|
|
353
|
+
if (conversation === undefined)
|
|
354
|
+
return unknownConversation(query.conversationId);
|
|
355
|
+
let upper = query.maxEntryId;
|
|
356
|
+
if (after !== undefined)
|
|
357
|
+
upper = Math.min(upper ?? Number.MAX_SAFE_INTEGER, after - 1);
|
|
358
|
+
const values = [];
|
|
359
|
+
while (true) {
|
|
360
|
+
const range = this.#range(conversation.id, query.minEntryId, upper);
|
|
361
|
+
const rows = await this.#queries.entries.select(executor, range, "descending", limit + 1 - values.length);
|
|
362
|
+
this.#queries.entries.records(rows, values, context);
|
|
363
|
+
if (values.length > limit || conversation.parent === undefined)
|
|
364
|
+
break;
|
|
365
|
+
upper = upper === undefined ? conversation.parent.at : Math.min(upper, conversation.parent.at);
|
|
366
|
+
if (query.minEntryId !== undefined && upper < query.minEntryId)
|
|
367
|
+
break;
|
|
368
|
+
conversation = (await this.#conversation(executor, conversation.parent.conversationId, context));
|
|
369
|
+
}
|
|
370
|
+
return { kind: "found", value: page(values, limit, "descending") };
|
|
371
|
+
}
|
|
372
|
+
/** Oldest first: the fork chain's segments from the root forward, each up to its fork point. */
|
|
373
|
+
async #entriesAscending(executor, query, limit, after, context) {
|
|
374
|
+
const segments = [];
|
|
375
|
+
let conversation = await this.#conversation(executor, query.conversationId, context);
|
|
376
|
+
if (conversation === undefined)
|
|
377
|
+
return unknownConversation(query.conversationId);
|
|
378
|
+
let upper = query.maxEntryId;
|
|
379
|
+
while (true) {
|
|
380
|
+
segments.push({ conversationId: conversation.id, upper });
|
|
381
|
+
if (conversation.parent === undefined)
|
|
382
|
+
break;
|
|
383
|
+
upper = upper === undefined ? conversation.parent.at : Math.min(upper, conversation.parent.at);
|
|
384
|
+
if (query.minEntryId !== undefined && upper < query.minEntryId)
|
|
385
|
+
break;
|
|
386
|
+
conversation = (await this.#conversation(executor, conversation.parent.conversationId, context));
|
|
387
|
+
}
|
|
388
|
+
let lower = query.minEntryId;
|
|
389
|
+
if (after !== undefined)
|
|
390
|
+
lower = Math.max(lower ?? after + 1, after + 1);
|
|
391
|
+
const values = [];
|
|
392
|
+
for (const segment of segments.reverse()) {
|
|
393
|
+
const range = this.#range(segment.conversationId, lower, segment.upper);
|
|
394
|
+
const rows = await this.#queries.entries.select(executor, range, "ascending", limit + 1 - values.length);
|
|
395
|
+
this.#queries.entries.records(rows, values, context);
|
|
396
|
+
if (values.length > limit)
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
return { kind: "found", value: page(values, limit, "ascending") };
|
|
400
|
+
}
|
|
401
|
+
/** One conversation's entries with ids from `lower` to `upper` (inclusive, each optional). */
|
|
402
|
+
#range(conversationId, lower, upper) {
|
|
403
|
+
const conditions = [["conversation_id", "eq", conversationId]];
|
|
404
|
+
if (lower !== undefined)
|
|
405
|
+
conditions.push(["id", "gte", lower]);
|
|
406
|
+
if (upper !== undefined)
|
|
407
|
+
conditions.push(["id", "lte", upper]);
|
|
408
|
+
return conditions;
|
|
409
|
+
}
|
|
410
|
+
/** A page of a table scan: up to `limit` records of `queries`' table whose `conditions` hold, from `start`. */
|
|
411
|
+
#scan(queries, conditions, start, limit, context) {
|
|
412
|
+
return this.#store
|
|
413
|
+
.read((executor) => queries.select(executor, conditions, start.order, limit + 1), context)
|
|
414
|
+
.then((rows) => {
|
|
415
|
+
const values = [];
|
|
416
|
+
queries.records(rows, values, context);
|
|
417
|
+
return page(values, limit, start.order);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/** A read that found nothing. */
|
|
422
|
+
const NOTHING_FOUND = { kind: "found", value: undefined };
|
|
423
|
+
function unknownConversation(id) {
|
|
424
|
+
return { kind: "refused", message: `Unknown conversation: ${id}` };
|
|
425
|
+
}
|
|
426
|
+
/** A read's data, its request error thrown now that the guarded read returned (slice 6b §1.4). */
|
|
427
|
+
function told(read) {
|
|
428
|
+
if (read.kind === "refused")
|
|
429
|
+
throw new StorageRequestError(read.message);
|
|
430
|
+
return read.value;
|
|
431
|
+
}
|
|
432
|
+
/** A document read's result, a record that keeps no history at the point thrown as Durable's request error. */
|
|
433
|
+
function documentOf(read) {
|
|
434
|
+
if (read.kind === "current_only")
|
|
435
|
+
throw new StorageRequestError(`Document ${read.id} does not retain historical content`);
|
|
436
|
+
return read.document;
|
|
437
|
+
}
|
|
438
|
+
//# sourceMappingURL=storage.js.map
|