@typicalday/firegraph 0.4.0 → 0.5.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 +114 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/editor/server/index.mjs +755 -44
- package/dist/{index-DR3jF5_b.d.cts → index-B9aodfYD.d.cts} +101 -1
- package/dist/{index-DR3jF5_b.d.ts → index-B9aodfYD.d.ts} +101 -1
- package/dist/index.cjs +794 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +164 -4
- package/dist/index.d.ts +164 -4
- package/dist/index.js +776 -41
- package/dist/index.js.map +1 -1
- package/package.json +27 -24
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
InvalidQueryError: () => InvalidQueryError,
|
|
41
41
|
META_EDGE_TYPE: () => META_EDGE_TYPE,
|
|
42
42
|
META_NODE_TYPE: () => META_NODE_TYPE,
|
|
43
|
+
MigrationError: () => MigrationError,
|
|
43
44
|
NODE_TYPE_SCHEMA: () => NODE_TYPE_SCHEMA,
|
|
44
45
|
NodeNotFoundError: () => NodeNotFoundError,
|
|
45
46
|
QueryClient: () => QueryClient,
|
|
@@ -47,13 +48,17 @@ __export(index_exports, {
|
|
|
47
48
|
QuerySafetyError: () => QuerySafetyError,
|
|
48
49
|
RegistryScopeError: () => RegistryScopeError,
|
|
49
50
|
RegistryViolationError: () => RegistryViolationError,
|
|
51
|
+
SERIALIZATION_TAG: () => SERIALIZATION_TAG,
|
|
50
52
|
TraversalError: () => TraversalError,
|
|
51
53
|
ValidationError: () => ValidationError,
|
|
52
54
|
analyzeQuerySafety: () => analyzeQuerySafety,
|
|
55
|
+
applyMigrationChain: () => applyMigrationChain,
|
|
53
56
|
buildEdgeQueryPlan: () => buildEdgeQueryPlan,
|
|
54
57
|
buildEdgeRecord: () => buildEdgeRecord,
|
|
55
58
|
buildNodeQueryPlan: () => buildNodeQueryPlan,
|
|
56
59
|
buildNodeRecord: () => buildNodeRecord,
|
|
60
|
+
compileMigrationFn: () => compileMigrationFn,
|
|
61
|
+
compileMigrations: () => compileMigrations,
|
|
57
62
|
compileSchema: () => compileSchema,
|
|
58
63
|
computeEdgeDocId: () => computeEdgeDocId,
|
|
59
64
|
computeNodeDocId: () => computeNodeDocId,
|
|
@@ -63,24 +68,33 @@ __export(index_exports, {
|
|
|
63
68
|
createRegistry: () => createRegistry,
|
|
64
69
|
createRegistryFromGraph: () => createRegistryFromGraph,
|
|
65
70
|
createTraversal: () => createTraversal,
|
|
71
|
+
defaultExecutor: () => defaultExecutor,
|
|
66
72
|
defineConfig: () => defineConfig,
|
|
67
73
|
defineViews: () => defineViews,
|
|
74
|
+
deserializeFirestoreTypes: () => deserializeFirestoreTypes,
|
|
75
|
+
destroySandboxWorker: () => destroySandboxWorker,
|
|
68
76
|
discoverEntities: () => discoverEntities,
|
|
69
77
|
generateDeterministicUid: () => generateDeterministicUid,
|
|
70
78
|
generateId: () => generateId,
|
|
71
79
|
generateIndexConfig: () => generateIndexConfig,
|
|
72
80
|
generateTypes: () => generateTypes,
|
|
73
81
|
isAncestorUid: () => isAncestorUid,
|
|
82
|
+
isTaggedValue: () => isTaggedValue,
|
|
74
83
|
jsonSchemaToFieldMeta: () => jsonSchemaToFieldMeta,
|
|
75
84
|
matchScope: () => matchScope,
|
|
76
85
|
matchScopeAny: () => matchScopeAny,
|
|
86
|
+
migrateRecord: () => migrateRecord,
|
|
87
|
+
migrateRecords: () => migrateRecords,
|
|
88
|
+
precompileSource: () => precompileSource,
|
|
77
89
|
resolveAncestorCollection: () => resolveAncestorCollection,
|
|
78
|
-
resolveView: () => resolveView
|
|
90
|
+
resolveView: () => resolveView,
|
|
91
|
+
serializeFirestoreTypes: () => serializeFirestoreTypes,
|
|
92
|
+
validateMigrationChain: () => validateMigrationChain
|
|
79
93
|
});
|
|
80
94
|
module.exports = __toCommonJS(index_exports);
|
|
81
95
|
|
|
82
96
|
// src/client.ts
|
|
83
|
-
var
|
|
97
|
+
var import_firestore5 = require("@google-cloud/firestore");
|
|
84
98
|
|
|
85
99
|
// src/docid.ts
|
|
86
100
|
var import_node_crypto = require("crypto");
|
|
@@ -208,6 +222,12 @@ var RegistryScopeError = class extends FiregraphError {
|
|
|
208
222
|
this.name = "RegistryScopeError";
|
|
209
223
|
}
|
|
210
224
|
};
|
|
225
|
+
var MigrationError = class extends FiregraphError {
|
|
226
|
+
constructor(message) {
|
|
227
|
+
super(message, "MIGRATION_ERROR");
|
|
228
|
+
this.name = "MigrationError";
|
|
229
|
+
}
|
|
230
|
+
};
|
|
211
231
|
|
|
212
232
|
// src/query.ts
|
|
213
233
|
function buildEdgeQueryPlan(params) {
|
|
@@ -398,7 +418,7 @@ function createPipelineQueryAdapter(db, collectionPath) {
|
|
|
398
418
|
}
|
|
399
419
|
|
|
400
420
|
// src/transaction.ts
|
|
401
|
-
var
|
|
421
|
+
var import_firestore3 = require("@google-cloud/firestore");
|
|
402
422
|
|
|
403
423
|
// src/query-safety.ts
|
|
404
424
|
var SAFE_INDEX_PATTERNS = [
|
|
@@ -448,24 +468,248 @@ function analyzeQuerySafety(filters) {
|
|
|
448
468
|
};
|
|
449
469
|
}
|
|
450
470
|
|
|
471
|
+
// src/serialization.ts
|
|
472
|
+
var import_firestore2 = require("@google-cloud/firestore");
|
|
473
|
+
var SERIALIZATION_TAG = "__firegraph_ser__";
|
|
474
|
+
var KNOWN_TYPES = /* @__PURE__ */ new Set(["Timestamp", "GeoPoint", "VectorValue", "DocumentReference"]);
|
|
475
|
+
var _docRefWarned = false;
|
|
476
|
+
function isTaggedValue(value) {
|
|
477
|
+
if (value === null || typeof value !== "object") return false;
|
|
478
|
+
const tag = value[SERIALIZATION_TAG];
|
|
479
|
+
return typeof tag === "string" && KNOWN_TYPES.has(tag);
|
|
480
|
+
}
|
|
481
|
+
function isTimestamp(value) {
|
|
482
|
+
return value instanceof import_firestore2.Timestamp;
|
|
483
|
+
}
|
|
484
|
+
function isGeoPoint(value) {
|
|
485
|
+
return value instanceof import_firestore2.GeoPoint;
|
|
486
|
+
}
|
|
487
|
+
function isDocumentReference(value) {
|
|
488
|
+
if (value === null || typeof value !== "object") return false;
|
|
489
|
+
const v = value;
|
|
490
|
+
return typeof v.path === "string" && v.firestore !== void 0 && typeof v.id === "string" && v.constructor?.name === "DocumentReference";
|
|
491
|
+
}
|
|
492
|
+
function isVectorValue(value) {
|
|
493
|
+
if (value === null || typeof value !== "object") return false;
|
|
494
|
+
const v = value;
|
|
495
|
+
return v.constructor?.name === "VectorValue" && Array.isArray(v._values);
|
|
496
|
+
}
|
|
497
|
+
function serializeFirestoreTypes(data) {
|
|
498
|
+
return serializeValue(data);
|
|
499
|
+
}
|
|
500
|
+
function serializeValue(value) {
|
|
501
|
+
if (value === null || value === void 0) return value;
|
|
502
|
+
if (typeof value !== "object") return value;
|
|
503
|
+
if (isTimestamp(value)) {
|
|
504
|
+
return { [SERIALIZATION_TAG]: "Timestamp", seconds: value.seconds, nanoseconds: value.nanoseconds };
|
|
505
|
+
}
|
|
506
|
+
if (isGeoPoint(value)) {
|
|
507
|
+
return { [SERIALIZATION_TAG]: "GeoPoint", latitude: value.latitude, longitude: value.longitude };
|
|
508
|
+
}
|
|
509
|
+
if (isDocumentReference(value)) {
|
|
510
|
+
return { [SERIALIZATION_TAG]: "DocumentReference", path: value.path };
|
|
511
|
+
}
|
|
512
|
+
if (isVectorValue(value)) {
|
|
513
|
+
const v = value;
|
|
514
|
+
const values = typeof v.toArray === "function" ? v.toArray() : v._values;
|
|
515
|
+
return { [SERIALIZATION_TAG]: "VectorValue", values: [...values] };
|
|
516
|
+
}
|
|
517
|
+
if (Array.isArray(value)) {
|
|
518
|
+
return value.map(serializeValue);
|
|
519
|
+
}
|
|
520
|
+
const result = {};
|
|
521
|
+
for (const key of Object.keys(value)) {
|
|
522
|
+
result[key] = serializeValue(value[key]);
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
function deserializeFirestoreTypes(data, db) {
|
|
527
|
+
return deserializeValue(data, db);
|
|
528
|
+
}
|
|
529
|
+
function deserializeValue(value, db) {
|
|
530
|
+
if (value === null || value === void 0) return value;
|
|
531
|
+
if (typeof value !== "object") return value;
|
|
532
|
+
if (isTimestamp(value) || isGeoPoint(value) || isDocumentReference(value) || isVectorValue(value)) {
|
|
533
|
+
return value;
|
|
534
|
+
}
|
|
535
|
+
if (Array.isArray(value)) {
|
|
536
|
+
return value.map((v) => deserializeValue(v, db));
|
|
537
|
+
}
|
|
538
|
+
const obj = value;
|
|
539
|
+
if (isTaggedValue(obj)) {
|
|
540
|
+
const tag = obj[SERIALIZATION_TAG];
|
|
541
|
+
switch (tag) {
|
|
542
|
+
case "Timestamp":
|
|
543
|
+
if (typeof obj.seconds !== "number" || typeof obj.nanoseconds !== "number") return obj;
|
|
544
|
+
return new import_firestore2.Timestamp(obj.seconds, obj.nanoseconds);
|
|
545
|
+
case "GeoPoint":
|
|
546
|
+
if (typeof obj.latitude !== "number" || typeof obj.longitude !== "number") return obj;
|
|
547
|
+
return new import_firestore2.GeoPoint(obj.latitude, obj.longitude);
|
|
548
|
+
case "VectorValue":
|
|
549
|
+
if (!Array.isArray(obj.values)) return obj;
|
|
550
|
+
return import_firestore2.FieldValue.vector(obj.values);
|
|
551
|
+
case "DocumentReference":
|
|
552
|
+
if (typeof obj.path !== "string") return obj;
|
|
553
|
+
if (db) {
|
|
554
|
+
return db.doc(obj.path);
|
|
555
|
+
}
|
|
556
|
+
if (!_docRefWarned) {
|
|
557
|
+
_docRefWarned = true;
|
|
558
|
+
console.warn(
|
|
559
|
+
"[firegraph] DocumentReference encountered during migration deserialization but no Firestore instance available. The reference will remain as a tagged object with its path. Enable write-back for full reconstruction."
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
return obj;
|
|
563
|
+
default:
|
|
564
|
+
return obj;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const result = {};
|
|
568
|
+
for (const key of Object.keys(obj)) {
|
|
569
|
+
result[key] = deserializeValue(obj[key], db);
|
|
570
|
+
}
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/migration.ts
|
|
575
|
+
async function applyMigrationChain(data, currentVersion, targetVersion, migrations) {
|
|
576
|
+
const sorted = [...migrations].sort((a, b) => a.fromVersion - b.fromVersion);
|
|
577
|
+
let result = { ...data };
|
|
578
|
+
let version = currentVersion;
|
|
579
|
+
for (const step of sorted) {
|
|
580
|
+
if (step.fromVersion === version) {
|
|
581
|
+
try {
|
|
582
|
+
result = await step.up(result);
|
|
583
|
+
} catch (err) {
|
|
584
|
+
if (err instanceof MigrationError) throw err;
|
|
585
|
+
throw new MigrationError(
|
|
586
|
+
`Migration from v${step.fromVersion} to v${step.toVersion} failed: ${err.message}`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
if (!result || typeof result !== "object") {
|
|
590
|
+
throw new MigrationError(
|
|
591
|
+
`Migration from v${step.fromVersion} to v${step.toVersion} returned invalid data (expected object)`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
version = step.toVersion;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (version !== targetVersion) {
|
|
598
|
+
throw new MigrationError(
|
|
599
|
+
`Incomplete migration chain: reached v${version} but target is v${targetVersion}`
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
604
|
+
function validateMigrationChain(migrations, label) {
|
|
605
|
+
if (migrations.length === 0) return;
|
|
606
|
+
const seen = /* @__PURE__ */ new Set();
|
|
607
|
+
for (const step of migrations) {
|
|
608
|
+
if (step.toVersion <= step.fromVersion) {
|
|
609
|
+
throw new MigrationError(
|
|
610
|
+
`${label}: migration step has toVersion (${step.toVersion}) <= fromVersion (${step.fromVersion})`
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
if (seen.has(step.fromVersion)) {
|
|
614
|
+
throw new MigrationError(
|
|
615
|
+
`${label}: duplicate migration step for fromVersion ${step.fromVersion}`
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
seen.add(step.fromVersion);
|
|
619
|
+
}
|
|
620
|
+
const sorted = [...migrations].sort((a, b) => a.fromVersion - b.fromVersion);
|
|
621
|
+
const targetVersion = Math.max(...migrations.map((m) => m.toVersion));
|
|
622
|
+
let version = 0;
|
|
623
|
+
for (const step of sorted) {
|
|
624
|
+
if (step.fromVersion === version) {
|
|
625
|
+
version = step.toVersion;
|
|
626
|
+
} else if (step.fromVersion > version) {
|
|
627
|
+
throw new MigrationError(
|
|
628
|
+
`${label}: migration chain has a gap \u2014 no step covers v${version} \u2192 v${step.fromVersion}`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (version !== targetVersion) {
|
|
633
|
+
throw new MigrationError(
|
|
634
|
+
`${label}: migration chain does not reach v${targetVersion} (stuck at v${version})`
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
async function migrateRecord(record, registry, globalWriteBack = "off") {
|
|
639
|
+
const entry = registry.lookup(record.aType, record.axbType, record.bType);
|
|
640
|
+
if (!entry?.migrations?.length || !entry.schemaVersion) {
|
|
641
|
+
return { record, migrated: false, writeBack: "off" };
|
|
642
|
+
}
|
|
643
|
+
const currentVersion = record.v ?? 0;
|
|
644
|
+
if (currentVersion >= entry.schemaVersion) {
|
|
645
|
+
return { record, migrated: false, writeBack: "off" };
|
|
646
|
+
}
|
|
647
|
+
const migratedData = await applyMigrationChain(
|
|
648
|
+
record.data,
|
|
649
|
+
currentVersion,
|
|
650
|
+
entry.schemaVersion,
|
|
651
|
+
entry.migrations
|
|
652
|
+
);
|
|
653
|
+
const writeBack = entry.migrationWriteBack ?? globalWriteBack ?? "off";
|
|
654
|
+
return {
|
|
655
|
+
record: { ...record, data: migratedData, v: entry.schemaVersion },
|
|
656
|
+
migrated: true,
|
|
657
|
+
writeBack
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
async function migrateRecords(records, registry, globalWriteBack = "off") {
|
|
661
|
+
return Promise.all(
|
|
662
|
+
records.map((r) => migrateRecord(r, registry, globalWriteBack))
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
|
|
451
666
|
// src/transaction.ts
|
|
452
667
|
var GraphTransactionImpl = class {
|
|
453
|
-
constructor(adapter, registry, scanProtection = "error", scopePath = "") {
|
|
668
|
+
constructor(adapter, registry, scanProtection = "error", scopePath = "", globalWriteBack = "off", db) {
|
|
454
669
|
this.adapter = adapter;
|
|
455
670
|
this.registry = registry;
|
|
456
671
|
this.scanProtection = scanProtection;
|
|
457
672
|
this.scopePath = scopePath;
|
|
673
|
+
this.globalWriteBack = globalWriteBack;
|
|
674
|
+
this.db = db;
|
|
458
675
|
}
|
|
459
676
|
async getNode(uid) {
|
|
460
677
|
const docId = computeNodeDocId(uid);
|
|
461
|
-
|
|
678
|
+
const record = await this.adapter.getDoc(docId);
|
|
679
|
+
if (!record || !this.registry) return record;
|
|
680
|
+
const result = await migrateRecord(record, this.registry, this.globalWriteBack);
|
|
681
|
+
if (result.migrated && result.writeBack !== "off") {
|
|
682
|
+
const update = {
|
|
683
|
+
data: deserializeFirestoreTypes(result.record.data, this.db),
|
|
684
|
+
updatedAt: import_firestore3.FieldValue.serverTimestamp()
|
|
685
|
+
};
|
|
686
|
+
if (result.record.v !== void 0) {
|
|
687
|
+
update.v = result.record.v;
|
|
688
|
+
}
|
|
689
|
+
this.adapter.updateDoc(docId, update);
|
|
690
|
+
}
|
|
691
|
+
return result.record;
|
|
462
692
|
}
|
|
463
693
|
async getEdge(aUid, axbType, bUid) {
|
|
464
694
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
465
|
-
|
|
695
|
+
const record = await this.adapter.getDoc(docId);
|
|
696
|
+
if (!record || !this.registry) return record;
|
|
697
|
+
const result = await migrateRecord(record, this.registry, this.globalWriteBack);
|
|
698
|
+
if (result.migrated && result.writeBack !== "off") {
|
|
699
|
+
const update = {
|
|
700
|
+
data: deserializeFirestoreTypes(result.record.data, this.db),
|
|
701
|
+
updatedAt: import_firestore3.FieldValue.serverTimestamp()
|
|
702
|
+
};
|
|
703
|
+
if (result.record.v !== void 0) {
|
|
704
|
+
update.v = result.record.v;
|
|
705
|
+
}
|
|
706
|
+
this.adapter.updateDoc(docId, update);
|
|
707
|
+
}
|
|
708
|
+
return result.record;
|
|
466
709
|
}
|
|
467
710
|
async edgeExists(aUid, axbType, bUid) {
|
|
468
|
-
const
|
|
711
|
+
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
712
|
+
const record = await this.adapter.getDoc(docId);
|
|
469
713
|
return record !== null;
|
|
470
714
|
}
|
|
471
715
|
checkQuerySafety(filters, allowCollectionScan) {
|
|
@@ -479,21 +723,45 @@ var GraphTransactionImpl = class {
|
|
|
479
723
|
}
|
|
480
724
|
async findEdges(params) {
|
|
481
725
|
const plan = buildEdgeQueryPlan(params);
|
|
726
|
+
let records;
|
|
482
727
|
if (plan.strategy === "get") {
|
|
483
728
|
const record = await this.adapter.getDoc(plan.docId);
|
|
484
|
-
|
|
729
|
+
records = record ? [record] : [];
|
|
730
|
+
} else {
|
|
731
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
732
|
+
records = await this.adapter.query(plan.filters, plan.options);
|
|
485
733
|
}
|
|
486
|
-
this.
|
|
487
|
-
return this.adapter.query(plan.filters, plan.options);
|
|
734
|
+
return this.applyMigrations(records);
|
|
488
735
|
}
|
|
489
736
|
async findNodes(params) {
|
|
490
737
|
const plan = buildNodeQueryPlan(params);
|
|
738
|
+
let records;
|
|
491
739
|
if (plan.strategy === "get") {
|
|
492
740
|
const record = await this.adapter.getDoc(plan.docId);
|
|
493
|
-
|
|
741
|
+
records = record ? [record] : [];
|
|
742
|
+
} else {
|
|
743
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
744
|
+
records = await this.adapter.query(plan.filters, plan.options);
|
|
745
|
+
}
|
|
746
|
+
return this.applyMigrations(records);
|
|
747
|
+
}
|
|
748
|
+
async applyMigrations(records) {
|
|
749
|
+
if (!this.registry || records.length === 0) return records;
|
|
750
|
+
const results = await migrateRecords(records, this.registry, this.globalWriteBack);
|
|
751
|
+
for (const result of results) {
|
|
752
|
+
if (result.migrated && result.writeBack !== "off") {
|
|
753
|
+
const docId = result.record.axbType === NODE_RELATION ? computeNodeDocId(result.record.aUid) : computeEdgeDocId(result.record.aUid, result.record.axbType, result.record.bUid);
|
|
754
|
+
const update = {
|
|
755
|
+
data: deserializeFirestoreTypes(result.record.data, this.db),
|
|
756
|
+
updatedAt: import_firestore3.FieldValue.serverTimestamp()
|
|
757
|
+
};
|
|
758
|
+
if (result.record.v !== void 0) {
|
|
759
|
+
update.v = result.record.v;
|
|
760
|
+
}
|
|
761
|
+
this.adapter.updateDoc(docId, update);
|
|
762
|
+
}
|
|
494
763
|
}
|
|
495
|
-
|
|
496
|
-
return this.adapter.query(plan.filters, plan.options);
|
|
764
|
+
return results.map((r) => r.record);
|
|
497
765
|
}
|
|
498
766
|
async putNode(aType, uid, data) {
|
|
499
767
|
if (this.registry) {
|
|
@@ -501,6 +769,12 @@ var GraphTransactionImpl = class {
|
|
|
501
769
|
}
|
|
502
770
|
const docId = computeNodeDocId(uid);
|
|
503
771
|
const record = buildNodeRecord(aType, uid, data);
|
|
772
|
+
if (this.registry) {
|
|
773
|
+
const entry = this.registry.lookup(aType, NODE_RELATION, aType);
|
|
774
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
775
|
+
record.v = entry.schemaVersion;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
504
778
|
this.adapter.setDoc(docId, record);
|
|
505
779
|
}
|
|
506
780
|
async putEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
@@ -509,13 +783,19 @@ var GraphTransactionImpl = class {
|
|
|
509
783
|
}
|
|
510
784
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
511
785
|
const record = buildEdgeRecord(aType, aUid, axbType, bType, bUid, data);
|
|
786
|
+
if (this.registry) {
|
|
787
|
+
const entry = this.registry.lookup(aType, axbType, bType);
|
|
788
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
789
|
+
record.v = entry.schemaVersion;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
512
792
|
this.adapter.setDoc(docId, record);
|
|
513
793
|
}
|
|
514
794
|
async updateNode(uid, data) {
|
|
515
795
|
const docId = computeNodeDocId(uid);
|
|
516
796
|
this.adapter.updateDoc(docId, {
|
|
517
797
|
...data,
|
|
518
|
-
updatedAt:
|
|
798
|
+
updatedAt: import_firestore3.FieldValue.serverTimestamp()
|
|
519
799
|
});
|
|
520
800
|
}
|
|
521
801
|
async removeNode(uid) {
|
|
@@ -529,7 +809,7 @@ var GraphTransactionImpl = class {
|
|
|
529
809
|
};
|
|
530
810
|
|
|
531
811
|
// src/batch.ts
|
|
532
|
-
var
|
|
812
|
+
var import_firestore4 = require("@google-cloud/firestore");
|
|
533
813
|
var GraphBatchImpl = class {
|
|
534
814
|
constructor(adapter, registry, scopePath = "") {
|
|
535
815
|
this.adapter = adapter;
|
|
@@ -542,6 +822,12 @@ var GraphBatchImpl = class {
|
|
|
542
822
|
}
|
|
543
823
|
const docId = computeNodeDocId(uid);
|
|
544
824
|
const record = buildNodeRecord(aType, uid, data);
|
|
825
|
+
if (this.registry) {
|
|
826
|
+
const entry = this.registry.lookup(aType, NODE_RELATION, aType);
|
|
827
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
828
|
+
record.v = entry.schemaVersion;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
545
831
|
this.adapter.setDoc(docId, record);
|
|
546
832
|
}
|
|
547
833
|
async putEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
@@ -550,13 +836,19 @@ var GraphBatchImpl = class {
|
|
|
550
836
|
}
|
|
551
837
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
552
838
|
const record = buildEdgeRecord(aType, aUid, axbType, bType, bUid, data);
|
|
839
|
+
if (this.registry) {
|
|
840
|
+
const entry = this.registry.lookup(aType, axbType, bType);
|
|
841
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
842
|
+
record.v = entry.schemaVersion;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
553
845
|
this.adapter.setDoc(docId, record);
|
|
554
846
|
}
|
|
555
847
|
async updateNode(uid, data) {
|
|
556
848
|
const docId = computeNodeDocId(uid);
|
|
557
849
|
this.adapter.updateDoc(docId, {
|
|
558
850
|
...data,
|
|
559
|
-
updatedAt:
|
|
851
|
+
updatedAt: import_firestore4.FieldValue.serverTimestamp()
|
|
560
852
|
});
|
|
561
853
|
}
|
|
562
854
|
async removeNode(uid) {
|
|
@@ -715,7 +1007,7 @@ async function removeNodeCascade(db, collectionPath, reader, uid, options) {
|
|
|
715
1007
|
}
|
|
716
1008
|
|
|
717
1009
|
// src/dynamic-registry.ts
|
|
718
|
-
var
|
|
1010
|
+
var import_node_crypto3 = require("crypto");
|
|
719
1011
|
|
|
720
1012
|
// src/json-schema.ts
|
|
721
1013
|
var import_ajv = __toESM(require("ajv"), 1);
|
|
@@ -864,6 +1156,13 @@ function createRegistry(input) {
|
|
|
864
1156
|
`Entry (${entry.aType}) -[${entry.axbType}]-> (${entry.bType}) has invalid targetGraph "${entry.targetGraph}" \u2014 must be a single segment (no "/")`
|
|
865
1157
|
);
|
|
866
1158
|
}
|
|
1159
|
+
if (entry.migrations?.length) {
|
|
1160
|
+
const label = `Entry (${entry.aType}) -[${entry.axbType}]-> (${entry.bType})`;
|
|
1161
|
+
validateMigrationChain(entry.migrations, label);
|
|
1162
|
+
entry.schemaVersion = Math.max(...entry.migrations.map((m) => m.toVersion));
|
|
1163
|
+
} else {
|
|
1164
|
+
entry.schemaVersion = void 0;
|
|
1165
|
+
}
|
|
867
1166
|
const key = tripleKey(entry.aType, entry.axbType, entry.bType);
|
|
868
1167
|
const validator = entry.jsonSchema ? compileSchema(entry.jsonSchema, `(${entry.aType}) -[${entry.axbType}]-> (${entry.bType})`) : void 0;
|
|
869
1168
|
map.set(key, { entry, validate: validator });
|
|
@@ -965,7 +1264,9 @@ function discoveryToEntries(discovery) {
|
|
|
965
1264
|
description: entity.description,
|
|
966
1265
|
titleField: entity.titleField,
|
|
967
1266
|
subtitleField: entity.subtitleField,
|
|
968
|
-
allowedIn: entity.allowedIn
|
|
1267
|
+
allowedIn: entity.allowedIn,
|
|
1268
|
+
migrations: entity.migrations,
|
|
1269
|
+
migrationWriteBack: entity.migrationWriteBack
|
|
969
1270
|
});
|
|
970
1271
|
}
|
|
971
1272
|
for (const [axbType, entity] of discovery.edges) {
|
|
@@ -991,7 +1292,9 @@ function discoveryToEntries(discovery) {
|
|
|
991
1292
|
titleField: entity.titleField,
|
|
992
1293
|
subtitleField: entity.subtitleField,
|
|
993
1294
|
allowedIn: entity.allowedIn,
|
|
994
|
-
targetGraph: resolvedTargetGraph
|
|
1295
|
+
targetGraph: resolvedTargetGraph,
|
|
1296
|
+
migrations: entity.migrations,
|
|
1297
|
+
migrationWriteBack: entity.migrationWriteBack
|
|
995
1298
|
});
|
|
996
1299
|
}
|
|
997
1300
|
}
|
|
@@ -999,9 +1302,259 @@ function discoveryToEntries(discovery) {
|
|
|
999
1302
|
return entries;
|
|
1000
1303
|
}
|
|
1001
1304
|
|
|
1305
|
+
// src/sandbox.ts
|
|
1306
|
+
var import_node_worker_threads = require("worker_threads");
|
|
1307
|
+
var import_node_crypto2 = require("crypto");
|
|
1308
|
+
var import_meta = {};
|
|
1309
|
+
var _worker = null;
|
|
1310
|
+
var _requestId = 0;
|
|
1311
|
+
var _pending = /* @__PURE__ */ new Map();
|
|
1312
|
+
var WORKER_SOURCE = [
|
|
1313
|
+
`'use strict';`,
|
|
1314
|
+
`var _wt = require('node:worker_threads');`,
|
|
1315
|
+
`var _mod = require('node:module');`,
|
|
1316
|
+
`var _crypto = require('node:crypto');`,
|
|
1317
|
+
`var parentPort = _wt.parentPort;`,
|
|
1318
|
+
`var workerData = _wt.workerData;`,
|
|
1319
|
+
``,
|
|
1320
|
+
`// Load SES using the parent module's resolution context`,
|
|
1321
|
+
`var esmRequire = _mod.createRequire(workerData.parentUrl);`,
|
|
1322
|
+
`esmRequire('ses');`,
|
|
1323
|
+
``,
|
|
1324
|
+
`lockdown({`,
|
|
1325
|
+
` errorTaming: 'unsafe',`,
|
|
1326
|
+
` consoleTaming: 'unsafe',`,
|
|
1327
|
+
` evalTaming: 'safe-eval',`,
|
|
1328
|
+
` overrideTaming: 'moderate',`,
|
|
1329
|
+
` stackFiltering: 'verbose'`,
|
|
1330
|
+
`});`,
|
|
1331
|
+
``,
|
|
1332
|
+
`// Defense-in-depth: verify lockdown() actually hardened JSON.`,
|
|
1333
|
+
`if (!Object.isFrozen(JSON)) {`,
|
|
1334
|
+
` throw new Error('SES lockdown failed: JSON is not frozen');`,
|
|
1335
|
+
`}`,
|
|
1336
|
+
``,
|
|
1337
|
+
`var cache = new Map();`,
|
|
1338
|
+
``,
|
|
1339
|
+
`function hashSource(s) {`,
|
|
1340
|
+
` return _crypto.createHash('sha256').update(s).digest('hex');`,
|
|
1341
|
+
`}`,
|
|
1342
|
+
``,
|
|
1343
|
+
`function buildWrapper(source) {`,
|
|
1344
|
+
` return '(function() {' +`,
|
|
1345
|
+
` ' var fn = (' + source + ');\\n' +`,
|
|
1346
|
+
` ' if (typeof fn !== "function") return null;\\n' +`,
|
|
1347
|
+
` ' return function(jsonIn) {\\n' +`,
|
|
1348
|
+
` ' var data = JSON.parse(jsonIn);\\n' +`,
|
|
1349
|
+
` ' var result = fn(data);\\n' +`,
|
|
1350
|
+
` ' if (result !== null && typeof result === "object" && typeof result.then === "function") {\\n' +`,
|
|
1351
|
+
` ' return result.then(function(r) { return JSON.stringify(r); });\\n' +`,
|
|
1352
|
+
` ' }\\n' +`,
|
|
1353
|
+
` ' return JSON.stringify(result);\\n' +`,
|
|
1354
|
+
` ' };\\n' +`,
|
|
1355
|
+
` '})()';`,
|
|
1356
|
+
`}`,
|
|
1357
|
+
``,
|
|
1358
|
+
`function compileSource(source) {`,
|
|
1359
|
+
` var key = hashSource(source);`,
|
|
1360
|
+
` var cached = cache.get(key);`,
|
|
1361
|
+
` if (cached) return cached;`,
|
|
1362
|
+
``,
|
|
1363
|
+
` var compartmentFn;`,
|
|
1364
|
+
` try {`,
|
|
1365
|
+
` var c = new Compartment({ JSON: JSON });`,
|
|
1366
|
+
` compartmentFn = c.evaluate(buildWrapper(source));`,
|
|
1367
|
+
` } catch (err) {`,
|
|
1368
|
+
` throw new Error('Failed to compile migration source: ' + (err.message || String(err)));`,
|
|
1369
|
+
` }`,
|
|
1370
|
+
``,
|
|
1371
|
+
` if (typeof compartmentFn !== 'function') {`,
|
|
1372
|
+
` throw new Error('Migration source did not produce a function: ' + source.slice(0, 80));`,
|
|
1373
|
+
` }`,
|
|
1374
|
+
``,
|
|
1375
|
+
` cache.set(key, compartmentFn);`,
|
|
1376
|
+
` return compartmentFn;`,
|
|
1377
|
+
`}`,
|
|
1378
|
+
``,
|
|
1379
|
+
`parentPort.on('message', function(msg) {`,
|
|
1380
|
+
` var id = msg.id;`,
|
|
1381
|
+
` try {`,
|
|
1382
|
+
` if (msg.type === 'compile') {`,
|
|
1383
|
+
` compileSource(msg.source);`,
|
|
1384
|
+
` parentPort.postMessage({ id: id, type: 'compiled' });`,
|
|
1385
|
+
` return;`,
|
|
1386
|
+
` }`,
|
|
1387
|
+
` if (msg.type === 'execute') {`,
|
|
1388
|
+
` var fn = compileSource(msg.source);`,
|
|
1389
|
+
` var raw;`,
|
|
1390
|
+
` try {`,
|
|
1391
|
+
` raw = fn(msg.jsonData);`,
|
|
1392
|
+
` } catch (err) {`,
|
|
1393
|
+
` parentPort.postMessage({ id: id, type: 'error', message: 'Migration function threw: ' + (err.message || String(err)) });`,
|
|
1394
|
+
` return;`,
|
|
1395
|
+
` }`,
|
|
1396
|
+
` if (raw !== null && typeof raw === 'object' && typeof raw.then === 'function') {`,
|
|
1397
|
+
` raw.then(`,
|
|
1398
|
+
` function(jsonResult) {`,
|
|
1399
|
+
` if (jsonResult === undefined || jsonResult === null) {`,
|
|
1400
|
+
` parentPort.postMessage({ id: id, type: 'error', message: 'Migration returned a non-JSON-serializable value' });`,
|
|
1401
|
+
` } else {`,
|
|
1402
|
+
` parentPort.postMessage({ id: id, type: 'result', jsonResult: jsonResult });`,
|
|
1403
|
+
` }`,
|
|
1404
|
+
` },`,
|
|
1405
|
+
` function(err) {`,
|
|
1406
|
+
` parentPort.postMessage({ id: id, type: 'error', message: 'Async migration function threw: ' + (err.message || String(err)) });`,
|
|
1407
|
+
` }`,
|
|
1408
|
+
` );`,
|
|
1409
|
+
` return;`,
|
|
1410
|
+
` }`,
|
|
1411
|
+
` if (raw === undefined || raw === null) {`,
|
|
1412
|
+
` parentPort.postMessage({ id: id, type: 'error', message: 'Migration returned a non-JSON-serializable value' });`,
|
|
1413
|
+
` } else {`,
|
|
1414
|
+
` parentPort.postMessage({ id: id, type: 'result', jsonResult: raw });`,
|
|
1415
|
+
` }`,
|
|
1416
|
+
` }`,
|
|
1417
|
+
` } catch (err) {`,
|
|
1418
|
+
` parentPort.postMessage({ id: id, type: 'error', message: err.message || String(err) });`,
|
|
1419
|
+
` }`,
|
|
1420
|
+
`});`
|
|
1421
|
+
].join("\n");
|
|
1422
|
+
function ensureWorker() {
|
|
1423
|
+
if (_worker) return _worker;
|
|
1424
|
+
_worker = new import_node_worker_threads.Worker(WORKER_SOURCE, {
|
|
1425
|
+
eval: true,
|
|
1426
|
+
workerData: { parentUrl: import_meta.url }
|
|
1427
|
+
});
|
|
1428
|
+
_worker.unref();
|
|
1429
|
+
_worker.on("message", (msg) => {
|
|
1430
|
+
if (msg.id === void 0) return;
|
|
1431
|
+
const pending = _pending.get(msg.id);
|
|
1432
|
+
if (!pending) return;
|
|
1433
|
+
_pending.delete(msg.id);
|
|
1434
|
+
if (msg.type === "error") {
|
|
1435
|
+
pending.reject(new MigrationError(msg.message ?? "Unknown sandbox error"));
|
|
1436
|
+
} else {
|
|
1437
|
+
pending.resolve(msg);
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1440
|
+
_worker.on("error", (err) => {
|
|
1441
|
+
for (const [, p] of _pending) {
|
|
1442
|
+
p.reject(new MigrationError(`Sandbox worker error: ${err.message}`));
|
|
1443
|
+
}
|
|
1444
|
+
_pending.clear();
|
|
1445
|
+
_worker = null;
|
|
1446
|
+
});
|
|
1447
|
+
_worker.on("exit", (code) => {
|
|
1448
|
+
if (_pending.size > 0) {
|
|
1449
|
+
for (const [, p] of _pending) {
|
|
1450
|
+
p.reject(new MigrationError(`Sandbox worker exited with code ${code}`));
|
|
1451
|
+
}
|
|
1452
|
+
_pending.clear();
|
|
1453
|
+
}
|
|
1454
|
+
_worker = null;
|
|
1455
|
+
});
|
|
1456
|
+
return _worker;
|
|
1457
|
+
}
|
|
1458
|
+
function sendToWorker(msg) {
|
|
1459
|
+
const worker = ensureWorker();
|
|
1460
|
+
if (_requestId >= Number.MAX_SAFE_INTEGER) _requestId = 0;
|
|
1461
|
+
const id = ++_requestId;
|
|
1462
|
+
return new Promise((resolve2, reject) => {
|
|
1463
|
+
_pending.set(id, { resolve: resolve2, reject });
|
|
1464
|
+
worker.postMessage({ ...msg, id });
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
var compiledCache = /* @__PURE__ */ new WeakMap();
|
|
1468
|
+
function getExecutorCache(executor) {
|
|
1469
|
+
let cache = compiledCache.get(executor);
|
|
1470
|
+
if (!cache) {
|
|
1471
|
+
cache = /* @__PURE__ */ new Map();
|
|
1472
|
+
compiledCache.set(executor, cache);
|
|
1473
|
+
}
|
|
1474
|
+
return cache;
|
|
1475
|
+
}
|
|
1476
|
+
function hashSource(source) {
|
|
1477
|
+
return (0, import_node_crypto2.createHash)("sha256").update(source).digest("hex");
|
|
1478
|
+
}
|
|
1479
|
+
function defaultExecutor(source) {
|
|
1480
|
+
ensureWorker();
|
|
1481
|
+
return ((data) => {
|
|
1482
|
+
const jsonData = JSON.stringify(serializeFirestoreTypes(data));
|
|
1483
|
+
return sendToWorker({ type: "execute", source, jsonData }).then(
|
|
1484
|
+
(response) => {
|
|
1485
|
+
if (response.jsonResult === void 0 || response.jsonResult === null) {
|
|
1486
|
+
throw new MigrationError("Migration returned a non-JSON-serializable value");
|
|
1487
|
+
}
|
|
1488
|
+
try {
|
|
1489
|
+
return deserializeFirestoreTypes(JSON.parse(response.jsonResult));
|
|
1490
|
+
} catch {
|
|
1491
|
+
throw new MigrationError("Migration returned a non-JSON-serializable value");
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
);
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
async function precompileSource(source, executor) {
|
|
1498
|
+
if (executor && executor !== defaultExecutor) {
|
|
1499
|
+
try {
|
|
1500
|
+
executor(source);
|
|
1501
|
+
} catch (err) {
|
|
1502
|
+
if (err instanceof MigrationError) throw err;
|
|
1503
|
+
throw new MigrationError(
|
|
1504
|
+
`Failed to compile migration source: ${err.message}`
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
await sendToWorker({ type: "compile", source });
|
|
1510
|
+
}
|
|
1511
|
+
function compileMigrationFn(source, executor = defaultExecutor) {
|
|
1512
|
+
const cache = getExecutorCache(executor);
|
|
1513
|
+
const key = hashSource(source);
|
|
1514
|
+
const cached = cache.get(key);
|
|
1515
|
+
if (cached) return cached;
|
|
1516
|
+
try {
|
|
1517
|
+
const fn = executor(source);
|
|
1518
|
+
cache.set(key, fn);
|
|
1519
|
+
return fn;
|
|
1520
|
+
} catch (err) {
|
|
1521
|
+
if (err instanceof MigrationError) throw err;
|
|
1522
|
+
throw new MigrationError(
|
|
1523
|
+
`Failed to compile migration source: ${err.message}`
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
function compileMigrations(stored, executor) {
|
|
1528
|
+
return stored.map((step) => ({
|
|
1529
|
+
fromVersion: step.fromVersion,
|
|
1530
|
+
toVersion: step.toVersion,
|
|
1531
|
+
up: compileMigrationFn(step.up, executor)
|
|
1532
|
+
}));
|
|
1533
|
+
}
|
|
1534
|
+
async function destroySandboxWorker() {
|
|
1535
|
+
if (!_worker) return;
|
|
1536
|
+
const w = _worker;
|
|
1537
|
+
_worker = null;
|
|
1538
|
+
for (const [, p] of _pending) {
|
|
1539
|
+
p.reject(new MigrationError("Sandbox worker terminated"));
|
|
1540
|
+
}
|
|
1541
|
+
_pending.clear();
|
|
1542
|
+
await w.terminate();
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1002
1545
|
// src/dynamic-registry.ts
|
|
1003
1546
|
var META_NODE_TYPE = "nodeType";
|
|
1004
1547
|
var META_EDGE_TYPE = "edgeType";
|
|
1548
|
+
var STORED_MIGRATION_STEP_SCHEMA = {
|
|
1549
|
+
type: "object",
|
|
1550
|
+
required: ["fromVersion", "toVersion", "up"],
|
|
1551
|
+
properties: {
|
|
1552
|
+
fromVersion: { type: "integer", minimum: 0 },
|
|
1553
|
+
toVersion: { type: "integer", minimum: 1 },
|
|
1554
|
+
up: { type: "string", minLength: 1 }
|
|
1555
|
+
},
|
|
1556
|
+
additionalProperties: false
|
|
1557
|
+
};
|
|
1005
1558
|
var NODE_TYPE_SCHEMA = {
|
|
1006
1559
|
type: "object",
|
|
1007
1560
|
required: ["name", "jsonSchema"],
|
|
@@ -1013,7 +1566,10 @@ var NODE_TYPE_SCHEMA = {
|
|
|
1013
1566
|
subtitleField: { type: "string" },
|
|
1014
1567
|
viewTemplate: { type: "string" },
|
|
1015
1568
|
viewCss: { type: "string" },
|
|
1016
|
-
allowedIn: { type: "array", items: { type: "string", minLength: 1 } }
|
|
1569
|
+
allowedIn: { type: "array", items: { type: "string", minLength: 1 } },
|
|
1570
|
+
schemaVersion: { type: "integer", minimum: 0 },
|
|
1571
|
+
migrations: { type: "array", items: STORED_MIGRATION_STEP_SCHEMA },
|
|
1572
|
+
migrationWriteBack: { type: "string", enum: ["off", "eager", "background"] }
|
|
1017
1573
|
},
|
|
1018
1574
|
additionalProperties: false
|
|
1019
1575
|
};
|
|
@@ -1042,7 +1598,10 @@ var EDGE_TYPE_SCHEMA = {
|
|
|
1042
1598
|
viewTemplate: { type: "string" },
|
|
1043
1599
|
viewCss: { type: "string" },
|
|
1044
1600
|
allowedIn: { type: "array", items: { type: "string", minLength: 1 } },
|
|
1045
|
-
targetGraph: { type: "string", minLength: 1, pattern: "^[^/]+$" }
|
|
1601
|
+
targetGraph: { type: "string", minLength: 1, pattern: "^[^/]+$" },
|
|
1602
|
+
schemaVersion: { type: "integer", minimum: 0 },
|
|
1603
|
+
migrations: { type: "array", items: STORED_MIGRATION_STEP_SCHEMA },
|
|
1604
|
+
migrationWriteBack: { type: "string", enum: ["off", "eager", "background"] }
|
|
1046
1605
|
},
|
|
1047
1606
|
additionalProperties: false
|
|
1048
1607
|
};
|
|
@@ -1066,15 +1625,33 @@ function createBootstrapRegistry() {
|
|
|
1066
1625
|
return createRegistry([...BOOTSTRAP_ENTRIES]);
|
|
1067
1626
|
}
|
|
1068
1627
|
function generateDeterministicUid(metaType, name) {
|
|
1069
|
-
const hash = (0,
|
|
1628
|
+
const hash = (0, import_node_crypto3.createHash)("sha256").update(`${metaType}:${name}`).digest("base64url");
|
|
1070
1629
|
return hash.slice(0, 21);
|
|
1071
1630
|
}
|
|
1072
|
-
async function createRegistryFromGraph(reader) {
|
|
1631
|
+
async function createRegistryFromGraph(reader, executor) {
|
|
1073
1632
|
const [nodeTypes, edgeTypes] = await Promise.all([
|
|
1074
1633
|
reader.findNodes({ aType: META_NODE_TYPE }),
|
|
1075
1634
|
reader.findNodes({ aType: META_EDGE_TYPE })
|
|
1076
1635
|
]);
|
|
1077
1636
|
const entries = [...BOOTSTRAP_ENTRIES];
|
|
1637
|
+
const prevalidations = [];
|
|
1638
|
+
for (const record of nodeTypes) {
|
|
1639
|
+
const data = record.data;
|
|
1640
|
+
if (data.migrations) {
|
|
1641
|
+
for (const m of data.migrations) {
|
|
1642
|
+
prevalidations.push(precompileSource(m.up, executor));
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
for (const record of edgeTypes) {
|
|
1647
|
+
const data = record.data;
|
|
1648
|
+
if (data.migrations) {
|
|
1649
|
+
for (const m of data.migrations) {
|
|
1650
|
+
prevalidations.push(precompileSource(m.up, executor));
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
await Promise.all(prevalidations);
|
|
1078
1655
|
for (const record of nodeTypes) {
|
|
1079
1656
|
const data = record.data;
|
|
1080
1657
|
entries.push({
|
|
@@ -1085,13 +1662,16 @@ async function createRegistryFromGraph(reader) {
|
|
|
1085
1662
|
description: data.description,
|
|
1086
1663
|
titleField: data.titleField,
|
|
1087
1664
|
subtitleField: data.subtitleField,
|
|
1088
|
-
allowedIn: data.allowedIn
|
|
1665
|
+
allowedIn: data.allowedIn,
|
|
1666
|
+
migrations: data.migrations ? compileMigrations(data.migrations, executor) : void 0,
|
|
1667
|
+
migrationWriteBack: data.migrationWriteBack
|
|
1089
1668
|
});
|
|
1090
1669
|
}
|
|
1091
1670
|
for (const record of edgeTypes) {
|
|
1092
1671
|
const data = record.data;
|
|
1093
1672
|
const fromTypes = Array.isArray(data.from) ? data.from : [data.from];
|
|
1094
1673
|
const toTypes = Array.isArray(data.to) ? data.to : [data.to];
|
|
1674
|
+
const compiledMigrations = data.migrations ? compileMigrations(data.migrations, executor) : void 0;
|
|
1095
1675
|
for (const aType of fromTypes) {
|
|
1096
1676
|
for (const bType of toTypes) {
|
|
1097
1677
|
entries.push({
|
|
@@ -1104,7 +1684,9 @@ async function createRegistryFromGraph(reader) {
|
|
|
1104
1684
|
titleField: data.titleField,
|
|
1105
1685
|
subtitleField: data.subtitleField,
|
|
1106
1686
|
allowedIn: data.allowedIn,
|
|
1107
|
-
targetGraph: data.targetGraph
|
|
1687
|
+
targetGraph: data.targetGraph,
|
|
1688
|
+
migrations: compiledMigrations,
|
|
1689
|
+
migrationWriteBack: data.migrationWriteBack
|
|
1108
1690
|
});
|
|
1109
1691
|
}
|
|
1110
1692
|
}
|
|
@@ -1120,6 +1702,8 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1120
1702
|
this.db = db;
|
|
1121
1703
|
this.scopePath = scopePath;
|
|
1122
1704
|
this.adapter = createFirestoreAdapter(db, collectionPath);
|
|
1705
|
+
this.globalWriteBack = options?.migrationWriteBack ?? "off";
|
|
1706
|
+
this.migrationSandbox = options?.migrationSandbox;
|
|
1123
1707
|
if (options?.registryMode) {
|
|
1124
1708
|
this.dynamicConfig = options.registryMode;
|
|
1125
1709
|
this.bootstrapRegistry = createBootstrapRegistry();
|
|
@@ -1171,6 +1755,9 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1171
1755
|
metaPipelineAdapter;
|
|
1172
1756
|
// Subgraph scope tracking
|
|
1173
1757
|
scopePath;
|
|
1758
|
+
// Migration settings
|
|
1759
|
+
globalWriteBack;
|
|
1760
|
+
migrationSandbox;
|
|
1174
1761
|
// ---------------------------------------------------------------------------
|
|
1175
1762
|
// Registry routing
|
|
1176
1763
|
// ---------------------------------------------------------------------------
|
|
@@ -1240,37 +1827,114 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1240
1827
|
console.warn(`[firegraph] Query safety warning: ${result.reason}`);
|
|
1241
1828
|
}
|
|
1242
1829
|
// ---------------------------------------------------------------------------
|
|
1830
|
+
// Migration helpers
|
|
1831
|
+
// ---------------------------------------------------------------------------
|
|
1832
|
+
/**
|
|
1833
|
+
* Apply migration to a single record. Returns the (possibly migrated)
|
|
1834
|
+
* record and triggers write-back if applicable.
|
|
1835
|
+
*/
|
|
1836
|
+
async applyMigration(record, docId) {
|
|
1837
|
+
const registry = this.getCombinedRegistry();
|
|
1838
|
+
if (!registry) return record;
|
|
1839
|
+
const result = await migrateRecord(record, registry, this.globalWriteBack);
|
|
1840
|
+
if (result.migrated) {
|
|
1841
|
+
this.handleWriteBack(result, docId);
|
|
1842
|
+
}
|
|
1843
|
+
return result.record;
|
|
1844
|
+
}
|
|
1845
|
+
/**
|
|
1846
|
+
* Apply migrations to an array of records. Returns all records
|
|
1847
|
+
* (migrated where applicable) and triggers write-backs.
|
|
1848
|
+
*/
|
|
1849
|
+
async applyMigrations(records) {
|
|
1850
|
+
const registry = this.getCombinedRegistry();
|
|
1851
|
+
if (!registry || records.length === 0) return records;
|
|
1852
|
+
const results = await migrateRecords(records, registry, this.globalWriteBack);
|
|
1853
|
+
for (const result of results) {
|
|
1854
|
+
if (result.migrated) {
|
|
1855
|
+
const docId = result.record.axbType === NODE_RELATION ? computeNodeDocId(result.record.aUid) : computeEdgeDocId(result.record.aUid, result.record.axbType, result.record.bUid);
|
|
1856
|
+
this.handleWriteBack(result, docId);
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
return results.map((r) => r.record);
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Handle write-back for a migrated record based on the resolved mode.
|
|
1863
|
+
*
|
|
1864
|
+
* Both `'eager'` and `'background'` are fire-and-forget (not awaited by
|
|
1865
|
+
* the caller). The difference is logging level on failure:
|
|
1866
|
+
* - `eager`: logs an error via `console.error`
|
|
1867
|
+
* - `background`: logs a warning via `console.warn`
|
|
1868
|
+
*
|
|
1869
|
+
* For truly synchronous write-back guarantees, use transactions — the
|
|
1870
|
+
* `GraphTransactionImpl` performs write-back inline within the transaction.
|
|
1871
|
+
*/
|
|
1872
|
+
handleWriteBack(result, docId) {
|
|
1873
|
+
if (result.writeBack === "off") return;
|
|
1874
|
+
const doWriteBack = async () => {
|
|
1875
|
+
try {
|
|
1876
|
+
const update = {
|
|
1877
|
+
data: deserializeFirestoreTypes(result.record.data, this.db),
|
|
1878
|
+
updatedAt: import_firestore5.FieldValue.serverTimestamp()
|
|
1879
|
+
};
|
|
1880
|
+
if (result.record.v !== void 0) {
|
|
1881
|
+
update.v = result.record.v;
|
|
1882
|
+
}
|
|
1883
|
+
await this.adapter.updateDoc(docId, update);
|
|
1884
|
+
} catch (err) {
|
|
1885
|
+
const msg = `[firegraph] Migration write-back failed for ${docId}: ${err.message}`;
|
|
1886
|
+
if (result.writeBack === "eager") {
|
|
1887
|
+
console.error(msg);
|
|
1888
|
+
} else {
|
|
1889
|
+
console.warn(msg);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
void doWriteBack();
|
|
1894
|
+
}
|
|
1895
|
+
// ---------------------------------------------------------------------------
|
|
1243
1896
|
// GraphReader
|
|
1244
1897
|
// ---------------------------------------------------------------------------
|
|
1245
1898
|
async getNode(uid) {
|
|
1246
1899
|
const docId = computeNodeDocId(uid);
|
|
1247
|
-
|
|
1900
|
+
const record = await this.adapter.getDoc(docId);
|
|
1901
|
+
if (!record) return null;
|
|
1902
|
+
return this.applyMigration(record, docId);
|
|
1248
1903
|
}
|
|
1249
1904
|
async getEdge(aUid, axbType, bUid) {
|
|
1250
1905
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
1251
|
-
|
|
1906
|
+
const record = await this.adapter.getDoc(docId);
|
|
1907
|
+
if (!record) return null;
|
|
1908
|
+
return this.applyMigration(record, docId);
|
|
1252
1909
|
}
|
|
1253
1910
|
async edgeExists(aUid, axbType, bUid) {
|
|
1254
|
-
const
|
|
1911
|
+
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
1912
|
+
const record = await this.adapter.getDoc(docId);
|
|
1255
1913
|
return record !== null;
|
|
1256
1914
|
}
|
|
1257
1915
|
async findEdges(params) {
|
|
1258
1916
|
const plan = buildEdgeQueryPlan(params);
|
|
1917
|
+
let records;
|
|
1259
1918
|
if (plan.strategy === "get") {
|
|
1260
1919
|
const record = await this.adapter.getDoc(plan.docId);
|
|
1261
|
-
|
|
1920
|
+
records = record ? [record] : [];
|
|
1921
|
+
} else {
|
|
1922
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
1923
|
+
records = await this.executeQuery(plan.filters, plan.options);
|
|
1262
1924
|
}
|
|
1263
|
-
this.
|
|
1264
|
-
return this.executeQuery(plan.filters, plan.options);
|
|
1925
|
+
return this.applyMigrations(records);
|
|
1265
1926
|
}
|
|
1266
1927
|
async findNodes(params) {
|
|
1267
1928
|
const plan = buildNodeQueryPlan(params);
|
|
1929
|
+
let records;
|
|
1268
1930
|
if (plan.strategy === "get") {
|
|
1269
1931
|
const record = await this.adapter.getDoc(plan.docId);
|
|
1270
|
-
|
|
1932
|
+
records = record ? [record] : [];
|
|
1933
|
+
} else {
|
|
1934
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
1935
|
+
records = await this.executeQuery(plan.filters, plan.options);
|
|
1271
1936
|
}
|
|
1272
|
-
this.
|
|
1273
|
-
return this.executeQuery(plan.filters, plan.options);
|
|
1937
|
+
return this.applyMigrations(records);
|
|
1274
1938
|
}
|
|
1275
1939
|
// ---------------------------------------------------------------------------
|
|
1276
1940
|
// GraphWriter
|
|
@@ -1283,6 +1947,12 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1283
1947
|
const adapter = this.getAdapterForType(aType);
|
|
1284
1948
|
const docId = computeNodeDocId(uid);
|
|
1285
1949
|
const record = buildNodeRecord(aType, uid, data);
|
|
1950
|
+
if (registry) {
|
|
1951
|
+
const entry = registry.lookup(aType, NODE_RELATION, aType);
|
|
1952
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
1953
|
+
record.v = entry.schemaVersion;
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1286
1956
|
await adapter.setDoc(docId, record);
|
|
1287
1957
|
}
|
|
1288
1958
|
async putEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
@@ -1293,13 +1963,19 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1293
1963
|
const adapter = this.getAdapterForType(aType);
|
|
1294
1964
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
1295
1965
|
const record = buildEdgeRecord(aType, aUid, axbType, bType, bUid, data);
|
|
1966
|
+
if (registry) {
|
|
1967
|
+
const entry = registry.lookup(aType, axbType, bType);
|
|
1968
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
1969
|
+
record.v = entry.schemaVersion;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1296
1972
|
await adapter.setDoc(docId, record);
|
|
1297
1973
|
}
|
|
1298
1974
|
async updateNode(uid, data) {
|
|
1299
1975
|
const docId = computeNodeDocId(uid);
|
|
1300
1976
|
await this.adapter.updateDoc(docId, {
|
|
1301
1977
|
...data,
|
|
1302
|
-
updatedAt:
|
|
1978
|
+
updatedAt: import_firestore5.FieldValue.serverTimestamp()
|
|
1303
1979
|
});
|
|
1304
1980
|
}
|
|
1305
1981
|
async removeNode(uid) {
|
|
@@ -1320,7 +1996,7 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1320
1996
|
this.adapter.collectionPath,
|
|
1321
1997
|
firestoreTx
|
|
1322
1998
|
);
|
|
1323
|
-
const graphTx = new GraphTransactionImpl(adapter, this.getCombinedRegistry(), this.scanProtection, this.scopePath);
|
|
1999
|
+
const graphTx = new GraphTransactionImpl(adapter, this.getCombinedRegistry(), this.scanProtection, this.scopePath, this.globalWriteBack, this.db);
|
|
1324
2000
|
return fn(graphTx);
|
|
1325
2001
|
});
|
|
1326
2002
|
}
|
|
@@ -1352,7 +2028,9 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1352
2028
|
{
|
|
1353
2029
|
registry: this.getCombinedRegistry(),
|
|
1354
2030
|
queryMode: this.queryMode === "pipeline" ? "pipeline" : "standard",
|
|
1355
|
-
scanProtection: this.scanProtection
|
|
2031
|
+
scanProtection: this.scanProtection,
|
|
2032
|
+
migrationWriteBack: this.globalWriteBack,
|
|
2033
|
+
migrationSandbox: this.migrationSandbox
|
|
1356
2034
|
},
|
|
1357
2035
|
newScopePath
|
|
1358
2036
|
);
|
|
@@ -1382,7 +2060,8 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1382
2060
|
q = q.limit(plan.options.limit);
|
|
1383
2061
|
}
|
|
1384
2062
|
const snap = await q.get();
|
|
1385
|
-
|
|
2063
|
+
const records = snap.docs.map((doc) => doc.data());
|
|
2064
|
+
return this.applyMigrations(records);
|
|
1386
2065
|
}
|
|
1387
2066
|
// ---------------------------------------------------------------------------
|
|
1388
2067
|
// Bulk operations
|
|
@@ -1420,6 +2099,10 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1420
2099
|
if (options?.viewTemplate !== void 0) data.viewTemplate = options.viewTemplate;
|
|
1421
2100
|
if (options?.viewCss !== void 0) data.viewCss = options.viewCss;
|
|
1422
2101
|
if (options?.allowedIn !== void 0) data.allowedIn = options.allowedIn;
|
|
2102
|
+
if (options?.migrationWriteBack !== void 0) data.migrationWriteBack = options.migrationWriteBack;
|
|
2103
|
+
if (options?.migrations !== void 0) {
|
|
2104
|
+
data.migrations = await this.serializeMigrations(options.migrations);
|
|
2105
|
+
}
|
|
1423
2106
|
await this.putNode(META_NODE_TYPE, uid, data);
|
|
1424
2107
|
}
|
|
1425
2108
|
async defineEdgeType(name, topology, jsonSchema, description, options) {
|
|
@@ -1461,6 +2144,10 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1461
2144
|
if (options?.viewTemplate !== void 0) data.viewTemplate = options.viewTemplate;
|
|
1462
2145
|
if (options?.viewCss !== void 0) data.viewCss = options.viewCss;
|
|
1463
2146
|
if (options?.allowedIn !== void 0) data.allowedIn = options.allowedIn;
|
|
2147
|
+
if (options?.migrationWriteBack !== void 0) data.migrationWriteBack = options.migrationWriteBack;
|
|
2148
|
+
if (options?.migrations !== void 0) {
|
|
2149
|
+
data.migrations = await this.serializeMigrations(options.migrations);
|
|
2150
|
+
}
|
|
1464
2151
|
await this.putNode(META_EDGE_TYPE, uid, data);
|
|
1465
2152
|
}
|
|
1466
2153
|
async reloadRegistry() {
|
|
@@ -1470,13 +2157,28 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1470
2157
|
);
|
|
1471
2158
|
}
|
|
1472
2159
|
const reader = this.createMetaReader();
|
|
1473
|
-
const dynamicOnly = await createRegistryFromGraph(reader);
|
|
2160
|
+
const dynamicOnly = await createRegistryFromGraph(reader, this.migrationSandbox);
|
|
1474
2161
|
if (this.staticRegistry) {
|
|
1475
2162
|
this.dynamicRegistry = createMergedRegistry(this.staticRegistry, dynamicOnly);
|
|
1476
2163
|
} else {
|
|
1477
2164
|
this.dynamicRegistry = dynamicOnly;
|
|
1478
2165
|
}
|
|
1479
2166
|
}
|
|
2167
|
+
/**
|
|
2168
|
+
* Serialize migration steps for storage in Firestore.
|
|
2169
|
+
* Function objects are converted via `.toString()`; strings are stored as-is.
|
|
2170
|
+
* Each migration is validated at define-time by pre-compiling in the sandbox.
|
|
2171
|
+
*/
|
|
2172
|
+
async serializeMigrations(migrations) {
|
|
2173
|
+
const result = migrations.map((m) => {
|
|
2174
|
+
const source = typeof m.up === "function" ? m.up.toString() : m.up;
|
|
2175
|
+
return { fromVersion: m.fromVersion, toVersion: m.toVersion, up: source };
|
|
2176
|
+
});
|
|
2177
|
+
await Promise.all(
|
|
2178
|
+
result.map((m) => precompileSource(m.up, this.migrationSandbox))
|
|
2179
|
+
);
|
|
2180
|
+
return result;
|
|
2181
|
+
}
|
|
1480
2182
|
/**
|
|
1481
2183
|
* Create a GraphReader for the meta-collection.
|
|
1482
2184
|
* If meta-collection is the same as main collection, returns `this`.
|
|
@@ -1843,7 +2545,7 @@ function resolveView(resolverConfig, availableViewNames, context) {
|
|
|
1843
2545
|
var import_node_fs = require("fs");
|
|
1844
2546
|
var import_node_module = require("module");
|
|
1845
2547
|
var import_node_path = require("path");
|
|
1846
|
-
var
|
|
2548
|
+
var import_meta2 = {};
|
|
1847
2549
|
var DiscoveryError = class extends FiregraphError {
|
|
1848
2550
|
constructor(message) {
|
|
1849
2551
|
super(message, "DISCOVERY_ERROR");
|
|
@@ -1882,7 +2584,7 @@ function loadSchema(dir, entityLabel) {
|
|
|
1882
2584
|
var _jiti;
|
|
1883
2585
|
function getJiti() {
|
|
1884
2586
|
if (!_jiti) {
|
|
1885
|
-
const base = typeof __filename !== "undefined" ? __filename :
|
|
2587
|
+
const base = typeof __filename !== "undefined" ? __filename : import_meta2.url;
|
|
1886
2588
|
const esmRequire = (0, import_node_module.createRequire)(base);
|
|
1887
2589
|
const { createJiti } = esmRequire("jiti");
|
|
1888
2590
|
_jiti = createJiti(base, { interopDefault: true });
|
|
@@ -1915,11 +2617,39 @@ function findViewsFile(dir) {
|
|
|
1915
2617
|
}
|
|
1916
2618
|
return void 0;
|
|
1917
2619
|
}
|
|
2620
|
+
var MIGRATION_EXTENSIONS = [".ts", ".js", ".mts", ".mjs"];
|
|
2621
|
+
function findMigrationsFile(dir) {
|
|
2622
|
+
for (const ext of MIGRATION_EXTENSIONS) {
|
|
2623
|
+
const candidate = (0, import_node_path.join)(dir, `migrations${ext}`);
|
|
2624
|
+
if ((0, import_node_fs.existsSync)(candidate)) return candidate;
|
|
2625
|
+
}
|
|
2626
|
+
return void 0;
|
|
2627
|
+
}
|
|
2628
|
+
function loadMigrations(filePath, entityLabel) {
|
|
2629
|
+
try {
|
|
2630
|
+
const jiti = getJiti();
|
|
2631
|
+
const mod = jiti(filePath);
|
|
2632
|
+
const migrations = mod && typeof mod === "object" && "default" in mod ? mod.default : mod;
|
|
2633
|
+
if (!Array.isArray(migrations)) {
|
|
2634
|
+
throw new DiscoveryError(
|
|
2635
|
+
`Migrations file ${filePath} for ${entityLabel} must default-export an array of MigrationStep.`
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
return migrations;
|
|
2639
|
+
} catch (err) {
|
|
2640
|
+
if (err instanceof DiscoveryError) throw err;
|
|
2641
|
+
throw new DiscoveryError(
|
|
2642
|
+
`Failed to load migrations ${filePath} for ${entityLabel}: ${err.message}`
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
1918
2646
|
function loadNodeEntity(dir, name) {
|
|
1919
2647
|
const schema = loadSchema(dir, `node type "${name}"`);
|
|
1920
2648
|
const meta = readJsonIfExists((0, import_node_path.join)(dir, "meta.json"));
|
|
1921
2649
|
const sampleData = readJsonIfExists((0, import_node_path.join)(dir, "sample.json"));
|
|
1922
2650
|
const viewsPath = findViewsFile(dir);
|
|
2651
|
+
const migrationsPath = findMigrationsFile(dir);
|
|
2652
|
+
const migrations = migrationsPath ? loadMigrations(migrationsPath, `node type "${name}"`) : void 0;
|
|
1923
2653
|
return {
|
|
1924
2654
|
kind: "node",
|
|
1925
2655
|
name,
|
|
@@ -1930,7 +2660,9 @@ function loadNodeEntity(dir, name) {
|
|
|
1930
2660
|
viewDefaults: meta?.viewDefaults,
|
|
1931
2661
|
viewsPath,
|
|
1932
2662
|
sampleData,
|
|
1933
|
-
allowedIn: meta?.allowedIn
|
|
2663
|
+
allowedIn: meta?.allowedIn,
|
|
2664
|
+
migrations,
|
|
2665
|
+
migrationWriteBack: meta?.migrationWriteBack
|
|
1934
2666
|
};
|
|
1935
2667
|
}
|
|
1936
2668
|
function loadEdgeEntity(dir, name) {
|
|
@@ -1955,6 +2687,8 @@ function loadEdgeEntity(dir, name) {
|
|
|
1955
2687
|
const meta = readJsonIfExists((0, import_node_path.join)(dir, "meta.json"));
|
|
1956
2688
|
const sampleData = readJsonIfExists((0, import_node_path.join)(dir, "sample.json"));
|
|
1957
2689
|
const viewsPath = findViewsFile(dir);
|
|
2690
|
+
const migrationsPath = findMigrationsFile(dir);
|
|
2691
|
+
const migrations = migrationsPath ? loadMigrations(migrationsPath, `edge type "${name}"`) : void 0;
|
|
1958
2692
|
return {
|
|
1959
2693
|
kind: "edge",
|
|
1960
2694
|
name,
|
|
@@ -1967,7 +2701,9 @@ function loadEdgeEntity(dir, name) {
|
|
|
1967
2701
|
viewsPath,
|
|
1968
2702
|
sampleData,
|
|
1969
2703
|
allowedIn: meta?.allowedIn,
|
|
1970
|
-
targetGraph: topology.targetGraph ?? meta?.targetGraph
|
|
2704
|
+
targetGraph: topology.targetGraph ?? meta?.targetGraph,
|
|
2705
|
+
migrations,
|
|
2706
|
+
migrationWriteBack: meta?.migrationWriteBack
|
|
1971
2707
|
};
|
|
1972
2708
|
}
|
|
1973
2709
|
function getSubdirectories(dir) {
|
|
@@ -2478,6 +3214,7 @@ var QueryClient = class {
|
|
|
2478
3214
|
InvalidQueryError,
|
|
2479
3215
|
META_EDGE_TYPE,
|
|
2480
3216
|
META_NODE_TYPE,
|
|
3217
|
+
MigrationError,
|
|
2481
3218
|
NODE_TYPE_SCHEMA,
|
|
2482
3219
|
NodeNotFoundError,
|
|
2483
3220
|
QueryClient,
|
|
@@ -2485,13 +3222,17 @@ var QueryClient = class {
|
|
|
2485
3222
|
QuerySafetyError,
|
|
2486
3223
|
RegistryScopeError,
|
|
2487
3224
|
RegistryViolationError,
|
|
3225
|
+
SERIALIZATION_TAG,
|
|
2488
3226
|
TraversalError,
|
|
2489
3227
|
ValidationError,
|
|
2490
3228
|
analyzeQuerySafety,
|
|
3229
|
+
applyMigrationChain,
|
|
2491
3230
|
buildEdgeQueryPlan,
|
|
2492
3231
|
buildEdgeRecord,
|
|
2493
3232
|
buildNodeQueryPlan,
|
|
2494
3233
|
buildNodeRecord,
|
|
3234
|
+
compileMigrationFn,
|
|
3235
|
+
compileMigrations,
|
|
2495
3236
|
compileSchema,
|
|
2496
3237
|
computeEdgeDocId,
|
|
2497
3238
|
computeNodeDocId,
|
|
@@ -2501,18 +3242,27 @@ var QueryClient = class {
|
|
|
2501
3242
|
createRegistry,
|
|
2502
3243
|
createRegistryFromGraph,
|
|
2503
3244
|
createTraversal,
|
|
3245
|
+
defaultExecutor,
|
|
2504
3246
|
defineConfig,
|
|
2505
3247
|
defineViews,
|
|
3248
|
+
deserializeFirestoreTypes,
|
|
3249
|
+
destroySandboxWorker,
|
|
2506
3250
|
discoverEntities,
|
|
2507
3251
|
generateDeterministicUid,
|
|
2508
3252
|
generateId,
|
|
2509
3253
|
generateIndexConfig,
|
|
2510
3254
|
generateTypes,
|
|
2511
3255
|
isAncestorUid,
|
|
3256
|
+
isTaggedValue,
|
|
2512
3257
|
jsonSchemaToFieldMeta,
|
|
2513
3258
|
matchScope,
|
|
2514
3259
|
matchScopeAny,
|
|
3260
|
+
migrateRecord,
|
|
3261
|
+
migrateRecords,
|
|
3262
|
+
precompileSource,
|
|
2515
3263
|
resolveAncestorCollection,
|
|
2516
|
-
resolveView
|
|
3264
|
+
resolveView,
|
|
3265
|
+
serializeFirestoreTypes,
|
|
3266
|
+
validateMigrationChain
|
|
2517
3267
|
});
|
|
2518
3268
|
//# sourceMappingURL=index.cjs.map
|