@typicalday/firegraph 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -15
- package/dist/backend-BsR0lnFL.d.ts +200 -0
- package/dist/backend-Ct-fLlkG.d.cts +200 -0
- package/dist/backend.cjs +143 -2
- package/dist/backend.cjs.map +1 -1
- package/dist/backend.d.cts +3 -3
- package/dist/backend.d.ts +3 -3
- package/dist/backend.js +13 -4
- package/dist/backend.js.map +1 -1
- package/dist/chunk-AWW4MUJ5.js +245 -0
- package/dist/chunk-AWW4MUJ5.js.map +1 -0
- package/dist/{chunk-5753Y42M.js → chunk-C2QMD7RY.js} +6 -10
- package/dist/chunk-C2QMD7RY.js.map +1 -0
- package/dist/chunk-EQJUUVFG.js +14 -0
- package/dist/chunk-EQJUUVFG.js.map +1 -0
- package/dist/{chunk-6SB34IPQ.js → chunk-HONQY4HF.js} +100 -28
- package/dist/chunk-HONQY4HF.js.map +1 -0
- package/dist/cloudflare/index.cjs +509 -102
- package/dist/cloudflare/index.cjs.map +1 -1
- package/dist/cloudflare/index.d.cts +45 -17
- package/dist/cloudflare/index.d.ts +45 -17
- package/dist/cloudflare/index.js +265 -74
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/index.cjs +291 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -77
- package/dist/index.d.ts +59 -77
- package/dist/index.js +58 -28
- package/dist/index.js.map +1 -1
- package/dist/registry-B1qsVL0E.d.cts +64 -0
- package/dist/registry-Fi074zVa.d.ts +64 -0
- package/dist/{serialization-ZZ7RSDRX.js → serialization-OE2PFZMY.js} +6 -4
- package/dist/{types-BGWxcpI_.d.cts → types-DxYLy8Ol.d.cts} +36 -2
- package/dist/{types-BGWxcpI_.d.ts → types-DxYLy8Ol.d.ts} +36 -2
- package/package.json +8 -3
- package/dist/backend-U-MLShlg.d.ts +0 -97
- package/dist/backend-np4gEVhB.d.cts +0 -97
- package/dist/chunk-5753Y42M.js.map +0 -1
- package/dist/chunk-6SB34IPQ.js.map +0 -1
- package/dist/chunk-R7CRGYY4.js +0 -94
- package/dist/chunk-R7CRGYY4.js.map +0 -1
- /package/dist/{serialization-ZZ7RSDRX.js.map → serialization-OE2PFZMY.js.map} +0 -0
|
@@ -30,6 +30,21 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
));
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
|
+
// src/internal/serialization-tag.ts
|
|
34
|
+
function isTaggedValue(value) {
|
|
35
|
+
if (value === null || typeof value !== "object") return false;
|
|
36
|
+
const tag = value[SERIALIZATION_TAG];
|
|
37
|
+
return typeof tag === "string" && KNOWN_TYPES.has(tag);
|
|
38
|
+
}
|
|
39
|
+
var SERIALIZATION_TAG, KNOWN_TYPES;
|
|
40
|
+
var init_serialization_tag = __esm({
|
|
41
|
+
"src/internal/serialization-tag.ts"() {
|
|
42
|
+
"use strict";
|
|
43
|
+
SERIALIZATION_TAG = "__firegraph_ser__";
|
|
44
|
+
KNOWN_TYPES = /* @__PURE__ */ new Set(["Timestamp", "GeoPoint", "VectorValue", "DocumentReference"]);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
33
48
|
// src/serialization.ts
|
|
34
49
|
var serialization_exports = {};
|
|
35
50
|
__export(serialization_exports, {
|
|
@@ -38,11 +53,6 @@ __export(serialization_exports, {
|
|
|
38
53
|
isTaggedValue: () => isTaggedValue,
|
|
39
54
|
serializeFirestoreTypes: () => serializeFirestoreTypes
|
|
40
55
|
});
|
|
41
|
-
function isTaggedValue(value) {
|
|
42
|
-
if (value === null || typeof value !== "object") return false;
|
|
43
|
-
const tag = value[SERIALIZATION_TAG];
|
|
44
|
-
return typeof tag === "string" && KNOWN_TYPES.has(tag);
|
|
45
|
-
}
|
|
46
56
|
function isTimestamp(value) {
|
|
47
57
|
return value instanceof import_firestore.Timestamp;
|
|
48
58
|
}
|
|
@@ -143,13 +153,13 @@ function deserializeValue(value, db) {
|
|
|
143
153
|
}
|
|
144
154
|
return result;
|
|
145
155
|
}
|
|
146
|
-
var import_firestore,
|
|
156
|
+
var import_firestore, _docRefWarned;
|
|
147
157
|
var init_serialization = __esm({
|
|
148
158
|
"src/serialization.ts"() {
|
|
149
159
|
"use strict";
|
|
150
160
|
import_firestore = require("@google-cloud/firestore");
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
init_serialization_tag();
|
|
162
|
+
init_serialization_tag();
|
|
153
163
|
_docRefWarned = false;
|
|
154
164
|
}
|
|
155
165
|
});
|
|
@@ -159,9 +169,15 @@ var cloudflare_exports = {};
|
|
|
159
169
|
__export(cloudflare_exports, {
|
|
160
170
|
DORPCBackend: () => DORPCBackend,
|
|
161
171
|
FiregraphDO: () => FiregraphDO,
|
|
172
|
+
META_EDGE_TYPE: () => META_EDGE_TYPE,
|
|
173
|
+
META_NODE_TYPE: () => META_NODE_TYPE,
|
|
162
174
|
buildDOSchemaStatements: () => buildDOSchemaStatements,
|
|
163
175
|
createDOClient: () => createDOClient,
|
|
164
|
-
|
|
176
|
+
createMergedRegistry: () => createMergedRegistry,
|
|
177
|
+
createRegistry: () => createRegistry,
|
|
178
|
+
createSiblingClient: () => createSiblingClient,
|
|
179
|
+
deleteField: () => deleteField,
|
|
180
|
+
generateId: () => generateId
|
|
165
181
|
});
|
|
166
182
|
module.exports = __toCommonJS(cloudflare_exports);
|
|
167
183
|
|
|
@@ -234,6 +250,296 @@ var BUILTIN_FIELDS = /* @__PURE__ */ new Set([
|
|
|
234
250
|
]);
|
|
235
251
|
var SHARD_SEPARATOR = ":";
|
|
236
252
|
|
|
253
|
+
// src/internal/sqlite-data-ops.ts
|
|
254
|
+
var FIRESTORE_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
255
|
+
"Timestamp",
|
|
256
|
+
"GeoPoint",
|
|
257
|
+
"VectorValue",
|
|
258
|
+
"DocumentReference",
|
|
259
|
+
"FieldValue"
|
|
260
|
+
]);
|
|
261
|
+
function isFirestoreSpecialType(value) {
|
|
262
|
+
const ctorName = value.constructor?.name;
|
|
263
|
+
if (ctorName && FIRESTORE_TYPE_NAMES.has(ctorName)) return ctorName;
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
var JSON_PATH_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
267
|
+
function validateJsonPathKey(key, backendLabel) {
|
|
268
|
+
if (key.length === 0) {
|
|
269
|
+
throw new FiregraphError(
|
|
270
|
+
`${backendLabel}: empty JSON path component is not allowed`,
|
|
271
|
+
"INVALID_QUERY"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
if (!JSON_PATH_KEY_RE.test(key)) {
|
|
275
|
+
throw new FiregraphError(
|
|
276
|
+
`${backendLabel}: data field path component "${key}" is not a safe JSON-path identifier. Allowed pattern: /^[A-Za-z_][A-Za-z0-9_-]*$/. Use replaceNode/replaceEdge (full-data overwrite) for keys with reserved characters (whitespace, dots, brackets, quotes, etc.).`,
|
|
277
|
+
"INVALID_QUERY"
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function jsonBind(value, backendLabel) {
|
|
282
|
+
if (value === void 0) return "null";
|
|
283
|
+
if (value !== null && typeof value === "object") {
|
|
284
|
+
const firestoreType = isFirestoreSpecialType(value);
|
|
285
|
+
if (firestoreType) {
|
|
286
|
+
throw new FiregraphError(
|
|
287
|
+
`${backendLabel} cannot persist a Firestore ${firestoreType} value. Convert to a primitive before writing (e.g. \`ts.toMillis()\` for Timestamp).`,
|
|
288
|
+
"INVALID_ARGUMENT"
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return JSON.stringify(value);
|
|
293
|
+
}
|
|
294
|
+
function compileDataOpsExpr(ops, base, params, backendLabel) {
|
|
295
|
+
if (ops.length === 0) return null;
|
|
296
|
+
const deletes = [];
|
|
297
|
+
const sets = [];
|
|
298
|
+
for (const op of ops) (op.delete ? deletes : sets).push(op);
|
|
299
|
+
let expr = base;
|
|
300
|
+
if (deletes.length > 0) {
|
|
301
|
+
const placeholders = deletes.map(() => "?").join(", ");
|
|
302
|
+
expr = `json_remove(${expr}, ${placeholders})`;
|
|
303
|
+
for (const op of deletes) {
|
|
304
|
+
for (const seg of op.path) validateJsonPathKey(seg, backendLabel);
|
|
305
|
+
params.push(`$.${op.path.join(".")}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (sets.length > 0) {
|
|
309
|
+
const pieces = sets.map(() => "?, json(?)").join(", ");
|
|
310
|
+
expr = `json_set(${expr}, ${pieces})`;
|
|
311
|
+
for (const op of sets) {
|
|
312
|
+
for (const seg of op.path) validateJsonPathKey(seg, backendLabel);
|
|
313
|
+
params.push(`$.${op.path.join(".")}`);
|
|
314
|
+
params.push(jsonBind(op.value, backendLabel));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return expr;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/internal/sqlite-payload-guard.ts
|
|
321
|
+
init_serialization_tag();
|
|
322
|
+
|
|
323
|
+
// src/internal/write-plan.ts
|
|
324
|
+
init_serialization_tag();
|
|
325
|
+
var DELETE_FIELD = /* @__PURE__ */ Symbol.for("firegraph.deleteField");
|
|
326
|
+
function deleteField() {
|
|
327
|
+
return DELETE_FIELD;
|
|
328
|
+
}
|
|
329
|
+
function isDeleteSentinel(value) {
|
|
330
|
+
return value === DELETE_FIELD;
|
|
331
|
+
}
|
|
332
|
+
var FIRESTORE_TERMINAL_CTOR = /* @__PURE__ */ new Set([
|
|
333
|
+
"Timestamp",
|
|
334
|
+
"GeoPoint",
|
|
335
|
+
"VectorValue",
|
|
336
|
+
"DocumentReference",
|
|
337
|
+
"FieldValue",
|
|
338
|
+
"NumericIncrementTransform",
|
|
339
|
+
"ArrayUnionTransform",
|
|
340
|
+
"ArrayRemoveTransform",
|
|
341
|
+
"ServerTimestampTransform",
|
|
342
|
+
"DeleteTransform"
|
|
343
|
+
]);
|
|
344
|
+
function isTerminalValue(value) {
|
|
345
|
+
if (value === null) return true;
|
|
346
|
+
const t = typeof value;
|
|
347
|
+
if (t !== "object") return true;
|
|
348
|
+
if (Array.isArray(value)) return true;
|
|
349
|
+
if (isTaggedValue(value)) return true;
|
|
350
|
+
const proto = Object.getPrototypeOf(value);
|
|
351
|
+
if (proto === null || proto === Object.prototype) return false;
|
|
352
|
+
const ctor = value.constructor;
|
|
353
|
+
if (ctor && typeof ctor.name === "string" && FIRESTORE_TERMINAL_CTOR.has(ctor.name)) return true;
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
var SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
357
|
+
function assertUpdatePayloadExclusive(update) {
|
|
358
|
+
if (update.replaceData !== void 0 && update.dataOps !== void 0) {
|
|
359
|
+
throw new Error(
|
|
360
|
+
"firegraph: UpdatePayload cannot specify both `replaceData` and `dataOps`. Use one or the other \u2014 `replaceData` is the migration-write-back form, `dataOps` is the standard partial-update form."
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function assertNoDeleteSentinels(data, callerLabel) {
|
|
365
|
+
walkForDeleteSentinels(data, [], { kind: "root" }, ({ path }) => {
|
|
366
|
+
const where = path.length === 0 ? "<root>" : path.map((p) => JSON.stringify(p)).join(" > ");
|
|
367
|
+
throw new Error(
|
|
368
|
+
`firegraph: ${callerLabel} payload contains a deleteField() sentinel at ${where}. deleteField() is only valid inside updateNode/updateEdge \u2014 full-data writes (put*, replace*) cannot delete individual fields. Use updateNode with a deleteField() value, or omit the field from the replace payload.`
|
|
369
|
+
);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function walkForDeleteSentinels(node, path, parent, visit) {
|
|
373
|
+
if (node === null || node === void 0) return;
|
|
374
|
+
if (isDeleteSentinel(node)) {
|
|
375
|
+
visit({ path, parent });
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (typeof node !== "object") return;
|
|
379
|
+
if (isTaggedValue(node)) return;
|
|
380
|
+
if (Array.isArray(node)) {
|
|
381
|
+
for (let i = 0; i < node.length; i++) {
|
|
382
|
+
walkForDeleteSentinels(node[i], [...path, String(i)], { kind: "array", index: i }, visit);
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const proto = Object.getPrototypeOf(node);
|
|
387
|
+
if (proto !== null && proto !== Object.prototype) return;
|
|
388
|
+
const obj = node;
|
|
389
|
+
for (const key of Object.keys(obj)) {
|
|
390
|
+
walkForDeleteSentinels(obj[key], [...path, key], { kind: "object" }, visit);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function assertSafePath(path) {
|
|
394
|
+
for (const seg of path) {
|
|
395
|
+
if (!SAFE_KEY_RE.test(seg)) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`firegraph: unsafe object key ${JSON.stringify(seg)} at path ${path.map((p) => JSON.stringify(p)).join(" > ")}. Keys used inside update payloads must match /^[A-Za-z_][A-Za-z0-9_-]*$/ so they can be embedded safely in SQLite JSON paths.`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function flattenPatch(data) {
|
|
403
|
+
const ops = [];
|
|
404
|
+
walk(data, [], ops);
|
|
405
|
+
return ops;
|
|
406
|
+
}
|
|
407
|
+
function assertNoDeleteSentinelsInArrayValue(arr, arrayPath) {
|
|
408
|
+
walkForDeleteSentinels(arr, arrayPath, { kind: "root" }, ({ parent }) => {
|
|
409
|
+
const arrayPathStr = arrayPath.length === 0 ? "<root>" : arrayPath.map((p) => JSON.stringify(p)).join(" > ");
|
|
410
|
+
if (parent.kind === "array") {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`firegraph: deleteField() sentinel at index ${parent.index} inside an array at path ${arrayPathStr}. Arrays are terminal in update payloads (replaced as a unit), so the sentinel would be silently dropped by JSON serialization. To remove the field entirely, pass deleteField() in place of the whole array.`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
throw new Error(
|
|
416
|
+
`firegraph: deleteField() sentinel inside an array element at path ${arrayPathStr}. Arrays are terminal in update payloads \u2014 the sentinel would be silently dropped by JSON serialization.`
|
|
417
|
+
);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
function walk(node, path, out) {
|
|
421
|
+
if (node === void 0) return;
|
|
422
|
+
if (isDeleteSentinel(node)) {
|
|
423
|
+
if (path.length === 0) {
|
|
424
|
+
throw new Error("firegraph: deleteField() cannot be the entire update payload.");
|
|
425
|
+
}
|
|
426
|
+
assertSafePath(path);
|
|
427
|
+
out.push({ path: [...path], value: void 0, delete: true });
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (isTerminalValue(node)) {
|
|
431
|
+
if (path.length === 0) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
"firegraph: update payload must be a plain object. Got " + (node === null ? "null" : Array.isArray(node) ? "array" : typeof node) + "."
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (Array.isArray(node)) {
|
|
437
|
+
assertNoDeleteSentinelsInArrayValue(node, path);
|
|
438
|
+
}
|
|
439
|
+
assertSafePath(path);
|
|
440
|
+
out.push({ path: [...path], value: node, delete: false });
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const obj = node;
|
|
444
|
+
const keys = Object.keys(obj);
|
|
445
|
+
if (keys.length === 0) {
|
|
446
|
+
if (path.length > 0) {
|
|
447
|
+
assertSafePath(path);
|
|
448
|
+
out.push({ path: [...path], value: {}, delete: false });
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
for (const key of keys) {
|
|
453
|
+
if (key === SERIALIZATION_TAG) {
|
|
454
|
+
const where = path.length === 0 ? "<root>" : path.map((p) => JSON.stringify(p)).join(" > ");
|
|
455
|
+
throw new Error(
|
|
456
|
+
`firegraph: update payload contains a literal \`${SERIALIZATION_TAG}\` key at ${where}. That key is reserved for firegraph's serialization envelope and cannot appear on a plain object in user data. Use a different field name, or pass a recognized tagged value through replaceNode/replaceEdge instead.`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
walk(obj[key], [...path, key], out);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/internal/sqlite-payload-guard.ts
|
|
464
|
+
var FIRESTORE_TYPE_NAMES2 = /* @__PURE__ */ new Set([
|
|
465
|
+
"Timestamp",
|
|
466
|
+
"GeoPoint",
|
|
467
|
+
"VectorValue",
|
|
468
|
+
"DocumentReference",
|
|
469
|
+
"FieldValue"
|
|
470
|
+
]);
|
|
471
|
+
function assertJsonSafePayload(data, label) {
|
|
472
|
+
walk2(data, [], label);
|
|
473
|
+
}
|
|
474
|
+
function walk2(node, path, label) {
|
|
475
|
+
if (node === null || node === void 0) return;
|
|
476
|
+
if (isDeleteSentinel(node)) {
|
|
477
|
+
throw new FiregraphError(
|
|
478
|
+
`${label} backend cannot persist a deleteField() sentinel inside a full-data payload (replaceNode/replaceEdge or first-insert). The sentinel is only valid inside an updateNode/updateEdge dataOps patch. Path: ${formatPath(path)}.`,
|
|
479
|
+
"INVALID_ARGUMENT"
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
const t = typeof node;
|
|
483
|
+
if (t === "symbol" || t === "function") {
|
|
484
|
+
throw new FiregraphError(
|
|
485
|
+
`${label} backend cannot persist a value of type ${t}. JSON.stringify drops it silently. Path: ${formatPath(path)}.`,
|
|
486
|
+
"INVALID_ARGUMENT"
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
if (t === "bigint") {
|
|
490
|
+
throw new FiregraphError(
|
|
491
|
+
`${label} backend cannot persist a value of type bigint. JSON.stringify cannot serialize this type (throws TypeError). Path: ${formatPath(path)}.`,
|
|
492
|
+
"INVALID_ARGUMENT"
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
if (t !== "object") return;
|
|
496
|
+
if (Array.isArray(node)) {
|
|
497
|
+
for (let i = 0; i < node.length; i++) {
|
|
498
|
+
walk2(node[i], [...path, String(i)], label);
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const obj = node;
|
|
503
|
+
if (Object.prototype.hasOwnProperty.call(obj, SERIALIZATION_TAG)) {
|
|
504
|
+
const tagValue = obj[SERIALIZATION_TAG];
|
|
505
|
+
throw new FiregraphError(
|
|
506
|
+
`${label} backend cannot persist an object with a \`${SERIALIZATION_TAG}\` key (value: ${formatTagValue(tagValue)}). Recognised tags are valid only on the Firestore backend (migration-sandbox output); a literal \`${SERIALIZATION_TAG}\` field in user data is reserved and not allowed. Path: ${formatPath(path)}.`,
|
|
507
|
+
"INVALID_ARGUMENT"
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
const proto = Object.getPrototypeOf(node);
|
|
511
|
+
if (proto !== null && proto !== Object.prototype) {
|
|
512
|
+
const ctor = node.constructor;
|
|
513
|
+
const ctorName = ctor && typeof ctor.name === "string" ? ctor.name : "<anonymous>";
|
|
514
|
+
if (FIRESTORE_TYPE_NAMES2.has(ctorName)) {
|
|
515
|
+
throw new FiregraphError(
|
|
516
|
+
`${label} backend cannot persist a Firestore ${ctorName} value. Convert to a primitive before writing (e.g. \`ts.toMillis()\` for Timestamp, \`{lat,lng}\` for GeoPoint). Path: ${formatPath(path)}.`,
|
|
517
|
+
"INVALID_ARGUMENT"
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
if (node instanceof Date) return;
|
|
521
|
+
throw new FiregraphError(
|
|
522
|
+
`${label} backend cannot persist a class instance of type ${ctorName}. Only plain objects, arrays, and primitives round-trip safely through JSON storage. Path: ${formatPath(path)}.`,
|
|
523
|
+
"INVALID_ARGUMENT"
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
for (const key of Object.keys(obj)) {
|
|
527
|
+
walk2(obj[key], [...path, key], label);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function formatPath(path) {
|
|
531
|
+
return path.length === 0 ? "<root>" : path.map((p) => JSON.stringify(p)).join(" > ");
|
|
532
|
+
}
|
|
533
|
+
function formatTagValue(value) {
|
|
534
|
+
if (value === null) return "null";
|
|
535
|
+
if (value === void 0) return "undefined";
|
|
536
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
537
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
538
|
+
return String(value);
|
|
539
|
+
}
|
|
540
|
+
return typeof value;
|
|
541
|
+
}
|
|
542
|
+
|
|
237
543
|
// src/timestamp.ts
|
|
238
544
|
var GraphTimestampImpl = class _GraphTimestampImpl {
|
|
239
545
|
constructor(seconds, nanoseconds) {
|
|
@@ -273,7 +579,7 @@ var DEFAULT_CORE_INDEXES = Object.freeze([
|
|
|
273
579
|
|
|
274
580
|
// src/internal/sqlite-index-ddl.ts
|
|
275
581
|
var IDENT_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
276
|
-
var
|
|
582
|
+
var JSON_PATH_KEY_RE2 = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
277
583
|
function quoteIdent(name) {
|
|
278
584
|
if (!IDENT_RE.test(name)) {
|
|
279
585
|
throw new FiregraphError(
|
|
@@ -321,7 +627,7 @@ function compileFieldExpr(path, fieldToColumn) {
|
|
|
321
627
|
const suffix = path.slice(5);
|
|
322
628
|
const parts = suffix.split(".");
|
|
323
629
|
for (const part of parts) {
|
|
324
|
-
if (!
|
|
630
|
+
if (!JSON_PATH_KEY_RE2.test(part)) {
|
|
325
631
|
throw new FiregraphError(
|
|
326
632
|
`IndexSpec data path "${path}" has invalid component "${part}". Each component must match /^[A-Za-z_][A-Za-z0-9_-]*$/.`,
|
|
327
633
|
"INVALID_INDEX"
|
|
@@ -416,6 +722,8 @@ function buildDOSchemaStatements(table, options = {}) {
|
|
|
416
722
|
}
|
|
417
723
|
|
|
418
724
|
// src/cloudflare/sql.ts
|
|
725
|
+
var DO_BACKEND_LABEL = "DO SQLite";
|
|
726
|
+
var DO_BACKEND_ERR_LABEL = "DO SQLite backend";
|
|
419
727
|
function compileFieldRef(field) {
|
|
420
728
|
const column = DO_FIELD_TO_COLUMN[field];
|
|
421
729
|
if (column) {
|
|
@@ -424,7 +732,7 @@ function compileFieldRef(field) {
|
|
|
424
732
|
if (field.startsWith("data.")) {
|
|
425
733
|
const suffix = field.slice(5);
|
|
426
734
|
for (const part of suffix.split(".")) {
|
|
427
|
-
validateJsonPathKey(part);
|
|
735
|
+
validateJsonPathKey(part, DO_BACKEND_ERR_LABEL);
|
|
428
736
|
}
|
|
429
737
|
return { expr: `json_extract("data", '$.${suffix}')` };
|
|
430
738
|
}
|
|
@@ -436,18 +744,6 @@ function compileFieldRef(field) {
|
|
|
436
744
|
"INVALID_QUERY"
|
|
437
745
|
);
|
|
438
746
|
}
|
|
439
|
-
var FIRESTORE_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
440
|
-
"Timestamp",
|
|
441
|
-
"GeoPoint",
|
|
442
|
-
"VectorValue",
|
|
443
|
-
"DocumentReference",
|
|
444
|
-
"FieldValue"
|
|
445
|
-
]);
|
|
446
|
-
function isFirestoreSpecialType(value) {
|
|
447
|
-
const ctorName = value.constructor?.name;
|
|
448
|
-
if (ctorName && FIRESTORE_TYPE_NAMES.has(ctorName)) return ctorName;
|
|
449
|
-
return null;
|
|
450
|
-
}
|
|
451
747
|
function bindValue(value) {
|
|
452
748
|
if (value === null || value === void 0) return null;
|
|
453
749
|
if (typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
@@ -466,21 +762,6 @@ function bindValue(value) {
|
|
|
466
762
|
}
|
|
467
763
|
return String(value);
|
|
468
764
|
}
|
|
469
|
-
var JSON_PATH_KEY_RE2 = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
470
|
-
function validateJsonPathKey(key) {
|
|
471
|
-
if (key.length === 0) {
|
|
472
|
-
throw new FiregraphError(
|
|
473
|
-
"DO SQLite backend: empty JSON path component is not allowed",
|
|
474
|
-
"INVALID_QUERY"
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
if (!JSON_PATH_KEY_RE2.test(key)) {
|
|
478
|
-
throw new FiregraphError(
|
|
479
|
-
`DO SQLite backend: data field path component "${key}" is not a safe JSON-path identifier. Allowed pattern: /^[A-Za-z_][A-Za-z0-9_-]*$/. Use replaceData (full-data overwrite) for keys with reserved characters (whitespace, dots, brackets, quotes, etc.).`,
|
|
480
|
-
"INVALID_QUERY"
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
765
|
function compileFilter(filter, params) {
|
|
485
766
|
const { expr } = compileFieldRef(filter.field);
|
|
486
767
|
switch (filter.op) {
|
|
@@ -567,11 +848,27 @@ function compileDOSelectByDocId(table, docId) {
|
|
|
567
848
|
params: [docId]
|
|
568
849
|
};
|
|
569
850
|
}
|
|
570
|
-
function compileDOSet(table, docId, record, nowMillis) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
851
|
+
function compileDOSet(table, docId, record, nowMillis, mode) {
|
|
852
|
+
assertJsonSafePayload(record.data, DO_BACKEND_LABEL);
|
|
853
|
+
if (mode === "replace") {
|
|
854
|
+
const sql2 = `INSERT OR REPLACE INTO ${quoteDOIdent(table)} (
|
|
855
|
+
doc_id, a_type, a_uid, axb_type, b_type, b_uid, data, v, created_at, updated_at
|
|
856
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
|
857
|
+
const params = [
|
|
858
|
+
docId,
|
|
859
|
+
record.aType,
|
|
860
|
+
record.aUid,
|
|
861
|
+
record.axbType,
|
|
862
|
+
record.bType,
|
|
863
|
+
record.bUid,
|
|
864
|
+
JSON.stringify(record.data ?? {}),
|
|
865
|
+
record.v ?? null,
|
|
866
|
+
nowMillis,
|
|
867
|
+
nowMillis
|
|
868
|
+
];
|
|
869
|
+
return { sql: sql2, params };
|
|
870
|
+
}
|
|
871
|
+
const insertParams = [
|
|
575
872
|
docId,
|
|
576
873
|
record.aType,
|
|
577
874
|
record.aUid,
|
|
@@ -583,22 +880,44 @@ function compileDOSet(table, docId, record, nowMillis) {
|
|
|
583
880
|
nowMillis,
|
|
584
881
|
nowMillis
|
|
585
882
|
];
|
|
586
|
-
|
|
883
|
+
const ops = flattenPatch(record.data ?? {});
|
|
884
|
+
const updateParams = [];
|
|
885
|
+
const dataExpr = compileDataOpsExpr(ops, `COALESCE("data", '{}')`, updateParams, DO_BACKEND_ERR_LABEL) ?? `COALESCE("data", '{}')`;
|
|
886
|
+
const sql = `INSERT INTO ${quoteDOIdent(table)} (
|
|
887
|
+
doc_id, a_type, a_uid, axb_type, b_type, b_uid, data, v, created_at, updated_at
|
|
888
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
889
|
+
ON CONFLICT(doc_id) DO UPDATE SET
|
|
890
|
+
"a_type" = excluded."a_type",
|
|
891
|
+
"a_uid" = excluded."a_uid",
|
|
892
|
+
"axb_type" = excluded."axb_type",
|
|
893
|
+
"b_type" = excluded."b_type",
|
|
894
|
+
"b_uid" = excluded."b_uid",
|
|
895
|
+
"data" = ${dataExpr},
|
|
896
|
+
"v" = COALESCE(excluded."v", "v"),
|
|
897
|
+
"created_at" = excluded."created_at",
|
|
898
|
+
"updated_at" = excluded."updated_at"`;
|
|
899
|
+
return { sql, params: [...insertParams, ...updateParams] };
|
|
587
900
|
}
|
|
588
901
|
function compileDOUpdate(table, docId, update, nowMillis) {
|
|
902
|
+
assertUpdatePayloadExclusive(update);
|
|
589
903
|
const setClauses = [];
|
|
590
904
|
const params = [];
|
|
591
905
|
if (update.replaceData) {
|
|
906
|
+
assertJsonSafePayload(update.replaceData, DO_BACKEND_LABEL);
|
|
592
907
|
setClauses.push(`"data" = ?`);
|
|
593
908
|
params.push(JSON.stringify(update.replaceData));
|
|
594
|
-
} else if (update.
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
params
|
|
909
|
+
} else if (update.dataOps && update.dataOps.length > 0) {
|
|
910
|
+
for (const op of update.dataOps) {
|
|
911
|
+
if (!op.delete) assertJsonSafePayload(op.value, DO_BACKEND_LABEL);
|
|
912
|
+
}
|
|
913
|
+
const expr = compileDataOpsExpr(
|
|
914
|
+
update.dataOps,
|
|
915
|
+
`COALESCE("data", '{}')`,
|
|
916
|
+
params,
|
|
917
|
+
DO_BACKEND_ERR_LABEL
|
|
918
|
+
);
|
|
919
|
+
if (expr !== null) {
|
|
920
|
+
setClauses.push(`"data" = ${expr}`);
|
|
602
921
|
}
|
|
603
922
|
}
|
|
604
923
|
if (update.v !== void 0) {
|
|
@@ -694,8 +1013,8 @@ var DORPCBatchBackend = class {
|
|
|
694
1013
|
this.getStub = getStub;
|
|
695
1014
|
}
|
|
696
1015
|
ops = [];
|
|
697
|
-
setDoc(docId, record) {
|
|
698
|
-
this.ops.push({ kind: "set", docId, record });
|
|
1016
|
+
setDoc(docId, record, mode) {
|
|
1017
|
+
this.ops.push({ kind: "set", docId, record, mode });
|
|
699
1018
|
}
|
|
700
1019
|
updateDoc(docId, update) {
|
|
701
1020
|
this.ops.push({ kind: "update", docId, update });
|
|
@@ -745,8 +1064,8 @@ var DORPCBackend = class _DORPCBackend {
|
|
|
745
1064
|
return wires.map(hydrateDORecord);
|
|
746
1065
|
}
|
|
747
1066
|
// --- Writes ---
|
|
748
|
-
async setDoc(docId, record) {
|
|
749
|
-
return this.stub._fgSetDoc(docId, record);
|
|
1067
|
+
async setDoc(docId, record, mode) {
|
|
1068
|
+
return this.stub._fgSetDoc(docId, record, mode);
|
|
750
1069
|
}
|
|
751
1070
|
async updateDoc(docId, update) {
|
|
752
1071
|
return this.stub._fgUpdateDoc(docId, update);
|
|
@@ -875,6 +1194,19 @@ var GraphBatchImpl = class {
|
|
|
875
1194
|
this.scopePath = scopePath;
|
|
876
1195
|
}
|
|
877
1196
|
async putNode(aType, uid, data) {
|
|
1197
|
+
this.writeNode(aType, uid, data, "merge");
|
|
1198
|
+
}
|
|
1199
|
+
async putEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
1200
|
+
this.writeEdge(aType, aUid, axbType, bType, bUid, data, "merge");
|
|
1201
|
+
}
|
|
1202
|
+
async replaceNode(aType, uid, data) {
|
|
1203
|
+
this.writeNode(aType, uid, data, "replace");
|
|
1204
|
+
}
|
|
1205
|
+
async replaceEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
1206
|
+
this.writeEdge(aType, aUid, axbType, bType, bUid, data, "replace");
|
|
1207
|
+
}
|
|
1208
|
+
writeNode(aType, uid, data, mode) {
|
|
1209
|
+
assertNoDeleteSentinels(data, mode === "replace" ? "replaceNode" : "putNode");
|
|
878
1210
|
if (this.registry) {
|
|
879
1211
|
this.registry.validate(aType, NODE_RELATION, aType, data, this.scopePath);
|
|
880
1212
|
}
|
|
@@ -886,9 +1218,10 @@ var GraphBatchImpl = class {
|
|
|
886
1218
|
record.v = entry.schemaVersion;
|
|
887
1219
|
}
|
|
888
1220
|
}
|
|
889
|
-
this.backend.setDoc(docId, record);
|
|
1221
|
+
this.backend.setDoc(docId, record, mode);
|
|
890
1222
|
}
|
|
891
|
-
|
|
1223
|
+
writeEdge(aType, aUid, axbType, bType, bUid, data, mode) {
|
|
1224
|
+
assertNoDeleteSentinels(data, mode === "replace" ? "replaceEdge" : "putEdge");
|
|
892
1225
|
if (this.registry) {
|
|
893
1226
|
this.registry.validate(aType, axbType, bType, data, this.scopePath);
|
|
894
1227
|
}
|
|
@@ -900,11 +1233,15 @@ var GraphBatchImpl = class {
|
|
|
900
1233
|
record.v = entry.schemaVersion;
|
|
901
1234
|
}
|
|
902
1235
|
}
|
|
903
|
-
this.backend.setDoc(docId, record);
|
|
1236
|
+
this.backend.setDoc(docId, record, mode);
|
|
904
1237
|
}
|
|
905
1238
|
async updateNode(uid, data) {
|
|
906
1239
|
const docId = computeNodeDocId(uid);
|
|
907
|
-
this.backend.updateDoc(docId, {
|
|
1240
|
+
this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
|
|
1241
|
+
}
|
|
1242
|
+
async updateEdge(aUid, axbType, bUid, data) {
|
|
1243
|
+
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
1244
|
+
this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
|
|
908
1245
|
}
|
|
909
1246
|
async removeNode(uid) {
|
|
910
1247
|
const docId = computeNodeDocId(uid);
|
|
@@ -923,23 +1260,29 @@ var GraphBatchImpl = class {
|
|
|
923
1260
|
var import_node_crypto3 = require("crypto");
|
|
924
1261
|
|
|
925
1262
|
// src/json-schema.ts
|
|
926
|
-
var
|
|
927
|
-
var
|
|
928
|
-
var ajv = new import_ajv.default({ allErrors: true, strict: false });
|
|
929
|
-
(0, import_ajv_formats.default)(ajv);
|
|
1263
|
+
var import_json_schema = require("@cfworker/json-schema");
|
|
1264
|
+
var MAX_RENDERED_ERRORS = 20;
|
|
930
1265
|
function compileSchema(schema, label) {
|
|
931
|
-
const
|
|
1266
|
+
const validator = new import_json_schema.Validator(schema, "2020-12", false);
|
|
932
1267
|
return (data) => {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const
|
|
1268
|
+
const result = validator.validate(data);
|
|
1269
|
+
if (!result.valid) {
|
|
1270
|
+
const total = result.errors.length;
|
|
1271
|
+
const head = result.errors.slice(0, MAX_RENDERED_ERRORS).map(formatError).join("; ");
|
|
1272
|
+
const overflow = total > MAX_RENDERED_ERRORS ? ` (+${total - MAX_RENDERED_ERRORS} more)` : "";
|
|
936
1273
|
throw new ValidationError(
|
|
937
|
-
`Data validation failed${label ? " for " + label : ""}: ${
|
|
938
|
-
errors
|
|
1274
|
+
`Data validation failed${label ? " for " + label : ""}: ${head}${overflow}`,
|
|
1275
|
+
result.errors
|
|
939
1276
|
);
|
|
940
1277
|
}
|
|
941
1278
|
};
|
|
942
1279
|
}
|
|
1280
|
+
function formatError(err) {
|
|
1281
|
+
const path = err.instanceLocation.replace(/^#/, "") || "/";
|
|
1282
|
+
const keyword = err.keyword ? `[${err.keyword}] ` : "";
|
|
1283
|
+
const detail = err.error ? `: ${keyword}${err.error}` : "";
|
|
1284
|
+
return `${path}${detail}`;
|
|
1285
|
+
}
|
|
943
1286
|
|
|
944
1287
|
// src/migration.ts
|
|
945
1288
|
async function applyMigrationChain(data, currentVersion, targetVersion, migrations) {
|
|
@@ -1589,8 +1932,11 @@ var BOOTSTRAP_ENTRIES = [
|
|
|
1589
1932
|
description: "Meta-type: defines an edge type"
|
|
1590
1933
|
}
|
|
1591
1934
|
];
|
|
1935
|
+
var _bootstrapRegistry = null;
|
|
1592
1936
|
function createBootstrapRegistry() {
|
|
1593
|
-
|
|
1937
|
+
if (_bootstrapRegistry) return _bootstrapRegistry;
|
|
1938
|
+
_bootstrapRegistry = createRegistry([...BOOTSTRAP_ENTRIES]);
|
|
1939
|
+
return _bootstrapRegistry;
|
|
1594
1940
|
}
|
|
1595
1941
|
function generateDeterministicUid(metaType, name) {
|
|
1596
1942
|
const hash = (0, import_node_crypto3.createHash)("sha256").update(`${metaType}:${name}`).digest("base64url");
|
|
@@ -1844,6 +2190,19 @@ var GraphTransactionImpl = class {
|
|
|
1844
2190
|
return results.map((r) => r.record);
|
|
1845
2191
|
}
|
|
1846
2192
|
async putNode(aType, uid, data) {
|
|
2193
|
+
await this.writeNode(aType, uid, data, "merge");
|
|
2194
|
+
}
|
|
2195
|
+
async putEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
2196
|
+
await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "merge");
|
|
2197
|
+
}
|
|
2198
|
+
async replaceNode(aType, uid, data) {
|
|
2199
|
+
await this.writeNode(aType, uid, data, "replace");
|
|
2200
|
+
}
|
|
2201
|
+
async replaceEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
2202
|
+
await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "replace");
|
|
2203
|
+
}
|
|
2204
|
+
async writeNode(aType, uid, data, mode) {
|
|
2205
|
+
assertNoDeleteSentinels(data, mode === "replace" ? "replaceNode" : "putNode");
|
|
1847
2206
|
if (this.registry) {
|
|
1848
2207
|
this.registry.validate(aType, NODE_RELATION, aType, data, this.scopePath);
|
|
1849
2208
|
}
|
|
@@ -1855,9 +2214,10 @@ var GraphTransactionImpl = class {
|
|
|
1855
2214
|
record.v = entry.schemaVersion;
|
|
1856
2215
|
}
|
|
1857
2216
|
}
|
|
1858
|
-
await this.backend.setDoc(docId, record);
|
|
2217
|
+
await this.backend.setDoc(docId, record, mode);
|
|
1859
2218
|
}
|
|
1860
|
-
async
|
|
2219
|
+
async writeEdge(aType, aUid, axbType, bType, bUid, data, mode) {
|
|
2220
|
+
assertNoDeleteSentinels(data, mode === "replace" ? "replaceEdge" : "putEdge");
|
|
1861
2221
|
if (this.registry) {
|
|
1862
2222
|
this.registry.validate(aType, axbType, bType, data, this.scopePath);
|
|
1863
2223
|
}
|
|
@@ -1869,11 +2229,15 @@ var GraphTransactionImpl = class {
|
|
|
1869
2229
|
record.v = entry.schemaVersion;
|
|
1870
2230
|
}
|
|
1871
2231
|
}
|
|
1872
|
-
await this.backend.setDoc(docId, record);
|
|
2232
|
+
await this.backend.setDoc(docId, record, mode);
|
|
1873
2233
|
}
|
|
1874
2234
|
async updateNode(uid, data) {
|
|
1875
2235
|
const docId = computeNodeDocId(uid);
|
|
1876
|
-
await this.backend.updateDoc(docId, {
|
|
2236
|
+
await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
|
|
2237
|
+
}
|
|
2238
|
+
async updateEdge(aUid, axbType, bUid, data) {
|
|
2239
|
+
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
2240
|
+
await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
|
|
1877
2241
|
}
|
|
1878
2242
|
async removeNode(uid) {
|
|
1879
2243
|
const docId = computeNodeDocId(uid);
|
|
@@ -2072,6 +2436,19 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
2072
2436
|
// GraphWriter
|
|
2073
2437
|
// ---------------------------------------------------------------------------
|
|
2074
2438
|
async putNode(aType, uid, data) {
|
|
2439
|
+
await this.writeNode(aType, uid, data, "merge");
|
|
2440
|
+
}
|
|
2441
|
+
async putEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
2442
|
+
await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "merge");
|
|
2443
|
+
}
|
|
2444
|
+
async replaceNode(aType, uid, data) {
|
|
2445
|
+
await this.writeNode(aType, uid, data, "replace");
|
|
2446
|
+
}
|
|
2447
|
+
async replaceEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
2448
|
+
await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "replace");
|
|
2449
|
+
}
|
|
2450
|
+
async writeNode(aType, uid, data, mode) {
|
|
2451
|
+
assertNoDeleteSentinels(data, mode === "replace" ? "replaceNode" : "putNode");
|
|
2075
2452
|
const registry = this.getRegistryForType(aType);
|
|
2076
2453
|
if (registry) {
|
|
2077
2454
|
registry.validate(aType, NODE_RELATION, aType, data, this.backend.scopePath);
|
|
@@ -2085,9 +2462,10 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
2085
2462
|
record.v = entry.schemaVersion;
|
|
2086
2463
|
}
|
|
2087
2464
|
}
|
|
2088
|
-
await backend.setDoc(docId, record);
|
|
2465
|
+
await backend.setDoc(docId, record, mode);
|
|
2089
2466
|
}
|
|
2090
|
-
async
|
|
2467
|
+
async writeEdge(aType, aUid, axbType, bType, bUid, data, mode) {
|
|
2468
|
+
assertNoDeleteSentinels(data, mode === "replace" ? "replaceEdge" : "putEdge");
|
|
2091
2469
|
const registry = this.getRegistryForType(aType);
|
|
2092
2470
|
if (registry) {
|
|
2093
2471
|
registry.validate(aType, axbType, bType, data, this.backend.scopePath);
|
|
@@ -2101,11 +2479,15 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
2101
2479
|
record.v = entry.schemaVersion;
|
|
2102
2480
|
}
|
|
2103
2481
|
}
|
|
2104
|
-
await backend.setDoc(docId, record);
|
|
2482
|
+
await backend.setDoc(docId, record, mode);
|
|
2105
2483
|
}
|
|
2106
2484
|
async updateNode(uid, data) {
|
|
2107
2485
|
const docId = computeNodeDocId(uid);
|
|
2108
|
-
await this.backend.updateDoc(docId, {
|
|
2486
|
+
await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
|
|
2487
|
+
}
|
|
2488
|
+
async updateEdge(aUid, axbType, bUid, data) {
|
|
2489
|
+
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
2490
|
+
await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
|
|
2109
2491
|
}
|
|
2110
2492
|
async removeNode(uid) {
|
|
2111
2493
|
const docId = computeNodeDocId(uid);
|
|
@@ -2423,15 +2805,28 @@ function createSiblingClient(client, siblingRootKey) {
|
|
|
2423
2805
|
}
|
|
2424
2806
|
|
|
2425
2807
|
// src/cloudflare/do.ts
|
|
2808
|
+
var import_cloudflare_workers = require("cloudflare:workers");
|
|
2426
2809
|
var DEFAULT_OPTIONS = {
|
|
2427
2810
|
table: "firegraph",
|
|
2428
2811
|
autoMigrate: true
|
|
2429
2812
|
};
|
|
2430
|
-
var FiregraphDO = class {
|
|
2431
|
-
/**
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2813
|
+
var FiregraphDO = class extends import_cloudflare_workers.DurableObject {
|
|
2814
|
+
/**
|
|
2815
|
+
* @internal — locally-narrowed alias for `this.ctx`, used only by
|
|
2816
|
+
* FiregraphDO's own SQL helpers. Same runtime object as the inherited
|
|
2817
|
+
* `this.ctx`, but typed as `DurableObjectStateLike` (just `storage.sql`
|
|
2818
|
+
* / `transactionSync` / `blockConcurrencyWhile`) so internal calls
|
|
2819
|
+
* don't trip over workers-types' stricter
|
|
2820
|
+
* `SqlStorage.exec<T extends Record<string, SqlStorageValue>>`
|
|
2821
|
+
* constraint vs the `Record<string, unknown>` rows firegraph passes.
|
|
2822
|
+
*
|
|
2823
|
+
* **Subclasses should use `this.ctx`, not `this.state`.** `this.state`
|
|
2824
|
+
* deliberately exposes only the slice FiregraphDO needs internally;
|
|
2825
|
+
* subclasses that want `id`, `acceptWebSocket`, `setAlarm`, `getAlarm`,
|
|
2826
|
+
* `waitUntil`, `props`, etc. must reach for the inherited `this.ctx`
|
|
2827
|
+
* (the full workers-types `DurableObjectState`).
|
|
2828
|
+
*/
|
|
2829
|
+
state;
|
|
2435
2830
|
/** @internal — table name used by every compiled statement. */
|
|
2436
2831
|
table;
|
|
2437
2832
|
/** @internal — registry consulted by `runSchema` for per-entry indexes. */
|
|
@@ -2439,8 +2834,8 @@ var FiregraphDO = class {
|
|
|
2439
2834
|
/** @internal — overrides `DEFAULT_CORE_INDEXES` when set. */
|
|
2440
2835
|
coreIndexes;
|
|
2441
2836
|
constructor(ctx, env, options = {}) {
|
|
2442
|
-
|
|
2443
|
-
this.
|
|
2837
|
+
super(ctx, env);
|
|
2838
|
+
this.state = ctx;
|
|
2444
2839
|
const table = options.table ?? DEFAULT_OPTIONS.table;
|
|
2445
2840
|
validateDOTableName(table);
|
|
2446
2841
|
this.table = table;
|
|
@@ -2448,7 +2843,7 @@ var FiregraphDO = class {
|
|
|
2448
2843
|
this.coreIndexes = options.coreIndexes;
|
|
2449
2844
|
const autoMigrate = options.autoMigrate ?? DEFAULT_OPTIONS.autoMigrate;
|
|
2450
2845
|
if (autoMigrate) {
|
|
2451
|
-
void this.
|
|
2846
|
+
void this.state.blockConcurrencyWhile(async () => {
|
|
2452
2847
|
this.runSchema();
|
|
2453
2848
|
});
|
|
2454
2849
|
}
|
|
@@ -2473,14 +2868,14 @@ var FiregraphDO = class {
|
|
|
2473
2868
|
// ---------------------------------------------------------------------------
|
|
2474
2869
|
// RPC: writes
|
|
2475
2870
|
// ---------------------------------------------------------------------------
|
|
2476
|
-
async _fgSetDoc(docId, record) {
|
|
2477
|
-
const stmt = compileDOSet(this.table, docId, record, Date.now());
|
|
2871
|
+
async _fgSetDoc(docId, record, mode) {
|
|
2872
|
+
const stmt = compileDOSet(this.table, docId, record, Date.now(), mode);
|
|
2478
2873
|
this.execRun(stmt);
|
|
2479
2874
|
}
|
|
2480
2875
|
async _fgUpdateDoc(docId, update) {
|
|
2481
2876
|
const stmt = compileDOUpdate(this.table, docId, update, Date.now());
|
|
2482
2877
|
const sqlWithReturning = `${stmt.sql} RETURNING "doc_id"`;
|
|
2483
|
-
const rows = this.
|
|
2878
|
+
const rows = this.state.storage.sql.exec(sqlWithReturning, ...stmt.params).toArray();
|
|
2484
2879
|
if (rows.length === 0) {
|
|
2485
2880
|
throw new FiregraphError(`updateDoc: no document found for doc_id=${docId}`, "NOT_FOUND");
|
|
2486
2881
|
}
|
|
@@ -2504,16 +2899,16 @@ var FiregraphDO = class {
|
|
|
2504
2899
|
const statements = ops.map((op) => {
|
|
2505
2900
|
switch (op.kind) {
|
|
2506
2901
|
case "set":
|
|
2507
|
-
return compileDOSet(this.table, op.docId, op.record, now);
|
|
2902
|
+
return compileDOSet(this.table, op.docId, op.record, now, op.mode);
|
|
2508
2903
|
case "update":
|
|
2509
2904
|
return compileDOUpdate(this.table, op.docId, op.update, now);
|
|
2510
2905
|
case "delete":
|
|
2511
2906
|
return compileDODelete(this.table, op.docId);
|
|
2512
2907
|
}
|
|
2513
2908
|
});
|
|
2514
|
-
this.
|
|
2909
|
+
this.state.storage.transactionSync(() => {
|
|
2515
2910
|
for (const stmt of statements) {
|
|
2516
|
-
this.
|
|
2911
|
+
this.state.storage.sql.exec(stmt.sql, ...stmt.params).toArray();
|
|
2517
2912
|
}
|
|
2518
2913
|
});
|
|
2519
2914
|
}
|
|
@@ -2562,9 +2957,9 @@ var FiregraphDO = class {
|
|
|
2562
2957
|
};
|
|
2563
2958
|
}
|
|
2564
2959
|
try {
|
|
2565
|
-
this.
|
|
2960
|
+
this.state.storage.transactionSync(() => {
|
|
2566
2961
|
for (const stmt of statements) {
|
|
2567
|
-
this.
|
|
2962
|
+
this.state.storage.sql.exec(stmt.sql, ...stmt.params).toArray();
|
|
2568
2963
|
}
|
|
2569
2964
|
});
|
|
2570
2965
|
return {
|
|
@@ -2604,9 +2999,9 @@ var FiregraphDO = class {
|
|
|
2604
2999
|
}
|
|
2605
3000
|
const deleteStmts = docIds.map((id) => compileDODelete(this.table, id));
|
|
2606
3001
|
try {
|
|
2607
|
-
this.
|
|
3002
|
+
this.state.storage.transactionSync(() => {
|
|
2608
3003
|
for (const stmt of deleteStmts) {
|
|
2609
|
-
this.
|
|
3004
|
+
this.state.storage.sql.exec(stmt.sql, ...stmt.params).toArray();
|
|
2610
3005
|
}
|
|
2611
3006
|
});
|
|
2612
3007
|
return { deleted: deleteStmts.length, batches: 1, errors: [] };
|
|
@@ -2640,22 +3035,34 @@ var FiregraphDO = class {
|
|
|
2640
3035
|
registry: this.registry
|
|
2641
3036
|
});
|
|
2642
3037
|
for (const sql of statements) {
|
|
2643
|
-
this.
|
|
3038
|
+
this.state.storage.sql.exec(sql).toArray();
|
|
2644
3039
|
}
|
|
2645
3040
|
}
|
|
2646
3041
|
execAll(stmt) {
|
|
2647
|
-
return this.
|
|
3042
|
+
return this.state.storage.sql.exec(stmt.sql, ...stmt.params).toArray();
|
|
2648
3043
|
}
|
|
2649
3044
|
execRun(stmt) {
|
|
2650
|
-
this.
|
|
3045
|
+
this.state.storage.sql.exec(stmt.sql, ...stmt.params).toArray();
|
|
2651
3046
|
}
|
|
2652
3047
|
};
|
|
3048
|
+
|
|
3049
|
+
// src/id.ts
|
|
3050
|
+
var import_nanoid = require("nanoid");
|
|
3051
|
+
function generateId() {
|
|
3052
|
+
return (0, import_nanoid.nanoid)();
|
|
3053
|
+
}
|
|
2653
3054
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2654
3055
|
0 && (module.exports = {
|
|
2655
3056
|
DORPCBackend,
|
|
2656
3057
|
FiregraphDO,
|
|
3058
|
+
META_EDGE_TYPE,
|
|
3059
|
+
META_NODE_TYPE,
|
|
2657
3060
|
buildDOSchemaStatements,
|
|
2658
3061
|
createDOClient,
|
|
2659
|
-
|
|
3062
|
+
createMergedRegistry,
|
|
3063
|
+
createRegistry,
|
|
3064
|
+
createSiblingClient,
|
|
3065
|
+
deleteField,
|
|
3066
|
+
generateId
|
|
2660
3067
|
});
|
|
2661
3068
|
//# sourceMappingURL=index.cjs.map
|