@kurotako/ir 0.1.0 → 0.3.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/dist/index.cjs CHANGED
@@ -51,10 +51,13 @@ __export(index_exports, {
51
51
  ScalarTypeSchema: () => ScalarTypeSchema,
52
52
  SourceIrSchema: () => SourceIrSchema,
53
53
  StringFormatSchema: () => StringFormatSchema,
54
+ TypeAliasSchema: () => TypeAliasSchema,
54
55
  assertIR: () => assertIR,
55
56
  assertSourceIR: () => assertSourceIR,
57
+ collectRefNames: () => collectRefNames,
56
58
  createFields: () => createFields,
57
59
  createSourceIR: () => createSourceIR,
60
+ flattenUnion: () => flattenUnion,
58
61
  getSource: () => getSource,
59
62
  isCompatible: () => isCompatible,
60
63
  isCreateOptional: () => isCreateOptional,
@@ -62,11 +65,15 @@ __export(index_exports, {
62
65
  isDbAssigned: () => isDbAssigned,
63
66
  iterEntities: () => iterEntities,
64
67
  iterFields: () => iterFields,
68
+ iterTypeAliases: () => iterTypeAliases,
65
69
  parseIR: () => parseIR,
66
70
  primaryKeyFields: () => primaryKeyFields,
71
+ refCycleMembers: () => refCycleMembers,
67
72
  resolveEntity: () => resolveEntity,
68
73
  resolveEnum: () => resolveEnum,
74
+ resolveRef: () => resolveRef,
69
75
  resolveRelationTarget: () => resolveRelationTarget,
76
+ resolveTypeAlias: () => resolveTypeAlias,
70
77
  scalarTsType: () => scalarTsType,
71
78
  updateFields: () => updateFields,
72
79
  validateIR: () => validateIR,
@@ -131,11 +138,31 @@ var IndexTypeSchema = v.picklist([
131
138
  "brin",
132
139
  "spgist"
133
140
  ]);
134
- var FieldTypeSchema = v.variant("kind", [
135
- v.object({ kind: v.literal("scalar"), scalar: ScalarTypeSchema }),
136
- v.object({ kind: v.literal("enum"), ref: v.string() }),
137
- v.object({ kind: v.literal("unknown"), hint: v.optional(v.string()) })
138
- ]);
141
+ var FieldTypeSchema = v.lazy(
142
+ () => v.variant("kind", [
143
+ v.object({ kind: v.literal("scalar"), scalar: ScalarTypeSchema }),
144
+ v.object({ kind: v.literal("enum"), ref: v.string() }),
145
+ v.object({ kind: v.literal("unknown"), hint: v.optional(v.string()) }),
146
+ v.object({ kind: v.literal("ref"), ref: v.string() }),
147
+ v.object({ kind: v.literal("map"), value: FieldTypeSchema }),
148
+ v.object({ kind: v.literal("array"), element: FieldTypeSchema }),
149
+ v.object({
150
+ kind: v.literal("union"),
151
+ variants: v.array(FieldTypeSchema),
152
+ discriminator: v.optional(
153
+ v.object({
154
+ propertyName: v.string(),
155
+ mapping: v.optional(v.record(v.string(), v.string()))
156
+ })
157
+ )
158
+ })
159
+ ])
160
+ );
161
+ var TypeAliasSchema = v.object({
162
+ name: v.string(),
163
+ type: FieldTypeSchema,
164
+ doc: v.optional(v.string())
165
+ });
139
166
  var ConstraintsSchema = v.object({
140
167
  min: v.optional(v.number()),
141
168
  max: v.optional(v.number()),
@@ -203,6 +230,7 @@ var CompositeUniqueSchema = v.object({
203
230
  var EntitySchema = v.object({
204
231
  name: v.string(),
205
232
  fields: v.array(FieldSchema),
233
+ additionalProperties: v.optional(FieldTypeSchema),
206
234
  relations: v.array(RelationSchema),
207
235
  enums: v.optional(v.record(v.string(), EnumDefSchema)),
208
236
  primaryKey: v.optional(v.array(v.string())),
@@ -216,7 +244,8 @@ var SourceIrSchema = v.object({
216
244
  parser: v.string(),
217
245
  parserVersion: v.optional(v.string()),
218
246
  entities: v.record(v.string(), EntitySchema),
219
- enums: v.record(v.string(), EnumDefSchema)
247
+ enums: v.record(v.string(), EnumDefSchema),
248
+ typeAliases: v.optional(v.record(v.string(), TypeAliasSchema))
220
249
  });
221
250
  var IrSchema = v.object({
222
251
  irVersion: v.string(),
@@ -227,7 +256,7 @@ var IrSchema = v.object({
227
256
  var v2 = __toESM(require("valibot"), 1);
228
257
 
229
258
  // src/version.ts
230
- var IR_VERSION = "1";
259
+ var IR_VERSION = "4";
231
260
  function isCompatible(irVersion) {
232
261
  return irVersion === IR_VERSION;
233
262
  }
@@ -296,7 +325,151 @@ function checkEnumValues(issues, path, values) {
296
325
  seen.add(value.name);
297
326
  }
298
327
  }
299
- function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues) {
328
+ function walkFieldType(type, path, entity, source, issues, info) {
329
+ switch (type.kind) {
330
+ case "scalar":
331
+ case "unknown":
332
+ return;
333
+ case "enum": {
334
+ const resolved = entity?.enums?.[type.ref] ?? source.enums[type.ref];
335
+ if (resolved === void 0) {
336
+ pushIssue(
337
+ issues,
338
+ path,
339
+ "unresolved_enum_ref",
340
+ `field type references unknown enum '${type.ref}'`
341
+ );
342
+ }
343
+ return;
344
+ }
345
+ case "ref": {
346
+ const known = source.entities[type.ref] !== void 0 || source.typeAliases?.[type.ref] !== void 0;
347
+ if (!known) {
348
+ pushIssue(
349
+ issues,
350
+ path,
351
+ "unresolved_ref",
352
+ `ref '${type.ref}' resolves to no entity and no type alias`
353
+ );
354
+ }
355
+ return;
356
+ }
357
+ case "map":
358
+ walkFieldType(type.value, `${path}.value`, entity, source, issues, info);
359
+ return;
360
+ case "array":
361
+ walkFieldType(
362
+ type.element,
363
+ `${path}.element`,
364
+ entity,
365
+ source,
366
+ issues,
367
+ info
368
+ );
369
+ return;
370
+ case "union": {
371
+ if (type.variants.length < 2) {
372
+ pushIssue(
373
+ info,
374
+ path,
375
+ "degenerate_union",
376
+ `union has ${type.variants.length} variant(s); expected at least 2`
377
+ );
378
+ }
379
+ type.variants.forEach((variant2, i) => {
380
+ walkFieldType(
381
+ variant2,
382
+ `${path}.variants.${i}`,
383
+ entity,
384
+ source,
385
+ issues,
386
+ info
387
+ );
388
+ });
389
+ const mapping = type.discriminator?.mapping;
390
+ if (mapping !== void 0) {
391
+ const refVariants = new Set(
392
+ type.variants.flatMap((vv) => vv.kind === "ref" ? [vv.ref] : [])
393
+ );
394
+ for (const [key, target] of Object.entries(mapping)) {
395
+ if (!refVariants.has(target)) {
396
+ pushIssue(
397
+ issues,
398
+ `${path}.discriminator.mapping.${key}`,
399
+ "unresolved_type_alias",
400
+ `discriminator mapping '${key}' -> '${target}' names no ref variant of the union`
401
+ );
402
+ }
403
+ }
404
+ }
405
+ return;
406
+ }
407
+ }
408
+ }
409
+ function collectRefs(type, out) {
410
+ if (type.kind === "ref") {
411
+ out.add(type.ref);
412
+ } else if (type.kind === "union") {
413
+ for (const variant2 of type.variants) {
414
+ collectRefs(variant2, out);
415
+ }
416
+ } else if (type.kind === "map") {
417
+ collectRefs(type.value, out);
418
+ } else if (type.kind === "array") {
419
+ collectRefs(type.element, out);
420
+ }
421
+ }
422
+ function checkRefCycles(namespace, source, info) {
423
+ const adjacency = /* @__PURE__ */ new Map();
424
+ for (const [key, alias] of Object.entries(source.typeAliases ?? {})) {
425
+ const refs = /* @__PURE__ */ new Set();
426
+ collectRefs(alias.type, refs);
427
+ adjacency.set(key, refs);
428
+ }
429
+ for (const [key, entity] of Object.entries(source.entities)) {
430
+ const refs = adjacency.get(key) ?? /* @__PURE__ */ new Set();
431
+ for (const field of entity.fields) {
432
+ collectRefs(field.type, refs);
433
+ }
434
+ adjacency.set(key, refs);
435
+ }
436
+ const state = /* @__PURE__ */ new Map();
437
+ const stack = [];
438
+ const reported = /* @__PURE__ */ new Set();
439
+ const visit = (node) => {
440
+ state.set(node, "gray");
441
+ stack.push(node);
442
+ for (const next of adjacency.get(node) ?? []) {
443
+ if (!adjacency.has(next)) {
444
+ continue;
445
+ }
446
+ const seen = state.get(next);
447
+ if (seen === "gray") {
448
+ const cycle = stack.slice(stack.indexOf(next));
449
+ const signature = [...cycle].sort().join("|");
450
+ if (!reported.has(signature)) {
451
+ reported.add(signature);
452
+ pushIssue(
453
+ info,
454
+ namespace,
455
+ "union_cycle",
456
+ `reference cycle: ${[...cycle, next].join(" -> ")}`
457
+ );
458
+ }
459
+ } else if (seen === void 0) {
460
+ visit(next);
461
+ }
462
+ }
463
+ stack.pop();
464
+ state.set(node, "black");
465
+ };
466
+ for (const node of adjacency.keys()) {
467
+ if (state.get(node) === void 0) {
468
+ visit(node);
469
+ }
470
+ }
471
+ }
472
+ function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues, info) {
300
473
  for (const [key, def] of Object.entries(source.enums)) {
301
474
  if (def.name !== key) {
302
475
  pushIssue(
@@ -331,17 +504,17 @@ function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues
331
504
  }
332
505
  fieldNames.add(field.name);
333
506
  checkConstraints(issues, `${fPath}.constraints`, field.constraints);
334
- if (field.type.kind === "enum") {
335
- const resolved = entity.enums?.[field.type.ref] ?? source.enums[field.type.ref];
336
- if (resolved === void 0) {
337
- pushIssue(
338
- issues,
339
- fPath,
340
- "unresolved_enum_ref",
341
- `field '${field.name}' references unknown enum '${field.type.ref}'`
342
- );
343
- }
344
- }
507
+ walkFieldType(field.type, fPath, entity, source, issues, info);
508
+ }
509
+ if (entity.additionalProperties !== void 0) {
510
+ walkFieldType(
511
+ entity.additionalProperties,
512
+ `${ePath}.additionalProperties`,
513
+ entity,
514
+ source,
515
+ issues,
516
+ info
517
+ );
345
518
  }
346
519
  for (const [localKey, def] of Object.entries(entity.enums ?? {})) {
347
520
  if (def.name !== localKey) {
@@ -439,6 +612,19 @@ function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues
439
612
  }
440
613
  });
441
614
  }
615
+ for (const [key, alias] of Object.entries(source.typeAliases ?? {})) {
616
+ const aPath = `${namespace}.typeAliases.${key}`;
617
+ if (alias.name !== key) {
618
+ pushIssue(
619
+ issues,
620
+ aPath,
621
+ "type_alias_key_mismatch",
622
+ `type alias key '${key}' does not match name '${alias.name}'`
623
+ );
624
+ }
625
+ walkFieldType(alias.type, `${aPath}.type`, void 0, source, issues, info);
626
+ }
627
+ checkRefCycles(namespace, source, info);
442
628
  }
443
629
  function validateSourceIR(value) {
444
630
  const parsed = v2.safeParse(SourceIrSchema, value);
@@ -447,10 +633,14 @@ function validateSourceIR(value) {
447
633
  }
448
634
  const source = parsed.output;
449
635
  const issues = [];
636
+ const info = [];
450
637
  const lookup = (ns, name) => ns === source.namespace ? source.entities[name] : void 0;
451
638
  const isPresent = (ns) => ns === source.namespace;
452
- checkSource(source.namespace, source, lookup, isPresent, issues);
453
- return issues.length > 0 ? { ok: false, issues } : { ok: true, value: source };
639
+ checkSource(source.namespace, source, lookup, isPresent, issues, info);
640
+ if (issues.length > 0) {
641
+ return { ok: false, issues };
642
+ }
643
+ return info.length > 0 ? { ok: true, value: source, info } : { ok: true, value: source };
454
644
  }
455
645
  function validateIR(value) {
456
646
  const parsed = v2.safeParse(IrSchema, value);
@@ -459,6 +649,7 @@ function validateIR(value) {
459
649
  }
460
650
  const ir = parsed.output;
461
651
  const issues = [];
652
+ const info = [];
462
653
  if (!isCompatible(ir.irVersion)) {
463
654
  pushIssue(
464
655
  issues,
@@ -478,9 +669,12 @@ function validateIR(value) {
478
669
  `source key '${key}' does not match namespace '${source.namespace}'`
479
670
  );
480
671
  }
481
- checkSource(source.namespace, source, lookup, isPresent, issues);
672
+ checkSource(source.namespace, source, lookup, isPresent, issues, info);
673
+ }
674
+ if (issues.length > 0) {
675
+ return { ok: false, issues };
482
676
  }
483
- return issues.length > 0 ? { ok: false, issues } : { ok: true, value: ir };
677
+ return info.length > 0 ? { ok: true, value: ir, info } : { ok: true, value: ir };
484
678
  }
485
679
  function assertIR(value) {
486
680
  const result = validateIR(value);
@@ -539,6 +733,147 @@ var EnumBuilderImpl = class {
539
733
  return this.#def;
540
734
  }
541
735
  };
736
+ function checkedScalar(path, t) {
737
+ if (!v3.is(ScalarTypeSchema, t)) {
738
+ throw new IrBuildError(path, `unknown scalar type '${t}'`);
739
+ }
740
+ return { kind: "scalar", scalar: t };
741
+ }
742
+ var UnionBuilderImpl = class _UnionBuilderImpl {
743
+ #path;
744
+ #variants = [];
745
+ #discriminator;
746
+ constructor(path) {
747
+ this.#path = path;
748
+ }
749
+ scalar(t) {
750
+ this.#variants.push(checkedScalar(this.#path, t));
751
+ return this;
752
+ }
753
+ enum(ref) {
754
+ this.#variants.push({ kind: "enum", ref });
755
+ return this;
756
+ }
757
+ ref(name) {
758
+ this.#variants.push({ kind: "ref", ref: name });
759
+ return this;
760
+ }
761
+ union(build) {
762
+ const nested = new _UnionBuilderImpl(this.#path);
763
+ build(nested);
764
+ for (const variant2 of nested.#variants) {
765
+ this.#variants.push(variant2);
766
+ }
767
+ return this;
768
+ }
769
+ map(build) {
770
+ const value = new TypeAliasBuilderImpl(this.#path, "map-value");
771
+ build(value);
772
+ this.#variants.push({ kind: "map", value: value.build().type });
773
+ return this;
774
+ }
775
+ array(build) {
776
+ const element = new TypeAliasBuilderImpl(this.#path, "array-element");
777
+ build(element);
778
+ this.#variants.push({ kind: "array", element: element.build().type });
779
+ return this;
780
+ }
781
+ unknown(hint) {
782
+ this.#variants.push(
783
+ hint === void 0 ? { kind: "unknown" } : { kind: "unknown", hint }
784
+ );
785
+ return this;
786
+ }
787
+ discriminator(propertyName, mapping) {
788
+ this.#discriminator = mapping === void 0 ? { propertyName } : { propertyName, mapping };
789
+ return this;
790
+ }
791
+ build() {
792
+ if (this.#variants.length < 2) {
793
+ throw new IrBuildError(
794
+ this.#path,
795
+ `union() needs at least 2 variants, got ${this.#variants.length}`
796
+ );
797
+ }
798
+ const mapping = this.#discriminator?.mapping;
799
+ if (mapping !== void 0) {
800
+ const refVariants = new Set(
801
+ this.#variants.flatMap((vv) => vv.kind === "ref" ? [vv.ref] : [])
802
+ );
803
+ for (const [key, target] of Object.entries(mapping)) {
804
+ if (!refVariants.has(target)) {
805
+ throw new IrBuildError(
806
+ this.#path,
807
+ `discriminator mapping '${key}' -> '${target}' names no ref variant`
808
+ );
809
+ }
810
+ }
811
+ }
812
+ const type = {
813
+ kind: "union",
814
+ variants: this.#variants
815
+ };
816
+ if (this.#discriminator !== void 0) {
817
+ type.discriminator = this.#discriminator;
818
+ }
819
+ return type;
820
+ }
821
+ };
822
+ var TypeAliasBuilderImpl = class _TypeAliasBuilderImpl {
823
+ #path;
824
+ #name;
825
+ #type = { kind: "unknown" };
826
+ #doc;
827
+ constructor(path, name) {
828
+ this.#path = path;
829
+ this.#name = name;
830
+ }
831
+ scalar(t) {
832
+ this.#type = checkedScalar(this.#path, t);
833
+ return this;
834
+ }
835
+ enum(ref) {
836
+ this.#type = { kind: "enum", ref };
837
+ return this;
838
+ }
839
+ ref(name) {
840
+ this.#type = { kind: "ref", ref: name };
841
+ return this;
842
+ }
843
+ union(build) {
844
+ const nested = new UnionBuilderImpl(this.#path);
845
+ build(nested);
846
+ this.#type = nested.build();
847
+ return this;
848
+ }
849
+ map(build) {
850
+ const value = new _TypeAliasBuilderImpl(this.#path, "map-value");
851
+ build(value);
852
+ this.#type = { kind: "map", value: value.build().type };
853
+ return this;
854
+ }
855
+ array(build) {
856
+ const element = new _TypeAliasBuilderImpl(this.#path, "array-element");
857
+ build(element);
858
+ this.#type = { kind: "array", element: element.build().type };
859
+ return this;
860
+ }
861
+ unknown(hint) {
862
+ this.#type = hint === void 0 ? { kind: "unknown" } : { kind: "unknown", hint };
863
+ return this;
864
+ }
865
+ doc(text) {
866
+ this.#doc = text;
867
+ return this;
868
+ }
869
+ build() {
870
+ const alias = { name: this.#name, type: this.#type };
871
+ if (this.#doc !== void 0) {
872
+ alias.doc = this.#doc;
873
+ }
874
+ return alias;
875
+ }
876
+ };
542
877
  var FieldBuilderImpl = class {
543
878
  #path;
544
879
  #field;
@@ -566,6 +901,28 @@ var FieldBuilderImpl = class {
566
901
  this.#field.type = { kind: "enum", ref };
567
902
  return this;
568
903
  }
904
+ ref(name) {
905
+ this.#field.type = { kind: "ref", ref: name };
906
+ return this;
907
+ }
908
+ union(build) {
909
+ const builder = new UnionBuilderImpl(this.#path);
910
+ build(builder);
911
+ this.#field.type = builder.build();
912
+ return this;
913
+ }
914
+ map(build) {
915
+ const value = new TypeAliasBuilderImpl(this.#path, "map-value");
916
+ build(value);
917
+ this.#field.type = { kind: "map", value: value.build().type };
918
+ return this;
919
+ }
920
+ array(build) {
921
+ const element = new TypeAliasBuilderImpl(this.#path, "array-element");
922
+ build(element);
923
+ this.#field.type = { kind: "array", element: element.build().type };
924
+ return this;
925
+ }
569
926
  unknown(hint) {
570
927
  this.#field.type = hint === void 0 ? { kind: "unknown" } : { kind: "unknown", hint };
571
928
  return this;
@@ -709,6 +1066,7 @@ var EntityBuilderImpl = class {
709
1066
  #uniques = [];
710
1067
  #doc;
711
1068
  #dbName;
1069
+ #additionalProperties;
712
1070
  constructor(namespace, name) {
713
1071
  this.#namespace = namespace;
714
1072
  this.#name = name;
@@ -735,6 +1093,15 @@ var EntityBuilderImpl = class {
735
1093
  this.#fields.push(builder);
736
1094
  return this;
737
1095
  }
1096
+ additionalProperties(def) {
1097
+ const value = new TypeAliasBuilderImpl(
1098
+ `${this.#namespace}.${this.#name}.additionalProperties`,
1099
+ "additionalProperties"
1100
+ );
1101
+ def(value);
1102
+ this.#additionalProperties = value.build().type;
1103
+ return this;
1104
+ }
738
1105
  relation(name, def) {
739
1106
  const builder = new RelationBuilderImpl(name);
740
1107
  def(builder);
@@ -800,6 +1167,9 @@ var EntityBuilderImpl = class {
800
1167
  if (this.#dbName !== void 0) {
801
1168
  entity.dbName = this.#dbName;
802
1169
  }
1170
+ if (this.#additionalProperties !== void 0) {
1171
+ entity.additionalProperties = this.#additionalProperties;
1172
+ }
803
1173
  return entity;
804
1174
  }
805
1175
  };
@@ -810,6 +1180,7 @@ var SourceIrBuilderImpl = class {
810
1180
  #entities = [];
811
1181
  #entityNames = /* @__PURE__ */ new Set();
812
1182
  #enums = {};
1183
+ #typeAliases = {};
813
1184
  constructor(init) {
814
1185
  this.#namespace = init.namespace;
815
1186
  this.#parser = init.parser;
@@ -834,6 +1205,21 @@ var SourceIrBuilderImpl = class {
834
1205
  this.#entities.push(builder);
835
1206
  return this;
836
1207
  }
1208
+ addTypeAlias(name, def) {
1209
+ if (name in this.#typeAliases) {
1210
+ throw new IrBuildError(
1211
+ `${this.#namespace}.typeAliases.${name}`,
1212
+ `duplicate type alias '${name}'`
1213
+ );
1214
+ }
1215
+ const builder = new TypeAliasBuilderImpl(
1216
+ `${this.#namespace}.typeAliases.${name}`,
1217
+ name
1218
+ );
1219
+ def(builder);
1220
+ this.#typeAliases[name] = builder.build();
1221
+ return this;
1222
+ }
837
1223
  build() {
838
1224
  const source = {
839
1225
  namespace: this.#namespace,
@@ -849,6 +1235,9 @@ var SourceIrBuilderImpl = class {
849
1235
  if (this.#parserVersion !== void 0) {
850
1236
  source.parserVersion = this.#parserVersion;
851
1237
  }
1238
+ if (Object.keys(this.#typeAliases).length > 0) {
1239
+ source.typeAliases = this.#typeAliases;
1240
+ }
852
1241
  try {
853
1242
  assertSourceIR(source);
854
1243
  } catch (err) {
@@ -879,6 +1268,123 @@ function resolveEntity(ir, namespace, name) {
879
1268
  function resolveEnum(source, entity, ref) {
880
1269
  return entity?.enums?.[ref] ?? source.enums[ref];
881
1270
  }
1271
+ function resolveRef(source, ref) {
1272
+ return source.entities[ref] ?? source.typeAliases?.[ref];
1273
+ }
1274
+ function resolveTypeAlias(source, name) {
1275
+ return source.typeAliases?.[name];
1276
+ }
1277
+ function* iterTypeAliases(ir) {
1278
+ for (const [namespace, source] of Object.entries(ir.sources)) {
1279
+ for (const alias of Object.values(source.typeAliases ?? {})) {
1280
+ yield { namespace, alias };
1281
+ }
1282
+ }
1283
+ }
1284
+ function collectRefNames(type, into = /* @__PURE__ */ new Set()) {
1285
+ if (type.kind === "ref") {
1286
+ into.add(type.ref);
1287
+ } else if (type.kind === "union") {
1288
+ for (const variant2 of type.variants) {
1289
+ collectRefNames(variant2, into);
1290
+ }
1291
+ } else if (type.kind === "map") {
1292
+ collectRefNames(type.value, into);
1293
+ } else if (type.kind === "array") {
1294
+ collectRefNames(type.element, into);
1295
+ }
1296
+ return into;
1297
+ }
1298
+ function refCycleMembers(source) {
1299
+ const adjacency = /* @__PURE__ */ new Map();
1300
+ const edgesFor = (key) => {
1301
+ let set = adjacency.get(key);
1302
+ if (set === void 0) {
1303
+ set = /* @__PURE__ */ new Set();
1304
+ adjacency.set(key, set);
1305
+ }
1306
+ return set;
1307
+ };
1308
+ for (const [key, alias] of Object.entries(source.typeAliases ?? {})) {
1309
+ collectRefNames(alias.type, edgesFor(key));
1310
+ }
1311
+ for (const [key, entity] of Object.entries(source.entities)) {
1312
+ const set = edgesFor(key);
1313
+ for (const field of entity.fields) {
1314
+ collectRefNames(field.type, set);
1315
+ }
1316
+ if (entity.additionalProperties !== void 0) {
1317
+ collectRefNames(entity.additionalProperties, set);
1318
+ }
1319
+ }
1320
+ const canReachSelf = (start) => {
1321
+ const seen = /* @__PURE__ */ new Set();
1322
+ const stack = [...adjacency.get(start) ?? []];
1323
+ while (stack.length > 0) {
1324
+ const node = stack.pop();
1325
+ if (node === void 0) {
1326
+ break;
1327
+ }
1328
+ if (node === start) {
1329
+ return true;
1330
+ }
1331
+ if (seen.has(node) || !adjacency.has(node)) {
1332
+ continue;
1333
+ }
1334
+ seen.add(node);
1335
+ for (const next of adjacency.get(node) ?? []) {
1336
+ stack.push(next);
1337
+ }
1338
+ }
1339
+ return false;
1340
+ };
1341
+ const members = /* @__PURE__ */ new Set();
1342
+ for (const node of adjacency.keys()) {
1343
+ if (canReachSelf(node)) {
1344
+ members.add(node);
1345
+ }
1346
+ }
1347
+ return members;
1348
+ }
1349
+ function fieldTypeKey(type) {
1350
+ switch (type.kind) {
1351
+ case "scalar":
1352
+ return `scalar:${type.scalar}`;
1353
+ case "enum":
1354
+ return `enum:${type.ref}`;
1355
+ case "ref":
1356
+ return `ref:${type.ref}`;
1357
+ case "unknown":
1358
+ return `unknown:${type.hint ?? ""}`;
1359
+ case "map":
1360
+ return `map:${fieldTypeKey(type.value)}`;
1361
+ case "array":
1362
+ return `array:${fieldTypeKey(type.element)}`;
1363
+ case "union":
1364
+ return `union:${flattenUnion(type).map(fieldTypeKey).join(",")}`;
1365
+ }
1366
+ }
1367
+ function flattenUnion(type) {
1368
+ const out = [];
1369
+ const seen = /* @__PURE__ */ new Set();
1370
+ const push = (t) => {
1371
+ if (t.kind === "union") {
1372
+ for (const variant2 of t.variants) {
1373
+ push(variant2);
1374
+ }
1375
+ return;
1376
+ }
1377
+ const key = fieldTypeKey(t);
1378
+ if (!seen.has(key)) {
1379
+ seen.add(key);
1380
+ out.push(t);
1381
+ }
1382
+ };
1383
+ for (const variant2 of type.variants) {
1384
+ push(variant2);
1385
+ }
1386
+ return out;
1387
+ }
882
1388
  function isCrossSource(fromNamespace, rel) {
883
1389
  return rel.target.namespace !== fromNamespace;
884
1390
  }
@@ -931,10 +1437,22 @@ function scalarTsType(type) {
931
1437
  switch (type.kind) {
932
1438
  case "enum":
933
1439
  return type.ref;
1440
+ case "ref":
1441
+ return type.ref;
934
1442
  case "unknown":
935
1443
  return "unknown";
936
1444
  case "scalar":
937
1445
  return mapScalar(type.scalar);
1446
+ case "union":
1447
+ return flattenUnion(type).map(
1448
+ (variant2) => variant2.kind === "union" ? `(${scalarTsType(variant2)})` : scalarTsType(variant2)
1449
+ ).join(" | ");
1450
+ case "map":
1451
+ return `Record<string, ${scalarTsType(type.value)}>`;
1452
+ case "array": {
1453
+ const element = scalarTsType(type.element);
1454
+ return type.element.kind === "union" ? `(${element})[]` : `${element}[]`;
1455
+ }
938
1456
  }
939
1457
  }
940
1458
  function mapScalar(scalar) {
@@ -982,10 +1500,13 @@ function mapScalar(scalar) {
982
1500
  ScalarTypeSchema,
983
1501
  SourceIrSchema,
984
1502
  StringFormatSchema,
1503
+ TypeAliasSchema,
985
1504
  assertIR,
986
1505
  assertSourceIR,
1506
+ collectRefNames,
987
1507
  createFields,
988
1508
  createSourceIR,
1509
+ flattenUnion,
989
1510
  getSource,
990
1511
  isCompatible,
991
1512
  isCreateOptional,
@@ -993,11 +1514,15 @@ function mapScalar(scalar) {
993
1514
  isDbAssigned,
994
1515
  iterEntities,
995
1516
  iterFields,
1517
+ iterTypeAliases,
996
1518
  parseIR,
997
1519
  primaryKeyFields,
1520
+ refCycleMembers,
998
1521
  resolveEntity,
999
1522
  resolveEnum,
1523
+ resolveRef,
1000
1524
  resolveRelationTarget,
1525
+ resolveTypeAlias,
1001
1526
  scalarTsType,
1002
1527
  updateFields,
1003
1528
  validateIR,