@brightbeamai/chap-coordinator 0.3.3 → 0.3.5
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 +54 -8
- package/dist/{api-59aU01Bs.d.cts → api-CjZQnj63.d.cts} +128 -14
- package/dist/{api-CxciGFHA.d.ts → api-DG0VFGVr.d.ts} +128 -14
- package/dist/api.cjs +3 -1
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +3 -1
- package/dist/api.js.map +1 -1
- package/dist/index.cjs +1578 -276
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -6
- package/dist/index.d.ts +20 -6
- package/dist/index.js +1577 -276
- package/dist/index.js.map +1 -1
- package/dist/jsonrpc.cjs +1 -0
- package/dist/jsonrpc.cjs.map +1 -1
- package/dist/jsonrpc.d.cts +1 -0
- package/dist/jsonrpc.d.ts +1 -0
- package/dist/jsonrpc.js +1 -0
- package/dist/jsonrpc.js.map +1 -1
- package/dist/methods.cjs.map +1 -1
- package/dist/methods.d.cts +36 -3
- package/dist/methods.d.ts +36 -3
- package/dist/patch.cjs +195 -61
- package/dist/patch.cjs.map +1 -1
- package/dist/patch.d.cts +16 -2
- package/dist/patch.d.ts +16 -2
- package/dist/patch.js +194 -61
- package/dist/patch.js.map +1 -1
- package/dist/storage/sqlite.cjs +81 -19
- package/dist/storage/sqlite.cjs.map +1 -1
- package/dist/storage/sqlite.d.cts +19 -9
- package/dist/storage/sqlite.d.ts +19 -9
- package/dist/storage/sqlite.js +81 -19
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/storage/store.cjs +20 -15
- package/dist/storage/store.cjs.map +1 -1
- package/dist/storage/store.d.cts +24 -6
- package/dist/storage/store.d.ts +24 -6
- package/dist/storage/store.js +20 -15
- package/dist/storage/store.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +56 -1
- package/dist/types.d.ts +56 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
package/dist/patch.js
CHANGED
|
@@ -107,77 +107,116 @@ function deepEqual(a, b) {
|
|
|
107
107
|
}
|
|
108
108
|
return true;
|
|
109
109
|
}
|
|
110
|
-
function
|
|
110
|
+
function addAt(doc, path, value, size, copied = false, movedFrom) {
|
|
111
|
+
const parts = splitPath(path);
|
|
112
|
+
if (parts.length === 0) {
|
|
113
|
+
if (size) {
|
|
114
|
+
size.reset(value);
|
|
115
|
+
if (copied || movedFrom !== void 0) size.carry(size);
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
const [parent, key] = navigate(doc, parts);
|
|
120
|
+
let newKey;
|
|
121
|
+
if (Array.isArray(parent)) {
|
|
122
|
+
if (key === "-") {
|
|
123
|
+
parent.push(value);
|
|
124
|
+
} else {
|
|
125
|
+
const idx = key;
|
|
126
|
+
if (idx < 0 || idx > parent.length) {
|
|
127
|
+
throw new PatchError(`Index out of range for add: ${path}`);
|
|
128
|
+
}
|
|
129
|
+
parent.splice(idx, 0, value);
|
|
130
|
+
}
|
|
131
|
+
} else if (parent !== null && typeof parent === "object") {
|
|
132
|
+
if (Object.prototype.hasOwnProperty.call(parent, key)) size?.take(parent[key]);
|
|
133
|
+
else newKey = key;
|
|
134
|
+
parent[key] = value;
|
|
135
|
+
} else {
|
|
136
|
+
throw new PatchError(`Cannot add into ${jsonType(parent)}`);
|
|
137
|
+
}
|
|
138
|
+
if (size) {
|
|
139
|
+
if (movedFrom === void 0) {
|
|
140
|
+
const inserted = size.insert(value, parts.length, newKey);
|
|
141
|
+
if (copied) size.carry(inserted);
|
|
142
|
+
} else {
|
|
143
|
+
if (newKey !== void 0) size.bytes += utf8Length(newKey);
|
|
144
|
+
if (parts.length > movedFrom) {
|
|
145
|
+
const moved = sizeOf(value);
|
|
146
|
+
size.depth = Math.max(size.depth, parts.length + moved.depth);
|
|
147
|
+
size.carry(moved);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return doc;
|
|
152
|
+
}
|
|
153
|
+
function removeAt(doc, path, size, moved = false) {
|
|
154
|
+
const parts = splitPath(path);
|
|
155
|
+
if (parts.length === 0) throw new PatchError("Cannot remove root.");
|
|
156
|
+
const [parent, key] = navigate(doc, parts);
|
|
157
|
+
if (Array.isArray(parent)) {
|
|
158
|
+
const idx = key === "-" ? -1 : key;
|
|
159
|
+
if (idx < 0 || idx >= parent.length) {
|
|
160
|
+
throw new PatchError(`Index out of range for remove: ${path}`);
|
|
161
|
+
}
|
|
162
|
+
if (size && !moved) size.take(parent[idx]);
|
|
163
|
+
parent.splice(idx, 1);
|
|
164
|
+
} else if (parent !== null && typeof parent === "object") {
|
|
165
|
+
if (!Object.prototype.hasOwnProperty.call(parent, key)) {
|
|
166
|
+
throw new PatchError(`Path not found for remove: ${path}`);
|
|
167
|
+
}
|
|
168
|
+
if (size) {
|
|
169
|
+
if (moved) size.bytes -= utf8Length(key);
|
|
170
|
+
else size.take(parent[key], key);
|
|
171
|
+
}
|
|
172
|
+
delete parent[key];
|
|
173
|
+
} else {
|
|
174
|
+
throw new PatchError(`Cannot remove from ${jsonType(parent)}`);
|
|
175
|
+
}
|
|
176
|
+
return doc;
|
|
177
|
+
}
|
|
178
|
+
function applyOne(doc, op, size) {
|
|
111
179
|
const kind = op.op;
|
|
112
180
|
const path = op.path ?? "";
|
|
113
181
|
if (kind === "add") {
|
|
114
182
|
if (!("value" in op)) throw new PatchError("'add' requires 'value'");
|
|
115
|
-
|
|
116
|
-
if (parts.length === 0) return op.value;
|
|
117
|
-
const [parent, key] = navigate(doc, parts);
|
|
118
|
-
if (Array.isArray(parent)) {
|
|
119
|
-
if (key === "-") {
|
|
120
|
-
parent.push(op.value);
|
|
121
|
-
} else {
|
|
122
|
-
const idx = key;
|
|
123
|
-
if (idx < 0 || idx > parent.length) {
|
|
124
|
-
throw new PatchError(`Index out of range for add: ${path}`);
|
|
125
|
-
}
|
|
126
|
-
parent.splice(idx, 0, op.value);
|
|
127
|
-
}
|
|
128
|
-
} else if (parent !== null && typeof parent === "object") {
|
|
129
|
-
parent[key] = op.value;
|
|
130
|
-
} else {
|
|
131
|
-
throw new PatchError(`Cannot add into ${jsonType(parent)}`);
|
|
132
|
-
}
|
|
133
|
-
return doc;
|
|
183
|
+
return addAt(doc, path, clone(op.value), size);
|
|
134
184
|
}
|
|
135
185
|
if (kind === "replace") {
|
|
136
186
|
if (!("value" in op)) throw new PatchError("'replace' requires 'value'");
|
|
137
187
|
const parts = splitPath(path);
|
|
138
|
-
|
|
188
|
+
const value = clone(op.value);
|
|
189
|
+
if (parts.length === 0) {
|
|
190
|
+
size?.reset(value);
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
139
193
|
const [parent, key] = navigate(doc, parts);
|
|
140
194
|
if (Array.isArray(parent)) {
|
|
141
195
|
const idx = key === "-" ? -1 : key;
|
|
142
196
|
if (idx < 0 || idx >= parent.length) {
|
|
143
197
|
throw new PatchError(`Path not found for replace: ${path}`);
|
|
144
198
|
}
|
|
145
|
-
parent[idx]
|
|
199
|
+
size?.take(parent[idx]);
|
|
200
|
+
parent[idx] = value;
|
|
146
201
|
} else if (parent !== null && typeof parent === "object") {
|
|
147
202
|
if (!Object.prototype.hasOwnProperty.call(parent, key)) {
|
|
148
203
|
throw new PatchError(`Path not found for replace: ${path}`);
|
|
149
204
|
}
|
|
150
|
-
parent[key]
|
|
205
|
+
size?.take(parent[key]);
|
|
206
|
+
parent[key] = value;
|
|
151
207
|
} else {
|
|
152
208
|
throw new PatchError(`Cannot replace in ${jsonType(parent)}`);
|
|
153
209
|
}
|
|
210
|
+
size?.insert(value, parts.length);
|
|
154
211
|
return doc;
|
|
155
212
|
}
|
|
156
213
|
if (kind === "remove") {
|
|
157
|
-
|
|
158
|
-
if (parts.length === 0) throw new PatchError("Cannot remove root.");
|
|
159
|
-
const [parent, key] = navigate(doc, parts);
|
|
160
|
-
if (Array.isArray(parent)) {
|
|
161
|
-
const idx = key === "-" ? -1 : key;
|
|
162
|
-
if (idx < 0 || idx >= parent.length) {
|
|
163
|
-
throw new PatchError(`Index out of range for remove: ${path}`);
|
|
164
|
-
}
|
|
165
|
-
parent.splice(idx, 1);
|
|
166
|
-
} else if (parent !== null && typeof parent === "object") {
|
|
167
|
-
if (!Object.prototype.hasOwnProperty.call(parent, key)) {
|
|
168
|
-
throw new PatchError(`Path not found for remove: ${path}`);
|
|
169
|
-
}
|
|
170
|
-
delete parent[key];
|
|
171
|
-
} else {
|
|
172
|
-
throw new PatchError(`Cannot remove from ${jsonType(parent)}`);
|
|
173
|
-
}
|
|
174
|
-
return doc;
|
|
214
|
+
return removeAt(doc, path, size);
|
|
175
215
|
}
|
|
176
216
|
if (kind === "copy") {
|
|
177
217
|
const src = op.from;
|
|
178
218
|
if (src === void 0) throw new PatchError("'copy' requires 'from'");
|
|
179
|
-
|
|
180
|
-
return applyOne(doc, { op: "add", path, value });
|
|
219
|
+
return addAt(doc, path, clone(getAt(doc, src)), size, true);
|
|
181
220
|
}
|
|
182
221
|
if (kind === "move") {
|
|
183
222
|
const src = op.from;
|
|
@@ -185,9 +224,9 @@ function applyOne(doc, op) {
|
|
|
185
224
|
if (path.startsWith(src + "/")) {
|
|
186
225
|
throw new PatchError("'move' cannot move a location into its own child.");
|
|
187
226
|
}
|
|
188
|
-
const value =
|
|
189
|
-
doc =
|
|
190
|
-
return
|
|
227
|
+
const value = getAt(doc, src);
|
|
228
|
+
doc = removeAt(doc, src, size, true);
|
|
229
|
+
return addAt(doc, path, value, size, false, splitPath(src).length);
|
|
191
230
|
}
|
|
192
231
|
if (kind === "test") {
|
|
193
232
|
if (!("value" in op)) throw new PatchError("'test' requires 'value'");
|
|
@@ -199,29 +238,122 @@ function applyOne(doc, op) {
|
|
|
199
238
|
}
|
|
200
239
|
var MAX_PATCH_OPS = 1e3;
|
|
201
240
|
var MAX_DOCUMENT_NODES = 1e5;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
241
|
+
var MAX_DOCUMENT_DEPTH = 64;
|
|
242
|
+
var MAX_CARRIED_TIMES = 3;
|
|
243
|
+
var DEFAULT_MAX_PATCHED_BYTES = 1048576;
|
|
244
|
+
function utf8Length(s) {
|
|
245
|
+
const buffer = globalThis.Buffer;
|
|
246
|
+
if (typeof buffer === "function") {
|
|
247
|
+
return buffer.byteLength(s, "utf8");
|
|
248
|
+
}
|
|
249
|
+
let n = 0;
|
|
250
|
+
for (let i = 0; i < s.length; i++) {
|
|
251
|
+
const c = s.charCodeAt(i);
|
|
252
|
+
if (c < 128) n += 1;
|
|
253
|
+
else if (c < 2048) n += 2;
|
|
254
|
+
else if (c >= 55296 && c <= 56319 && i + 1 < s.length) {
|
|
255
|
+
const d = s.charCodeAt(i + 1);
|
|
256
|
+
if (d >= 56320 && d <= 57343) {
|
|
257
|
+
n += 4;
|
|
258
|
+
i++;
|
|
259
|
+
} else n += 3;
|
|
260
|
+
} else n += 3;
|
|
261
|
+
}
|
|
262
|
+
return n;
|
|
214
263
|
}
|
|
215
|
-
function
|
|
264
|
+
function sizeOf(value) {
|
|
265
|
+
if (value === null || typeof value !== "object") {
|
|
266
|
+
return { nodes: 1, bytes: typeof value === "string" ? utf8Length(value) : 0, depth: 1 };
|
|
267
|
+
}
|
|
268
|
+
let nodes = 0;
|
|
269
|
+
let bytes = 0;
|
|
270
|
+
let depth = 0;
|
|
271
|
+
const values = [value];
|
|
272
|
+
const levels = [1];
|
|
273
|
+
const visit = (x, level) => {
|
|
274
|
+
if (x !== null && typeof x === "object") {
|
|
275
|
+
values.push(x);
|
|
276
|
+
levels.push(level);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
nodes += 1;
|
|
280
|
+
if (level > depth) depth = level;
|
|
281
|
+
if (typeof x === "string") bytes += utf8Length(x);
|
|
282
|
+
};
|
|
283
|
+
while (values.length > 0) {
|
|
284
|
+
const v = values.pop();
|
|
285
|
+
const level = levels.pop();
|
|
286
|
+
nodes += 1;
|
|
287
|
+
if (level > depth) depth = level;
|
|
288
|
+
if (Array.isArray(v)) {
|
|
289
|
+
for (const x of v) visit(x, level + 1);
|
|
290
|
+
} else {
|
|
291
|
+
const o = v;
|
|
292
|
+
for (const k of Object.keys(o)) {
|
|
293
|
+
bytes += utf8Length(k);
|
|
294
|
+
visit(o[k], level + 1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return { nodes, bytes, depth };
|
|
299
|
+
}
|
|
300
|
+
var Size = class {
|
|
301
|
+
nodes = 0;
|
|
302
|
+
bytes = 0;
|
|
303
|
+
depth = 0;
|
|
304
|
+
/** What copies, and moves to a deeper place or onto the whole document, have carried. */
|
|
305
|
+
carriedNodes = 0;
|
|
306
|
+
carriedBytes = 0;
|
|
307
|
+
constructor(doc) {
|
|
308
|
+
this.reset(doc);
|
|
309
|
+
}
|
|
310
|
+
reset(doc) {
|
|
311
|
+
const s = sizeOf(doc);
|
|
312
|
+
this.nodes = s.nodes;
|
|
313
|
+
this.bytes = s.bytes;
|
|
314
|
+
this.depth = s.depth;
|
|
315
|
+
}
|
|
316
|
+
/** `value` goes in `segments` levels below the top, under a new `key`. */
|
|
317
|
+
insert(value, segments, key) {
|
|
318
|
+
const s = sizeOf(value);
|
|
319
|
+
this.nodes += s.nodes;
|
|
320
|
+
this.bytes += s.bytes + (key === void 0 ? 0 : utf8Length(key));
|
|
321
|
+
this.depth = Math.max(this.depth, segments + s.depth);
|
|
322
|
+
return s;
|
|
323
|
+
}
|
|
324
|
+
carry(s) {
|
|
325
|
+
this.carriedNodes += s.nodes;
|
|
326
|
+
this.carriedBytes += s.bytes;
|
|
327
|
+
}
|
|
328
|
+
/** `value` comes out, with its `key`. */
|
|
329
|
+
take(value, key) {
|
|
330
|
+
const s = sizeOf(value);
|
|
331
|
+
this.nodes -= s.nodes;
|
|
332
|
+
this.bytes -= s.bytes + (key === void 0 ? 0 : utf8Length(key));
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
function applyJsonPatch(doc, ops, limits = {}) {
|
|
336
|
+
const maxBytes = limits.maxStringBytes ?? DEFAULT_MAX_PATCHED_BYTES;
|
|
216
337
|
if (ops.length > MAX_PATCH_OPS) {
|
|
217
338
|
throw new PatchError(`Patch has too many operations (${ops.length} > ${MAX_PATCH_OPS})`);
|
|
218
339
|
}
|
|
219
340
|
let out = JSON.parse(JSON.stringify(doc));
|
|
341
|
+
const size = new Size(out);
|
|
220
342
|
for (const op of ops) {
|
|
221
|
-
out = applyOne(out, op);
|
|
222
|
-
if (
|
|
343
|
+
out = applyOne(out, op, size);
|
|
344
|
+
if (size.depth > MAX_DOCUMENT_DEPTH) size.depth = sizeOf(out).depth;
|
|
345
|
+
if (size.nodes > MAX_DOCUMENT_NODES) {
|
|
223
346
|
throw new PatchError(`Patched document exceeds the node limit (${MAX_DOCUMENT_NODES})`);
|
|
224
347
|
}
|
|
348
|
+
if (size.bytes > maxBytes) {
|
|
349
|
+
throw new PatchError(`Patched document exceeds the size limit (${maxBytes} bytes of strings and keys)`);
|
|
350
|
+
}
|
|
351
|
+
if (size.depth > MAX_DOCUMENT_DEPTH) {
|
|
352
|
+
throw new PatchError(`Patched document nests deeper than ${MAX_DOCUMENT_DEPTH} levels`);
|
|
353
|
+
}
|
|
354
|
+
if (size.carriedNodes > MAX_CARRIED_TIMES * MAX_DOCUMENT_NODES || size.carriedBytes > MAX_CARRIED_TIMES * maxBytes) {
|
|
355
|
+
throw new PatchError(`Patch copies or moves more than its limit (${MAX_CARRIED_TIMES * MAX_DOCUMENT_NODES} values or ${MAX_CARRIED_TIMES * maxBytes} bytes of strings and keys)`);
|
|
356
|
+
}
|
|
225
357
|
}
|
|
226
358
|
return out;
|
|
227
359
|
}
|
|
@@ -260,6 +392,7 @@ function diffJsonPatch(from, to, basePath = "") {
|
|
|
260
392
|
return ops;
|
|
261
393
|
}
|
|
262
394
|
export {
|
|
395
|
+
DEFAULT_MAX_PATCHED_BYTES,
|
|
263
396
|
PatchError,
|
|
264
397
|
applyJsonPatch,
|
|
265
398
|
diffJsonPatch
|
package/dist/patch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/patch.ts"],"sourcesContent":["/**\n * @brightbeamai/chap-coordinator/patch\n *\n * RFC 6902 JSON Patch implementation, plus a small diff computer that\n * produces a patch from two values. Used by the Coordinator for\n * `decide.override` apply and by the playground UI (in the browser) to\n * compute the patch from a textarea edit.\n *\n * Implements all six RFC 6902 operations (add, remove, replace, move,\n * copy, test) with semantics identical to the Python reference. Path\n * segments that enable prototype pollution in a JavaScript runtime\n * (`__proto__`, `constructor`, `prototype`) are rejected, so the two\n * references accept and reject exactly the same patches.\n */\n\nimport type { JsonPatchOp } from \"./types.js\";\n\nexport class PatchError extends Error {}\n\n// Rejected in every JSON Pointer segment: these enable prototype pollution.\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n// An array index is an RFC 6901 array index: \"0\" or a positive integer with no\n// leading zero. A shared strict rule keeps the two references in step -- JS\n// Number() and Python int() otherwise accept disjoint token sets (e.g. \"1e1\",\n// \"0x0a\", \"3.0\", \"\", \"1_0\"), which would apply the same patch differently.\nconst ARRAY_INDEX_RE = /^(0|[1-9][0-9]*)$/;\n\n// The JSON type name of a value. Python and JavaScript name their own types\n// differently (dict against object, list against object, str against string).\n// The refusal names the JSON type, which both references and the\n// specification share.\nfunction jsonType(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n switch (typeof value) {\n case \"object\": return \"object\";\n case \"string\": return \"string\";\n case \"number\": return \"number\";\n case \"boolean\": return \"boolean\";\n default: return \"null\";\n }\n}\n\nfunction arrayIndex(seg: string): number {\n if (!ARRAY_INDEX_RE.test(seg)) throw new PatchError(`Array index expected at ${JSON.stringify(seg)}`);\n return Number(seg);\n}\n\nfunction unescape(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\nfunction splitPath(path: string): string[] {\n if (path === \"\") return [];\n if (!path.startsWith(\"/\")) {\n throw new PatchError(`JSON Pointer must start with '/': ${JSON.stringify(path)}`);\n }\n const segments = path.slice(1).split(\"/\").map(unescape);\n for (const seg of segments) {\n if (DANGEROUS_KEYS.has(seg)) {\n throw new PatchError(`Refusing unsafe path segment ${JSON.stringify(seg)}`);\n }\n }\n return segments;\n}\n\n/** Navigate to the parent of the target; return [parent, lastKey]. */\nfunction navigate(doc: any, parts: string[]): [any, string | number] {\n if (parts.length === 0) {\n throw new PatchError(\"Cannot operate on root with this helper.\");\n }\n let parent = doc;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (Array.isArray(parent)) {\n const idx = arrayIndex(part);\n if (idx >= parent.length) {\n throw new PatchError(`Index out of range at /${parts.slice(0, i + 1).join(\"/\")}`);\n }\n parent = parent[idx];\n } else if (parent !== null && typeof parent === \"object\") {\n if (!Object.prototype.hasOwnProperty.call(parent, part)) {\n throw new PatchError(`Path not found: /${parts.slice(0, i + 1).join(\"/\")}`);\n }\n parent = parent[part];\n } else {\n throw new PatchError(`Cannot traverse into ${jsonType(parent)} at ${JSON.stringify(part)}`);\n }\n }\n const last = parts[parts.length - 1];\n if (Array.isArray(parent)) {\n if (last === \"-\") return [parent, \"-\"];\n return [parent, arrayIndex(last)];\n }\n return [parent, last];\n}\n\nfunction getAt(doc: any, path: string): any {\n const parts = splitPath(path);\n if (parts.length === 0) return doc;\n const [parent, key] = navigate(doc, parts);\n if (Array.isArray(parent)) {\n if (key === \"-\") throw new PatchError(\"Cannot read '-' position.\");\n const idx = key as number;\n if (idx < 0 || idx >= parent.length) throw new PatchError(`Index out of range: ${path}`);\n return parent[idx];\n }\n if (\n parent === null ||\n typeof parent !== \"object\" ||\n !Object.prototype.hasOwnProperty.call(parent, key)\n ) {\n throw new PatchError(`Path not found: ${path}`);\n }\n return parent[key as string];\n}\n\nfunction clone<T>(v: T): T {\n return v === undefined ? v : (JSON.parse(JSON.stringify(v)) as T);\n}\n\nfunction deepEqual(a: any, b: any): boolean {\n if (a === b) return true;\n if (a === null || b === null) return a === b;\n if (typeof a !== typeof b) return false;\n if (typeof a !== \"object\") return a === b;\n const aArr = Array.isArray(a);\n if (aArr !== Array.isArray(b)) return false;\n if (aArr) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;\n return true;\n }\n const ak = Object.keys(a);\n const bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (!Object.prototype.hasOwnProperty.call(b, k)) return false;\n if (!deepEqual(a[k], b[k])) return false;\n }\n return true;\n}\n\nfunction applyOne(doc: any, op: JsonPatchOp): any {\n const kind = op.op;\n const path = op.path ?? \"\";\n\n if (kind === \"add\") {\n if (!(\"value\" in op)) throw new PatchError(\"'add' requires 'value'\");\n const parts = splitPath(path);\n if (parts.length === 0) return op.value; // replace whole document\n const [parent, key] = navigate(doc, parts);\n if (Array.isArray(parent)) {\n if (key === \"-\") {\n parent.push(op.value);\n } else {\n const idx = key as number;\n if (idx < 0 || idx > parent.length) {\n throw new PatchError(`Index out of range for add: ${path}`);\n }\n parent.splice(idx, 0, op.value);\n }\n } else if (parent !== null && typeof parent === \"object\") {\n parent[key as string] = op.value;\n } else {\n throw new PatchError(`Cannot add into ${jsonType(parent)}`);\n }\n return doc;\n }\n\n if (kind === \"replace\") {\n if (!(\"value\" in op)) throw new PatchError(\"'replace' requires 'value'\");\n const parts = splitPath(path);\n if (parts.length === 0) return op.value;\n const [parent, key] = navigate(doc, parts);\n if (Array.isArray(parent)) {\n // RFC 6901: \"-\" names the position after the last element, which no\n // element occupies, so only \"add\" can use it. Left as a string it slips\n // through a numeric range check and writes a \"-\" property on the array.\n const idx = key === \"-\" ? -1 : (key as number);\n if (idx < 0 || idx >= parent.length) {\n throw new PatchError(`Path not found for replace: ${path}`);\n }\n parent[idx] = op.value;\n } else if (parent !== null && typeof parent === \"object\") {\n if (!Object.prototype.hasOwnProperty.call(parent, key)) {\n throw new PatchError(`Path not found for replace: ${path}`);\n }\n parent[key as string] = op.value;\n } else {\n throw new PatchError(`Cannot replace in ${jsonType(parent)}`);\n }\n return doc;\n }\n\n if (kind === \"remove\") {\n const parts = splitPath(path);\n if (parts.length === 0) throw new PatchError(\"Cannot remove root.\");\n const [parent, key] = navigate(doc, parts);\n if (Array.isArray(parent)) {\n // RFC 6901: \"-\" names no existing element, so it cannot be removed.\n // Left as a string it passes the numeric range check and then splice\n // coerces it to zero, removing the first element instead.\n const idx = key === \"-\" ? -1 : (key as number);\n if (idx < 0 || idx >= parent.length) {\n throw new PatchError(`Index out of range for remove: ${path}`);\n }\n parent.splice(idx, 1);\n } else if (parent !== null && typeof parent === \"object\") {\n if (!Object.prototype.hasOwnProperty.call(parent, key)) {\n throw new PatchError(`Path not found for remove: ${path}`);\n }\n delete parent[key as string];\n } else {\n throw new PatchError(`Cannot remove from ${jsonType(parent)}`);\n }\n return doc;\n }\n\n if (kind === \"copy\") {\n const src = op.from;\n if (src === undefined) throw new PatchError(\"'copy' requires 'from'\");\n const value = clone(getAt(doc, src));\n return applyOne(doc, { op: \"add\", path, value });\n }\n\n if (kind === \"move\") {\n const src = op.from;\n if (src === undefined) throw new PatchError(\"'move' requires 'from'\");\n // RFC 6902 4.4: 'from' MUST NOT be a proper prefix of 'path'.\n if (path.startsWith(src + \"/\")) {\n throw new PatchError(\"'move' cannot move a location into its own child.\");\n }\n const value = clone(getAt(doc, src));\n doc = applyOne(doc, { op: \"remove\", path: src });\n return applyOne(doc, { op: \"add\", path, value });\n }\n\n if (kind === \"test\") {\n if (!(\"value\" in op)) throw new PatchError(\"'test' requires 'value'\");\n const actual = getAt(doc, path);\n if (!deepEqual(actual, op.value)) throw new PatchError(`'test' failed at ${path}`);\n return doc;\n }\n\n throw new PatchError(`Unsupported op: ${JSON.stringify(kind)}`);\n}\n\nconst MAX_PATCH_OPS = 1000;\nconst MAX_DOCUMENT_NODES = 100_000;\n\nfunction nodeCount(value: unknown): number {\n if (Array.isArray(value)) {\n let n = 1;\n for (const v of value) n += nodeCount(v);\n return n;\n }\n if (value !== null && typeof value === \"object\") {\n let n = 1;\n for (const v of Object.values(value as Record<string, unknown>)) n += nodeCount(v);\n return n;\n }\n return 1;\n}\n\nexport function applyJsonPatch(doc: unknown, ops: JsonPatchOp[]): unknown {\n if (ops.length > MAX_PATCH_OPS) {\n throw new PatchError(`Patch has too many operations (${ops.length} > ${MAX_PATCH_OPS})`);\n }\n let out: any = JSON.parse(JSON.stringify(doc));\n for (const op of ops) {\n out = applyOne(out, op);\n if (nodeCount(out) > MAX_DOCUMENT_NODES) {\n throw new PatchError(`Patched document exceeds the node limit (${MAX_DOCUMENT_NODES})`);\n }\n }\n return out;\n}\n\n// ============================================================\n// diff: produce a patch that transforms `from` into `to`\n// ============================================================\n\n/**\n * Compute an RFC 6902 patch that, when applied to `from`, produces\n * `to`. Walks objects key-by-key; arrays use a shallow whole-array\n * replace if any element differs. Sufficient for the playground's\n * draft-edit use case where drafts are small JSON objects.\n */\nexport function diffJsonPatch(from: unknown, to: unknown, basePath = \"\"): JsonPatchOp[] {\n const ops: JsonPatchOp[] = [];\n\n if (typeof from !== typeof to || Array.isArray(from) !== Array.isArray(to)) {\n ops.push({ op: \"replace\", path: basePath || \"/\", value: to });\n return ops;\n }\n\n if (from === null || to === null || typeof from !== \"object\") {\n if (from !== to) {\n ops.push({ op: \"replace\", path: basePath || \"/\", value: to });\n }\n return ops;\n }\n\n if (Array.isArray(from) && Array.isArray(to)) {\n if (JSON.stringify(from) !== JSON.stringify(to)) {\n ops.push({ op: \"replace\", path: basePath || \"/\", value: to });\n }\n return ops;\n }\n\n const fromObj = from as Record<string, unknown>;\n const toObj = to as Record<string, unknown>;\n const allKeys = new Set([...Object.keys(fromObj), ...Object.keys(toObj)]);\n\n for (const key of allKeys) {\n const escaped = key.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n const path = `${basePath}/${escaped}`;\n if (!(key in fromObj)) {\n ops.push({ op: \"add\", path, value: toObj[key] });\n } else if (!(key in toObj)) {\n ops.push({ op: \"remove\", path });\n } else {\n ops.push(...diffJsonPatch(fromObj[key], toObj[key], path));\n }\n }\n\n return ops;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/patch.ts"],"sourcesContent":["/**\n * @brightbeamai/chap-coordinator/patch\n *\n * RFC 6902 JSON Patch implementation, plus a small diff computer that\n * produces a patch from two values. Used by the Coordinator for\n * `decide.override` apply and by the playground UI (in the browser) to\n * compute the patch from a textarea edit.\n *\n * Implements all six RFC 6902 operations (add, remove, replace, move,\n * copy, test) with semantics identical to the Python reference. Path\n * segments that enable prototype pollution in a JavaScript runtime\n * (`__proto__`, `constructor`, `prototype`) are rejected, so the two\n * references accept and reject exactly the same patches.\n */\n\nimport type { JsonPatchOp } from \"./types.js\";\n\nexport class PatchError extends Error {}\n\n// Rejected in every JSON Pointer segment: these enable prototype pollution.\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n// An array index is an RFC 6901 array index: \"0\" or a positive integer with no\n// leading zero. A shared strict rule keeps the two references in step -- JS\n// Number() and Python int() otherwise accept disjoint token sets (e.g. \"1e1\",\n// \"0x0a\", \"3.0\", \"\", \"1_0\"), which would apply the same patch differently.\nconst ARRAY_INDEX_RE = /^(0|[1-9][0-9]*)$/;\n\n// The JSON type name of a value. Python and JavaScript name their own types\n// differently (dict against object, list against object, str against string).\n// The refusal names the JSON type, which both references and the\n// specification share.\nfunction jsonType(value: unknown): string {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n switch (typeof value) {\n case \"object\": return \"object\";\n case \"string\": return \"string\";\n case \"number\": return \"number\";\n case \"boolean\": return \"boolean\";\n default: return \"null\";\n }\n}\n\nfunction arrayIndex(seg: string): number {\n if (!ARRAY_INDEX_RE.test(seg)) throw new PatchError(`Array index expected at ${JSON.stringify(seg)}`);\n return Number(seg);\n}\n\nfunction unescape(token: string): string {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\n\nfunction splitPath(path: string): string[] {\n if (path === \"\") return [];\n if (!path.startsWith(\"/\")) {\n throw new PatchError(`JSON Pointer must start with '/': ${JSON.stringify(path)}`);\n }\n const segments = path.slice(1).split(\"/\").map(unescape);\n for (const seg of segments) {\n if (DANGEROUS_KEYS.has(seg)) {\n throw new PatchError(`Refusing unsafe path segment ${JSON.stringify(seg)}`);\n }\n }\n return segments;\n}\n\n/** Navigate to the parent of the target; return [parent, lastKey]. */\nfunction navigate(doc: any, parts: string[]): [any, string | number] {\n if (parts.length === 0) {\n throw new PatchError(\"Cannot operate on root with this helper.\");\n }\n let parent = doc;\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n if (Array.isArray(parent)) {\n const idx = arrayIndex(part);\n if (idx >= parent.length) {\n throw new PatchError(`Index out of range at /${parts.slice(0, i + 1).join(\"/\")}`);\n }\n parent = parent[idx];\n } else if (parent !== null && typeof parent === \"object\") {\n if (!Object.prototype.hasOwnProperty.call(parent, part)) {\n throw new PatchError(`Path not found: /${parts.slice(0, i + 1).join(\"/\")}`);\n }\n parent = parent[part];\n } else {\n throw new PatchError(`Cannot traverse into ${jsonType(parent)} at ${JSON.stringify(part)}`);\n }\n }\n const last = parts[parts.length - 1];\n if (Array.isArray(parent)) {\n if (last === \"-\") return [parent, \"-\"];\n return [parent, arrayIndex(last)];\n }\n return [parent, last];\n}\n\nfunction getAt(doc: any, path: string): any {\n const parts = splitPath(path);\n if (parts.length === 0) return doc;\n const [parent, key] = navigate(doc, parts);\n if (Array.isArray(parent)) {\n if (key === \"-\") throw new PatchError(\"Cannot read '-' position.\");\n const idx = key as number;\n if (idx < 0 || idx >= parent.length) throw new PatchError(`Index out of range: ${path}`);\n return parent[idx];\n }\n if (\n parent === null ||\n typeof parent !== \"object\" ||\n !Object.prototype.hasOwnProperty.call(parent, key)\n ) {\n throw new PatchError(`Path not found: ${path}`);\n }\n return parent[key as string];\n}\n\nfunction clone<T>(v: T): T {\n return v === undefined ? v : (JSON.parse(JSON.stringify(v)) as T);\n}\n\nfunction deepEqual(a: any, b: any): boolean {\n if (a === b) return true;\n if (a === null || b === null) return a === b;\n if (typeof a !== typeof b) return false;\n if (typeof a !== \"object\") return a === b;\n const aArr = Array.isArray(a);\n if (aArr !== Array.isArray(b)) return false;\n if (aArr) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;\n return true;\n }\n const ak = Object.keys(a);\n const bk = Object.keys(b);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (!Object.prototype.hasOwnProperty.call(b, k)) return false;\n if (!deepEqual(a[k], b[k])) return false;\n }\n return true;\n}\n\n/**\n * Puts `value`, which the document owns from here on, at `path`. A copied\n * value counts towards what the patch carries. A moved value is counted\n * already: `movedFrom` is the number of segments of the place it left, and\n * only its key, a value it replaces and how deep it now reaches change the\n * count. A move to a deeper place, or onto the whole document, costs a pass\n * over the value, so it counts towards what the patch carries as well.\n */\nfunction addAt(doc: any, path: string, value: unknown, size: Size | undefined,\n copied = false, movedFrom?: number): any {\n const parts = splitPath(path);\n if (parts.length === 0) {\n if (size) {\n size.reset(value);\n if (copied || movedFrom !== undefined) size.carry(size);\n }\n return value; // replace whole document\n }\n const [parent, key] = navigate(doc, parts);\n let newKey: string | undefined;\n if (Array.isArray(parent)) {\n if (key === \"-\") {\n parent.push(value);\n } else {\n const idx = key as number;\n if (idx < 0 || idx > parent.length) {\n throw new PatchError(`Index out of range for add: ${path}`);\n }\n parent.splice(idx, 0, value);\n }\n } else if (parent !== null && typeof parent === \"object\") {\n if (Object.prototype.hasOwnProperty.call(parent, key)) size?.take(parent[key as string]);\n else newKey = key as string;\n parent[key as string] = value;\n } else {\n throw new PatchError(`Cannot add into ${jsonType(parent)}`);\n }\n if (size) {\n if (movedFrom === undefined) {\n const inserted = size.insert(value, parts.length, newKey);\n if (copied) size.carry(inserted);\n } else {\n if (newKey !== undefined) size.bytes += utf8Length(newKey);\n if (parts.length > movedFrom) {\n // A move to a deeper place costs a pass over the value, for its depth.\n const moved = sizeOf(value);\n size.depth = Math.max(size.depth, parts.length + moved.depth);\n size.carry(moved);\n }\n }\n }\n return doc;\n}\n\n/** Removes the value at `path`. A moved value goes back in elsewhere, so only its key leaves the count. */\nfunction removeAt(doc: any, path: string, size: Size | undefined, moved = false): any {\n const parts = splitPath(path);\n if (parts.length === 0) throw new PatchError(\"Cannot remove root.\");\n const [parent, key] = navigate(doc, parts);\n if (Array.isArray(parent)) {\n // RFC 6901: \"-\" names no existing element, so it cannot be removed.\n // Left as a string it passes the numeric range check and then splice\n // coerces it to zero, removing the first element instead.\n const idx = key === \"-\" ? -1 : (key as number);\n if (idx < 0 || idx >= parent.length) {\n throw new PatchError(`Index out of range for remove: ${path}`);\n }\n if (size && !moved) size.take(parent[idx]);\n parent.splice(idx, 1);\n } else if (parent !== null && typeof parent === \"object\") {\n if (!Object.prototype.hasOwnProperty.call(parent, key)) {\n throw new PatchError(`Path not found for remove: ${path}`);\n }\n if (size) {\n if (moved) size.bytes -= utf8Length(key as string);\n else size.take(parent[key as string], key as string);\n }\n delete parent[key as string];\n } else {\n throw new PatchError(`Cannot remove from ${jsonType(parent)}`);\n }\n return doc;\n}\n\nfunction applyOne(doc: any, op: JsonPatchOp, size?: Size): any {\n const kind = op.op;\n const path = op.path ?? \"\";\n\n if (kind === \"add\") {\n if (!(\"value\" in op)) throw new PatchError(\"'add' requires 'value'\");\n // A copy, so an operation on the inserted value leaves the patch as it\n // was sent.\n return addAt(doc, path, clone(op.value), size);\n }\n\n if (kind === \"replace\") {\n if (!(\"value\" in op)) throw new PatchError(\"'replace' requires 'value'\");\n const parts = splitPath(path);\n const value = clone(op.value);\n if (parts.length === 0) {\n size?.reset(value);\n return value;\n }\n const [parent, key] = navigate(doc, parts);\n if (Array.isArray(parent)) {\n // RFC 6901: \"-\" names the position after the last element, which no\n // element occupies, so only \"add\" can use it. Left as a string it slips\n // through a numeric range check and writes a \"-\" property on the array.\n const idx = key === \"-\" ? -1 : (key as number);\n if (idx < 0 || idx >= parent.length) {\n throw new PatchError(`Path not found for replace: ${path}`);\n }\n size?.take(parent[idx]);\n parent[idx] = value;\n } else if (parent !== null && typeof parent === \"object\") {\n if (!Object.prototype.hasOwnProperty.call(parent, key)) {\n throw new PatchError(`Path not found for replace: ${path}`);\n }\n size?.take(parent[key as string]);\n parent[key as string] = value;\n } else {\n throw new PatchError(`Cannot replace in ${jsonType(parent)}`);\n }\n size?.insert(value, parts.length);\n return doc;\n }\n\n if (kind === \"remove\") {\n return removeAt(doc, path, size);\n }\n\n if (kind === \"copy\") {\n const src = op.from;\n if (src === undefined) throw new PatchError(\"'copy' requires 'from'\");\n return addAt(doc, path, clone(getAt(doc, src)), size, true);\n }\n\n if (kind === \"move\") {\n const src = op.from;\n if (src === undefined) throw new PatchError(\"'move' requires 'from'\");\n // RFC 6902 4.4: 'from' MUST NOT be a proper prefix of 'path'.\n if (path.startsWith(src + \"/\")) {\n throw new PatchError(\"'move' cannot move a location into its own child.\");\n }\n // The remove detaches the value, so it moves as it is, uncopied.\n const value = getAt(doc, src);\n doc = removeAt(doc, src, size, true);\n return addAt(doc, path, value, size, false, splitPath(src).length);\n }\n\n if (kind === \"test\") {\n if (!(\"value\" in op)) throw new PatchError(\"'test' requires 'value'\");\n const actual = getAt(doc, path);\n if (!deepEqual(actual, op.value)) throw new PatchError(`'test' failed at ${path}`);\n return doc;\n }\n\n throw new PatchError(`Unsupported op: ${JSON.stringify(kind)}`);\n}\n\nconst MAX_PATCH_OPS = 1000;\nconst MAX_DOCUMENT_NODES = 100_000;\n\n/**\n * How deeply a patched document may nest, counting the document as level one,\n * as the coordinator counts a request's nesting. Each operation's value comes\n * within the request's limit, and a patch that puts one value inside another\n * would otherwise nest the document without limit.\n */\nconst MAX_DOCUMENT_DEPTH = 64;\n\n/**\n * How much a patch's copies, and its moves to a deeper place or onto the whole\n * document, may carry between them, as a multiple of the document's limits.\n * Each costs a pass over the value it carries, so a patch that copied the same\n * large value back and forth could otherwise keep a coordinator busy for\n * seconds.\n */\nconst MAX_CARRIED_TIMES = 3;\n\n/**\n * The default cap on the string and key text a patched document may hold, in\n * UTF-8 bytes: the default `max_envelope_bytes`. An artefact that arrives in a\n * request holds no more text than the request's size. The node cap alone does\n * not bound size: a `copy` of the root doubles the document, long strings\n * included, so fifteen of them grow 64 KiB to about 2 GiB within 100,000\n * nodes.\n */\nexport const DEFAULT_MAX_PATCHED_BYTES = 1_048_576;\n\n/** Limits a caller can set on {@link applyJsonPatch}. */\nexport interface PatchLimits {\n /** The most UTF-8 bytes of string and key text the result may hold. */\n maxStringBytes?: number;\n}\n\n/**\n * UTF-8 length of a string: Buffer's count where Node provides it, and the\n * same count by hand elsewhere, so the module runs anywhere. A lone surrogate\n * counts three bytes either way, as does Python's\n * `len(s.encode(\"utf-8\", \"surrogatepass\"))`, which keeps the two references\n * refusing the same patches.\n */\nfunction utf8Length(s: string): number {\n const buffer = (globalThis as { Buffer?: unknown }).Buffer;\n if (typeof buffer === \"function\") {\n return (buffer as unknown as { byteLength(s: string, encoding: \"utf8\"): number }).byteLength(s, \"utf8\");\n }\n let n = 0;\n for (let i = 0; i < s.length; i++) {\n const c = s.charCodeAt(i);\n if (c < 0x80) n += 1;\n else if (c < 0x800) n += 2;\n else if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) {\n const d = s.charCodeAt(i + 1);\n if (d >= 0xdc00 && d <= 0xdfff) { n += 4; i++; } else n += 3;\n } else n += 3;\n }\n return n;\n}\n\ninterface Measured { nodes: number; bytes: number; depth: number }\n\n/**\n * A value's count of values, the UTF-8 bytes of its strings and keys, and its\n * depth, counting the value itself as level one, in one pass that does not\n * recurse.\n */\nfunction sizeOf(value: unknown): Measured {\n if (value === null || typeof value !== \"object\") {\n return { nodes: 1, bytes: typeof value === \"string\" ? utf8Length(value) : 0, depth: 1 };\n }\n let nodes = 0;\n let bytes = 0;\n let depth = 0;\n const values: object[] = [value];\n const levels: number[] = [1];\n // A scalar child is counted where it is found, which saves a trip through\n // the stack.\n const visit = (x: unknown, level: number): void => {\n if (x !== null && typeof x === \"object\") {\n values.push(x);\n levels.push(level);\n return;\n }\n nodes += 1;\n if (level > depth) depth = level;\n if (typeof x === \"string\") bytes += utf8Length(x);\n };\n while (values.length > 0) {\n const v = values.pop()!;\n const level = levels.pop()!;\n nodes += 1;\n if (level > depth) depth = level;\n if (Array.isArray(v)) {\n for (const x of v) visit(x, level + 1);\n } else {\n const o = v as Record<string, unknown>;\n for (const k of Object.keys(o)) {\n bytes += utf8Length(k);\n visit(o[k], level + 1);\n }\n }\n }\n return { nodes, bytes, depth };\n}\n\n/**\n * What the working document holds, kept in step by each operation. The values\n * and the bytes are exact: each operation counts what it puts in and what it\n * takes out, so a patch costs what it changes, whatever the document's size.\n * The depth is a bound, raised by what each operation puts in; it passes the\n * limit when the document does, or when the document started deeper.\n */\nclass Size implements Measured {\n nodes = 0;\n bytes = 0;\n depth = 0;\n /** What copies, and moves to a deeper place or onto the whole document, have carried. */\n carriedNodes = 0;\n carriedBytes = 0;\n\n constructor(doc: unknown) {\n this.reset(doc);\n }\n\n reset(doc: unknown): void {\n const s = sizeOf(doc);\n this.nodes = s.nodes;\n this.bytes = s.bytes;\n this.depth = s.depth;\n }\n\n /** `value` goes in `segments` levels below the top, under a new `key`. */\n insert(value: unknown, segments: number, key?: string): Measured {\n const s = sizeOf(value);\n this.nodes += s.nodes;\n this.bytes += s.bytes + (key === undefined ? 0 : utf8Length(key));\n this.depth = Math.max(this.depth, segments + s.depth);\n return s;\n }\n\n carry(s: Measured): void {\n this.carriedNodes += s.nodes;\n this.carriedBytes += s.bytes;\n }\n\n /** `value` comes out, with its `key`. */\n take(value: unknown, key?: string): void {\n const s = sizeOf(value);\n this.nodes -= s.nodes;\n this.bytes -= s.bytes + (key === undefined ? 0 : utf8Length(key));\n }\n}\n\nexport function applyJsonPatch(doc: unknown, ops: JsonPatchOp[], limits: PatchLimits = {}): unknown {\n const maxBytes = limits.maxStringBytes ?? DEFAULT_MAX_PATCHED_BYTES;\n if (ops.length > MAX_PATCH_OPS) {\n throw new PatchError(`Patch has too many operations (${ops.length} > ${MAX_PATCH_OPS})`);\n }\n let out: any = JSON.parse(JSON.stringify(doc));\n const size = new Size(out);\n for (const op of ops) {\n out = applyOne(out, op, size);\n if (size.depth > MAX_DOCUMENT_DEPTH) size.depth = sizeOf(out).depth;\n if (size.nodes > MAX_DOCUMENT_NODES) {\n throw new PatchError(`Patched document exceeds the node limit (${MAX_DOCUMENT_NODES})`);\n }\n if (size.bytes > maxBytes) {\n throw new PatchError(`Patched document exceeds the size limit (${maxBytes} bytes of strings and keys)`);\n }\n if (size.depth > MAX_DOCUMENT_DEPTH) {\n throw new PatchError(`Patched document nests deeper than ${MAX_DOCUMENT_DEPTH} levels`);\n }\n if (size.carriedNodes > MAX_CARRIED_TIMES * MAX_DOCUMENT_NODES || size.carriedBytes > MAX_CARRIED_TIMES * maxBytes) {\n throw new PatchError(`Patch copies or moves more than its limit (${MAX_CARRIED_TIMES * MAX_DOCUMENT_NODES} values or ${MAX_CARRIED_TIMES * maxBytes} bytes of strings and keys)`);\n }\n }\n return out;\n}\n\n// ============================================================\n// diff: produce a patch that transforms `from` into `to`\n// ============================================================\n\n/**\n * Compute an RFC 6902 patch that, when applied to `from`, produces\n * `to`. Walks objects key-by-key; arrays use a shallow whole-array\n * replace if any element differs. Sufficient for the playground's\n * draft-edit use case where drafts are small JSON objects.\n */\nexport function diffJsonPatch(from: unknown, to: unknown, basePath = \"\"): JsonPatchOp[] {\n const ops: JsonPatchOp[] = [];\n\n if (typeof from !== typeof to || Array.isArray(from) !== Array.isArray(to)) {\n ops.push({ op: \"replace\", path: basePath || \"/\", value: to });\n return ops;\n }\n\n if (from === null || to === null || typeof from !== \"object\") {\n if (from !== to) {\n ops.push({ op: \"replace\", path: basePath || \"/\", value: to });\n }\n return ops;\n }\n\n if (Array.isArray(from) && Array.isArray(to)) {\n if (JSON.stringify(from) !== JSON.stringify(to)) {\n ops.push({ op: \"replace\", path: basePath || \"/\", value: to });\n }\n return ops;\n }\n\n const fromObj = from as Record<string, unknown>;\n const toObj = to as Record<string, unknown>;\n const allKeys = new Set([...Object.keys(fromObj), ...Object.keys(toObj)]);\n\n for (const key of allKeys) {\n const escaped = key.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n const path = `${basePath}/${escaped}`;\n if (!(key in fromObj)) {\n ops.push({ op: \"add\", path, value: toObj[key] });\n } else if (!(key in toObj)) {\n ops.push({ op: \"remove\", path });\n } else {\n ops.push(...diffJsonPatch(fromObj[key], toObj[key], path));\n }\n }\n\n return ops;\n}\n"],"mappings":"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","names":[]}
|
package/dist/storage/sqlite.cjs
CHANGED
|
@@ -34,7 +34,8 @@ var COLLECTIONS = [
|
|
|
34
34
|
"deliberations",
|
|
35
35
|
"handoffs",
|
|
36
36
|
"snapshots",
|
|
37
|
-
"route_decisions"
|
|
37
|
+
"route_decisions",
|
|
38
|
+
"invitations"
|
|
38
39
|
];
|
|
39
40
|
var IDEMPOTENCY_KEYS = "idempotency_keys";
|
|
40
41
|
var StoreConflictError = class extends Error {
|
|
@@ -45,7 +46,7 @@ var StoreConflictError = class extends Error {
|
|
|
45
46
|
base;
|
|
46
47
|
constructor(workspace, held, base) {
|
|
47
48
|
super(
|
|
48
|
-
`The store holds ${held} audit entries for workspace ${workspace} and this write expected ${base}, so nothing was written. Another coordinator may be writing to this workspace, the store may have been changed outside this coordinator
|
|
49
|
+
`The store holds ${held} audit entries for workspace ${workspace} and this write expected ${base}, so nothing was written. Another coordinator may be writing to this workspace, or the store may have been changed outside this coordinator. A coordinator reloads the workspace and answers the call as failed, and the call may be sent again.`
|
|
49
50
|
);
|
|
50
51
|
this.name = "StoreConflictError";
|
|
51
52
|
this.workspace = workspace;
|
|
@@ -122,6 +123,54 @@ function assembleSnapshot(meta, entries, objects) {
|
|
|
122
123
|
|
|
123
124
|
// src/storage/sqlite.ts
|
|
124
125
|
var import_meta = {};
|
|
126
|
+
function fromNodeSqlite(db) {
|
|
127
|
+
const transaction = (fn) => {
|
|
128
|
+
const run = (begin) => (...args) => {
|
|
129
|
+
db.exec(begin);
|
|
130
|
+
try {
|
|
131
|
+
const result = fn(...args);
|
|
132
|
+
db.exec("COMMIT");
|
|
133
|
+
return result;
|
|
134
|
+
} catch (err) {
|
|
135
|
+
db.exec("ROLLBACK");
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
return Object.assign(run("BEGIN"), { immediate: run("BEGIN IMMEDIATE") });
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
exec: (sql) => db.exec(sql),
|
|
143
|
+
prepare: (sql) => db.prepare(sql),
|
|
144
|
+
pragma: (name, options) => {
|
|
145
|
+
const row = db.prepare(`PRAGMA ${name}`).get();
|
|
146
|
+
return options?.simple ? row ? Object.values(row)[0] : void 0 : row;
|
|
147
|
+
},
|
|
148
|
+
transaction,
|
|
149
|
+
close: () => db.close()
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function openDatabase(path, driver) {
|
|
153
|
+
const failures = [];
|
|
154
|
+
const load = (name) => {
|
|
155
|
+
try {
|
|
156
|
+
return (0, import_node_module.createRequire)(typeof __filename === "string" ? __filename : import_meta.url)(name);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
failures.push(`${name}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`);
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
if (driver !== "node:sqlite") {
|
|
163
|
+
const Database = load("better-sqlite3");
|
|
164
|
+
if (Database) return new Database(path);
|
|
165
|
+
}
|
|
166
|
+
if (driver !== "better-sqlite3") {
|
|
167
|
+
const sqlite = load("node:sqlite");
|
|
168
|
+
if (sqlite) return fromNodeSqlite(new sqlite.DatabaseSync(path));
|
|
169
|
+
}
|
|
170
|
+
throw new Error(
|
|
171
|
+
`SqliteStore needs the \`better-sqlite3\` package, or Node 22.13 or later for node:sqlite. Install it with \`npm install better-sqlite3\` or use MemoryStore for non-persistent workloads (${failures.join("; ")})`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
125
174
|
var SCHEMA = `
|
|
126
175
|
CREATE TABLE IF NOT EXISTS chap_meta (
|
|
127
176
|
workspace TEXT PRIMARY KEY,
|
|
@@ -148,23 +197,15 @@ var LEGACY_VIEW = `CREATE VIEW IF NOT EXISTS ${LEGACY_TABLE} AS SELECT m.workspa
|
|
|
148
197
|
"'$.audit', json((SELECT json_group_array(json(e.data)) FROM (SELECT data FROM chap_entries WHERE workspace = m.workspace ORDER BY seq) AS e))",
|
|
149
198
|
...[...COLLECTIONS, IDEMPOTENCY_KEYS].map((c) => `'$.${c}', json((SELECT json_group_object(id, json(data)) FROM chap_objects WHERE workspace = m.workspace AND collection = '${c}'))`)
|
|
150
199
|
].join(", ") + ") AS data, m.version AS version, m.updated_at AS updated_at FROM chap_meta AS m";
|
|
200
|
+
var LEGACY_VIEW_RECORDED = LEGACY_VIEW.replace("CREATE VIEW IF NOT EXISTS ", "CREATE VIEW ");
|
|
151
201
|
var SqliteStore = class {
|
|
152
202
|
db;
|
|
153
203
|
stmt;
|
|
154
204
|
txWrite;
|
|
155
205
|
txDelete;
|
|
156
206
|
constructor(path, options = {}) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const here = typeof __filename === "string" ? __filename : import_meta.url;
|
|
160
|
-
Database = (0, import_node_module.createRequire)(here)("better-sqlite3");
|
|
161
|
-
} catch (err) {
|
|
162
|
-
const detail = err instanceof Error ? `: ${err.message.split("\n")[0]}` : "";
|
|
163
|
-
throw new Error(
|
|
164
|
-
`SqliteStore requires the \`better-sqlite3\` package. Install it with \`npm install better-sqlite3\` or use MemoryStore for non-persistent workloads${detail}`
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
this.db = new Database(path);
|
|
207
|
+
this.db = openDatabase(path, options.driver);
|
|
208
|
+
this.db.pragma("busy_timeout = 5000");
|
|
168
209
|
if (path !== ":memory:" && options.wal !== false) {
|
|
169
210
|
this.db.pragma("journal_mode = WAL");
|
|
170
211
|
}
|
|
@@ -188,7 +229,10 @@ var SqliteStore = class {
|
|
|
188
229
|
dropObjects: prepare("DELETE FROM chap_objects WHERE workspace = ?"),
|
|
189
230
|
metas: prepare("SELECT workspace, data, version, updated_at FROM chap_meta"),
|
|
190
231
|
entries: prepare("SELECT workspace, seq, data FROM chap_entries ORDER BY workspace, seq"),
|
|
191
|
-
objects: prepare("SELECT workspace, collection, id, data FROM chap_objects ORDER BY rowid")
|
|
232
|
+
objects: prepare("SELECT workspace, collection, id, data FROM chap_objects ORDER BY rowid"),
|
|
233
|
+
metaOf: prepare("SELECT workspace, data, version, updated_at FROM chap_meta WHERE workspace = ?"),
|
|
234
|
+
entriesOf: prepare("SELECT workspace, seq, data FROM chap_entries WHERE workspace = ? ORDER BY seq"),
|
|
235
|
+
objectsOf: prepare("SELECT workspace, collection, id, data FROM chap_objects WHERE workspace = ? ORDER BY rowid")
|
|
192
236
|
};
|
|
193
237
|
this.txWrite = this.db.transaction((change) => this.write(change));
|
|
194
238
|
this.txDelete = this.db.transaction((id) => this.drop(id));
|
|
@@ -230,6 +274,8 @@ var SqliteStore = class {
|
|
|
230
274
|
}
|
|
231
275
|
this.db.exec("DROP INDEX IF EXISTS idx_chap_workspaces_updated_at");
|
|
232
276
|
}
|
|
277
|
+
const view = this.db.prepare("SELECT type, sql FROM sqlite_master WHERE name = ?").get(LEGACY_TABLE);
|
|
278
|
+
if (view?.type === "view" && view.sql !== LEGACY_VIEW_RECORDED) this.db.exec(`DROP VIEW ${LEGACY_TABLE}`);
|
|
233
279
|
if (!this.stmt.hasName.get(LEGACY_TABLE)) this.db.exec(LEGACY_VIEW);
|
|
234
280
|
}).immediate();
|
|
235
281
|
for (const id of unreadable) {
|
|
@@ -263,12 +309,28 @@ var SqliteStore = class {
|
|
|
263
309
|
this.stmt.dropObjects.run(id);
|
|
264
310
|
}
|
|
265
311
|
load() {
|
|
266
|
-
return this.db.transaction(() => this.
|
|
312
|
+
return this.db.transaction(() => this.read(
|
|
313
|
+
this.stmt.metas.all(),
|
|
314
|
+
this.stmt.entries.all(),
|
|
315
|
+
this.stmt.objects.all()
|
|
316
|
+
))();
|
|
317
|
+
}
|
|
318
|
+
loadOne(id) {
|
|
319
|
+
return this.db.transaction(() => this.read(
|
|
320
|
+
this.stmt.metaOf.all(id),
|
|
321
|
+
this.stmt.entriesOf.all(id),
|
|
322
|
+
this.stmt.objectsOf.all(id)
|
|
323
|
+
))()[0];
|
|
324
|
+
}
|
|
325
|
+
version(id) {
|
|
326
|
+
if (!this.stmt.hasMeta.get(id)) return void 0;
|
|
327
|
+
const last = this.stmt.held.get(id)?.last;
|
|
328
|
+
return last === null || last === void 0 ? 0 : last + 1;
|
|
267
329
|
}
|
|
268
|
-
|
|
330
|
+
read(metas, entryRows, objectRows) {
|
|
269
331
|
const broken = /* @__PURE__ */ new Map();
|
|
270
332
|
const entries = /* @__PURE__ */ new Map();
|
|
271
|
-
for (const r of
|
|
333
|
+
for (const r of entryRows) {
|
|
272
334
|
if (broken.has(r.workspace)) continue;
|
|
273
335
|
let list = entries.get(r.workspace);
|
|
274
336
|
if (!list) entries.set(r.workspace, list = []);
|
|
@@ -283,7 +345,7 @@ var SqliteStore = class {
|
|
|
283
345
|
}
|
|
284
346
|
}
|
|
285
347
|
const objects = /* @__PURE__ */ new Map();
|
|
286
|
-
for (const r of
|
|
348
|
+
for (const r of objectRows) {
|
|
287
349
|
if (broken.has(r.workspace)) continue;
|
|
288
350
|
let list = objects.get(r.workspace);
|
|
289
351
|
if (!list) objects.set(r.workspace, list = []);
|
|
@@ -294,7 +356,7 @@ var SqliteStore = class {
|
|
|
294
356
|
}
|
|
295
357
|
}
|
|
296
358
|
const out = [];
|
|
297
|
-
for (const r of
|
|
359
|
+
for (const r of metas) {
|
|
298
360
|
let meta;
|
|
299
361
|
if (!broken.has(r.workspace)) {
|
|
300
362
|
try {
|