arborkit 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/README.md +8 -0
- package/dist/addressing.js +4 -52
- package/dist/addressing.js.map +1 -1
- package/dist/ag-ui.js +6 -44
- package/dist/ag-ui.js.map +1 -1
- package/dist/arbor.js +25 -1537
- package/dist/arbor.js.map +1 -1
- package/dist/artifact-tree.js +5 -281
- package/dist/artifact-tree.js.map +1 -1
- package/dist/chunk-2UY5KJ5P.js +72 -0
- package/dist/chunk-2UY5KJ5P.js.map +1 -0
- package/dist/chunk-32FKMLEA.js +117 -0
- package/dist/chunk-32FKMLEA.js.map +1 -0
- package/dist/chunk-3BWK5SLN.js +22 -0
- package/dist/chunk-3BWK5SLN.js.map +1 -0
- package/dist/chunk-53XPRLFF.js +213 -0
- package/dist/chunk-53XPRLFF.js.map +1 -0
- package/dist/chunk-55J6XMHW.js +1 -0
- package/dist/chunk-55J6XMHW.js.map +1 -0
- package/dist/chunk-6DOI4FNX.js +18 -0
- package/dist/chunk-6DOI4FNX.js.map +1 -0
- package/dist/chunk-AGPH62II.js +78 -0
- package/dist/chunk-AGPH62II.js.map +1 -0
- package/dist/chunk-BYXJ5B5F.js +40 -0
- package/dist/chunk-BYXJ5B5F.js.map +1 -0
- package/dist/chunk-DQW5EEWU.js +20 -0
- package/dist/chunk-DQW5EEWU.js.map +1 -0
- package/dist/chunk-I4NLMLE2.js +43 -0
- package/dist/chunk-I4NLMLE2.js.map +1 -0
- package/dist/chunk-KRJCSJL4.js +208 -0
- package/dist/chunk-KRJCSJL4.js.map +1 -0
- package/dist/chunk-L4HFSLBD.js +112 -0
- package/dist/chunk-L4HFSLBD.js.map +1 -0
- package/dist/chunk-M72UOSPG.js +43 -0
- package/dist/chunk-M72UOSPG.js.map +1 -0
- package/dist/chunk-OZCCBVUV.js +50 -0
- package/dist/chunk-OZCCBVUV.js.map +1 -0
- package/dist/chunk-QRGDUGY6.js +143 -0
- package/dist/chunk-QRGDUGY6.js.map +1 -0
- package/dist/chunk-QWYVTW2C.js +70 -0
- package/dist/chunk-QWYVTW2C.js.map +1 -0
- package/dist/chunk-TEJZF3IR.js +24 -0
- package/dist/chunk-TEJZF3IR.js.map +1 -0
- package/dist/chunk-U3NXJNUY.js +23 -0
- package/dist/chunk-U3NXJNUY.js.map +1 -0
- package/dist/chunk-UTJZYWK3.js +272 -0
- package/dist/chunk-UTJZYWK3.js.map +1 -0
- package/dist/chunk-V3HDDWER.js +60 -0
- package/dist/chunk-V3HDDWER.js.map +1 -0
- package/dist/chunk-VG5BEOBI.js +34 -0
- package/dist/chunk-VG5BEOBI.js.map +1 -0
- package/dist/chunk-VGHBPFCV.js +18 -0
- package/dist/chunk-VGHBPFCV.js.map +1 -0
- package/dist/chunk-WEO5KBRI.js +148 -0
- package/dist/chunk-WEO5KBRI.js.map +1 -0
- package/dist/chunk-XT7WR7OF.js +23 -0
- package/dist/chunk-XT7WR7OF.js.map +1 -0
- package/dist/chunk-XWCA5XHZ.js +12 -0
- package/dist/chunk-XWCA5XHZ.js.map +1 -0
- package/dist/chunk-YCDDDYGG.js +22 -0
- package/dist/chunk-YCDDDYGG.js.map +1 -0
- package/dist/chunk-YWY2EK2Q.js +40 -0
- package/dist/chunk-YWY2EK2Q.js.map +1 -0
- package/dist/chunk-ZBBMJMLG.js +33 -0
- package/dist/chunk-ZBBMJMLG.js.map +1 -0
- package/dist/chunk-ZDANDXJ6.js +148 -0
- package/dist/chunk-ZDANDXJ6.js.map +1 -0
- package/dist/clock.js +4 -18
- package/dist/clock.js.map +1 -1
- package/dist/decompose.js +5 -16
- package/dist/decompose.js.map +1 -1
- package/dist/delta-storage.js +5 -101
- package/dist/delta-storage.js.map +1 -1
- package/dist/delta.js +15 -732
- package/dist/delta.js.map +1 -1
- package/dist/embedding-port.js +3 -17
- package/dist/embedding-port.js.map +1 -1
- package/dist/embedding-text.js +4 -16
- package/dist/embedding-text.js.map +1 -1
- package/dist/errors.js +8 -50
- package/dist/errors.js.map +1 -1
- package/dist/event-log.js +4 -78
- package/dist/event-log.js.map +1 -1
- package/dist/file-storage.js +3 -38
- package/dist/file-storage.js.map +1 -1
- package/dist/ids.js +4 -17
- package/dist/ids.js.map +1 -1
- package/dist/index.js +117 -1763
- package/dist/index.js.map +1 -1
- package/dist/json-edit.js +10 -75
- package/dist/json-edit.js.map +1 -1
- package/dist/jsonpointer.js +8 -24
- package/dist/jsonpointer.js.map +1 -1
- package/dist/mutator.js +5 -240
- package/dist/mutator.js.map +1 -1
- package/dist/navigator.js +7 -188
- package/dist/navigator.js.map +1 -1
- package/dist/path-glob.js +5 -35
- package/dist/path-glob.js.map +1 -1
- package/dist/registry-validator.js +3 -7
- package/dist/registry-validator.js.map +1 -1
- package/dist/replay.js +6 -179
- package/dist/replay.js.map +1 -1
- package/dist/semantic-index.js +5 -222
- package/dist/semantic-index.js.map +1 -1
- package/dist/storage.js +9 -372
- package/dist/storage.js.map +1 -1
- package/dist/toolset.d.ts +10 -0
- package/dist/toolset.js +8 -302
- package/dist/toolset.js.map +1 -1
- package/dist/type-aware-decision.js +3 -13
- package/dist/type-aware-decision.js.map +1 -1
- package/dist/type-registry.js +3 -13
- package/dist/type-registry.js.map +1 -1
- package/dist/types.js +1 -0
- package/dist/vector-index-port.js +3 -45
- package/dist/vector-index-port.js.map +1 -1
- package/dist/zod-adapter.js +4 -30
- package/dist/zod-adapter.js.map +1 -1
- package/package.json +128 -5
package/dist/arbor.js
CHANGED
|
@@ -1,1540 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
};
|
|
27
|
-
var NodeNotFoundError = class extends ArborError {
|
|
28
|
-
constructor(ref) {
|
|
29
|
-
super("NODE_NOT_FOUND", `Node not found: ${JSON.stringify(ref)}`);
|
|
30
|
-
this.ref = ref;
|
|
31
|
-
}
|
|
32
|
-
ref;
|
|
33
|
-
};
|
|
34
|
-
var ScopeViolationError = class extends ArborError {
|
|
35
|
-
constructor(targetPath, scope) {
|
|
36
|
-
super("SCOPE_VIOLATION", `Access outside scope: ${targetPath} (scope: ${scope})`);
|
|
37
|
-
this.targetPath = targetPath;
|
|
38
|
-
this.scope = scope;
|
|
39
|
-
}
|
|
40
|
-
targetPath;
|
|
41
|
-
scope;
|
|
42
|
-
};
|
|
43
|
-
var StaleVersionError = class extends ArborError {
|
|
44
|
-
constructor(id, expected, actual) {
|
|
45
|
-
super("STALE_VERSION", `Stale version for ${id}: expected ${expected}, actual ${actual}`);
|
|
46
|
-
this.id = id;
|
|
47
|
-
this.expected = expected;
|
|
48
|
-
this.actual = actual;
|
|
49
|
-
}
|
|
50
|
-
id;
|
|
51
|
-
expected;
|
|
52
|
-
actual;
|
|
53
|
-
};
|
|
54
|
-
var InvalidOpError = class extends ArborError {
|
|
55
|
-
constructor(message) {
|
|
56
|
-
super("INVALID_OP", message);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// src/artifact-tree.ts
|
|
61
|
-
var ArtifactTree = class _ArtifactTree {
|
|
62
|
-
constructor(deps) {
|
|
63
|
-
this.deps = deps;
|
|
64
|
-
}
|
|
65
|
-
deps;
|
|
66
|
-
nodes = /* @__PURE__ */ new Map();
|
|
67
|
-
rootId;
|
|
68
|
-
/** Lazily built per-parent key→childId maps for O(1) object-child lookup.
|
|
69
|
-
* A cache OUTSIDE the nodes (ArbNode/StoredArtifact stay byte-identical);
|
|
70
|
-
* invalidated on every child-set change, rebuilt on read. Arrays never
|
|
71
|
-
* populate it (index lookup is already O(1)). */
|
|
72
|
-
keyMaps = /* @__PURE__ */ new Map();
|
|
73
|
-
static fromJson(json, deps) {
|
|
74
|
-
const tree = new _ArtifactTree(deps);
|
|
75
|
-
tree.rootId = tree.build(json, null, null);
|
|
76
|
-
return tree;
|
|
77
|
-
}
|
|
78
|
-
build(value, parentId, key, type) {
|
|
79
|
-
const opaque = this.deps.decision.isOpaque(value, type);
|
|
80
|
-
const kind = kindOf(value, opaque);
|
|
81
|
-
const id = this.deps.idGen.next();
|
|
82
|
-
const node = {
|
|
83
|
-
id,
|
|
84
|
-
parentId,
|
|
85
|
-
key,
|
|
86
|
-
kind,
|
|
87
|
-
content: kind === "leaf" ? value : null,
|
|
88
|
-
childIds: [],
|
|
89
|
-
meta: { version: 0, updatedAt: this.deps.clock.now(), embedding: { state: "none" } }
|
|
90
|
-
};
|
|
91
|
-
if (type !== void 0) node.type = type;
|
|
92
|
-
this.nodes.set(id, node);
|
|
93
|
-
if (kind === "object") {
|
|
94
|
-
for (const [k, v] of Object.entries(value)) {
|
|
95
|
-
node.childIds.push(this.build(v, id, k));
|
|
96
|
-
}
|
|
97
|
-
} else if (kind === "array") {
|
|
98
|
-
value.forEach((v, i) => {
|
|
99
|
-
node.childIds.push(this.build(v, id, i));
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
return id;
|
|
103
|
-
}
|
|
104
|
-
get(id) {
|
|
105
|
-
return this.nodes.get(id);
|
|
106
|
-
}
|
|
107
|
-
root() {
|
|
108
|
-
return this.nodes.get(this.rootId);
|
|
109
|
-
}
|
|
110
|
-
rootIdValue() {
|
|
111
|
-
return this.rootId;
|
|
112
|
-
}
|
|
113
|
-
/** O(1) child lookup: arrays by index, objects via a lazily built key map. */
|
|
114
|
-
childByKey(parentId, key) {
|
|
115
|
-
const parent = this.nodes.get(parentId);
|
|
116
|
-
if (!parent) return void 0;
|
|
117
|
-
if (parent.kind === "array") {
|
|
118
|
-
if (!/^(0|[1-9]\d*)$/.test(key)) return void 0;
|
|
119
|
-
const i = Number(key);
|
|
120
|
-
if (i >= parent.childIds.length) return void 0;
|
|
121
|
-
return this.nodes.get(parent.childIds[i]);
|
|
122
|
-
}
|
|
123
|
-
let map = this.keyMaps.get(parentId);
|
|
124
|
-
if (!map) {
|
|
125
|
-
map = /* @__PURE__ */ new Map();
|
|
126
|
-
for (const cid2 of parent.childIds) map.set(String(this.nodes.get(cid2).key), cid2);
|
|
127
|
-
this.keyMaps.set(parentId, map);
|
|
128
|
-
}
|
|
129
|
-
const cid = map.get(key);
|
|
130
|
-
return cid === void 0 ? void 0 : this.nodes.get(cid);
|
|
131
|
-
}
|
|
132
|
-
children(id) {
|
|
133
|
-
const n = this.nodes.get(id);
|
|
134
|
-
if (!n) return [];
|
|
135
|
-
return n.childIds.map((cid) => this.nodes.get(cid));
|
|
136
|
-
}
|
|
137
|
-
has(id) {
|
|
138
|
-
return this.nodes.has(id);
|
|
139
|
-
}
|
|
140
|
-
size() {
|
|
141
|
-
return this.nodes.size;
|
|
142
|
-
}
|
|
143
|
-
/** Reconstruct the JSON value rooted at `id` (defaults to the tree root). */
|
|
144
|
-
toJson(id = this.rootId) {
|
|
145
|
-
const n = this.nodes.get(id);
|
|
146
|
-
if (!n) throw new Error(`Unknown node: ${id}`);
|
|
147
|
-
if (n.kind === "leaf") return n.content;
|
|
148
|
-
if (n.kind === "array") return n.childIds.map((cid) => this.toJson(cid));
|
|
149
|
-
const obj = {};
|
|
150
|
-
for (const cid of n.childIds) {
|
|
151
|
-
const c = this.nodes.get(cid);
|
|
152
|
-
obj[String(c.key)] = this.toJson(cid);
|
|
153
|
-
}
|
|
154
|
-
return obj;
|
|
155
|
-
}
|
|
156
|
-
/** Replace the subtree value at `id` in place, keeping the node's id/key/parentId.
|
|
157
|
-
* `clearType` explicitly un-types the node (used by type-aware revert). */
|
|
158
|
-
replaceValue(id, value, type, clearType = false) {
|
|
159
|
-
const node = this.nodes.get(id);
|
|
160
|
-
if (!node) throw new InvalidOpError(`Unknown node: ${id}`);
|
|
161
|
-
this.deleteDescendants(id);
|
|
162
|
-
this.keyMaps.delete(id);
|
|
163
|
-
const opaque = this.deps.decision.isOpaque(value, type);
|
|
164
|
-
const kind = kindOf(value, opaque);
|
|
165
|
-
node.kind = kind;
|
|
166
|
-
node.content = kind === "leaf" ? value : null;
|
|
167
|
-
node.childIds = [];
|
|
168
|
-
if (clearType) node.type = void 0;
|
|
169
|
-
else if (type !== void 0) node.type = type;
|
|
170
|
-
if (kind === "object") {
|
|
171
|
-
for (const [k, v] of Object.entries(value)) {
|
|
172
|
-
node.childIds.push(this.build(v, id, k));
|
|
173
|
-
}
|
|
174
|
-
} else if (kind === "array") {
|
|
175
|
-
value.forEach((v, i) => {
|
|
176
|
-
node.childIds.push(this.build(v, id, i));
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
/** Recursively remove all descendants of `id` from the node map (keeps `id` itself). */
|
|
181
|
-
deleteDescendants(id) {
|
|
182
|
-
const node = this.nodes.get(id);
|
|
183
|
-
if (!node) return;
|
|
184
|
-
for (const cid of node.childIds) {
|
|
185
|
-
this.deleteDescendants(cid);
|
|
186
|
-
this.nodes.delete(cid);
|
|
187
|
-
this.keyMaps.delete(cid);
|
|
188
|
-
}
|
|
189
|
-
node.childIds = [];
|
|
190
|
-
}
|
|
191
|
-
/** Deep, independent copy of the tree state for transaction rollback.
|
|
192
|
-
* `restore` consumes the snapshot; do not reuse it afterwards. */
|
|
193
|
-
snapshot() {
|
|
194
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
195
|
-
for (const [id, node] of this.nodes) {
|
|
196
|
-
nodes.set(id, structuredClone(node));
|
|
197
|
-
}
|
|
198
|
-
return { nodes, rootId: this.rootId };
|
|
199
|
-
}
|
|
200
|
-
/** Replace the tree state with a previously taken snapshot. The snapshot's
|
|
201
|
-
* nodes are adopted BY REFERENCE — restore consumes the snapshot; do not
|
|
202
|
-
* reuse it afterwards. */
|
|
203
|
-
restore(snap) {
|
|
204
|
-
this.nodes.clear();
|
|
205
|
-
this.keyMaps.clear();
|
|
206
|
-
for (const [id, node] of snap.nodes) {
|
|
207
|
-
this.nodes.set(id, node);
|
|
208
|
-
}
|
|
209
|
-
this.rootId = snap.rootId;
|
|
210
|
-
}
|
|
211
|
-
/** Insert a decomposed `value` as a child of `parentId`. For objects `keyOrIndex` is the string key; for arrays it is the insert index. Returns the new child's id. */
|
|
212
|
-
insertChild(parentId, keyOrIndex, value, type) {
|
|
213
|
-
const parent = this.nodes.get(parentId);
|
|
214
|
-
if (!parent) throw new InvalidOpError(`Unknown node: ${parentId}`);
|
|
215
|
-
if (parent.kind === "object") {
|
|
216
|
-
if (typeof keyOrIndex !== "string") {
|
|
217
|
-
throw new InvalidOpError("object insert requires a string key");
|
|
218
|
-
}
|
|
219
|
-
if (parent.childIds.some((cid2) => this.nodes.get(cid2).key === keyOrIndex)) {
|
|
220
|
-
throw new InvalidOpError(`key already exists: ${keyOrIndex}`);
|
|
221
|
-
}
|
|
222
|
-
const cid = this.build(value, parentId, keyOrIndex, type);
|
|
223
|
-
parent.childIds.push(cid);
|
|
224
|
-
this.keyMaps.delete(parentId);
|
|
225
|
-
return cid;
|
|
226
|
-
}
|
|
227
|
-
if (parent.kind === "array") {
|
|
228
|
-
if (typeof keyOrIndex !== "number") {
|
|
229
|
-
throw new InvalidOpError("array insert requires a numeric index");
|
|
230
|
-
}
|
|
231
|
-
const at = Math.max(0, Math.min(keyOrIndex, parent.childIds.length));
|
|
232
|
-
const cid = this.build(value, parentId, at, type);
|
|
233
|
-
parent.childIds.splice(at, 0, cid);
|
|
234
|
-
this.renumberArray(parentId);
|
|
235
|
-
return cid;
|
|
236
|
-
}
|
|
237
|
-
throw new InvalidOpError("cannot insert into a leaf node");
|
|
238
|
-
}
|
|
239
|
-
/** Remove `childId` (and its subtree) from `parentId`. Renumbers array siblings. */
|
|
240
|
-
removeChild(parentId, childId) {
|
|
241
|
-
const parent = this.nodes.get(parentId);
|
|
242
|
-
if (!parent) throw new InvalidOpError(`Unknown node: ${parentId}`);
|
|
243
|
-
const idx = parent.childIds.indexOf(childId);
|
|
244
|
-
if (idx < 0) throw new InvalidOpError(`${childId} is not a child of ${parentId}`);
|
|
245
|
-
this.deleteDescendants(childId);
|
|
246
|
-
this.nodes.delete(childId);
|
|
247
|
-
this.keyMaps.delete(childId);
|
|
248
|
-
parent.childIds.splice(idx, 1);
|
|
249
|
-
this.keyMaps.delete(parentId);
|
|
250
|
-
if (parent.kind === "array") this.renumberArray(parentId);
|
|
251
|
-
}
|
|
252
|
-
/** Move `id` under `newParentId` at `keyOrIndex`, preserving `id`. Renumbers affected arrays.
|
|
253
|
-
* ALL validation happens before any mutation — a rejected move leaves the tree untouched. */
|
|
254
|
-
moveNode(id, newParentId, keyOrIndex) {
|
|
255
|
-
const node = this.nodes.get(id);
|
|
256
|
-
if (!node) throw new InvalidOpError(`Unknown node: ${id}`);
|
|
257
|
-
if (node.parentId === null) throw new InvalidOpError("cannot move the root");
|
|
258
|
-
const newParent = this.nodes.get(newParentId);
|
|
259
|
-
if (!newParent) throw new InvalidOpError(`Unknown node: ${newParentId}`);
|
|
260
|
-
if (newParent.kind === "leaf") throw new InvalidOpError("cannot move into a leaf node");
|
|
261
|
-
let anc = newParentId;
|
|
262
|
-
while (anc !== null) {
|
|
263
|
-
if (anc === id) throw new InvalidOpError("cannot move a node into itself or its own subtree");
|
|
264
|
-
anc = this.nodes.get(anc)?.parentId ?? null;
|
|
265
|
-
}
|
|
266
|
-
if (newParent.kind === "object") {
|
|
267
|
-
if (typeof keyOrIndex !== "string") throw new InvalidOpError("object move requires a string key");
|
|
268
|
-
if (newParent.childIds.some((cid) => cid !== id && this.nodes.get(cid).key === keyOrIndex)) {
|
|
269
|
-
throw new InvalidOpError(`key already exists: ${keyOrIndex}`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
const oldParent = this.nodes.get(node.parentId);
|
|
273
|
-
const oldIdx = oldParent.childIds.indexOf(id);
|
|
274
|
-
oldParent.childIds.splice(oldIdx, 1);
|
|
275
|
-
this.keyMaps.delete(oldParent.id);
|
|
276
|
-
this.keyMaps.delete(newParentId);
|
|
277
|
-
if (oldParent.kind === "array") this.renumberArray(oldParent.id);
|
|
278
|
-
if (newParent.kind === "object") {
|
|
279
|
-
node.parentId = newParentId;
|
|
280
|
-
node.key = keyOrIndex;
|
|
281
|
-
newParent.childIds.push(id);
|
|
282
|
-
} else {
|
|
283
|
-
const at = typeof keyOrIndex === "number" ? Math.max(0, Math.min(keyOrIndex, newParent.childIds.length)) : newParent.childIds.length;
|
|
284
|
-
node.parentId = newParentId;
|
|
285
|
-
newParent.childIds.splice(at, 0, id);
|
|
286
|
-
this.renumberArray(newParentId);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
/** Set each array child's `key` to its current position. */
|
|
290
|
-
renumberArray(parentId) {
|
|
291
|
-
const parent = this.nodes.get(parentId);
|
|
292
|
-
if (!parent) return;
|
|
293
|
-
parent.childIds.forEach((cid, i) => {
|
|
294
|
-
this.nodes.get(cid).key = i;
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
/** All nodes in the tree (for serialization). */
|
|
298
|
-
allNodes() {
|
|
299
|
-
return [...this.nodes.values()];
|
|
300
|
-
}
|
|
301
|
-
/** Rebuild a tree from previously serialized nodes, preserving their ids. */
|
|
302
|
-
static fromStored(nodes, rootId, deps) {
|
|
303
|
-
const tree = new _ArtifactTree(deps);
|
|
304
|
-
for (const node of nodes) tree.nodes.set(node.id, node);
|
|
305
|
-
tree.rootId = rootId;
|
|
306
|
-
return tree;
|
|
307
|
-
}
|
|
308
|
-
/** All transitive descendant ids of `id` (depth-first), excluding `id` itself. */
|
|
309
|
-
descendantIds(id) {
|
|
310
|
-
const out = [];
|
|
311
|
-
const node = this.nodes.get(id);
|
|
312
|
-
if (!node) return out;
|
|
313
|
-
for (const cid of node.childIds) {
|
|
314
|
-
out.push(cid);
|
|
315
|
-
out.push(...this.descendantIds(cid));
|
|
316
|
-
}
|
|
317
|
-
return out;
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
// src/jsonpointer.ts
|
|
322
|
-
function encodeSegment(s) {
|
|
323
|
-
return s.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
324
|
-
}
|
|
325
|
-
function decodeSegment(s) {
|
|
326
|
-
return s.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
327
|
-
}
|
|
328
|
-
function buildPointer(segments) {
|
|
329
|
-
if (segments.length === 0) return "";
|
|
330
|
-
return "/" + segments.map((s) => encodeSegment(String(s))).join("/");
|
|
331
|
-
}
|
|
332
|
-
function appendPointer(parent, key) {
|
|
333
|
-
return parent + "/" + encodeSegment(String(key));
|
|
334
|
-
}
|
|
335
|
-
function isWithin(path, scope) {
|
|
336
|
-
return scope === void 0 || path === scope || path.startsWith(scope + "/");
|
|
337
|
-
}
|
|
338
|
-
function parsePointer(pointer) {
|
|
339
|
-
if (pointer === "") return [];
|
|
340
|
-
if (!pointer.startsWith("/")) {
|
|
341
|
-
throw new Error(`Invalid JSON Pointer (must be "" or start with "/"): ${pointer}`);
|
|
342
|
-
}
|
|
343
|
-
return pointer.slice(1).split("/").map(decodeSegment);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// src/addressing.ts
|
|
347
|
-
var Addressing = class {
|
|
348
|
-
constructor(tree) {
|
|
349
|
-
this.tree = tree;
|
|
350
|
-
}
|
|
351
|
-
tree;
|
|
352
|
-
byId(id) {
|
|
353
|
-
return this.tree.get(id);
|
|
354
|
-
}
|
|
355
|
-
/** Compute the JSON Pointer for a node by walking parent links to the root. */
|
|
356
|
-
pathOf(id) {
|
|
357
|
-
const cur0 = this.tree.get(id);
|
|
358
|
-
if (!cur0) throw new Error(`Unknown node: ${id}`);
|
|
359
|
-
const segments = [];
|
|
360
|
-
let cur = cur0;
|
|
361
|
-
while (cur && cur.parentId !== null) {
|
|
362
|
-
segments.unshift(cur.key);
|
|
363
|
-
cur = this.tree.get(cur.parentId);
|
|
364
|
-
}
|
|
365
|
-
return buildPointer(segments);
|
|
366
|
-
}
|
|
367
|
-
/** Resolve a JSON Pointer to a node, or undefined if any segment is missing. */
|
|
368
|
-
byPath(pointer) {
|
|
369
|
-
const segments = parsePointer(pointer);
|
|
370
|
-
let cur = this.tree.root();
|
|
371
|
-
for (const seg of segments) {
|
|
372
|
-
if (!cur) return void 0;
|
|
373
|
-
cur = this.tree.childByKey(cur.id, seg);
|
|
374
|
-
if (!cur) return void 0;
|
|
375
|
-
}
|
|
376
|
-
return cur;
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
// src/event-log.ts
|
|
381
|
-
var EventLog = class _EventLog {
|
|
382
|
-
events = [];
|
|
383
|
-
baseSeq = 0;
|
|
384
|
-
// count of compacted-away events; events[0].seq === baseSeq
|
|
385
|
-
append(event) {
|
|
386
|
-
const full = { ...event, seq: this.baseSeq + this.events.length };
|
|
387
|
-
this.events.push(full);
|
|
388
|
-
return full;
|
|
389
|
-
}
|
|
390
|
-
entries() {
|
|
391
|
-
return this.events;
|
|
392
|
-
}
|
|
393
|
-
/** Absolute seq of the oldest retained event (0 until compaction). Versions below
|
|
394
|
-
* this have been compacted away and are no longer reconstructable. */
|
|
395
|
-
baseSeqValue() {
|
|
396
|
-
return this.baseSeq;
|
|
397
|
-
}
|
|
398
|
-
/** The event at absolute `seq`, or undefined if compacted away / past the end. */
|
|
399
|
-
at(seq) {
|
|
400
|
-
const i = seq - this.baseSeq;
|
|
401
|
-
return i >= 0 && i < this.events.length ? this.events[i] : void 0;
|
|
402
|
-
}
|
|
403
|
-
since(seq) {
|
|
404
|
-
return this.events.filter((e) => e.seq >= seq);
|
|
405
|
-
}
|
|
406
|
-
/** Absolute next-seq / current version (unchanged across compaction). */
|
|
407
|
-
length() {
|
|
408
|
-
return this.baseSeq + this.events.length;
|
|
409
|
-
}
|
|
410
|
-
/** Drop events past absolute `length` — used to roll back a failed transaction.
|
|
411
|
-
* Throws below the compaction floor: that history is gone and the log cannot roll
|
|
412
|
-
* back past it — `compactTo` must never run inside a transaction. */
|
|
413
|
-
truncateTo(length) {
|
|
414
|
-
if (length < this.baseSeq) {
|
|
415
|
-
throw new InvalidOpError(
|
|
416
|
-
`cannot truncate to ${length}: events before ${this.baseSeq} were compacted away (compactTo must not run inside a transaction)`
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
this.events.length = length - this.baseSeq;
|
|
420
|
-
}
|
|
421
|
-
/** Compaction: drop every retained event with seq < `floorSeq` (history before it
|
|
422
|
-
* becomes unreconstructable). `floorSeq` is clamped to [baseSeq, length()].
|
|
423
|
-
* Returns the number of events dropped. Must NOT be called inside a Mutator
|
|
424
|
-
* transaction (rollback would need the dropped events). */
|
|
425
|
-
compactTo(floorSeq) {
|
|
426
|
-
const floor = Math.max(this.baseSeq, Math.min(floorSeq, this.length()));
|
|
427
|
-
const drop = floor - this.baseSeq;
|
|
428
|
-
if (drop > 0) {
|
|
429
|
-
this.events.splice(0, drop);
|
|
430
|
-
this.baseSeq = floor;
|
|
431
|
-
}
|
|
432
|
-
return drop;
|
|
433
|
-
}
|
|
434
|
-
/** Rebuild a log from previously serialized events, preserving their seq + the
|
|
435
|
-
* compaction floor. */
|
|
436
|
-
static fromStored(events, baseSeq = 0) {
|
|
437
|
-
const log = new _EventLog();
|
|
438
|
-
log.baseSeq = baseSeq;
|
|
439
|
-
for (const e of events) log.events.push({ ...e });
|
|
440
|
-
return log;
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
// src/mutator.ts
|
|
445
|
-
var Mutator = class {
|
|
446
|
-
constructor(tree, addressing, log, deps) {
|
|
447
|
-
this.tree = tree;
|
|
448
|
-
this.addressing = addressing;
|
|
449
|
-
this.log = log;
|
|
450
|
-
this.deps = deps;
|
|
451
|
-
}
|
|
452
|
-
tree;
|
|
453
|
-
addressing;
|
|
454
|
-
log;
|
|
455
|
-
deps;
|
|
456
|
-
resolve(ref) {
|
|
457
|
-
const node = "id" in ref ? this.addressing.byId(ref.id) : this.addressing.byPath(ref.path);
|
|
458
|
-
if (!node) throw new NodeNotFoundError(ref);
|
|
459
|
-
return node;
|
|
460
|
-
}
|
|
461
|
-
checkScope(node, writeScope) {
|
|
462
|
-
if (writeScope === void 0) return;
|
|
463
|
-
const path = this.addressing.pathOf(node.id);
|
|
464
|
-
if (!isWithin(path, writeScope)) throw new ScopeViolationError(path, writeScope);
|
|
465
|
-
}
|
|
466
|
-
checkVersion(node, ifVersion) {
|
|
467
|
-
if (ifVersion !== void 0 && node.meta.version !== ifVersion) {
|
|
468
|
-
throw new StaleVersionError(node.id, ifVersion, node.meta.version);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
bump(node, owner) {
|
|
472
|
-
node.meta.version += 1;
|
|
473
|
-
node.meta.updatedAt = this.deps.clock.now();
|
|
474
|
-
if (owner !== void 0) node.meta.owner = owner;
|
|
475
|
-
}
|
|
476
|
-
set(ref, value, opts = {}) {
|
|
477
|
-
const node = this.resolve(ref);
|
|
478
|
-
this.checkScope(node, opts.writeScope);
|
|
479
|
-
this.checkVersion(node, opts.ifVersion);
|
|
480
|
-
const cloned = structuredClone(value);
|
|
481
|
-
const clearType = opts.type === null;
|
|
482
|
-
const type = clearType ? void 0 : opts.type ?? node.type;
|
|
483
|
-
this.deps.validate?.({ node, proposed: cloned, type, op: "set" });
|
|
484
|
-
const before = this.tree.toJson(node.id);
|
|
485
|
-
const typeBefore = node.type;
|
|
486
|
-
const tagsBefore = [...node.tags ?? []];
|
|
487
|
-
const orphaned = this.tree.descendantIds(node.id);
|
|
488
|
-
this.tree.replaceValue(node.id, cloned, type, clearType);
|
|
489
|
-
if (opts.tags !== void 0) node.tags = [...opts.tags];
|
|
490
|
-
this.bump(node, opts.owner);
|
|
491
|
-
this.deps.onChange?.(node);
|
|
492
|
-
if (this.deps.onChange) {
|
|
493
|
-
for (const id of this.tree.descendantIds(node.id)) {
|
|
494
|
-
const child = this.tree.get(id);
|
|
495
|
-
if (child) this.deps.onChange(child);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
if (this.deps.onRemove) {
|
|
499
|
-
for (const id of orphaned) this.deps.onRemove(id);
|
|
500
|
-
}
|
|
501
|
-
this.log.append({
|
|
502
|
-
kind: "set",
|
|
503
|
-
targetId: node.id,
|
|
504
|
-
parentId: node.parentId,
|
|
505
|
-
key: node.key,
|
|
506
|
-
path: this.addressing.pathOf(node.id),
|
|
507
|
-
before,
|
|
508
|
-
after: cloned,
|
|
509
|
-
nodeTypeBefore: typeBefore ?? null,
|
|
510
|
-
nodeType: type ?? null,
|
|
511
|
-
tagsBefore,
|
|
512
|
-
tags: [...node.tags ?? []],
|
|
513
|
-
actor: opts.owner,
|
|
514
|
-
ts: this.deps.clock.now()
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
insert(parentRef, keyOrIndex, value, opts = {}) {
|
|
518
|
-
const parent = this.resolve(parentRef);
|
|
519
|
-
this.checkScope(parent, opts.writeScope);
|
|
520
|
-
this.checkVersion(parent, opts.ifVersion);
|
|
521
|
-
const cloned = structuredClone(value);
|
|
522
|
-
const type = opts.type === null ? void 0 : opts.type;
|
|
523
|
-
this.deps.validate?.({ node: null, proposed: cloned, type, op: "insert" });
|
|
524
|
-
const newId = this.tree.insertChild(parent.id, keyOrIndex, cloned, type);
|
|
525
|
-
const child = this.tree.get(newId);
|
|
526
|
-
if (opts.tags !== void 0) child.tags = [...opts.tags];
|
|
527
|
-
this.bump(parent, opts.owner);
|
|
528
|
-
this.deps.onChange?.(child);
|
|
529
|
-
if (this.deps.onChange) {
|
|
530
|
-
for (const id of this.tree.descendantIds(newId)) {
|
|
531
|
-
const desc = this.tree.get(id);
|
|
532
|
-
if (desc) this.deps.onChange(desc);
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
this.log.append({
|
|
536
|
-
kind: "insert",
|
|
537
|
-
targetId: newId,
|
|
538
|
-
parentId: parent.id,
|
|
539
|
-
key: child.key,
|
|
540
|
-
path: this.addressing.pathOf(newId),
|
|
541
|
-
after: cloned,
|
|
542
|
-
nodeType: child.type ?? null,
|
|
543
|
-
tags: [...child.tags ?? []],
|
|
544
|
-
actor: opts.owner,
|
|
545
|
-
ts: this.deps.clock.now()
|
|
546
|
-
});
|
|
547
|
-
return newId;
|
|
548
|
-
}
|
|
549
|
-
remove(ref, opts = {}) {
|
|
550
|
-
const node = this.resolve(ref);
|
|
551
|
-
if (node.parentId === null) throw new InvalidOpError("cannot remove the root");
|
|
552
|
-
this.checkScope(node, opts.writeScope);
|
|
553
|
-
this.checkVersion(node, opts.ifVersion);
|
|
554
|
-
const before = this.tree.toJson(node.id);
|
|
555
|
-
const path = this.addressing.pathOf(node.id);
|
|
556
|
-
const removedIds = [node.id, ...this.tree.descendantIds(node.id)];
|
|
557
|
-
const parent = this.tree.get(node.parentId);
|
|
558
|
-
const removedKey = node.key;
|
|
559
|
-
this.tree.removeChild(node.parentId, node.id);
|
|
560
|
-
this.bump(parent, opts.owner);
|
|
561
|
-
if (this.deps.onRemove) {
|
|
562
|
-
for (const id of removedIds) this.deps.onRemove(id);
|
|
563
|
-
}
|
|
564
|
-
this.deps.onChange?.(parent);
|
|
565
|
-
this.log.append({
|
|
566
|
-
kind: "remove",
|
|
567
|
-
targetId: node.id,
|
|
568
|
-
parentId: parent.id,
|
|
569
|
-
key: removedKey,
|
|
570
|
-
path,
|
|
571
|
-
before,
|
|
572
|
-
nodeTypeBefore: node.type ?? null,
|
|
573
|
-
tagsBefore: [...node.tags ?? []],
|
|
574
|
-
actor: opts.owner,
|
|
575
|
-
ts: this.deps.clock.now()
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
move(ref, toParentRef, keyOrIndex, opts = {}) {
|
|
579
|
-
const node = this.resolve(ref);
|
|
580
|
-
if (node.parentId === null) throw new InvalidOpError("cannot move the root");
|
|
581
|
-
const toParent = this.resolve(toParentRef);
|
|
582
|
-
this.checkScope(node, opts.writeScope);
|
|
583
|
-
this.checkScope(toParent, opts.writeScope);
|
|
584
|
-
this.checkVersion(node, opts.ifVersion);
|
|
585
|
-
const oldParentId = node.parentId;
|
|
586
|
-
const from = { parentId: node.parentId, key: node.key };
|
|
587
|
-
const fromPath = this.addressing.pathOf(node.id);
|
|
588
|
-
this.tree.moveNode(node.id, toParent.id, keyOrIndex);
|
|
589
|
-
const toPath = this.addressing.pathOf(node.id);
|
|
590
|
-
const bumped = /* @__PURE__ */ new Set();
|
|
591
|
-
for (const id of [node.id, oldParentId, toParent.id]) {
|
|
592
|
-
if (id !== null && !bumped.has(id)) {
|
|
593
|
-
const n = this.tree.get(id);
|
|
594
|
-
if (n) this.bump(n, opts.owner);
|
|
595
|
-
bumped.add(id);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
if (this.deps.onChange) {
|
|
599
|
-
this.deps.onChange(node);
|
|
600
|
-
for (const id of this.tree.descendantIds(node.id)) {
|
|
601
|
-
const d = this.tree.get(id);
|
|
602
|
-
if (d) this.deps.onChange(d);
|
|
603
|
-
}
|
|
604
|
-
const oldP = oldParentId !== null ? this.tree.get(oldParentId) : void 0;
|
|
605
|
-
if (oldP) this.deps.onChange(oldP);
|
|
606
|
-
const newP = this.tree.get(toParent.id);
|
|
607
|
-
if (newP) this.deps.onChange(newP);
|
|
608
|
-
}
|
|
609
|
-
this.log.append({
|
|
610
|
-
kind: "move",
|
|
611
|
-
targetId: node.id,
|
|
612
|
-
parentId: toParent.id,
|
|
613
|
-
key: node.key,
|
|
614
|
-
from,
|
|
615
|
-
to: { parentId: toParent.id, key: node.key },
|
|
616
|
-
fromPath,
|
|
617
|
-
toPath,
|
|
618
|
-
actor: opts.owner,
|
|
619
|
-
ts: this.deps.clock.now()
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
/** Run `fn` atomically: if it throws, the tree, log, and any hooked index state are restored. */
|
|
623
|
-
transaction(fn) {
|
|
624
|
-
const snap = this.tree.snapshot();
|
|
625
|
-
const logLen = this.log.length();
|
|
626
|
-
const hookSnap = this.deps.onTxSnapshot?.();
|
|
627
|
-
try {
|
|
628
|
-
fn();
|
|
629
|
-
} catch (err) {
|
|
630
|
-
this.tree.restore(snap);
|
|
631
|
-
this.log.truncateTo(logLen);
|
|
632
|
-
if (this.deps.onTxRestore) this.deps.onTxRestore(hookSnap);
|
|
633
|
-
throw err;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
};
|
|
637
|
-
|
|
638
|
-
// src/json-edit.ts
|
|
639
|
-
function getAtPath(value, pointer) {
|
|
640
|
-
const segs = parsePointer(pointer);
|
|
641
|
-
let cur = value;
|
|
642
|
-
for (const seg of segs) {
|
|
643
|
-
if (Array.isArray(cur)) {
|
|
644
|
-
const i = Number(seg);
|
|
645
|
-
cur = Number.isInteger(i) && i >= 0 && i < cur.length ? cur[i] : void 0;
|
|
646
|
-
} else if (cur !== null && typeof cur === "object") {
|
|
647
|
-
cur = seg in cur ? cur[seg] : void 0;
|
|
648
|
-
} else {
|
|
649
|
-
return void 0;
|
|
650
|
-
}
|
|
651
|
-
if (cur === void 0) return void 0;
|
|
652
|
-
}
|
|
653
|
-
return cur;
|
|
654
|
-
}
|
|
655
|
-
function navParent(root, pointer) {
|
|
656
|
-
const segs = parsePointer(pointer);
|
|
657
|
-
if (segs.length === 0) return void 0;
|
|
658
|
-
let cur = root;
|
|
659
|
-
for (let i = 0; i < segs.length - 1; i++) {
|
|
660
|
-
const seg = segs[i];
|
|
661
|
-
if (Array.isArray(cur)) cur = cur[Number(seg)];
|
|
662
|
-
else if (cur !== null && typeof cur === "object") cur = cur[seg];
|
|
663
|
-
else return void 0;
|
|
664
|
-
if (cur === void 0 || cur === null) return void 0;
|
|
665
|
-
}
|
|
666
|
-
return { parent: cur, key: segs[segs.length - 1] };
|
|
667
|
-
}
|
|
668
|
-
function setAtPathMut(value, pointer, newVal) {
|
|
669
|
-
if (pointer === "") return newVal;
|
|
670
|
-
const pk = navParent(value, pointer);
|
|
671
|
-
if (!pk || pk.parent === null || typeof pk.parent !== "object") return value;
|
|
672
|
-
if (Array.isArray(pk.parent)) pk.parent[Number(pk.key)] = newVal;
|
|
673
|
-
else pk.parent[pk.key] = newVal;
|
|
674
|
-
return value;
|
|
675
|
-
}
|
|
676
|
-
function removeAtPathMut(value, pointer) {
|
|
677
|
-
if (pointer === "") return null;
|
|
678
|
-
const pk = navParent(value, pointer);
|
|
679
|
-
if (!pk || pk.parent === null || typeof pk.parent !== "object") return value;
|
|
680
|
-
if (Array.isArray(pk.parent)) pk.parent.splice(Number(pk.key), 1);
|
|
681
|
-
else delete pk.parent[pk.key];
|
|
682
|
-
return value;
|
|
683
|
-
}
|
|
684
|
-
function insertAtPathMut(value, pointer, val) {
|
|
685
|
-
if (pointer === "") return val;
|
|
686
|
-
const pk = navParent(value, pointer);
|
|
687
|
-
if (!pk || pk.parent === null || typeof pk.parent !== "object") return value;
|
|
688
|
-
if (Array.isArray(pk.parent)) pk.parent.splice(Number(pk.key), 0, val);
|
|
689
|
-
else pk.parent[pk.key] = val;
|
|
690
|
-
return value;
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// src/replay.ts
|
|
694
|
-
function reverseApplyValue(value, e) {
|
|
695
|
-
switch (e.kind) {
|
|
696
|
-
case "set":
|
|
697
|
-
return e.path === void 0 ? value : setAtPathMut(value, e.path, structuredClone(e.before ?? null));
|
|
698
|
-
case "insert":
|
|
699
|
-
return e.path === void 0 ? value : removeAtPathMut(value, e.path);
|
|
700
|
-
case "remove":
|
|
701
|
-
return e.path === void 0 ? value : insertAtPathMut(value, e.path, structuredClone(e.before ?? null));
|
|
702
|
-
case "move": {
|
|
703
|
-
if (e.toPath === void 0 || e.fromPath === void 0) return value;
|
|
704
|
-
const moved = getAtPath(value, e.toPath) ?? null;
|
|
705
|
-
const withoutMoved = removeAtPathMut(value, e.toPath);
|
|
706
|
-
return insertAtPathMut(withoutMoved, e.fromPath, moved);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
var Replay = class {
|
|
711
|
-
constructor(tree, log) {
|
|
712
|
-
this.tree = tree;
|
|
713
|
-
this.log = log;
|
|
714
|
-
}
|
|
715
|
-
tree;
|
|
716
|
-
log;
|
|
717
|
-
/** The whole artifact's JSON value as of `version` (0 = initial, log.length = current).
|
|
718
|
-
* Throws if `version` is below the compaction floor (that history was dropped). */
|
|
719
|
-
reconstructValueAt(version) {
|
|
720
|
-
const total = this.log.length();
|
|
721
|
-
const floor = this.log.baseSeqValue();
|
|
722
|
-
if (version < floor) {
|
|
723
|
-
throw new InvalidOpError(`cannot reconstruct version ${version}: history before ${floor} was compacted`);
|
|
724
|
-
}
|
|
725
|
-
const target = Math.min(version, total);
|
|
726
|
-
let value = structuredClone(this.tree.toJson());
|
|
727
|
-
for (let seq = total - 1; seq >= target; seq--) {
|
|
728
|
-
value = reverseApplyValue(value, this.log.at(seq));
|
|
729
|
-
}
|
|
730
|
-
return value;
|
|
731
|
-
}
|
|
732
|
-
/** The value at JSON Pointer `path` as of `version`, or undefined if absent then. */
|
|
733
|
-
getAt(path, version) {
|
|
734
|
-
return getAtPath(this.reconstructValueAt(version), path);
|
|
735
|
-
}
|
|
736
|
-
/** The mutations applied between version `vA` (inclusive) and `vB` (exclusive).
|
|
737
|
-
* Events compacted away are not included. */
|
|
738
|
-
diff(vA, vB) {
|
|
739
|
-
return this.log.since(vA).filter((e) => e.seq < vB);
|
|
740
|
-
}
|
|
741
|
-
/** The node's {type, tags} as of `version`, by scanning later events on its path.
|
|
742
|
-
* Move-aware: if the version-`version` occupant was moved away, follow it to its
|
|
743
|
-
* new path and keep scanning — its type/tags travel with it. If the scan exhausts
|
|
744
|
-
* on a FOLLOWED path, the occupant is the live node there — read it directly
|
|
745
|
-
* ("keep current" would read whatever now sits at the original path).
|
|
746
|
-
* type: string | null (untyped/absent) | undefined (unknown — keep current).
|
|
747
|
-
* tags: string[] ([] = untagged) | undefined (unknown/pre-M14 — keep current).
|
|
748
|
-
* Limitation (same as pre-M14): array-index paths can mis-resolve across sibling
|
|
749
|
-
* index shifts; exact for object paths. */
|
|
750
|
-
stateAt(path, version, addressing) {
|
|
751
|
-
const total = this.log.length();
|
|
752
|
-
let p = path;
|
|
753
|
-
for (let seq = Math.max(version, this.log.baseSeqValue()); seq < total; seq++) {
|
|
754
|
-
const e = this.log.at(seq);
|
|
755
|
-
if (e.kind === "move") {
|
|
756
|
-
if (e.fromPath === p) {
|
|
757
|
-
p = e.toPath ?? p;
|
|
758
|
-
continue;
|
|
759
|
-
}
|
|
760
|
-
if (e.toPath === p) return { type: null, tags: [] };
|
|
761
|
-
continue;
|
|
762
|
-
}
|
|
763
|
-
if (e.path !== p) continue;
|
|
764
|
-
if (e.kind === "set" || e.kind === "remove") {
|
|
765
|
-
return {
|
|
766
|
-
type: e.nodeTypeBefore === void 0 ? void 0 : e.nodeTypeBefore,
|
|
767
|
-
tags: e.tagsBefore
|
|
768
|
-
// absent (pre-M14) → undefined = keep current
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
if (e.kind === "insert") return { type: null, tags: [] };
|
|
772
|
-
}
|
|
773
|
-
if (p !== path) {
|
|
774
|
-
const live = addressing.byPath(p);
|
|
775
|
-
return { type: live?.type ?? null, tags: live?.tags ?? [] };
|
|
776
|
-
}
|
|
777
|
-
return { type: void 0, tags: void 0 };
|
|
778
|
-
}
|
|
779
|
-
/** Restore the node at `ref` to its value, type, AND tags as of `toVersion`, as a new live mutation. */
|
|
780
|
-
revert(mutator, addressing, ref, toVersion) {
|
|
781
|
-
const path = "id" in ref ? addressing.pathOf(ref.id) : ref.path;
|
|
782
|
-
const past = this.getAt(path, toVersion);
|
|
783
|
-
const { type, tags } = this.stateAt(path, toVersion, addressing);
|
|
784
|
-
const opts = {};
|
|
785
|
-
if (type !== void 0) opts.type = type;
|
|
786
|
-
if (tags !== void 0) opts.tags = tags;
|
|
787
|
-
mutator.set({ path }, past ?? null, opts);
|
|
788
|
-
}
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
// src/embedding-text.ts
|
|
792
|
-
function toEmbeddingText(node, value, typeDef) {
|
|
793
|
-
if (typeDef?.embedText) return typeDef.embedText(value);
|
|
794
|
-
if (node.kind !== "leaf") return null;
|
|
795
|
-
if (typeof value === "string") return value;
|
|
796
|
-
if (value !== null && typeof value === "object") return JSON.stringify(value);
|
|
797
|
-
return null;
|
|
798
|
-
}
|
|
799
|
-
function textHash(text) {
|
|
800
|
-
let h = 2166136261 >>> 0;
|
|
801
|
-
for (let i = 0; i < text.length; i++) {
|
|
802
|
-
h ^= text.charCodeAt(i);
|
|
803
|
-
h = Math.imul(h, 16777619) >>> 0;
|
|
804
|
-
}
|
|
805
|
-
return h.toString(16);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
// src/semantic-index.ts
|
|
809
|
-
var SemanticIndex = class {
|
|
810
|
-
constructor(tree, addressing, embedding, vectors, registry) {
|
|
811
|
-
this.tree = tree;
|
|
812
|
-
this.addressing = addressing;
|
|
813
|
-
this.embedding = embedding;
|
|
814
|
-
this.vectors = vectors;
|
|
815
|
-
this.registry = registry;
|
|
816
|
-
for (const node of tree.allNodes()) {
|
|
817
|
-
if (node.meta.embedding.state === "stale") this.stale.add(node.id);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
tree;
|
|
821
|
-
addressing;
|
|
822
|
-
embedding;
|
|
823
|
-
vectors;
|
|
824
|
-
registry;
|
|
825
|
-
stale = /* @__PURE__ */ new Set();
|
|
826
|
-
pendingRemoval = /* @__PURE__ */ new Set();
|
|
827
|
-
/** The nearest ancestor whose type declares embedText — the semantic unit that
|
|
828
|
-
* owns this shard — or undefined. (O(depth) walk per call — fine at v1 sizes.) */
|
|
829
|
-
embedTextAncestor(node) {
|
|
830
|
-
let pid = node.parentId;
|
|
831
|
-
while (pid !== null) {
|
|
832
|
-
const anc = this.tree.get(pid);
|
|
833
|
-
const ancDef = anc?.type ? this.registry?.get(anc.type) : void 0;
|
|
834
|
-
if (ancDef?.embedText) return anc;
|
|
835
|
-
pid = anc?.parentId ?? null;
|
|
836
|
-
}
|
|
837
|
-
return void 0;
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
|
-
* A node is a suppressed decomposition shard iff some ANCESTOR has a typed
|
|
841
|
-
* embedText AND the node does not itself declare a typed embedText.
|
|
842
|
-
*/
|
|
843
|
-
isSuppressedShard(node) {
|
|
844
|
-
const ownTypeDef = node.type ? this.registry?.get(node.type) : void 0;
|
|
845
|
-
if (ownTypeDef?.embedText) return false;
|
|
846
|
-
return this.embedTextAncestor(node) !== void 0;
|
|
847
|
-
}
|
|
848
|
-
/** Mutation hook: a node's content changed (set/insert). Marks it stale if its embedding-text changed. */
|
|
849
|
-
onChange(node) {
|
|
850
|
-
if (this.isSuppressedShard(node)) {
|
|
851
|
-
node.meta.embedding = { state: "none" };
|
|
852
|
-
this.pendingRemoval.add(node.id);
|
|
853
|
-
this.stale.delete(node.id);
|
|
854
|
-
const anc = this.embedTextAncestor(node);
|
|
855
|
-
if (anc) this.onChange(anc);
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
const value = this.tree.toJson(node.id);
|
|
859
|
-
const typeDef = node.type ? this.registry?.get(node.type) : void 0;
|
|
860
|
-
const text = toEmbeddingText(node, value, typeDef);
|
|
861
|
-
if (text === null) {
|
|
862
|
-
node.meta.embedding = { state: "none" };
|
|
863
|
-
this.pendingRemoval.add(node.id);
|
|
864
|
-
this.stale.delete(node.id);
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
const hash = textHash(text);
|
|
868
|
-
if (node.meta.embedding.state === "fresh" && node.meta.embedding.textHash === hash) {
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
node.meta.embedding = { state: "stale", textHash: hash };
|
|
872
|
-
this.pendingRemoval.delete(node.id);
|
|
873
|
-
this.stale.add(node.id);
|
|
874
|
-
}
|
|
875
|
-
/** Mutation hook: a node was removed. Drops it from the index and the stale queue. */
|
|
876
|
-
onRemove(nodeId) {
|
|
877
|
-
this.pendingRemoval.add(nodeId);
|
|
878
|
-
this.stale.delete(nodeId);
|
|
879
|
-
}
|
|
880
|
-
/**
|
|
881
|
-
* Snapshot of both queues for transaction rollback.
|
|
882
|
-
*
|
|
883
|
-
* The sync hooks (`onChange`, `onRemove`) only move ids between the `stale`
|
|
884
|
-
* queue and the `pendingRemoval` queue; all actual vector mutations happen in
|
|
885
|
-
* the async `reindex()`. This snapshot covers both queues so that a rollback
|
|
886
|
-
* fully restores the observable state of the index without touching vectors.
|
|
887
|
-
*/
|
|
888
|
-
txSnapshot() {
|
|
889
|
-
return {
|
|
890
|
-
stale: new Set(this.stale),
|
|
891
|
-
pendingRemoval: new Set(this.pendingRemoval)
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
/** Restore both queues captured by `txSnapshot`. */
|
|
895
|
-
txRestore(snapshot) {
|
|
896
|
-
const snap = snapshot;
|
|
897
|
-
this.stale.clear();
|
|
898
|
-
for (const id of snap.stale) this.stale.add(id);
|
|
899
|
-
this.pendingRemoval.clear();
|
|
900
|
-
for (const id of snap.pendingRemoval) this.pendingRemoval.add(id);
|
|
901
|
-
}
|
|
902
|
-
/** Convenience: the hooks to wire into `MutatorDeps`. */
|
|
903
|
-
hooks() {
|
|
904
|
-
return {
|
|
905
|
-
onChange: (node) => this.onChange(node),
|
|
906
|
-
onRemove: (nodeId) => this.onRemove(nodeId),
|
|
907
|
-
onTxSnapshot: () => this.txSnapshot(),
|
|
908
|
-
onTxRestore: (snapshot) => this.txRestore(snapshot)
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
staleCount() {
|
|
912
|
-
return this.stale.size;
|
|
913
|
-
}
|
|
914
|
-
/** Embed every stale node (one batch), upsert vectors, mark fresh, clear the processed ids.
|
|
915
|
-
* Interleave-safe: mutations landing during the embed await are respected — a node
|
|
916
|
-
* removed mid-flight is dropped (never resurrected), and a node whose text changed
|
|
917
|
-
* stays queued for the next pass instead of being marked fresh for the old text. */
|
|
918
|
-
async reindex() {
|
|
919
|
-
for (const id of this.pendingRemoval) await this.vectors.remove(id);
|
|
920
|
-
this.pendingRemoval.clear();
|
|
921
|
-
const ids = [...this.stale];
|
|
922
|
-
if (ids.length === 0) return;
|
|
923
|
-
const completed = /* @__PURE__ */ new Set();
|
|
924
|
-
const items = [];
|
|
925
|
-
for (const id of ids) {
|
|
926
|
-
const node = this.tree.get(id);
|
|
927
|
-
if (!node) {
|
|
928
|
-
completed.add(id);
|
|
929
|
-
continue;
|
|
930
|
-
}
|
|
931
|
-
if (this.isSuppressedShard(node)) {
|
|
932
|
-
node.meta.embedding = { state: "none" };
|
|
933
|
-
await this.vectors.remove(id);
|
|
934
|
-
completed.add(id);
|
|
935
|
-
continue;
|
|
936
|
-
}
|
|
937
|
-
const value = this.tree.toJson(id);
|
|
938
|
-
const typeDef = node.type ? this.registry?.get(node.type) : void 0;
|
|
939
|
-
const text = toEmbeddingText(node, value, typeDef);
|
|
940
|
-
if (text === null) {
|
|
941
|
-
node.meta.embedding = { state: "none" };
|
|
942
|
-
await this.vectors.remove(id);
|
|
943
|
-
completed.add(id);
|
|
944
|
-
continue;
|
|
945
|
-
}
|
|
946
|
-
items.push({ id, text, hash: textHash(text) });
|
|
947
|
-
}
|
|
948
|
-
if (items.length > 0) {
|
|
949
|
-
const embedded = await this.embedding.embed(items.map((it) => it.text));
|
|
950
|
-
const upserts = [];
|
|
951
|
-
for (const [i, it] of items.entries()) {
|
|
952
|
-
const node = this.tree.get(it.id);
|
|
953
|
-
if (!node) {
|
|
954
|
-
await this.vectors.remove(it.id);
|
|
955
|
-
completed.add(it.id);
|
|
956
|
-
continue;
|
|
957
|
-
}
|
|
958
|
-
const cur = node.meta.embedding;
|
|
959
|
-
if (cur.state !== "stale" || cur.textHash !== void 0 && cur.textHash !== it.hash) {
|
|
960
|
-
if (node.meta.embedding.state === "none") completed.add(it.id);
|
|
961
|
-
continue;
|
|
962
|
-
}
|
|
963
|
-
upserts.push({ nodeId: it.id, vector: embedded[i] });
|
|
964
|
-
node.meta.embedding = { state: "fresh", textHash: it.hash };
|
|
965
|
-
completed.add(it.id);
|
|
966
|
-
}
|
|
967
|
-
if (upserts.length > 0) await this.vectors.upsert(upserts);
|
|
968
|
-
}
|
|
969
|
-
for (const id of ids) {
|
|
970
|
-
if (completed.has(id)) this.stale.delete(id);
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
snippetOf(value) {
|
|
974
|
-
const s = JSON.stringify(value);
|
|
975
|
-
return s.length <= 80 ? s : s.slice(0, 80) + "\u2026";
|
|
976
|
-
}
|
|
977
|
-
/**
|
|
978
|
-
* Semantic search: embed the query, rank indexed nodes by cosine, post-filter by
|
|
979
|
-
* under/type/tag, return top-k. `freshness: "wait"` flushes the reindexer first;
|
|
980
|
-
* default `best-effort` searches what's indexed and reports `staleCount`.
|
|
981
|
-
*/
|
|
982
|
-
async search(queryText, opts = {}) {
|
|
983
|
-
if (opts.freshness === "wait") await this.reindex();
|
|
984
|
-
const k = opts.k ?? 8;
|
|
985
|
-
const [queryVec] = await this.embedding.embed([queryText]);
|
|
986
|
-
const ranked = await this.vectors.search(queryVec, await this.vectors.size());
|
|
987
|
-
const results = [];
|
|
988
|
-
for (const hit of ranked) {
|
|
989
|
-
if (results.length >= k) break;
|
|
990
|
-
if (this.pendingRemoval.has(hit.nodeId)) continue;
|
|
991
|
-
const node = this.tree.get(hit.nodeId);
|
|
992
|
-
if (!node) continue;
|
|
993
|
-
const path = this.addressing.pathOf(node.id);
|
|
994
|
-
if (opts.under !== void 0 && !isWithin(path, opts.under)) continue;
|
|
995
|
-
if (opts.type !== void 0 && node.type !== opts.type) continue;
|
|
996
|
-
if (opts.tag !== void 0 && !(node.tags?.includes(opts.tag) ?? false)) continue;
|
|
997
|
-
results.push({
|
|
998
|
-
id: node.id,
|
|
999
|
-
path,
|
|
1000
|
-
type: node.type,
|
|
1001
|
-
score: hit.score,
|
|
1002
|
-
snippet: this.snippetOf(this.tree.toJson(node.id))
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
|
-
return { results, staleCount: this.staleCount() };
|
|
1006
|
-
}
|
|
1007
|
-
};
|
|
1008
|
-
|
|
1009
|
-
// src/path-glob.ts
|
|
1010
|
-
function matchSegments(pattern, path) {
|
|
1011
|
-
if (pattern.length === 0) return path.length === 0;
|
|
1012
|
-
const [head, ...rest] = pattern;
|
|
1013
|
-
if (head === "**") {
|
|
1014
|
-
for (let i = 0; i <= path.length; i++) {
|
|
1015
|
-
if (matchSegments(rest, path.slice(i))) return true;
|
|
1016
|
-
}
|
|
1017
|
-
return false;
|
|
1018
|
-
}
|
|
1019
|
-
if (path.length === 0) return false;
|
|
1020
|
-
if (head === "*" || head === path[0]) {
|
|
1021
|
-
return matchSegments(rest, path.slice(1));
|
|
1022
|
-
}
|
|
1023
|
-
return false;
|
|
1024
|
-
}
|
|
1025
|
-
function compileGlob(pattern) {
|
|
1026
|
-
const patternSegs = parsePointer(pattern);
|
|
1027
|
-
return (path) => matchSegments(patternSegs, parsePointer(path));
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
// src/navigator.ts
|
|
1031
|
-
var DEFAULT_LIMIT = 100;
|
|
1032
|
-
var PREVIEW_MAX = 50;
|
|
1033
|
-
function previewOf(node) {
|
|
1034
|
-
if (node.kind === "leaf") {
|
|
1035
|
-
const s = JSON.stringify(node.content);
|
|
1036
|
-
return s.length <= PREVIEW_MAX ? s : s.slice(0, PREVIEW_MAX) + "\u2026";
|
|
1037
|
-
}
|
|
1038
|
-
return node.kind === "array" ? `[${node.childIds.length} items]` : `{${node.childIds.length} keys}`;
|
|
1039
|
-
}
|
|
1040
|
-
var Navigator = class {
|
|
1041
|
-
constructor(tree, addressing) {
|
|
1042
|
-
this.tree = tree;
|
|
1043
|
-
this.addressing = addressing;
|
|
1044
|
-
}
|
|
1045
|
-
tree;
|
|
1046
|
-
addressing;
|
|
1047
|
-
resolve(ref) {
|
|
1048
|
-
const node = "id" in ref ? this.addressing.byId(ref.id) : this.addressing.byPath(ref.path);
|
|
1049
|
-
if (!node) throw new NodeNotFoundError(ref);
|
|
1050
|
-
return node;
|
|
1051
|
-
}
|
|
1052
|
-
summarize(node) {
|
|
1053
|
-
return {
|
|
1054
|
-
id: node.id,
|
|
1055
|
-
key: node.key,
|
|
1056
|
-
kind: node.kind,
|
|
1057
|
-
type: node.type,
|
|
1058
|
-
hasChildren: node.childIds.length > 0,
|
|
1059
|
-
size: node.kind === "leaf" ? byteSize(node.content) : node.childIds.length,
|
|
1060
|
-
preview: previewOf(node)
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
describe(ref = { path: "" }, opts = {}) {
|
|
1064
|
-
const node = this.resolve(ref);
|
|
1065
|
-
const all = this.tree.children(node.id);
|
|
1066
|
-
const total = all.length;
|
|
1067
|
-
const offset = opts.offset ?? 0;
|
|
1068
|
-
const limit = opts.limit ?? DEFAULT_LIMIT;
|
|
1069
|
-
const page = all.slice(offset, offset + limit);
|
|
1070
|
-
const result = {
|
|
1071
|
-
node: {
|
|
1072
|
-
id: node.id,
|
|
1073
|
-
path: this.addressing.pathOf(node.id),
|
|
1074
|
-
key: node.key,
|
|
1075
|
-
kind: node.kind,
|
|
1076
|
-
type: node.type
|
|
1077
|
-
},
|
|
1078
|
-
children: page.map((c) => this.summarize(c))
|
|
1079
|
-
};
|
|
1080
|
-
const nextOffset = offset + page.length;
|
|
1081
|
-
if (nextOffset < total) {
|
|
1082
|
-
result.truncated = { shown: page.length, total, nextOffset };
|
|
1083
|
-
}
|
|
1084
|
-
return result;
|
|
1085
|
-
}
|
|
1086
|
-
get(ref, opts = {}) {
|
|
1087
|
-
const node = this.resolve(ref);
|
|
1088
|
-
const maxDepth = opts.maxDepth;
|
|
1089
|
-
let truncated = false;
|
|
1090
|
-
const reconstruct = (id, depth) => {
|
|
1091
|
-
const n = this.tree.get(id);
|
|
1092
|
-
if (n.kind === "leaf") return n.content;
|
|
1093
|
-
if (maxDepth !== void 0 && depth >= maxDepth) {
|
|
1094
|
-
truncated = true;
|
|
1095
|
-
const label = n.kind === "array" ? `${n.childIds.length} items` : `${n.childIds.length} keys`;
|
|
1096
|
-
return `[truncated: ${label}]`;
|
|
1097
|
-
}
|
|
1098
|
-
if (n.kind === "array") return n.childIds.map((cid) => reconstruct(cid, depth + 1));
|
|
1099
|
-
const obj = {};
|
|
1100
|
-
for (const cid of n.childIds) {
|
|
1101
|
-
const c = this.tree.get(cid);
|
|
1102
|
-
obj[String(c.key)] = reconstruct(cid, depth + 1);
|
|
1103
|
-
}
|
|
1104
|
-
return obj;
|
|
1105
|
-
};
|
|
1106
|
-
const content = reconstruct(node.id, 0);
|
|
1107
|
-
const result = {
|
|
1108
|
-
id: node.id,
|
|
1109
|
-
path: this.addressing.pathOf(node.id),
|
|
1110
|
-
type: node.type,
|
|
1111
|
-
content,
|
|
1112
|
-
meta: node.meta
|
|
1113
|
-
};
|
|
1114
|
-
if (truncated) result.truncated = true;
|
|
1115
|
-
return result;
|
|
1116
|
-
}
|
|
1117
|
-
matches(node, sel, path, glob) {
|
|
1118
|
-
if (sel.type !== void 0 && node.type !== sel.type) return false;
|
|
1119
|
-
if (sel.tag !== void 0 && !(node.tags?.includes(sel.tag) ?? false)) return false;
|
|
1120
|
-
if (glob !== void 0 && !glob(path)) return false;
|
|
1121
|
-
return true;
|
|
1122
|
-
}
|
|
1123
|
-
/** Find nodes matching ALL provided selector fields (exact `type`, `tag` membership, glob `pathPattern`). */
|
|
1124
|
-
find(selector, opts = {}) {
|
|
1125
|
-
const limit = opts.limit ?? DEFAULT_LIMIT;
|
|
1126
|
-
const within = opts.within;
|
|
1127
|
-
const glob = selector.pathPattern !== void 0 ? compileGlob(selector.pathPattern) : void 0;
|
|
1128
|
-
const hits = [];
|
|
1129
|
-
let truncated = false;
|
|
1130
|
-
const visit = (id, path) => {
|
|
1131
|
-
if (hits.length >= limit) {
|
|
1132
|
-
truncated = true;
|
|
1133
|
-
return;
|
|
1134
|
-
}
|
|
1135
|
-
const node = this.tree.get(id);
|
|
1136
|
-
if (this.matches(node, selector, path, glob)) {
|
|
1137
|
-
if (isWithin(path, within)) {
|
|
1138
|
-
hits.push({ id: node.id, path, type: node.type });
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
for (const cid of node.childIds) {
|
|
1142
|
-
if (hits.length >= limit) {
|
|
1143
|
-
truncated = true;
|
|
1144
|
-
break;
|
|
1145
|
-
}
|
|
1146
|
-
const child = this.tree.get(cid);
|
|
1147
|
-
visit(cid, appendPointer(path, child.key));
|
|
1148
|
-
}
|
|
1149
|
-
};
|
|
1150
|
-
visit(this.tree.rootIdValue(), "");
|
|
1151
|
-
return { hits, truncated };
|
|
1152
|
-
}
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
// src/toolset.ts
|
|
1156
|
-
function eventWithinScope(e, scope) {
|
|
1157
|
-
if (e.path !== void 0 && isWithin(e.path, scope)) return true;
|
|
1158
|
-
if (e.toPath !== void 0 && isWithin(e.toPath, scope)) return true;
|
|
1159
|
-
if (e.fromPath !== void 0 && isWithin(e.fromPath, scope)) return true;
|
|
1160
|
-
return false;
|
|
1161
|
-
}
|
|
1162
|
-
async function run(fn) {
|
|
1163
|
-
try {
|
|
1164
|
-
return { ok: true, value: await fn() };
|
|
1165
|
-
} catch (e) {
|
|
1166
|
-
if (e instanceof ArborError) return { ok: false, error: { code: e.code, message: e.message } };
|
|
1167
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1168
|
-
return { ok: false, error: { code: "ERROR", message } };
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
function makeToolset(deps, binding = {}) {
|
|
1172
|
-
const { tree, addressing } = deps;
|
|
1173
|
-
const navigator = new Navigator(tree, addressing);
|
|
1174
|
-
return {
|
|
1175
|
-
describe: (ref, opts) => run(() => {
|
|
1176
|
-
const target = ref ?? (binding.readScope !== void 0 ? { path: binding.readScope } : { path: "" });
|
|
1177
|
-
const r = navigator.describe(target, opts);
|
|
1178
|
-
if (!isWithin(r.node.path, binding.readScope)) {
|
|
1179
|
-
throw new ScopeViolationError(r.node.path, binding.readScope);
|
|
1180
|
-
}
|
|
1181
|
-
return r;
|
|
1182
|
-
}),
|
|
1183
|
-
get: (ref, opts) => run(() => {
|
|
1184
|
-
const r = navigator.get(ref, opts);
|
|
1185
|
-
if (!isWithin(r.path, binding.readScope)) {
|
|
1186
|
-
throw new ScopeViolationError(r.path, binding.readScope);
|
|
1187
|
-
}
|
|
1188
|
-
return structuredClone(r);
|
|
1189
|
-
}),
|
|
1190
|
-
find: (selector, opts) => run(() => {
|
|
1191
|
-
if (binding.readScope !== void 0 && opts?.within !== void 0 && !isWithin(opts.within, binding.readScope)) {
|
|
1192
|
-
throw new ScopeViolationError(opts.within, binding.readScope);
|
|
1193
|
-
}
|
|
1194
|
-
return navigator.find(selector, { ...opts, within: opts?.within ?? binding.readScope });
|
|
1195
|
-
}),
|
|
1196
|
-
search: (query, opts = {}) => run(async () => {
|
|
1197
|
-
if (!deps.index) throw new InvalidOpError("no semantic index configured for this toolset");
|
|
1198
|
-
if (binding.readScope !== void 0 && opts.under !== void 0 && !isWithin(opts.under, binding.readScope)) {
|
|
1199
|
-
throw new ScopeViolationError(opts.under, binding.readScope);
|
|
1200
|
-
}
|
|
1201
|
-
const under = opts.under ?? binding.readScope;
|
|
1202
|
-
return deps.index.search(query, { ...opts, under });
|
|
1203
|
-
}),
|
|
1204
|
-
patch: (ref, op) => run(() => {
|
|
1205
|
-
const common = { owner: binding.owner, writeScope: binding.writeScope, ifVersion: op.ifVersion };
|
|
1206
|
-
const resolve = (r) => {
|
|
1207
|
-
const node = "id" in r ? addressing.byId(r.id) : addressing.byPath(r.path);
|
|
1208
|
-
if (!node) throw new NodeNotFoundError(r);
|
|
1209
|
-
return node;
|
|
1210
|
-
};
|
|
1211
|
-
switch (op.op) {
|
|
1212
|
-
case "set": {
|
|
1213
|
-
deps.mutator.set(ref, op.value, common);
|
|
1214
|
-
const node = resolve(ref);
|
|
1215
|
-
return { id: node.id, path: addressing.pathOf(node.id), version: node.meta.version };
|
|
1216
|
-
}
|
|
1217
|
-
case "insert": {
|
|
1218
|
-
const id = deps.mutator.insert(ref, op.key, op.value, common);
|
|
1219
|
-
const node = tree.get(id);
|
|
1220
|
-
return { id, path: addressing.pathOf(id), version: node.meta.version };
|
|
1221
|
-
}
|
|
1222
|
-
case "remove": {
|
|
1223
|
-
const node = resolve(ref);
|
|
1224
|
-
const removed = { id: node.id, path: addressing.pathOf(node.id) };
|
|
1225
|
-
const parentId = node.parentId;
|
|
1226
|
-
deps.mutator.remove(ref, common);
|
|
1227
|
-
const parent = parentId !== null ? tree.get(parentId) : void 0;
|
|
1228
|
-
return { id: removed.id, path: removed.path, version: parent?.meta.version ?? 0 };
|
|
1229
|
-
}
|
|
1230
|
-
case "move": {
|
|
1231
|
-
const node = resolve(ref);
|
|
1232
|
-
deps.mutator.move(ref, op.to, op.key, common);
|
|
1233
|
-
return { id: node.id, path: addressing.pathOf(node.id), version: node.meta.version };
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
}),
|
|
1237
|
-
history: (ref, opts = {}) => run(() => {
|
|
1238
|
-
let events = [...deps.log.entries()];
|
|
1239
|
-
if (ref !== void 0) {
|
|
1240
|
-
const node = "id" in ref ? addressing.byId(ref.id) : addressing.byPath(ref.path);
|
|
1241
|
-
if (!node) throw new NodeNotFoundError(ref);
|
|
1242
|
-
const path = addressing.pathOf(node.id);
|
|
1243
|
-
if (!isWithin(path, binding.readScope)) throw new ScopeViolationError(path, binding.readScope);
|
|
1244
|
-
const id = node.id;
|
|
1245
|
-
events = events.filter((e) => e.targetId === id);
|
|
1246
|
-
} else if (binding.readScope !== void 0) {
|
|
1247
|
-
const scope = binding.readScope;
|
|
1248
|
-
events = events.filter((e) => eventWithinScope(e, scope));
|
|
1249
|
-
}
|
|
1250
|
-
return structuredClone(opts.limit !== void 0 ? events.slice(-opts.limit) : events);
|
|
1251
|
-
})
|
|
1252
|
-
};
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
// src/registry-validator.ts
|
|
1256
|
-
function makeRegistryValidator(registry) {
|
|
1257
|
-
return ({ proposed, type }) => {
|
|
1258
|
-
if (type === void 0) return;
|
|
1259
|
-
registry.get(type)?.validate?.(proposed);
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
// src/type-aware-decision.ts
|
|
1264
|
-
function typeAwareDecision(base, registry) {
|
|
1265
|
-
return {
|
|
1266
|
-
isOpaque(value, type) {
|
|
1267
|
-
if (type !== void 0) {
|
|
1268
|
-
const override = registry.get(type)?.decompose;
|
|
1269
|
-
if (override === "opaque") return true;
|
|
1270
|
-
if (override === "children") return false;
|
|
1271
|
-
}
|
|
1272
|
-
return base.isOpaque(value, type);
|
|
1273
|
-
}
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
// src/ids.ts
|
|
1278
|
-
import { randomUUID } from "crypto";
|
|
1279
|
-
var UuidIdGen = class {
|
|
1280
|
-
next() {
|
|
1281
|
-
return randomUUID();
|
|
1282
|
-
}
|
|
1283
|
-
};
|
|
1284
|
-
|
|
1285
|
-
// src/clock.ts
|
|
1286
|
-
var SystemClock = class {
|
|
1287
|
-
now() {
|
|
1288
|
-
return Date.now();
|
|
1289
|
-
}
|
|
1290
|
-
};
|
|
1291
|
-
|
|
1292
|
-
// src/vector-index-port.ts
|
|
1293
|
-
function normalize(v) {
|
|
1294
|
-
const out = new Float32Array(v.length);
|
|
1295
|
-
let n = 0;
|
|
1296
|
-
for (let i = 0; i < v.length; i++) n += v[i] * v[i];
|
|
1297
|
-
if (n === 0) return out;
|
|
1298
|
-
const inv = 1 / Math.sqrt(n);
|
|
1299
|
-
for (let i = 0; i < v.length; i++) out[i] = v[i] * inv;
|
|
1300
|
-
return out;
|
|
1301
|
-
}
|
|
1302
|
-
function dot(a, b) {
|
|
1303
|
-
const len = Math.min(a.length, b.length);
|
|
1304
|
-
let d = 0;
|
|
1305
|
-
for (let i = 0; i < len; i++) d += a[i] * b[i];
|
|
1306
|
-
return d;
|
|
1307
|
-
}
|
|
1308
|
-
var MemoryVectorIndex = class {
|
|
1309
|
-
vectors = /* @__PURE__ */ new Map();
|
|
1310
|
-
async upsert(entries) {
|
|
1311
|
-
for (const e of entries) {
|
|
1312
|
-
this.vectors.set(e.nodeId, { raw: Float32Array.from(e.vector), unit: normalize(e.vector) });
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
async remove(nodeId) {
|
|
1316
|
-
this.vectors.delete(nodeId);
|
|
1317
|
-
}
|
|
1318
|
-
async has(nodeId) {
|
|
1319
|
-
return this.vectors.has(nodeId);
|
|
1320
|
-
}
|
|
1321
|
-
async size() {
|
|
1322
|
-
return this.vectors.size;
|
|
1323
|
-
}
|
|
1324
|
-
async search(query, k) {
|
|
1325
|
-
const q = normalize(query);
|
|
1326
|
-
const hits = [];
|
|
1327
|
-
for (const [nodeId, { unit }] of this.vectors) {
|
|
1328
|
-
hits.push({ nodeId, score: dot(q, unit) });
|
|
1329
|
-
}
|
|
1330
|
-
hits.sort((a, b) => b.score - a.score);
|
|
1331
|
-
return hits.slice(0, k);
|
|
1332
|
-
}
|
|
1333
|
-
async entries() {
|
|
1334
|
-
return [...this.vectors].map(([nodeId, { raw }]) => ({ nodeId, vector: Array.from(raw) }));
|
|
1335
|
-
}
|
|
1336
|
-
};
|
|
1337
|
-
|
|
1338
|
-
// src/storage.ts
|
|
1339
|
-
async function serializeArtifact(tree, log, vectors) {
|
|
1340
|
-
return {
|
|
1341
|
-
version: 2,
|
|
1342
|
-
rootId: tree.rootIdValue(),
|
|
1343
|
-
nodes: tree.allNodes(),
|
|
1344
|
-
events: [...log.entries()],
|
|
1345
|
-
baseSeq: log.baseSeqValue(),
|
|
1346
|
-
vectors: await vectors.entries()
|
|
1347
|
-
};
|
|
1348
|
-
}
|
|
1349
|
-
async function restoreArtifact(stored, deps, vectors) {
|
|
1350
|
-
const tree = ArtifactTree.fromStored(stored.nodes, stored.rootId, deps);
|
|
1351
|
-
const log = EventLog.fromStored(stored.events, stored.baseSeq ?? 0);
|
|
1352
|
-
await vectors.upsert(stored.vectors);
|
|
1353
|
-
return { tree, log };
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
// src/delta.ts
|
|
1357
|
-
function parentPointer(pointer) {
|
|
1358
|
-
const i = pointer.lastIndexOf("/");
|
|
1359
|
-
return i <= 0 ? "" : pointer.slice(0, i);
|
|
1360
|
-
}
|
|
1361
|
-
function applyEventForward(mutator, e) {
|
|
1362
|
-
switch (e.kind) {
|
|
1363
|
-
case "set": {
|
|
1364
|
-
if (e.path === void 0) return;
|
|
1365
|
-
const opts = {};
|
|
1366
|
-
if (e.nodeType !== void 0) opts.type = e.nodeType;
|
|
1367
|
-
if (e.tags !== void 0) opts.tags = e.tags;
|
|
1368
|
-
mutator.set({ path: e.path }, e.after ?? null, opts);
|
|
1369
|
-
return;
|
|
1370
|
-
}
|
|
1371
|
-
case "insert": {
|
|
1372
|
-
if (e.path === void 0 || e.key === null) return;
|
|
1373
|
-
const opts = {};
|
|
1374
|
-
if (e.nodeType !== void 0) opts.type = e.nodeType;
|
|
1375
|
-
if (e.tags !== void 0) opts.tags = e.tags;
|
|
1376
|
-
mutator.insert({ path: parentPointer(e.path) }, e.key, e.after ?? null, opts);
|
|
1377
|
-
return;
|
|
1378
|
-
}
|
|
1379
|
-
case "remove":
|
|
1380
|
-
if (e.path === void 0) return;
|
|
1381
|
-
mutator.remove({ path: e.path });
|
|
1382
|
-
return;
|
|
1383
|
-
case "move":
|
|
1384
|
-
if (e.fromPath === void 0 || e.toPath === void 0 || !e.to || e.to.key === null) return;
|
|
1385
|
-
mutator.move({ path: e.fromPath }, { path: parentPointer(e.toPath) }, e.to.key);
|
|
1386
|
-
return;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
function replayForward(mutator, events) {
|
|
1390
|
-
for (const e of events) applyEventForward(mutator, e);
|
|
1391
|
-
}
|
|
1392
|
-
async function persistDelta(store, log, sinceSeq) {
|
|
1393
|
-
if (sinceSeq < log.baseSeqValue()) {
|
|
1394
|
-
throw new InvalidOpError(
|
|
1395
|
-
`persistDelta: events [${sinceSeq}, ${log.baseSeqValue()}) were compacted before being journaled`
|
|
1396
|
-
);
|
|
1397
|
-
}
|
|
1398
|
-
const fresh = log.since(sinceSeq);
|
|
1399
|
-
if (fresh.length > 0) await store.appendEvents(fresh);
|
|
1400
|
-
return log.length();
|
|
1401
|
-
}
|
|
1402
|
-
async function persistCheckpoint(store, tree, log, vectors) {
|
|
1403
|
-
await store.writeCheckpoint(await serializeArtifact(tree, log, vectors));
|
|
1404
|
-
return log.length();
|
|
1405
|
-
}
|
|
1406
|
-
async function restoreFromDelta(store, deps, vectors) {
|
|
1407
|
-
const { checkpoint, journal } = await store.loadDelta();
|
|
1408
|
-
if (!checkpoint) return null;
|
|
1409
|
-
const checkpointVersion = (checkpoint.baseSeq ?? 0) + checkpoint.events.length;
|
|
1410
|
-
for (let i = 0; i < journal.length; i++) {
|
|
1411
|
-
if (journal[i].seq !== checkpointVersion + i) {
|
|
1412
|
-
throw new InvalidOpError(
|
|
1413
|
-
`restoreFromDelta: journal not contiguous with checkpoint (expected seq ${checkpointVersion + i}, got ${journal[i].seq})`
|
|
1414
|
-
);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
const usedIds = new Set(checkpoint.nodes.map((n) => n.id));
|
|
1418
|
-
const guardedDeps = {
|
|
1419
|
-
...deps,
|
|
1420
|
-
idGen: {
|
|
1421
|
-
next: () => {
|
|
1422
|
-
let id = deps.idGen.next();
|
|
1423
|
-
while (usedIds.has(id)) id = deps.idGen.next();
|
|
1424
|
-
usedIds.add(id);
|
|
1425
|
-
return id;
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
};
|
|
1429
|
-
const { tree } = await restoreArtifact(checkpoint, guardedDeps, vectors);
|
|
1430
|
-
const addressing = new Addressing(tree);
|
|
1431
|
-
const replayLog = new EventLog();
|
|
1432
|
-
const removedIds = [];
|
|
1433
|
-
const mutator = new Mutator(tree, addressing, replayLog, {
|
|
1434
|
-
clock: deps.clock,
|
|
1435
|
-
onChange: (node) => {
|
|
1436
|
-
node.meta.embedding = { state: "stale" };
|
|
1437
|
-
},
|
|
1438
|
-
onRemove: (id) => {
|
|
1439
|
-
removedIds.push(id);
|
|
1440
|
-
}
|
|
1441
|
-
});
|
|
1442
|
-
replayForward(mutator, journal);
|
|
1443
|
-
for (const id of removedIds) await vectors.remove(id);
|
|
1444
|
-
const log = EventLog.fromStored([...checkpoint.events, ...journal], checkpoint.baseSeq ?? 0);
|
|
1445
|
-
return { tree, log };
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
// src/arbor.ts
|
|
1449
|
-
function buildDeps(opts) {
|
|
1450
|
-
const base = opts.decompose ?? sizeBasedDecision(200);
|
|
1451
|
-
return {
|
|
1452
|
-
idGen: opts.idGen ?? new UuidIdGen(),
|
|
1453
|
-
clock: opts.clock ?? new SystemClock(),
|
|
1454
|
-
decision: opts.registry ? typeAwareDecision(base, opts.registry) : base
|
|
1455
|
-
};
|
|
1456
|
-
}
|
|
1457
|
-
function guardIdGen(idGen, used) {
|
|
1458
|
-
return {
|
|
1459
|
-
next: () => {
|
|
1460
|
-
let id = idGen.next();
|
|
1461
|
-
while (used.has(id)) id = idGen.next();
|
|
1462
|
-
used.add(id);
|
|
1463
|
-
return id;
|
|
1464
|
-
}
|
|
1465
|
-
};
|
|
1466
|
-
}
|
|
1467
|
-
function assemble(opts, tree, log, vectors, clock, hasCheckpoint) {
|
|
1468
|
-
const addressing = new Addressing(tree);
|
|
1469
|
-
const index = opts.embedding ? new SemanticIndex(tree, addressing, opts.embedding, vectors, opts.registry) : void 0;
|
|
1470
|
-
const mdeps = { clock };
|
|
1471
|
-
if (opts.registry) mdeps.validate = makeRegistryValidator(opts.registry);
|
|
1472
|
-
if (index) Object.assign(mdeps, index.hooks());
|
|
1473
|
-
const mutator = new Mutator(tree, addressing, log, mdeps);
|
|
1474
|
-
const replay = new Replay(tree, log);
|
|
1475
|
-
let highWater = log.length();
|
|
1476
|
-
let checkpointed = hasCheckpoint;
|
|
1477
|
-
async function doCheckpoint(keepLast) {
|
|
1478
|
-
if (!opts.delta) throw new InvalidOpError("checkpoint(): no delta storage configured");
|
|
1479
|
-
if (keepLast !== void 0) log.compactTo(log.length() - keepLast);
|
|
1480
|
-
highWater = await persistCheckpoint(opts.delta, tree, log, vectors);
|
|
1481
|
-
checkpointed = true;
|
|
1482
|
-
}
|
|
1483
|
-
return {
|
|
1484
|
-
tree,
|
|
1485
|
-
addressing,
|
|
1486
|
-
log,
|
|
1487
|
-
mutator,
|
|
1488
|
-
replay,
|
|
1489
|
-
index,
|
|
1490
|
-
vectors,
|
|
1491
|
-
toolset: (binding) => makeToolset({ tree, addressing, log, mutator, index }, binding),
|
|
1492
|
-
save: async () => {
|
|
1493
|
-
if (!opts.storage) throw new InvalidOpError("save(): no storage configured");
|
|
1494
|
-
await opts.storage.save(await serializeArtifact(tree, log, vectors));
|
|
1495
|
-
},
|
|
1496
|
-
saveDelta: async () => {
|
|
1497
|
-
if (!opts.delta) throw new InvalidOpError("saveDelta(): no delta storage configured");
|
|
1498
|
-
if (!checkpointed) {
|
|
1499
|
-
await doCheckpoint();
|
|
1500
|
-
return;
|
|
1501
|
-
}
|
|
1502
|
-
highWater = await persistDelta(opts.delta, log, highWater);
|
|
1503
|
-
},
|
|
1504
|
-
checkpoint: (o) => doCheckpoint(o?.keepLast)
|
|
1505
|
-
};
|
|
1506
|
-
}
|
|
1507
|
-
function createArbor(opts = {}) {
|
|
1508
|
-
const deps = buildDeps(opts);
|
|
1509
|
-
const tree = ArtifactTree.fromJson(opts.initial ?? {}, deps);
|
|
1510
|
-
const log = new EventLog();
|
|
1511
|
-
const vectors = opts.vectors ?? new MemoryVectorIndex();
|
|
1512
|
-
const arbor = assemble(opts, tree, log, vectors, deps.clock, false);
|
|
1513
|
-
if (arbor.index) {
|
|
1514
|
-
for (const node of tree.allNodes()) arbor.index.onChange(node);
|
|
1515
|
-
}
|
|
1516
|
-
return arbor;
|
|
1517
|
-
}
|
|
1518
|
-
async function restoreArbor(opts) {
|
|
1519
|
-
const deps = buildDeps(opts);
|
|
1520
|
-
const vectors = opts.vectors ?? new MemoryVectorIndex();
|
|
1521
|
-
if (opts.delta) {
|
|
1522
|
-
const restored = await restoreFromDelta(opts.delta, deps, vectors);
|
|
1523
|
-
if (restored) return assemble(opts, restored.tree, restored.log, vectors, deps.clock, true);
|
|
1524
|
-
}
|
|
1525
|
-
if (opts.storage) {
|
|
1526
|
-
const stored = await opts.storage.load();
|
|
1527
|
-
if (stored) {
|
|
1528
|
-
const guarded = {
|
|
1529
|
-
...deps,
|
|
1530
|
-
idGen: guardIdGen(deps.idGen, new Set(stored.nodes.map((n) => n.id)))
|
|
1531
|
-
};
|
|
1532
|
-
const { tree, log } = await restoreArtifact(stored, guarded, vectors);
|
|
1533
|
-
return assemble(opts, tree, log, vectors, deps.clock, false);
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
return null;
|
|
1537
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
createArbor,
|
|
3
|
+
restoreArbor
|
|
4
|
+
} from "./chunk-WEO5KBRI.js";
|
|
5
|
+
import "./chunk-ZDANDXJ6.js";
|
|
6
|
+
import "./chunk-6DOI4FNX.js";
|
|
7
|
+
import "./chunk-OZCCBVUV.js";
|
|
8
|
+
import "./chunk-QRGDUGY6.js";
|
|
9
|
+
import "./chunk-ZBBMJMLG.js";
|
|
10
|
+
import "./chunk-XWCA5XHZ.js";
|
|
11
|
+
import "./chunk-L4HFSLBD.js";
|
|
12
|
+
import "./chunk-53XPRLFF.js";
|
|
13
|
+
import "./chunk-YCDDDYGG.js";
|
|
14
|
+
import "./chunk-U3NXJNUY.js";
|
|
15
|
+
import "./chunk-AGPH62II.js";
|
|
16
|
+
import "./chunk-TEJZF3IR.js";
|
|
17
|
+
import "./chunk-32FKMLEA.js";
|
|
18
|
+
import "./chunk-KRJCSJL4.js";
|
|
19
|
+
import "./chunk-YWY2EK2Q.js";
|
|
20
|
+
import "./chunk-2UY5KJ5P.js";
|
|
21
|
+
import "./chunk-M72UOSPG.js";
|
|
22
|
+
import "./chunk-VG5BEOBI.js";
|
|
23
|
+
import "./chunk-UTJZYWK3.js";
|
|
24
|
+
import "./chunk-V3HDDWER.js";
|
|
25
|
+
import "./chunk-XT7WR7OF.js";
|
|
1538
26
|
export {
|
|
1539
27
|
createArbor,
|
|
1540
28
|
restoreArbor
|