@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/CHANGELOG.md +55 -0
- package/dist/index.cjs +548 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -54
- package/dist/index.d.ts +118 -54
- package/dist/index.js +541 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 = "
|
|
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
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
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
|
|
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,
|