@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.js CHANGED
@@ -55,11 +55,31 @@ var IndexTypeSchema = v.picklist([
55
55
  "brin",
56
56
  "spgist"
57
57
  ]);
58
- var FieldTypeSchema = v.variant("kind", [
59
- v.object({ kind: v.literal("scalar"), scalar: ScalarTypeSchema }),
60
- v.object({ kind: v.literal("enum"), ref: v.string() }),
61
- v.object({ kind: v.literal("unknown"), hint: v.optional(v.string()) })
62
- ]);
58
+ var FieldTypeSchema = v.lazy(
59
+ () => v.variant("kind", [
60
+ v.object({ kind: v.literal("scalar"), scalar: ScalarTypeSchema }),
61
+ v.object({ kind: v.literal("enum"), ref: v.string() }),
62
+ v.object({ kind: v.literal("unknown"), hint: v.optional(v.string()) }),
63
+ v.object({ kind: v.literal("ref"), ref: v.string() }),
64
+ v.object({ kind: v.literal("map"), value: FieldTypeSchema }),
65
+ v.object({ kind: v.literal("array"), element: FieldTypeSchema }),
66
+ v.object({
67
+ kind: v.literal("union"),
68
+ variants: v.array(FieldTypeSchema),
69
+ discriminator: v.optional(
70
+ v.object({
71
+ propertyName: v.string(),
72
+ mapping: v.optional(v.record(v.string(), v.string()))
73
+ })
74
+ )
75
+ })
76
+ ])
77
+ );
78
+ var TypeAliasSchema = v.object({
79
+ name: v.string(),
80
+ type: FieldTypeSchema,
81
+ doc: v.optional(v.string())
82
+ });
63
83
  var ConstraintsSchema = v.object({
64
84
  min: v.optional(v.number()),
65
85
  max: v.optional(v.number()),
@@ -127,6 +147,7 @@ var CompositeUniqueSchema = v.object({
127
147
  var EntitySchema = v.object({
128
148
  name: v.string(),
129
149
  fields: v.array(FieldSchema),
150
+ additionalProperties: v.optional(FieldTypeSchema),
130
151
  relations: v.array(RelationSchema),
131
152
  enums: v.optional(v.record(v.string(), EnumDefSchema)),
132
153
  primaryKey: v.optional(v.array(v.string())),
@@ -140,7 +161,8 @@ var SourceIrSchema = v.object({
140
161
  parser: v.string(),
141
162
  parserVersion: v.optional(v.string()),
142
163
  entities: v.record(v.string(), EntitySchema),
143
- enums: v.record(v.string(), EnumDefSchema)
164
+ enums: v.record(v.string(), EnumDefSchema),
165
+ typeAliases: v.optional(v.record(v.string(), TypeAliasSchema))
144
166
  });
145
167
  var IrSchema = v.object({
146
168
  irVersion: v.string(),
@@ -151,7 +173,7 @@ var IrSchema = v.object({
151
173
  import * as v2 from "valibot";
152
174
 
153
175
  // src/version.ts
154
- var IR_VERSION = "1";
176
+ var IR_VERSION = "4";
155
177
  function isCompatible(irVersion) {
156
178
  return irVersion === IR_VERSION;
157
179
  }
@@ -220,7 +242,151 @@ function checkEnumValues(issues, path, values) {
220
242
  seen.add(value.name);
221
243
  }
222
244
  }
223
- function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues) {
245
+ function walkFieldType(type, path, entity, source, issues, info) {
246
+ switch (type.kind) {
247
+ case "scalar":
248
+ case "unknown":
249
+ return;
250
+ case "enum": {
251
+ const resolved = entity?.enums?.[type.ref] ?? source.enums[type.ref];
252
+ if (resolved === void 0) {
253
+ pushIssue(
254
+ issues,
255
+ path,
256
+ "unresolved_enum_ref",
257
+ `field type references unknown enum '${type.ref}'`
258
+ );
259
+ }
260
+ return;
261
+ }
262
+ case "ref": {
263
+ const known = source.entities[type.ref] !== void 0 || source.typeAliases?.[type.ref] !== void 0;
264
+ if (!known) {
265
+ pushIssue(
266
+ issues,
267
+ path,
268
+ "unresolved_ref",
269
+ `ref '${type.ref}' resolves to no entity and no type alias`
270
+ );
271
+ }
272
+ return;
273
+ }
274
+ case "map":
275
+ walkFieldType(type.value, `${path}.value`, entity, source, issues, info);
276
+ return;
277
+ case "array":
278
+ walkFieldType(
279
+ type.element,
280
+ `${path}.element`,
281
+ entity,
282
+ source,
283
+ issues,
284
+ info
285
+ );
286
+ return;
287
+ case "union": {
288
+ if (type.variants.length < 2) {
289
+ pushIssue(
290
+ info,
291
+ path,
292
+ "degenerate_union",
293
+ `union has ${type.variants.length} variant(s); expected at least 2`
294
+ );
295
+ }
296
+ type.variants.forEach((variant2, i) => {
297
+ walkFieldType(
298
+ variant2,
299
+ `${path}.variants.${i}`,
300
+ entity,
301
+ source,
302
+ issues,
303
+ info
304
+ );
305
+ });
306
+ const mapping = type.discriminator?.mapping;
307
+ if (mapping !== void 0) {
308
+ const refVariants = new Set(
309
+ type.variants.flatMap((vv) => vv.kind === "ref" ? [vv.ref] : [])
310
+ );
311
+ for (const [key, target] of Object.entries(mapping)) {
312
+ if (!refVariants.has(target)) {
313
+ pushIssue(
314
+ issues,
315
+ `${path}.discriminator.mapping.${key}`,
316
+ "unresolved_type_alias",
317
+ `discriminator mapping '${key}' -> '${target}' names no ref variant of the union`
318
+ );
319
+ }
320
+ }
321
+ }
322
+ return;
323
+ }
324
+ }
325
+ }
326
+ function collectRefs(type, out) {
327
+ if (type.kind === "ref") {
328
+ out.add(type.ref);
329
+ } else if (type.kind === "union") {
330
+ for (const variant2 of type.variants) {
331
+ collectRefs(variant2, out);
332
+ }
333
+ } else if (type.kind === "map") {
334
+ collectRefs(type.value, out);
335
+ } else if (type.kind === "array") {
336
+ collectRefs(type.element, out);
337
+ }
338
+ }
339
+ function checkRefCycles(namespace, source, info) {
340
+ const adjacency = /* @__PURE__ */ new Map();
341
+ for (const [key, alias] of Object.entries(source.typeAliases ?? {})) {
342
+ const refs = /* @__PURE__ */ new Set();
343
+ collectRefs(alias.type, refs);
344
+ adjacency.set(key, refs);
345
+ }
346
+ for (const [key, entity] of Object.entries(source.entities)) {
347
+ const refs = adjacency.get(key) ?? /* @__PURE__ */ new Set();
348
+ for (const field of entity.fields) {
349
+ collectRefs(field.type, refs);
350
+ }
351
+ adjacency.set(key, refs);
352
+ }
353
+ const state = /* @__PURE__ */ new Map();
354
+ const stack = [];
355
+ const reported = /* @__PURE__ */ new Set();
356
+ const visit = (node) => {
357
+ state.set(node, "gray");
358
+ stack.push(node);
359
+ for (const next of adjacency.get(node) ?? []) {
360
+ if (!adjacency.has(next)) {
361
+ continue;
362
+ }
363
+ const seen = state.get(next);
364
+ if (seen === "gray") {
365
+ const cycle = stack.slice(stack.indexOf(next));
366
+ const signature = [...cycle].sort().join("|");
367
+ if (!reported.has(signature)) {
368
+ reported.add(signature);
369
+ pushIssue(
370
+ info,
371
+ namespace,
372
+ "union_cycle",
373
+ `reference cycle: ${[...cycle, next].join(" -> ")}`
374
+ );
375
+ }
376
+ } else if (seen === void 0) {
377
+ visit(next);
378
+ }
379
+ }
380
+ stack.pop();
381
+ state.set(node, "black");
382
+ };
383
+ for (const node of adjacency.keys()) {
384
+ if (state.get(node) === void 0) {
385
+ visit(node);
386
+ }
387
+ }
388
+ }
389
+ function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues, info) {
224
390
  for (const [key, def] of Object.entries(source.enums)) {
225
391
  if (def.name !== key) {
226
392
  pushIssue(
@@ -255,17 +421,17 @@ function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues
255
421
  }
256
422
  fieldNames.add(field.name);
257
423
  checkConstraints(issues, `${fPath}.constraints`, field.constraints);
258
- if (field.type.kind === "enum") {
259
- const resolved = entity.enums?.[field.type.ref] ?? source.enums[field.type.ref];
260
- if (resolved === void 0) {
261
- pushIssue(
262
- issues,
263
- fPath,
264
- "unresolved_enum_ref",
265
- `field '${field.name}' references unknown enum '${field.type.ref}'`
266
- );
267
- }
268
- }
424
+ walkFieldType(field.type, fPath, entity, source, issues, info);
425
+ }
426
+ if (entity.additionalProperties !== void 0) {
427
+ walkFieldType(
428
+ entity.additionalProperties,
429
+ `${ePath}.additionalProperties`,
430
+ entity,
431
+ source,
432
+ issues,
433
+ info
434
+ );
269
435
  }
270
436
  for (const [localKey, def] of Object.entries(entity.enums ?? {})) {
271
437
  if (def.name !== localKey) {
@@ -363,6 +529,19 @@ function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues
363
529
  }
364
530
  });
365
531
  }
532
+ for (const [key, alias] of Object.entries(source.typeAliases ?? {})) {
533
+ const aPath = `${namespace}.typeAliases.${key}`;
534
+ if (alias.name !== key) {
535
+ pushIssue(
536
+ issues,
537
+ aPath,
538
+ "type_alias_key_mismatch",
539
+ `type alias key '${key}' does not match name '${alias.name}'`
540
+ );
541
+ }
542
+ walkFieldType(alias.type, `${aPath}.type`, void 0, source, issues, info);
543
+ }
544
+ checkRefCycles(namespace, source, info);
366
545
  }
367
546
  function validateSourceIR(value) {
368
547
  const parsed = v2.safeParse(SourceIrSchema, value);
@@ -371,10 +550,14 @@ function validateSourceIR(value) {
371
550
  }
372
551
  const source = parsed.output;
373
552
  const issues = [];
553
+ const info = [];
374
554
  const lookup = (ns, name) => ns === source.namespace ? source.entities[name] : void 0;
375
555
  const isPresent = (ns) => ns === source.namespace;
376
- checkSource(source.namespace, source, lookup, isPresent, issues);
377
- return issues.length > 0 ? { ok: false, issues } : { ok: true, value: source };
556
+ checkSource(source.namespace, source, lookup, isPresent, issues, info);
557
+ if (issues.length > 0) {
558
+ return { ok: false, issues };
559
+ }
560
+ return info.length > 0 ? { ok: true, value: source, info } : { ok: true, value: source };
378
561
  }
379
562
  function validateIR(value) {
380
563
  const parsed = v2.safeParse(IrSchema, value);
@@ -383,6 +566,7 @@ function validateIR(value) {
383
566
  }
384
567
  const ir = parsed.output;
385
568
  const issues = [];
569
+ const info = [];
386
570
  if (!isCompatible(ir.irVersion)) {
387
571
  pushIssue(
388
572
  issues,
@@ -402,9 +586,12 @@ function validateIR(value) {
402
586
  `source key '${key}' does not match namespace '${source.namespace}'`
403
587
  );
404
588
  }
405
- checkSource(source.namespace, source, lookup, isPresent, issues);
589
+ checkSource(source.namespace, source, lookup, isPresent, issues, info);
590
+ }
591
+ if (issues.length > 0) {
592
+ return { ok: false, issues };
406
593
  }
407
- return issues.length > 0 ? { ok: false, issues } : { ok: true, value: ir };
594
+ return info.length > 0 ? { ok: true, value: ir, info } : { ok: true, value: ir };
408
595
  }
409
596
  function assertIR(value) {
410
597
  const result = validateIR(value);
@@ -463,6 +650,147 @@ var EnumBuilderImpl = class {
463
650
  return this.#def;
464
651
  }
465
652
  };
653
+ function checkedScalar(path, t) {
654
+ if (!v3.is(ScalarTypeSchema, t)) {
655
+ throw new IrBuildError(path, `unknown scalar type '${t}'`);
656
+ }
657
+ return { kind: "scalar", scalar: t };
658
+ }
659
+ var UnionBuilderImpl = class _UnionBuilderImpl {
660
+ #path;
661
+ #variants = [];
662
+ #discriminator;
663
+ constructor(path) {
664
+ this.#path = path;
665
+ }
666
+ scalar(t) {
667
+ this.#variants.push(checkedScalar(this.#path, t));
668
+ return this;
669
+ }
670
+ enum(ref) {
671
+ this.#variants.push({ kind: "enum", ref });
672
+ return this;
673
+ }
674
+ ref(name) {
675
+ this.#variants.push({ kind: "ref", ref: name });
676
+ return this;
677
+ }
678
+ union(build) {
679
+ const nested = new _UnionBuilderImpl(this.#path);
680
+ build(nested);
681
+ for (const variant2 of nested.#variants) {
682
+ this.#variants.push(variant2);
683
+ }
684
+ return this;
685
+ }
686
+ map(build) {
687
+ const value = new TypeAliasBuilderImpl(this.#path, "map-value");
688
+ build(value);
689
+ this.#variants.push({ kind: "map", value: value.build().type });
690
+ return this;
691
+ }
692
+ array(build) {
693
+ const element = new TypeAliasBuilderImpl(this.#path, "array-element");
694
+ build(element);
695
+ this.#variants.push({ kind: "array", element: element.build().type });
696
+ return this;
697
+ }
698
+ unknown(hint) {
699
+ this.#variants.push(
700
+ hint === void 0 ? { kind: "unknown" } : { kind: "unknown", hint }
701
+ );
702
+ return this;
703
+ }
704
+ discriminator(propertyName, mapping) {
705
+ this.#discriminator = mapping === void 0 ? { propertyName } : { propertyName, mapping };
706
+ return this;
707
+ }
708
+ build() {
709
+ if (this.#variants.length < 2) {
710
+ throw new IrBuildError(
711
+ this.#path,
712
+ `union() needs at least 2 variants, got ${this.#variants.length}`
713
+ );
714
+ }
715
+ const mapping = this.#discriminator?.mapping;
716
+ if (mapping !== void 0) {
717
+ const refVariants = new Set(
718
+ this.#variants.flatMap((vv) => vv.kind === "ref" ? [vv.ref] : [])
719
+ );
720
+ for (const [key, target] of Object.entries(mapping)) {
721
+ if (!refVariants.has(target)) {
722
+ throw new IrBuildError(
723
+ this.#path,
724
+ `discriminator mapping '${key}' -> '${target}' names no ref variant`
725
+ );
726
+ }
727
+ }
728
+ }
729
+ const type = {
730
+ kind: "union",
731
+ variants: this.#variants
732
+ };
733
+ if (this.#discriminator !== void 0) {
734
+ type.discriminator = this.#discriminator;
735
+ }
736
+ return type;
737
+ }
738
+ };
739
+ var TypeAliasBuilderImpl = class _TypeAliasBuilderImpl {
740
+ #path;
741
+ #name;
742
+ #type = { kind: "unknown" };
743
+ #doc;
744
+ constructor(path, name) {
745
+ this.#path = path;
746
+ this.#name = name;
747
+ }
748
+ scalar(t) {
749
+ this.#type = checkedScalar(this.#path, t);
750
+ return this;
751
+ }
752
+ enum(ref) {
753
+ this.#type = { kind: "enum", ref };
754
+ return this;
755
+ }
756
+ ref(name) {
757
+ this.#type = { kind: "ref", ref: name };
758
+ return this;
759
+ }
760
+ union(build) {
761
+ const nested = new UnionBuilderImpl(this.#path);
762
+ build(nested);
763
+ this.#type = nested.build();
764
+ return this;
765
+ }
766
+ map(build) {
767
+ const value = new _TypeAliasBuilderImpl(this.#path, "map-value");
768
+ build(value);
769
+ this.#type = { kind: "map", value: value.build().type };
770
+ return this;
771
+ }
772
+ array(build) {
773
+ const element = new _TypeAliasBuilderImpl(this.#path, "array-element");
774
+ build(element);
775
+ this.#type = { kind: "array", element: element.build().type };
776
+ return this;
777
+ }
778
+ unknown(hint) {
779
+ this.#type = hint === void 0 ? { kind: "unknown" } : { kind: "unknown", hint };
780
+ return this;
781
+ }
782
+ doc(text) {
783
+ this.#doc = text;
784
+ return this;
785
+ }
786
+ build() {
787
+ const alias = { name: this.#name, type: this.#type };
788
+ if (this.#doc !== void 0) {
789
+ alias.doc = this.#doc;
790
+ }
791
+ return alias;
792
+ }
793
+ };
466
794
  var FieldBuilderImpl = class {
467
795
  #path;
468
796
  #field;
@@ -490,6 +818,28 @@ var FieldBuilderImpl = class {
490
818
  this.#field.type = { kind: "enum", ref };
491
819
  return this;
492
820
  }
821
+ ref(name) {
822
+ this.#field.type = { kind: "ref", ref: name };
823
+ return this;
824
+ }
825
+ union(build) {
826
+ const builder = new UnionBuilderImpl(this.#path);
827
+ build(builder);
828
+ this.#field.type = builder.build();
829
+ return this;
830
+ }
831
+ map(build) {
832
+ const value = new TypeAliasBuilderImpl(this.#path, "map-value");
833
+ build(value);
834
+ this.#field.type = { kind: "map", value: value.build().type };
835
+ return this;
836
+ }
837
+ array(build) {
838
+ const element = new TypeAliasBuilderImpl(this.#path, "array-element");
839
+ build(element);
840
+ this.#field.type = { kind: "array", element: element.build().type };
841
+ return this;
842
+ }
493
843
  unknown(hint) {
494
844
  this.#field.type = hint === void 0 ? { kind: "unknown" } : { kind: "unknown", hint };
495
845
  return this;
@@ -633,6 +983,7 @@ var EntityBuilderImpl = class {
633
983
  #uniques = [];
634
984
  #doc;
635
985
  #dbName;
986
+ #additionalProperties;
636
987
  constructor(namespace, name) {
637
988
  this.#namespace = namespace;
638
989
  this.#name = name;
@@ -659,6 +1010,15 @@ var EntityBuilderImpl = class {
659
1010
  this.#fields.push(builder);
660
1011
  return this;
661
1012
  }
1013
+ additionalProperties(def) {
1014
+ const value = new TypeAliasBuilderImpl(
1015
+ `${this.#namespace}.${this.#name}.additionalProperties`,
1016
+ "additionalProperties"
1017
+ );
1018
+ def(value);
1019
+ this.#additionalProperties = value.build().type;
1020
+ return this;
1021
+ }
662
1022
  relation(name, def) {
663
1023
  const builder = new RelationBuilderImpl(name);
664
1024
  def(builder);
@@ -724,6 +1084,9 @@ var EntityBuilderImpl = class {
724
1084
  if (this.#dbName !== void 0) {
725
1085
  entity.dbName = this.#dbName;
726
1086
  }
1087
+ if (this.#additionalProperties !== void 0) {
1088
+ entity.additionalProperties = this.#additionalProperties;
1089
+ }
727
1090
  return entity;
728
1091
  }
729
1092
  };
@@ -734,6 +1097,7 @@ var SourceIrBuilderImpl = class {
734
1097
  #entities = [];
735
1098
  #entityNames = /* @__PURE__ */ new Set();
736
1099
  #enums = {};
1100
+ #typeAliases = {};
737
1101
  constructor(init) {
738
1102
  this.#namespace = init.namespace;
739
1103
  this.#parser = init.parser;
@@ -758,6 +1122,21 @@ var SourceIrBuilderImpl = class {
758
1122
  this.#entities.push(builder);
759
1123
  return this;
760
1124
  }
1125
+ addTypeAlias(name, def) {
1126
+ if (name in this.#typeAliases) {
1127
+ throw new IrBuildError(
1128
+ `${this.#namespace}.typeAliases.${name}`,
1129
+ `duplicate type alias '${name}'`
1130
+ );
1131
+ }
1132
+ const builder = new TypeAliasBuilderImpl(
1133
+ `${this.#namespace}.typeAliases.${name}`,
1134
+ name
1135
+ );
1136
+ def(builder);
1137
+ this.#typeAliases[name] = builder.build();
1138
+ return this;
1139
+ }
761
1140
  build() {
762
1141
  const source = {
763
1142
  namespace: this.#namespace,
@@ -773,6 +1152,9 @@ var SourceIrBuilderImpl = class {
773
1152
  if (this.#parserVersion !== void 0) {
774
1153
  source.parserVersion = this.#parserVersion;
775
1154
  }
1155
+ if (Object.keys(this.#typeAliases).length > 0) {
1156
+ source.typeAliases = this.#typeAliases;
1157
+ }
776
1158
  try {
777
1159
  assertSourceIR(source);
778
1160
  } catch (err) {
@@ -803,6 +1185,123 @@ function resolveEntity(ir, namespace, name) {
803
1185
  function resolveEnum(source, entity, ref) {
804
1186
  return entity?.enums?.[ref] ?? source.enums[ref];
805
1187
  }
1188
+ function resolveRef(source, ref) {
1189
+ return source.entities[ref] ?? source.typeAliases?.[ref];
1190
+ }
1191
+ function resolveTypeAlias(source, name) {
1192
+ return source.typeAliases?.[name];
1193
+ }
1194
+ function* iterTypeAliases(ir) {
1195
+ for (const [namespace, source] of Object.entries(ir.sources)) {
1196
+ for (const alias of Object.values(source.typeAliases ?? {})) {
1197
+ yield { namespace, alias };
1198
+ }
1199
+ }
1200
+ }
1201
+ function collectRefNames(type, into = /* @__PURE__ */ new Set()) {
1202
+ if (type.kind === "ref") {
1203
+ into.add(type.ref);
1204
+ } else if (type.kind === "union") {
1205
+ for (const variant2 of type.variants) {
1206
+ collectRefNames(variant2, into);
1207
+ }
1208
+ } else if (type.kind === "map") {
1209
+ collectRefNames(type.value, into);
1210
+ } else if (type.kind === "array") {
1211
+ collectRefNames(type.element, into);
1212
+ }
1213
+ return into;
1214
+ }
1215
+ function refCycleMembers(source) {
1216
+ const adjacency = /* @__PURE__ */ new Map();
1217
+ const edgesFor = (key) => {
1218
+ let set = adjacency.get(key);
1219
+ if (set === void 0) {
1220
+ set = /* @__PURE__ */ new Set();
1221
+ adjacency.set(key, set);
1222
+ }
1223
+ return set;
1224
+ };
1225
+ for (const [key, alias] of Object.entries(source.typeAliases ?? {})) {
1226
+ collectRefNames(alias.type, edgesFor(key));
1227
+ }
1228
+ for (const [key, entity] of Object.entries(source.entities)) {
1229
+ const set = edgesFor(key);
1230
+ for (const field of entity.fields) {
1231
+ collectRefNames(field.type, set);
1232
+ }
1233
+ if (entity.additionalProperties !== void 0) {
1234
+ collectRefNames(entity.additionalProperties, set);
1235
+ }
1236
+ }
1237
+ const canReachSelf = (start) => {
1238
+ const seen = /* @__PURE__ */ new Set();
1239
+ const stack = [...adjacency.get(start) ?? []];
1240
+ while (stack.length > 0) {
1241
+ const node = stack.pop();
1242
+ if (node === void 0) {
1243
+ break;
1244
+ }
1245
+ if (node === start) {
1246
+ return true;
1247
+ }
1248
+ if (seen.has(node) || !adjacency.has(node)) {
1249
+ continue;
1250
+ }
1251
+ seen.add(node);
1252
+ for (const next of adjacency.get(node) ?? []) {
1253
+ stack.push(next);
1254
+ }
1255
+ }
1256
+ return false;
1257
+ };
1258
+ const members = /* @__PURE__ */ new Set();
1259
+ for (const node of adjacency.keys()) {
1260
+ if (canReachSelf(node)) {
1261
+ members.add(node);
1262
+ }
1263
+ }
1264
+ return members;
1265
+ }
1266
+ function fieldTypeKey(type) {
1267
+ switch (type.kind) {
1268
+ case "scalar":
1269
+ return `scalar:${type.scalar}`;
1270
+ case "enum":
1271
+ return `enum:${type.ref}`;
1272
+ case "ref":
1273
+ return `ref:${type.ref}`;
1274
+ case "unknown":
1275
+ return `unknown:${type.hint ?? ""}`;
1276
+ case "map":
1277
+ return `map:${fieldTypeKey(type.value)}`;
1278
+ case "array":
1279
+ return `array:${fieldTypeKey(type.element)}`;
1280
+ case "union":
1281
+ return `union:${flattenUnion(type).map(fieldTypeKey).join(",")}`;
1282
+ }
1283
+ }
1284
+ function flattenUnion(type) {
1285
+ const out = [];
1286
+ const seen = /* @__PURE__ */ new Set();
1287
+ const push = (t) => {
1288
+ if (t.kind === "union") {
1289
+ for (const variant2 of t.variants) {
1290
+ push(variant2);
1291
+ }
1292
+ return;
1293
+ }
1294
+ const key = fieldTypeKey(t);
1295
+ if (!seen.has(key)) {
1296
+ seen.add(key);
1297
+ out.push(t);
1298
+ }
1299
+ };
1300
+ for (const variant2 of type.variants) {
1301
+ push(variant2);
1302
+ }
1303
+ return out;
1304
+ }
806
1305
  function isCrossSource(fromNamespace, rel) {
807
1306
  return rel.target.namespace !== fromNamespace;
808
1307
  }
@@ -855,10 +1354,22 @@ function scalarTsType(type) {
855
1354
  switch (type.kind) {
856
1355
  case "enum":
857
1356
  return type.ref;
1357
+ case "ref":
1358
+ return type.ref;
858
1359
  case "unknown":
859
1360
  return "unknown";
860
1361
  case "scalar":
861
1362
  return mapScalar(type.scalar);
1363
+ case "union":
1364
+ return flattenUnion(type).map(
1365
+ (variant2) => variant2.kind === "union" ? `(${scalarTsType(variant2)})` : scalarTsType(variant2)
1366
+ ).join(" | ");
1367
+ case "map":
1368
+ return `Record<string, ${scalarTsType(type.value)}>`;
1369
+ case "array": {
1370
+ const element = scalarTsType(type.element);
1371
+ return type.element.kind === "union" ? `(${element})[]` : `${element}[]`;
1372
+ }
862
1373
  }
863
1374
  }
864
1375
  function mapScalar(scalar) {
@@ -905,10 +1416,13 @@ export {
905
1416
  ScalarTypeSchema,
906
1417
  SourceIrSchema,
907
1418
  StringFormatSchema,
1419
+ TypeAliasSchema,
908
1420
  assertIR,
909
1421
  assertSourceIR,
1422
+ collectRefNames,
910
1423
  createFields,
911
1424
  createSourceIR,
1425
+ flattenUnion,
912
1426
  getSource,
913
1427
  isCompatible,
914
1428
  isCreateOptional,
@@ -916,11 +1430,15 @@ export {
916
1430
  isDbAssigned,
917
1431
  iterEntities,
918
1432
  iterFields,
1433
+ iterTypeAliases,
919
1434
  parseIR,
920
1435
  primaryKeyFields,
1436
+ refCycleMembers,
921
1437
  resolveEntity,
922
1438
  resolveEnum,
1439
+ resolveRef,
923
1440
  resolveRelationTarget,
1441
+ resolveTypeAlias,
924
1442
  scalarTsType,
925
1443
  updateFields,
926
1444
  validateIR,