@earendil-works/pi-durable 0.87.0 → 0.99.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/README.md +72 -1
- package/dist/documents.d.ts +66 -0
- package/dist/documents.d.ts.map +1 -0
- package/dist/documents.js +98 -0
- package/dist/documents.js.map +1 -0
- package/dist/entries.d.ts +13 -0
- package/dist/entries.d.ts.map +1 -0
- package/dist/entries.js +18 -0
- package/dist/entries.js.map +1 -0
- package/dist/env/index.d.ts +131 -0
- package/dist/env/index.d.ts.map +1 -0
- package/dist/env/index.js +45 -0
- package/dist/env/index.js.map +1 -0
- package/dist/env/node.d.ts +45 -0
- package/dist/env/node.d.ts.map +1 -0
- package/dist/env/node.js +908 -0
- package/dist/env/node.js.map +1 -0
- package/dist/env/utils/adaptive-publisher.d.ts +24 -0
- package/dist/env/utils/adaptive-publisher.d.ts.map +1 -0
- package/dist/env/utils/adaptive-publisher.js +79 -0
- package/dist/env/utils/adaptive-publisher.js.map +1 -0
- package/dist/env/utils/output-capture.d.ts +31 -0
- package/dist/env/utils/output-capture.d.ts.map +1 -0
- package/dist/env/utils/output-capture.js +238 -0
- package/dist/env/utils/output-capture.js.map +1 -0
- package/dist/env/utils/truncate.d.ts +71 -0
- package/dist/env/utils/truncate.d.ts.map +1 -0
- package/dist/env/utils/truncate.js +294 -0
- package/dist/env/utils/truncate.js.map +1 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +24 -0
- package/dist/errors.js.map +1 -0
- package/dist/harness/config.d.ts +20 -0
- package/dist/harness/config.d.ts.map +1 -0
- package/dist/harness/config.js +18 -0
- package/dist/harness/config.js.map +1 -0
- package/dist/harness/context.d.ts +27 -0
- package/dist/harness/context.d.ts.map +1 -0
- package/dist/harness/context.js +109 -0
- package/dist/harness/context.js.map +1 -0
- package/dist/harness/generation.d.ts +36 -0
- package/dist/harness/generation.d.ts.map +1 -0
- package/dist/harness/generation.js +266 -0
- package/dist/harness/generation.js.map +1 -0
- package/dist/harness/harness.d.ts +10 -0
- package/dist/harness/harness.d.ts.map +1 -0
- package/dist/harness/harness.js +266 -0
- package/dist/harness/harness.js.map +1 -0
- package/dist/harness/live.d.ts +40 -0
- package/dist/harness/live.d.ts.map +1 -0
- package/dist/harness/live.js +41 -0
- package/dist/harness/live.js.map +1 -0
- package/dist/harness/prompt.d.ts +24 -0
- package/dist/harness/prompt.d.ts.map +1 -0
- package/dist/harness/prompt.js +88 -0
- package/dist/harness/prompt.js.map +1 -0
- package/dist/harness/registry.d.ts +7 -0
- package/dist/harness/registry.d.ts.map +1 -0
- package/dist/harness/registry.js +309 -0
- package/dist/harness/registry.js.map +1 -0
- package/dist/harness/scheduler.d.ts +63 -0
- package/dist/harness/scheduler.d.ts.map +1 -0
- package/dist/harness/scheduler.js +605 -0
- package/dist/harness/scheduler.js.map +1 -0
- package/dist/harness/submissions.d.ts +24 -0
- package/dist/harness/submissions.d.ts.map +1 -0
- package/dist/harness/submissions.js +133 -0
- package/dist/harness/submissions.js.map +1 -0
- package/dist/harness/types.d.ts +358 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +2 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/harness/util.d.ts +14 -0
- package/dist/harness/util.d.ts.map +1 -0
- package/dist/harness/util.js +55 -0
- package/dist/harness/util.js.map +1 -0
- package/dist/ids.d.ts +6 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +9 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/session/forks.d.ts +10 -0
- package/dist/session/forks.d.ts.map +1 -0
- package/dist/session/forks.js +42 -0
- package/dist/session/forks.js.map +1 -0
- package/dist/session/observation.d.ts +28 -0
- package/dist/session/observation.d.ts.map +1 -0
- package/dist/session/observation.js +280 -0
- package/dist/session/observation.js.map +1 -0
- package/dist/session/session.d.ts +59 -0
- package/dist/session/session.d.ts.map +1 -0
- package/dist/session/session.js +380 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/transaction.d.ts +105 -0
- package/dist/session/transaction.d.ts.map +1 -0
- package/dist/session/transaction.js +718 -0
- package/dist/session/transaction.js.map +1 -0
- package/dist/storage/jsonl/index.d.ts +2 -0
- package/dist/storage/jsonl/index.d.ts.map +1 -0
- package/dist/storage/jsonl/index.js +2 -0
- package/dist/storage/jsonl/index.js.map +1 -0
- package/dist/storage/jsonl/node.d.ts +6 -0
- package/dist/storage/jsonl/node.d.ts.map +1 -0
- package/dist/storage/jsonl/node.js +8 -0
- package/dist/storage/jsonl/node.js.map +1 -0
- package/dist/storage/jsonl/storage.d.ts +115 -0
- package/dist/storage/jsonl/storage.d.ts.map +1 -0
- package/dist/storage/jsonl/storage.js +681 -0
- package/dist/storage/jsonl/storage.js.map +1 -0
- package/dist/storage/memory.d.ts +60 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +681 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/storage/sqlite/database.d.ts +30 -0
- package/dist/storage/sqlite/database.d.ts.map +1 -0
- package/dist/storage/sqlite/database.js +2 -0
- package/dist/storage/sqlite/database.js.map +1 -0
- package/dist/storage/sqlite/index.d.ts +4 -0
- package/dist/storage/sqlite/index.d.ts.map +1 -0
- package/dist/storage/sqlite/index.js +3 -0
- package/dist/storage/sqlite/index.js.map +1 -0
- package/dist/storage/sqlite/migrations.d.ts +11 -0
- package/dist/storage/sqlite/migrations.d.ts.map +1 -0
- package/dist/storage/sqlite/migrations.js +111 -0
- package/dist/storage/sqlite/migrations.js.map +1 -0
- package/dist/storage/sqlite/node.d.ts +25 -0
- package/dist/storage/sqlite/node.d.ts.map +1 -0
- package/dist/storage/sqlite/node.js +97 -0
- package/dist/storage/sqlite/node.js.map +1 -0
- package/dist/storage/sqlite/storage.d.ts +51 -0
- package/dist/storage/sqlite/storage.d.ts.map +1 -0
- package/dist/storage/sqlite/storage.js +628 -0
- package/dist/storage/sqlite/storage.js.map +1 -0
- package/dist/tasks.d.ts +6 -0
- package/dist/tasks.d.ts.map +1 -0
- package/dist/tasks.js +5 -0
- package/dist/tasks.js.map +1 -0
- package/dist/testing/assertions.d.ts +5 -0
- package/dist/testing/assertions.d.ts.map +1 -0
- package/dist/testing/assertions.js +25 -0
- package/dist/testing/assertions.js.map +1 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/runner.d.ts +10 -0
- package/dist/testing/runner.d.ts.map +1 -0
- package/dist/testing/runner.js +14 -0
- package/dist/testing/runner.js.map +1 -0
- package/dist/testing/storage-benchmark.d.ts +41 -0
- package/dist/testing/storage-benchmark.d.ts.map +1 -0
- package/dist/testing/storage-benchmark.js +351 -0
- package/dist/testing/storage-benchmark.js.map +1 -0
- package/dist/testing/storage-conformance.d.ts +4 -0
- package/dist/testing/storage-conformance.d.ts.map +1 -0
- package/dist/testing/storage-conformance.js +1122 -0
- package/dist/testing/storage-conformance.js.map +1 -0
- package/dist/testing/types.d.ts +19 -0
- package/dist/testing/types.d.ts.map +1 -0
- package/dist/testing/types.js +2 -0
- package/dist/testing/types.js.map +1 -0
- package/dist/types.d.ts +525 -78
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/chord-delta-findings.md +1 -1
- package/docs/pico-v5-chord-usage.md +90 -102
- package/docs/pico-v5-handoff.md +493 -253
- package/docs/pico-v5.md +1901 -848
- package/package.json +47 -5
- package/dist/memory-storage.d.ts +0 -37
- package/dist/memory-storage.d.ts.map +0 -1
- package/dist/memory-storage.js +0 -322
- package/dist/memory-storage.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-durable",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.99.0",
|
|
4
4
|
"description": "Durable conversation, task, and document runtime for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,6 +11,46 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js"
|
|
13
13
|
},
|
|
14
|
+
"./env": {
|
|
15
|
+
"source": "./src/env/index.ts",
|
|
16
|
+
"types": "./dist/env/index.d.ts",
|
|
17
|
+
"import": "./dist/env/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./env/node": {
|
|
20
|
+
"source": "./src/env/node.ts",
|
|
21
|
+
"types": "./dist/env/node.d.ts",
|
|
22
|
+
"import": "./dist/env/node.js"
|
|
23
|
+
},
|
|
24
|
+
"./storage/memory": {
|
|
25
|
+
"source": "./src/storage/memory.ts",
|
|
26
|
+
"types": "./dist/storage/memory.d.ts",
|
|
27
|
+
"import": "./dist/storage/memory.js"
|
|
28
|
+
},
|
|
29
|
+
"./storage/jsonl": {
|
|
30
|
+
"source": "./src/storage/jsonl/index.ts",
|
|
31
|
+
"types": "./dist/storage/jsonl/index.d.ts",
|
|
32
|
+
"import": "./dist/storage/jsonl/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./storage/jsonl/node": {
|
|
35
|
+
"source": "./src/storage/jsonl/node.ts",
|
|
36
|
+
"types": "./dist/storage/jsonl/node.d.ts",
|
|
37
|
+
"import": "./dist/storage/jsonl/node.js"
|
|
38
|
+
},
|
|
39
|
+
"./storage/sqlite": {
|
|
40
|
+
"source": "./src/storage/sqlite/index.ts",
|
|
41
|
+
"types": "./dist/storage/sqlite/index.d.ts",
|
|
42
|
+
"import": "./dist/storage/sqlite/index.js"
|
|
43
|
+
},
|
|
44
|
+
"./storage/sqlite/node": {
|
|
45
|
+
"source": "./src/storage/sqlite/node.ts",
|
|
46
|
+
"types": "./dist/storage/sqlite/node.d.ts",
|
|
47
|
+
"import": "./dist/storage/sqlite/node.js"
|
|
48
|
+
},
|
|
49
|
+
"./testing": {
|
|
50
|
+
"source": "./src/testing/index.ts",
|
|
51
|
+
"types": "./dist/testing/index.d.ts",
|
|
52
|
+
"import": "./dist/testing/index.js"
|
|
53
|
+
},
|
|
14
54
|
"./package.json": "./package.json"
|
|
15
55
|
},
|
|
16
56
|
"sideEffects": false,
|
|
@@ -21,8 +61,10 @@
|
|
|
21
61
|
],
|
|
22
62
|
"scripts": {
|
|
23
63
|
"clean": "shx rm -rf dist",
|
|
24
|
-
"build": "
|
|
64
|
+
"build": "tsc -p tsconfig.build.json",
|
|
25
65
|
"test": "vitest --run",
|
|
66
|
+
"bench:storage": "vitest bench --config vitest.benchmark.config.ts",
|
|
67
|
+
"bench:storage:memory": "node --conditions=source --experimental-strip-types test/storage-memory.ts",
|
|
26
68
|
"prepublishOnly": "npm run clean && npm run build"
|
|
27
69
|
},
|
|
28
70
|
"keywords": [
|
|
@@ -42,11 +84,11 @@
|
|
|
42
84
|
"node": ">=22.19.0"
|
|
43
85
|
},
|
|
44
86
|
"dependencies": {
|
|
45
|
-
"@earendil-works/chord": "^0.
|
|
46
|
-
"@earendil-works/pi-ai": "^0.
|
|
87
|
+
"@earendil-works/chord": "^0.99.0",
|
|
88
|
+
"@earendil-works/pi-ai": "^0.99.0"
|
|
47
89
|
},
|
|
48
90
|
"devDependencies": {
|
|
49
91
|
"shx": "0.4.0",
|
|
50
|
-
"vitest": "4.1.
|
|
92
|
+
"vitest": "4.1.11"
|
|
51
93
|
}
|
|
52
94
|
}
|
package/dist/memory-storage.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { Context, JsonValue } from "@earendil-works/chord";
|
|
2
|
-
import type { ConversationRecord, Cursor, EntryQuery, EntryRecord, Id, Input, Page, Seq, Storage, StorageWrite, TaskQuery, TaskRecord } from "./types.ts";
|
|
3
|
-
type StoredTask = TaskRecord<JsonValue, JsonValue, JsonValue>;
|
|
4
|
-
/**
|
|
5
|
-
* Detached in-memory reference implementation of `Storage`.
|
|
6
|
-
*
|
|
7
|
-
* Reads and retained writes are cloned intentionally to match the ownership boundary
|
|
8
|
-
* of serialization-backed stores. This is backend conformance, not validation.
|
|
9
|
-
*/
|
|
10
|
-
export declare class MemoryStorage implements Storage {
|
|
11
|
-
private readonly state;
|
|
12
|
-
private nextId;
|
|
13
|
-
private nextSeq;
|
|
14
|
-
private closed;
|
|
15
|
-
commit(writes: readonly StorageWrite[], _context: Context): Promise<Seq>;
|
|
16
|
-
mintId(): Id;
|
|
17
|
-
conversation(id: Id, _context: Context): Promise<ConversationRecord | undefined>;
|
|
18
|
-
scanConversations(cursor: Cursor | undefined, limit: number, _context: Context): Promise<Page<ConversationRecord, Cursor>>;
|
|
19
|
-
entry(id: Id, _context: Context): Promise<{
|
|
20
|
-
readonly entry: EntryRecord;
|
|
21
|
-
readonly commitSeq: Seq;
|
|
22
|
-
} | undefined>;
|
|
23
|
-
findLatestHeadMarker(conversationId: Id, atOrBeforeEntryId: Id | undefined, _context: Context): Promise<(EntryRecord & {
|
|
24
|
-
readonly head: Id;
|
|
25
|
-
}) | undefined>;
|
|
26
|
-
scanEntries(query: EntryQuery, cursor: Cursor | undefined, limit: number, _context: Context): Promise<Page<EntryRecord, Cursor>>;
|
|
27
|
-
task(id: Id, _context: Context): Promise<StoredTask | undefined>;
|
|
28
|
-
scanTasks(query: TaskQuery, cursor: Cursor | undefined, limit: number, _context: Context): Promise<Page<StoredTask, Cursor>>;
|
|
29
|
-
input(id: Id, _context: Context): Promise<Input | undefined>;
|
|
30
|
-
inputByRequest(conversationId: Id, requestId: string, _context: Context): Promise<Input | undefined>;
|
|
31
|
-
close(_context: Context): Promise<void>;
|
|
32
|
-
private visibleEntries;
|
|
33
|
-
private checkImmutableIds;
|
|
34
|
-
private assertOpen;
|
|
35
|
-
}
|
|
36
|
-
export {};
|
|
37
|
-
//# sourceMappingURL=memory-storage.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-storage.d.ts","sourceRoot":"","sources":["../src/memory-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,KAAK,EACX,kBAAkB,EAClB,MAAM,EACN,UAAU,EACV,WAAW,EACX,EAAE,EACF,KAAK,EACL,IAAI,EACJ,GAAG,EACH,OAAO,EACP,YAAY,EACZ,SAAS,EACT,UAAU,EACV,MAAM,YAAY,CAAC;AAEpB,KAAK,UAAU,GAAG,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AAyF9D;;;;;GAKG;AACH,qBAAa,aAAc,YAAW,OAAO;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAYpB;IACF,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,MAAM,CAAS;IAEjB,MAAM,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAoE7E;IAED,MAAM,IAAI,EAAE,CAIX;IAEK,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAIrF;IAEK,iBAAiB,CACtB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAQ3C;IAEK,KAAK,CACV,EAAE,EAAE,EAAE,EACN,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC;QAAE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAA;KAAE,GAAG,SAAS,CAAC,CAK/E;IAEK,oBAAoB,CACzB,cAAc,EAAE,EAAE,EAClB,iBAAiB,EAAE,EAAE,GAAG,SAAS,EACjC,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,CAAC,WAAW,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC,CAmB5D;IAEK,WAAW,CAChB,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAWpC;IAEK,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAIrE;IAEK,SAAS,CACd,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,OAAO,GACf,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAenC;IAEK,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAIjE;IAEK,cAAc,CAAC,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAKzG;IAEK,KAAK,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5C;IAED,OAAO,CAAE,cAAc;IAyBvB,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,UAAU;CAGlB","sourcesContent":["import type { Context, JsonValue } from \"@earendil-works/chord\";\nimport type {\n\tConversationRecord,\n\tCursor,\n\tEntryQuery,\n\tEntryRecord,\n\tId,\n\tInput,\n\tPage,\n\tSeq,\n\tStorage,\n\tStorageWrite,\n\tTaskQuery,\n\tTaskRecord,\n} from \"./types.ts\";\n\ntype StoredTask = TaskRecord<JsonValue, JsonValue, JsonValue>;\ntype TaskStatus = StoredTask[\"state\"][\"status\"];\ntype TableName = \"conversation\" | \"entry\" | \"task\" | \"input\";\n\ntype State = {\n\tconversations: Map<Id, ConversationRecord>;\n\tconversationIds: Id[];\n\tentries: Map<Id, EntryRecord>;\n\tentryIds: Map<Id, Id[]>;\n\theadEntryIds: Map<Id, Id[]>;\n\tentryCommitSeqs: Map<Id, Seq>;\n\ttasks: Map<Id, StoredTask>;\n\ttaskIds: Id[];\n\ttaskIdsByStatus: Record<TaskStatus, Id[]>;\n\tinputs: Map<Id, Input>;\n\tinputIdsByRequest: Map<Id, Map<string, Id>>;\n};\n\nconst clone = <T>(value: T): T => {\n\tif (value === null || typeof value !== \"object\") return value;\n\tif (Array.isArray(value)) return value.map((item) => clone(item)) as T;\n\tconst source = value as Record<string, unknown>;\n\tconst nullPrototype = Object.getPrototypeOf(value) === null;\n\tconst result = (nullPrototype ? Object.create(null) : {}) as Record<string, unknown>;\n\tfor (const key of Object.keys(source)) {\n\t\tconst copied = clone(source[key]);\n\t\tif (!nullPrototype && key in result) {\n\t\t\tObject.defineProperty(result, key, {\n\t\t\t\tvalue: copied,\n\t\t\t\twritable: true,\n\t\t\t\tenumerable: true,\n\t\t\t\tconfigurable: true,\n\t\t\t});\n\t\t} else {\n\t\t\tresult[key] = copied;\n\t\t}\n\t}\n\treturn result as T;\n};\n\nconst cursorId = (cursor: Readonly<Record<string, JsonValue>> | undefined): Id | undefined =>\n\tcursor?.after as Id | undefined;\n\nconst lowerBound = (ids: readonly Id[], target: Id): number => {\n\tlet low = 0;\n\tlet high = ids.length;\n\twhile (low < high) {\n\t\tconst middle = (low + high) >>> 1;\n\t\tif (ids[middle] < target) low = middle + 1;\n\t\telse high = middle;\n\t}\n\treturn low;\n};\n\nconst upperBound = (ids: readonly Id[], target: Id): number => {\n\tlet low = 0;\n\tlet high = ids.length;\n\twhile (low < high) {\n\t\tconst middle = (low + high) >>> 1;\n\t\tif (ids[middle] <= target) low = middle + 1;\n\t\telse high = middle;\n\t}\n\treturn low;\n};\n\nconst insertSorted = (ids: Id[], id: Id): void => {\n\tif (ids.length === 0 || ids[ids.length - 1] < id) ids.push(id);\n\telse ids.splice(lowerBound(ids, id), 0, id);\n};\n\nconst removeSorted = (ids: Id[], id: Id): void => {\n\tconst index = lowerBound(ids, id);\n\tif (ids[index] === id) ids.splice(index, 1);\n};\n\nconst tableContaining = (state: State, id: Id): TableName | undefined => {\n\tif (state.conversations.has(id)) return \"conversation\";\n\tif (state.entries.has(id)) return \"entry\";\n\tif (state.tasks.has(id)) return \"task\";\n\tif (state.inputs.has(id)) return \"input\";\n\treturn undefined;\n};\n\nconst page = <T extends { readonly id: Id }>(values: readonly T[], limit: number): Page<T, Cursor> => {\n\tconst items = values.slice(0, limit);\n\tif (values.length <= limit) return { items: clone(items) };\n\treturn { items: clone(items), next: { after: items.at(-1)!.id } };\n};\n\n/**\n * Detached in-memory reference implementation of `Storage`.\n *\n * Reads and retained writes are cloned intentionally to match the ownership boundary\n * of serialization-backed stores. This is backend conformance, not validation.\n */\nexport class MemoryStorage implements Storage {\n\tprivate readonly state: State = {\n\t\tconversations: new Map(),\n\t\tconversationIds: [],\n\t\tentries: new Map(),\n\t\tentryIds: new Map(),\n\t\theadEntryIds: new Map(),\n\t\tentryCommitSeqs: new Map(),\n\t\ttasks: new Map(),\n\t\ttaskIds: [],\n\t\ttaskIdsByStatus: { pending: [], running: [], terminal: [] },\n\t\tinputs: new Map(),\n\t\tinputIdsByRequest: new Map(),\n\t};\n\tprivate nextId = 2;\n\tprivate nextSeq = 1;\n\tprivate closed = false;\n\n\tasync commit(writes: readonly StorageWrite[], _context: Context): Promise<Seq> {\n\t\tthis.assertOpen();\n\t\tconst prepared = writes.map((write) => clone(write));\n\t\tthis.checkImmutableIds(prepared);\n\t\tconst seq = this.nextSeq;\n\n\t\tfor (const write of prepared) {\n\t\t\tswitch (write.type) {\n\t\t\t\tcase \"conversation\":\n\t\t\t\t\tthis.state.conversations.set(write.value.id, write.value);\n\t\t\t\t\tinsertSorted(this.state.conversationIds, write.value.id);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"entry\": {\n\t\t\t\t\tthis.state.entries.set(write.value.id, write.value);\n\t\t\t\t\tthis.state.entryCommitSeqs.set(write.value.id, seq);\n\t\t\t\t\tlet ids = this.state.entryIds.get(write.value.conversationId);\n\t\t\t\t\tif (ids === undefined) {\n\t\t\t\t\t\tids = [];\n\t\t\t\t\t\tthis.state.entryIds.set(write.value.conversationId, ids);\n\t\t\t\t\t}\n\t\t\t\t\tinsertSorted(ids, write.value.id);\n\t\t\t\t\tif (write.value.head !== undefined) {\n\t\t\t\t\t\tlet headIds = this.state.headEntryIds.get(write.value.conversationId);\n\t\t\t\t\t\tif (headIds === undefined) {\n\t\t\t\t\t\t\theadIds = [];\n\t\t\t\t\t\t\tthis.state.headEntryIds.set(write.value.conversationId, headIds);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinsertSorted(headIds, write.value.id);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"task\": {\n\t\t\t\t\tconst previous = this.state.tasks.get(write.value.id);\n\t\t\t\t\tif (previous === undefined) {\n\t\t\t\t\t\tinsertSorted(this.state.taskIds, write.value.id);\n\t\t\t\t\t\tinsertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);\n\t\t\t\t\t} else if (previous.state.status !== write.value.state.status) {\n\t\t\t\t\t\tremoveSorted(this.state.taskIdsByStatus[previous.state.status], write.value.id);\n\t\t\t\t\t\tinsertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);\n\t\t\t\t\t}\n\t\t\t\t\tthis.state.tasks.set(write.value.id, write.value);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"input\": {\n\t\t\t\t\tconst previous = this.state.inputs.get(write.value.id);\n\t\t\t\t\tif (previous?.requestId !== undefined) {\n\t\t\t\t\t\tconst previousRequests = this.state.inputIdsByRequest.get(previous.conversationId);\n\t\t\t\t\t\tif (previousRequests?.get(previous.requestId) === write.value.id) {\n\t\t\t\t\t\t\tpreviousRequests.delete(previous.requestId);\n\t\t\t\t\t\t\tif (previousRequests.size === 0) this.state.inputIdsByRequest.delete(previous.conversationId);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.state.inputs.set(write.value.id, write.value);\n\t\t\t\t\tif (write.value.requestId !== undefined) {\n\t\t\t\t\t\tlet requests = this.state.inputIdsByRequest.get(write.value.conversationId);\n\t\t\t\t\t\tif (requests === undefined) {\n\t\t\t\t\t\t\trequests = new Map();\n\t\t\t\t\t\t\tthis.state.inputIdsByRequest.set(write.value.conversationId, requests);\n\t\t\t\t\t\t}\n\t\t\t\t\t\trequests.set(write.value.requestId, write.value.id);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.nextId = Math.max(this.nextId, write.value.id + 1);\n\t\t}\n\t\tthis.nextSeq++;\n\t\treturn seq;\n\t}\n\n\tmintId(): Id {\n\t\tthis.assertOpen();\n\t\tif (!Number.isSafeInteger(this.nextId)) throw new Error(\"ID space is exhausted\");\n\t\treturn this.nextId++;\n\t}\n\n\tasync conversation(id: Id, _context: Context): Promise<ConversationRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst value = this.state.conversations.get(id);\n\t\treturn value === undefined ? undefined : clone(value);\n\t}\n\n\tasync scanConversations(\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<ConversationRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor);\n\t\tconst start = after === undefined ? 0 : upperBound(this.state.conversationIds, after);\n\t\tconst values = this.state.conversationIds\n\t\t\t.slice(start, start + limit + 1)\n\t\t\t.map((id) => this.state.conversations.get(id)!);\n\t\treturn page(values, limit);\n\t}\n\n\tasync entry(\n\t\tid: Id,\n\t\t_context: Context,\n\t): Promise<{ readonly entry: EntryRecord; readonly commitSeq: Seq } | undefined> {\n\t\tthis.assertOpen();\n\t\tconst entry = this.state.entries.get(id);\n\t\tif (entry === undefined) return undefined;\n\t\treturn { entry: clone(entry), commitSeq: this.state.entryCommitSeqs.get(id)! };\n\t}\n\n\tasync findLatestHeadMarker(\n\t\tconversationId: Id,\n\t\tatOrBeforeEntryId: Id | undefined,\n\t\t_context: Context,\n\t): Promise<(EntryRecord & { readonly head: Id }) | undefined> {\n\t\tthis.assertOpen();\n\t\tif (!this.state.conversations.has(conversationId)) {\n\t\t\tthrow new Error(`Unknown conversation: ${conversationId}`);\n\t\t}\n\t\tlet currentId = conversationId;\n\t\tlet upperEntryId = atOrBeforeEntryId ?? Number.POSITIVE_INFINITY;\n\t\twhile (true) {\n\t\t\tconst ids = this.state.headEntryIds.get(currentId) ?? [];\n\t\t\tconst index = upperBound(ids, upperEntryId) - 1;\n\t\t\tif (index >= 0) {\n\t\t\t\tconst entry = this.state.entries.get(ids[index])!;\n\t\t\t\treturn clone({ ...entry, head: entry.head! });\n\t\t\t}\n\t\t\tconst conversation = this.state.conversations.get(currentId)!;\n\t\t\tif (conversation.parent === undefined) return undefined;\n\t\t\tupperEntryId = Math.min(upperEntryId, conversation.parent.at);\n\t\t\tcurrentId = conversation.parent.conversationId;\n\t\t}\n\t}\n\n\tasync scanEntries(\n\t\tquery: EntryQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<EntryRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor);\n\t\tconst maxEntryId =\n\t\t\tafter === undefined ? query.maxEntryId : Math.min(query.maxEntryId ?? Number.POSITIVE_INFINITY, after - 1);\n\t\tconst visible: EntryRecord[] = [];\n\t\tfor (const entry of this.visibleEntries(query.conversationId, query.minEntryId, maxEntryId)) {\n\t\t\tvisible.push(entry);\n\t\t\tif (visible.length > limit) break;\n\t\t}\n\t\treturn page(visible, limit);\n\t}\n\n\tasync task(id: Id, _context: Context): Promise<StoredTask | undefined> {\n\t\tthis.assertOpen();\n\t\tconst value = this.state.tasks.get(id);\n\t\treturn value === undefined ? undefined : clone(value);\n\t}\n\n\tasync scanTasks(\n\t\tquery: TaskQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<StoredTask, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor);\n\t\tconst ids = query.status === undefined ? this.state.taskIds : this.state.taskIdsByStatus[query.status];\n\t\tconst start = after === undefined ? 0 : upperBound(ids, after);\n\t\tconst values: StoredTask[] = [];\n\t\tfor (let index = start; index < ids.length && values.length <= limit; index++) {\n\t\t\tconst value = this.state.tasks.get(ids[index])!;\n\t\t\tif (query.conversationId !== undefined && value.conversationId !== query.conversationId) continue;\n\t\t\tif (query.kind !== undefined && value.kind !== query.kind) continue;\n\t\t\tif (query.abortRequested !== undefined && value.abortRequested !== query.abortRequested) continue;\n\t\t\tif (query.background !== undefined && value.background !== query.background) continue;\n\t\t\tvalues.push(value);\n\t\t}\n\t\treturn page(values, limit);\n\t}\n\n\tasync input(id: Id, _context: Context): Promise<Input | undefined> {\n\t\tthis.assertOpen();\n\t\tconst value = this.state.inputs.get(id);\n\t\treturn value === undefined ? undefined : clone(value);\n\t}\n\n\tasync inputByRequest(conversationId: Id, requestId: string, _context: Context): Promise<Input | undefined> {\n\t\tthis.assertOpen();\n\t\tconst id = this.state.inputIdsByRequest.get(conversationId)?.get(requestId);\n\t\tif (id === undefined) return undefined;\n\t\treturn clone(this.state.inputs.get(id)!);\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tthis.closed = true;\n\t}\n\n\tprivate *visibleEntries(\n\t\tconversationId: Id,\n\t\tminEntryId = Number.NEGATIVE_INFINITY,\n\t\tmaxEntryId = Number.POSITIVE_INFINITY,\n\t): Generator<EntryRecord> {\n\t\tif (!this.state.conversations.has(conversationId)) {\n\t\t\tthrow new Error(`Unknown conversation: ${conversationId}`);\n\t\t}\n\t\tlet currentId = conversationId;\n\t\tlet upperEntryId = maxEntryId;\n\t\twhile (true) {\n\t\t\tconst ids = this.state.entryIds.get(currentId) ?? [];\n\t\t\tfor (let index = upperBound(ids, upperEntryId) - 1; index >= 0; index--) {\n\t\t\t\tconst id = ids[index];\n\t\t\t\tif (id < minEntryId) break;\n\t\t\t\tyield this.state.entries.get(id)!;\n\t\t\t}\n\t\t\tconst conversation = this.state.conversations.get(currentId)!;\n\t\t\tif (conversation.parent === undefined) break;\n\t\t\tupperEntryId = Math.min(upperEntryId, conversation.parent.at);\n\t\t\tif (upperEntryId < minEntryId) break;\n\t\t\tcurrentId = conversation.parent.conversationId;\n\t\t}\n\t}\n\n\tprivate checkImmutableIds(writes: readonly StorageWrite[]): void {\n\t\tconst claimed = new Map<Id, TableName>();\n\t\tfor (const write of writes) {\n\t\t\tconst table = write.type;\n\t\t\tconst id = write.value.id;\n\t\t\tconst existing = tableContaining(this.state, id);\n\t\t\tconst earlier = claimed.get(id);\n\t\t\tif (table === \"conversation\" || table === \"entry\") {\n\t\t\t\tif (existing !== undefined) throw new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\tif (earlier !== undefined) throw new Error(`ID ${id} is written more than once`);\n\t\t\t} else {\n\t\t\t\tif (existing !== undefined && existing !== table) {\n\t\t\t\t\tthrow new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\t}\n\t\t\t\tif (earlier !== undefined && earlier !== table) throw new Error(`ID ${id} is written as two record types`);\n\t\t\t}\n\t\t\tclaimed.set(id, table);\n\t\t}\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (this.closed) throw new Error(\"MemoryStorage is closed\");\n\t}\n}\n"]}
|
package/dist/memory-storage.js
DELETED
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
const clone = (value) => {
|
|
2
|
-
if (value === null || typeof value !== "object")
|
|
3
|
-
return value;
|
|
4
|
-
if (Array.isArray(value))
|
|
5
|
-
return value.map((item) => clone(item));
|
|
6
|
-
const source = value;
|
|
7
|
-
const nullPrototype = Object.getPrototypeOf(value) === null;
|
|
8
|
-
const result = (nullPrototype ? Object.create(null) : {});
|
|
9
|
-
for (const key of Object.keys(source)) {
|
|
10
|
-
const copied = clone(source[key]);
|
|
11
|
-
if (!nullPrototype && key in result) {
|
|
12
|
-
Object.defineProperty(result, key, {
|
|
13
|
-
value: copied,
|
|
14
|
-
writable: true,
|
|
15
|
-
enumerable: true,
|
|
16
|
-
configurable: true,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
result[key] = copied;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
const cursorId = (cursor) => cursor?.after;
|
|
26
|
-
const lowerBound = (ids, target) => {
|
|
27
|
-
let low = 0;
|
|
28
|
-
let high = ids.length;
|
|
29
|
-
while (low < high) {
|
|
30
|
-
const middle = (low + high) >>> 1;
|
|
31
|
-
if (ids[middle] < target)
|
|
32
|
-
low = middle + 1;
|
|
33
|
-
else
|
|
34
|
-
high = middle;
|
|
35
|
-
}
|
|
36
|
-
return low;
|
|
37
|
-
};
|
|
38
|
-
const upperBound = (ids, target) => {
|
|
39
|
-
let low = 0;
|
|
40
|
-
let high = ids.length;
|
|
41
|
-
while (low < high) {
|
|
42
|
-
const middle = (low + high) >>> 1;
|
|
43
|
-
if (ids[middle] <= target)
|
|
44
|
-
low = middle + 1;
|
|
45
|
-
else
|
|
46
|
-
high = middle;
|
|
47
|
-
}
|
|
48
|
-
return low;
|
|
49
|
-
};
|
|
50
|
-
const insertSorted = (ids, id) => {
|
|
51
|
-
if (ids.length === 0 || ids[ids.length - 1] < id)
|
|
52
|
-
ids.push(id);
|
|
53
|
-
else
|
|
54
|
-
ids.splice(lowerBound(ids, id), 0, id);
|
|
55
|
-
};
|
|
56
|
-
const removeSorted = (ids, id) => {
|
|
57
|
-
const index = lowerBound(ids, id);
|
|
58
|
-
if (ids[index] === id)
|
|
59
|
-
ids.splice(index, 1);
|
|
60
|
-
};
|
|
61
|
-
const tableContaining = (state, id) => {
|
|
62
|
-
if (state.conversations.has(id))
|
|
63
|
-
return "conversation";
|
|
64
|
-
if (state.entries.has(id))
|
|
65
|
-
return "entry";
|
|
66
|
-
if (state.tasks.has(id))
|
|
67
|
-
return "task";
|
|
68
|
-
if (state.inputs.has(id))
|
|
69
|
-
return "input";
|
|
70
|
-
return undefined;
|
|
71
|
-
};
|
|
72
|
-
const page = (values, limit) => {
|
|
73
|
-
const items = values.slice(0, limit);
|
|
74
|
-
if (values.length <= limit)
|
|
75
|
-
return { items: clone(items) };
|
|
76
|
-
return { items: clone(items), next: { after: items.at(-1).id } };
|
|
77
|
-
};
|
|
78
|
-
/**
|
|
79
|
-
* Detached in-memory reference implementation of `Storage`.
|
|
80
|
-
*
|
|
81
|
-
* Reads and retained writes are cloned intentionally to match the ownership boundary
|
|
82
|
-
* of serialization-backed stores. This is backend conformance, not validation.
|
|
83
|
-
*/
|
|
84
|
-
export class MemoryStorage {
|
|
85
|
-
state = {
|
|
86
|
-
conversations: new Map(),
|
|
87
|
-
conversationIds: [],
|
|
88
|
-
entries: new Map(),
|
|
89
|
-
entryIds: new Map(),
|
|
90
|
-
headEntryIds: new Map(),
|
|
91
|
-
entryCommitSeqs: new Map(),
|
|
92
|
-
tasks: new Map(),
|
|
93
|
-
taskIds: [],
|
|
94
|
-
taskIdsByStatus: { pending: [], running: [], terminal: [] },
|
|
95
|
-
inputs: new Map(),
|
|
96
|
-
inputIdsByRequest: new Map(),
|
|
97
|
-
};
|
|
98
|
-
nextId = 2;
|
|
99
|
-
nextSeq = 1;
|
|
100
|
-
closed = false;
|
|
101
|
-
async commit(writes, _context) {
|
|
102
|
-
this.assertOpen();
|
|
103
|
-
const prepared = writes.map((write) => clone(write));
|
|
104
|
-
this.checkImmutableIds(prepared);
|
|
105
|
-
const seq = this.nextSeq;
|
|
106
|
-
for (const write of prepared) {
|
|
107
|
-
switch (write.type) {
|
|
108
|
-
case "conversation":
|
|
109
|
-
this.state.conversations.set(write.value.id, write.value);
|
|
110
|
-
insertSorted(this.state.conversationIds, write.value.id);
|
|
111
|
-
break;
|
|
112
|
-
case "entry": {
|
|
113
|
-
this.state.entries.set(write.value.id, write.value);
|
|
114
|
-
this.state.entryCommitSeqs.set(write.value.id, seq);
|
|
115
|
-
let ids = this.state.entryIds.get(write.value.conversationId);
|
|
116
|
-
if (ids === undefined) {
|
|
117
|
-
ids = [];
|
|
118
|
-
this.state.entryIds.set(write.value.conversationId, ids);
|
|
119
|
-
}
|
|
120
|
-
insertSorted(ids, write.value.id);
|
|
121
|
-
if (write.value.head !== undefined) {
|
|
122
|
-
let headIds = this.state.headEntryIds.get(write.value.conversationId);
|
|
123
|
-
if (headIds === undefined) {
|
|
124
|
-
headIds = [];
|
|
125
|
-
this.state.headEntryIds.set(write.value.conversationId, headIds);
|
|
126
|
-
}
|
|
127
|
-
insertSorted(headIds, write.value.id);
|
|
128
|
-
}
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case "task": {
|
|
132
|
-
const previous = this.state.tasks.get(write.value.id);
|
|
133
|
-
if (previous === undefined) {
|
|
134
|
-
insertSorted(this.state.taskIds, write.value.id);
|
|
135
|
-
insertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);
|
|
136
|
-
}
|
|
137
|
-
else if (previous.state.status !== write.value.state.status) {
|
|
138
|
-
removeSorted(this.state.taskIdsByStatus[previous.state.status], write.value.id);
|
|
139
|
-
insertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);
|
|
140
|
-
}
|
|
141
|
-
this.state.tasks.set(write.value.id, write.value);
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
case "input": {
|
|
145
|
-
const previous = this.state.inputs.get(write.value.id);
|
|
146
|
-
if (previous?.requestId !== undefined) {
|
|
147
|
-
const previousRequests = this.state.inputIdsByRequest.get(previous.conversationId);
|
|
148
|
-
if (previousRequests?.get(previous.requestId) === write.value.id) {
|
|
149
|
-
previousRequests.delete(previous.requestId);
|
|
150
|
-
if (previousRequests.size === 0)
|
|
151
|
-
this.state.inputIdsByRequest.delete(previous.conversationId);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
this.state.inputs.set(write.value.id, write.value);
|
|
155
|
-
if (write.value.requestId !== undefined) {
|
|
156
|
-
let requests = this.state.inputIdsByRequest.get(write.value.conversationId);
|
|
157
|
-
if (requests === undefined) {
|
|
158
|
-
requests = new Map();
|
|
159
|
-
this.state.inputIdsByRequest.set(write.value.conversationId, requests);
|
|
160
|
-
}
|
|
161
|
-
requests.set(write.value.requestId, write.value.id);
|
|
162
|
-
}
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
this.nextId = Math.max(this.nextId, write.value.id + 1);
|
|
167
|
-
}
|
|
168
|
-
this.nextSeq++;
|
|
169
|
-
return seq;
|
|
170
|
-
}
|
|
171
|
-
mintId() {
|
|
172
|
-
this.assertOpen();
|
|
173
|
-
if (!Number.isSafeInteger(this.nextId))
|
|
174
|
-
throw new Error("ID space is exhausted");
|
|
175
|
-
return this.nextId++;
|
|
176
|
-
}
|
|
177
|
-
async conversation(id, _context) {
|
|
178
|
-
this.assertOpen();
|
|
179
|
-
const value = this.state.conversations.get(id);
|
|
180
|
-
return value === undefined ? undefined : clone(value);
|
|
181
|
-
}
|
|
182
|
-
async scanConversations(cursor, limit, _context) {
|
|
183
|
-
this.assertOpen();
|
|
184
|
-
const after = cursorId(cursor);
|
|
185
|
-
const start = after === undefined ? 0 : upperBound(this.state.conversationIds, after);
|
|
186
|
-
const values = this.state.conversationIds
|
|
187
|
-
.slice(start, start + limit + 1)
|
|
188
|
-
.map((id) => this.state.conversations.get(id));
|
|
189
|
-
return page(values, limit);
|
|
190
|
-
}
|
|
191
|
-
async entry(id, _context) {
|
|
192
|
-
this.assertOpen();
|
|
193
|
-
const entry = this.state.entries.get(id);
|
|
194
|
-
if (entry === undefined)
|
|
195
|
-
return undefined;
|
|
196
|
-
return { entry: clone(entry), commitSeq: this.state.entryCommitSeqs.get(id) };
|
|
197
|
-
}
|
|
198
|
-
async findLatestHeadMarker(conversationId, atOrBeforeEntryId, _context) {
|
|
199
|
-
this.assertOpen();
|
|
200
|
-
if (!this.state.conversations.has(conversationId)) {
|
|
201
|
-
throw new Error(`Unknown conversation: ${conversationId}`);
|
|
202
|
-
}
|
|
203
|
-
let currentId = conversationId;
|
|
204
|
-
let upperEntryId = atOrBeforeEntryId ?? Number.POSITIVE_INFINITY;
|
|
205
|
-
while (true) {
|
|
206
|
-
const ids = this.state.headEntryIds.get(currentId) ?? [];
|
|
207
|
-
const index = upperBound(ids, upperEntryId) - 1;
|
|
208
|
-
if (index >= 0) {
|
|
209
|
-
const entry = this.state.entries.get(ids[index]);
|
|
210
|
-
return clone({ ...entry, head: entry.head });
|
|
211
|
-
}
|
|
212
|
-
const conversation = this.state.conversations.get(currentId);
|
|
213
|
-
if (conversation.parent === undefined)
|
|
214
|
-
return undefined;
|
|
215
|
-
upperEntryId = Math.min(upperEntryId, conversation.parent.at);
|
|
216
|
-
currentId = conversation.parent.conversationId;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
async scanEntries(query, cursor, limit, _context) {
|
|
220
|
-
this.assertOpen();
|
|
221
|
-
const after = cursorId(cursor);
|
|
222
|
-
const maxEntryId = after === undefined ? query.maxEntryId : Math.min(query.maxEntryId ?? Number.POSITIVE_INFINITY, after - 1);
|
|
223
|
-
const visible = [];
|
|
224
|
-
for (const entry of this.visibleEntries(query.conversationId, query.minEntryId, maxEntryId)) {
|
|
225
|
-
visible.push(entry);
|
|
226
|
-
if (visible.length > limit)
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
return page(visible, limit);
|
|
230
|
-
}
|
|
231
|
-
async task(id, _context) {
|
|
232
|
-
this.assertOpen();
|
|
233
|
-
const value = this.state.tasks.get(id);
|
|
234
|
-
return value === undefined ? undefined : clone(value);
|
|
235
|
-
}
|
|
236
|
-
async scanTasks(query, cursor, limit, _context) {
|
|
237
|
-
this.assertOpen();
|
|
238
|
-
const after = cursorId(cursor);
|
|
239
|
-
const ids = query.status === undefined ? this.state.taskIds : this.state.taskIdsByStatus[query.status];
|
|
240
|
-
const start = after === undefined ? 0 : upperBound(ids, after);
|
|
241
|
-
const values = [];
|
|
242
|
-
for (let index = start; index < ids.length && values.length <= limit; index++) {
|
|
243
|
-
const value = this.state.tasks.get(ids[index]);
|
|
244
|
-
if (query.conversationId !== undefined && value.conversationId !== query.conversationId)
|
|
245
|
-
continue;
|
|
246
|
-
if (query.kind !== undefined && value.kind !== query.kind)
|
|
247
|
-
continue;
|
|
248
|
-
if (query.abortRequested !== undefined && value.abortRequested !== query.abortRequested)
|
|
249
|
-
continue;
|
|
250
|
-
if (query.background !== undefined && value.background !== query.background)
|
|
251
|
-
continue;
|
|
252
|
-
values.push(value);
|
|
253
|
-
}
|
|
254
|
-
return page(values, limit);
|
|
255
|
-
}
|
|
256
|
-
async input(id, _context) {
|
|
257
|
-
this.assertOpen();
|
|
258
|
-
const value = this.state.inputs.get(id);
|
|
259
|
-
return value === undefined ? undefined : clone(value);
|
|
260
|
-
}
|
|
261
|
-
async inputByRequest(conversationId, requestId, _context) {
|
|
262
|
-
this.assertOpen();
|
|
263
|
-
const id = this.state.inputIdsByRequest.get(conversationId)?.get(requestId);
|
|
264
|
-
if (id === undefined)
|
|
265
|
-
return undefined;
|
|
266
|
-
return clone(this.state.inputs.get(id));
|
|
267
|
-
}
|
|
268
|
-
async close(_context) {
|
|
269
|
-
this.closed = true;
|
|
270
|
-
}
|
|
271
|
-
*visibleEntries(conversationId, minEntryId = Number.NEGATIVE_INFINITY, maxEntryId = Number.POSITIVE_INFINITY) {
|
|
272
|
-
if (!this.state.conversations.has(conversationId)) {
|
|
273
|
-
throw new Error(`Unknown conversation: ${conversationId}`);
|
|
274
|
-
}
|
|
275
|
-
let currentId = conversationId;
|
|
276
|
-
let upperEntryId = maxEntryId;
|
|
277
|
-
while (true) {
|
|
278
|
-
const ids = this.state.entryIds.get(currentId) ?? [];
|
|
279
|
-
for (let index = upperBound(ids, upperEntryId) - 1; index >= 0; index--) {
|
|
280
|
-
const id = ids[index];
|
|
281
|
-
if (id < minEntryId)
|
|
282
|
-
break;
|
|
283
|
-
yield this.state.entries.get(id);
|
|
284
|
-
}
|
|
285
|
-
const conversation = this.state.conversations.get(currentId);
|
|
286
|
-
if (conversation.parent === undefined)
|
|
287
|
-
break;
|
|
288
|
-
upperEntryId = Math.min(upperEntryId, conversation.parent.at);
|
|
289
|
-
if (upperEntryId < minEntryId)
|
|
290
|
-
break;
|
|
291
|
-
currentId = conversation.parent.conversationId;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
checkImmutableIds(writes) {
|
|
295
|
-
const claimed = new Map();
|
|
296
|
-
for (const write of writes) {
|
|
297
|
-
const table = write.type;
|
|
298
|
-
const id = write.value.id;
|
|
299
|
-
const existing = tableContaining(this.state, id);
|
|
300
|
-
const earlier = claimed.get(id);
|
|
301
|
-
if (table === "conversation" || table === "entry") {
|
|
302
|
-
if (existing !== undefined)
|
|
303
|
-
throw new Error(`ID ${id} already belongs to ${existing}`);
|
|
304
|
-
if (earlier !== undefined)
|
|
305
|
-
throw new Error(`ID ${id} is written more than once`);
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
if (existing !== undefined && existing !== table) {
|
|
309
|
-
throw new Error(`ID ${id} already belongs to ${existing}`);
|
|
310
|
-
}
|
|
311
|
-
if (earlier !== undefined && earlier !== table)
|
|
312
|
-
throw new Error(`ID ${id} is written as two record types`);
|
|
313
|
-
}
|
|
314
|
-
claimed.set(id, table);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
assertOpen() {
|
|
318
|
-
if (this.closed)
|
|
319
|
-
throw new Error("MemoryStorage is closed");
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
//# sourceMappingURL=memory-storage.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory-storage.js","sourceRoot":"","sources":["../src/memory-storage.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context, JsonValue } from \"@earendil-works/chord\";\nimport type {\n\tConversationRecord,\n\tCursor,\n\tEntryQuery,\n\tEntryRecord,\n\tId,\n\tInput,\n\tPage,\n\tSeq,\n\tStorage,\n\tStorageWrite,\n\tTaskQuery,\n\tTaskRecord,\n} from \"./types.ts\";\n\ntype StoredTask = TaskRecord<JsonValue, JsonValue, JsonValue>;\ntype TaskStatus = StoredTask[\"state\"][\"status\"];\ntype TableName = \"conversation\" | \"entry\" | \"task\" | \"input\";\n\ntype State = {\n\tconversations: Map<Id, ConversationRecord>;\n\tconversationIds: Id[];\n\tentries: Map<Id, EntryRecord>;\n\tentryIds: Map<Id, Id[]>;\n\theadEntryIds: Map<Id, Id[]>;\n\tentryCommitSeqs: Map<Id, Seq>;\n\ttasks: Map<Id, StoredTask>;\n\ttaskIds: Id[];\n\ttaskIdsByStatus: Record<TaskStatus, Id[]>;\n\tinputs: Map<Id, Input>;\n\tinputIdsByRequest: Map<Id, Map<string, Id>>;\n};\n\nconst clone = <T>(value: T): T => {\n\tif (value === null || typeof value !== \"object\") return value;\n\tif (Array.isArray(value)) return value.map((item) => clone(item)) as T;\n\tconst source = value as Record<string, unknown>;\n\tconst nullPrototype = Object.getPrototypeOf(value) === null;\n\tconst result = (nullPrototype ? Object.create(null) : {}) as Record<string, unknown>;\n\tfor (const key of Object.keys(source)) {\n\t\tconst copied = clone(source[key]);\n\t\tif (!nullPrototype && key in result) {\n\t\t\tObject.defineProperty(result, key, {\n\t\t\t\tvalue: copied,\n\t\t\t\twritable: true,\n\t\t\t\tenumerable: true,\n\t\t\t\tconfigurable: true,\n\t\t\t});\n\t\t} else {\n\t\t\tresult[key] = copied;\n\t\t}\n\t}\n\treturn result as T;\n};\n\nconst cursorId = (cursor: Readonly<Record<string, JsonValue>> | undefined): Id | undefined =>\n\tcursor?.after as Id | undefined;\n\nconst lowerBound = (ids: readonly Id[], target: Id): number => {\n\tlet low = 0;\n\tlet high = ids.length;\n\twhile (low < high) {\n\t\tconst middle = (low + high) >>> 1;\n\t\tif (ids[middle] < target) low = middle + 1;\n\t\telse high = middle;\n\t}\n\treturn low;\n};\n\nconst upperBound = (ids: readonly Id[], target: Id): number => {\n\tlet low = 0;\n\tlet high = ids.length;\n\twhile (low < high) {\n\t\tconst middle = (low + high) >>> 1;\n\t\tif (ids[middle] <= target) low = middle + 1;\n\t\telse high = middle;\n\t}\n\treturn low;\n};\n\nconst insertSorted = (ids: Id[], id: Id): void => {\n\tif (ids.length === 0 || ids[ids.length - 1] < id) ids.push(id);\n\telse ids.splice(lowerBound(ids, id), 0, id);\n};\n\nconst removeSorted = (ids: Id[], id: Id): void => {\n\tconst index = lowerBound(ids, id);\n\tif (ids[index] === id) ids.splice(index, 1);\n};\n\nconst tableContaining = (state: State, id: Id): TableName | undefined => {\n\tif (state.conversations.has(id)) return \"conversation\";\n\tif (state.entries.has(id)) return \"entry\";\n\tif (state.tasks.has(id)) return \"task\";\n\tif (state.inputs.has(id)) return \"input\";\n\treturn undefined;\n};\n\nconst page = <T extends { readonly id: Id }>(values: readonly T[], limit: number): Page<T, Cursor> => {\n\tconst items = values.slice(0, limit);\n\tif (values.length <= limit) return { items: clone(items) };\n\treturn { items: clone(items), next: { after: items.at(-1)!.id } };\n};\n\n/**\n * Detached in-memory reference implementation of `Storage`.\n *\n * Reads and retained writes are cloned intentionally to match the ownership boundary\n * of serialization-backed stores. This is backend conformance, not validation.\n */\nexport class MemoryStorage implements Storage {\n\tprivate readonly state: State = {\n\t\tconversations: new Map(),\n\t\tconversationIds: [],\n\t\tentries: new Map(),\n\t\tentryIds: new Map(),\n\t\theadEntryIds: new Map(),\n\t\tentryCommitSeqs: new Map(),\n\t\ttasks: new Map(),\n\t\ttaskIds: [],\n\t\ttaskIdsByStatus: { pending: [], running: [], terminal: [] },\n\t\tinputs: new Map(),\n\t\tinputIdsByRequest: new Map(),\n\t};\n\tprivate nextId = 2;\n\tprivate nextSeq = 1;\n\tprivate closed = false;\n\n\tasync commit(writes: readonly StorageWrite[], _context: Context): Promise<Seq> {\n\t\tthis.assertOpen();\n\t\tconst prepared = writes.map((write) => clone(write));\n\t\tthis.checkImmutableIds(prepared);\n\t\tconst seq = this.nextSeq;\n\n\t\tfor (const write of prepared) {\n\t\t\tswitch (write.type) {\n\t\t\t\tcase \"conversation\":\n\t\t\t\t\tthis.state.conversations.set(write.value.id, write.value);\n\t\t\t\t\tinsertSorted(this.state.conversationIds, write.value.id);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"entry\": {\n\t\t\t\t\tthis.state.entries.set(write.value.id, write.value);\n\t\t\t\t\tthis.state.entryCommitSeqs.set(write.value.id, seq);\n\t\t\t\t\tlet ids = this.state.entryIds.get(write.value.conversationId);\n\t\t\t\t\tif (ids === undefined) {\n\t\t\t\t\t\tids = [];\n\t\t\t\t\t\tthis.state.entryIds.set(write.value.conversationId, ids);\n\t\t\t\t\t}\n\t\t\t\t\tinsertSorted(ids, write.value.id);\n\t\t\t\t\tif (write.value.head !== undefined) {\n\t\t\t\t\t\tlet headIds = this.state.headEntryIds.get(write.value.conversationId);\n\t\t\t\t\t\tif (headIds === undefined) {\n\t\t\t\t\t\t\theadIds = [];\n\t\t\t\t\t\t\tthis.state.headEntryIds.set(write.value.conversationId, headIds);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tinsertSorted(headIds, write.value.id);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"task\": {\n\t\t\t\t\tconst previous = this.state.tasks.get(write.value.id);\n\t\t\t\t\tif (previous === undefined) {\n\t\t\t\t\t\tinsertSorted(this.state.taskIds, write.value.id);\n\t\t\t\t\t\tinsertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);\n\t\t\t\t\t} else if (previous.state.status !== write.value.state.status) {\n\t\t\t\t\t\tremoveSorted(this.state.taskIdsByStatus[previous.state.status], write.value.id);\n\t\t\t\t\t\tinsertSorted(this.state.taskIdsByStatus[write.value.state.status], write.value.id);\n\t\t\t\t\t}\n\t\t\t\t\tthis.state.tasks.set(write.value.id, write.value);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"input\": {\n\t\t\t\t\tconst previous = this.state.inputs.get(write.value.id);\n\t\t\t\t\tif (previous?.requestId !== undefined) {\n\t\t\t\t\t\tconst previousRequests = this.state.inputIdsByRequest.get(previous.conversationId);\n\t\t\t\t\t\tif (previousRequests?.get(previous.requestId) === write.value.id) {\n\t\t\t\t\t\t\tpreviousRequests.delete(previous.requestId);\n\t\t\t\t\t\t\tif (previousRequests.size === 0) this.state.inputIdsByRequest.delete(previous.conversationId);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tthis.state.inputs.set(write.value.id, write.value);\n\t\t\t\t\tif (write.value.requestId !== undefined) {\n\t\t\t\t\t\tlet requests = this.state.inputIdsByRequest.get(write.value.conversationId);\n\t\t\t\t\t\tif (requests === undefined) {\n\t\t\t\t\t\t\trequests = new Map();\n\t\t\t\t\t\t\tthis.state.inputIdsByRequest.set(write.value.conversationId, requests);\n\t\t\t\t\t\t}\n\t\t\t\t\t\trequests.set(write.value.requestId, write.value.id);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.nextId = Math.max(this.nextId, write.value.id + 1);\n\t\t}\n\t\tthis.nextSeq++;\n\t\treturn seq;\n\t}\n\n\tmintId(): Id {\n\t\tthis.assertOpen();\n\t\tif (!Number.isSafeInteger(this.nextId)) throw new Error(\"ID space is exhausted\");\n\t\treturn this.nextId++;\n\t}\n\n\tasync conversation(id: Id, _context: Context): Promise<ConversationRecord | undefined> {\n\t\tthis.assertOpen();\n\t\tconst value = this.state.conversations.get(id);\n\t\treturn value === undefined ? undefined : clone(value);\n\t}\n\n\tasync scanConversations(\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<ConversationRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor);\n\t\tconst start = after === undefined ? 0 : upperBound(this.state.conversationIds, after);\n\t\tconst values = this.state.conversationIds\n\t\t\t.slice(start, start + limit + 1)\n\t\t\t.map((id) => this.state.conversations.get(id)!);\n\t\treturn page(values, limit);\n\t}\n\n\tasync entry(\n\t\tid: Id,\n\t\t_context: Context,\n\t): Promise<{ readonly entry: EntryRecord; readonly commitSeq: Seq } | undefined> {\n\t\tthis.assertOpen();\n\t\tconst entry = this.state.entries.get(id);\n\t\tif (entry === undefined) return undefined;\n\t\treturn { entry: clone(entry), commitSeq: this.state.entryCommitSeqs.get(id)! };\n\t}\n\n\tasync findLatestHeadMarker(\n\t\tconversationId: Id,\n\t\tatOrBeforeEntryId: Id | undefined,\n\t\t_context: Context,\n\t): Promise<(EntryRecord & { readonly head: Id }) | undefined> {\n\t\tthis.assertOpen();\n\t\tif (!this.state.conversations.has(conversationId)) {\n\t\t\tthrow new Error(`Unknown conversation: ${conversationId}`);\n\t\t}\n\t\tlet currentId = conversationId;\n\t\tlet upperEntryId = atOrBeforeEntryId ?? Number.POSITIVE_INFINITY;\n\t\twhile (true) {\n\t\t\tconst ids = this.state.headEntryIds.get(currentId) ?? [];\n\t\t\tconst index = upperBound(ids, upperEntryId) - 1;\n\t\t\tif (index >= 0) {\n\t\t\t\tconst entry = this.state.entries.get(ids[index])!;\n\t\t\t\treturn clone({ ...entry, head: entry.head! });\n\t\t\t}\n\t\t\tconst conversation = this.state.conversations.get(currentId)!;\n\t\t\tif (conversation.parent === undefined) return undefined;\n\t\t\tupperEntryId = Math.min(upperEntryId, conversation.parent.at);\n\t\t\tcurrentId = conversation.parent.conversationId;\n\t\t}\n\t}\n\n\tasync scanEntries(\n\t\tquery: EntryQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<EntryRecord, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor);\n\t\tconst maxEntryId =\n\t\t\tafter === undefined ? query.maxEntryId : Math.min(query.maxEntryId ?? Number.POSITIVE_INFINITY, after - 1);\n\t\tconst visible: EntryRecord[] = [];\n\t\tfor (const entry of this.visibleEntries(query.conversationId, query.minEntryId, maxEntryId)) {\n\t\t\tvisible.push(entry);\n\t\t\tif (visible.length > limit) break;\n\t\t}\n\t\treturn page(visible, limit);\n\t}\n\n\tasync task(id: Id, _context: Context): Promise<StoredTask | undefined> {\n\t\tthis.assertOpen();\n\t\tconst value = this.state.tasks.get(id);\n\t\treturn value === undefined ? undefined : clone(value);\n\t}\n\n\tasync scanTasks(\n\t\tquery: TaskQuery,\n\t\tcursor: Cursor | undefined,\n\t\tlimit: number,\n\t\t_context: Context,\n\t): Promise<Page<StoredTask, Cursor>> {\n\t\tthis.assertOpen();\n\t\tconst after = cursorId(cursor);\n\t\tconst ids = query.status === undefined ? this.state.taskIds : this.state.taskIdsByStatus[query.status];\n\t\tconst start = after === undefined ? 0 : upperBound(ids, after);\n\t\tconst values: StoredTask[] = [];\n\t\tfor (let index = start; index < ids.length && values.length <= limit; index++) {\n\t\t\tconst value = this.state.tasks.get(ids[index])!;\n\t\t\tif (query.conversationId !== undefined && value.conversationId !== query.conversationId) continue;\n\t\t\tif (query.kind !== undefined && value.kind !== query.kind) continue;\n\t\t\tif (query.abortRequested !== undefined && value.abortRequested !== query.abortRequested) continue;\n\t\t\tif (query.background !== undefined && value.background !== query.background) continue;\n\t\t\tvalues.push(value);\n\t\t}\n\t\treturn page(values, limit);\n\t}\n\n\tasync input(id: Id, _context: Context): Promise<Input | undefined> {\n\t\tthis.assertOpen();\n\t\tconst value = this.state.inputs.get(id);\n\t\treturn value === undefined ? undefined : clone(value);\n\t}\n\n\tasync inputByRequest(conversationId: Id, requestId: string, _context: Context): Promise<Input | undefined> {\n\t\tthis.assertOpen();\n\t\tconst id = this.state.inputIdsByRequest.get(conversationId)?.get(requestId);\n\t\tif (id === undefined) return undefined;\n\t\treturn clone(this.state.inputs.get(id)!);\n\t}\n\n\tasync close(_context: Context): Promise<void> {\n\t\tthis.closed = true;\n\t}\n\n\tprivate *visibleEntries(\n\t\tconversationId: Id,\n\t\tminEntryId = Number.NEGATIVE_INFINITY,\n\t\tmaxEntryId = Number.POSITIVE_INFINITY,\n\t): Generator<EntryRecord> {\n\t\tif (!this.state.conversations.has(conversationId)) {\n\t\t\tthrow new Error(`Unknown conversation: ${conversationId}`);\n\t\t}\n\t\tlet currentId = conversationId;\n\t\tlet upperEntryId = maxEntryId;\n\t\twhile (true) {\n\t\t\tconst ids = this.state.entryIds.get(currentId) ?? [];\n\t\t\tfor (let index = upperBound(ids, upperEntryId) - 1; index >= 0; index--) {\n\t\t\t\tconst id = ids[index];\n\t\t\t\tif (id < minEntryId) break;\n\t\t\t\tyield this.state.entries.get(id)!;\n\t\t\t}\n\t\t\tconst conversation = this.state.conversations.get(currentId)!;\n\t\t\tif (conversation.parent === undefined) break;\n\t\t\tupperEntryId = Math.min(upperEntryId, conversation.parent.at);\n\t\t\tif (upperEntryId < minEntryId) break;\n\t\t\tcurrentId = conversation.parent.conversationId;\n\t\t}\n\t}\n\n\tprivate checkImmutableIds(writes: readonly StorageWrite[]): void {\n\t\tconst claimed = new Map<Id, TableName>();\n\t\tfor (const write of writes) {\n\t\t\tconst table = write.type;\n\t\t\tconst id = write.value.id;\n\t\t\tconst existing = tableContaining(this.state, id);\n\t\t\tconst earlier = claimed.get(id);\n\t\t\tif (table === \"conversation\" || table === \"entry\") {\n\t\t\t\tif (existing !== undefined) throw new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\tif (earlier !== undefined) throw new Error(`ID ${id} is written more than once`);\n\t\t\t} else {\n\t\t\t\tif (existing !== undefined && existing !== table) {\n\t\t\t\t\tthrow new Error(`ID ${id} already belongs to ${existing}`);\n\t\t\t\t}\n\t\t\t\tif (earlier !== undefined && earlier !== table) throw new Error(`ID ${id} is written as two record types`);\n\t\t\t}\n\t\t\tclaimed.set(id, table);\n\t\t}\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (this.closed) throw new Error(\"MemoryStorage is closed\");\n\t}\n}\n"]}
|