@danceroutine/tango-schema 1.3.0 → 1.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.
@@ -1,6 +1,10 @@
1
- import { getLogger } from "@danceroutine/tango-core";
1
+ import { getLogger, getLogger as getLogger$1 } from "@danceroutine/tango-core";
2
2
  import { z } from "zod";
3
3
  import { AsyncLocalStorage } from "node:async_hooks";
4
+ import { existsSync, existsSync as existsSync$1, readFileSync } from "node:fs";
5
+ import { dirname, extname, resolve, resolve as resolve$1 } from "node:path";
6
+ import { createHash } from "node:crypto";
7
+ import { fileURLToPath } from "node:url";
4
8
 
5
9
  //#region rolldown:runtime
6
10
  var __defProp = Object.defineProperty;
@@ -55,7 +59,7 @@ const warnedDecoratorKinds = new Set();
55
59
  function warnDeprecatedSchemaOverload(kind, replacement) {
56
60
  if (warnedDecoratorKinds.has(kind)) return;
57
61
  warnedDecoratorKinds.add(kind);
58
- getLogger("tango.schema.decorators").warn(`Deprecated positional schema overload used for t.${kind}(...). Prefer ${replacement} instead.`);
62
+ getLogger$1("tango.schema.decorators").warn(`Deprecated positional schema overload used for t.${kind}(...). Prefer ${replacement} instead.`);
59
63
  }
