@earendil-works/pi-agent-core 0.85.0 → 0.86.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 +16 -6
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +64 -22
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent.d.ts +5 -3
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +15 -6
- package/dist/agent.js.map +1 -1
- package/dist/harness/config.d.ts +1 -1
- package/dist/harness/config.d.ts.map +1 -1
- package/dist/harness/config.js +10 -2
- package/dist/harness/config.js.map +1 -1
- package/dist/harness/env/nodejs.d.ts +2 -1
- package/dist/harness/env/nodejs.d.ts.map +1 -1
- package/dist/harness/env/nodejs.js +98 -27
- package/dist/harness/env/nodejs.js.map +1 -1
- package/dist/harness/execution/tools.d.ts +1 -1
- package/dist/harness/execution/tools.d.ts.map +1 -1
- package/dist/harness/execution/tools.js +0 -2
- package/dist/harness/execution/tools.js.map +1 -1
- package/dist/harness/messages.d.ts.map +1 -1
- package/dist/harness/messages.js +1 -0
- package/dist/harness/messages.js.map +1 -1
- package/dist/harness/pico3/bash.d.ts +10 -0
- package/dist/harness/pico3/bash.d.ts.map +1 -0
- package/dist/harness/pico3/bash.js +25 -0
- package/dist/harness/pico3/bash.js.map +1 -0
- package/dist/harness/pico3/bounded.d.ts +21 -0
- package/dist/harness/pico3/bounded.d.ts.map +1 -0
- package/dist/harness/pico3/bounded.js +98 -0
- package/dist/harness/pico3/bounded.js.map +1 -0
- package/dist/harness/pico3/chord.d.ts +41 -0
- package/dist/harness/pico3/chord.d.ts.map +1 -0
- package/dist/harness/pico3/chord.js +130 -0
- package/dist/harness/pico3/chord.js.map +1 -0
- package/dist/harness/pico3/context.d.ts +16 -0
- package/dist/harness/pico3/context.d.ts.map +1 -0
- package/dist/harness/pico3/context.js +88 -0
- package/dist/harness/pico3/context.js.map +1 -0
- package/dist/harness/pico3/harness.d.ts +197 -0
- package/dist/harness/pico3/harness.d.ts.map +1 -0
- package/dist/harness/pico3/harness.js +654 -0
- package/dist/harness/pico3/harness.js.map +1 -0
- package/dist/harness/pico3/hooks.d.ts +10 -0
- package/dist/harness/pico3/hooks.d.ts.map +1 -0
- package/dist/harness/pico3/hooks.js +33 -0
- package/dist/harness/pico3/hooks.js.map +1 -0
- package/dist/harness/pico3/index.d.ts +19 -0
- package/dist/harness/pico3/index.d.ts.map +1 -0
- package/dist/harness/pico3/index.js +15 -0
- package/dist/harness/pico3/index.js.map +1 -0
- package/dist/harness/pico3/jsonl.d.ts +58 -0
- package/dist/harness/pico3/jsonl.d.ts.map +1 -0
- package/dist/harness/pico3/jsonl.js +325 -0
- package/dist/harness/pico3/jsonl.js.map +1 -0
- package/dist/harness/pico3/kinds/collapse.d.ts +49 -0
- package/dist/harness/pico3/kinds/collapse.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/collapse.js +191 -0
- package/dist/harness/pico3/kinds/collapse.js.map +1 -0
- package/dist/harness/pico3/kinds/entries.d.ts +78 -0
- package/dist/harness/pico3/kinds/entries.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/entries.js +13 -0
- package/dist/harness/pico3/kinds/entries.js.map +1 -0
- package/dist/harness/pico3/kinds/frames.d.ts +9 -0
- package/dist/harness/pico3/kinds/frames.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/frames.js +75 -0
- package/dist/harness/pico3/kinds/frames.js.map +1 -0
- package/dist/harness/pico3/kinds/generation.d.ts +94 -0
- package/dist/harness/pico3/kinds/generation.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/generation.js +504 -0
- package/dist/harness/pico3/kinds/generation.js.map +1 -0
- package/dist/harness/pico3/kinds/job.d.ts +44 -0
- package/dist/harness/pico3/kinds/job.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/job.js +143 -0
- package/dist/harness/pico3/kinds/job.js.map +1 -0
- package/dist/harness/pico3/kinds/plugin.d.ts +15 -0
- package/dist/harness/pico3/kinds/plugin.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/plugin.js +35 -0
- package/dist/harness/pico3/kinds/plugin.js.map +1 -0
- package/dist/harness/pico3/kinds/post-tools.d.ts +22 -0
- package/dist/harness/pico3/kinds/post-tools.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/post-tools.js +145 -0
- package/dist/harness/pico3/kinds/post-tools.js.map +1 -0
- package/dist/harness/pico3/kinds/task-api.d.ts +3 -0
- package/dist/harness/pico3/kinds/task-api.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/task-api.js +45 -0
- package/dist/harness/pico3/kinds/task-api.js.map +1 -0
- package/dist/harness/pico3/kinds/tool.d.ts +43 -0
- package/dist/harness/pico3/kinds/tool.d.ts.map +1 -0
- package/dist/harness/pico3/kinds/tool.js +373 -0
- package/dist/harness/pico3/kinds/tool.js.map +1 -0
- package/dist/harness/pico3/membrane.d.ts +23 -0
- package/dist/harness/pico3/membrane.d.ts.map +1 -0
- package/dist/harness/pico3/membrane.js +141 -0
- package/dist/harness/pico3/membrane.js.map +1 -0
- package/dist/harness/pico3/memory.d.ts +53 -0
- package/dist/harness/pico3/memory.d.ts.map +1 -0
- package/dist/harness/pico3/memory.js +264 -0
- package/dist/harness/pico3/memory.js.map +1 -0
- package/dist/harness/pico3/scheduler.d.ts +46 -0
- package/dist/harness/pico3/scheduler.d.ts.map +1 -0
- package/dist/harness/pico3/scheduler.js +438 -0
- package/dist/harness/pico3/scheduler.js.map +1 -0
- package/dist/harness/pico3/session.d.ts +263 -0
- package/dist/harness/pico3/session.d.ts.map +1 -0
- package/dist/harness/pico3/session.js +1347 -0
- package/dist/harness/pico3/session.js.map +1 -0
- package/dist/harness/pico3/system.d.ts +126 -0
- package/dist/harness/pico3/system.d.ts.map +1 -0
- package/dist/harness/pico3/system.js +244 -0
- package/dist/harness/pico3/system.js.map +1 -0
- package/dist/harness/pico3/types.d.ts +973 -0
- package/dist/harness/pico3/types.d.ts.map +1 -0
- package/dist/harness/pico3/types.js +96 -0
- package/dist/harness/pico3/types.js.map +1 -0
- package/dist/harness/pico3/view.d.ts +34 -0
- package/dist/harness/pico3/view.d.ts.map +1 -0
- package/dist/harness/pico3/view.js +417 -0
- package/dist/harness/pico3/view.js.map +1 -0
- package/dist/harness/runtime/drive/boundary.d.ts.map +1 -1
- package/dist/harness/runtime/drive/boundary.js +6 -3
- package/dist/harness/runtime/drive/boundary.js.map +1 -1
- package/dist/harness/runtime/drive/response.d.ts.map +1 -1
- package/dist/harness/runtime/drive/response.js +4 -4
- package/dist/harness/runtime/drive/response.js.map +1 -1
- package/dist/harness/runtime/drive/retry.d.ts +2 -2
- package/dist/harness/runtime/drive/retry.d.ts.map +1 -1
- package/dist/harness/runtime/drive/retry.js +3 -6
- package/dist/harness/runtime/drive/retry.js.map +1 -1
- package/dist/harness/runtime/drive/structural.d.ts.map +1 -1
- package/dist/harness/runtime/drive/structural.js +5 -5
- package/dist/harness/runtime/drive/structural.js.map +1 -1
- package/dist/harness/runtime/drive/tool-placement.d.ts.map +1 -1
- package/dist/harness/runtime/drive/tool-placement.js +2 -27
- package/dist/harness/runtime/drive/tool-placement.js.map +1 -1
- package/dist/harness/runtime/drive/tools.d.ts.map +1 -1
- package/dist/harness/runtime/drive/tools.js.map +1 -1
- package/dist/harness/session/fork-policy.d.ts +20 -12
- package/dist/harness/session/fork-policy.d.ts.map +1 -1
- package/dist/harness/session/fork-policy.js +45 -12
- package/dist/harness/session/fork-policy.js.map +1 -1
- package/dist/harness/session/fork.d.ts +0 -2
- package/dist/harness/session/fork.d.ts.map +1 -1
- package/dist/harness/session/fork.js +20 -68
- package/dist/harness/session/fork.js.map +1 -1
- package/dist/harness/session/in-memory-storage-state.d.ts +5 -5
- package/dist/harness/session/in-memory-storage-state.d.ts.map +1 -1
- package/dist/harness/session/in-memory-storage-state.js +99 -39
- package/dist/harness/session/in-memory-storage-state.js.map +1 -1
- package/dist/harness/session/index.d.ts +1 -1
- package/dist/harness/session/index.d.ts.map +1 -1
- package/dist/harness/session/index.js +1 -1
- package/dist/harness/session/index.js.map +1 -1
- package/dist/harness/session/jsonl/fork.d.ts +38 -0
- package/dist/harness/session/jsonl/fork.d.ts.map +1 -0
- package/dist/harness/session/jsonl/fork.js +264 -0
- package/dist/harness/session/jsonl/fork.js.map +1 -0
- package/dist/harness/session/jsonl/io.d.ts +14 -0
- package/dist/harness/session/jsonl/io.d.ts.map +1 -0
- package/dist/harness/session/jsonl/io.js +86 -0
- package/dist/harness/session/jsonl/io.js.map +1 -0
- package/dist/harness/session/jsonl/legacy-v3.d.ts +33 -8
- package/dist/harness/session/jsonl/legacy-v3.d.ts.map +1 -1
- package/dist/harness/session/jsonl/legacy-v3.js +285 -206
- package/dist/harness/session/jsonl/legacy-v3.js.map +1 -1
- package/dist/harness/session/jsonl/repo.d.ts +2 -1
- package/dist/harness/session/jsonl/repo.d.ts.map +1 -1
- package/dist/harness/session/jsonl/repo.js +36 -20
- package/dist/harness/session/jsonl/repo.js.map +1 -1
- package/dist/harness/session/jsonl/storage.d.ts +4 -5
- package/dist/harness/session/jsonl/storage.d.ts.map +1 -1
- package/dist/harness/session/jsonl/storage.js +39 -113
- package/dist/harness/session/jsonl/storage.js.map +1 -1
- package/dist/harness/session/memory.d.ts +3 -5
- package/dist/harness/session/memory.d.ts.map +1 -1
- package/dist/harness/session/memory.js +9 -14
- package/dist/harness/session/memory.js.map +1 -1
- package/dist/harness/session/testing/conformance/session-repo.d.ts +2 -0
- package/dist/harness/session/testing/conformance/session-repo.d.ts.map +1 -1
- package/dist/harness/session/testing/conformance/session-repo.js +220 -1
- package/dist/harness/session/testing/conformance/session-repo.js.map +1 -1
- package/dist/harness/session/testing/index.d.ts +1 -1
- package/dist/harness/session/testing/index.d.ts.map +1 -1
- package/dist/harness/session/testing/index.js +1 -1
- package/dist/harness/session/testing/index.js.map +1 -1
- package/dist/harness/session/types.d.ts +1 -0
- package/dist/harness/session/types.d.ts.map +1 -1
- package/dist/harness/session/types.js.map +1 -1
- package/dist/harness/types.d.ts +14 -0
- package/dist/harness/types.d.ts.map +1 -1
- package/dist/harness/types.js.map +1 -1
- package/dist/proxy.d.ts +2 -2
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js.map +1 -1
- package/dist/types.d.ts +30 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +10 -6
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A transaction-scoped revocable membrane over a Chord-tracked document.
|
|
3
|
+
*
|
|
4
|
+
* Every object reached through a document proxy is wrapped lazily; all wrappers of one
|
|
5
|
+
* transaction share one liveness flag; identity is preserved by a WeakMap. Mutations forward
|
|
6
|
+
* to the underlying Chord proxy so its tracker records ops. At transaction finish — success,
|
|
7
|
+
* callback failure, validation failure, storage failure — `revoke()` flips the flag and every
|
|
8
|
+
* retained wrapper, root or nested, throws on any operation.
|
|
9
|
+
*
|
|
10
|
+
* Assigning one wrapper into another (`a.list = b.list`) is rejected: Chord stores the value it
|
|
11
|
+
* is given, and a stored proxy is the classic footgun. Assign plain values; splice in place.
|
|
12
|
+
*/
|
|
13
|
+
export class Membrane {
|
|
14
|
+
alive = true;
|
|
15
|
+
wrappers = new WeakMap();
|
|
16
|
+
isWrapper = new WeakSet();
|
|
17
|
+
what;
|
|
18
|
+
constructor(what) {
|
|
19
|
+
this.what = what;
|
|
20
|
+
}
|
|
21
|
+
revoke() {
|
|
22
|
+
this.alive = false;
|
|
23
|
+
}
|
|
24
|
+
wrap(target) {
|
|
25
|
+
const hit = this.wrappers.get(target);
|
|
26
|
+
if (hit !== undefined)
|
|
27
|
+
return hit;
|
|
28
|
+
const m = this;
|
|
29
|
+
const dead = () => new TypeError(`document proxy (${m.what}) used outside its transaction`);
|
|
30
|
+
const out = (v) => (typeof v === "object" && v !== null ? m.wrap(v) : v);
|
|
31
|
+
const inp = (value) => {
|
|
32
|
+
if (typeof value !== "object" || value === null)
|
|
33
|
+
return value;
|
|
34
|
+
m.assertPlainInput(value, new WeakSet());
|
|
35
|
+
return JSON.parse(JSON.stringify(value));
|
|
36
|
+
};
|
|
37
|
+
const proxy = new Proxy(target, {
|
|
38
|
+
get(t, key, receiver) {
|
|
39
|
+
if (!m.alive)
|
|
40
|
+
throw dead();
|
|
41
|
+
const v = Reflect.get(t, key, receiver === proxy ? t : receiver);
|
|
42
|
+
if (typeof v === "function") {
|
|
43
|
+
// Methods run against the tracked target. Callback arguments (array elements and
|
|
44
|
+
// the array itself) must cross back through the membrane before user code sees them.
|
|
45
|
+
return (...args) => {
|
|
46
|
+
if (!m.alive)
|
|
47
|
+
throw dead();
|
|
48
|
+
const safeArgs = args.map((arg) => {
|
|
49
|
+
if (typeof arg !== "function")
|
|
50
|
+
return inp(arg);
|
|
51
|
+
return function (...callbackArgs) {
|
|
52
|
+
if (!m.alive)
|
|
53
|
+
throw dead();
|
|
54
|
+
return Reflect.apply(arg, this, callbackArgs.map(out));
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
return out(Reflect.apply(v, t, safeArgs));
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return out(v);
|
|
61
|
+
},
|
|
62
|
+
set(t, key, value) {
|
|
63
|
+
if (!m.alive)
|
|
64
|
+
throw dead();
|
|
65
|
+
if (key === "__proto__")
|
|
66
|
+
throw new TypeError(`document proxy (${m.what}) cannot change prototypes`);
|
|
67
|
+
return Reflect.set(t, key, inp(value));
|
|
68
|
+
},
|
|
69
|
+
deleteProperty(t, key) {
|
|
70
|
+
if (!m.alive)
|
|
71
|
+
throw dead();
|
|
72
|
+
return Reflect.deleteProperty(t, key);
|
|
73
|
+
},
|
|
74
|
+
has(t, key) {
|
|
75
|
+
if (!m.alive)
|
|
76
|
+
throw dead();
|
|
77
|
+
return Reflect.has(t, key);
|
|
78
|
+
},
|
|
79
|
+
ownKeys(t) {
|
|
80
|
+
if (!m.alive)
|
|
81
|
+
throw dead();
|
|
82
|
+
return Reflect.ownKeys(t);
|
|
83
|
+
},
|
|
84
|
+
getOwnPropertyDescriptor(t, key) {
|
|
85
|
+
if (!m.alive)
|
|
86
|
+
throw dead();
|
|
87
|
+
const d = Reflect.getOwnPropertyDescriptor(t, key);
|
|
88
|
+
if (d === undefined)
|
|
89
|
+
return undefined;
|
|
90
|
+
// Descriptors must not leak a raw nested object.
|
|
91
|
+
if ("value" in d)
|
|
92
|
+
d.value = out(d.value);
|
|
93
|
+
return d;
|
|
94
|
+
},
|
|
95
|
+
defineProperty(t, key, descriptor) {
|
|
96
|
+
if (!m.alive)
|
|
97
|
+
throw dead();
|
|
98
|
+
if ("get" in descriptor || "set" in descriptor)
|
|
99
|
+
throw new TypeError(`document proxy (${m.what}) cannot define accessors`);
|
|
100
|
+
const safe = "value" in descriptor ? { ...descriptor, value: inp(descriptor.value) } : descriptor;
|
|
101
|
+
return Reflect.defineProperty(t, key, safe);
|
|
102
|
+
},
|
|
103
|
+
getPrototypeOf(t) {
|
|
104
|
+
if (!m.alive)
|
|
105
|
+
throw dead();
|
|
106
|
+
return Reflect.getPrototypeOf(t);
|
|
107
|
+
},
|
|
108
|
+
setPrototypeOf() {
|
|
109
|
+
if (!m.alive)
|
|
110
|
+
throw dead();
|
|
111
|
+
throw new TypeError(`document proxy (${m.what}) cannot change prototypes`);
|
|
112
|
+
},
|
|
113
|
+
isExtensible(t) {
|
|
114
|
+
if (!m.alive)
|
|
115
|
+
throw dead();
|
|
116
|
+
return Reflect.isExtensible(t);
|
|
117
|
+
},
|
|
118
|
+
preventExtensions() {
|
|
119
|
+
if (!m.alive)
|
|
120
|
+
throw dead();
|
|
121
|
+
throw new TypeError(`document proxy (${m.what}) cannot change extensibility`);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
this.wrappers.set(target, proxy);
|
|
125
|
+
this.isWrapper.add(proxy);
|
|
126
|
+
return proxy;
|
|
127
|
+
}
|
|
128
|
+
assertPlainInput(value, seen) {
|
|
129
|
+
if (this.isWrapper.has(value))
|
|
130
|
+
throw new TypeError(`assigning a document proxy into a document (${this.what}); assign a plain value`);
|
|
131
|
+
if (seen.has(value))
|
|
132
|
+
throw new TypeError(`assigning a cyclic value into a document (${this.what})`);
|
|
133
|
+
seen.add(value);
|
|
134
|
+
for (const nested of Object.values(value)) {
|
|
135
|
+
if (typeof nested === "object" && nested !== null)
|
|
136
|
+
this.assertPlainInput(nested, seen);
|
|
137
|
+
}
|
|
138
|
+
seen.delete(value);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=membrane.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"membrane.js","sourceRoot":"","sources":["../../../src/harness/pico3/membrane.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A transaction-scoped revocable membrane over a Chord-tracked document.\n *\n * Every object reached through a document proxy is wrapped lazily; all wrappers of one\n * transaction share one liveness flag; identity is preserved by a WeakMap. Mutations forward\n * to the underlying Chord proxy so its tracker records ops. At transaction finish — success,\n * callback failure, validation failure, storage failure — `revoke()` flips the flag and every\n * retained wrapper, root or nested, throws on any operation.\n *\n * Assigning one wrapper into another (`a.list = b.list`) is rejected: Chord stores the value it\n * is given, and a stored proxy is the classic footgun. Assign plain values; splice in place.\n */\nexport class Membrane {\n\tprivate alive = true;\n\tprivate readonly wrappers = new WeakMap<object, object>();\n\tprivate readonly isWrapper = new WeakSet<object>();\n\tprivate readonly what: string;\n\n\tconstructor(what: string) {\n\t\tthis.what = what;\n\t}\n\n\trevoke() {\n\t\tthis.alive = false;\n\t}\n\n\twrap<T extends object>(target: T): T {\n\t\tconst hit = this.wrappers.get(target);\n\t\tif (hit !== undefined) return hit as T;\n\t\tconst m = this;\n\t\tconst dead = () => new TypeError(`document proxy (${m.what}) used outside its transaction`);\n\t\tconst out = (v: unknown): unknown => (typeof v === \"object\" && v !== null ? m.wrap(v) : v);\n\t\tconst inp = (value: unknown): unknown => {\n\t\t\tif (typeof value !== \"object\" || value === null) return value;\n\t\t\tm.assertPlainInput(value, new WeakSet());\n\t\t\treturn JSON.parse(JSON.stringify(value)) as unknown;\n\t\t};\n\t\tconst proxy = new Proxy(target, {\n\t\t\tget(t, key, receiver) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tconst v = Reflect.get(t, key, receiver === proxy ? t : receiver);\n\t\t\t\tif (typeof v === \"function\") {\n\t\t\t\t\t// Methods run against the tracked target. Callback arguments (array elements and\n\t\t\t\t\t// the array itself) must cross back through the membrane before user code sees them.\n\t\t\t\t\treturn (...args: unknown[]) => {\n\t\t\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\t\t\tconst safeArgs = args.map((arg) => {\n\t\t\t\t\t\t\tif (typeof arg !== \"function\") return inp(arg);\n\t\t\t\t\t\t\treturn function (this: unknown, ...callbackArgs: unknown[]) {\n\t\t\t\t\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\t\t\t\t\treturn Reflect.apply(arg, this, callbackArgs.map(out));\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn out(Reflect.apply(v, t, safeArgs));\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\treturn out(v);\n\t\t\t},\n\t\t\tset(t, key, value) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tif (key === \"__proto__\") throw new TypeError(`document proxy (${m.what}) cannot change prototypes`);\n\t\t\t\treturn Reflect.set(t, key, inp(value) as never);\n\t\t\t},\n\t\t\tdeleteProperty(t, key) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.deleteProperty(t, key);\n\t\t\t},\n\t\t\thas(t, key) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.has(t, key);\n\t\t\t},\n\t\t\townKeys(t) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.ownKeys(t);\n\t\t\t},\n\t\t\tgetOwnPropertyDescriptor(t, key) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tconst d = Reflect.getOwnPropertyDescriptor(t, key);\n\t\t\t\tif (d === undefined) return undefined;\n\t\t\t\t// Descriptors must not leak a raw nested object.\n\t\t\t\tif (\"value\" in d) (d as { value: unknown }).value = out(d.value);\n\t\t\t\treturn d;\n\t\t\t},\n\t\t\tdefineProperty(t, key, descriptor) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tif (\"get\" in descriptor || \"set\" in descriptor)\n\t\t\t\t\tthrow new TypeError(`document proxy (${m.what}) cannot define accessors`);\n\t\t\t\tconst safe = \"value\" in descriptor ? { ...descriptor, value: inp(descriptor.value) } : descriptor;\n\t\t\t\treturn Reflect.defineProperty(t, key, safe);\n\t\t\t},\n\t\t\tgetPrototypeOf(t) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.getPrototypeOf(t);\n\t\t\t},\n\t\t\tsetPrototypeOf() {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tthrow new TypeError(`document proxy (${m.what}) cannot change prototypes`);\n\t\t\t},\n\t\t\tisExtensible(t) {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\treturn Reflect.isExtensible(t);\n\t\t\t},\n\t\t\tpreventExtensions() {\n\t\t\t\tif (!m.alive) throw dead();\n\t\t\t\tthrow new TypeError(`document proxy (${m.what}) cannot change extensibility`);\n\t\t\t},\n\t\t});\n\t\tthis.wrappers.set(target, proxy);\n\t\tthis.isWrapper.add(proxy);\n\t\treturn proxy as T;\n\t}\n\n\tprivate assertPlainInput(value: object, seen: WeakSet<object>): void {\n\t\tif (this.isWrapper.has(value))\n\t\t\tthrow new TypeError(`assigning a document proxy into a document (${this.what}); assign a plain value`);\n\t\tif (seen.has(value)) throw new TypeError(`assigning a cyclic value into a document (${this.what})`);\n\t\tseen.add(value);\n\t\tfor (const nested of Object.values(value)) {\n\t\t\tif (typeof nested === \"object\" && nested !== null) this.assertPlainInput(nested, seen);\n\t\t}\n\t\tseen.delete(value);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Context } from "@earendil-works/chord";
|
|
2
|
+
import { type Op } from "@earendil-works/chord/delta";
|
|
3
|
+
import type { Conversation, DocRef, Entry, EntryScan, Id, Input, JsonObject, Seq, Storage, Task, TaskScan, Write } from "./types.ts";
|
|
4
|
+
/**
|
|
5
|
+
* In-memory Storage. The reference backend, and the read path of JsonlStorage.
|
|
6
|
+
*
|
|
7
|
+
* A batch is validated and staged in full before any table changes: one failed
|
|
8
|
+
* batch changes no table, no document, no ID high-water, no sequence. Committed
|
|
9
|
+
* IDs are never reused; IDs minted but never committed may be reused after reopen.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MemoryStorage implements Storage {
|
|
12
|
+
protected readonly conversationsById: Map<number, Conversation>;
|
|
13
|
+
protected readonly entriesById: Map<number, Entry>;
|
|
14
|
+
protected readonly entriesByConversation: Map<number, Entry[]>;
|
|
15
|
+
protected readonly tasksById: Map<number, Task<import("@earendil-works/chord").JsonValue, import("./types.ts").Checkpoint>>;
|
|
16
|
+
protected readonly inputsById: Map<number, Input>;
|
|
17
|
+
protected readonly inputsByRequest: Map<string, Input>;
|
|
18
|
+
/** Rewindable docs keep their full op history with the seq of each batch, for `docAsOf`. */
|
|
19
|
+
protected readonly rewindableLog: Map<number, {
|
|
20
|
+
seq: number;
|
|
21
|
+
ops: Op[];
|
|
22
|
+
}[]>;
|
|
23
|
+
protected readonly stickyLog: Map<number, Op[][]>;
|
|
24
|
+
protected sessionDoc: JsonObject | undefined;
|
|
25
|
+
protected readonly entrySeq: Map<number, number>;
|
|
26
|
+
protected nextIdValue: number;
|
|
27
|
+
protected seq: Seq;
|
|
28
|
+
private closed;
|
|
29
|
+
mintId(): Id;
|
|
30
|
+
protected setNextId(n: Id): void;
|
|
31
|
+
commit(writes: readonly Write[], _ctx: Context): Promise<Seq>;
|
|
32
|
+
/** Validate the whole batch against the current tables; return a function that applies it. */
|
|
33
|
+
protected stage(writes: readonly Write[], seq: Seq): () => void;
|
|
34
|
+
conversation(id: Id, _ctx?: Context): Promise<Conversation | undefined>;
|
|
35
|
+
conversations(_ctx?: Context): Promise<Conversation[]>;
|
|
36
|
+
entries(ids: readonly Id[], _ctx?: Context): Promise<Map<number, Entry>>;
|
|
37
|
+
/** Newest-first, fork-aware: after this conversation's own entries, the parent's up to the fork point, and so on. */
|
|
38
|
+
scanEntries(scan: EntryScan, _ctx?: Context): Promise<Entry[]>;
|
|
39
|
+
task(id: Id, _ctx?: Context): Promise<Task<import("@earendil-works/chord").JsonValue, import("./types.ts").Checkpoint> | undefined>;
|
|
40
|
+
scanTasks(scan: TaskScan, _ctx?: Context): Promise<Task<import("@earendil-works/chord").JsonValue, import("./types.ts").Checkpoint>[]>;
|
|
41
|
+
input(id: Id, _ctx?: Context): Promise<Input | undefined>;
|
|
42
|
+
inputByRequest(conversationId: Id, requestId: string, _ctx?: Context): Promise<Input | undefined>;
|
|
43
|
+
doc(ref: DocRef, _ctx?: Context): Promise<JsonObject | undefined>;
|
|
44
|
+
/**
|
|
45
|
+
* Commit-granular history: the rewindable state after the atomic commit that contains
|
|
46
|
+
* entry `at`, walking the fork chain for entries owned by ancestors.
|
|
47
|
+
*/
|
|
48
|
+
docAsOf(conversationId: Id, at: Id, _ctx?: Context): Promise<JsonObject | undefined>;
|
|
49
|
+
protected fold(log: Op[][]): JsonObject;
|
|
50
|
+
truncate(ref: DocRef, _ctx?: Context): Promise<void>;
|
|
51
|
+
close(_ctx?: Context): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../../src/harness/pico3/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAA0B,KAAK,EAAE,EAAE,MAAM,6BAA6B,CAAC;AAC9E,OAAO,KAAK,EACX,YAAY,EACZ,MAAM,EACN,KAAK,EACL,SAAS,EACT,EAAE,EACF,KAAK,EACL,UAAU,EACV,GAAG,EACH,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,qBAAa,aAAc,YAAW,OAAO;IAC5C,SAAS,CAAC,QAAQ,CAAC,iBAAiB,4BAA+B;IACnE,SAAS,CAAC,QAAQ,CAAC,WAAW,qBAAwB;IACtD,SAAS,CAAC,QAAQ,CAAC,qBAAqB,uBAA0B;IAClE,SAAS,CAAC,QAAQ,CAAC,SAAS,gGAAuB;IACnD,SAAS,CAAC,QAAQ,CAAC,UAAU,qBAAwB;IACrD,SAAS,CAAC,QAAQ,CAAC,eAAe,qBAA4B;IAC9D,4FAA4F;IAC5F,SAAS,CAAC,QAAQ,CAAC,aAAa;;;SAA4C;IAC5E,SAAS,CAAC,QAAQ,CAAC,SAAS,sBAAyB;IACrD,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;IAC7C,SAAS,CAAC,QAAQ,CAAC,QAAQ,sBAAsB;IACjD,SAAS,CAAC,WAAW,SAAK;IAC1B,SAAS,CAAC,GAAG,EAAE,GAAG,CAAK;IACvB,OAAO,CAAC,MAAM,CAAS;IAEvB,MAAM,IAAI,EAAE,CAEX;IACD,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,QAExB;IAEK,MAAM,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAOlE;IAED,8FAA8F;IAC9F,SAAS,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,GAAG,MAAM,IAAI,CAsG9D;IAEK,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,qCAExC;IACK,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,2BAEjC;IACK,OAAO,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,+BAO/C;IACD,qHAAqH;IAC/G,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,oBAkBhD;IACK,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,yGAEhC;IACK,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,OAAO,+FAS7C;IACK,KAAK,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,8BAEjC;IACK,cAAc,CAAC,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,8BAEzE;IAEK,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAStE;IACD;;;OAGG;IACG,OAAO,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAazF;IACD,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,GAAG,UAAU,CAUtC;IACK,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,iBAWzC;IACK,KAAK,CAAC,IAAI,CAAC,EAAE,OAAO,iBAEzB;CACD","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport { applyImmutable, isBase, type Op } from \"@earendil-works/chord/delta\";\nimport type {\n\tConversation,\n\tDocRef,\n\tEntry,\n\tEntryScan,\n\tId,\n\tInput,\n\tJsonObject,\n\tSeq,\n\tStorage,\n\tTask,\n\tTaskScan,\n\tWrite,\n} from \"./types.ts\";\n\n/**\n * In-memory Storage. The reference backend, and the read path of JsonlStorage.\n *\n * A batch is validated and staged in full before any table changes: one failed\n * batch changes no table, no document, no ID high-water, no sequence. Committed\n * IDs are never reused; IDs minted but never committed may be reused after reopen.\n */\nexport class MemoryStorage implements Storage {\n\tprotected readonly conversationsById = new Map<Id, Conversation>();\n\tprotected readonly entriesById = new Map<Id, Entry>();\n\tprotected readonly entriesByConversation = new Map<Id, Entry[]>(); // ascending\n\tprotected readonly tasksById = new Map<Id, Task>();\n\tprotected readonly inputsById = new Map<Id, Input>();\n\tprotected readonly inputsByRequest = new Map<string, Input>();\n\t/** Rewindable docs keep their full op history with the seq of each batch, for `docAsOf`. */\n\tprotected readonly rewindableLog = new Map<Id, { seq: Seq; ops: Op[] }[]>();\n\tprotected readonly stickyLog = new Map<Id, Op[][]>();\n\tprotected sessionDoc: JsonObject | undefined;\n\tprotected readonly entrySeq = new Map<Id, Seq>(); // entry id → seq of its batch\n\tprotected nextIdValue = 1;\n\tprotected seq: Seq = 0;\n\tprivate closed = false;\n\n\tmintId(): Id {\n\t\treturn this.nextIdValue++;\n\t}\n\tprotected setNextId(n: Id) {\n\t\tthis.nextIdValue = Math.max(this.nextIdValue, n);\n\t}\n\n\tasync commit(writes: readonly Write[], _ctx: Context): Promise<Seq> {\n\t\tif (this.closed) throw new Error(\"storage closed\");\n\t\tconst seq = this.seq + 1;\n\t\tconst staged = this.stage(writes, seq); // throws before any mutation\n\t\tstaged();\n\t\tthis.seq = seq;\n\t\treturn seq;\n\t}\n\n\t/** Validate the whole batch against the current tables; return a function that applies it. */\n\tprotected stage(writes: readonly Write[], seq: Seq): () => void {\n\t\tconst ops: (() => void)[] = [];\n\t\tconst created = new Set<Id>();\n\t\tlet maxId = 0;\n\t\tconst claim = (id: Id, what: string) => {\n\t\t\tif (!Number.isSafeInteger(id) || id <= 0) throw new Error(`${what}: invalid id ${id}`);\n\t\t\tif (created.has(id)) throw new Error(`${what}: id ${id} created twice in one batch`);\n\t\t\tcreated.add(id);\n\t\t\tmaxId = Math.max(maxId, id);\n\t\t};\n\t\tfor (const w of writes) {\n\t\t\tswitch (w.type) {\n\t\t\t\tcase \"conversation\": {\n\t\t\t\t\tif (this.conversationsById.has(w.conversation.id))\n\t\t\t\t\t\tthrow new Error(`conversation ${w.conversation.id} exists`);\n\t\t\t\t\tclaim(w.conversation.id, \"conversation\");\n\t\t\t\t\tconst c = clone(w.conversation);\n\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\tthis.conversationsById.set(c.id, c);\n\t\t\t\t\t\tthis.entriesByConversation.set(c.id, []);\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"entry\": {\n\t\t\t\t\tif (this.entriesById.has(w.entry.id)) throw new Error(`entry ${w.entry.id} exists`);\n\t\t\t\t\tclaim(w.entry.id, \"entry\");\n\t\t\t\t\tconst e = clone(w.entry);\n\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\tthis.entriesById.set(e.id, e);\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\tthis.entriesByConversation.get(e.conversationId) ??\n\t\t\t\t\t\t\tthis.entriesByConversation.set(e.conversationId, []).get(e.conversationId)!\n\t\t\t\t\t\t).push(e);\n\t\t\t\t\t\tthis.entrySeq.set(e.id, seq);\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\tif (this.tasksById.has(w.task.id)) throw new Error(`task ${w.task.id} exists`);\n\t\t\t\t\tclaim(w.task.id, \"task\");\n\t\t\t\t\tconst t = clone(w.task);\n\t\t\t\t\tops.push(() => this.tasksById.set(t.id, t));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"task.patch\": {\n\t\t\t\t\tconst prev =\n\t\t\t\t\t\tthis.tasksById.get(w.patch.id) ??\n\t\t\t\t\t\t[...writes]\n\t\t\t\t\t\t\t.filter((x): x is Extract<Write, { type: \"task\" }> => x.type === \"task\")\n\t\t\t\t\t\t\t.find((x) => x.task.id === w.patch.id)?.task;\n\t\t\t\t\tif (prev === undefined) throw new Error(`patch for unknown task ${w.patch.id}`);\n\t\t\t\t\tconst p = clone(w.patch);\n\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\tconst cur = this.tasksById.get(p.id)!;\n\t\t\t\t\t\tconst { id: _i, ...fields } = p;\n\t\t\t\t\t\tconst next = { ...cur, ...fields } as Task;\n\t\t\t\t\t\tif (p.checkpoint === null) delete (next as { checkpoint?: unknown }).checkpoint;\n\t\t\t\t\t\tthis.tasksById.set(p.id, next);\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"input\": {\n\t\t\t\t\tconst i = clone(w.input);\n\t\t\t\t\tif (!this.inputsById.has(i.id)) claim(i.id, \"input\");\n\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\tthis.inputsById.set(i.id, i);\n\t\t\t\t\t\tif (i.requestId !== undefined) this.inputsByRequest.set(`${i.conversationId}:${i.requestId}`, i);\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"doc\": {\n\t\t\t\t\tconst ref = clone(w.ref);\n\t\t\t\t\tconst o = clone(w.ops);\n\t\t\t\t\tif (ref.doc === \"session\")\n\t\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\t\tthis.sessionDoc = applyImmutable(\n\t\t\t\t\t\t\t\t(this.sessionDoc ?? { plugins: {} }) as JsonObject,\n\t\t\t\t\t\t\t\to,\n\t\t\t\t\t\t\t) as JsonObject;\n\t\t\t\t\t\t});\n\t\t\t\t\telse if (ref.doc === \"rewindable\")\n\t\t\t\t\t\tops.push(() =>\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tthis.rewindableLog.get(ref.conversationId) ??\n\t\t\t\t\t\t\t\tthis.rewindableLog.set(ref.conversationId, []).get(ref.conversationId)!\n\t\t\t\t\t\t\t).push({ seq, ops: o }),\n\t\t\t\t\t\t);\n\t\t\t\t\telse\n\t\t\t\t\t\tops.push(() =>\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tthis.stickyLog.get(ref.conversationId) ??\n\t\t\t\t\t\t\t\tthis.stickyLog.set(ref.conversationId, []).get(ref.conversationId)!\n\t\t\t\t\t\t\t).push(o),\n\t\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\tfor (const op of ops) op();\n\t\t\tthis.setNextId(maxId + 1);\n\t\t};\n\t}\n\n\tasync conversation(id: Id, _ctx?: Context) {\n\t\treturn optionalClone(this.conversationsById.get(id));\n\t}\n\tasync conversations(_ctx?: Context) {\n\t\treturn [...this.conversationsById.values()].map(clone);\n\t}\n\tasync entries(ids: readonly Id[], _ctx?: Context) {\n\t\tconst out = new Map<Id, Entry>();\n\t\tfor (const id of ids) {\n\t\t\tconst e = this.entriesById.get(id);\n\t\t\tif (e) out.set(id, clone(e));\n\t\t}\n\t\treturn out;\n\t}\n\t/** Newest-first, fork-aware: after this conversation's own entries, the parent's up to the fork point, and so on. */\n\tasync scanEntries(scan: EntryScan, _ctx?: Context) {\n\t\tconst out: Entry[] = [];\n\t\tlet conversationId: Id | undefined = scan.conversationId;\n\t\tlet cap: Id | undefined = scan.before;\n\t\twhile (conversationId !== undefined && out.length < scan.limit) {\n\t\t\tconst own = this.entriesByConversation.get(conversationId) ?? [];\n\t\t\tfor (let i = own.length - 1; i >= 0 && out.length < scan.limit; i--) {\n\t\t\t\tconst e = own[i]!;\n\t\t\t\tif (cap !== undefined && e.id >= cap) continue;\n\t\t\t\tif (scan.kind !== undefined && e.kind !== scan.kind) continue;\n\t\t\t\tif (scan.withHead && e.head === undefined) continue;\n\t\t\t\tout.push(clone(e));\n\t\t\t}\n\t\t\tconst c = this.conversationsById.get(conversationId);\n\t\t\tconversationId = c?.parent?.conversationId;\n\t\t\tcap = c?.parent === undefined ? undefined : Math.min(cap ?? Infinity, c.parent.at + 1);\n\t\t}\n\t\treturn out;\n\t}\n\tasync task(id: Id, _ctx?: Context) {\n\t\treturn optionalClone(this.tasksById.get(id));\n\t}\n\tasync scanTasks(scan: TaskScan, _ctx?: Context) {\n\t\tconst out: Task[] = [];\n\t\tfor (const t of this.tasksById.values()) {\n\t\t\tif (scan.conversationId !== undefined && t.conversationId !== scan.conversationId) continue;\n\t\t\tif (scan.status !== undefined && !scan.status.includes(t.status)) continue;\n\t\t\tif (scan.kind !== undefined && t.kind !== scan.kind) continue;\n\t\t\tout.push(clone(t));\n\t\t}\n\t\treturn out;\n\t}\n\tasync input(id: Id, _ctx?: Context) {\n\t\treturn optionalClone(this.inputsById.get(id));\n\t}\n\tasync inputByRequest(conversationId: Id, requestId: string, _ctx?: Context) {\n\t\treturn optionalClone(this.inputsByRequest.get(`${conversationId}:${requestId}`));\n\t}\n\n\tasync doc(ref: DocRef, _ctx?: Context): Promise<JsonObject | undefined> {\n\t\tif (ref.doc === \"session\")\n\t\t\treturn this.sessionDoc === undefined ? { plugins: {} } : structuredClone(this.sessionDoc);\n\t\tconst log =\n\t\t\tref.doc === \"rewindable\"\n\t\t\t\t? this.rewindableLog.get(ref.conversationId)?.map((r) => r.ops)\n\t\t\t\t: this.stickyLog.get(ref.conversationId);\n\t\tif (log === undefined) return undefined;\n\t\treturn clone(this.fold(log));\n\t}\n\t/**\n\t * Commit-granular history: the rewindable state after the atomic commit that contains\n\t * entry `at`, walking the fork chain for entries owned by ancestors.\n\t */\n\tasync docAsOf(conversationId: Id, at: Id, _ctx?: Context): Promise<JsonObject | undefined> {\n\t\tconst owner = this.entriesById.get(at)?.conversationId;\n\t\tif (owner === undefined) return undefined;\n\t\tconst chain: Id[] = [];\n\t\tlet c: Conversation | undefined = this.conversationsById.get(conversationId);\n\t\twhile (c !== undefined && c.id !== owner) {\n\t\t\tchain.push(c.id);\n\t\t\tc = c.parent ? this.conversationsById.get(c.parent.conversationId) : undefined;\n\t\t}\n\t\tif (c === undefined) return undefined;\n\t\tconst seqAt = this.entrySeq.get(at) ?? 0;\n\t\tconst log = (this.rewindableLog.get(owner) ?? []).filter((r) => r.seq <= seqAt).map((r) => r.ops);\n\t\treturn log.length === 0 ? undefined : clone(this.fold(log));\n\t}\n\tprotected fold(log: Op[][]): JsonObject {\n\t\tlet start = 0;\n\t\tfor (let i = log.length - 1; i >= 0; i--)\n\t\t\tif (isBase(log[i]!)) {\n\t\t\t\tstart = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\tlet state: JsonObject = {};\n\t\tfor (let i = start; i < log.length; i++) state = applyImmutable(state, log[i]!) as JsonObject;\n\t\treturn state;\n\t}\n\tasync truncate(ref: DocRef, _ctx?: Context) {\n\t\tif (ref.doc !== \"sticky\") return;\n\t\tconst log = this.stickyLog.get(ref.conversationId);\n\t\tif (log === undefined) return;\n\t\tlet start = 0;\n\t\tfor (let i = log.length - 1; i >= 0; i--)\n\t\t\tif (isBase(log[i]!)) {\n\t\t\t\tstart = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\tlog.splice(0, start);\n\t}\n\tasync close(_ctx?: Context) {\n\t\tthis.closed = true;\n\t}\n}\n\nfunction clone<T>(value: T): T {\n\treturn JSON.parse(JSON.stringify(value)) as T;\n}\n\nfunction optionalClone<T>(value: T | undefined): T | undefined {\n\treturn value === undefined ? undefined : clone(value);\n}\n"]}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { applyImmutable, isBase } from "@earendil-works/chord/delta";
|
|
2
|
+
/**
|
|
3
|
+
* In-memory Storage. The reference backend, and the read path of JsonlStorage.
|
|
4
|
+
*
|
|
5
|
+
* A batch is validated and staged in full before any table changes: one failed
|
|
6
|
+
* batch changes no table, no document, no ID high-water, no sequence. Committed
|
|
7
|
+
* IDs are never reused; IDs minted but never committed may be reused after reopen.
|
|
8
|
+
*/
|
|
9
|
+
export class MemoryStorage {
|
|
10
|
+
conversationsById = new Map();
|
|
11
|
+
entriesById = new Map();
|
|
12
|
+
entriesByConversation = new Map(); // ascending
|
|
13
|
+
tasksById = new Map();
|
|
14
|
+
inputsById = new Map();
|
|
15
|
+
inputsByRequest = new Map();
|
|
16
|
+
/** Rewindable docs keep their full op history with the seq of each batch, for `docAsOf`. */
|
|
17
|
+
rewindableLog = new Map();
|
|
18
|
+
stickyLog = new Map();
|
|
19
|
+
sessionDoc;
|
|
20
|
+
entrySeq = new Map(); // entry id → seq of its batch
|
|
21
|
+
nextIdValue = 1;
|
|
22
|
+
seq = 0;
|
|
23
|
+
closed = false;
|
|
24
|
+
mintId() {
|
|
25
|
+
return this.nextIdValue++;
|
|
26
|
+
}
|
|
27
|
+
setNextId(n) {
|
|
28
|
+
this.nextIdValue = Math.max(this.nextIdValue, n);
|
|
29
|
+
}
|
|
30
|
+
async commit(writes, _ctx) {
|
|
31
|
+
if (this.closed)
|
|
32
|
+
throw new Error("storage closed");
|
|
33
|
+
const seq = this.seq + 1;
|
|
34
|
+
const staged = this.stage(writes, seq); // throws before any mutation
|
|
35
|
+
staged();
|
|
36
|
+
this.seq = seq;
|
|
37
|
+
return seq;
|
|
38
|
+
}
|
|
39
|
+
/** Validate the whole batch against the current tables; return a function that applies it. */
|
|
40
|
+
stage(writes, seq) {
|
|
41
|
+
const ops = [];
|
|
42
|
+
const created = new Set();
|
|
43
|
+
let maxId = 0;
|
|
44
|
+
const claim = (id, what) => {
|
|
45
|
+
if (!Number.isSafeInteger(id) || id <= 0)
|
|
46
|
+
throw new Error(`${what}: invalid id ${id}`);
|
|
47
|
+
if (created.has(id))
|
|
48
|
+
throw new Error(`${what}: id ${id} created twice in one batch`);
|
|
49
|
+
created.add(id);
|
|
50
|
+
maxId = Math.max(maxId, id);
|
|
51
|
+
};
|
|
52
|
+
for (const w of writes) {
|
|
53
|
+
switch (w.type) {
|
|
54
|
+
case "conversation": {
|
|
55
|
+
if (this.conversationsById.has(w.conversation.id))
|
|
56
|
+
throw new Error(`conversation ${w.conversation.id} exists`);
|
|
57
|
+
claim(w.conversation.id, "conversation");
|
|
58
|
+
const c = clone(w.conversation);
|
|
59
|
+
ops.push(() => {
|
|
60
|
+
this.conversationsById.set(c.id, c);
|
|
61
|
+
this.entriesByConversation.set(c.id, []);
|
|
62
|
+
});
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case "entry": {
|
|
66
|
+
if (this.entriesById.has(w.entry.id))
|
|
67
|
+
throw new Error(`entry ${w.entry.id} exists`);
|
|
68
|
+
claim(w.entry.id, "entry");
|
|
69
|
+
const e = clone(w.entry);
|
|
70
|
+
ops.push(() => {
|
|
71
|
+
this.entriesById.set(e.id, e);
|
|
72
|
+
(this.entriesByConversation.get(e.conversationId) ??
|
|
73
|
+
this.entriesByConversation.set(e.conversationId, []).get(e.conversationId)).push(e);
|
|
74
|
+
this.entrySeq.set(e.id, seq);
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "task": {
|
|
79
|
+
if (this.tasksById.has(w.task.id))
|
|
80
|
+
throw new Error(`task ${w.task.id} exists`);
|
|
81
|
+
claim(w.task.id, "task");
|
|
82
|
+
const t = clone(w.task);
|
|
83
|
+
ops.push(() => this.tasksById.set(t.id, t));
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "task.patch": {
|
|
87
|
+
const prev = this.tasksById.get(w.patch.id) ??
|
|
88
|
+
[...writes]
|
|
89
|
+
.filter((x) => x.type === "task")
|
|
90
|
+
.find((x) => x.task.id === w.patch.id)?.task;
|
|
91
|
+
if (prev === undefined)
|
|
92
|
+
throw new Error(`patch for unknown task ${w.patch.id}`);
|
|
93
|
+
const p = clone(w.patch);
|
|
94
|
+
ops.push(() => {
|
|
95
|
+
const cur = this.tasksById.get(p.id);
|
|
96
|
+
const { id: _i, ...fields } = p;
|
|
97
|
+
const next = { ...cur, ...fields };
|
|
98
|
+
if (p.checkpoint === null)
|
|
99
|
+
delete next.checkpoint;
|
|
100
|
+
this.tasksById.set(p.id, next);
|
|
101
|
+
});
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "input": {
|
|
105
|
+
const i = clone(w.input);
|
|
106
|
+
if (!this.inputsById.has(i.id))
|
|
107
|
+
claim(i.id, "input");
|
|
108
|
+
ops.push(() => {
|
|
109
|
+
this.inputsById.set(i.id, i);
|
|
110
|
+
if (i.requestId !== undefined)
|
|
111
|
+
this.inputsByRequest.set(`${i.conversationId}:${i.requestId}`, i);
|
|
112
|
+
});
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case "doc": {
|
|
116
|
+
const ref = clone(w.ref);
|
|
117
|
+
const o = clone(w.ops);
|
|
118
|
+
if (ref.doc === "session")
|
|
119
|
+
ops.push(() => {
|
|
120
|
+
this.sessionDoc = applyImmutable((this.sessionDoc ?? { plugins: {} }), o);
|
|
121
|
+
});
|
|
122
|
+
else if (ref.doc === "rewindable")
|
|
123
|
+
ops.push(() => (this.rewindableLog.get(ref.conversationId) ??
|
|
124
|
+
this.rewindableLog.set(ref.conversationId, []).get(ref.conversationId)).push({ seq, ops: o }));
|
|
125
|
+
else
|
|
126
|
+
ops.push(() => (this.stickyLog.get(ref.conversationId) ??
|
|
127
|
+
this.stickyLog.set(ref.conversationId, []).get(ref.conversationId)).push(o));
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return () => {
|
|
133
|
+
for (const op of ops)
|
|
134
|
+
op();
|
|
135
|
+
this.setNextId(maxId + 1);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async conversation(id, _ctx) {
|
|
139
|
+
return optionalClone(this.conversationsById.get(id));
|
|
140
|
+
}
|
|
141
|
+
async conversations(_ctx) {
|
|
142
|
+
return [...this.conversationsById.values()].map(clone);
|
|
143
|
+
}
|
|
144
|
+
async entries(ids, _ctx) {
|
|
145
|
+
const out = new Map();
|
|
146
|
+
for (const id of ids) {
|
|
147
|
+
const e = this.entriesById.get(id);
|
|
148
|
+
if (e)
|
|
149
|
+
out.set(id, clone(e));
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
/** Newest-first, fork-aware: after this conversation's own entries, the parent's up to the fork point, and so on. */
|
|
154
|
+
async scanEntries(scan, _ctx) {
|
|
155
|
+
const out = [];
|
|
156
|
+
let conversationId = scan.conversationId;
|
|
157
|
+
let cap = scan.before;
|
|
158
|
+
while (conversationId !== undefined && out.length < scan.limit) {
|
|
159
|
+
const own = this.entriesByConversation.get(conversationId) ?? [];
|
|
160
|
+
for (let i = own.length - 1; i >= 0 && out.length < scan.limit; i--) {
|
|
161
|
+
const e = own[i];
|
|
162
|
+
if (cap !== undefined && e.id >= cap)
|
|
163
|
+
continue;
|
|
164
|
+
if (scan.kind !== undefined && e.kind !== scan.kind)
|
|
165
|
+
continue;
|
|
166
|
+
if (scan.withHead && e.head === undefined)
|
|
167
|
+
continue;
|
|
168
|
+
out.push(clone(e));
|
|
169
|
+
}
|
|
170
|
+
const c = this.conversationsById.get(conversationId);
|
|
171
|
+
conversationId = c?.parent?.conversationId;
|
|
172
|
+
cap = c?.parent === undefined ? undefined : Math.min(cap ?? Infinity, c.parent.at + 1);
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
async task(id, _ctx) {
|
|
177
|
+
return optionalClone(this.tasksById.get(id));
|
|
178
|
+
}
|
|
179
|
+
async scanTasks(scan, _ctx) {
|
|
180
|
+
const out = [];
|
|
181
|
+
for (const t of this.tasksById.values()) {
|
|
182
|
+
if (scan.conversationId !== undefined && t.conversationId !== scan.conversationId)
|
|
183
|
+
continue;
|
|
184
|
+
if (scan.status !== undefined && !scan.status.includes(t.status))
|
|
185
|
+
continue;
|
|
186
|
+
if (scan.kind !== undefined && t.kind !== scan.kind)
|
|
187
|
+
continue;
|
|
188
|
+
out.push(clone(t));
|
|
189
|
+
}
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
async input(id, _ctx) {
|
|
193
|
+
return optionalClone(this.inputsById.get(id));
|
|
194
|
+
}
|
|
195
|
+
async inputByRequest(conversationId, requestId, _ctx) {
|
|
196
|
+
return optionalClone(this.inputsByRequest.get(`${conversationId}:${requestId}`));
|
|
197
|
+
}
|
|
198
|
+
async doc(ref, _ctx) {
|
|
199
|
+
if (ref.doc === "session")
|
|
200
|
+
return this.sessionDoc === undefined ? { plugins: {} } : structuredClone(this.sessionDoc);
|
|
201
|
+
const log = ref.doc === "rewindable"
|
|
202
|
+
? this.rewindableLog.get(ref.conversationId)?.map((r) => r.ops)
|
|
203
|
+
: this.stickyLog.get(ref.conversationId);
|
|
204
|
+
if (log === undefined)
|
|
205
|
+
return undefined;
|
|
206
|
+
return clone(this.fold(log));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Commit-granular history: the rewindable state after the atomic commit that contains
|
|
210
|
+
* entry `at`, walking the fork chain for entries owned by ancestors.
|
|
211
|
+
*/
|
|
212
|
+
async docAsOf(conversationId, at, _ctx) {
|
|
213
|
+
const owner = this.entriesById.get(at)?.conversationId;
|
|
214
|
+
if (owner === undefined)
|
|
215
|
+
return undefined;
|
|
216
|
+
const chain = [];
|
|
217
|
+
let c = this.conversationsById.get(conversationId);
|
|
218
|
+
while (c !== undefined && c.id !== owner) {
|
|
219
|
+
chain.push(c.id);
|
|
220
|
+
c = c.parent ? this.conversationsById.get(c.parent.conversationId) : undefined;
|
|
221
|
+
}
|
|
222
|
+
if (c === undefined)
|
|
223
|
+
return undefined;
|
|
224
|
+
const seqAt = this.entrySeq.get(at) ?? 0;
|
|
225
|
+
const log = (this.rewindableLog.get(owner) ?? []).filter((r) => r.seq <= seqAt).map((r) => r.ops);
|
|
226
|
+
return log.length === 0 ? undefined : clone(this.fold(log));
|
|
227
|
+
}
|
|
228
|
+
fold(log) {
|
|
229
|
+
let start = 0;
|
|
230
|
+
for (let i = log.length - 1; i >= 0; i--)
|
|
231
|
+
if (isBase(log[i])) {
|
|
232
|
+
start = i;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
let state = {};
|
|
236
|
+
for (let i = start; i < log.length; i++)
|
|
237
|
+
state = applyImmutable(state, log[i]);
|
|
238
|
+
return state;
|
|
239
|
+
}
|
|
240
|
+
async truncate(ref, _ctx) {
|
|
241
|
+
if (ref.doc !== "sticky")
|
|
242
|
+
return;
|
|
243
|
+
const log = this.stickyLog.get(ref.conversationId);
|
|
244
|
+
if (log === undefined)
|
|
245
|
+
return;
|
|
246
|
+
let start = 0;
|
|
247
|
+
for (let i = log.length - 1; i >= 0; i--)
|
|
248
|
+
if (isBase(log[i])) {
|
|
249
|
+
start = i;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
log.splice(0, start);
|
|
253
|
+
}
|
|
254
|
+
async close(_ctx) {
|
|
255
|
+
this.closed = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function clone(value) {
|
|
259
|
+
return JSON.parse(JSON.stringify(value));
|
|
260
|
+
}
|
|
261
|
+
function optionalClone(value) {
|
|
262
|
+
return value === undefined ? undefined : clone(value);
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../../src/harness/pico3/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context } from \"@earendil-works/chord\";\nimport { applyImmutable, isBase, type Op } from \"@earendil-works/chord/delta\";\nimport type {\n\tConversation,\n\tDocRef,\n\tEntry,\n\tEntryScan,\n\tId,\n\tInput,\n\tJsonObject,\n\tSeq,\n\tStorage,\n\tTask,\n\tTaskScan,\n\tWrite,\n} from \"./types.ts\";\n\n/**\n * In-memory Storage. The reference backend, and the read path of JsonlStorage.\n *\n * A batch is validated and staged in full before any table changes: one failed\n * batch changes no table, no document, no ID high-water, no sequence. Committed\n * IDs are never reused; IDs minted but never committed may be reused after reopen.\n */\nexport class MemoryStorage implements Storage {\n\tprotected readonly conversationsById = new Map<Id, Conversation>();\n\tprotected readonly entriesById = new Map<Id, Entry>();\n\tprotected readonly entriesByConversation = new Map<Id, Entry[]>(); // ascending\n\tprotected readonly tasksById = new Map<Id, Task>();\n\tprotected readonly inputsById = new Map<Id, Input>();\n\tprotected readonly inputsByRequest = new Map<string, Input>();\n\t/** Rewindable docs keep their full op history with the seq of each batch, for `docAsOf`. */\n\tprotected readonly rewindableLog = new Map<Id, { seq: Seq; ops: Op[] }[]>();\n\tprotected readonly stickyLog = new Map<Id, Op[][]>();\n\tprotected sessionDoc: JsonObject | undefined;\n\tprotected readonly entrySeq = new Map<Id, Seq>(); // entry id → seq of its batch\n\tprotected nextIdValue = 1;\n\tprotected seq: Seq = 0;\n\tprivate closed = false;\n\n\tmintId(): Id {\n\t\treturn this.nextIdValue++;\n\t}\n\tprotected setNextId(n: Id) {\n\t\tthis.nextIdValue = Math.max(this.nextIdValue, n);\n\t}\n\n\tasync commit(writes: readonly Write[], _ctx: Context): Promise<Seq> {\n\t\tif (this.closed) throw new Error(\"storage closed\");\n\t\tconst seq = this.seq + 1;\n\t\tconst staged = this.stage(writes, seq); // throws before any mutation\n\t\tstaged();\n\t\tthis.seq = seq;\n\t\treturn seq;\n\t}\n\n\t/** Validate the whole batch against the current tables; return a function that applies it. */\n\tprotected stage(writes: readonly Write[], seq: Seq): () => void {\n\t\tconst ops: (() => void)[] = [];\n\t\tconst created = new Set<Id>();\n\t\tlet maxId = 0;\n\t\tconst claim = (id: Id, what: string) => {\n\t\t\tif (!Number.isSafeInteger(id) || id <= 0) throw new Error(`${what}: invalid id ${id}`);\n\t\t\tif (created.has(id)) throw new Error(`${what}: id ${id} created twice in one batch`);\n\t\t\tcreated.add(id);\n\t\t\tmaxId = Math.max(maxId, id);\n\t\t};\n\t\tfor (const w of writes) {\n\t\t\tswitch (w.type) {\n\t\t\t\tcase \"conversation\": {\n\t\t\t\t\tif (this.conversationsById.has(w.conversation.id))\n\t\t\t\t\t\tthrow new Error(`conversation ${w.conversation.id} exists`);\n\t\t\t\t\tclaim(w.conversation.id, \"conversation\");\n\t\t\t\t\tconst c = clone(w.conversation);\n\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\tthis.conversationsById.set(c.id, c);\n\t\t\t\t\t\tthis.entriesByConversation.set(c.id, []);\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"entry\": {\n\t\t\t\t\tif (this.entriesById.has(w.entry.id)) throw new Error(`entry ${w.entry.id} exists`);\n\t\t\t\t\tclaim(w.entry.id, \"entry\");\n\t\t\t\t\tconst e = clone(w.entry);\n\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\tthis.entriesById.set(e.id, e);\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\tthis.entriesByConversation.get(e.conversationId) ??\n\t\t\t\t\t\t\tthis.entriesByConversation.set(e.conversationId, []).get(e.conversationId)!\n\t\t\t\t\t\t).push(e);\n\t\t\t\t\t\tthis.entrySeq.set(e.id, seq);\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\tif (this.tasksById.has(w.task.id)) throw new Error(`task ${w.task.id} exists`);\n\t\t\t\t\tclaim(w.task.id, \"task\");\n\t\t\t\t\tconst t = clone(w.task);\n\t\t\t\t\tops.push(() => this.tasksById.set(t.id, t));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"task.patch\": {\n\t\t\t\t\tconst prev =\n\t\t\t\t\t\tthis.tasksById.get(w.patch.id) ??\n\t\t\t\t\t\t[...writes]\n\t\t\t\t\t\t\t.filter((x): x is Extract<Write, { type: \"task\" }> => x.type === \"task\")\n\t\t\t\t\t\t\t.find((x) => x.task.id === w.patch.id)?.task;\n\t\t\t\t\tif (prev === undefined) throw new Error(`patch for unknown task ${w.patch.id}`);\n\t\t\t\t\tconst p = clone(w.patch);\n\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\tconst cur = this.tasksById.get(p.id)!;\n\t\t\t\t\t\tconst { id: _i, ...fields } = p;\n\t\t\t\t\t\tconst next = { ...cur, ...fields } as Task;\n\t\t\t\t\t\tif (p.checkpoint === null) delete (next as { checkpoint?: unknown }).checkpoint;\n\t\t\t\t\t\tthis.tasksById.set(p.id, next);\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"input\": {\n\t\t\t\t\tconst i = clone(w.input);\n\t\t\t\t\tif (!this.inputsById.has(i.id)) claim(i.id, \"input\");\n\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\tthis.inputsById.set(i.id, i);\n\t\t\t\t\t\tif (i.requestId !== undefined) this.inputsByRequest.set(`${i.conversationId}:${i.requestId}`, i);\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"doc\": {\n\t\t\t\t\tconst ref = clone(w.ref);\n\t\t\t\t\tconst o = clone(w.ops);\n\t\t\t\t\tif (ref.doc === \"session\")\n\t\t\t\t\t\tops.push(() => {\n\t\t\t\t\t\t\tthis.sessionDoc = applyImmutable(\n\t\t\t\t\t\t\t\t(this.sessionDoc ?? { plugins: {} }) as JsonObject,\n\t\t\t\t\t\t\t\to,\n\t\t\t\t\t\t\t) as JsonObject;\n\t\t\t\t\t\t});\n\t\t\t\t\telse if (ref.doc === \"rewindable\")\n\t\t\t\t\t\tops.push(() =>\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tthis.rewindableLog.get(ref.conversationId) ??\n\t\t\t\t\t\t\t\tthis.rewindableLog.set(ref.conversationId, []).get(ref.conversationId)!\n\t\t\t\t\t\t\t).push({ seq, ops: o }),\n\t\t\t\t\t\t);\n\t\t\t\t\telse\n\t\t\t\t\t\tops.push(() =>\n\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\tthis.stickyLog.get(ref.conversationId) ??\n\t\t\t\t\t\t\t\tthis.stickyLog.set(ref.conversationId, []).get(ref.conversationId)!\n\t\t\t\t\t\t\t).push(o),\n\t\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\tfor (const op of ops) op();\n\t\t\tthis.setNextId(maxId + 1);\n\t\t};\n\t}\n\n\tasync conversation(id: Id, _ctx?: Context) {\n\t\treturn optionalClone(this.conversationsById.get(id));\n\t}\n\tasync conversations(_ctx?: Context) {\n\t\treturn [...this.conversationsById.values()].map(clone);\n\t}\n\tasync entries(ids: readonly Id[], _ctx?: Context) {\n\t\tconst out = new Map<Id, Entry>();\n\t\tfor (const id of ids) {\n\t\t\tconst e = this.entriesById.get(id);\n\t\t\tif (e) out.set(id, clone(e));\n\t\t}\n\t\treturn out;\n\t}\n\t/** Newest-first, fork-aware: after this conversation's own entries, the parent's up to the fork point, and so on. */\n\tasync scanEntries(scan: EntryScan, _ctx?: Context) {\n\t\tconst out: Entry[] = [];\n\t\tlet conversationId: Id | undefined = scan.conversationId;\n\t\tlet cap: Id | undefined = scan.before;\n\t\twhile (conversationId !== undefined && out.length < scan.limit) {\n\t\t\tconst own = this.entriesByConversation.get(conversationId) ?? [];\n\t\t\tfor (let i = own.length - 1; i >= 0 && out.length < scan.limit; i--) {\n\t\t\t\tconst e = own[i]!;\n\t\t\t\tif (cap !== undefined && e.id >= cap) continue;\n\t\t\t\tif (scan.kind !== undefined && e.kind !== scan.kind) continue;\n\t\t\t\tif (scan.withHead && e.head === undefined) continue;\n\t\t\t\tout.push(clone(e));\n\t\t\t}\n\t\t\tconst c = this.conversationsById.get(conversationId);\n\t\t\tconversationId = c?.parent?.conversationId;\n\t\t\tcap = c?.parent === undefined ? undefined : Math.min(cap ?? Infinity, c.parent.at + 1);\n\t\t}\n\t\treturn out;\n\t}\n\tasync task(id: Id, _ctx?: Context) {\n\t\treturn optionalClone(this.tasksById.get(id));\n\t}\n\tasync scanTasks(scan: TaskScan, _ctx?: Context) {\n\t\tconst out: Task[] = [];\n\t\tfor (const t of this.tasksById.values()) {\n\t\t\tif (scan.conversationId !== undefined && t.conversationId !== scan.conversationId) continue;\n\t\t\tif (scan.status !== undefined && !scan.status.includes(t.status)) continue;\n\t\t\tif (scan.kind !== undefined && t.kind !== scan.kind) continue;\n\t\t\tout.push(clone(t));\n\t\t}\n\t\treturn out;\n\t}\n\tasync input(id: Id, _ctx?: Context) {\n\t\treturn optionalClone(this.inputsById.get(id));\n\t}\n\tasync inputByRequest(conversationId: Id, requestId: string, _ctx?: Context) {\n\t\treturn optionalClone(this.inputsByRequest.get(`${conversationId}:${requestId}`));\n\t}\n\n\tasync doc(ref: DocRef, _ctx?: Context): Promise<JsonObject | undefined> {\n\t\tif (ref.doc === \"session\")\n\t\t\treturn this.sessionDoc === undefined ? { plugins: {} } : structuredClone(this.sessionDoc);\n\t\tconst log =\n\t\t\tref.doc === \"rewindable\"\n\t\t\t\t? this.rewindableLog.get(ref.conversationId)?.map((r) => r.ops)\n\t\t\t\t: this.stickyLog.get(ref.conversationId);\n\t\tif (log === undefined) return undefined;\n\t\treturn clone(this.fold(log));\n\t}\n\t/**\n\t * Commit-granular history: the rewindable state after the atomic commit that contains\n\t * entry `at`, walking the fork chain for entries owned by ancestors.\n\t */\n\tasync docAsOf(conversationId: Id, at: Id, _ctx?: Context): Promise<JsonObject | undefined> {\n\t\tconst owner = this.entriesById.get(at)?.conversationId;\n\t\tif (owner === undefined) return undefined;\n\t\tconst chain: Id[] = [];\n\t\tlet c: Conversation | undefined = this.conversationsById.get(conversationId);\n\t\twhile (c !== undefined && c.id !== owner) {\n\t\t\tchain.push(c.id);\n\t\t\tc = c.parent ? this.conversationsById.get(c.parent.conversationId) : undefined;\n\t\t}\n\t\tif (c === undefined) return undefined;\n\t\tconst seqAt = this.entrySeq.get(at) ?? 0;\n\t\tconst log = (this.rewindableLog.get(owner) ?? []).filter((r) => r.seq <= seqAt).map((r) => r.ops);\n\t\treturn log.length === 0 ? undefined : clone(this.fold(log));\n\t}\n\tprotected fold(log: Op[][]): JsonObject {\n\t\tlet start = 0;\n\t\tfor (let i = log.length - 1; i >= 0; i--)\n\t\t\tif (isBase(log[i]!)) {\n\t\t\t\tstart = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\tlet state: JsonObject = {};\n\t\tfor (let i = start; i < log.length; i++) state = applyImmutable(state, log[i]!) as JsonObject;\n\t\treturn state;\n\t}\n\tasync truncate(ref: DocRef, _ctx?: Context) {\n\t\tif (ref.doc !== \"sticky\") return;\n\t\tconst log = this.stickyLog.get(ref.conversationId);\n\t\tif (log === undefined) return;\n\t\tlet start = 0;\n\t\tfor (let i = log.length - 1; i >= 0; i--)\n\t\t\tif (isBase(log[i]!)) {\n\t\t\t\tstart = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\tlog.splice(0, start);\n\t}\n\tasync close(_ctx?: Context) {\n\t\tthis.closed = true;\n\t}\n}\n\nfunction clone<T>(value: T): T {\n\treturn JSON.parse(JSON.stringify(value)) as T;\n}\n\nfunction optionalClone<T>(value: T | undefined): T | undefined {\n\treturn value === undefined ? undefined : clone(value);\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Context } from "@earendil-works/chord";
|
|
2
|
+
import type { Session } from "./session.ts";
|
|
3
|
+
import { type AnyKind, type Id, type Input, type Invoker, type Runtime, type Task } from "./types.ts";
|
|
4
|
+
export interface SchedulerDeps {
|
|
5
|
+
session: Session;
|
|
6
|
+
kinds: ReadonlyMap<string, AnyKind>;
|
|
7
|
+
runtime(task: Task, invoker: Extract<Invoker, {
|
|
8
|
+
type: "task";
|
|
9
|
+
}>, ctx: Context): Runtime;
|
|
10
|
+
onReport(error: unknown): void;
|
|
11
|
+
ctx: Context;
|
|
12
|
+
}
|
|
13
|
+
export declare class Scheduler {
|
|
14
|
+
private readonly deps;
|
|
15
|
+
private enabled;
|
|
16
|
+
private holds;
|
|
17
|
+
private dirty;
|
|
18
|
+
private draining;
|
|
19
|
+
private readonly invocations;
|
|
20
|
+
private readonly taskWaiters;
|
|
21
|
+
private readonly inputWaiters;
|
|
22
|
+
private readonly idleWaiters;
|
|
23
|
+
constructor(deps: SchedulerDeps);
|
|
24
|
+
resume(): void;
|
|
25
|
+
stop(): void;
|
|
26
|
+
hold(): () => void;
|
|
27
|
+
kick(): void;
|
|
28
|
+
private drain;
|
|
29
|
+
private reserveEligible;
|
|
30
|
+
private dispatch;
|
|
31
|
+
private lease;
|
|
32
|
+
private runPhases;
|
|
33
|
+
/** Mark, revoke, signal, join. The scheduler then reserves the fresh abort on its next drain. */
|
|
34
|
+
abortTask(id: Id, ctx: Context): Promise<"marked" | "terminal">;
|
|
35
|
+
waitForTask(id: Id, ctx: Context): Promise<Task>;
|
|
36
|
+
waitForInput(id: Id, ctx: Context): Promise<Input>;
|
|
37
|
+
private waiter;
|
|
38
|
+
waitForIdle(conversationId: Id | undefined, ctx: Context): Promise<void>;
|
|
39
|
+
private isIdle;
|
|
40
|
+
private checkIdle;
|
|
41
|
+
/** Signal every invocation and wait for them; nothing is written. */
|
|
42
|
+
joinAll(): Promise<void>;
|
|
43
|
+
quiescent(): boolean;
|
|
44
|
+
get liveInvocations(): number;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=scheduler.d.ts.map
|