@typicalday/firegraph 0.3.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 +115 -2
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/editor/server/index.mjs +831 -55
- package/dist/{index-CQkofEC_.d.cts → index-B9aodfYD.d.cts} +111 -2
- package/dist/{index-CQkofEC_.d.ts → index-B9aodfYD.d.ts} +111 -2
- package/dist/index.cjs +872 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +175 -4
- package/dist/index.d.ts +175 -4
- package/dist/index.js +853 -52
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -29324,7 +29324,7 @@ import { fileURLToPath } from "url";
|
|
|
29324
29324
|
import { Firestore } from "@google-cloud/firestore";
|
|
29325
29325
|
|
|
29326
29326
|
// src/client.ts
|
|
29327
|
-
import { FieldValue as
|
|
29327
|
+
import { FieldValue as FieldValue5 } from "@google-cloud/firestore";
|
|
29328
29328
|
|
|
29329
29329
|
// src/docid.ts
|
|
29330
29330
|
import { createHash } from "node:crypto";
|
|
@@ -29434,6 +29434,12 @@ var RegistryScopeError = class extends FiregraphError {
|
|
|
29434
29434
|
this.name = "RegistryScopeError";
|
|
29435
29435
|
}
|
|
29436
29436
|
};
|
|
29437
|
+
var MigrationError = class extends FiregraphError {
|
|
29438
|
+
constructor(message) {
|
|
29439
|
+
super(message, "MIGRATION_ERROR");
|
|
29440
|
+
this.name = "MigrationError";
|
|
29441
|
+
}
|
|
29442
|
+
};
|
|
29437
29443
|
|
|
29438
29444
|
// src/query.ts
|
|
29439
29445
|
function buildEdgeQueryPlan(params) {
|
|
@@ -29624,7 +29630,7 @@ function createPipelineQueryAdapter(db2, collectionPath) {
|
|
|
29624
29630
|
}
|
|
29625
29631
|
|
|
29626
29632
|
// src/transaction.ts
|
|
29627
|
-
import { FieldValue as
|
|
29633
|
+
import { FieldValue as FieldValue3 } from "@google-cloud/firestore";
|
|
29628
29634
|
|
|
29629
29635
|
// src/query-safety.ts
|
|
29630
29636
|
var SAFE_INDEX_PATTERNS = [
|
|
@@ -29674,24 +29680,248 @@ function analyzeQuerySafety(filters) {
|
|
|
29674
29680
|
};
|
|
29675
29681
|
}
|
|
29676
29682
|
|
|
29683
|
+
// src/serialization.ts
|
|
29684
|
+
import { Timestamp, GeoPoint, FieldValue as FieldValue2 } from "@google-cloud/firestore";
|
|
29685
|
+
var SERIALIZATION_TAG = "__firegraph_ser__";
|
|
29686
|
+
var KNOWN_TYPES = /* @__PURE__ */ new Set(["Timestamp", "GeoPoint", "VectorValue", "DocumentReference"]);
|
|
29687
|
+
var _docRefWarned = false;
|
|
29688
|
+
function isTaggedValue(value) {
|
|
29689
|
+
if (value === null || typeof value !== "object") return false;
|
|
29690
|
+
const tag = value[SERIALIZATION_TAG];
|
|
29691
|
+
return typeof tag === "string" && KNOWN_TYPES.has(tag);
|
|
29692
|
+
}
|
|
29693
|
+
function isTimestamp(value) {
|
|
29694
|
+
return value instanceof Timestamp;
|
|
29695
|
+
}
|
|
29696
|
+
function isGeoPoint(value) {
|
|
29697
|
+
return value instanceof GeoPoint;
|
|
29698
|
+
}
|
|
29699
|
+
function isDocumentReference(value) {
|
|
29700
|
+
if (value === null || typeof value !== "object") return false;
|
|
29701
|
+
const v = value;
|
|
29702
|
+
return typeof v.path === "string" && v.firestore !== void 0 && typeof v.id === "string" && v.constructor?.name === "DocumentReference";
|
|
29703
|
+
}
|
|
29704
|
+
function isVectorValue(value) {
|
|
29705
|
+
if (value === null || typeof value !== "object") return false;
|
|
29706
|
+
const v = value;
|
|
29707
|
+
return v.constructor?.name === "VectorValue" && Array.isArray(v._values);
|
|
29708
|
+
}
|
|
29709
|
+
function serializeFirestoreTypes(data) {
|
|
29710
|
+
return serializeValue(data);
|
|
29711
|
+
}
|
|
29712
|
+
function serializeValue(value) {
|
|
29713
|
+
if (value === null || value === void 0) return value;
|
|
29714
|
+
if (typeof value !== "object") return value;
|
|
29715
|
+
if (isTimestamp(value)) {
|
|
29716
|
+
return { [SERIALIZATION_TAG]: "Timestamp", seconds: value.seconds, nanoseconds: value.nanoseconds };
|
|
29717
|
+
}
|
|
29718
|
+
if (isGeoPoint(value)) {
|
|
29719
|
+
return { [SERIALIZATION_TAG]: "GeoPoint", latitude: value.latitude, longitude: value.longitude };
|
|
29720
|
+
}
|
|
29721
|
+
if (isDocumentReference(value)) {
|
|
29722
|
+
return { [SERIALIZATION_TAG]: "DocumentReference", path: value.path };
|
|
29723
|
+
}
|
|
29724
|
+
if (isVectorValue(value)) {
|
|
29725
|
+
const v = value;
|
|
29726
|
+
const values = typeof v.toArray === "function" ? v.toArray() : v._values;
|
|
29727
|
+
return { [SERIALIZATION_TAG]: "VectorValue", values: [...values] };
|
|
29728
|
+
}
|
|
29729
|
+
if (Array.isArray(value)) {
|
|
29730
|
+
return value.map(serializeValue);
|
|
29731
|
+
}
|
|
29732
|
+
const result = {};
|
|
29733
|
+
for (const key of Object.keys(value)) {
|
|
29734
|
+
result[key] = serializeValue(value[key]);
|
|
29735
|
+
}
|
|
29736
|
+
return result;
|
|
29737
|
+
}
|
|
29738
|
+
function deserializeFirestoreTypes(data, db2) {
|
|
29739
|
+
return deserializeValue(data, db2);
|
|
29740
|
+
}
|
|
29741
|
+
function deserializeValue(value, db2) {
|
|
29742
|
+
if (value === null || value === void 0) return value;
|
|
29743
|
+
if (typeof value !== "object") return value;
|
|
29744
|
+
if (isTimestamp(value) || isGeoPoint(value) || isDocumentReference(value) || isVectorValue(value)) {
|
|
29745
|
+
return value;
|
|
29746
|
+
}
|
|
29747
|
+
if (Array.isArray(value)) {
|
|
29748
|
+
return value.map((v) => deserializeValue(v, db2));
|
|
29749
|
+
}
|
|
29750
|
+
const obj = value;
|
|
29751
|
+
if (isTaggedValue(obj)) {
|
|
29752
|
+
const tag = obj[SERIALIZATION_TAG];
|
|
29753
|
+
switch (tag) {
|
|
29754
|
+
case "Timestamp":
|
|
29755
|
+
if (typeof obj.seconds !== "number" || typeof obj.nanoseconds !== "number") return obj;
|
|
29756
|
+
return new Timestamp(obj.seconds, obj.nanoseconds);
|
|
29757
|
+
case "GeoPoint":
|
|
29758
|
+
if (typeof obj.latitude !== "number" || typeof obj.longitude !== "number") return obj;
|
|
29759
|
+
return new GeoPoint(obj.latitude, obj.longitude);
|
|
29760
|
+
case "VectorValue":
|
|
29761
|
+
if (!Array.isArray(obj.values)) return obj;
|
|
29762
|
+
return FieldValue2.vector(obj.values);
|
|
29763
|
+
case "DocumentReference":
|
|
29764
|
+
if (typeof obj.path !== "string") return obj;
|
|
29765
|
+
if (db2) {
|
|
29766
|
+
return db2.doc(obj.path);
|
|
29767
|
+
}
|
|
29768
|
+
if (!_docRefWarned) {
|
|
29769
|
+
_docRefWarned = true;
|
|
29770
|
+
console.warn(
|
|
29771
|
+
"[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."
|
|
29772
|
+
);
|
|
29773
|
+
}
|
|
29774
|
+
return obj;
|
|
29775
|
+
default:
|
|
29776
|
+
return obj;
|
|
29777
|
+
}
|
|
29778
|
+
}
|
|
29779
|
+
const result = {};
|
|
29780
|
+
for (const key of Object.keys(obj)) {
|
|
29781
|
+
result[key] = deserializeValue(obj[key], db2);
|
|
29782
|
+
}
|
|
29783
|
+
return result;
|
|
29784
|
+
}
|
|
29785
|
+
|
|
29786
|
+
// src/migration.ts
|
|
29787
|
+
async function applyMigrationChain(data, currentVersion, targetVersion, migrations) {
|
|
29788
|
+
const sorted = [...migrations].sort((a, b) => a.fromVersion - b.fromVersion);
|
|
29789
|
+
let result = { ...data };
|
|
29790
|
+
let version2 = currentVersion;
|
|
29791
|
+
for (const step of sorted) {
|
|
29792
|
+
if (step.fromVersion === version2) {
|
|
29793
|
+
try {
|
|
29794
|
+
result = await step.up(result);
|
|
29795
|
+
} catch (err) {
|
|
29796
|
+
if (err instanceof MigrationError) throw err;
|
|
29797
|
+
throw new MigrationError(
|
|
29798
|
+
`Migration from v${step.fromVersion} to v${step.toVersion} failed: ${err.message}`
|
|
29799
|
+
);
|
|
29800
|
+
}
|
|
29801
|
+
if (!result || typeof result !== "object") {
|
|
29802
|
+
throw new MigrationError(
|
|
29803
|
+
`Migration from v${step.fromVersion} to v${step.toVersion} returned invalid data (expected object)`
|
|
29804
|
+
);
|
|
29805
|
+
}
|
|
29806
|
+
version2 = step.toVersion;
|
|
29807
|
+
}
|
|
29808
|
+
}
|
|
29809
|
+
if (version2 !== targetVersion) {
|
|
29810
|
+
throw new MigrationError(
|
|
29811
|
+
`Incomplete migration chain: reached v${version2} but target is v${targetVersion}`
|
|
29812
|
+
);
|
|
29813
|
+
}
|
|
29814
|
+
return result;
|
|
29815
|
+
}
|
|
29816
|
+
function validateMigrationChain(migrations, label) {
|
|
29817
|
+
if (migrations.length === 0) return;
|
|
29818
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29819
|
+
for (const step of migrations) {
|
|
29820
|
+
if (step.toVersion <= step.fromVersion) {
|
|
29821
|
+
throw new MigrationError(
|
|
29822
|
+
`${label}: migration step has toVersion (${step.toVersion}) <= fromVersion (${step.fromVersion})`
|
|
29823
|
+
);
|
|
29824
|
+
}
|
|
29825
|
+
if (seen.has(step.fromVersion)) {
|
|
29826
|
+
throw new MigrationError(
|
|
29827
|
+
`${label}: duplicate migration step for fromVersion ${step.fromVersion}`
|
|
29828
|
+
);
|
|
29829
|
+
}
|
|
29830
|
+
seen.add(step.fromVersion);
|
|
29831
|
+
}
|
|
29832
|
+
const sorted = [...migrations].sort((a, b) => a.fromVersion - b.fromVersion);
|
|
29833
|
+
const targetVersion = Math.max(...migrations.map((m) => m.toVersion));
|
|
29834
|
+
let version2 = 0;
|
|
29835
|
+
for (const step of sorted) {
|
|
29836
|
+
if (step.fromVersion === version2) {
|
|
29837
|
+
version2 = step.toVersion;
|
|
29838
|
+
} else if (step.fromVersion > version2) {
|
|
29839
|
+
throw new MigrationError(
|
|
29840
|
+
`${label}: migration chain has a gap \u2014 no step covers v${version2} \u2192 v${step.fromVersion}`
|
|
29841
|
+
);
|
|
29842
|
+
}
|
|
29843
|
+
}
|
|
29844
|
+
if (version2 !== targetVersion) {
|
|
29845
|
+
throw new MigrationError(
|
|
29846
|
+
`${label}: migration chain does not reach v${targetVersion} (stuck at v${version2})`
|
|
29847
|
+
);
|
|
29848
|
+
}
|
|
29849
|
+
}
|
|
29850
|
+
async function migrateRecord(record2, registry2, globalWriteBack = "off") {
|
|
29851
|
+
const entry = registry2.lookup(record2.aType, record2.axbType, record2.bType);
|
|
29852
|
+
if (!entry?.migrations?.length || !entry.schemaVersion) {
|
|
29853
|
+
return { record: record2, migrated: false, writeBack: "off" };
|
|
29854
|
+
}
|
|
29855
|
+
const currentVersion = record2.v ?? 0;
|
|
29856
|
+
if (currentVersion >= entry.schemaVersion) {
|
|
29857
|
+
return { record: record2, migrated: false, writeBack: "off" };
|
|
29858
|
+
}
|
|
29859
|
+
const migratedData = await applyMigrationChain(
|
|
29860
|
+
record2.data,
|
|
29861
|
+
currentVersion,
|
|
29862
|
+
entry.schemaVersion,
|
|
29863
|
+
entry.migrations
|
|
29864
|
+
);
|
|
29865
|
+
const writeBack = entry.migrationWriteBack ?? globalWriteBack ?? "off";
|
|
29866
|
+
return {
|
|
29867
|
+
record: { ...record2, data: migratedData, v: entry.schemaVersion },
|
|
29868
|
+
migrated: true,
|
|
29869
|
+
writeBack
|
|
29870
|
+
};
|
|
29871
|
+
}
|
|
29872
|
+
async function migrateRecords(records, registry2, globalWriteBack = "off") {
|
|
29873
|
+
return Promise.all(
|
|
29874
|
+
records.map((r) => migrateRecord(r, registry2, globalWriteBack))
|
|
29875
|
+
);
|
|
29876
|
+
}
|
|
29877
|
+
|
|
29677
29878
|
// src/transaction.ts
|
|
29678
29879
|
var GraphTransactionImpl = class {
|
|
29679
|
-
constructor(adapter, registry2, scanProtection = "error", scopePath = "") {
|
|
29880
|
+
constructor(adapter, registry2, scanProtection = "error", scopePath = "", globalWriteBack = "off", db2) {
|
|
29680
29881
|
this.adapter = adapter;
|
|
29681
29882
|
this.registry = registry2;
|
|
29682
29883
|
this.scanProtection = scanProtection;
|
|
29683
29884
|
this.scopePath = scopePath;
|
|
29885
|
+
this.globalWriteBack = globalWriteBack;
|
|
29886
|
+
this.db = db2;
|
|
29684
29887
|
}
|
|
29685
29888
|
async getNode(uid) {
|
|
29686
29889
|
const docId = computeNodeDocId(uid);
|
|
29687
|
-
|
|
29890
|
+
const record2 = await this.adapter.getDoc(docId);
|
|
29891
|
+
if (!record2 || !this.registry) return record2;
|
|
29892
|
+
const result = await migrateRecord(record2, this.registry, this.globalWriteBack);
|
|
29893
|
+
if (result.migrated && result.writeBack !== "off") {
|
|
29894
|
+
const update = {
|
|
29895
|
+
data: deserializeFirestoreTypes(result.record.data, this.db),
|
|
29896
|
+
updatedAt: FieldValue3.serverTimestamp()
|
|
29897
|
+
};
|
|
29898
|
+
if (result.record.v !== void 0) {
|
|
29899
|
+
update.v = result.record.v;
|
|
29900
|
+
}
|
|
29901
|
+
this.adapter.updateDoc(docId, update);
|
|
29902
|
+
}
|
|
29903
|
+
return result.record;
|
|
29688
29904
|
}
|
|
29689
29905
|
async getEdge(aUid, axbType, bUid) {
|
|
29690
29906
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
29691
|
-
|
|
29907
|
+
const record2 = await this.adapter.getDoc(docId);
|
|
29908
|
+
if (!record2 || !this.registry) return record2;
|
|
29909
|
+
const result = await migrateRecord(record2, this.registry, this.globalWriteBack);
|
|
29910
|
+
if (result.migrated && result.writeBack !== "off") {
|
|
29911
|
+
const update = {
|
|
29912
|
+
data: deserializeFirestoreTypes(result.record.data, this.db),
|
|
29913
|
+
updatedAt: FieldValue3.serverTimestamp()
|
|
29914
|
+
};
|
|
29915
|
+
if (result.record.v !== void 0) {
|
|
29916
|
+
update.v = result.record.v;
|
|
29917
|
+
}
|
|
29918
|
+
this.adapter.updateDoc(docId, update);
|
|
29919
|
+
}
|
|
29920
|
+
return result.record;
|
|
29692
29921
|
}
|
|
29693
29922
|
async edgeExists(aUid, axbType, bUid) {
|
|
29694
|
-
const
|
|
29923
|
+
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
29924
|
+
const record2 = await this.adapter.getDoc(docId);
|
|
29695
29925
|
return record2 !== null;
|
|
29696
29926
|
}
|
|
29697
29927
|
checkQuerySafety(filters, allowCollectionScan) {
|
|
@@ -29705,21 +29935,45 @@ var GraphTransactionImpl = class {
|
|
|
29705
29935
|
}
|
|
29706
29936
|
async findEdges(params) {
|
|
29707
29937
|
const plan = buildEdgeQueryPlan(params);
|
|
29938
|
+
let records;
|
|
29708
29939
|
if (plan.strategy === "get") {
|
|
29709
29940
|
const record2 = await this.adapter.getDoc(plan.docId);
|
|
29710
|
-
|
|
29941
|
+
records = record2 ? [record2] : [];
|
|
29942
|
+
} else {
|
|
29943
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
29944
|
+
records = await this.adapter.query(plan.filters, plan.options);
|
|
29711
29945
|
}
|
|
29712
|
-
this.
|
|
29713
|
-
return this.adapter.query(plan.filters, plan.options);
|
|
29946
|
+
return this.applyMigrations(records);
|
|
29714
29947
|
}
|
|
29715
29948
|
async findNodes(params) {
|
|
29716
29949
|
const plan = buildNodeQueryPlan(params);
|
|
29950
|
+
let records;
|
|
29717
29951
|
if (plan.strategy === "get") {
|
|
29718
29952
|
const record2 = await this.adapter.getDoc(plan.docId);
|
|
29719
|
-
|
|
29953
|
+
records = record2 ? [record2] : [];
|
|
29954
|
+
} else {
|
|
29955
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
29956
|
+
records = await this.adapter.query(plan.filters, plan.options);
|
|
29957
|
+
}
|
|
29958
|
+
return this.applyMigrations(records);
|
|
29959
|
+
}
|
|
29960
|
+
async applyMigrations(records) {
|
|
29961
|
+
if (!this.registry || records.length === 0) return records;
|
|
29962
|
+
const results = await migrateRecords(records, this.registry, this.globalWriteBack);
|
|
29963
|
+
for (const result of results) {
|
|
29964
|
+
if (result.migrated && result.writeBack !== "off") {
|
|
29965
|
+
const docId = result.record.axbType === NODE_RELATION ? computeNodeDocId(result.record.aUid) : computeEdgeDocId(result.record.aUid, result.record.axbType, result.record.bUid);
|
|
29966
|
+
const update = {
|
|
29967
|
+
data: deserializeFirestoreTypes(result.record.data, this.db),
|
|
29968
|
+
updatedAt: FieldValue3.serverTimestamp()
|
|
29969
|
+
};
|
|
29970
|
+
if (result.record.v !== void 0) {
|
|
29971
|
+
update.v = result.record.v;
|
|
29972
|
+
}
|
|
29973
|
+
this.adapter.updateDoc(docId, update);
|
|
29974
|
+
}
|
|
29720
29975
|
}
|
|
29721
|
-
|
|
29722
|
-
return this.adapter.query(plan.filters, plan.options);
|
|
29976
|
+
return results.map((r) => r.record);
|
|
29723
29977
|
}
|
|
29724
29978
|
async putNode(aType, uid, data) {
|
|
29725
29979
|
if (this.registry) {
|
|
@@ -29727,6 +29981,12 @@ var GraphTransactionImpl = class {
|
|
|
29727
29981
|
}
|
|
29728
29982
|
const docId = computeNodeDocId(uid);
|
|
29729
29983
|
const record2 = buildNodeRecord(aType, uid, data);
|
|
29984
|
+
if (this.registry) {
|
|
29985
|
+
const entry = this.registry.lookup(aType, NODE_RELATION, aType);
|
|
29986
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
29987
|
+
record2.v = entry.schemaVersion;
|
|
29988
|
+
}
|
|
29989
|
+
}
|
|
29730
29990
|
this.adapter.setDoc(docId, record2);
|
|
29731
29991
|
}
|
|
29732
29992
|
async putEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
@@ -29735,13 +29995,19 @@ var GraphTransactionImpl = class {
|
|
|
29735
29995
|
}
|
|
29736
29996
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
29737
29997
|
const record2 = buildEdgeRecord(aType, aUid, axbType, bType, bUid, data);
|
|
29998
|
+
if (this.registry) {
|
|
29999
|
+
const entry = this.registry.lookup(aType, axbType, bType);
|
|
30000
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
30001
|
+
record2.v = entry.schemaVersion;
|
|
30002
|
+
}
|
|
30003
|
+
}
|
|
29738
30004
|
this.adapter.setDoc(docId, record2);
|
|
29739
30005
|
}
|
|
29740
30006
|
async updateNode(uid, data) {
|
|
29741
30007
|
const docId = computeNodeDocId(uid);
|
|
29742
30008
|
this.adapter.updateDoc(docId, {
|
|
29743
30009
|
...data,
|
|
29744
|
-
updatedAt:
|
|
30010
|
+
updatedAt: FieldValue3.serverTimestamp()
|
|
29745
30011
|
});
|
|
29746
30012
|
}
|
|
29747
30013
|
async removeNode(uid) {
|
|
@@ -29755,7 +30021,7 @@ var GraphTransactionImpl = class {
|
|
|
29755
30021
|
};
|
|
29756
30022
|
|
|
29757
30023
|
// src/batch.ts
|
|
29758
|
-
import { FieldValue as
|
|
30024
|
+
import { FieldValue as FieldValue4 } from "@google-cloud/firestore";
|
|
29759
30025
|
var GraphBatchImpl = class {
|
|
29760
30026
|
constructor(adapter, registry2, scopePath = "") {
|
|
29761
30027
|
this.adapter = adapter;
|
|
@@ -29768,6 +30034,12 @@ var GraphBatchImpl = class {
|
|
|
29768
30034
|
}
|
|
29769
30035
|
const docId = computeNodeDocId(uid);
|
|
29770
30036
|
const record2 = buildNodeRecord(aType, uid, data);
|
|
30037
|
+
if (this.registry) {
|
|
30038
|
+
const entry = this.registry.lookup(aType, NODE_RELATION, aType);
|
|
30039
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
30040
|
+
record2.v = entry.schemaVersion;
|
|
30041
|
+
}
|
|
30042
|
+
}
|
|
29771
30043
|
this.adapter.setDoc(docId, record2);
|
|
29772
30044
|
}
|
|
29773
30045
|
async putEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
@@ -29776,13 +30048,19 @@ var GraphBatchImpl = class {
|
|
|
29776
30048
|
}
|
|
29777
30049
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
29778
30050
|
const record2 = buildEdgeRecord(aType, aUid, axbType, bType, bUid, data);
|
|
30051
|
+
if (this.registry) {
|
|
30052
|
+
const entry = this.registry.lookup(aType, axbType, bType);
|
|
30053
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
30054
|
+
record2.v = entry.schemaVersion;
|
|
30055
|
+
}
|
|
30056
|
+
}
|
|
29779
30057
|
this.adapter.setDoc(docId, record2);
|
|
29780
30058
|
}
|
|
29781
30059
|
async updateNode(uid, data) {
|
|
29782
30060
|
const docId = computeNodeDocId(uid);
|
|
29783
30061
|
this.adapter.updateDoc(docId, {
|
|
29784
30062
|
...data,
|
|
29785
|
-
updatedAt:
|
|
30063
|
+
updatedAt: FieldValue4.serverTimestamp()
|
|
29786
30064
|
});
|
|
29787
30065
|
}
|
|
29788
30066
|
async removeNode(uid) {
|
|
@@ -29941,7 +30219,7 @@ async function removeNodeCascade(db2, collectionPath, reader, uid, options) {
|
|
|
29941
30219
|
}
|
|
29942
30220
|
|
|
29943
30221
|
// src/dynamic-registry.ts
|
|
29944
|
-
import { createHash as
|
|
30222
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
29945
30223
|
|
|
29946
30224
|
// src/json-schema.ts
|
|
29947
30225
|
var import_ajv = __toESM(require_ajv(), 1);
|
|
@@ -30072,6 +30350,9 @@ function matchSegments(path4, pi, pattern, qi) {
|
|
|
30072
30350
|
function tripleKey(aType, axbType, bType) {
|
|
30073
30351
|
return `${aType}:${axbType}:${bType}`;
|
|
30074
30352
|
}
|
|
30353
|
+
function tripleKeyFor(e) {
|
|
30354
|
+
return tripleKey(e.aType, e.axbType, e.bType);
|
|
30355
|
+
}
|
|
30075
30356
|
function createRegistry(input) {
|
|
30076
30357
|
const map2 = /* @__PURE__ */ new Map();
|
|
30077
30358
|
let entries;
|
|
@@ -30087,6 +30368,13 @@ function createRegistry(input) {
|
|
|
30087
30368
|
`Entry (${entry.aType}) -[${entry.axbType}]-> (${entry.bType}) has invalid targetGraph "${entry.targetGraph}" \u2014 must be a single segment (no "/")`
|
|
30088
30369
|
);
|
|
30089
30370
|
}
|
|
30371
|
+
if (entry.migrations?.length) {
|
|
30372
|
+
const label = `Entry (${entry.aType}) -[${entry.axbType}]-> (${entry.bType})`;
|
|
30373
|
+
validateMigrationChain(entry.migrations, label);
|
|
30374
|
+
entry.schemaVersion = Math.max(...entry.migrations.map((m) => m.toVersion));
|
|
30375
|
+
} else {
|
|
30376
|
+
entry.schemaVersion = void 0;
|
|
30377
|
+
}
|
|
30090
30378
|
const key = tripleKey(entry.aType, entry.axbType, entry.bType);
|
|
30091
30379
|
const validator = entry.jsonSchema ? compileSchema(entry.jsonSchema, `(${entry.aType}) -[${entry.axbType}]-> (${entry.bType})`) : void 0;
|
|
30092
30380
|
map2.set(key, { entry, validate: validator });
|
|
@@ -30138,6 +30426,45 @@ function createRegistry(input) {
|
|
|
30138
30426
|
}
|
|
30139
30427
|
};
|
|
30140
30428
|
}
|
|
30429
|
+
function createMergedRegistry(base, extension) {
|
|
30430
|
+
const baseKeys = new Set(base.entries().map(tripleKeyFor));
|
|
30431
|
+
return {
|
|
30432
|
+
lookup(aType, axbType, bType) {
|
|
30433
|
+
return base.lookup(aType, axbType, bType) ?? extension.lookup(aType, axbType, bType);
|
|
30434
|
+
},
|
|
30435
|
+
lookupByAxbType(axbType) {
|
|
30436
|
+
const baseResults = base.lookupByAxbType(axbType);
|
|
30437
|
+
const extResults = extension.lookupByAxbType(axbType);
|
|
30438
|
+
if (extResults.length === 0) return baseResults;
|
|
30439
|
+
if (baseResults.length === 0) return extResults;
|
|
30440
|
+
const seen = new Set(baseResults.map(tripleKeyFor));
|
|
30441
|
+
const merged = [...baseResults];
|
|
30442
|
+
for (const entry of extResults) {
|
|
30443
|
+
if (!seen.has(tripleKeyFor(entry))) {
|
|
30444
|
+
merged.push(entry);
|
|
30445
|
+
}
|
|
30446
|
+
}
|
|
30447
|
+
return Object.freeze(merged);
|
|
30448
|
+
},
|
|
30449
|
+
validate(aType, axbType, bType, data, scopePath) {
|
|
30450
|
+
if (baseKeys.has(tripleKey(aType, axbType, bType))) {
|
|
30451
|
+
return base.validate(aType, axbType, bType, data, scopePath);
|
|
30452
|
+
}
|
|
30453
|
+
return extension.validate(aType, axbType, bType, data, scopePath);
|
|
30454
|
+
},
|
|
30455
|
+
entries() {
|
|
30456
|
+
const extEntries = extension.entries();
|
|
30457
|
+
if (extEntries.length === 0) return base.entries();
|
|
30458
|
+
const merged = [...base.entries()];
|
|
30459
|
+
for (const entry of extEntries) {
|
|
30460
|
+
if (!baseKeys.has(tripleKeyFor(entry))) {
|
|
30461
|
+
merged.push(entry);
|
|
30462
|
+
}
|
|
30463
|
+
}
|
|
30464
|
+
return Object.freeze(merged);
|
|
30465
|
+
}
|
|
30466
|
+
};
|
|
30467
|
+
}
|
|
30141
30468
|
function discoveryToEntries(discovery) {
|
|
30142
30469
|
const entries = [];
|
|
30143
30470
|
for (const [name, entity] of discovery.nodes) {
|
|
@@ -30149,7 +30476,9 @@ function discoveryToEntries(discovery) {
|
|
|
30149
30476
|
description: entity.description,
|
|
30150
30477
|
titleField: entity.titleField,
|
|
30151
30478
|
subtitleField: entity.subtitleField,
|
|
30152
|
-
allowedIn: entity.allowedIn
|
|
30479
|
+
allowedIn: entity.allowedIn,
|
|
30480
|
+
migrations: entity.migrations,
|
|
30481
|
+
migrationWriteBack: entity.migrationWriteBack
|
|
30153
30482
|
});
|
|
30154
30483
|
}
|
|
30155
30484
|
for (const [axbType, entity] of discovery.edges) {
|
|
@@ -30175,7 +30504,9 @@ function discoveryToEntries(discovery) {
|
|
|
30175
30504
|
titleField: entity.titleField,
|
|
30176
30505
|
subtitleField: entity.subtitleField,
|
|
30177
30506
|
allowedIn: entity.allowedIn,
|
|
30178
|
-
targetGraph: resolvedTargetGraph
|
|
30507
|
+
targetGraph: resolvedTargetGraph,
|
|
30508
|
+
migrations: entity.migrations,
|
|
30509
|
+
migrationWriteBack: entity.migrationWriteBack
|
|
30179
30510
|
});
|
|
30180
30511
|
}
|
|
30181
30512
|
}
|
|
@@ -30183,9 +30514,248 @@ function discoveryToEntries(discovery) {
|
|
|
30183
30514
|
return entries;
|
|
30184
30515
|
}
|
|
30185
30516
|
|
|
30517
|
+
// src/sandbox.ts
|
|
30518
|
+
import { Worker } from "node:worker_threads";
|
|
30519
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
30520
|
+
var _worker = null;
|
|
30521
|
+
var _requestId = 0;
|
|
30522
|
+
var _pending = /* @__PURE__ */ new Map();
|
|
30523
|
+
var WORKER_SOURCE = [
|
|
30524
|
+
`'use strict';`,
|
|
30525
|
+
`var _wt = require('node:worker_threads');`,
|
|
30526
|
+
`var _mod = require('node:module');`,
|
|
30527
|
+
`var _crypto = require('node:crypto');`,
|
|
30528
|
+
`var parentPort = _wt.parentPort;`,
|
|
30529
|
+
`var workerData = _wt.workerData;`,
|
|
30530
|
+
``,
|
|
30531
|
+
`// Load SES using the parent module's resolution context`,
|
|
30532
|
+
`var esmRequire = _mod.createRequire(workerData.parentUrl);`,
|
|
30533
|
+
`esmRequire('ses');`,
|
|
30534
|
+
``,
|
|
30535
|
+
`lockdown({`,
|
|
30536
|
+
` errorTaming: 'unsafe',`,
|
|
30537
|
+
` consoleTaming: 'unsafe',`,
|
|
30538
|
+
` evalTaming: 'safe-eval',`,
|
|
30539
|
+
` overrideTaming: 'moderate',`,
|
|
30540
|
+
` stackFiltering: 'verbose'`,
|
|
30541
|
+
`});`,
|
|
30542
|
+
``,
|
|
30543
|
+
`// Defense-in-depth: verify lockdown() actually hardened JSON.`,
|
|
30544
|
+
`if (!Object.isFrozen(JSON)) {`,
|
|
30545
|
+
` throw new Error('SES lockdown failed: JSON is not frozen');`,
|
|
30546
|
+
`}`,
|
|
30547
|
+
``,
|
|
30548
|
+
`var cache = new Map();`,
|
|
30549
|
+
``,
|
|
30550
|
+
`function hashSource(s) {`,
|
|
30551
|
+
` return _crypto.createHash('sha256').update(s).digest('hex');`,
|
|
30552
|
+
`}`,
|
|
30553
|
+
``,
|
|
30554
|
+
`function buildWrapper(source) {`,
|
|
30555
|
+
` return '(function() {' +`,
|
|
30556
|
+
` ' var fn = (' + source + ');\\n' +`,
|
|
30557
|
+
` ' if (typeof fn !== "function") return null;\\n' +`,
|
|
30558
|
+
` ' return function(jsonIn) {\\n' +`,
|
|
30559
|
+
` ' var data = JSON.parse(jsonIn);\\n' +`,
|
|
30560
|
+
` ' var result = fn(data);\\n' +`,
|
|
30561
|
+
` ' if (result !== null && typeof result === "object" && typeof result.then === "function") {\\n' +`,
|
|
30562
|
+
` ' return result.then(function(r) { return JSON.stringify(r); });\\n' +`,
|
|
30563
|
+
` ' }\\n' +`,
|
|
30564
|
+
` ' return JSON.stringify(result);\\n' +`,
|
|
30565
|
+
` ' };\\n' +`,
|
|
30566
|
+
` '})()';`,
|
|
30567
|
+
`}`,
|
|
30568
|
+
``,
|
|
30569
|
+
`function compileSource(source) {`,
|
|
30570
|
+
` var key = hashSource(source);`,
|
|
30571
|
+
` var cached = cache.get(key);`,
|
|
30572
|
+
` if (cached) return cached;`,
|
|
30573
|
+
``,
|
|
30574
|
+
` var compartmentFn;`,
|
|
30575
|
+
` try {`,
|
|
30576
|
+
` var c = new Compartment({ JSON: JSON });`,
|
|
30577
|
+
` compartmentFn = c.evaluate(buildWrapper(source));`,
|
|
30578
|
+
` } catch (err) {`,
|
|
30579
|
+
` throw new Error('Failed to compile migration source: ' + (err.message || String(err)));`,
|
|
30580
|
+
` }`,
|
|
30581
|
+
``,
|
|
30582
|
+
` if (typeof compartmentFn !== 'function') {`,
|
|
30583
|
+
` throw new Error('Migration source did not produce a function: ' + source.slice(0, 80));`,
|
|
30584
|
+
` }`,
|
|
30585
|
+
``,
|
|
30586
|
+
` cache.set(key, compartmentFn);`,
|
|
30587
|
+
` return compartmentFn;`,
|
|
30588
|
+
`}`,
|
|
30589
|
+
``,
|
|
30590
|
+
`parentPort.on('message', function(msg) {`,
|
|
30591
|
+
` var id = msg.id;`,
|
|
30592
|
+
` try {`,
|
|
30593
|
+
` if (msg.type === 'compile') {`,
|
|
30594
|
+
` compileSource(msg.source);`,
|
|
30595
|
+
` parentPort.postMessage({ id: id, type: 'compiled' });`,
|
|
30596
|
+
` return;`,
|
|
30597
|
+
` }`,
|
|
30598
|
+
` if (msg.type === 'execute') {`,
|
|
30599
|
+
` var fn = compileSource(msg.source);`,
|
|
30600
|
+
` var raw;`,
|
|
30601
|
+
` try {`,
|
|
30602
|
+
` raw = fn(msg.jsonData);`,
|
|
30603
|
+
` } catch (err) {`,
|
|
30604
|
+
` parentPort.postMessage({ id: id, type: 'error', message: 'Migration function threw: ' + (err.message || String(err)) });`,
|
|
30605
|
+
` return;`,
|
|
30606
|
+
` }`,
|
|
30607
|
+
` if (raw !== null && typeof raw === 'object' && typeof raw.then === 'function') {`,
|
|
30608
|
+
` raw.then(`,
|
|
30609
|
+
` function(jsonResult) {`,
|
|
30610
|
+
` if (jsonResult === undefined || jsonResult === null) {`,
|
|
30611
|
+
` parentPort.postMessage({ id: id, type: 'error', message: 'Migration returned a non-JSON-serializable value' });`,
|
|
30612
|
+
` } else {`,
|
|
30613
|
+
` parentPort.postMessage({ id: id, type: 'result', jsonResult: jsonResult });`,
|
|
30614
|
+
` }`,
|
|
30615
|
+
` },`,
|
|
30616
|
+
` function(err) {`,
|
|
30617
|
+
` parentPort.postMessage({ id: id, type: 'error', message: 'Async migration function threw: ' + (err.message || String(err)) });`,
|
|
30618
|
+
` }`,
|
|
30619
|
+
` );`,
|
|
30620
|
+
` return;`,
|
|
30621
|
+
` }`,
|
|
30622
|
+
` if (raw === undefined || raw === null) {`,
|
|
30623
|
+
` parentPort.postMessage({ id: id, type: 'error', message: 'Migration returned a non-JSON-serializable value' });`,
|
|
30624
|
+
` } else {`,
|
|
30625
|
+
` parentPort.postMessage({ id: id, type: 'result', jsonResult: raw });`,
|
|
30626
|
+
` }`,
|
|
30627
|
+
` }`,
|
|
30628
|
+
` } catch (err) {`,
|
|
30629
|
+
` parentPort.postMessage({ id: id, type: 'error', message: err.message || String(err) });`,
|
|
30630
|
+
` }`,
|
|
30631
|
+
`});`
|
|
30632
|
+
].join("\n");
|
|
30633
|
+
function ensureWorker() {
|
|
30634
|
+
if (_worker) return _worker;
|
|
30635
|
+
_worker = new Worker(WORKER_SOURCE, {
|
|
30636
|
+
eval: true,
|
|
30637
|
+
workerData: { parentUrl: import.meta.url }
|
|
30638
|
+
});
|
|
30639
|
+
_worker.unref();
|
|
30640
|
+
_worker.on("message", (msg) => {
|
|
30641
|
+
if (msg.id === void 0) return;
|
|
30642
|
+
const pending = _pending.get(msg.id);
|
|
30643
|
+
if (!pending) return;
|
|
30644
|
+
_pending.delete(msg.id);
|
|
30645
|
+
if (msg.type === "error") {
|
|
30646
|
+
pending.reject(new MigrationError(msg.message ?? "Unknown sandbox error"));
|
|
30647
|
+
} else {
|
|
30648
|
+
pending.resolve(msg);
|
|
30649
|
+
}
|
|
30650
|
+
});
|
|
30651
|
+
_worker.on("error", (err) => {
|
|
30652
|
+
for (const [, p] of _pending) {
|
|
30653
|
+
p.reject(new MigrationError(`Sandbox worker error: ${err.message}`));
|
|
30654
|
+
}
|
|
30655
|
+
_pending.clear();
|
|
30656
|
+
_worker = null;
|
|
30657
|
+
});
|
|
30658
|
+
_worker.on("exit", (code) => {
|
|
30659
|
+
if (_pending.size > 0) {
|
|
30660
|
+
for (const [, p] of _pending) {
|
|
30661
|
+
p.reject(new MigrationError(`Sandbox worker exited with code ${code}`));
|
|
30662
|
+
}
|
|
30663
|
+
_pending.clear();
|
|
30664
|
+
}
|
|
30665
|
+
_worker = null;
|
|
30666
|
+
});
|
|
30667
|
+
return _worker;
|
|
30668
|
+
}
|
|
30669
|
+
function sendToWorker(msg) {
|
|
30670
|
+
const worker = ensureWorker();
|
|
30671
|
+
if (_requestId >= Number.MAX_SAFE_INTEGER) _requestId = 0;
|
|
30672
|
+
const id = ++_requestId;
|
|
30673
|
+
return new Promise((resolve2, reject) => {
|
|
30674
|
+
_pending.set(id, { resolve: resolve2, reject });
|
|
30675
|
+
worker.postMessage({ ...msg, id });
|
|
30676
|
+
});
|
|
30677
|
+
}
|
|
30678
|
+
var compiledCache = /* @__PURE__ */ new WeakMap();
|
|
30679
|
+
function getExecutorCache(executor) {
|
|
30680
|
+
let cache = compiledCache.get(executor);
|
|
30681
|
+
if (!cache) {
|
|
30682
|
+
cache = /* @__PURE__ */ new Map();
|
|
30683
|
+
compiledCache.set(executor, cache);
|
|
30684
|
+
}
|
|
30685
|
+
return cache;
|
|
30686
|
+
}
|
|
30687
|
+
function hashSource(source) {
|
|
30688
|
+
return createHash2("sha256").update(source).digest("hex");
|
|
30689
|
+
}
|
|
30690
|
+
function defaultExecutor(source) {
|
|
30691
|
+
ensureWorker();
|
|
30692
|
+
return (data) => {
|
|
30693
|
+
const jsonData = JSON.stringify(serializeFirestoreTypes(data));
|
|
30694
|
+
return sendToWorker({ type: "execute", source, jsonData }).then(
|
|
30695
|
+
(response) => {
|
|
30696
|
+
if (response.jsonResult === void 0 || response.jsonResult === null) {
|
|
30697
|
+
throw new MigrationError("Migration returned a non-JSON-serializable value");
|
|
30698
|
+
}
|
|
30699
|
+
try {
|
|
30700
|
+
return deserializeFirestoreTypes(JSON.parse(response.jsonResult));
|
|
30701
|
+
} catch {
|
|
30702
|
+
throw new MigrationError("Migration returned a non-JSON-serializable value");
|
|
30703
|
+
}
|
|
30704
|
+
}
|
|
30705
|
+
);
|
|
30706
|
+
};
|
|
30707
|
+
}
|
|
30708
|
+
async function precompileSource(source, executor) {
|
|
30709
|
+
if (executor && executor !== defaultExecutor) {
|
|
30710
|
+
try {
|
|
30711
|
+
executor(source);
|
|
30712
|
+
} catch (err) {
|
|
30713
|
+
if (err instanceof MigrationError) throw err;
|
|
30714
|
+
throw new MigrationError(
|
|
30715
|
+
`Failed to compile migration source: ${err.message}`
|
|
30716
|
+
);
|
|
30717
|
+
}
|
|
30718
|
+
return;
|
|
30719
|
+
}
|
|
30720
|
+
await sendToWorker({ type: "compile", source });
|
|
30721
|
+
}
|
|
30722
|
+
function compileMigrationFn(source, executor = defaultExecutor) {
|
|
30723
|
+
const cache = getExecutorCache(executor);
|
|
30724
|
+
const key = hashSource(source);
|
|
30725
|
+
const cached2 = cache.get(key);
|
|
30726
|
+
if (cached2) return cached2;
|
|
30727
|
+
try {
|
|
30728
|
+
const fn = executor(source);
|
|
30729
|
+
cache.set(key, fn);
|
|
30730
|
+
return fn;
|
|
30731
|
+
} catch (err) {
|
|
30732
|
+
if (err instanceof MigrationError) throw err;
|
|
30733
|
+
throw new MigrationError(
|
|
30734
|
+
`Failed to compile migration source: ${err.message}`
|
|
30735
|
+
);
|
|
30736
|
+
}
|
|
30737
|
+
}
|
|
30738
|
+
function compileMigrations(stored, executor) {
|
|
30739
|
+
return stored.map((step) => ({
|
|
30740
|
+
fromVersion: step.fromVersion,
|
|
30741
|
+
toVersion: step.toVersion,
|
|
30742
|
+
up: compileMigrationFn(step.up, executor)
|
|
30743
|
+
}));
|
|
30744
|
+
}
|
|
30745
|
+
|
|
30186
30746
|
// src/dynamic-registry.ts
|
|
30187
30747
|
var META_NODE_TYPE = "nodeType";
|
|
30188
30748
|
var META_EDGE_TYPE = "edgeType";
|
|
30749
|
+
var STORED_MIGRATION_STEP_SCHEMA = {
|
|
30750
|
+
type: "object",
|
|
30751
|
+
required: ["fromVersion", "toVersion", "up"],
|
|
30752
|
+
properties: {
|
|
30753
|
+
fromVersion: { type: "integer", minimum: 0 },
|
|
30754
|
+
toVersion: { type: "integer", minimum: 1 },
|
|
30755
|
+
up: { type: "string", minLength: 1 }
|
|
30756
|
+
},
|
|
30757
|
+
additionalProperties: false
|
|
30758
|
+
};
|
|
30189
30759
|
var NODE_TYPE_SCHEMA = {
|
|
30190
30760
|
type: "object",
|
|
30191
30761
|
required: ["name", "jsonSchema"],
|
|
@@ -30197,7 +30767,10 @@ var NODE_TYPE_SCHEMA = {
|
|
|
30197
30767
|
subtitleField: { type: "string" },
|
|
30198
30768
|
viewTemplate: { type: "string" },
|
|
30199
30769
|
viewCss: { type: "string" },
|
|
30200
|
-
allowedIn: { type: "array", items: { type: "string", minLength: 1 } }
|
|
30770
|
+
allowedIn: { type: "array", items: { type: "string", minLength: 1 } },
|
|
30771
|
+
schemaVersion: { type: "integer", minimum: 0 },
|
|
30772
|
+
migrations: { type: "array", items: STORED_MIGRATION_STEP_SCHEMA },
|
|
30773
|
+
migrationWriteBack: { type: "string", enum: ["off", "eager", "background"] }
|
|
30201
30774
|
},
|
|
30202
30775
|
additionalProperties: false
|
|
30203
30776
|
};
|
|
@@ -30226,7 +30799,10 @@ var EDGE_TYPE_SCHEMA = {
|
|
|
30226
30799
|
viewTemplate: { type: "string" },
|
|
30227
30800
|
viewCss: { type: "string" },
|
|
30228
30801
|
allowedIn: { type: "array", items: { type: "string", minLength: 1 } },
|
|
30229
|
-
targetGraph: { type: "string", minLength: 1, pattern: "^[^/]+$" }
|
|
30802
|
+
targetGraph: { type: "string", minLength: 1, pattern: "^[^/]+$" },
|
|
30803
|
+
schemaVersion: { type: "integer", minimum: 0 },
|
|
30804
|
+
migrations: { type: "array", items: STORED_MIGRATION_STEP_SCHEMA },
|
|
30805
|
+
migrationWriteBack: { type: "string", enum: ["off", "eager", "background"] }
|
|
30230
30806
|
},
|
|
30231
30807
|
additionalProperties: false
|
|
30232
30808
|
};
|
|
@@ -30250,15 +30826,33 @@ function createBootstrapRegistry() {
|
|
|
30250
30826
|
return createRegistry([...BOOTSTRAP_ENTRIES]);
|
|
30251
30827
|
}
|
|
30252
30828
|
function generateDeterministicUid(metaType, name) {
|
|
30253
|
-
const hash2 =
|
|
30829
|
+
const hash2 = createHash3("sha256").update(`${metaType}:${name}`).digest("base64url");
|
|
30254
30830
|
return hash2.slice(0, 21);
|
|
30255
30831
|
}
|
|
30256
|
-
async function createRegistryFromGraph(reader) {
|
|
30832
|
+
async function createRegistryFromGraph(reader, executor) {
|
|
30257
30833
|
const [nodeTypes, edgeTypes] = await Promise.all([
|
|
30258
30834
|
reader.findNodes({ aType: META_NODE_TYPE }),
|
|
30259
30835
|
reader.findNodes({ aType: META_EDGE_TYPE })
|
|
30260
30836
|
]);
|
|
30261
30837
|
const entries = [...BOOTSTRAP_ENTRIES];
|
|
30838
|
+
const prevalidations = [];
|
|
30839
|
+
for (const record2 of nodeTypes) {
|
|
30840
|
+
const data = record2.data;
|
|
30841
|
+
if (data.migrations) {
|
|
30842
|
+
for (const m of data.migrations) {
|
|
30843
|
+
prevalidations.push(precompileSource(m.up, executor));
|
|
30844
|
+
}
|
|
30845
|
+
}
|
|
30846
|
+
}
|
|
30847
|
+
for (const record2 of edgeTypes) {
|
|
30848
|
+
const data = record2.data;
|
|
30849
|
+
if (data.migrations) {
|
|
30850
|
+
for (const m of data.migrations) {
|
|
30851
|
+
prevalidations.push(precompileSource(m.up, executor));
|
|
30852
|
+
}
|
|
30853
|
+
}
|
|
30854
|
+
}
|
|
30855
|
+
await Promise.all(prevalidations);
|
|
30262
30856
|
for (const record2 of nodeTypes) {
|
|
30263
30857
|
const data = record2.data;
|
|
30264
30858
|
entries.push({
|
|
@@ -30269,13 +30863,16 @@ async function createRegistryFromGraph(reader) {
|
|
|
30269
30863
|
description: data.description,
|
|
30270
30864
|
titleField: data.titleField,
|
|
30271
30865
|
subtitleField: data.subtitleField,
|
|
30272
|
-
allowedIn: data.allowedIn
|
|
30866
|
+
allowedIn: data.allowedIn,
|
|
30867
|
+
migrations: data.migrations ? compileMigrations(data.migrations, executor) : void 0,
|
|
30868
|
+
migrationWriteBack: data.migrationWriteBack
|
|
30273
30869
|
});
|
|
30274
30870
|
}
|
|
30275
30871
|
for (const record2 of edgeTypes) {
|
|
30276
30872
|
const data = record2.data;
|
|
30277
30873
|
const fromTypes = Array.isArray(data.from) ? data.from : [data.from];
|
|
30278
30874
|
const toTypes = Array.isArray(data.to) ? data.to : [data.to];
|
|
30875
|
+
const compiledMigrations = data.migrations ? compileMigrations(data.migrations, executor) : void 0;
|
|
30279
30876
|
for (const aType of fromTypes) {
|
|
30280
30877
|
for (const bType of toTypes) {
|
|
30281
30878
|
entries.push({
|
|
@@ -30288,7 +30885,9 @@ async function createRegistryFromGraph(reader) {
|
|
|
30288
30885
|
titleField: data.titleField,
|
|
30289
30886
|
subtitleField: data.subtitleField,
|
|
30290
30887
|
allowedIn: data.allowedIn,
|
|
30291
|
-
targetGraph: data.targetGraph
|
|
30888
|
+
targetGraph: data.targetGraph,
|
|
30889
|
+
migrations: compiledMigrations,
|
|
30890
|
+
migrationWriteBack: data.migrationWriteBack
|
|
30292
30891
|
});
|
|
30293
30892
|
}
|
|
30294
30893
|
}
|
|
@@ -30304,14 +30903,14 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30304
30903
|
this.db = db2;
|
|
30305
30904
|
this.scopePath = scopePath;
|
|
30306
30905
|
this.adapter = createFirestoreAdapter(db2, collectionPath);
|
|
30307
|
-
|
|
30308
|
-
|
|
30309
|
-
'Cannot provide both "registry" and "registryMode". Use "registry" for static mode or "registryMode" for dynamic mode.'
|
|
30310
|
-
);
|
|
30311
|
-
}
|
|
30906
|
+
this.globalWriteBack = options?.migrationWriteBack ?? "off";
|
|
30907
|
+
this.migrationSandbox = options?.migrationSandbox;
|
|
30312
30908
|
if (options?.registryMode) {
|
|
30313
30909
|
this.dynamicConfig = options.registryMode;
|
|
30314
30910
|
this.bootstrapRegistry = createBootstrapRegistry();
|
|
30911
|
+
if (options.registry) {
|
|
30912
|
+
this.staticRegistry = options.registry;
|
|
30913
|
+
}
|
|
30315
30914
|
const metaCollectionPath = options.registryMode.collection;
|
|
30316
30915
|
if (metaCollectionPath && metaCollectionPath !== collectionPath) {
|
|
30317
30916
|
this.metaAdapter = createFirestoreAdapter(db2, metaCollectionPath);
|
|
@@ -30357,24 +30956,29 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30357
30956
|
metaPipelineAdapter;
|
|
30358
30957
|
// Subgraph scope tracking
|
|
30359
30958
|
scopePath;
|
|
30959
|
+
// Migration settings
|
|
30960
|
+
globalWriteBack;
|
|
30961
|
+
migrationSandbox;
|
|
30360
30962
|
// ---------------------------------------------------------------------------
|
|
30361
30963
|
// Registry routing
|
|
30362
30964
|
// ---------------------------------------------------------------------------
|
|
30363
30965
|
/**
|
|
30364
30966
|
* Get the appropriate registry for validating a write to the given type.
|
|
30365
30967
|
*
|
|
30366
|
-
* - Static mode: returns staticRegistry (or undefined if none set)
|
|
30367
|
-
* - Dynamic mode:
|
|
30968
|
+
* - Static-only mode: returns staticRegistry (or undefined if none set)
|
|
30969
|
+
* - Dynamic mode (pure or merged):
|
|
30368
30970
|
* - Meta-types (nodeType, edgeType): validated against bootstrapRegistry
|
|
30369
30971
|
* - Domain types: validated against dynamicRegistry (falls back to
|
|
30370
30972
|
* bootstrapRegistry which rejects unknown types)
|
|
30973
|
+
* - Merged mode: dynamicRegistry is a merged wrapper (static + dynamic
|
|
30974
|
+
* extension), so static entries take priority automatically.
|
|
30371
30975
|
*/
|
|
30372
30976
|
getRegistryForType(aType) {
|
|
30373
30977
|
if (!this.dynamicConfig) return this.staticRegistry;
|
|
30374
30978
|
if (aType === META_NODE_TYPE || aType === META_EDGE_TYPE) {
|
|
30375
30979
|
return this.bootstrapRegistry;
|
|
30376
30980
|
}
|
|
30377
|
-
return this.dynamicRegistry ?? this.bootstrapRegistry;
|
|
30981
|
+
return this.dynamicRegistry ?? this.staticRegistry ?? this.bootstrapRegistry;
|
|
30378
30982
|
}
|
|
30379
30983
|
/**
|
|
30380
30984
|
* Get the Firestore adapter for writing the given type.
|
|
@@ -30388,13 +30992,13 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30388
30992
|
}
|
|
30389
30993
|
/**
|
|
30390
30994
|
* Get the combined registry for transaction/batch context.
|
|
30391
|
-
* In static mode, returns staticRegistry.
|
|
30995
|
+
* In static-only mode, returns staticRegistry.
|
|
30392
30996
|
* In dynamic mode, returns dynamicRegistry (which includes bootstrap entries)
|
|
30393
|
-
* or
|
|
30997
|
+
* or falls back to staticRegistry (merged mode) or bootstrapRegistry.
|
|
30394
30998
|
*/
|
|
30395
30999
|
getCombinedRegistry() {
|
|
30396
31000
|
if (!this.dynamicConfig) return this.staticRegistry;
|
|
30397
|
-
return this.dynamicRegistry ?? this.bootstrapRegistry;
|
|
31001
|
+
return this.dynamicRegistry ?? this.staticRegistry ?? this.bootstrapRegistry;
|
|
30398
31002
|
}
|
|
30399
31003
|
// ---------------------------------------------------------------------------
|
|
30400
31004
|
// Query dispatch
|
|
@@ -30424,37 +31028,114 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30424
31028
|
console.warn(`[firegraph] Query safety warning: ${result.reason}`);
|
|
30425
31029
|
}
|
|
30426
31030
|
// ---------------------------------------------------------------------------
|
|
31031
|
+
// Migration helpers
|
|
31032
|
+
// ---------------------------------------------------------------------------
|
|
31033
|
+
/**
|
|
31034
|
+
* Apply migration to a single record. Returns the (possibly migrated)
|
|
31035
|
+
* record and triggers write-back if applicable.
|
|
31036
|
+
*/
|
|
31037
|
+
async applyMigration(record2, docId) {
|
|
31038
|
+
const registry2 = this.getCombinedRegistry();
|
|
31039
|
+
if (!registry2) return record2;
|
|
31040
|
+
const result = await migrateRecord(record2, registry2, this.globalWriteBack);
|
|
31041
|
+
if (result.migrated) {
|
|
31042
|
+
this.handleWriteBack(result, docId);
|
|
31043
|
+
}
|
|
31044
|
+
return result.record;
|
|
31045
|
+
}
|
|
31046
|
+
/**
|
|
31047
|
+
* Apply migrations to an array of records. Returns all records
|
|
31048
|
+
* (migrated where applicable) and triggers write-backs.
|
|
31049
|
+
*/
|
|
31050
|
+
async applyMigrations(records) {
|
|
31051
|
+
const registry2 = this.getCombinedRegistry();
|
|
31052
|
+
if (!registry2 || records.length === 0) return records;
|
|
31053
|
+
const results = await migrateRecords(records, registry2, this.globalWriteBack);
|
|
31054
|
+
for (const result of results) {
|
|
31055
|
+
if (result.migrated) {
|
|
31056
|
+
const docId = result.record.axbType === NODE_RELATION ? computeNodeDocId(result.record.aUid) : computeEdgeDocId(result.record.aUid, result.record.axbType, result.record.bUid);
|
|
31057
|
+
this.handleWriteBack(result, docId);
|
|
31058
|
+
}
|
|
31059
|
+
}
|
|
31060
|
+
return results.map((r) => r.record);
|
|
31061
|
+
}
|
|
31062
|
+
/**
|
|
31063
|
+
* Handle write-back for a migrated record based on the resolved mode.
|
|
31064
|
+
*
|
|
31065
|
+
* Both `'eager'` and `'background'` are fire-and-forget (not awaited by
|
|
31066
|
+
* the caller). The difference is logging level on failure:
|
|
31067
|
+
* - `eager`: logs an error via `console.error`
|
|
31068
|
+
* - `background`: logs a warning via `console.warn`
|
|
31069
|
+
*
|
|
31070
|
+
* For truly synchronous write-back guarantees, use transactions — the
|
|
31071
|
+
* `GraphTransactionImpl` performs write-back inline within the transaction.
|
|
31072
|
+
*/
|
|
31073
|
+
handleWriteBack(result, docId) {
|
|
31074
|
+
if (result.writeBack === "off") return;
|
|
31075
|
+
const doWriteBack = async () => {
|
|
31076
|
+
try {
|
|
31077
|
+
const update = {
|
|
31078
|
+
data: deserializeFirestoreTypes(result.record.data, this.db),
|
|
31079
|
+
updatedAt: FieldValue5.serverTimestamp()
|
|
31080
|
+
};
|
|
31081
|
+
if (result.record.v !== void 0) {
|
|
31082
|
+
update.v = result.record.v;
|
|
31083
|
+
}
|
|
31084
|
+
await this.adapter.updateDoc(docId, update);
|
|
31085
|
+
} catch (err) {
|
|
31086
|
+
const msg = `[firegraph] Migration write-back failed for ${docId}: ${err.message}`;
|
|
31087
|
+
if (result.writeBack === "eager") {
|
|
31088
|
+
console.error(msg);
|
|
31089
|
+
} else {
|
|
31090
|
+
console.warn(msg);
|
|
31091
|
+
}
|
|
31092
|
+
}
|
|
31093
|
+
};
|
|
31094
|
+
void doWriteBack();
|
|
31095
|
+
}
|
|
31096
|
+
// ---------------------------------------------------------------------------
|
|
30427
31097
|
// GraphReader
|
|
30428
31098
|
// ---------------------------------------------------------------------------
|
|
30429
31099
|
async getNode(uid) {
|
|
30430
31100
|
const docId = computeNodeDocId(uid);
|
|
30431
|
-
|
|
31101
|
+
const record2 = await this.adapter.getDoc(docId);
|
|
31102
|
+
if (!record2) return null;
|
|
31103
|
+
return this.applyMigration(record2, docId);
|
|
30432
31104
|
}
|
|
30433
31105
|
async getEdge(aUid, axbType, bUid) {
|
|
30434
31106
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
30435
|
-
|
|
31107
|
+
const record2 = await this.adapter.getDoc(docId);
|
|
31108
|
+
if (!record2) return null;
|
|
31109
|
+
return this.applyMigration(record2, docId);
|
|
30436
31110
|
}
|
|
30437
31111
|
async edgeExists(aUid, axbType, bUid) {
|
|
30438
|
-
const
|
|
31112
|
+
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
31113
|
+
const record2 = await this.adapter.getDoc(docId);
|
|
30439
31114
|
return record2 !== null;
|
|
30440
31115
|
}
|
|
30441
31116
|
async findEdges(params) {
|
|
30442
31117
|
const plan = buildEdgeQueryPlan(params);
|
|
31118
|
+
let records;
|
|
30443
31119
|
if (plan.strategy === "get") {
|
|
30444
31120
|
const record2 = await this.adapter.getDoc(plan.docId);
|
|
30445
|
-
|
|
31121
|
+
records = record2 ? [record2] : [];
|
|
31122
|
+
} else {
|
|
31123
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
31124
|
+
records = await this.executeQuery(plan.filters, plan.options);
|
|
30446
31125
|
}
|
|
30447
|
-
this.
|
|
30448
|
-
return this.executeQuery(plan.filters, plan.options);
|
|
31126
|
+
return this.applyMigrations(records);
|
|
30449
31127
|
}
|
|
30450
31128
|
async findNodes(params) {
|
|
30451
31129
|
const plan = buildNodeQueryPlan(params);
|
|
31130
|
+
let records;
|
|
30452
31131
|
if (plan.strategy === "get") {
|
|
30453
31132
|
const record2 = await this.adapter.getDoc(plan.docId);
|
|
30454
|
-
|
|
31133
|
+
records = record2 ? [record2] : [];
|
|
31134
|
+
} else {
|
|
31135
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
31136
|
+
records = await this.executeQuery(plan.filters, plan.options);
|
|
30455
31137
|
}
|
|
30456
|
-
this.
|
|
30457
|
-
return this.executeQuery(plan.filters, plan.options);
|
|
31138
|
+
return this.applyMigrations(records);
|
|
30458
31139
|
}
|
|
30459
31140
|
// ---------------------------------------------------------------------------
|
|
30460
31141
|
// GraphWriter
|
|
@@ -30467,6 +31148,12 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30467
31148
|
const adapter = this.getAdapterForType(aType);
|
|
30468
31149
|
const docId = computeNodeDocId(uid);
|
|
30469
31150
|
const record2 = buildNodeRecord(aType, uid, data);
|
|
31151
|
+
if (registry2) {
|
|
31152
|
+
const entry = registry2.lookup(aType, NODE_RELATION, aType);
|
|
31153
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
31154
|
+
record2.v = entry.schemaVersion;
|
|
31155
|
+
}
|
|
31156
|
+
}
|
|
30470
31157
|
await adapter.setDoc(docId, record2);
|
|
30471
31158
|
}
|
|
30472
31159
|
async putEdge(aType, aUid, axbType, bType, bUid, data) {
|
|
@@ -30477,13 +31164,19 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30477
31164
|
const adapter = this.getAdapterForType(aType);
|
|
30478
31165
|
const docId = computeEdgeDocId(aUid, axbType, bUid);
|
|
30479
31166
|
const record2 = buildEdgeRecord(aType, aUid, axbType, bType, bUid, data);
|
|
31167
|
+
if (registry2) {
|
|
31168
|
+
const entry = registry2.lookup(aType, axbType, bType);
|
|
31169
|
+
if (entry?.schemaVersion && entry.schemaVersion > 0) {
|
|
31170
|
+
record2.v = entry.schemaVersion;
|
|
31171
|
+
}
|
|
31172
|
+
}
|
|
30480
31173
|
await adapter.setDoc(docId, record2);
|
|
30481
31174
|
}
|
|
30482
31175
|
async updateNode(uid, data) {
|
|
30483
31176
|
const docId = computeNodeDocId(uid);
|
|
30484
31177
|
await this.adapter.updateDoc(docId, {
|
|
30485
31178
|
...data,
|
|
30486
|
-
updatedAt:
|
|
31179
|
+
updatedAt: FieldValue5.serverTimestamp()
|
|
30487
31180
|
});
|
|
30488
31181
|
}
|
|
30489
31182
|
async removeNode(uid) {
|
|
@@ -30504,7 +31197,7 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30504
31197
|
this.adapter.collectionPath,
|
|
30505
31198
|
firestoreTx
|
|
30506
31199
|
);
|
|
30507
|
-
const graphTx = new GraphTransactionImpl(adapter, this.getCombinedRegistry(), this.scanProtection, this.scopePath);
|
|
31200
|
+
const graphTx = new GraphTransactionImpl(adapter, this.getCombinedRegistry(), this.scanProtection, this.scopePath, this.globalWriteBack, this.db);
|
|
30508
31201
|
return fn(graphTx);
|
|
30509
31202
|
});
|
|
30510
31203
|
}
|
|
@@ -30536,7 +31229,9 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30536
31229
|
{
|
|
30537
31230
|
registry: this.getCombinedRegistry(),
|
|
30538
31231
|
queryMode: this.queryMode === "pipeline" ? "pipeline" : "standard",
|
|
30539
|
-
scanProtection: this.scanProtection
|
|
31232
|
+
scanProtection: this.scanProtection,
|
|
31233
|
+
migrationWriteBack: this.globalWriteBack,
|
|
31234
|
+
migrationSandbox: this.migrationSandbox
|
|
30540
31235
|
},
|
|
30541
31236
|
newScopePath
|
|
30542
31237
|
);
|
|
@@ -30566,7 +31261,8 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30566
31261
|
q = q.limit(plan.options.limit);
|
|
30567
31262
|
}
|
|
30568
31263
|
const snap = await q.get();
|
|
30569
|
-
|
|
31264
|
+
const records = snap.docs.map((doc) => doc.data());
|
|
31265
|
+
return this.applyMigrations(records);
|
|
30570
31266
|
}
|
|
30571
31267
|
// ---------------------------------------------------------------------------
|
|
30572
31268
|
// Bulk operations
|
|
@@ -30591,6 +31287,11 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30591
31287
|
`Cannot define type "${name}": this name is reserved for the meta-registry.`
|
|
30592
31288
|
);
|
|
30593
31289
|
}
|
|
31290
|
+
if (this.staticRegistry?.lookup(name, NODE_RELATION, name)) {
|
|
31291
|
+
throw new DynamicRegistryError(
|
|
31292
|
+
`Cannot define node type "${name}": already defined in the static registry.`
|
|
31293
|
+
);
|
|
31294
|
+
}
|
|
30594
31295
|
const uid = generateDeterministicUid(META_NODE_TYPE, name);
|
|
30595
31296
|
const data = { name, jsonSchema };
|
|
30596
31297
|
if (description !== void 0) data.description = description;
|
|
@@ -30599,6 +31300,10 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30599
31300
|
if (options?.viewTemplate !== void 0) data.viewTemplate = options.viewTemplate;
|
|
30600
31301
|
if (options?.viewCss !== void 0) data.viewCss = options.viewCss;
|
|
30601
31302
|
if (options?.allowedIn !== void 0) data.allowedIn = options.allowedIn;
|
|
31303
|
+
if (options?.migrationWriteBack !== void 0) data.migrationWriteBack = options.migrationWriteBack;
|
|
31304
|
+
if (options?.migrations !== void 0) {
|
|
31305
|
+
data.migrations = await this.serializeMigrations(options.migrations);
|
|
31306
|
+
}
|
|
30602
31307
|
await this.putNode(META_NODE_TYPE, uid, data);
|
|
30603
31308
|
}
|
|
30604
31309
|
async defineEdgeType(name, topology, jsonSchema, description, options) {
|
|
@@ -30612,6 +31317,19 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30612
31317
|
`Cannot define type "${name}": this name is reserved for the meta-registry.`
|
|
30613
31318
|
);
|
|
30614
31319
|
}
|
|
31320
|
+
if (this.staticRegistry) {
|
|
31321
|
+
const fromTypes = Array.isArray(topology.from) ? topology.from : [topology.from];
|
|
31322
|
+
const toTypes = Array.isArray(topology.to) ? topology.to : [topology.to];
|
|
31323
|
+
for (const aType of fromTypes) {
|
|
31324
|
+
for (const bType of toTypes) {
|
|
31325
|
+
if (this.staticRegistry.lookup(aType, name, bType)) {
|
|
31326
|
+
throw new DynamicRegistryError(
|
|
31327
|
+
`Cannot define edge type "${name}" for (${aType}) -> (${bType}): already defined in the static registry.`
|
|
31328
|
+
);
|
|
31329
|
+
}
|
|
31330
|
+
}
|
|
31331
|
+
}
|
|
31332
|
+
}
|
|
30615
31333
|
const uid = generateDeterministicUid(META_EDGE_TYPE, name);
|
|
30616
31334
|
const data = {
|
|
30617
31335
|
name,
|
|
@@ -30627,6 +31345,10 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30627
31345
|
if (options?.viewTemplate !== void 0) data.viewTemplate = options.viewTemplate;
|
|
30628
31346
|
if (options?.viewCss !== void 0) data.viewCss = options.viewCss;
|
|
30629
31347
|
if (options?.allowedIn !== void 0) data.allowedIn = options.allowedIn;
|
|
31348
|
+
if (options?.migrationWriteBack !== void 0) data.migrationWriteBack = options.migrationWriteBack;
|
|
31349
|
+
if (options?.migrations !== void 0) {
|
|
31350
|
+
data.migrations = await this.serializeMigrations(options.migrations);
|
|
31351
|
+
}
|
|
30630
31352
|
await this.putNode(META_EDGE_TYPE, uid, data);
|
|
30631
31353
|
}
|
|
30632
31354
|
async reloadRegistry() {
|
|
@@ -30636,7 +31358,27 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
30636
31358
|
);
|
|
30637
31359
|
}
|
|
30638
31360
|
const reader = this.createMetaReader();
|
|
30639
|
-
|
|
31361
|
+
const dynamicOnly = await createRegistryFromGraph(reader, this.migrationSandbox);
|
|
31362
|
+
if (this.staticRegistry) {
|
|
31363
|
+
this.dynamicRegistry = createMergedRegistry(this.staticRegistry, dynamicOnly);
|
|
31364
|
+
} else {
|
|
31365
|
+
this.dynamicRegistry = dynamicOnly;
|
|
31366
|
+
}
|
|
31367
|
+
}
|
|
31368
|
+
/**
|
|
31369
|
+
* Serialize migration steps for storage in Firestore.
|
|
31370
|
+
* Function objects are converted via `.toString()`; strings are stored as-is.
|
|
31371
|
+
* Each migration is validated at define-time by pre-compiling in the sandbox.
|
|
31372
|
+
*/
|
|
31373
|
+
async serializeMigrations(migrations) {
|
|
31374
|
+
const result = migrations.map((m) => {
|
|
31375
|
+
const source = typeof m.up === "function" ? m.up.toString() : m.up;
|
|
31376
|
+
return { fromVersion: m.fromVersion, toVersion: m.toVersion, up: source };
|
|
31377
|
+
});
|
|
31378
|
+
await Promise.all(
|
|
31379
|
+
result.map((m) => precompileSource(m.up, this.migrationSandbox))
|
|
31380
|
+
);
|
|
31381
|
+
return result;
|
|
30640
31382
|
}
|
|
30641
31383
|
/**
|
|
30642
31384
|
* Create a GraphReader for the meta-collection.
|
|
@@ -30795,11 +31537,39 @@ function findViewsFile(dir) {
|
|
|
30795
31537
|
}
|
|
30796
31538
|
return void 0;
|
|
30797
31539
|
}
|
|
31540
|
+
var MIGRATION_EXTENSIONS = [".ts", ".js", ".mts", ".mjs"];
|
|
31541
|
+
function findMigrationsFile(dir) {
|
|
31542
|
+
for (const ext of MIGRATION_EXTENSIONS) {
|
|
31543
|
+
const candidate = join(dir, `migrations${ext}`);
|
|
31544
|
+
if (existsSync(candidate)) return candidate;
|
|
31545
|
+
}
|
|
31546
|
+
return void 0;
|
|
31547
|
+
}
|
|
31548
|
+
function loadMigrations(filePath, entityLabel) {
|
|
31549
|
+
try {
|
|
31550
|
+
const jiti2 = getJiti();
|
|
31551
|
+
const mod = jiti2(filePath);
|
|
31552
|
+
const migrations = mod && typeof mod === "object" && "default" in mod ? mod.default : mod;
|
|
31553
|
+
if (!Array.isArray(migrations)) {
|
|
31554
|
+
throw new DiscoveryError(
|
|
31555
|
+
`Migrations file ${filePath} for ${entityLabel} must default-export an array of MigrationStep.`
|
|
31556
|
+
);
|
|
31557
|
+
}
|
|
31558
|
+
return migrations;
|
|
31559
|
+
} catch (err) {
|
|
31560
|
+
if (err instanceof DiscoveryError) throw err;
|
|
31561
|
+
throw new DiscoveryError(
|
|
31562
|
+
`Failed to load migrations ${filePath} for ${entityLabel}: ${err.message}`
|
|
31563
|
+
);
|
|
31564
|
+
}
|
|
31565
|
+
}
|
|
30798
31566
|
function loadNodeEntity(dir, name) {
|
|
30799
31567
|
const schema = loadSchema(dir, `node type "${name}"`);
|
|
30800
31568
|
const meta3 = readJsonIfExists(join(dir, "meta.json"));
|
|
30801
31569
|
const sampleData = readJsonIfExists(join(dir, "sample.json"));
|
|
30802
31570
|
const viewsPath = findViewsFile(dir);
|
|
31571
|
+
const migrationsPath = findMigrationsFile(dir);
|
|
31572
|
+
const migrations = migrationsPath ? loadMigrations(migrationsPath, `node type "${name}"`) : void 0;
|
|
30803
31573
|
return {
|
|
30804
31574
|
kind: "node",
|
|
30805
31575
|
name,
|
|
@@ -30810,7 +31580,9 @@ function loadNodeEntity(dir, name) {
|
|
|
30810
31580
|
viewDefaults: meta3?.viewDefaults,
|
|
30811
31581
|
viewsPath,
|
|
30812
31582
|
sampleData,
|
|
30813
|
-
allowedIn: meta3?.allowedIn
|
|
31583
|
+
allowedIn: meta3?.allowedIn,
|
|
31584
|
+
migrations,
|
|
31585
|
+
migrationWriteBack: meta3?.migrationWriteBack
|
|
30814
31586
|
};
|
|
30815
31587
|
}
|
|
30816
31588
|
function loadEdgeEntity(dir, name) {
|
|
@@ -30835,6 +31607,8 @@ function loadEdgeEntity(dir, name) {
|
|
|
30835
31607
|
const meta3 = readJsonIfExists(join(dir, "meta.json"));
|
|
30836
31608
|
const sampleData = readJsonIfExists(join(dir, "sample.json"));
|
|
30837
31609
|
const viewsPath = findViewsFile(dir);
|
|
31610
|
+
const migrationsPath = findMigrationsFile(dir);
|
|
31611
|
+
const migrations = migrationsPath ? loadMigrations(migrationsPath, `edge type "${name}"`) : void 0;
|
|
30838
31612
|
return {
|
|
30839
31613
|
kind: "edge",
|
|
30840
31614
|
name,
|
|
@@ -30847,7 +31621,9 @@ function loadEdgeEntity(dir, name) {
|
|
|
30847
31621
|
viewsPath,
|
|
30848
31622
|
sampleData,
|
|
30849
31623
|
allowedIn: meta3?.allowedIn,
|
|
30850
|
-
targetGraph: topology.targetGraph ?? meta3?.targetGraph
|
|
31624
|
+
targetGraph: topology.targetGraph ?? meta3?.targetGraph,
|
|
31625
|
+
migrations,
|
|
31626
|
+
migrationWriteBack: meta3?.migrationWriteBack
|
|
30851
31627
|
};
|
|
30852
31628
|
}
|
|
30853
31629
|
function getSubdirectories(dir) {
|
|
@@ -34206,7 +34982,7 @@ function createExpressMiddleware(opts) {
|
|
|
34206
34982
|
}
|
|
34207
34983
|
|
|
34208
34984
|
// editor/server/trpc.ts
|
|
34209
|
-
import { Timestamp } from "@google-cloud/firestore";
|
|
34985
|
+
import { Timestamp as Timestamp2 } from "@google-cloud/firestore";
|
|
34210
34986
|
|
|
34211
34987
|
// editor/node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
34212
34988
|
var external_exports = {};
|
|
@@ -48005,7 +48781,7 @@ var NODE_RELATION2 = "is";
|
|
|
48005
48781
|
function serializeRecord(doc) {
|
|
48006
48782
|
const result = {};
|
|
48007
48783
|
for (const [key, value] of Object.entries(doc)) {
|
|
48008
|
-
if (value instanceof
|
|
48784
|
+
if (value instanceof Timestamp2) {
|
|
48009
48785
|
result[key] = value.toDate().toISOString();
|
|
48010
48786
|
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
48011
48787
|
result[key] = serializeRecord(value);
|
|
@@ -48169,7 +48945,7 @@ var appRouter = t.router({
|
|
|
48169
48945
|
if (hasMore && docs.length > 0) {
|
|
48170
48946
|
const lastDoc = docs[docs.length - 1].data();
|
|
48171
48947
|
const cursorValue = lastDoc[effectiveSortBy];
|
|
48172
|
-
nextCursor = cursorValue instanceof
|
|
48948
|
+
nextCursor = cursorValue instanceof Timestamp2 ? cursorValue.toDate().toISOString() : String(cursorValue);
|
|
48173
48949
|
}
|
|
48174
48950
|
return { nodes, hasMore, nextCursor };
|
|
48175
48951
|
}),
|
|
@@ -48255,7 +49031,7 @@ var appRouter = t.router({
|
|
|
48255
49031
|
if (hasMore && docs.length > 0) {
|
|
48256
49032
|
const lastDoc = docs[docs.length - 1].data();
|
|
48257
49033
|
const cursorValue = effectiveSortBy.startsWith("data.") ? effectiveSortBy.split(".").reduce((obj, key) => obj?.[key], lastDoc) : lastDoc[effectiveSortBy];
|
|
48258
|
-
nextCursor = cursorValue instanceof
|
|
49034
|
+
nextCursor = cursorValue instanceof Timestamp2 ? cursorValue.toDate().toISOString() : String(cursorValue);
|
|
48259
49035
|
}
|
|
48260
49036
|
return { edges, hasMore, nextCursor };
|
|
48261
49037
|
}),
|