60
64
  function maybeDecorator(schemaOrUndefined, meta) {
61
65
  if (schemaOrUndefined) return decorate(schemaOrUndefined, meta);
@@ -622,24 +626,20 @@ function deriveTableName(name) {
622
626
 
623
627
  //#endregion
624
628
  //#region src/model/internal/InternalSchemaModel.ts
629
+ const REGISTRY_OWNER_KEY = Symbol.for("tango.schema.registryOwner");
630
+ const NORMALIZED_RELATIONS_KEY = Symbol.for("tango.schema.normalizedRelations");
631
+ const EXPLICIT_FIELDS_KEY = Symbol.for("tango.schema.explicitFields");
632
+ const EXPLICIT_RELATIONS_KEY = Symbol.for("tango.schema.explicitRelations");
625
633
  var InternalSchemaModel = class InternalSchemaModel {
626
634
  static BRAND = "tango.schema.internal_schema_model";
627
635
  __tangoBrand = InternalSchemaModel.BRAND;
628
636
  metadata;
629
637
  schema;
630
638
  hooks;
631
- registry;
632
- normalizedRelations;
633
- explicitFields;
634
- explicitRelations;
635
- constructor(registry, metadata, schema, hooks, normalizedRelations, explicitFields, explicitRelations) {
636
- this.registry = registry;
639
+ constructor(metadata, schema, hooks) {
637
640
  this.metadata = metadata;
638
641
  this.schema = schema;
639
642
  this.hooks = hooks;
640
- this.normalizedRelations = Object.freeze([...normalizedRelations]);
641
- this.explicitFields = explicitFields ? Object.freeze([...explicitFields]) : undefined;
642
- this.explicitRelations = explicitRelations;
643
643
  }
644
644
  static create(definition, registry) {
645
645
  InternalSchemaModel.validateDefinition(definition);
@@ -667,22 +667,31 @@ var InternalSchemaModel = class InternalSchemaModel {
667
667
  get: () => registry.getFinalizedFields(key)
668
668
  });
669
669
  Object.freeze(metadata);
670
- return new InternalSchemaModel(registry, metadata, definition.schema, definition.hooks, normalizedRelations, definition.fields, relations);
670
+ const model = new InternalSchemaModel(metadata, definition.schema, definition.hooks);
671
+ InternalSchemaModel.attachInternals(model, {
672
+ registry,
673
+ normalizedRelations,
674
+ explicitFields: definition.fields,
675
+ explicitRelations: relations
676
+ });
677
+ return model;
671
678
  }
672
679
  static isInternalSchemaModel(value) {
673
680
  return typeof value === "object" && value !== null && value.__tangoBrand === InternalSchemaModel.BRAND;
674
681
  }
675
682
  static getRegistryOwner(model) {
676
- return InternalSchemaModel.require(model).registry;
683
+ const owner = InternalSchemaModel.carrier(model)[REGISTRY_OWNER_KEY];
684
+ if (!owner) throw new Error(`Model '${model.metadata.key}' is missing internal registry ownership metadata.`);
685
+ return owner;
677
686
  }
678
687
  static getNormalizedRelations(model) {
679
- return InternalSchemaModel.require(model).normalizedRelations;
688
+ return InternalSchemaModel.carrier(model)[NORMALIZED_RELATIONS_KEY] ?? [];
680
689
  }
681
690
  static getExplicitFields(model) {
682
- return InternalSchemaModel.require(model).explicitFields;
691
+ return InternalSchemaModel.carrier(model)[EXPLICIT_FIELDS_KEY];
683
692
  }
684
693
  static getExplicitRelations(model) {
685
- return InternalSchemaModel.require(model).explicitRelations;
694
+ return InternalSchemaModel.carrier(model)[EXPLICIT_RELATIONS_KEY];
686
695
  }
687
696
  static validateDefinition(definition) {
688
697
  if (!definition.namespace.trim()) throw new Error("Model.namespace is required and cannot be empty.");
@@ -693,6 +702,38 @@ var InternalSchemaModel = class InternalSchemaModel {
693
702
  if (!InternalSchemaModel.isInternalSchemaModel(model)) throw new Error(`Model '${model.metadata.key}' is missing internal registry ownership metadata.`);
694
703
  return model;
695
704
  }
705
+ static carrier(model) {
706
+ return InternalSchemaModel.require(model);
707
+ }
708
+ static attachInternals(model, internals) {
709
+ const carrier = model;
710
+ Object.defineProperties(carrier, {
711
+ [REGISTRY_OWNER_KEY]: {
712
+ value: internals.registry,
713
+ enumerable: false,
714
+ configurable: false,
715
+ writable: false
716
+ },
717
+ [NORMALIZED_RELATIONS_KEY]: {
718
+ value: Object.freeze([...internals.normalizedRelations]),
719
+ enumerable: false,
720
+ configurable: false,
721
+ writable: false
722
+ },
723
+ [EXPLICIT_FIELDS_KEY]: {
724
+ value: internals.explicitFields ? Object.freeze([...internals.explicitFields]) : undefined,
725
+ enumerable: false,
726
+ configurable: false,
727
+ writable: false
728
+ },
729
+ [EXPLICIT_RELATIONS_KEY]: {
730
+ value: internals.explicitRelations,
731
+ enumerable: false,
732
+ configurable: false,
733
+ writable: false
734
+ }
735
+ });
736
+ }
696
737
  };
697
738
 
698
739
  //#endregion
@@ -742,6 +783,33 @@ var ResolvedRelationGraphBuilder = class ResolvedRelationGraphBuilder {
742
783
  return new ResolvedRelationGraphBuilder(options).build();
743
784
  }
744
785
  /**
786
+ * Serialize a resolved graph into the canonical snapshot shape used by
787
+ * relation-registry code generation and fingerprinting.
788
+ */
789
+ static createSnapshot(graph) {
790
+ return { models: Array.from(graph.byModel.entries()).sort(([left], [right]) => left.localeCompare(right)).map(([key, relations]) => ({
791
+ key,
792
+ relations: Array.from(relations.values()).sort((left, right) => left.name.localeCompare(right.name)).map((relation) => ({
793
+ edgeId: relation.edgeId,
794
+ sourceModelKey: relation.sourceModelKey,
795
+ targetModelKey: relation.targetModelKey,
796
+ name: relation.name,
797
+ inverseEdgeId: relation.inverseEdgeId,
798
+ kind: relation.kind,
799
+ storageStrategy: relation.storageStrategy,
800
+ cardinality: relation.cardinality,
801
+ localFieldName: relation.localFieldName,
802
+ targetFieldName: relation.targetFieldName,
803
+ alias: relation.alias,
804
+ capabilities: {
805
+ migratable: relation.capabilities.migratable,
806
+ queryable: relation.capabilities.queryable,
807
+ hydratable: relation.capabilities.hydratable
808
+ }
809
+ }))
810
+ })) };
811
+ }
812
+ /**
745
813
  * Resolve every model's normalized relation descriptors into a single
746
814
  * registry-scoped graph and fail when authoring ambiguity remains.
747
815
  */
@@ -868,34 +936,58 @@ var ResolvedRelationGraphBuilder = class ResolvedRelationGraphBuilder {
868
936
  }
869
937
  };
870
938
 
939
+ //#endregion
940
+ //#region src/model/registry/GeneratedRelationRegistryArtifact.ts
941
+ const GENERATED_RELATION_REGISTRY_DIRNAME = ".tango";
942
+ const GENERATED_RELATION_REGISTRY_TYPES_FILENAME = "relations.generated.d.ts";
943
+ const GENERATED_RELATION_REGISTRY_METADATA_FILENAME = "relations.generated.json";
944
+ const GENERATED_RELATION_REGISTRY_METADATA_VERSION = 1;
945
+
946
+ //#endregion
947
+ //#region src/model/registry/ResolvedRelationGraphArtifactFactory.ts
948
+ var ResolvedRelationGraphArtifactFactory = class ResolvedRelationGraphArtifactFactory {
949
+ static createSnapshot(graph) {
950
+ return ResolvedRelationGraphBuilder.createSnapshot(graph);
951
+ }
952
+ static createFingerprint(value) {
953
+ const snapshot = "byModel" in value ? ResolvedRelationGraphArtifactFactory.createSnapshot(value) : value;
954
+ return createHash("sha256").update(JSON.stringify(snapshot)).digest("hex");
955
+ }
956
+ };
957
+
871
958
  //#endregion
872
959
  //#region src/model/registry/ModelRegistry.ts
873
960
  const DEFAULT_IDENTIFIER_NAME = "id";
874
- const activeRegistryStorage = new AsyncLocalStorage();
961
+ const ACTIVE_REGISTRY_STORAGE_KEY = Symbol.for("tango.schema.activeRegistryStorage");
875
962
  var ModelRegistry = class ModelRegistry {
876
963
  static globalRegistry;
877
964
  models = new Map();
878
965
  version = 0;
879
966
  storageCache;
880
967
  relationGraphCache;
968
+ lastRelationRegistryDriftCheckVersion;
881
969
  /**
882
- * Return the shared process-wide registry used by `Model(...)`.
970
+ * Return the shared default registry used by `Model(...)` for this schema
971
+ * package instance.
883
972
  */
884
973
  static global() {
885
- if (!ModelRegistry.globalRegistry) ModelRegistry.globalRegistry = new ModelRegistry();
974
+ ModelRegistry.globalRegistry ??= new ModelRegistry();
886
975
  return ModelRegistry.globalRegistry;
887
976
  }
888
977
  /**
889
978
  * Return the registry currently bound to model construction work.
979
+ *
980
+ * This explicit binding is process-shared so code that imports separate
981
+ * schema package copies can still participate in one construction flow.
890
982
  */
891
983
  static active() {
892
- return activeRegistryStorage.getStore() ?? ModelRegistry.global();
984
+ return ModelRegistry.activeRegistryStorage().getStore() ?? ModelRegistry.global();
893
985
  }
894
986
  /**
895
987
  * Run work with a specific registry bound as the active construction target.
896
988
  */
897
989
  static async runWithRegistry(registry, work) {
898
- return await activeRegistryStorage.run(registry, work);
990
+ return await ModelRegistry.activeRegistryStorage().run(registry, work);
899
991
  }
900
992
  /**
901
993
  * Register a model on the shared global registry.
@@ -939,6 +1031,14 @@ var ModelRegistry = class ModelRegistry {
939
1031
  static getOwner(model) {
940
1032
  return InternalSchemaModel.getRegistryOwner(model);
941
1033
  }
1034
+ static runtimeGlobal() {
1035
+ return globalThis;
1036
+ }
1037
+ static activeRegistryStorage() {
1038
+ const runtimeGlobal = ModelRegistry.runtimeGlobal();
1039
+ if (!runtimeGlobal[ACTIVE_REGISTRY_STORAGE_KEY]) runtimeGlobal[ACTIVE_REGISTRY_STORAGE_KEY] = new AsyncLocalStorage();
1040
+ return runtimeGlobal[ACTIVE_REGISTRY_STORAGE_KEY];
1041
+ }
942
1042
  /**
943
1043
  * Register a model on this registry instance.
944
1044
  */
@@ -1039,9 +1139,22 @@ var ModelRegistry = class ModelRegistry {
1039
1139
  resolveRef: (ref) => this.resolveRef(ref)
1040
1140
  });
1041
1141
  this.relationGraphCache = finalized;
1142
+ this.warnOnGeneratedRelationRegistryDrift(finalized);
1042
1143
  return finalized;
1043
1144
  }
1044
1145
  /**
1146
+ * Return a canonical snapshot of the resolved relation graph.
1147
+ */
1148
+ getResolvedRelationGraphSnapshot() {
1149
+ return ResolvedRelationGraphArtifactFactory.createSnapshot(this.getResolvedRelationGraph());
1150
+ }
1151
+ /**
1152
+ * Return a deterministic fingerprint for the resolved relation graph.
1153
+ */
1154
+ getResolvedRelationGraphFingerprint() {
1155
+ return ResolvedRelationGraphArtifactFactory.createFingerprint(this.getResolvedRelationGraph());
1156
+ }
1157
+ /**
1045
1158
  * Remove all registered models from this registry instance.
1046
1159
  */
1047
1160
  clear() {
@@ -1058,6 +1171,7 @@ var ModelRegistry = class ModelRegistry {
1058
1171
  this.version += 1;
1059
1172
  this.storageCache = undefined;
1060
1173
  this.relationGraphCache = undefined;
1174
+ this.lastRelationRegistryDriftCheckVersion = undefined;
1061
1175
  }
1062
1176
  freezeFields(fields) {
1063
1177
  return Object.freeze(fields.map((field$1) => Object.freeze({ ...field$1 })));
@@ -1077,12 +1191,85 @@ var ModelRegistry = class ModelRegistry {
1077
1191
  for (const explicitField of explicitFields) mergedFields.set(explicitField.name, explicitField);
1078
1192
  return Array.from(mergedFields.values());
1079
1193
  }
1194
+ warnOnGeneratedRelationRegistryDrift(graph) {
1195
+ if (this.lastRelationRegistryDriftCheckVersion === this.version || !this.shouldCheckGeneratedRelationRegistry()) return;
1196
+ this.lastRelationRegistryDriftCheckVersion = this.version;
1197
+ const expected = this.readGeneratedRelationRegistryArtifact();
1198
+ if (!expected) return;
1199
+ const liveSnapshot = ResolvedRelationGraphArtifactFactory.createSnapshot(graph);
1200
+ if (this.isPartialRegistrySnapshot(liveSnapshot, expected.snapshot)) return;
1201
+ const liveFingerprint = ResolvedRelationGraphArtifactFactory.createFingerprint(liveSnapshot);
1202
+ if (liveFingerprint === expected.fingerprint) return;
1203
+ getLogger("tango.schema.registry").warn(`Generated relation registry drift detected. Run 'tango codegen relations' to refresh ${GENERATED_RELATION_REGISTRY_DIRNAME}/${GENERATED_RELATION_REGISTRY_METADATA_FILENAME}.`);
1204
+ }
1205
+ shouldCheckGeneratedRelationRegistry() {
1206
+ return process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test";
1207
+ }
1208
+ readGeneratedRelationRegistryArtifact() {
1209
+ const metadataPath = resolve$1(process.cwd(), GENERATED_RELATION_REGISTRY_DIRNAME, GENERATED_RELATION_REGISTRY_METADATA_FILENAME);
1210
+ if (!existsSync$1(metadataPath)) return undefined;
1211
+ try {
1212
+ const parsed = JSON.parse(readFileSync(metadataPath, "utf8"));
1213
+ if (parsed.version !== GENERATED_RELATION_REGISTRY_METADATA_VERSION || typeof parsed.fingerprint !== "string" || !parsed.snapshot || !Array.isArray(parsed.snapshot.models)) {
1214
+ getLogger("tango.schema.registry").warn(`Ignoring malformed generated relation registry metadata at '${metadataPath}'.`);
1215
+ return undefined;
1216
+ }
1217
+ return parsed;
1218
+ } catch (error) {
1219
+ getLogger("tango.schema.registry").warn(`Unable to read generated relation registry metadata at '${metadataPath}'.`, error);
1220
+ return undefined;
1221
+ }
1222
+ }
1223
+ isPartialRegistrySnapshot(liveSnapshot, expectedSnapshot) {
1224
+ const expectedModels = new Map(expectedSnapshot.models.map((model) => [model.key, model]));
1225
+ if (liveSnapshot.models.some((model) => !expectedModels.has(model.key))) return false;
1226
+ if (liveSnapshot.models.length >= expectedSnapshot.models.length) return false;
1227
+ for (const model of liveSnapshot.models) {
1228
+ const expectedModel = expectedModels.get(model.key);
1229
+ if (JSON.stringify(model.relations) !== JSON.stringify(expectedModel.relations)) return false;
1230
+ }
1231
+ return true;
1232
+ }
1080
1233
  };
1081
1234
 
1235
+ //#endregion
1236
+ //#region src/resolveSchemaModuleEntrypoint.ts
1237
+ const LOCAL_ENTRYPOINT_CANDIDATES = ["./index.ts", "./index.js"];
1238
+ function resolveSchemaModuleEntrypoint() {
1239
+ for (const relativePath of LOCAL_ENTRYPOINT_CANDIDATES) {
1240
+ const absolutePath = fileURLToPath(new URL(relativePath, import.meta.url));
1241
+ if (existsSync(absolutePath)) return absolutePath;
1242
+ }
1243
+ throw new Error(`Unable to resolve the @danceroutine/tango-schema entrypoint relative to '${fileURLToPath(import.meta.url)}'.`);
1244
+ }
1245
+ function createSchemaModuleAliases() {
1246
+ const entrypoint = resolveSchemaModuleEntrypoint();
1247
+ const packageRoot = dirname(entrypoint);
1248
+ const extension = extname(entrypoint);
1249
+ const modelEntrypoint = resolve(packageRoot, "model", `index${extension}`);
1250
+ const domainEntrypoint = resolve(packageRoot, "domain", `index${extension}`);
1251
+ if (!existsSync(modelEntrypoint) || !existsSync(domainEntrypoint)) throw new Error(`Unable to resolve the @danceroutine/tango-schema subpath entrypoints relative to '${entrypoint}'.`);
1252
+ return {
1253
+ "@danceroutine/tango-schema": entrypoint,
1254
+ "@danceroutine/tango-schema/model": modelEntrypoint,
1255
+ "@danceroutine/tango-schema/domain": domainEntrypoint,
1256
+ "@danceroutine/tango-schema/": `${packageRoot}/`
1257
+ };
1258
+ }
1259
+
1082
1260
  //#endregion
1083
1261
  //#region src/model/registry/index.ts
1084
1262
  var registry_exports = {};
1085
- __export(registry_exports, { ModelRegistry: () => ModelRegistry });
1263
+ __export(registry_exports, {
1264
+ GENERATED_RELATION_REGISTRY_DIRNAME: () => GENERATED_RELATION_REGISTRY_DIRNAME,
1265
+ GENERATED_RELATION_REGISTRY_METADATA_FILENAME: () => GENERATED_RELATION_REGISTRY_METADATA_FILENAME,
1266
+ GENERATED_RELATION_REGISTRY_METADATA_VERSION: () => GENERATED_RELATION_REGISTRY_METADATA_VERSION,
1267
+ GENERATED_RELATION_REGISTRY_TYPES_FILENAME: () => GENERATED_RELATION_REGISTRY_TYPES_FILENAME,
1268
+ ModelRegistry: () => ModelRegistry,
1269
+ ResolvedRelationGraphArtifactFactory: () => ResolvedRelationGraphArtifactFactory,
1270
+ createSchemaModuleAliases: () => createSchemaModuleAliases,
1271
+ resolveSchemaModuleEntrypoint: () => resolveSchemaModuleEntrypoint
1272
+ });
1086
1273
 
1087
1274
  //#endregion
1088
1275
  //#region src/model/relations/index.ts
@@ -1119,14 +1306,20 @@ var model_exports = {};
1119
1306
  __export(model_exports, {
1120
1307
  Constraints: () => Constraints,
1121
1308
  Decorators: () => Decorators,
1309
+ GENERATED_RELATION_REGISTRY_DIRNAME: () => GENERATED_RELATION_REGISTRY_DIRNAME,
1310
+ GENERATED_RELATION_REGISTRY_METADATA_FILENAME: () => GENERATED_RELATION_REGISTRY_METADATA_FILENAME,
1311
+ GENERATED_RELATION_REGISTRY_METADATA_VERSION: () => GENERATED_RELATION_REGISTRY_METADATA_VERSION,
1312
+ GENERATED_RELATION_REGISTRY_TYPES_FILENAME: () => GENERATED_RELATION_REGISTRY_TYPES_FILENAME,
1122
1313
  Indexes: () => Indexes,
1123
1314
  InternalDecoratedFieldKind: () => InternalDecoratedFieldKind,
1124
1315
  Meta: () => Meta,
1125
1316
  Model: () => Model,
1126
1317
  ModelRegistry: () => ModelRegistry,
1127
1318
  RelationBuilder: () => RelationBuilder,
1319
+ ResolvedRelationGraphArtifactFactory: () => ResolvedRelationGraphArtifactFactory,
1128
1320
  c: () => Constraints,
1129
1321
  constraints: () => constraints_exports,
1322
+ createSchemaModuleAliases: () => createSchemaModuleAliases,
1130
1323
  createTypedModelRef: () => createTypedModelRef,
1131
1324
  decorators: () => decorators_exports,
1132
1325
  i: () => Indexes,
@@ -1136,9 +1329,10 @@ __export(model_exports, {
1136
1329
  registerModelAugmentor: () => registerModelAugmentor,
1137
1330
  registry: () => registry_exports,
1138
1331
  relations: () => relations_exports,
1332
+ resolveSchemaModuleEntrypoint: () => resolveSchemaModuleEntrypoint,
1139
1333
  t: () => Decorators
1140
1334
  });
1141
1335
 
1142
1336
  //#endregion
1143
- export { Constraints, Decorators, Indexes, InternalDecoratedFieldKind, Meta, Model, ModelRegistry, RelationBuilder, constraints_exports, createTypedModelRef, decorators_exports, isTypedModelRef, meta_exports, model_exports, registerModelAugmentor, registry_exports, relations_exports };
1144
- //# sourceMappingURL=model-YLW1ydkV.js.map
1337
+ export { Constraints, Decorators, GENERATED_RELATION_REGISTRY_DIRNAME, GENERATED_RELATION_REGISTRY_METADATA_FILENAME, GENERATED_RELATION_REGISTRY_METADATA_VERSION, GENERATED_RELATION_REGISTRY_TYPES_FILENAME, Indexes, InternalDecoratedFieldKind, Meta, Model, ModelRegistry, RelationBuilder, ResolvedRelationGraphArtifactFactory, constraints_exports, createSchemaModuleAliases, createTypedModelRef, decorators_exports, isTypedModelRef, meta_exports, model_exports, registerModelAugmentor, registry_exports, relations_exports, resolveSchemaModuleEntrypoint };
1338
+ //# sourceMappingURL=model-BxkSqwrt.js.map