@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,33 @@
|
|
|
1
|
+
import type { ConversationId, EntryId, EntryRecord, StorageWrite } from "../types.ts";
|
|
2
|
+
/** An entry that carries a `head`: the marker of a context range. */
|
|
3
|
+
export type HeadMarker = EntryRecord & {
|
|
4
|
+
readonly head: EntryId;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* The newest entry ID and newest head marker per conversation, for the Session that owns its Storage: every entry is
|
|
8
|
+
* written by one of its commits, so `apply` keeps both current, and a value read from Storage stays valid until the
|
|
9
|
+
* next entry of that conversation. Read only on the Session line after touching the conversation's appends, so no
|
|
10
|
+
* commit that appends to it is unpublished and a value read from Storage can be kept. The newest entry visible from a conversation is its own newest, or for a fork without entries
|
|
11
|
+
* of its own the parent's entry at the fork point: IDs grow, so a conversation's own new entry is always the newest.
|
|
12
|
+
* Each map is bounded by a count, the least recently used conversation leaving first. Kept markers are frozen: entry
|
|
13
|
+
* records are immutable (`Tx.appendEntry()`).
|
|
14
|
+
*/
|
|
15
|
+
export declare class ConversationTails {
|
|
16
|
+
#private;
|
|
17
|
+
/** The newest entry ID visible from `conversationId`, read with `read` when not known. */
|
|
18
|
+
latest(conversationId: ConversationId, read: () => Promise<EntryId | undefined>): Promise<EntryId | undefined>;
|
|
19
|
+
/**
|
|
20
|
+
* The newest visible head marker at or before `atOrBefore` (any when `undefined`), read with `read` when not known.
|
|
21
|
+
* The newest marker answers every cutoff at or after it, and no marker answers every cutoff.
|
|
22
|
+
*/
|
|
23
|
+
marker(conversationId: ConversationId, atOrBefore: EntryId | undefined, read: () => Promise<HeadMarker | undefined>): Promise<HeadMarker | undefined>;
|
|
24
|
+
/** The newest entry ID of `conversationId` if known, without reading: `null` for none, `undefined` when unknown. */
|
|
25
|
+
peek(conversationId: ConversationId): EntryId | null | undefined;
|
|
26
|
+
/** At a commit's publication: apply a durable batch. */
|
|
27
|
+
apply(writes: readonly StorageWrite[]): void;
|
|
28
|
+
/** How many conversations have their newest entry kept, for tests that bound memory. */
|
|
29
|
+
get size(): number;
|
|
30
|
+
/** How many conversations have their newest head marker kept, for tests that bound memory. */
|
|
31
|
+
get markers(): number;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=conversation-tails.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-tails.d.ts","sourceRoot":"","sources":["../../src/session/conversation-tails.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEtF,qEAAqE;AACrE,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAQlE;;;;;;;;GAQG;AACH,qBAAa,iBAAiB;;IAM7B,0FAA0F;IACpF,MAAM,CACX,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,GACtC,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAM9B;IAED;;;OAGG;IACG,MAAM,CACX,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,OAAO,GAAG,SAAS,EAC/B,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,GACzC,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAWjC;IAED,oHAAoH;IACpH,IAAI,CAAC,cAAc,EAAE,cAAc,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAE/D;IAED,wDAAwD;IACxD,KAAK,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,GAAG,IAAI,CAe3C;IAED,wFAAwF;IACxF,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,8FAA8F;IAC9F,IAAI,OAAO,IAAI,MAAM,CAEpB;CACD"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/** Conversations whose newest entry ID is kept: IDs only, so many. */
|
|
2
|
+
const LATEST_CAPACITY = 4096;
|
|
3
|
+
/** Conversations whose newest head marker is kept: whole entries (a compaction summary is one), so fewer. */
|
|
4
|
+
const MARKER_CAPACITY = 256;
|
|
5
|
+
/**
|
|
6
|
+
* The newest entry ID and newest head marker per conversation, for the Session that owns its Storage: every entry is
|
|
7
|
+
* written by one of its commits, so `apply` keeps both current, and a value read from Storage stays valid until the
|
|
8
|
+
* next entry of that conversation. Read only on the Session line after touching the conversation's appends, so no
|
|
9
|
+
* commit that appends to it is unpublished and a value read from Storage can be kept. The newest entry visible from a conversation is its own newest, or for a fork without entries
|
|
10
|
+
* of its own the parent's entry at the fork point: IDs grow, so a conversation's own new entry is always the newest.
|
|
11
|
+
* Each map is bounded by a count, the least recently used conversation leaving first. Kept markers are frozen: entry
|
|
12
|
+
* records are immutable (`Tx.appendEntry()`).
|
|
13
|
+
*/
|
|
14
|
+
export class ConversationTails {
|
|
15
|
+
/** Newest entry ID per conversation, `null` for none. */
|
|
16
|
+
#latest = new Map();
|
|
17
|
+
/** Newest head marker per conversation, `null` for none. */
|
|
18
|
+
#markers = new Map();
|
|
19
|
+
/** The newest entry ID visible from `conversationId`, read with `read` when not known. */
|
|
20
|
+
async latest(conversationId, read) {
|
|
21
|
+
const known = use(this.#latest, conversationId);
|
|
22
|
+
if (known !== undefined)
|
|
23
|
+
return known ?? undefined;
|
|
24
|
+
const latest = await read();
|
|
25
|
+
put(this.#latest, conversationId, latest ?? null, LATEST_CAPACITY);
|
|
26
|
+
return latest;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The newest visible head marker at or before `atOrBefore` (any when `undefined`), read with `read` when not known.
|
|
30
|
+
* The newest marker answers every cutoff at or after it, and no marker answers every cutoff.
|
|
31
|
+
*/
|
|
32
|
+
async marker(conversationId, atOrBefore, read) {
|
|
33
|
+
const marker = use(this.#markers, conversationId);
|
|
34
|
+
if (marker === null)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (marker !== undefined && (atOrBefore === undefined || atOrBefore >= marker.id))
|
|
37
|
+
return marker;
|
|
38
|
+
const latest = this.#latest.get(conversationId);
|
|
39
|
+
const found = await read();
|
|
40
|
+
// Only a read that no newer entry can change: no cutoff, or one at or after the newest entry.
|
|
41
|
+
if (atOrBefore === undefined || (latest !== undefined && (latest ?? 0) <= atOrBefore)) {
|
|
42
|
+
put(this.#markers, conversationId, found === undefined ? null : freeze(found), MARKER_CAPACITY);
|
|
43
|
+
}
|
|
44
|
+
return found;
|
|
45
|
+
}
|
|
46
|
+
/** The newest entry ID of `conversationId` if known, without reading: `null` for none, `undefined` when unknown. */
|
|
47
|
+
peek(conversationId) {
|
|
48
|
+
return this.#latest.get(conversationId);
|
|
49
|
+
}
|
|
50
|
+
/** At a commit's publication: apply a durable batch. */
|
|
51
|
+
apply(writes) {
|
|
52
|
+
for (const write of writes) {
|
|
53
|
+
if (write.type === "conversation") {
|
|
54
|
+
// A new root conversation sees no entries; a fork sees its parent's up to the fork point.
|
|
55
|
+
if (write.value.parent !== undefined)
|
|
56
|
+
continue;
|
|
57
|
+
put(this.#latest, write.value.id, null, LATEST_CAPACITY);
|
|
58
|
+
put(this.#markers, write.value.id, null, MARKER_CAPACITY);
|
|
59
|
+
}
|
|
60
|
+
else if (write.type === "entry") {
|
|
61
|
+
const entry = write.value;
|
|
62
|
+
put(this.#latest, entry.conversationId, entry.id, LATEST_CAPACITY);
|
|
63
|
+
if (entry.head !== undefined) {
|
|
64
|
+
put(this.#markers, entry.conversationId, freeze(entry), MARKER_CAPACITY);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** How many conversations have their newest entry kept, for tests that bound memory. */
|
|
70
|
+
get size() {
|
|
71
|
+
return this.#latest.size;
|
|
72
|
+
}
|
|
73
|
+
/** How many conversations have their newest head marker kept, for tests that bound memory. */
|
|
74
|
+
get markers() {
|
|
75
|
+
return this.#markers.size;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** `map`'s value for `key`, made the most recently used. */
|
|
79
|
+
function use(map, key) {
|
|
80
|
+
const value = map.get(key);
|
|
81
|
+
if (value === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
map.delete(key);
|
|
84
|
+
map.set(key, value);
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
/** Set `key` as the most recently used, dropping the least recently used beyond `capacity`. */
|
|
88
|
+
function put(map, key, value, capacity) {
|
|
89
|
+
map.delete(key);
|
|
90
|
+
map.set(key, value);
|
|
91
|
+
if (map.size > capacity)
|
|
92
|
+
map.delete(map.keys().next().value);
|
|
93
|
+
}
|
|
94
|
+
function freeze(value) {
|
|
95
|
+
if (value === null || typeof value !== "object" || Object.isFrozen(value))
|
|
96
|
+
return value;
|
|
97
|
+
for (const child of Object.values(value))
|
|
98
|
+
freeze(child);
|
|
99
|
+
return Object.freeze(value);
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=conversation-tails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-tails.js","sourceRoot":"","sources":["../../src/session/conversation-tails.ts"],"names":[],"mappings":"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","sourcesContent":["// The newest entry and newest head marker of each recently used conversation, kept current from the Session's commits.\nimport type { ConversationId, EntryId, EntryRecord, StorageWrite } from \"../types.ts\";\n\n/** An entry that carries a `head`: the marker of a context range. */\nexport type HeadMarker = EntryRecord & { readonly head: EntryId };\n\n/** Conversations whose newest entry ID is kept: IDs only, so many. */\nconst LATEST_CAPACITY = 4096;\n\n/** Conversations whose newest head marker is kept: whole entries (a compaction summary is one), so fewer. */\nconst MARKER_CAPACITY = 256;\n\n/**\n * The newest entry ID and newest head marker per conversation, for the Session that owns its Storage: every entry is\n * written by one of its commits, so `apply` keeps both current, and a value read from Storage stays valid until the\n * next entry of that conversation. Read only on the Session line after touching the conversation's appends, so no\n * commit that appends to it is unpublished and a value read from Storage can be kept. The newest entry visible from a conversation is its own newest, or for a fork without entries\n * of its own the parent's entry at the fork point: IDs grow, so a conversation's own new entry is always the newest.\n * Each map is bounded by a count, the least recently used conversation leaving first. Kept markers are frozen: entry\n * records are immutable (`Tx.appendEntry()`).\n */\nexport class ConversationTails {\n\t/** Newest entry ID per conversation, `null` for none. */\n\treadonly #latest = new Map<ConversationId, EntryId | null>();\n\t/** Newest head marker per conversation, `null` for none. */\n\treadonly #markers = new Map<ConversationId, HeadMarker | null>();\n\n\t/** The newest entry ID visible from `conversationId`, read with `read` when not known. */\n\tasync latest(\n\t\tconversationId: ConversationId,\n\t\tread: () => Promise<EntryId | undefined>,\n\t): Promise<EntryId | undefined> {\n\t\tconst known = use(this.#latest, conversationId);\n\t\tif (known !== undefined) return known ?? undefined;\n\t\tconst latest = await read();\n\t\tput(this.#latest, conversationId, latest ?? null, LATEST_CAPACITY);\n\t\treturn latest;\n\t}\n\n\t/**\n\t * The newest visible head marker at or before `atOrBefore` (any when `undefined`), read with `read` when not known.\n\t * The newest marker answers every cutoff at or after it, and no marker answers every cutoff.\n\t */\n\tasync marker(\n\t\tconversationId: ConversationId,\n\t\tatOrBefore: EntryId | undefined,\n\t\tread: () => Promise<HeadMarker | undefined>,\n\t): Promise<HeadMarker | undefined> {\n\t\tconst marker = use(this.#markers, conversationId);\n\t\tif (marker === null) return undefined;\n\t\tif (marker !== undefined && (atOrBefore === undefined || atOrBefore >= marker.id)) return marker;\n\t\tconst latest = this.#latest.get(conversationId);\n\t\tconst found = await read();\n\t\t// Only a read that no newer entry can change: no cutoff, or one at or after the newest entry.\n\t\tif (atOrBefore === undefined || (latest !== undefined && (latest ?? 0) <= atOrBefore)) {\n\t\t\tput(this.#markers, conversationId, found === undefined ? null : freeze(found), MARKER_CAPACITY);\n\t\t}\n\t\treturn found;\n\t}\n\n\t/** The newest entry ID of `conversationId` if known, without reading: `null` for none, `undefined` when unknown. */\n\tpeek(conversationId: ConversationId): EntryId | null | undefined {\n\t\treturn this.#latest.get(conversationId);\n\t}\n\n\t/** At a commit's publication: apply a durable batch. */\n\tapply(writes: readonly StorageWrite[]): void {\n\t\tfor (const write of writes) {\n\t\t\tif (write.type === \"conversation\") {\n\t\t\t\t// A new root conversation sees no entries; a fork sees its parent's up to the fork point.\n\t\t\t\tif (write.value.parent !== undefined) continue;\n\t\t\t\tput(this.#latest, write.value.id, null, LATEST_CAPACITY);\n\t\t\t\tput(this.#markers, write.value.id, null, MARKER_CAPACITY);\n\t\t\t} else if (write.type === \"entry\") {\n\t\t\t\tconst entry = write.value;\n\t\t\t\tput(this.#latest, entry.conversationId, entry.id, LATEST_CAPACITY);\n\t\t\t\tif (entry.head !== undefined) {\n\t\t\t\t\tput(this.#markers, entry.conversationId, freeze(entry as HeadMarker), MARKER_CAPACITY);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/** How many conversations have their newest entry kept, for tests that bound memory. */\n\tget size(): number {\n\t\treturn this.#latest.size;\n\t}\n\n\t/** How many conversations have their newest head marker kept, for tests that bound memory. */\n\tget markers(): number {\n\t\treturn this.#markers.size;\n\t}\n}\n\n/** `map`'s value for `key`, made the most recently used. */\nfunction use<K, V>(map: Map<K, V>, key: K): V | undefined {\n\tconst value = map.get(key);\n\tif (value === undefined) return undefined;\n\tmap.delete(key);\n\tmap.set(key, value);\n\treturn value;\n}\n\n/** Set `key` as the most recently used, dropping the least recently used beyond `capacity`. */\nfunction put<K, V>(map: Map<K, V>, key: K, value: V, capacity: number): void {\n\tmap.delete(key);\n\tmap.set(key, value);\n\tif (map.size > capacity) map.delete(map.keys().next().value!);\n}\n\nfunction freeze<T>(value: T): T {\n\tif (value === null || typeof value !== \"object\" || Object.isFrozen(value)) return value;\n\tfor (const child of Object.values(value)) freeze(child);\n\treturn Object.freeze(value);\n}\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Context, JsonValue } from "@earendil-works/chord";
|
|
2
|
+
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
|
+
type StoredEntry = {
|
|
4
|
+
readonly entry: EntryRecord;
|
|
5
|
+
readonly commitSeq: Seq;
|
|
6
|
+
};
|
|
7
|
+
/** A task record. */
|
|
8
|
+
export declare function taskKey(id: TaskId): string;
|
|
9
|
+
/** A conversation record. */
|
|
10
|
+
export declare function conversationKey(id: ConversationId): string;
|
|
11
|
+
/** An entry record. */
|
|
12
|
+
export declare function entryKey(id: EntryId): string;
|
|
13
|
+
/** The newest entries of a conversation: appended blind, read by scans, the newest entry, and head markers. */
|
|
14
|
+
export declare function appendKey(id: ConversationId): string;
|
|
15
|
+
/** A submission record. */
|
|
16
|
+
export declare function submissionKey(id: SubmissionId): string;
|
|
17
|
+
/** The submission a conversation's request ID names. */
|
|
18
|
+
export declare function requestKey(conversationId: ConversationId, requestId: string): string;
|
|
19
|
+
/** A document address (`addressId()`): its current incarnation, value, and held changes. */
|
|
20
|
+
export declare function documentKey(addressId: string): string;
|
|
21
|
+
/** A document incarnation, for reads by its ID. */
|
|
22
|
+
export declare function incarnationKey(id: DocumentId): string;
|
|
23
|
+
/** The incarnations alive in a scope: created and retired blind, read by scans and the task-document index. */
|
|
24
|
+
export declare function scopeKey(scope: DocumentRecord["scope"]): string;
|
|
25
|
+
/**
|
|
26
|
+
* A node of the scheduler's ownership tree (a task ID, or `c<conversation ID>`): the live work listed below it. Written
|
|
27
|
+
* by task creation and a task becoming terminal for every node from the task's parent up to and including the nearest
|
|
28
|
+
* live task or the root conversation; read by every walk that expands the node.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ownedKey(node: TaskId | string): string;
|
|
31
|
+
/** The ownerless conversations with live work below them, which global idle walks from. */
|
|
32
|
+
export declare const ROOTS_KEY = "roots";
|
|
33
|
+
/** The live tasks with cancellation intent: abort marks, and held outcomes other than `completed`. */
|
|
34
|
+
export declare const INTENT_KEY = "intent";
|
|
35
|
+
/** Every unpublished write: what a read that cannot be keyed, such as a scan, touches. */
|
|
36
|
+
export declare const ALL_KEYS = "*";
|
|
37
|
+
/** The keys of the table records in `writes`; document writes are keyed by the transaction, which knows addresses. */
|
|
38
|
+
export declare function tableKeys(writes: readonly StorageWrite[], keys: string[]): void;
|
|
39
|
+
/**
|
|
40
|
+
* The Storage a job on the line reads through: every read first touches its key (`touch`), which waits while the key
|
|
41
|
+
* has an unpublished writer, so the read sees what the line admitted before it. Scans that cannot be keyed touch
|
|
42
|
+
* `ALL_KEYS`. Commits and `close()` pass through; the line never commits through it.
|
|
43
|
+
*/
|
|
44
|
+
export declare class GatedStorage implements Storage {
|
|
45
|
+
#private;
|
|
46
|
+
constructor(storage: Storage, touch: (key: string) => Promise<void> | undefined);
|
|
47
|
+
commit(batches: readonly (readonly StorageWrite[])[], receive: (index: number, seq: Seq) => void, context: Context): Promise<CommitResult>;
|
|
48
|
+
mintId<I extends Id<string>>(): Promise<I>;
|
|
49
|
+
conversation(id: ConversationId, context: Context): Promise<ConversationRecord | undefined>;
|
|
50
|
+
scanConversations(query: ConversationQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<ConversationRecord, Cursor>>;
|
|
51
|
+
entry(id: EntryId, context: Context): Promise<StoredEntry | undefined>;
|
|
52
|
+
entry(conversationId: ConversationId, id: EntryId, context: Context): Promise<StoredEntry | undefined>;
|
|
53
|
+
findLatestHeadMarker(conversationId: ConversationId, atOrBeforeEntryId: EntryId | undefined, context: Context): Promise<(EntryRecord & {
|
|
54
|
+
readonly head: EntryId;
|
|
55
|
+
}) | undefined>;
|
|
56
|
+
scanEntries(query: EntryQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<EntryRecord, Cursor>>;
|
|
57
|
+
task(id: TaskId, context: Context): Promise<TaskRecord<JsonValue, JsonValue, JsonValue> | undefined>;
|
|
58
|
+
scanTasks(query: TaskQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<TaskRecord<JsonValue, JsonValue, JsonValue>, Cursor>>;
|
|
59
|
+
submission(id: SubmissionId, context: Context): Promise<SubmissionRecord | undefined>;
|
|
60
|
+
scanSubmissions(query: SubmissionQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<SubmissionRecord, Cursor>>;
|
|
61
|
+
submissionByRequest(conversationId: ConversationId, requestId: string, context: Context): Promise<SubmissionRecord | undefined>;
|
|
62
|
+
findDocument(address: DocumentAddress, at: DocumentPoint, context: Context): Promise<DocumentRecord | undefined>;
|
|
63
|
+
document(id: DocumentId, at: DocumentPoint, context: Context): Promise<StoredDocument | undefined>;
|
|
64
|
+
scanDocuments(query: DocumentQuery, limit: number, cursor: Cursor | undefined, context: Context): Promise<Page<DocumentRecord, Cursor>>;
|
|
65
|
+
close(context: Context): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/session/keys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { addressId } from "../documents.js";
|
|
2
|
+
/** A task record. */
|
|
3
|
+
export function taskKey(id) {
|
|
4
|
+
return `task:${id}`;
|
|
5
|
+
}
|
|
6
|
+
/** A conversation record. */
|
|
7
|
+
export function conversationKey(id) {
|
|
8
|
+
return `conv:${id}`;
|
|
9
|
+
}
|
|
10
|
+
/** An entry record. */
|
|
11
|
+
export function entryKey(id) {
|
|
12
|
+
return `entry:${id}`;
|
|
13
|
+
}
|
|
14
|
+
/** The newest entries of a conversation: appended blind, read by scans, the newest entry, and head markers. */
|
|
15
|
+
export function appendKey(id) {
|
|
16
|
+
return `entry-append:${id}`;
|
|
17
|
+
}
|
|
18
|
+
/** A submission record. */
|
|
19
|
+
export function submissionKey(id) {
|
|
20
|
+
return `sub:${id}`;
|
|
21
|
+
}
|
|
22
|
+
/** The submission a conversation's request ID names. */
|
|
23
|
+
export function requestKey(conversationId, requestId) {
|
|
24
|
+
return `req:${conversationId}:${requestId}`;
|
|
25
|
+
}
|
|
26
|
+
/** A document address (`addressId()`): its current incarnation, value, and held changes. */
|
|
27
|
+
export function documentKey(addressId) {
|
|
28
|
+
return `doc:${addressId}`;
|
|
29
|
+
}
|
|
30
|
+
/** A document incarnation, for reads by its ID. */
|
|
31
|
+
export function incarnationKey(id) {
|
|
32
|
+
return `docid:${id}`;
|
|
33
|
+
}
|
|
34
|
+
/** The incarnations alive in a scope: created and retired blind, read by scans and the task-document index. */
|
|
35
|
+
export function scopeKey(scope) {
|
|
36
|
+
switch (scope.kind) {
|
|
37
|
+
case "session":
|
|
38
|
+
return "docscope:session";
|
|
39
|
+
case "conversation":
|
|
40
|
+
return `docscope:c${scope.conversationId}`;
|
|
41
|
+
case "task":
|
|
42
|
+
return `docscope:t${scope.taskId}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A node of the scheduler's ownership tree (a task ID, or `c<conversation ID>`): the live work listed below it. Written
|
|
47
|
+
* by task creation and a task becoming terminal for every node from the task's parent up to and including the nearest
|
|
48
|
+
* live task or the root conversation; read by every walk that expands the node.
|
|
49
|
+
*/
|
|
50
|
+
export function ownedKey(node) {
|
|
51
|
+
return `owned:${node}`;
|
|
52
|
+
}
|
|
53
|
+
/** The ownerless conversations with live work below them, which global idle walks from. */
|
|
54
|
+
export const ROOTS_KEY = "roots";
|
|
55
|
+
/** The live tasks with cancellation intent: abort marks, and held outcomes other than `completed`. */
|
|
56
|
+
export const INTENT_KEY = "intent";
|
|
57
|
+
/** Every unpublished write: what a read that cannot be keyed, such as a scan, touches. */
|
|
58
|
+
export const ALL_KEYS = "*";
|
|
59
|
+
/** The keys of the table records in `writes`; document writes are keyed by the transaction, which knows addresses. */
|
|
60
|
+
export function tableKeys(writes, keys) {
|
|
61
|
+
for (const write of writes) {
|
|
62
|
+
switch (write.type) {
|
|
63
|
+
case "conversation":
|
|
64
|
+
keys.push(conversationKey(write.value.id));
|
|
65
|
+
break;
|
|
66
|
+
case "entry":
|
|
67
|
+
keys.push(entryKey(write.value.id), appendKey(write.value.conversationId));
|
|
68
|
+
break;
|
|
69
|
+
case "task":
|
|
70
|
+
keys.push(taskKey(write.value.id));
|
|
71
|
+
break;
|
|
72
|
+
case "submission":
|
|
73
|
+
keys.push(submissionKey(write.value.id));
|
|
74
|
+
if (write.value.requestId !== undefined) {
|
|
75
|
+
keys.push(requestKey(write.value.conversationId, write.value.requestId));
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The Storage a job on the line reads through: every read first touches its key (`touch`), which waits while the key
|
|
83
|
+
* has an unpublished writer, so the read sees what the line admitted before it. Scans that cannot be keyed touch
|
|
84
|
+
* `ALL_KEYS`. Commits and `close()` pass through; the line never commits through it.
|
|
85
|
+
*/
|
|
86
|
+
export class GatedStorage {
|
|
87
|
+
#storage;
|
|
88
|
+
#touch;
|
|
89
|
+
constructor(storage, touch) {
|
|
90
|
+
this.#storage = storage;
|
|
91
|
+
this.#touch = touch;
|
|
92
|
+
}
|
|
93
|
+
commit(batches, receive, context) {
|
|
94
|
+
return this.#storage.commit(batches, receive, context);
|
|
95
|
+
}
|
|
96
|
+
mintId() {
|
|
97
|
+
return this.#storage.mintId();
|
|
98
|
+
}
|
|
99
|
+
conversation(id, context) {
|
|
100
|
+
return this.#after(conversationKey(id), () => this.#storage.conversation(id, context));
|
|
101
|
+
}
|
|
102
|
+
scanConversations(query, limit, cursor, context) {
|
|
103
|
+
return this.#after(ALL_KEYS, () => this.#storage.scanConversations(query, limit, cursor, context));
|
|
104
|
+
}
|
|
105
|
+
entry(first, second, third) {
|
|
106
|
+
if (third === undefined) {
|
|
107
|
+
const id = first;
|
|
108
|
+
return this.#after(entryKey(id), () => this.#storage.entry(id, second));
|
|
109
|
+
}
|
|
110
|
+
const conversationId = first;
|
|
111
|
+
const id = second;
|
|
112
|
+
// Visibility follows the conversation's immutable ancestry; the conversation may be unpublished itself.
|
|
113
|
+
return this.#after(conversationKey(conversationId), () => this.#after(entryKey(id), () => this.#storage.entry(conversationId, id, third)));
|
|
114
|
+
}
|
|
115
|
+
findLatestHeadMarker(conversationId, atOrBeforeEntryId, context) {
|
|
116
|
+
return this.#afterEntries(conversationId, () => this.#storage.findLatestHeadMarker(conversationId, atOrBeforeEntryId, context));
|
|
117
|
+
}
|
|
118
|
+
scanEntries(query, limit, cursor, context) {
|
|
119
|
+
return this.#afterEntries(query.conversationId, () => this.#storage.scanEntries(query, limit, cursor, context));
|
|
120
|
+
}
|
|
121
|
+
task(id, context) {
|
|
122
|
+
return this.#after(taskKey(id), () => this.#storage.task(id, context));
|
|
123
|
+
}
|
|
124
|
+
scanTasks(query, limit, cursor, context) {
|
|
125
|
+
return this.#after(ALL_KEYS, () => this.#storage.scanTasks(query, limit, cursor, context));
|
|
126
|
+
}
|
|
127
|
+
submission(id, context) {
|
|
128
|
+
return this.#after(submissionKey(id), () => this.#storage.submission(id, context));
|
|
129
|
+
}
|
|
130
|
+
scanSubmissions(query, limit, cursor, context) {
|
|
131
|
+
return this.#after(ALL_KEYS, () => this.#storage.scanSubmissions(query, limit, cursor, context));
|
|
132
|
+
}
|
|
133
|
+
submissionByRequest(conversationId, requestId, context) {
|
|
134
|
+
return this.#after(requestKey(conversationId, requestId), () => this.#storage.submissionByRequest(conversationId, requestId, context));
|
|
135
|
+
}
|
|
136
|
+
findDocument(address, at, context) {
|
|
137
|
+
return this.#after(documentKey(addressId(address)), () => this.#storage.findDocument(address, at, context));
|
|
138
|
+
}
|
|
139
|
+
document(id, at, context) {
|
|
140
|
+
return this.#after(incarnationKey(id), () => this.#storage.document(id, at, context));
|
|
141
|
+
}
|
|
142
|
+
scanDocuments(query, limit, cursor, context) {
|
|
143
|
+
return this.#after(scopeKey(query.scope), () => this.#storage.scanDocuments(query, limit, cursor, context));
|
|
144
|
+
}
|
|
145
|
+
close(context) {
|
|
146
|
+
return this.#storage.close(context);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Run a read of a conversation's entries once neither its appends nor its creation has an unpublished writer: Storage
|
|
150
|
+
* refuses the read of a conversation it does not have yet.
|
|
151
|
+
*/
|
|
152
|
+
#afterEntries(conversationId, read) {
|
|
153
|
+
return this.#after(conversationKey(conversationId), () => this.#after(appendKey(conversationId), read));
|
|
154
|
+
}
|
|
155
|
+
/** Run `read` once `key` has no unpublished writer. */
|
|
156
|
+
#after(key, read) {
|
|
157
|
+
const gate = this.#touch(key);
|
|
158
|
+
return gate === undefined ? read() : gate.then(read);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/session/keys.ts"],"names":[],"mappings":"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","sourcesContent":["// The keys of the Session line: what a commit writes and a job reads, so a read of an unpublished write waits for it.\nimport type { Context, JsonValue } from \"@earendil-works/chord\";\nimport { addressId } from \"../documents.ts\";\nimport type {\n\tCommitResult,\n\tConversationId,\n\tConversationQuery,\n\tConversationRecord,\n\tCursor,\n\tDocumentAddress,\n\tDocumentId,\n\tDocumentPoint,\n\tDocumentQuery,\n\tDocumentRecord,\n\tEntryId,\n\tEntryQuery,\n\tEntryRecord,\n\tId,\n\tPage,\n\tSeq,\n\tStorage,\n\tStorageWrite,\n\tStoredDocument,\n\tSubmissionId,\n\tSubmissionQuery,\n\tSubmissionRecord,\n\tTaskId,\n\tTaskQuery,\n\tTaskRecord,\n} from \"../types.ts\";\n\ntype StoredEntry = { readonly entry: EntryRecord; readonly commitSeq: Seq };\n\n/** A task record. */\nexport function taskKey(id: TaskId): string {\n\treturn `task:${id}`;\n}\n\n/** A conversation record. */\nexport function conversationKey(id: ConversationId): string {\n\treturn `conv:${id}`;\n}\n\n/** An entry record. */\nexport function entryKey(id: EntryId): string {\n\treturn `entry:${id}`;\n}\n\n/** The newest entries of a conversation: appended blind, read by scans, the newest entry, and head markers. */\nexport function appendKey(id: ConversationId): string {\n\treturn `entry-append:${id}`;\n}\n\n/** A submission record. */\nexport function submissionKey(id: SubmissionId): string {\n\treturn `sub:${id}`;\n}\n\n/** The submission a conversation's request ID names. */\nexport function requestKey(conversationId: ConversationId, requestId: string): string {\n\treturn `req:${conversationId}:${requestId}`;\n}\n\n/** A document address (`addressId()`): its current incarnation, value, and held changes. */\nexport function documentKey(addressId: string): string {\n\treturn `doc:${addressId}`;\n}\n\n/** A document incarnation, for reads by its ID. */\nexport function incarnationKey(id: DocumentId): string {\n\treturn `docid:${id}`;\n}\n\n/** The incarnations alive in a scope: created and retired blind, read by scans and the task-document index. */\nexport function scopeKey(scope: DocumentRecord[\"scope\"]): string {\n\tswitch (scope.kind) {\n\t\tcase \"session\":\n\t\t\treturn \"docscope:session\";\n\t\tcase \"conversation\":\n\t\t\treturn `docscope:c${scope.conversationId}`;\n\t\tcase \"task\":\n\t\t\treturn `docscope:t${scope.taskId}`;\n\t}\n}\n\n/**\n * A node of the scheduler's ownership tree (a task ID, or `c<conversation ID>`): the live work listed below it. Written\n * by task creation and a task becoming terminal for every node from the task's parent up to and including the nearest\n * live task or the root conversation; read by every walk that expands the node.\n */\nexport function ownedKey(node: TaskId | string): string {\n\treturn `owned:${node}`;\n}\n\n/** The ownerless conversations with live work below them, which global idle walks from. */\nexport const ROOTS_KEY = \"roots\";\n\n/** The live tasks with cancellation intent: abort marks, and held outcomes other than `completed`. */\nexport const INTENT_KEY = \"intent\";\n\n/** Every unpublished write: what a read that cannot be keyed, such as a scan, touches. */\nexport const ALL_KEYS = \"*\";\n\n/** The keys of the table records in `writes`; document writes are keyed by the transaction, which knows addresses. */\nexport function tableKeys(writes: readonly StorageWrite[], keys: string[]): void {\n\tfor (const write of writes) {\n\t\tswitch (write.type) {\n\t\t\tcase \"conversation\":\n\t\t\t\tkeys.push(conversationKey(write.value.id));\n\t\t\t\tbreak;\n\t\t\tcase \"entry\":\n\t\t\t\tkeys.push(entryKey(write.value.id), appendKey(write.value.conversationId));\n\t\t\t\tbreak;\n\t\t\tcase \"task\":\n\t\t\t\tkeys.push(taskKey(write.value.id));\n\t\t\t\tbreak;\n\t\t\tcase \"submission\":\n\t\t\t\tkeys.push(submissionKey(write.value.id));\n\t\t\t\tif (write.value.requestId !== undefined) {\n\t\t\t\t\tkeys.push(requestKey(write.value.conversationId, write.value.requestId));\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * The Storage a job on the line reads through: every read first touches its key (`touch`), which waits while the key\n * has an unpublished writer, so the read sees what the line admitted before it. Scans that cannot be keyed touch\n * `ALL_KEYS`. Commits and `close()` pass through; the line never commits through it.\n */\nexport class GatedStorage implements Storage {\n\treadonly #storage: Storage;\n\treadonly #touch: (key: string) => Promise<void> | undefined;\n\n\tconstructor(storage: Storage, touch: (key: string) => Promise<void> | undefined) {\n\t\tthis.#storage = storage;\n\t\tthis.#touch = touch;\n\t}\n\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\treturn this.#storage.commit(batches, receive, context);\n\t}\n\n\tmintId<I extends Id<string>>(): Promise<I> {\n\t\treturn this.#storage.mintId<I>();\n\t}\n\n\tconversation(id: ConversationId, context: Context): Promise<ConversationRecord | undefined> {\n\t\treturn this.#after(conversationKey(id), () => this.#storage.conversation(id, context));\n\t}\n\n\tscanConversations(\n\t\tquery: ConversationQuery,\n\t\tlimit: number,\n\t\tcursor: Cursor | undefined,\n\t\tcontext: Context,\n\t): Promise<Page<ConversationRecord, Cursor>> {\n\t\treturn this.#after(ALL_KEYS, () => this.#storage.scanConversations(query, limit, cursor, context));\n\t}\n\n\tentry(id: EntryId, context: Context): Promise<StoredEntry | undefined>;\n\tentry(conversationId: ConversationId, id: EntryId, context: Context): Promise<StoredEntry | undefined>;\n\tentry(\n\t\tfirst: EntryId | ConversationId,\n\t\tsecond: EntryId | Context,\n\t\tthird?: Context,\n\t): Promise<StoredEntry | undefined> {\n\t\tif (third === undefined) {\n\t\t\tconst id = first as EntryId;\n\t\t\treturn this.#after(entryKey(id), () => this.#storage.entry(id, second as Context));\n\t\t}\n\t\tconst conversationId = first as ConversationId;\n\t\tconst id = second as EntryId;\n\t\t// Visibility follows the conversation's immutable ancestry; the conversation may be unpublished itself.\n\t\treturn this.#after(conversationKey(conversationId), () =>\n\t\t\tthis.#after(entryKey(id), () => this.#storage.entry(conversationId, id, third)),\n\t\t);\n\t}\n\n\tfindLatestHeadMarker(\n\t\tconversationId: ConversationId,\n\t\tatOrBeforeEntryId: EntryId | undefined,\n\t\tcontext: Context,\n\t): Promise<(EntryRecord & { readonly head: EntryId }) | undefined> {\n\t\treturn this.#afterEntries(conversationId, () =>\n\t\t\tthis.#storage.findLatestHeadMarker(conversationId, atOrBeforeEntryId, context),\n\t\t);\n\t}\n\n\tscanEntries(\n\t\tquery: EntryQuery,\n\t\tlimit: number,\n\t\tcursor: Cursor | undefined,\n\t\tcontext: Context,\n\t): Promise<Page<EntryRecord, Cursor>> {\n\t\treturn this.#afterEntries(query.conversationId, () => this.#storage.scanEntries(query, limit, cursor, context));\n\t}\n\n\ttask(id: TaskId, context: Context): Promise<TaskRecord<JsonValue, JsonValue, JsonValue> | undefined> {\n\t\treturn this.#after(taskKey(id), () => this.#storage.task(id, context));\n\t}\n\n\tscanTasks(\n\t\tquery: TaskQuery,\n\t\tlimit: number,\n\t\tcursor: Cursor | undefined,\n\t\tcontext: Context,\n\t): Promise<Page<TaskRecord<JsonValue, JsonValue, JsonValue>, Cursor>> {\n\t\treturn this.#after(ALL_KEYS, () => this.#storage.scanTasks(query, limit, cursor, context));\n\t}\n\n\tsubmission(id: SubmissionId, context: Context): Promise<SubmissionRecord | undefined> {\n\t\treturn this.#after(submissionKey(id), () => this.#storage.submission(id, context));\n\t}\n\n\tscanSubmissions(\n\t\tquery: SubmissionQuery,\n\t\tlimit: number,\n\t\tcursor: Cursor | undefined,\n\t\tcontext: Context,\n\t): Promise<Page<SubmissionRecord, Cursor>> {\n\t\treturn this.#after(ALL_KEYS, () => this.#storage.scanSubmissions(query, limit, cursor, context));\n\t}\n\n\tsubmissionByRequest(\n\t\tconversationId: ConversationId,\n\t\trequestId: string,\n\t\tcontext: Context,\n\t): Promise<SubmissionRecord | undefined> {\n\t\treturn this.#after(requestKey(conversationId, requestId), () =>\n\t\t\tthis.#storage.submissionByRequest(conversationId, requestId, context),\n\t\t);\n\t}\n\n\tfindDocument(address: DocumentAddress, at: DocumentPoint, context: Context): Promise<DocumentRecord | undefined> {\n\t\treturn this.#after(documentKey(addressId(address)), () => this.#storage.findDocument(address, at, context));\n\t}\n\n\tdocument(id: DocumentId, at: DocumentPoint, context: Context): Promise<StoredDocument | undefined> {\n\t\treturn this.#after(incarnationKey(id), () => this.#storage.document(id, at, context));\n\t}\n\n\tscanDocuments(\n\t\tquery: DocumentQuery,\n\t\tlimit: number,\n\t\tcursor: Cursor | undefined,\n\t\tcontext: Context,\n\t): Promise<Page<DocumentRecord, Cursor>> {\n\t\treturn this.#after(scopeKey(query.scope), () => this.#storage.scanDocuments(query, limit, cursor, context));\n\t}\n\n\tclose(context: Context): Promise<void> {\n\t\treturn this.#storage.close(context);\n\t}\n\n\t/**\n\t * Run a read of a conversation's entries once neither its appends nor its creation has an unpublished writer: Storage\n\t * refuses the read of a conversation it does not have yet.\n\t */\n\t#afterEntries<T>(conversationId: ConversationId, read: () => Promise<T>): Promise<T> {\n\t\treturn this.#after(conversationKey(conversationId), () => this.#after(appendKey(conversationId), read));\n\t}\n\n\t/** Run `read` once `key` has no unpublished writer. */\n\t#after<T>(key: string, read: () => Promise<T>): Promise<T> {\n\t\tconst gate = this.#touch(key);\n\t\treturn gate === undefined ? read() : gate.then(read);\n\t}\n}\n"]}
|