@kurotako/ir 0.1.0 → 0.2.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/CHANGELOG.md +17 -0
- package/dist/index.cjs +452 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +104 -54
- package/dist/index.d.ts +104 -54
- package/dist/index.js +445 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @kurotako/ir
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7a50439: IR union type support: `FieldType` gains `ref` and `union` kinds (recursive,
|
|
8
|
+
`v.lazy`-wrapped schema, hand-written type), a `TypeAlias` registry
|
|
9
|
+
(`SourceIR.typeAliases?`), the builder API (`f.ref`, `f.union` with
|
|
10
|
+
`UnionBuilder`, `addTypeAlias`), resolution helpers (`resolveRef`,
|
|
11
|
+
`resolveTypeAlias`, `iterTypeAliases`, `flattenUnion`) and an exhaustive
|
|
12
|
+
`scalarTsType`. Validation walks field types recursively (`unresolved_ref`,
|
|
13
|
+
`unresolved_type_alias`, `type_alias_key_mismatch`), tolerates degenerate
|
|
14
|
+
unions and reference cycles through a new non-fatal `info` channel on
|
|
15
|
+
`IrValidation` (`degenerate_union`, `union_cycle`).
|
|
16
|
+
|
|
17
|
+
Bumps `IR_VERSION` from `1` to `2`; `isCompatible` stays strict equality, so a
|
|
18
|
+
persisted `irVersion: '1'` dump is rejected with `version_incompatible`.
|
|
19
|
+
|
|
3
20
|
## 0.1.0
|
|
4
21
|
|
|
5
22
|
### Minor Changes
|
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,29 @@ var IndexTypeSchema = v.picklist([
|
|
|
131
138
|
"brin",
|
|
132
139
|
"spgist"
|
|
133
140
|
]);
|
|
134
|
-
var FieldTypeSchema = v.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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({
|
|
148
|
+
kind: v.literal("union"),
|
|
149
|
+
variants: v.array(FieldTypeSchema),
|
|
150
|
+
discriminator: v.optional(
|
|
151
|
+
v.object({
|
|
152
|
+
propertyName: v.string(),
|
|
153
|
+
mapping: v.optional(v.record(v.string(), v.string()))
|
|
154
|
+
})
|
|
155
|
+
)
|
|
156
|
+
})
|
|
157
|
+
])
|
|
158
|
+
);
|
|
159
|
+
var TypeAliasSchema = v.object({
|
|
160
|
+
name: v.string(),
|
|
161
|
+
type: FieldTypeSchema,
|
|
162
|
+
doc: v.optional(v.string())
|
|
163
|
+
});
|
|
139
164
|
var ConstraintsSchema = v.object({
|
|
140
165
|
min: v.optional(v.number()),
|
|
141
166
|
max: v.optional(v.number()),
|
|
@@ -216,7 +241,8 @@ var SourceIrSchema = v.object({
|
|
|
216
241
|
parser: v.string(),
|
|
217
242
|
parserVersion: v.optional(v.string()),
|
|
218
243
|
entities: v.record(v.string(), EntitySchema),
|
|
219
|
-
enums: v.record(v.string(), EnumDefSchema)
|
|
244
|
+
enums: v.record(v.string(), EnumDefSchema),
|
|
245
|
+
typeAliases: v.optional(v.record(v.string(), TypeAliasSchema))
|
|
220
246
|
});
|
|
221
247
|
var IrSchema = v.object({
|
|
222
248
|
irVersion: v.string(),
|
|
@@ -227,7 +253,7 @@ var IrSchema = v.object({
|
|
|
227
253
|
var v2 = __toESM(require("valibot"), 1);
|
|
228
254
|
|
|
229
255
|
// src/version.ts
|
|
230
|
-
var IR_VERSION = "
|
|
256
|
+
var IR_VERSION = "2";
|
|
231
257
|
function isCompatible(irVersion) {
|
|
232
258
|
return irVersion === IR_VERSION;
|
|
233
259
|
}
|
|
@@ -296,7 +322,134 @@ function checkEnumValues(issues, path, values) {
|
|
|
296
322
|
seen.add(value.name);
|
|
297
323
|
}
|
|
298
324
|
}
|
|
299
|
-
function
|
|
325
|
+
function walkFieldType(type, path, entity, source, issues, info) {
|
|
326
|
+
switch (type.kind) {
|
|
327
|
+
case "scalar":
|
|
328
|
+
case "unknown":
|
|
329
|
+
return;
|
|
330
|
+
case "enum": {
|
|
331
|
+
const resolved = entity?.enums?.[type.ref] ?? source.enums[type.ref];
|
|
332
|
+
if (resolved === void 0) {
|
|
333
|
+
pushIssue(
|
|
334
|
+
issues,
|
|
335
|
+
path,
|
|
336
|
+
"unresolved_enum_ref",
|
|
337
|
+
`field type references unknown enum '${type.ref}'`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
case "ref": {
|
|
343
|
+
const known = source.entities[type.ref] !== void 0 || source.typeAliases?.[type.ref] !== void 0;
|
|
344
|
+
if (!known) {
|
|
345
|
+
pushIssue(
|
|
346
|
+
issues,
|
|
347
|
+
path,
|
|
348
|
+
"unresolved_ref",
|
|
349
|
+
`ref '${type.ref}' resolves to no entity and no type alias`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
case "union": {
|
|
355
|
+
if (type.variants.length < 2) {
|
|
356
|
+
pushIssue(
|
|
357
|
+
info,
|
|
358
|
+
path,
|
|
359
|
+
"degenerate_union",
|
|
360
|
+
`union has ${type.variants.length} variant(s); expected at least 2`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
type.variants.forEach((variant2, i) => {
|
|
364
|
+
walkFieldType(
|
|
365
|
+
variant2,
|
|
366
|
+
`${path}.variants.${i}`,
|
|
367
|
+
entity,
|
|
368
|
+
source,
|
|
369
|
+
issues,
|
|
370
|
+
info
|
|
371
|
+
);
|
|
372
|
+
});
|
|
373
|
+
const mapping = type.discriminator?.mapping;
|
|
374
|
+
if (mapping !== void 0) {
|
|
375
|
+
const refVariants = new Set(
|
|
376
|
+
type.variants.flatMap((vv) => vv.kind === "ref" ? [vv.ref] : [])
|
|
377
|
+
);
|
|
378
|
+
for (const [key, target] of Object.entries(mapping)) {
|
|
379
|
+
if (!refVariants.has(target)) {
|
|
380
|
+
pushIssue(
|
|
381
|
+
issues,
|
|
382
|
+
`${path}.discriminator.mapping.${key}`,
|
|
383
|
+
"unresolved_type_alias",
|
|
384
|
+
`discriminator mapping '${key}' -> '${target}' names no ref variant of the union`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
function collectRefs(type, out) {
|
|
394
|
+
if (type.kind === "ref") {
|
|
395
|
+
out.add(type.ref);
|
|
396
|
+
} else if (type.kind === "union") {
|
|
397
|
+
for (const variant2 of type.variants) {
|
|
398
|
+
collectRefs(variant2, out);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function checkRefCycles(namespace, source, info) {
|
|
403
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
404
|
+
for (const [key, alias] of Object.entries(source.typeAliases ?? {})) {
|
|
405
|
+
const refs = /* @__PURE__ */ new Set();
|
|
406
|
+
collectRefs(alias.type, refs);
|
|
407
|
+
adjacency.set(key, refs);
|
|
408
|
+
}
|
|
409
|
+
for (const [key, entity] of Object.entries(source.entities)) {
|
|
410
|
+
const refs = adjacency.get(key) ?? /* @__PURE__ */ new Set();
|
|
411
|
+
for (const field of entity.fields) {
|
|
412
|
+
collectRefs(field.type, refs);
|
|
413
|
+
}
|
|
414
|
+
adjacency.set(key, refs);
|
|
415
|
+
}
|
|
416
|
+
const state = /* @__PURE__ */ new Map();
|
|
417
|
+
const stack = [];
|
|
418
|
+
const reported = /* @__PURE__ */ new Set();
|
|
419
|
+
const visit = (node) => {
|
|
420
|
+
state.set(node, "gray");
|
|
421
|
+
stack.push(node);
|
|
422
|
+
for (const next of adjacency.get(node) ?? []) {
|
|
423
|
+
if (!adjacency.has(next)) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
const seen = state.get(next);
|
|
427
|
+
if (seen === "gray") {
|
|
428
|
+
const cycle = stack.slice(stack.indexOf(next));
|
|
429
|
+
const signature = [...cycle].sort().join("|");
|
|
430
|
+
if (!reported.has(signature)) {
|
|
431
|
+
reported.add(signature);
|
|
432
|
+
pushIssue(
|
|
433
|
+
info,
|
|
434
|
+
namespace,
|
|
435
|
+
"union_cycle",
|
|
436
|
+
`reference cycle: ${[...cycle, next].join(" -> ")}`
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
} else if (seen === void 0) {
|
|
440
|
+
visit(next);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
stack.pop();
|
|
444
|
+
state.set(node, "black");
|
|
445
|
+
};
|
|
446
|
+
for (const node of adjacency.keys()) {
|
|
447
|
+
if (state.get(node) === void 0) {
|
|
448
|
+
visit(node);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues, info) {
|
|
300
453
|
for (const [key, def] of Object.entries(source.enums)) {
|
|
301
454
|
if (def.name !== key) {
|
|
302
455
|
pushIssue(
|
|
@@ -331,17 +484,7 @@ function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues
|
|
|
331
484
|
}
|
|
332
485
|
fieldNames.add(field.name);
|
|
333
486
|
checkConstraints(issues, `${fPath}.constraints`, field.constraints);
|
|
334
|
-
|
|
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
|
-
}
|
|
487
|
+
walkFieldType(field.type, fPath, entity, source, issues, info);
|
|
345
488
|
}
|
|
346
489
|
for (const [localKey, def] of Object.entries(entity.enums ?? {})) {
|
|
347
490
|
if (def.name !== localKey) {
|
|
@@ -439,6 +582,19 @@ function checkSource(namespace, source, lookupEntity, isNamespacePresent, issues
|
|
|
439
582
|
}
|
|
440
583
|
});
|
|
441
584
|
}
|
|
585
|
+
for (const [key, alias] of Object.entries(source.typeAliases ?? {})) {
|
|
586
|
+
const aPath = `${namespace}.typeAliases.${key}`;
|
|
587
|
+
if (alias.name !== key) {
|
|
588
|
+
pushIssue(
|
|
589
|
+
issues,
|
|
590
|
+
aPath,
|
|
591
|
+
"type_alias_key_mismatch",
|
|
592
|
+
`type alias key '${key}' does not match name '${alias.name}'`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
walkFieldType(alias.type, `${aPath}.type`, void 0, source, issues, info);
|
|
596
|
+
}
|
|
597
|
+
checkRefCycles(namespace, source, info);
|
|
442
598
|
}
|
|
443
599
|
function validateSourceIR(value) {
|
|
444
600
|
const parsed = v2.safeParse(SourceIrSchema, value);
|
|
@@ -447,10 +603,14 @@ function validateSourceIR(value) {
|
|
|
447
603
|
}
|
|
448
604
|
const source = parsed.output;
|
|
449
605
|
const issues = [];
|
|
606
|
+
const info = [];
|
|
450
607
|
const lookup = (ns, name) => ns === source.namespace ? source.entities[name] : void 0;
|
|
451
608
|
const isPresent = (ns) => ns === source.namespace;
|
|
452
|
-
checkSource(source.namespace, source, lookup, isPresent, issues);
|
|
453
|
-
|
|
609
|
+
checkSource(source.namespace, source, lookup, isPresent, issues, info);
|
|
610
|
+
if (issues.length > 0) {
|
|
611
|
+
return { ok: false, issues };
|
|
612
|
+
}
|
|
613
|
+
return info.length > 0 ? { ok: true, value: source, info } : { ok: true, value: source };
|
|
454
614
|
}
|
|
455
615
|
function validateIR(value) {
|
|
456
616
|
const parsed = v2.safeParse(IrSchema, value);
|
|
@@ -459,6 +619,7 @@ function validateIR(value) {
|
|
|
459
619
|
}
|
|
460
620
|
const ir = parsed.output;
|
|
461
621
|
const issues = [];
|
|
622
|
+
const info = [];
|
|
462
623
|
if (!isCompatible(ir.irVersion)) {
|
|
463
624
|
pushIssue(
|
|
464
625
|
issues,
|
|
@@ -478,9 +639,12 @@ function validateIR(value) {
|
|
|
478
639
|
`source key '${key}' does not match namespace '${source.namespace}'`
|
|
479
640
|
);
|
|
480
641
|
}
|
|
481
|
-
checkSource(source.namespace, source, lookup, isPresent, issues);
|
|
642
|
+
checkSource(source.namespace, source, lookup, isPresent, issues, info);
|
|
643
|
+
}
|
|
644
|
+
if (issues.length > 0) {
|
|
645
|
+
return { ok: false, issues };
|
|
482
646
|
}
|
|
483
|
-
return
|
|
647
|
+
return info.length > 0 ? { ok: true, value: ir, info } : { ok: true, value: ir };
|
|
484
648
|
}
|
|
485
649
|
function assertIR(value) {
|
|
486
650
|
const result = validateIR(value);
|
|
@@ -539,6 +703,123 @@ var EnumBuilderImpl = class {
|
|
|
539
703
|
return this.#def;
|
|
540
704
|
}
|
|
541
705
|
};
|
|
706
|
+
function checkedScalar(path, t) {
|
|
707
|
+
if (!v3.is(ScalarTypeSchema, t)) {
|
|
708
|
+
throw new IrBuildError(path, `unknown scalar type '${t}'`);
|
|
709
|
+
}
|
|
710
|
+
return { kind: "scalar", scalar: t };
|
|
711
|
+
}
|
|
712
|
+
var UnionBuilderImpl = class _UnionBuilderImpl {
|
|
713
|
+
#path;
|
|
714
|
+
#variants = [];
|
|
715
|
+
#discriminator;
|
|
716
|
+
constructor(path) {
|
|
717
|
+
this.#path = path;
|
|
718
|
+
}
|
|
719
|
+
scalar(t) {
|
|
720
|
+
this.#variants.push(checkedScalar(this.#path, t));
|
|
721
|
+
return this;
|
|
722
|
+
}
|
|
723
|
+
enum(ref) {
|
|
724
|
+
this.#variants.push({ kind: "enum", ref });
|
|
725
|
+
return this;
|
|
726
|
+
}
|
|
727
|
+
ref(name) {
|
|
728
|
+
this.#variants.push({ kind: "ref", ref: name });
|
|
729
|
+
return this;
|
|
730
|
+
}
|
|
731
|
+
union(build) {
|
|
732
|
+
const nested = new _UnionBuilderImpl(this.#path);
|
|
733
|
+
build(nested);
|
|
734
|
+
for (const variant2 of nested.#variants) {
|
|
735
|
+
this.#variants.push(variant2);
|
|
736
|
+
}
|
|
737
|
+
return this;
|
|
738
|
+
}
|
|
739
|
+
unknown(hint) {
|
|
740
|
+
this.#variants.push(
|
|
741
|
+
hint === void 0 ? { kind: "unknown" } : { kind: "unknown", hint }
|
|
742
|
+
);
|
|
743
|
+
return this;
|
|
744
|
+
}
|
|
745
|
+
discriminator(propertyName, mapping) {
|
|
746
|
+
this.#discriminator = mapping === void 0 ? { propertyName } : { propertyName, mapping };
|
|
747
|
+
return this;
|
|
748
|
+
}
|
|
749
|
+
build() {
|
|
750
|
+
if (this.#variants.length < 2) {
|
|
751
|
+
throw new IrBuildError(
|
|
752
|
+
this.#path,
|
|
753
|
+
`union() needs at least 2 variants, got ${this.#variants.length}`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
const mapping = this.#discriminator?.mapping;
|
|
757
|
+
if (mapping !== void 0) {
|
|
758
|
+
const refVariants = new Set(
|
|
759
|
+
this.#variants.flatMap((vv) => vv.kind === "ref" ? [vv.ref] : [])
|
|
760
|
+
);
|
|
761
|
+
for (const [key, target] of Object.entries(mapping)) {
|
|
762
|
+
if (!refVariants.has(target)) {
|
|
763
|
+
throw new IrBuildError(
|
|
764
|
+
this.#path,
|
|
765
|
+
`discriminator mapping '${key}' -> '${target}' names no ref variant`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
const type = {
|
|
771
|
+
kind: "union",
|
|
772
|
+
variants: this.#variants
|
|
773
|
+
};
|
|
774
|
+
if (this.#discriminator !== void 0) {
|
|
775
|
+
type.discriminator = this.#discriminator;
|
|
776
|
+
}
|
|
777
|
+
return type;
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
var TypeAliasBuilderImpl = class {
|
|
781
|
+
#path;
|
|
782
|
+
#name;
|
|
783
|
+
#type = { kind: "unknown" };
|
|
784
|
+
#doc;
|
|
785
|
+
constructor(path, name) {
|
|
786
|
+
this.#path = path;
|
|
787
|
+
this.#name = name;
|
|
788
|
+
}
|
|
789
|
+
scalar(t) {
|
|
790
|
+
this.#type = checkedScalar(this.#path, t);
|
|
791
|
+
return this;
|
|
792
|
+
}
|
|
793
|
+
enum(ref) {
|
|
794
|
+
this.#type = { kind: "enum", ref };
|
|
795
|
+
return this;
|
|
796
|
+
}
|
|
797
|
+
ref(name) {
|
|
798
|
+
this.#type = { kind: "ref", ref: name };
|
|
799
|
+
return this;
|
|
800
|
+
}
|
|
801
|
+
union(build) {
|
|
802
|
+
const nested = new UnionBuilderImpl(this.#path);
|
|
803
|
+
build(nested);
|
|
804
|
+
this.#type = nested.build();
|
|
805
|
+
return this;
|
|
806
|
+
}
|
|
807
|
+
unknown(hint) {
|
|
808
|
+
this.#type = hint === void 0 ? { kind: "unknown" } : { kind: "unknown", hint };
|
|
809
|
+
return this;
|
|
810
|
+
}
|
|
811
|
+
doc(text) {
|
|
812
|
+
this.#doc = text;
|
|
813
|
+
return this;
|
|
814
|
+
}
|
|
815
|
+
build() {
|
|
816
|
+
const alias = { name: this.#name, type: this.#type };
|
|
817
|
+
if (this.#doc !== void 0) {
|
|
818
|
+
alias.doc = this.#doc;
|
|
819
|
+
}
|
|
820
|
+
return alias;
|
|
821
|
+
}
|
|
822
|
+
};
|
|
542
823
|
var FieldBuilderImpl = class {
|
|
543
824
|
#path;
|
|
544
825
|
#field;
|
|
@@ -566,6 +847,16 @@ var FieldBuilderImpl = class {
|
|
|
566
847
|
this.#field.type = { kind: "enum", ref };
|
|
567
848
|
return this;
|
|
568
849
|
}
|
|
850
|
+
ref(name) {
|
|
851
|
+
this.#field.type = { kind: "ref", ref: name };
|
|
852
|
+
return this;
|
|
853
|
+
}
|
|
854
|
+
union(build) {
|
|
855
|
+
const builder = new UnionBuilderImpl(this.#path);
|
|
856
|
+
build(builder);
|
|
857
|
+
this.#field.type = builder.build();
|
|
858
|
+
return this;
|
|
859
|
+
}
|
|
569
860
|
unknown(hint) {
|
|
570
861
|
this.#field.type = hint === void 0 ? { kind: "unknown" } : { kind: "unknown", hint };
|
|
571
862
|
return this;
|
|
@@ -810,6 +1101,7 @@ var SourceIrBuilderImpl = class {
|
|
|
810
1101
|
#entities = [];
|
|
811
1102
|
#entityNames = /* @__PURE__ */ new Set();
|
|
812
1103
|
#enums = {};
|
|
1104
|
+
#typeAliases = {};
|
|
813
1105
|
constructor(init) {
|
|
814
1106
|
this.#namespace = init.namespace;
|
|
815
1107
|
this.#parser = init.parser;
|
|
@@ -834,6 +1126,21 @@ var SourceIrBuilderImpl = class {
|
|
|
834
1126
|
this.#entities.push(builder);
|
|
835
1127
|
return this;
|
|
836
1128
|
}
|
|
1129
|
+
addTypeAlias(name, def) {
|
|
1130
|
+
if (name in this.#typeAliases) {
|
|
1131
|
+
throw new IrBuildError(
|
|
1132
|
+
`${this.#namespace}.typeAliases.${name}`,
|
|
1133
|
+
`duplicate type alias '${name}'`
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
const builder = new TypeAliasBuilderImpl(
|
|
1137
|
+
`${this.#namespace}.typeAliases.${name}`,
|
|
1138
|
+
name
|
|
1139
|
+
);
|
|
1140
|
+
def(builder);
|
|
1141
|
+
this.#typeAliases[name] = builder.build();
|
|
1142
|
+
return this;
|
|
1143
|
+
}
|
|
837
1144
|
build() {
|
|
838
1145
|
const source = {
|
|
839
1146
|
namespace: this.#namespace,
|
|
@@ -849,6 +1156,9 @@ var SourceIrBuilderImpl = class {
|
|
|
849
1156
|
if (this.#parserVersion !== void 0) {
|
|
850
1157
|
source.parserVersion = this.#parserVersion;
|
|
851
1158
|
}
|
|
1159
|
+
if (Object.keys(this.#typeAliases).length > 0) {
|
|
1160
|
+
source.typeAliases = this.#typeAliases;
|
|
1161
|
+
}
|
|
852
1162
|
try {
|
|
853
1163
|
assertSourceIR(source);
|
|
854
1164
|
} catch (err) {
|
|
@@ -879,6 +1189,112 @@ function resolveEntity(ir, namespace, name) {
|
|
|
879
1189
|
function resolveEnum(source, entity, ref) {
|
|
880
1190
|
return entity?.enums?.[ref] ?? source.enums[ref];
|
|
881
1191
|
}
|
|
1192
|
+
function resolveRef(source, ref) {
|
|
1193
|
+
return source.entities[ref] ?? source.typeAliases?.[ref];
|
|
1194
|
+
}
|
|
1195
|
+
function resolveTypeAlias(source, name) {
|
|
1196
|
+
return source.typeAliases?.[name];
|
|
1197
|
+
}
|
|
1198
|
+
function* iterTypeAliases(ir) {
|
|
1199
|
+
for (const [namespace, source] of Object.entries(ir.sources)) {
|
|
1200
|
+
for (const alias of Object.values(source.typeAliases ?? {})) {
|
|
1201
|
+
yield { namespace, alias };
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function collectRefNames(type, into = /* @__PURE__ */ new Set()) {
|
|
1206
|
+
if (type.kind === "ref") {
|
|
1207
|
+
into.add(type.ref);
|
|
1208
|
+
} else if (type.kind === "union") {
|
|
1209
|
+
for (const variant2 of type.variants) {
|
|
1210
|
+
collectRefNames(variant2, into);
|
|
1211
|
+
}
|
|
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
|
+
}
|
|
1234
|
+
const canReachSelf = (start) => {
|
|
1235
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1236
|
+
const stack = [...adjacency.get(start) ?? []];
|
|
1237
|
+
while (stack.length > 0) {
|
|
1238
|
+
const node = stack.pop();
|
|
1239
|
+
if (node === void 0) {
|
|
1240
|
+
break;
|
|
1241
|
+
}
|
|
1242
|
+
if (node === start) {
|
|
1243
|
+
return true;
|
|
1244
|
+
}
|
|
1245
|
+
if (seen.has(node) || !adjacency.has(node)) {
|
|
1246
|
+
continue;
|
|
1247
|
+
}
|
|
1248
|
+
seen.add(node);
|
|
1249
|
+
for (const next of adjacency.get(node) ?? []) {
|
|
1250
|
+
stack.push(next);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return false;
|
|
1254
|
+
};
|
|
1255
|
+
const members = /* @__PURE__ */ new Set();
|
|
1256
|
+
for (const node of adjacency.keys()) {
|
|
1257
|
+
if (canReachSelf(node)) {
|
|
1258
|
+
members.add(node);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return members;
|
|
1262
|
+
}
|
|
1263
|
+
function fieldTypeKey(type) {
|
|
1264
|
+
switch (type.kind) {
|
|
1265
|
+
case "scalar":
|
|
1266
|
+
return `scalar:${type.scalar}`;
|
|
1267
|
+
case "enum":
|
|
1268
|
+
return `enum:${type.ref}`;
|
|
1269
|
+
case "ref":
|
|
1270
|
+
return `ref:${type.ref}`;
|
|
1271
|
+
case "unknown":
|
|
1272
|
+
return `unknown:${type.hint ?? ""}`;
|
|
1273
|
+
case "union":
|
|
1274
|
+
return `union:${flattenUnion(type).map(fieldTypeKey).join(",")}`;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
function flattenUnion(type) {
|
|
1278
|
+
const out = [];
|
|
1279
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1280
|
+
const push = (t) => {
|
|
1281
|
+
if (t.kind === "union") {
|
|
1282
|
+
for (const variant2 of t.variants) {
|
|
1283
|
+
push(variant2);
|
|
1284
|
+
}
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
const key = fieldTypeKey(t);
|
|
1288
|
+
if (!seen.has(key)) {
|
|
1289
|
+
seen.add(key);
|
|
1290
|
+
out.push(t);
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
for (const variant2 of type.variants) {
|
|
1294
|
+
push(variant2);
|
|
1295
|
+
}
|
|
1296
|
+
return out;
|
|
1297
|
+
}
|
|
882
1298
|
function isCrossSource(fromNamespace, rel) {
|
|
883
1299
|
return rel.target.namespace !== fromNamespace;
|
|
884
1300
|
}
|
|
@@ -931,10 +1347,16 @@ function scalarTsType(type) {
|
|
|
931
1347
|
switch (type.kind) {
|
|
932
1348
|
case "enum":
|
|
933
1349
|
return type.ref;
|
|
1350
|
+
case "ref":
|
|
1351
|
+
return type.ref;
|
|
934
1352
|
case "unknown":
|
|
935
1353
|
return "unknown";
|
|
936
1354
|
case "scalar":
|
|
937
1355
|
return mapScalar(type.scalar);
|
|
1356
|
+
case "union":
|
|
1357
|
+
return flattenUnion(type).map(
|
|
1358
|
+
(variant2) => variant2.kind === "union" ? `(${scalarTsType(variant2)})` : scalarTsType(variant2)
|
|
1359
|
+
).join(" | ");
|
|
938
1360
|
}
|
|
939
1361
|
}
|
|
940
1362
|
function mapScalar(scalar) {
|
|
@@ -982,10 +1404,13 @@ function mapScalar(scalar) {
|
|
|
982
1404
|
ScalarTypeSchema,
|
|
983
1405
|
SourceIrSchema,
|
|
984
1406
|
StringFormatSchema,
|
|
1407
|
+
TypeAliasSchema,
|
|
985
1408
|
assertIR,
|
|
986
1409
|
assertSourceIR,
|
|
1410
|
+
collectRefNames,
|
|
987
1411
|
createFields,
|
|
988
1412
|
createSourceIR,
|
|
1413
|
+
flattenUnion,
|
|
989
1414
|
getSource,
|
|
990
1415
|
isCompatible,
|
|
991
1416
|
isCreateOptional,
|
|
@@ -993,11 +1418,15 @@ function mapScalar(scalar) {
|
|
|
993
1418
|
isDbAssigned,
|
|
994
1419
|
iterEntities,
|
|
995
1420
|
iterFields,
|
|
1421
|
+
iterTypeAliases,
|
|
996
1422
|
parseIR,
|
|
997
1423
|
primaryKeyFields,
|
|
1424
|
+
refCycleMembers,
|
|
998
1425
|
resolveEntity,
|
|
999
1426
|
resolveEnum,
|
|
1427
|
+
resolveRef,
|
|
1000
1428
|
resolveRelationTarget,
|
|
1429
|
+
resolveTypeAlias,
|
|
1001
1430
|
scalarTsType,
|
|
1002
1431
|
updateFields,
|
|
1003
1432
|
validateIR,
|