@earendil-works/pi-durable 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +204 -0
- package/README.md +195 -85
- package/dist/codemode/codemode.d.ts +6 -5
- package/dist/codemode/codemode.d.ts.map +1 -1
- package/dist/codemode/codemode.js +10 -49
- package/dist/codemode/codemode.js.map +1 -1
- package/dist/codemode/models.d.ts +2 -1
- package/dist/codemode/models.d.ts.map +1 -1
- package/dist/codemode/models.js +4 -4
- package/dist/codemode/models.js.map +1 -1
- package/dist/documents.d.ts +1 -1
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +1 -1
- package/dist/documents.js.map +1 -1
- package/dist/entries.d.ts +1 -1
- package/dist/entries.d.ts.map +1 -1
- package/dist/entries.js.map +1 -1
- package/dist/env/index.d.ts +13 -11
- package/dist/env/index.d.ts.map +1 -1
- package/dist/env/index.js.map +1 -1
- package/dist/env/node-watch.d.ts +4 -4
- package/dist/env/node-watch.d.ts.map +1 -1
- package/dist/env/node-watch.js +18 -16
- package/dist/env/node-watch.js.map +1 -1
- package/dist/env/node.d.ts +3 -3
- package/dist/env/node.d.ts.map +1 -1
- package/dist/env/node.js +1 -1
- package/dist/env/node.js.map +1 -1
- package/dist/errors.d.ts +18 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +26 -3
- package/dist/errors.js.map +1 -1
- package/dist/harness/agent.d.ts.map +1 -1
- package/dist/harness/agent.js +37 -3
- package/dist/harness/agent.js.map +1 -1
- package/dist/harness/compaction.d.ts +8 -3
- package/dist/harness/compaction.d.ts.map +1 -1
- package/dist/harness/compaction.js +40 -11
- package/dist/harness/compaction.js.map +1 -1
- package/dist/harness/context.d.ts +24 -8
- package/dist/harness/context.d.ts.map +1 -1
- package/dist/harness/context.js +71 -25
- package/dist/harness/context.js.map +1 -1
- package/dist/harness/define.d.ts +8 -4
- package/dist/harness/define.d.ts.map +1 -1
- package/dist/harness/define.js +4 -0
- package/dist/harness/define.js.map +1 -1
- package/dist/harness/generation.d.ts +39 -6
- package/dist/harness/generation.d.ts.map +1 -1
- package/dist/harness/generation.js +451 -100
- package/dist/harness/generation.js.map +1 -1
- package/dist/harness/harness.d.ts +3 -1
- package/dist/harness/harness.d.ts.map +1 -1
- package/dist/harness/harness.js +87 -62
- package/dist/harness/harness.js.map +1 -1
- package/dist/harness/inbox.d.ts +7 -2
- package/dist/harness/inbox.d.ts.map +1 -1
- package/dist/harness/inbox.js +5 -2
- package/dist/harness/inbox.js.map +1 -1
- package/dist/harness/json.d.ts +21 -5
- package/dist/harness/json.d.ts.map +1 -1
- package/dist/harness/json.js +23 -5
- package/dist/harness/json.js.map +1 -1
- package/dist/harness/live.d.ts +124 -17
- package/dist/harness/live.d.ts.map +1 -1
- package/dist/harness/live.js +93 -12
- package/dist/harness/live.js.map +1 -1
- package/dist/harness/partial-changes.d.ts +54 -0
- package/dist/harness/partial-changes.d.ts.map +1 -0
- package/dist/harness/partial-changes.js +163 -0
- package/dist/harness/partial-changes.js.map +1 -0
- package/dist/harness/prompt.d.ts +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/provider.d.ts +1 -1
- package/dist/harness/provider.d.ts.map +1 -1
- package/dist/harness/provider.js.map +1 -1
- package/dist/harness/registry-reader.d.ts +11 -0
- package/dist/harness/registry-reader.d.ts.map +1 -0
- package/dist/harness/registry-reader.js +25 -0
- package/dist/harness/registry-reader.js.map +1 -0
- package/dist/harness/scheduler.d.ts +48 -20
- package/dist/harness/scheduler.d.ts.map +1 -1
- package/dist/harness/scheduler.js +1006 -380
- package/dist/harness/scheduler.js.map +1 -1
- package/dist/harness/submissions.d.ts +18 -11
- package/dist/harness/submissions.d.ts.map +1 -1
- package/dist/harness/submissions.js +63 -26
- package/dist/harness/submissions.js.map +1 -1
- package/dist/harness/task-graph.d.ts +14 -9
- package/dist/harness/task-graph.d.ts.map +1 -1
- package/dist/harness/task-graph.js +38 -28
- package/dist/harness/task-graph.js.map +1 -1
- package/dist/harness/telemetry.d.ts +11 -11
- package/dist/harness/telemetry.d.ts.map +1 -1
- package/dist/harness/telemetry.js +7 -7
- package/dist/harness/telemetry.js.map +1 -1
- package/dist/harness/tool.d.ts +3 -1
- package/dist/harness/tool.d.ts.map +1 -1
- package/dist/harness/tool.js +172 -56
- package/dist/harness/tool.js.map +1 -1
- package/dist/harness/transcript.d.ts +72 -0
- package/dist/harness/transcript.d.ts.map +1 -0
- package/dist/harness/transcript.js +264 -0
- package/dist/harness/transcript.js.map +1 -0
- package/dist/harness/types.d.ts +237 -61
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/harness/usage.d.ts +2 -1
- package/dist/harness/usage.d.ts.map +1 -1
- package/dist/harness/usage.js +1 -1
- package/dist/harness/usage.js.map +1 -1
- package/dist/harness/util.d.ts +4 -2
- package/dist/harness/util.d.ts.map +1 -1
- package/dist/harness/util.js +9 -0
- package/dist/harness/util.js.map +1 -1
- package/dist/harness/view.d.ts +44 -22
- package/dist/harness/view.d.ts.map +1 -1
- package/dist/harness/view.js +130 -71
- package/dist/harness/view.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/search.d.ts +19 -3
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +107 -44
- package/dist/search.js.map +1 -1
- package/dist/session/cached-storage.d.ts +72 -0
- package/dist/session/cached-storage.d.ts.map +1 -0
- package/dist/session/cached-storage.js +269 -0
- package/dist/session/cached-storage.js.map +1 -0
- package/dist/session/conversation-tails.d.ts +33 -0
- package/dist/session/conversation-tails.d.ts.map +1 -0
- package/dist/session/conversation-tails.js +101 -0
- package/dist/session/conversation-tails.js.map +1 -0
- package/dist/session/forks.d.ts +1 -1
- package/dist/session/forks.d.ts.map +1 -1
- package/dist/session/forks.js.map +1 -1
- package/dist/session/keys.d.ts +69 -0
- package/dist/session/keys.d.ts.map +1 -0
- package/dist/session/keys.js +161 -0
- package/dist/session/keys.js.map +1 -0
- package/dist/session/session.d.ts +169 -26
- package/dist/session/session.d.ts.map +1 -1
- package/dist/session/session.js +1029 -171
- package/dist/session/session.js.map +1 -1
- package/dist/session/transaction.d.ts +84 -5
- package/dist/session/transaction.d.ts.map +1 -1
- package/dist/session/transaction.js +227 -40
- package/dist/session/transaction.js.map +1 -1
- package/dist/session/writer.d.ts +74 -0
- package/dist/session/writer.d.ts.map +1 -0
- package/dist/session/writer.js +294 -0
- package/dist/session/writer.js.map +1 -0
- package/dist/storage/indexeddb.d.ts +12 -3
- package/dist/storage/indexeddb.d.ts.map +1 -1
- package/dist/storage/indexeddb.js +91 -41
- package/dist/storage/indexeddb.js.map +1 -1
- package/dist/storage/jsonl/index.d.ts +1 -1
- package/dist/storage/jsonl/index.d.ts.map +1 -1
- package/dist/storage/jsonl/index.js +1 -1
- package/dist/storage/jsonl/index.js.map +1 -1
- package/dist/storage/jsonl/node.d.ts +3 -3
- package/dist/storage/jsonl/node.d.ts.map +1 -1
- package/dist/storage/jsonl/node.js +2 -2
- package/dist/storage/jsonl/node.js.map +1 -1
- package/dist/storage/jsonl/storage.d.ts +46 -5
- package/dist/storage/jsonl/storage.d.ts.map +1 -1
- package/dist/storage/jsonl/storage.js +158 -43
- package/dist/storage/jsonl/storage.js.map +1 -1
- package/dist/storage/memory.d.ts +9 -3
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +28 -2
- package/dist/storage/memory.js.map +1 -1
- package/dist/storage/sqlite/commit.d.ts +52 -0
- package/dist/storage/sqlite/commit.d.ts.map +1 -0
- package/dist/storage/sqlite/commit.js +371 -0
- package/dist/storage/sqlite/commit.js.map +1 -0
- package/dist/storage/sqlite/rows.d.ts +33 -0
- package/dist/storage/sqlite/rows.d.ts.map +1 -0
- package/dist/storage/sqlite/rows.js +53 -0
- package/dist/storage/sqlite/rows.js.map +1 -0
- package/dist/storage/sqlite/statements.d.ts +13 -0
- package/dist/storage/sqlite/statements.d.ts.map +1 -0
- package/dist/storage/sqlite/statements.js +88 -0
- package/dist/storage/sqlite/statements.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +23 -10
- package/dist/storage/sqlite/storage.d.ts.map +1 -1
- package/dist/storage/sqlite/storage.js +120 -261
- package/dist/storage/sqlite/storage.js.map +1 -1
- package/dist/storage/store/commit.d.ts +33 -0
- package/dist/storage/store/commit.d.ts.map +1 -0
- package/dist/storage/store/commit.js +286 -0
- package/dist/storage/store/commit.js.map +1 -0
- package/dist/storage/store/documents.d.ts +43 -0
- package/dist/storage/store/documents.d.ts.map +1 -0
- package/dist/storage/store/documents.js +102 -0
- package/dist/storage/store/documents.js.map +1 -0
- package/dist/storage/store/index.d.ts +2 -0
- package/dist/storage/store/index.d.ts.map +1 -0
- package/dist/storage/store/index.js +2 -0
- package/dist/storage/store/index.js.map +1 -0
- package/dist/storage/store/queries.d.ts +49 -0
- package/dist/storage/store/queries.d.ts.map +1 -0
- package/dist/storage/store/queries.js +93 -0
- package/dist/storage/store/queries.js.map +1 -0
- package/dist/storage/store/storage.d.ts +60 -0
- package/dist/storage/store/storage.d.ts.map +1 -0
- package/dist/storage/store/storage.js +438 -0
- package/dist/storage/store/storage.js.map +1 -0
- package/dist/storage/store/tables.d.ts +54 -0
- package/dist/storage/store/tables.d.ts.map +1 -0
- package/dist/storage/store/tables.js +108 -0
- package/dist/storage/store/tables.js.map +1 -0
- package/dist/testing/commit-writes.d.ts +5 -0
- package/dist/testing/commit-writes.d.ts.map +1 -0
- package/dist/testing/commit-writes.js +12 -0
- package/dist/testing/commit-writes.js.map +1 -0
- package/dist/testing/env-conformance.js +7 -7
- package/dist/testing/env-conformance.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/storage-benchmark.d.ts.map +1 -1
- package/dist/testing/storage-benchmark.js +18 -17
- package/dist/testing/storage-benchmark.js.map +1 -1
- package/dist/testing/storage-conformance.d.ts.map +1 -1
- package/dist/testing/storage-conformance.js +354 -69
- package/dist/testing/storage-conformance.js.map +1 -1
- package/dist/tool-search/tool-search.d.ts +8 -14
- package/dist/tool-search/tool-search.d.ts.map +1 -1
- package/dist/tool-search/tool-search.js +25 -13
- package/dist/tool-search/tool-search.js.map +1 -1
- package/dist/tools/bash.d.ts +13 -13
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js +16 -15
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +7 -7
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +9 -9
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/file-mutation-queue.d.ts +1 -1
- package/dist/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/tools/file-mutation-queue.js.map +1 -1
- package/dist/tools/index.d.ts +32 -32
- package/dist/tools/path-utils.d.ts +1 -1
- package/dist/tools/path-utils.d.ts.map +1 -1
- package/dist/tools/path-utils.js.map +1 -1
- package/dist/tools/read.d.ts +6 -6
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +19 -8
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +4 -4
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +5 -5
- package/dist/tools/write.js.map +1 -1
- package/dist/types.d.ts +228 -78
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +19 -12
- package/dist/harness/events.d.ts +0 -186
- package/dist/harness/events.d.ts.map +0 -1
- package/dist/harness/events.js +0 -373
- package/dist/harness/events.js.map +0 -1
- package/dist/session/observation.d.ts +0 -42
- package/dist/session/observation.d.ts.map +0 -1
- package/dist/session/observation.js +0 -281
- package/dist/session/observation.js.map +0 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { withoutAbortSignal } from "@earendil-works/chord/context";
|
|
2
|
+
import { SessionFailed } from "../errors.js";
|
|
3
|
+
const SESSION_FAILED = { kind: "failed" };
|
|
4
|
+
/**
|
|
5
|
+
* The writer of one Session. The line admits each commit as a part once its callback and settlement succeeded; the
|
|
6
|
+
* writer sends every part admitted while no call is in flight as one `commit()` call, off the line, and publishes
|
|
7
|
+
* the parts of a call one by one, in line order, with a macrotask between two, so the continuations of the waits one
|
|
8
|
+
* resolves start before the next is published. Held commits are parts without writes, published when the parts before
|
|
9
|
+
* them are.
|
|
10
|
+
*
|
|
11
|
+
* Each part registers the keys it writes; until it is published, it is the newest unpublished writer of each, and a
|
|
12
|
+
* job that touches one waits for its gate. A key leaves when its newest writer is published, so the map holds one entry
|
|
13
|
+
* per key with an unpublished writer.
|
|
14
|
+
*/
|
|
15
|
+
export class Writer {
|
|
16
|
+
#host;
|
|
17
|
+
/** Admitted parts not yet sent, in line order. */
|
|
18
|
+
#queue = [];
|
|
19
|
+
#flight = false;
|
|
20
|
+
/** Per key, the newest unpublished part that writes it. */
|
|
21
|
+
#unpublished = new Map();
|
|
22
|
+
/** The newest admitted part that is not yet published, whose gate opens after every earlier one's. */
|
|
23
|
+
#newest;
|
|
24
|
+
constructor(host) {
|
|
25
|
+
this.#host = host;
|
|
26
|
+
}
|
|
27
|
+
/** Admit a part: register its keys, queue it, and send it at once unless a call is in flight. */
|
|
28
|
+
admit(spec) {
|
|
29
|
+
const outcome = Promise.withResolvers();
|
|
30
|
+
outcome.promise.catch(() => { });
|
|
31
|
+
const part = {
|
|
32
|
+
...spec,
|
|
33
|
+
outcome: outcome.promise,
|
|
34
|
+
settle: {
|
|
35
|
+
resolve: () => {
|
|
36
|
+
part.done = true;
|
|
37
|
+
outcome.resolve();
|
|
38
|
+
},
|
|
39
|
+
reject: (error) => {
|
|
40
|
+
part.done = true;
|
|
41
|
+
outcome.reject(error);
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
done: false,
|
|
45
|
+
gate: Promise.withResolvers(),
|
|
46
|
+
};
|
|
47
|
+
for (const key of spec.keys)
|
|
48
|
+
this.#unpublished.set(key, part);
|
|
49
|
+
this.#newest = part;
|
|
50
|
+
this.#queue.push(part);
|
|
51
|
+
this.#pump();
|
|
52
|
+
return part;
|
|
53
|
+
}
|
|
54
|
+
/** The gate of the newest unpublished writer of `key`, or `undefined` when it has none. */
|
|
55
|
+
gate(key) {
|
|
56
|
+
return this.#unpublished.get(key)?.gate.promise;
|
|
57
|
+
}
|
|
58
|
+
/** Opens once every part admitted so far is published or never will be. */
|
|
59
|
+
drained() {
|
|
60
|
+
return this.#newest?.gate.promise;
|
|
61
|
+
}
|
|
62
|
+
/** Keys with an unpublished writer, for tests that bound memory. */
|
|
63
|
+
get unpublished() {
|
|
64
|
+
return this.#unpublished.size;
|
|
65
|
+
}
|
|
66
|
+
#pump() {
|
|
67
|
+
if (this.#flight || this.#queue.length === 0)
|
|
68
|
+
return;
|
|
69
|
+
this.#flight = true;
|
|
70
|
+
const parts = this.#queue;
|
|
71
|
+
this.#queue = [];
|
|
72
|
+
void this.#flush(parts);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Send and publish `parts`, then what was admitted meanwhile. Never rejects: a throw the writer did not expect fails
|
|
76
|
+
* the Session and settles the parts (`#abandon()`), so no outcome or gate is stranded.
|
|
77
|
+
*/
|
|
78
|
+
async #flush(parts) {
|
|
79
|
+
try {
|
|
80
|
+
await this.#send(parts);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.#abandon(parts, error);
|
|
84
|
+
}
|
|
85
|
+
this.#flight = false;
|
|
86
|
+
this.#pump();
|
|
87
|
+
}
|
|
88
|
+
/** Store `parts` in one call, then publish them in order; settles every one of them. */
|
|
89
|
+
async #send(parts) {
|
|
90
|
+
const stored = parts.filter((part) => part.writes !== undefined);
|
|
91
|
+
const seqs = [];
|
|
92
|
+
let result;
|
|
93
|
+
let rejected;
|
|
94
|
+
const failed = this.#host.failure();
|
|
95
|
+
if (failed !== undefined) {
|
|
96
|
+
for (const part of parts)
|
|
97
|
+
this.#unpublish(part, new SessionFailed(failed.error));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (stored.length > 0) {
|
|
101
|
+
try {
|
|
102
|
+
result = await this.#host.commit(stored.map((part) => part.writes), (index, seq) => {
|
|
103
|
+
seqs[index] = seq;
|
|
104
|
+
}, withoutAbortSignal(stored[0].context));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
rejected = { error };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// A call that settles once another failure failed the Session reports `SessionFailed` with that first error,
|
|
111
|
+
// whatever it returned (spec §10.1), and publishes nothing.
|
|
112
|
+
const failedMeanwhile = this.#host.failure();
|
|
113
|
+
if (failedMeanwhile !== undefined) {
|
|
114
|
+
for (const part of parts)
|
|
115
|
+
this.#unpublish(part, new SessionFailed(failedMeanwhile.error));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
await this.#publish(parts, seqs, result, rejected);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Publish the received parts and held ones in order, a macrotask apart, and settle every part (group commit §3.4). A
|
|
122
|
+
* refusal, a rejection, or a part whose adoption or own observer throws fails the Session once the parts before it
|
|
123
|
+
* are published and the host observers got the last one; the parts after it are not published, and reject with
|
|
124
|
+
* `SessionFailed`. What the failure decides (the outcomes, and the gates of the parts it concerns) is settled only
|
|
125
|
+
* once the Session failed, so no job waiting at such a gate, and no later outcome, goes on as if it had not.
|
|
126
|
+
*/
|
|
127
|
+
async #publish(parts, seqs, result, rejected) {
|
|
128
|
+
let stop;
|
|
129
|
+
/**
|
|
130
|
+
* Settled once the Session failed: each part, whether it was published, and its outcome: an error of its own (any
|
|
131
|
+
* value, `undefined` included), or `SessionFailed`.
|
|
132
|
+
*/
|
|
133
|
+
const failing = [];
|
|
134
|
+
let storedIndex = 0;
|
|
135
|
+
let published = 0;
|
|
136
|
+
for (const part of parts) {
|
|
137
|
+
const index = part.writes === undefined ? undefined : storedIndex++;
|
|
138
|
+
if (stop !== undefined) {
|
|
139
|
+
// A part after a rejected call's last receipt has the call's error; any other part after a stop was not sent,
|
|
140
|
+
// nor any part once something else failed the Session.
|
|
141
|
+
const outcome = rejected !== undefined && index !== undefined && stop.external !== true
|
|
142
|
+
? { kind: "own", error: rejected.error }
|
|
143
|
+
: SESSION_FAILED;
|
|
144
|
+
failing.push({ part, outcome, published: false });
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const seq = index === undefined ? undefined : seqs[index];
|
|
148
|
+
if (index !== undefined && seq === undefined) {
|
|
149
|
+
const error = rejected !== undefined
|
|
150
|
+
? rejected.error
|
|
151
|
+
: index === result?.stored && "refused" in result
|
|
152
|
+
? result.refused
|
|
153
|
+
: new Error("Storage committed without adopting the batch");
|
|
154
|
+
failing.push({ part, outcome: { kind: "own", error }, published: false });
|
|
155
|
+
stop = { error, at: part.index };
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
// A macrotask between two parts: what the first resolves runs before the second is published.
|
|
159
|
+
if (published++ > 0)
|
|
160
|
+
await hop();
|
|
161
|
+
let failure;
|
|
162
|
+
// Failed by something else meanwhile, during the hop or before a storage's scope runs the publication: nothing
|
|
163
|
+
// more of the call is published. Checked right before it would be, after every wait.
|
|
164
|
+
let external;
|
|
165
|
+
const publication = this.#host.publishing(() => {
|
|
166
|
+
external = this.#host.failure();
|
|
167
|
+
if (external !== undefined)
|
|
168
|
+
return;
|
|
169
|
+
// Its keys leave first, so what its publication schedules sees it published; its waiters wake after it.
|
|
170
|
+
this.#unregister(part);
|
|
171
|
+
// A throw anywhere in it, the read caches included, leaves memory behind what is stored: like an adoption's.
|
|
172
|
+
try {
|
|
173
|
+
if (part.writes !== undefined)
|
|
174
|
+
this.#host.stored(part.writes);
|
|
175
|
+
failure = part.publish(seq);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
failure = { kind: "adoption", error };
|
|
179
|
+
}
|
|
180
|
+
}, part.context);
|
|
181
|
+
if (publication !== undefined)
|
|
182
|
+
await publication;
|
|
183
|
+
if (external !== undefined) {
|
|
184
|
+
stop = { error: external.error, at: part.index, external: true };
|
|
185
|
+
failing.push({ part, outcome: SESSION_FAILED, published: false });
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (failure?.kind === "adoption") {
|
|
189
|
+
failing.push({ part, outcome: { kind: "own", error: failure.error }, published: true });
|
|
190
|
+
stop = { error: failure.error, at: part.index };
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// A part published while its call rejected is durable but rejects (spec §10.1); an own observer's throw is then
|
|
194
|
+
// reported, as the Session fails with the storage error.
|
|
195
|
+
if (rejected !== undefined) {
|
|
196
|
+
if (failure !== undefined)
|
|
197
|
+
this.#host.report(failure.error, part.context);
|
|
198
|
+
failing.push({ part, outcome: { kind: "own", error: rejected.error }, published: true });
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
// An own observer's throw: the commit stands; the Session fails after it.
|
|
202
|
+
if (failure !== undefined)
|
|
203
|
+
stop = { error: failure.error, at: part.index };
|
|
204
|
+
const failedMeanwhile = this.#host.failure();
|
|
205
|
+
if (failedMeanwhile !== undefined && failure === undefined) {
|
|
206
|
+
part.settle.reject(new SessionFailed(failedMeanwhile.error));
|
|
207
|
+
}
|
|
208
|
+
else
|
|
209
|
+
part.settle.resolve();
|
|
210
|
+
this.#open(part);
|
|
211
|
+
}
|
|
212
|
+
if (rejected !== undefined || stop !== undefined) {
|
|
213
|
+
// After the host dispatch of the last part published, which is one microtask away.
|
|
214
|
+
await Promise.resolve();
|
|
215
|
+
// Every part of the call that rejected keeps the storage error; the first of them is the failure's boundary.
|
|
216
|
+
const own = failing.flatMap(({ part, outcome }) => (outcome.kind === "own" ? [part.index] : []));
|
|
217
|
+
if (rejected !== undefined)
|
|
218
|
+
this.#host.fail(rejected.error, own[0], own);
|
|
219
|
+
else
|
|
220
|
+
this.#host.fail(stop.error, stop.at, own);
|
|
221
|
+
}
|
|
222
|
+
for (const { part, outcome, published: stored } of failing) {
|
|
223
|
+
const error = outcome.kind === "own" ? outcome.error : this.#failed(stop.error);
|
|
224
|
+
if (stored) {
|
|
225
|
+
part.settle.reject(error);
|
|
226
|
+
this.#open(part);
|
|
227
|
+
}
|
|
228
|
+
else
|
|
229
|
+
this.#unpublish(part, error);
|
|
230
|
+
}
|
|
231
|
+
// Admitted while this call ran, after the failure: never sent.
|
|
232
|
+
const failure = this.#host.failure();
|
|
233
|
+
if (failure !== undefined) {
|
|
234
|
+
const queued = this.#queue;
|
|
235
|
+
this.#queue = [];
|
|
236
|
+
for (const part of queued)
|
|
237
|
+
this.#unpublish(part, new SessionFailed(failure.error));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* A throw the writer itself did not expect while sending or publishing `parts` (a bug): fail the Session at the first
|
|
242
|
+
* of them and settle every one not settled yet, so no outcome, gate, or close waits on it forever.
|
|
243
|
+
*/
|
|
244
|
+
#abandon(parts, error) {
|
|
245
|
+
this.#host.fail(error, parts[0].index, []);
|
|
246
|
+
for (const part of parts) {
|
|
247
|
+
if (!part.done)
|
|
248
|
+
this.#unpublish(part, this.#failed(error));
|
|
249
|
+
this.#unregister(part);
|
|
250
|
+
this.#open(part);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/** `SessionFailed` with the Session's failure, or with `error` until the Session records it. */
|
|
254
|
+
#failed(error) {
|
|
255
|
+
const failure = this.#host.failure();
|
|
256
|
+
return new SessionFailed(failure !== undefined ? failure.error : error);
|
|
257
|
+
}
|
|
258
|
+
/** Settle a part that will never be published: discard it, reject it with `error`, and open its gate. */
|
|
259
|
+
#unpublish(part, error) {
|
|
260
|
+
part.discard();
|
|
261
|
+
part.settle.reject(error);
|
|
262
|
+
this.#unregister(part);
|
|
263
|
+
this.#open(part);
|
|
264
|
+
}
|
|
265
|
+
/** Drop the keys a part is still the newest writer of: it is published, or never will be. */
|
|
266
|
+
#unregister(part) {
|
|
267
|
+
for (const key of part.keys)
|
|
268
|
+
if (this.#unpublished.get(key) === part)
|
|
269
|
+
this.#unpublished.delete(key);
|
|
270
|
+
}
|
|
271
|
+
/** Open a part's gate: the jobs waiting for it go on. The newest part's opens `drained()` too. */
|
|
272
|
+
#open(part) {
|
|
273
|
+
part.gate.resolve();
|
|
274
|
+
if (this.#newest === part)
|
|
275
|
+
this.#newest = undefined;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Yield to the event loop once: `setImmediate` where there is one, otherwise a `MessageChannel` message (Workers,
|
|
280
|
+
* browsers). Microtasks queued before it, such as wait continuations, run first.
|
|
281
|
+
*/
|
|
282
|
+
function hop() {
|
|
283
|
+
if (typeof setImmediate === "function")
|
|
284
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
285
|
+
const channel = new MessageChannel();
|
|
286
|
+
return new Promise((resolve) => {
|
|
287
|
+
channel.port1.onmessage = () => {
|
|
288
|
+
channel.port1.close();
|
|
289
|
+
resolve();
|
|
290
|
+
};
|
|
291
|
+
channel.port2.postMessage(undefined);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=writer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/session/writer.ts"],"names":[],"mappings":"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","sourcesContent":["// The Session's writer: commits the mutation line admitted, stored in batches off the line and published in line order.\nimport type { Context } from \"@earendil-works/chord/context\";\nimport { withoutAbortSignal } from \"@earendil-works/chord/context\";\nimport { SessionFailed } from \"../errors.ts\";\nimport type { CommitResult, Seq, StorageWrite } from \"../types.ts\";\n\n/** A part's own failure at publication: memory could not adopt it, or one of the Session's own observers threw. */\nexport type PublishFailure = { readonly kind: \"adoption\" | \"listener\"; readonly error: unknown };\n\n/** One logical commit the line admitted: stored (`writes`, never empty), or held in memory only (no `writes`). */\nexport type PartSpec = {\n\t/** Line index of the job that admitted it: a failure it causes makes only later jobs' outcomes `SessionFailed`. */\n\treadonly index: number;\n\treadonly writes: readonly StorageWrite[] | undefined;\n\t/** Keys it writes: a read of one waits for its publication. */\n\treadonly keys: readonly string[];\n\treadonly context: Context;\n\t/** Publish it in memory, in line order, once it is durable (with its seq) or, held, once the parts before it are. */\n\treadonly publish: (seq: Seq | undefined) => PublishFailure | undefined;\n\t/** It will never be published: abort what it prepared. */\n\treadonly discard: () => void;\n};\n\n/** An admitted part and its outcome. */\nexport type Part = PartSpec & {\n\t/**\n\t * Resolves once it is published; rejects once it never will be, and also after publication when its storage call\n\t * rejected or the Session failed meanwhile. Always handled, so a part nobody awaits rejects silently.\n\t */\n\treadonly outcome: Promise<void>;\n};\n\ntype Admitted = Part & {\n\treadonly settle: { resolve(): void; reject(error: unknown): void };\n\t/** Set once its outcome is settled. */\n\tdone: boolean;\n\t/** Opens once it is published or never will be; never rejects. */\n\treadonly gate: PromiseWithResolvers<void>;\n};\n\n/** How a part that will not resolve settles: with an error of its own, which may be any value, or `SessionFailed`. */\ntype FailedOutcome = { readonly kind: \"own\"; readonly error: unknown } | { readonly kind: \"failed\" };\n\nconst SESSION_FAILED: FailedOutcome = { kind: \"failed\" };\n\n/** What the writer needs of its Session. */\nexport type WriterHost = {\n\tcommit(\n\t\tbatches: readonly (readonly StorageWrite[])[],\n\t\treceive: (index: number, seq: Seq) => void,\n\t\tcontext: Context,\n\t): Promise<CommitResult>;\n\t/** Apply a stored batch to the Session's read caches, right before it is published. */\n\tstored(writes: readonly StorageWrite[]): void;\n\t/**\n\t * Run one part's publication inside the Storage's own scope (`Storage.publishing`); `undefined` when it ran at once,\n\t * else settles once it ran.\n\t */\n\tpublishing(publish: () => void, context: Context): Promise<void> | undefined;\n\t/** The Session's failure, if it failed. */\n\tfailure(): { readonly error: unknown } | undefined;\n\t/**\n\t * Fail the Session; `at` is the line index of the first job whose outcome the failure is, and `own` the jobs after it\n\t * whose outcome is still their own error (the other parts of a call that rejected), not `SessionFailed`.\n\t */\n\tfail(error: unknown, at: number, own: readonly number[]): void;\n\t/** Report an own observer's throw that cannot fail the Session, as it fails with a storage error. */\n\treport(error: unknown, context: Context): void;\n};\n\n/**\n * The writer of one Session. The line admits each commit as a part once its callback and settlement succeeded; the\n * writer sends every part admitted while no call is in flight as one `commit()` call, off the line, and publishes\n * the parts of a call one by one, in line order, with a macrotask between two, so the continuations of the waits one\n * resolves start before the next is published. Held commits are parts without writes, published when the parts before\n * them are.\n *\n * Each part registers the keys it writes; until it is published, it is the newest unpublished writer of each, and a\n * job that touches one waits for its gate. A key leaves when its newest writer is published, so the map holds one entry\n * per key with an unpublished writer.\n */\nexport class Writer {\n\treadonly #host: WriterHost;\n\t/** Admitted parts not yet sent, in line order. */\n\t#queue: Admitted[] = [];\n\t#flight = false;\n\t/** Per key, the newest unpublished part that writes it. */\n\treadonly #unpublished = new Map<string, Admitted>();\n\t/** The newest admitted part that is not yet published, whose gate opens after every earlier one's. */\n\t#newest: Admitted | undefined;\n\n\tconstructor(host: WriterHost) {\n\t\tthis.#host = host;\n\t}\n\n\t/** Admit a part: register its keys, queue it, and send it at once unless a call is in flight. */\n\tadmit(spec: PartSpec): Part {\n\t\tconst outcome = Promise.withResolvers<void>();\n\t\toutcome.promise.catch(() => {});\n\t\tconst part: Admitted = {\n\t\t\t...spec,\n\t\t\toutcome: outcome.promise,\n\t\t\tsettle: {\n\t\t\t\tresolve: () => {\n\t\t\t\t\tpart.done = true;\n\t\t\t\t\toutcome.resolve();\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tpart.done = true;\n\t\t\t\t\toutcome.reject(error);\n\t\t\t\t},\n\t\t\t},\n\t\t\tdone: false,\n\t\t\tgate: Promise.withResolvers<void>(),\n\t\t};\n\t\tfor (const key of spec.keys) this.#unpublished.set(key, part);\n\t\tthis.#newest = part;\n\t\tthis.#queue.push(part);\n\t\tthis.#pump();\n\t\treturn part;\n\t}\n\n\t/** The gate of the newest unpublished writer of `key`, or `undefined` when it has none. */\n\tgate(key: string): Promise<void> | undefined {\n\t\treturn this.#unpublished.get(key)?.gate.promise;\n\t}\n\n\t/** Opens once every part admitted so far is published or never will be. */\n\tdrained(): Promise<void> | undefined {\n\t\treturn this.#newest?.gate.promise;\n\t}\n\n\t/** Keys with an unpublished writer, for tests that bound memory. */\n\tget unpublished(): number {\n\t\treturn this.#unpublished.size;\n\t}\n\n\t#pump(): void {\n\t\tif (this.#flight || this.#queue.length === 0) return;\n\t\tthis.#flight = true;\n\t\tconst parts = this.#queue;\n\t\tthis.#queue = [];\n\t\tvoid this.#flush(parts);\n\t}\n\n\t/**\n\t * Send and publish `parts`, then what was admitted meanwhile. Never rejects: a throw the writer did not expect fails\n\t * the Session and settles the parts (`#abandon()`), so no outcome or gate is stranded.\n\t */\n\tasync #flush(parts: readonly Admitted[]): Promise<void> {\n\t\ttry {\n\t\t\tawait this.#send(parts);\n\t\t} catch (error) {\n\t\t\tthis.#abandon(parts, error);\n\t\t}\n\t\tthis.#flight = false;\n\t\tthis.#pump();\n\t}\n\n\t/** Store `parts` in one call, then publish them in order; settles every one of them. */\n\tasync #send(parts: readonly Admitted[]): Promise<void> {\n\t\tconst stored = parts.filter((part) => part.writes !== undefined);\n\t\tconst seqs: (Seq | undefined)[] = [];\n\t\tlet result: CommitResult | undefined;\n\t\tlet rejected: { readonly error: unknown } | undefined;\n\t\tconst failed = this.#host.failure();\n\t\tif (failed !== undefined) {\n\t\t\tfor (const part of parts) this.#unpublish(part, new SessionFailed(failed.error));\n\t\t\treturn;\n\t\t}\n\t\tif (stored.length > 0) {\n\t\t\ttry {\n\t\t\t\tresult = await this.#host.commit(\n\t\t\t\t\tstored.map((part) => part.writes!),\n\t\t\t\t\t(index, seq) => {\n\t\t\t\t\t\tseqs[index] = seq;\n\t\t\t\t\t},\n\t\t\t\t\twithoutAbortSignal(stored[0]!.context),\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\trejected = { error };\n\t\t\t}\n\t\t}\n\t\t// A call that settles once another failure failed the Session reports `SessionFailed` with that first error,\n\t\t// whatever it returned (spec §10.1), and publishes nothing.\n\t\tconst failedMeanwhile = this.#host.failure();\n\t\tif (failedMeanwhile !== undefined) {\n\t\t\tfor (const part of parts) this.#unpublish(part, new SessionFailed(failedMeanwhile.error));\n\t\t\treturn;\n\t\t}\n\t\tawait this.#publish(parts, seqs, result, rejected);\n\t}\n\n\t/**\n\t * Publish the received parts and held ones in order, a macrotask apart, and settle every part (group commit §3.4). A\n\t * refusal, a rejection, or a part whose adoption or own observer throws fails the Session once the parts before it\n\t * are published and the host observers got the last one; the parts after it are not published, and reject with\n\t * `SessionFailed`. What the failure decides (the outcomes, and the gates of the parts it concerns) is settled only\n\t * once the Session failed, so no job waiting at such a gate, and no later outcome, goes on as if it had not.\n\t */\n\tasync #publish(\n\t\tparts: readonly Admitted[],\n\t\tseqs: readonly (Seq | undefined)[],\n\t\tresult: CommitResult | undefined,\n\t\trejected: { readonly error: unknown } | undefined,\n\t): Promise<void> {\n\t\tlet stop: { readonly error: unknown; readonly at: number; readonly external?: true } | undefined;\n\t\t/**\n\t\t * Settled once the Session failed: each part, whether it was published, and its outcome: an error of its own (any\n\t\t * value, `undefined` included), or `SessionFailed`.\n\t\t */\n\t\tconst failing: { readonly part: Admitted; readonly outcome: FailedOutcome; readonly published: boolean }[] = [];\n\t\tlet storedIndex = 0;\n\t\tlet published = 0;\n\t\tfor (const part of parts) {\n\t\t\tconst index = part.writes === undefined ? undefined : storedIndex++;\n\t\t\tif (stop !== undefined) {\n\t\t\t\t// A part after a rejected call's last receipt has the call's error; any other part after a stop was not sent,\n\t\t\t\t// nor any part once something else failed the Session.\n\t\t\t\tconst outcome: FailedOutcome =\n\t\t\t\t\trejected !== undefined && index !== undefined && stop.external !== true\n\t\t\t\t\t\t? { kind: \"own\", error: rejected.error }\n\t\t\t\t\t\t: SESSION_FAILED;\n\t\t\t\tfailing.push({ part, outcome, published: false });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst seq = index === undefined ? undefined : seqs[index];\n\t\t\tif (index !== undefined && seq === undefined) {\n\t\t\t\tconst error =\n\t\t\t\t\trejected !== undefined\n\t\t\t\t\t\t? rejected.error\n\t\t\t\t\t\t: index === result?.stored && \"refused\" in result\n\t\t\t\t\t\t\t? result.refused\n\t\t\t\t\t\t\t: new Error(\"Storage committed without adopting the batch\");\n\t\t\t\tfailing.push({ part, outcome: { kind: \"own\", error }, published: false });\n\t\t\t\tstop = { error, at: part.index };\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// A macrotask between two parts: what the first resolves runs before the second is published.\n\t\t\tif (published++ > 0) await hop();\n\t\t\tlet failure: PublishFailure | undefined;\n\t\t\t// Failed by something else meanwhile, during the hop or before a storage's scope runs the publication: nothing\n\t\t\t// more of the call is published. Checked right before it would be, after every wait.\n\t\t\tlet external: { readonly error: unknown } | undefined;\n\t\t\tconst publication = this.#host.publishing(() => {\n\t\t\t\texternal = this.#host.failure();\n\t\t\t\tif (external !== undefined) return;\n\t\t\t\t// Its keys leave first, so what its publication schedules sees it published; its waiters wake after it.\n\t\t\t\tthis.#unregister(part);\n\t\t\t\t// A throw anywhere in it, the read caches included, leaves memory behind what is stored: like an adoption's.\n\t\t\t\ttry {\n\t\t\t\t\tif (part.writes !== undefined) this.#host.stored(part.writes);\n\t\t\t\t\tfailure = part.publish(seq);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailure = { kind: \"adoption\", error };\n\t\t\t\t}\n\t\t\t}, part.context);\n\t\t\tif (publication !== undefined) await publication;\n\t\t\tif (external !== undefined) {\n\t\t\t\tstop = { error: external.error, at: part.index, external: true };\n\t\t\t\tfailing.push({ part, outcome: SESSION_FAILED, published: false });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (failure?.kind === \"adoption\") {\n\t\t\t\tfailing.push({ part, outcome: { kind: \"own\", error: failure.error }, published: true });\n\t\t\t\tstop = { error: failure.error, at: part.index };\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// A part published while its call rejected is durable but rejects (spec §10.1); an own observer's throw is then\n\t\t\t// reported, as the Session fails with the storage error.\n\t\t\tif (rejected !== undefined) {\n\t\t\t\tif (failure !== undefined) this.#host.report(failure.error, part.context);\n\t\t\t\tfailing.push({ part, outcome: { kind: \"own\", error: rejected.error }, published: true });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// An own observer's throw: the commit stands; the Session fails after it.\n\t\t\tif (failure !== undefined) stop = { error: failure.error, at: part.index };\n\t\t\tconst failedMeanwhile = this.#host.failure();\n\t\t\tif (failedMeanwhile !== undefined && failure === undefined) {\n\t\t\t\tpart.settle.reject(new SessionFailed(failedMeanwhile.error));\n\t\t\t} else part.settle.resolve();\n\t\t\tthis.#open(part);\n\t\t}\n\t\tif (rejected !== undefined || stop !== undefined) {\n\t\t\t// After the host dispatch of the last part published, which is one microtask away.\n\t\t\tawait Promise.resolve();\n\t\t\t// Every part of the call that rejected keeps the storage error; the first of them is the failure's boundary.\n\t\t\tconst own = failing.flatMap(({ part, outcome }) => (outcome.kind === \"own\" ? [part.index] : []));\n\t\t\tif (rejected !== undefined) this.#host.fail(rejected.error, own[0]!, own);\n\t\t\telse this.#host.fail(stop!.error, stop!.at, own);\n\t\t}\n\t\tfor (const { part, outcome, published: stored } of failing) {\n\t\t\tconst error = outcome.kind === \"own\" ? outcome.error : this.#failed(stop!.error);\n\t\t\tif (stored) {\n\t\t\t\tpart.settle.reject(error);\n\t\t\t\tthis.#open(part);\n\t\t\t} else this.#unpublish(part, error);\n\t\t}\n\t\t// Admitted while this call ran, after the failure: never sent.\n\t\tconst failure = this.#host.failure();\n\t\tif (failure !== undefined) {\n\t\t\tconst queued = this.#queue;\n\t\t\tthis.#queue = [];\n\t\t\tfor (const part of queued) this.#unpublish(part, new SessionFailed(failure.error));\n\t\t}\n\t}\n\n\t/**\n\t * A throw the writer itself did not expect while sending or publishing `parts` (a bug): fail the Session at the first\n\t * of them and settle every one not settled yet, so no outcome, gate, or close waits on it forever.\n\t */\n\t#abandon(parts: readonly Admitted[], error: unknown): void {\n\t\tthis.#host.fail(error, parts[0]!.index, []);\n\t\tfor (const part of parts) {\n\t\t\tif (!part.done) this.#unpublish(part, this.#failed(error));\n\t\t\tthis.#unregister(part);\n\t\t\tthis.#open(part);\n\t\t}\n\t}\n\n\t/** `SessionFailed` with the Session's failure, or with `error` until the Session records it. */\n\t#failed(error: unknown): SessionFailed {\n\t\tconst failure = this.#host.failure();\n\t\treturn new SessionFailed(failure !== undefined ? failure.error : error);\n\t}\n\n\t/** Settle a part that will never be published: discard it, reject it with `error`, and open its gate. */\n\t#unpublish(part: Admitted, error: unknown): void {\n\t\tpart.discard();\n\t\tpart.settle.reject(error);\n\t\tthis.#unregister(part);\n\t\tthis.#open(part);\n\t}\n\n\t/** Drop the keys a part is still the newest writer of: it is published, or never will be. */\n\t#unregister(part: Admitted): void {\n\t\tfor (const key of part.keys) if (this.#unpublished.get(key) === part) this.#unpublished.delete(key);\n\t}\n\n\t/** Open a part's gate: the jobs waiting for it go on. The newest part's opens `drained()` too. */\n\t#open(part: Admitted): void {\n\t\tpart.gate.resolve();\n\t\tif (this.#newest === part) this.#newest = undefined;\n\t}\n}\n\n/**\n * Yield to the event loop once: `setImmediate` where there is one, otherwise a `MessageChannel` message (Workers,\n * browsers). Microtasks queued before it, such as wait continuations, run first.\n */\nfunction hop(): Promise<void> {\n\tif (typeof setImmediate === \"function\") return new Promise((resolve) => setImmediate(resolve));\n\tconst channel = new MessageChannel();\n\treturn new Promise((resolve) => {\n\t\tchannel.port1.onmessage = () => {\n\t\t\tchannel.port1.close();\n\t\t\tresolve();\n\t\t};\n\t\tchannel.port2.postMessage(undefined);\n\t});\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { Context
|
|
2
|
-
import type {
|
|
1
|
+
import type { Context } from "@earendil-works/chord/context";
|
|
2
|
+
import type { JsonValue } from "@earendil-works/chord/json";
|
|
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";
|
|
3
4
|
type StoredTask = TaskRecord<JsonValue, JsonValue, JsonValue>;
|
|
4
5
|
type IndexedDbStorageOptions = {
|
|
5
6
|
readonly indexedDB?: IDBFactory;
|
|
@@ -16,7 +17,13 @@ export declare class IndexedDbStorage implements Storage {
|
|
|
16
17
|
static open(name: string, options?: IndexedDbStorageOptions): Promise<IndexedDbStorage>;
|
|
17
18
|
/** Delete a closed database. Open connections in this or another owner can block deletion. */
|
|
18
19
|
static delete(name: string, options?: IndexedDbStorageOptions): Promise<void>;
|
|
19
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Commit `batches` in one IndexedDB transaction, each with its own seq. Each batch does every read and check, copy
|
|
22
|
+
* resolution included, against the state the earlier ones left before it issues its first write; the first one
|
|
23
|
+
* refused ends the call, and the transaction commits the ones before it. An error of IndexedDB itself aborts the
|
|
24
|
+
* transaction: the call rejects with nothing stored.
|
|
25
|
+
*/
|
|
26
|
+
commit(batches: readonly (readonly StorageWrite[])[], receive: (index: number, seq: Seq) => void, _context: Context): Promise<CommitResult>;
|
|
20
27
|
mintId<I extends Id<string>>(): Promise<I>;
|
|
21
28
|
conversation(id: ConversationId, _context: Context): Promise<ConversationRecord | undefined>;
|
|
22
29
|
scanConversations(query: ConversationQuery, limit: number, cursor: Cursor | undefined, _context: Context): Promise<Page<ConversationRecord, Cursor>>;
|
|
@@ -53,6 +60,8 @@ export declare class IndexedDbStorage implements Storage {
|
|
|
53
60
|
private currentDocumentId;
|
|
54
61
|
private claimId;
|
|
55
62
|
private applyTableWrite;
|
|
63
|
+
/** Resolve each copy to a base of its source's state before this batch, which may not change the source. */
|
|
64
|
+
private resolveCopies;
|
|
56
65
|
private applyDocumentActions;
|
|
57
66
|
private assertOpen;
|
|
58
67
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../../src/storage/indexeddb.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"indexeddb.d.ts","sourceRoot":"","sources":["../../src/storage/indexeddb.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,16 +35,33 @@ const INDEXES = {
|
|
|
35
35
|
documentsByScopeKind: "documents_by_scope_kind",
|
|
36
36
|
documentRevisionsByKind: "document_revisions_by_kind",
|
|
37
37
|
};
|
|
38
|
+
/** A failure IndexedDB reported without an error of its own: the database's, like every `DOMException` it throws. */
|
|
39
|
+
class IndexedDbFailure extends Error {
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Whether `error`, or an error it was caused by (a copy's rejection wraps its cause), is IndexedDB's own: a
|
|
43
|
+
* `DOMException`, or a failure it reported without one. Any other error of a batch's checks is one of Durable's.
|
|
44
|
+
*/
|
|
45
|
+
function fromIndexedDb(error) {
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
for (let at = error; at !== null && typeof at === "object" && !seen.has(at);) {
|
|
48
|
+
if (at instanceof DOMException || at instanceof IndexedDbFailure)
|
|
49
|
+
return true;
|
|
50
|
+
seen.add(at);
|
|
51
|
+
at = at.cause;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
38
55
|
const requestResult = (request) => new Promise((resolve, reject) => {
|
|
39
56
|
request.addEventListener("success", () => resolve(request.result), { once: true });
|
|
40
|
-
request.addEventListener("error", () => reject(request.error ?? new
|
|
57
|
+
request.addEventListener("error", () => reject(request.error ?? new IndexedDbFailure("IndexedDB request failed")), {
|
|
41
58
|
once: true,
|
|
42
59
|
});
|
|
43
60
|
});
|
|
44
61
|
const transactionResult = (transaction) => new Promise((resolve, reject) => {
|
|
45
62
|
transaction.addEventListener("complete", () => resolve(), { once: true });
|
|
46
|
-
transaction.addEventListener("abort", () => reject(transaction.error ?? new
|
|
47
|
-
transaction.addEventListener("error", () => reject(transaction.error ?? new
|
|
63
|
+
transaction.addEventListener("abort", () => reject(transaction.error ?? new IndexedDbFailure("IndexedDB transaction was aborted")), { once: true });
|
|
64
|
+
transaction.addEventListener("error", () => reject(transaction.error ?? new IndexedDbFailure("IndexedDB transaction failed")), { once: true });
|
|
48
65
|
});
|
|
49
66
|
const keyRanges = () => {
|
|
50
67
|
if (typeof IDBKeyRange === "undefined")
|
|
@@ -54,7 +71,7 @@ const keyRanges = () => {
|
|
|
54
71
|
const cursorValues = (source, range, direction, limit, accept = () => true) => new Promise((resolve, reject) => {
|
|
55
72
|
const values = [];
|
|
56
73
|
const request = source.openCursor(range, direction);
|
|
57
|
-
request.addEventListener("error", () => reject(request.error ?? new
|
|
74
|
+
request.addEventListener("error", () => reject(request.error ?? new IndexedDbFailure("IndexedDB cursor failed")), {
|
|
58
75
|
once: true,
|
|
59
76
|
});
|
|
60
77
|
request.addEventListener("success", () => {
|
|
@@ -75,7 +92,7 @@ const cursorValues = (source, range, direction, limit, accept = () => true) => n
|
|
|
75
92
|
});
|
|
76
93
|
const firstCursorValue = (source, range, direction, accept = () => true) => new Promise((resolve, reject) => {
|
|
77
94
|
const request = source.openCursor(range, direction);
|
|
78
|
-
request.addEventListener("error", () => reject(request.error ?? new
|
|
95
|
+
request.addEventListener("error", () => reject(request.error ?? new IndexedDbFailure("IndexedDB cursor failed")), {
|
|
79
96
|
once: true,
|
|
80
97
|
});
|
|
81
98
|
request.addEventListener("success", () => {
|
|
@@ -242,29 +259,56 @@ export class IndexedDbStorage {
|
|
|
242
259
|
throw new Error("IndexedDB is not available");
|
|
243
260
|
await requestResult(factory.deleteDatabase(name));
|
|
244
261
|
}
|
|
245
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Commit `batches` in one IndexedDB transaction, each with its own seq. Each batch does every read and check, copy
|
|
264
|
+
* resolution included, against the state the earlier ones left before it issues its first write; the first one
|
|
265
|
+
* refused ends the call, and the transaction commits the ones before it. An error of IndexedDB itself aborts the
|
|
266
|
+
* transaction: the call rejects with nothing stored.
|
|
267
|
+
*/
|
|
268
|
+
async commit(batches, receive, _context) {
|
|
246
269
|
this.assertOpen();
|
|
247
|
-
const
|
|
248
|
-
|
|
270
|
+
const seqs = [];
|
|
271
|
+
let refused;
|
|
272
|
+
await this.transaction(ALL_STORES, "readwrite", async (transaction) => {
|
|
249
273
|
const metaStore = transaction.objectStore(STORES.meta);
|
|
250
274
|
const metadata = await requestResult(metaStore.get(META_KEY));
|
|
251
275
|
if (metadata === undefined)
|
|
252
276
|
throw new Error("Durable IndexedDB metadata is missing");
|
|
253
|
-
|
|
254
|
-
await this.checkGlobalIds(transaction, writes);
|
|
255
|
-
await this.checkDocumentActions(transaction, documentActions);
|
|
256
|
-
for (const write of writes)
|
|
257
|
-
await this.applyTableWrite(transaction, write, seq);
|
|
258
|
-
await this.applyDocumentActions(transaction, documentActions, seq);
|
|
277
|
+
let next = metadata.nextSeq;
|
|
259
278
|
let nextId = metadata.nextId;
|
|
260
|
-
for (const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
279
|
+
for (const writes of batches) {
|
|
280
|
+
let documentActions;
|
|
281
|
+
try {
|
|
282
|
+
documentActions = this.prepareDocumentActions(writes);
|
|
283
|
+
await this.checkGlobalIds(transaction, writes);
|
|
284
|
+
await this.checkDocumentActions(transaction, documentActions);
|
|
285
|
+
await this.resolveCopies(transaction, documentActions);
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
if (fromIndexedDb(error))
|
|
289
|
+
throw error;
|
|
290
|
+
refused = { error };
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
const seq = seqFromNumber(next++);
|
|
294
|
+
for (const write of writes)
|
|
295
|
+
await this.applyTableWrite(transaction, write, seq);
|
|
296
|
+
await this.applyDocumentActions(transaction, documentActions, seq);
|
|
297
|
+
for (const write of writes) {
|
|
298
|
+
const id = writeId(write);
|
|
299
|
+
if (id !== undefined)
|
|
300
|
+
nextId = Math.max(nextId, id + 1);
|
|
301
|
+
}
|
|
302
|
+
seqs.push(seq);
|
|
303
|
+
}
|
|
304
|
+
if (seqs.length > 0) {
|
|
305
|
+
await requestResult(metaStore.put({ key: META_KEY, nextId, nextSeq: next }));
|
|
264
306
|
}
|
|
265
|
-
await requestResult(metaStore.put({ key: META_KEY, nextId, nextSeq: seq + 1 }));
|
|
266
|
-
return seq;
|
|
267
307
|
});
|
|
308
|
+
// The transaction completed: the stored batches are durable.
|
|
309
|
+
for (const [index, seq] of seqs.entries())
|
|
310
|
+
receive(index, seq);
|
|
311
|
+
return refused === undefined ? { stored: seqs.length } : { stored: seqs.length, refused: refused.error };
|
|
268
312
|
}
|
|
269
313
|
async mintId() {
|
|
270
314
|
this.assertOpen();
|
|
@@ -754,31 +798,37 @@ export class IndexedDbStorage {
|
|
|
754
798
|
break;
|
|
755
799
|
}
|
|
756
800
|
}
|
|
801
|
+
/** Resolve each copy to a base of its source's state before this batch, which may not change the source. */
|
|
802
|
+
async resolveCopies(transaction, actions) {
|
|
803
|
+
for (const [id, action] of actions) {
|
|
804
|
+
if (action.copy === undefined)
|
|
805
|
+
continue;
|
|
806
|
+
try {
|
|
807
|
+
const stored = await this.materializeDocument(transaction, action.copy.id, action.copy.at);
|
|
808
|
+
if (stored === undefined)
|
|
809
|
+
throw new Error(`Fork source document ${action.copy.id} cannot be read`);
|
|
810
|
+
const create = action.create;
|
|
811
|
+
if (stored.record.scope.kind !== "conversation" ||
|
|
812
|
+
create.scope.kind !== "conversation" ||
|
|
813
|
+
stored.record.kind !== create.kind ||
|
|
814
|
+
stored.record.key !== create.key ||
|
|
815
|
+
stored.record.history !== create.history ||
|
|
816
|
+
stored.record.fork !== create.fork) {
|
|
817
|
+
throw new Error(`Fork source document ${action.copy.id} does not match the copied record`);
|
|
818
|
+
}
|
|
819
|
+
action.content = { kind: "base", version: stored.version, value: stored.value };
|
|
820
|
+
}
|
|
821
|
+
catch (error) {
|
|
822
|
+
throw new Error(`Document copy ${id} was rejected`, { cause: error });
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
757
826
|
async applyDocumentActions(transaction, actions, seq) {
|
|
758
827
|
const documents = transaction.objectStore(STORES.documents);
|
|
759
828
|
const revisions = transaction.objectStore(STORES.documentRevisions);
|
|
760
829
|
for (const [id, action] of actions) {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
try {
|
|
764
|
-
const stored = await this.materializeDocument(transaction, action.copy.id, action.copy.at);
|
|
765
|
-
if (stored === undefined)
|
|
766
|
-
throw new Error(`Fork source document ${action.copy.id} cannot be read`);
|
|
767
|
-
const create = action.create;
|
|
768
|
-
if (stored.record.scope.kind !== "conversation" ||
|
|
769
|
-
create.scope.kind !== "conversation" ||
|
|
770
|
-
stored.record.kind !== create.kind ||
|
|
771
|
-
stored.record.key !== create.key ||
|
|
772
|
-
stored.record.history !== create.history ||
|
|
773
|
-
stored.record.fork !== create.fork) {
|
|
774
|
-
throw new Error(`Fork source document ${action.copy.id} does not match the copied record`);
|
|
775
|
-
}
|
|
776
|
-
content = { kind: "base", version: stored.version, value: stored.value };
|
|
777
|
-
}
|
|
778
|
-
catch (error) {
|
|
779
|
-
throw new Error(`Document copy ${id} was rejected`, { cause: error });
|
|
780
|
-
}
|
|
781
|
-
}
|
|
830
|
+
// Copies are resolved to their content by `resolveCopies()`.
|
|
831
|
+
const content = action.content;
|
|
782
832
|
let record;
|
|
783
833
|
if (action.create !== undefined) {
|
|
784
834
|
record = {
|