@atscript/typescript 0.1.24 → 0.1.26

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.mjs CHANGED
@@ -944,6 +944,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
944
944
  __is_atscript_annotated_type: true,
945
945
  type: newBase.type,
946
946
  metadata,
947
+ id: newBase.id,
947
948
  validator(opts) {
948
949
  return new Validator(this, opts);
949
950
  }
@@ -954,6 +955,10 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
954
955
  annotate(key, value, asArray) {
955
956
  annotate(this.$metadata, key, value, asArray);
956
957
  return this;
958
+ },
959
+ id(value) {
960
+ this.$type.id = value;
961
+ return this;
957
962
  }
958
963
  };
959
964
  return handle;
@@ -964,40 +969,112 @@ function isPhantomType(def) {
964
969
 
965
970
  //#endregion
966
971
  //#region packages/typescript/src/json-schema.ts
972
+ /**
973
+ * Detects a discriminator property across union items.
974
+ *
975
+ * Scans all items for object-typed members that share a common property
976
+ * with distinct const/literal values. If exactly one such property exists,
977
+ * it is returned as the discriminator.
978
+ */ function detectDiscriminator(items) {
979
+ if (items.length < 2) return null;
980
+ for (const item of items) if (item.type.kind !== "object") return null;
981
+ const firstObj = items[0].type;
982
+ const candidates = [];
983
+ for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
984
+ const validCandidates = [];
985
+ for (const candidate of candidates) {
986
+ const values = new Set();
987
+ const mapping = {};
988
+ let valid = true;
989
+ for (let i = 0; i < items.length; i++) {
990
+ const obj = items[i].type;
991
+ const prop = obj.props.get(candidate);
992
+ if (!prop || prop.type.kind !== "" || prop.type.value === undefined) {
993
+ valid = false;
994
+ break;
995
+ }
996
+ const val = prop.type.value;
997
+ if (values.has(val)) {
998
+ valid = false;
999
+ break;
1000
+ }
1001
+ values.add(val);
1002
+ mapping[String(val)] = `#/oneOf/${i}`;
1003
+ }
1004
+ if (valid) validCandidates.push({
1005
+ propertyName: candidate,
1006
+ mapping
1007
+ });
1008
+ }
1009
+ if (validCandidates.length === 1) return validCandidates[0];
1010
+ return null;
1011
+ }
967
1012
  function buildJsonSchema(type) {
1013
+ const defs = {};
1014
+ let isRoot = true;
1015
+ const buildObject = (d) => {
1016
+ const properties = {};
1017
+ const required = [];
1018
+ for (const [key, val] of d.type.props.entries()) {
1019
+ if (isPhantomType(val)) continue;
1020
+ properties[key] = build(val);
1021
+ if (!val.optional) required.push(key);
1022
+ }
1023
+ const schema$1 = {
1024
+ type: "object",
1025
+ properties
1026
+ };
1027
+ if (required.length > 0) schema$1.required = required;
1028
+ return schema$1;
1029
+ };
968
1030
  const build = (def) => {
1031
+ if (def.id && def.type.kind === "object" && !isRoot) {
1032
+ const name = def.id;
1033
+ if (!defs[name]) {
1034
+ defs[name] = {};
1035
+ defs[name] = buildObject(def);
1036
+ }
1037
+ return { $ref: `#/$defs/${name}` };
1038
+ }
1039
+ isRoot = false;
969
1040
  const meta = def.metadata;
970
1041
  return forAnnotatedType(def, {
971
1042
  phantom() {
972
1043
  return {};
973
1044
  },
974
1045
  object(d) {
975
- const properties = {};
976
- const required = [];
977
- for (const [key, val] of d.type.props.entries()) {
978
- if (isPhantomType(val)) continue;
979
- properties[key] = build(val);
980
- if (!val.optional) required.push(key);
981
- }
982
- const schema = {
983
- type: "object",
984
- properties
985
- };
986
- if (required.length > 0) schema.required = required;
987
- return schema;
1046
+ return buildObject(d);
988
1047
  },
989
1048
  array(d) {
990
- const schema = {
1049
+ const schema$1 = {
991
1050
  type: "array",
992
1051
  items: build(d.type.of)
993
1052
  };
994
1053
  const minLength = meta.get("expect.minLength");
995
- if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
1054
+ if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
996
1055
  const maxLength = meta.get("expect.maxLength");
997
- if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
998
- return schema;
1056
+ if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1057
+ return schema$1;
999
1058
  },
1000
1059
  union(d) {
1060
+ const disc = detectDiscriminator(d.type.items);
1061
+ if (disc) {
1062
+ const oneOf = d.type.items.map(build);
1063
+ const mapping = {};
1064
+ for (const [val, origPath] of Object.entries(disc.mapping)) {
1065
+ const idx = Number.parseInt(origPath.split("/").pop());
1066
+ const item = d.type.items[idx];
1067
+ if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
1068
+ else mapping[val] = origPath;
1069
+ }
1070
+ return {
1071
+ oneOf,
1072
+ discriminator: {
1073
+ propertyName: disc.propertyName,
1074
+ mapping
1075
+ }
1076
+ };
1077
+ }
1001
1078
  return { anyOf: d.type.items.map(build) };
1002
1079
  },
1003
1080
  intersection(d) {
@@ -1011,33 +1088,38 @@ function buildJsonSchema(type) {
1011
1088
  };
1012
1089
  },
1013
1090
  final(d) {
1014
- const schema = {};
1015
- if (d.type.value !== undefined) schema.const = d.type.value;
1091
+ const schema$1 = {};
1092
+ if (d.type.value !== undefined) schema$1.const = d.type.value;
1016
1093
  if (d.type.designType && d.type.designType !== "any") {
1017
- schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1018
- if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
1094
+ schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1095
+ if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
1019
1096
  }
1020
- if (schema.type === "string") {
1021
- if (meta.get("meta.required")) schema.minLength = 1;
1097
+ if (schema$1.type === "string") {
1098
+ if (meta.get("meta.required")) schema$1.minLength = 1;
1022
1099
  const minLength = meta.get("expect.minLength");
1023
- if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
1100
+ if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
1024
1101
  const maxLength = meta.get("expect.maxLength");
1025
- if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1102
+ if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1026
1103
  const patterns = meta.get("expect.pattern");
1027
- if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
1028
- else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1104
+ if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
1105
+ else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1029
1106
  }
1030
- if (schema.type === "number" || schema.type === "integer") {
1107
+ if (schema$1.type === "number" || schema$1.type === "integer") {
1031
1108
  const min = meta.get("expect.min");
1032
- if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
1109
+ if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
1033
1110
  const max = meta.get("expect.max");
1034
- if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
1111
+ if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
1035
1112
  }
1036
- return schema;
1113
+ return schema$1;
1037
1114
  }
1038
1115
  });
1039
1116
  };
1040
- return build(type);
1117
+ const schema = build(type);
1118
+ if (Object.keys(defs).length > 0) return {
1119
+ ...schema,
1120
+ $defs: defs
1121
+ };
1122
+ return schema;
1041
1123
  }
1042
1124
 
1043
1125
  //#endregion
@@ -1058,11 +1140,23 @@ var JsRenderer = class extends BaseRenderer {
1058
1140
  this.writeln("/* eslint-disable */");
1059
1141
  this.writeln("/* oxlint-disable */");
1060
1142
  const imports = ["defineAnnotatedType as $", "annotate as $a"];
1143
+ const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
1144
+ if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
1061
1145
  const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
1062
1146
  if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
1063
1147
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
1064
1148
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
1065
1149
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
1150
+ const nameCounts = new Map();
1151
+ const nodesByName = new Map();
1152
+ for (const node of this.doc.nodes) if (node.__typeId != null && node.id) {
1153
+ const name = node.id;
1154
+ nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
1155
+ if (!nodesByName.has(name)) nodesByName.set(name, []);
1156
+ nodesByName.get(name).push(node);
1157
+ }
1158
+ for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
1159
+ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
1066
1160
  }
1067
1161
  buildAdHocMap(annotateNodes) {
1068
1162
  const map = new Map();
@@ -1076,6 +1170,15 @@ var JsRenderer = class extends BaseRenderer {
1076
1170
  }
1077
1171
  return map.size > 0 ? map : null;
1078
1172
  }
1173
+ /**
1174
+ * Checks if any ad-hoc annotation path extends beyond the current _propPath,
1175
+ * meaning annotations target properties inside a referenced type.
1176
+ */ hasAdHocAnnotationsThroughRef() {
1177
+ if (!this._adHocAnnotations || this._propPath.length === 0) return false;
1178
+ const prefix = this._propPath.join(".") + ".";
1179
+ for (const key of this._adHocAnnotations.keys()) if (key.startsWith(prefix)) return true;
1180
+ return false;
1181
+ }
1079
1182
  post() {
1080
1183
  for (const node of this.postAnnotate) if (node.entity === "annotate") {
1081
1184
  const annotateNode = node;
@@ -1106,6 +1209,8 @@ else {
1106
1209
  this.writeln("static __is_atscript_annotated_type = true");
1107
1210
  this.writeln("static type = {}");
1108
1211
  this.writeln("static metadata = new Map()");
1212
+ const typeId = this.typeIds.get(node);
1213
+ if (typeId) this.writeln(`static id = "${typeId}"`);
1109
1214
  this.renderJsonSchemaMethod(node);
1110
1215
  this.renderExampleDataMethod(node);
1111
1216
  }
@@ -1184,6 +1289,8 @@ else {
1184
1289
  case "type": {
1185
1290
  const def = node.getDefinition();
1186
1291
  const handle = this.toAnnotatedHandle(def, true);
1292
+ const typeId = this.typeIds.get(node) ?? (node.__typeId != null ? node.id : undefined);
1293
+ if (typeId) handle.id(typeId);
1187
1294
  return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1188
1295
  }
1189
1296
  case "prop": {
@@ -1310,6 +1417,14 @@ else handle.prop(prop.id, propHandle.$type);
1310
1417
  return this;
1311
1418
  }
1312
1419
  }
1420
+ if (this._adHocAnnotations && this.hasAdHocAnnotationsThroughRef()) {
1421
+ let resolved = decl ? this.doc.mergeIntersection(decl) : undefined;
1422
+ if (resolved && isInterface(resolved)) resolved = resolved.getDefinition() || resolved;
1423
+ if (resolved) {
1424
+ this.annotateType(resolved, name);
1425
+ return this;
1426
+ }
1427
+ }
1313
1428
  const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
1314
1429
  this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${ref.id}${chain})`);
1315
1430
  if (!ref.hasChain) {
@@ -1543,11 +1658,29 @@ else targetValue = "true";
1543
1658
  const targetName = node.targetName;
1544
1659
  const targetDef = this.resolveTargetDef(targetName);
1545
1660
  this.writeln("// Ad-hoc annotations for ", targetName);
1661
+ const allClones = [];
1662
+ const entryAccessors = [];
1546
1663
  for (const entry of node.entries) {
1547
1664
  const anns = entry.annotations;
1548
1665
  if (!anns || anns.length === 0) continue;
1549
1666
  const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
1550
- const accessors = this.buildMutatingAccessors(targetName, targetDef, parts);
1667
+ const { accessors, clones } = this.buildMutatingAccessors(targetName, targetDef, parts);
1668
+ allClones.push(...clones);
1669
+ entryAccessors.push({
1670
+ entry,
1671
+ accessors
1672
+ });
1673
+ }
1674
+ const cloneKeys = new Set();
1675
+ for (const clone of allClones) {
1676
+ const key = `${clone.parentPath}|${clone.propName}`;
1677
+ if (!cloneKeys.has(key)) {
1678
+ cloneKeys.add(key);
1679
+ this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
1680
+ }
1681
+ }
1682
+ for (const { entry, accessors } of entryAccessors) {
1683
+ const anns = entry.annotations;
1551
1684
  for (const accessor of accessors) {
1552
1685
  const cleared = new Set();
1553
1686
  for (const an of anns) {
@@ -1597,15 +1730,21 @@ else targetValue = "true";
1597
1730
  prefix: `${targetName}.type`,
1598
1731
  def: targetDef
1599
1732
  }];
1733
+ const clones = [];
1600
1734
  for (let i = 0; i < parts.length; i++) {
1601
1735
  const nextAccessors = [];
1602
1736
  for (const { prefix, def } of accessors) {
1603
1737
  const results = this.buildPropPaths(def, parts[i]);
1604
- if (results.length > 0) for (const result of results) if (i < parts.length - 1) nextAccessors.push({
1605
- prefix: `${prefix}${result.path}?.type`,
1606
- def: result.propDef
1607
- });
1608
- else nextAccessors.push({
1738
+ if (results.length > 0) for (const result of results) if (i < parts.length - 1) {
1739
+ if (result.propDef && isRef(result.propDef)) clones.push({
1740
+ parentPath: prefix,
1741
+ propName: parts[i]
1742
+ });
1743
+ nextAccessors.push({
1744
+ prefix: `${prefix}${result.path}?.type`,
1745
+ def: result.propDef
1746
+ });
1747
+ } else nextAccessors.push({
1609
1748
  prefix: `${prefix}${result.path}?`,
1610
1749
  def: result.propDef
1611
1750
  });
@@ -1619,7 +1758,10 @@ else {
1619
1758
  }
1620
1759
  accessors = nextAccessors;
1621
1760
  }
1622
- return accessors.map((a) => a.prefix);
1761
+ return {
1762
+ accessors: accessors.map((a) => a.prefix),
1763
+ clones
1764
+ };
1623
1765
  }
1624
1766
  /**
1625
1767
  * Finds a property in a type tree at compile time, returning all
@@ -1655,7 +1797,7 @@ else {
1655
1797
  return [];
1656
1798
  }
1657
1799
  constructor(doc, opts) {
1658
- super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [];
1800
+ super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), _define_property(this, "typeIds", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map();
1659
1801
  }
1660
1802
  };
1661
1803
 
package/dist/utils.cjs CHANGED
@@ -428,6 +428,63 @@ else metadata.set(key, [a, value]);
428
428
  } else metadata.set(key, [value]);
429
429
  else metadata.set(key, value);
430
430
  }
431
+ function cloneRefProp(parentType, propName) {
432
+ if (parentType.kind !== "object") return;
433
+ const objType = parentType;
434
+ const existing = objType.props.get(propName);
435
+ if (!existing) return;
436
+ const clonedType = cloneTypeDef(existing.type);
437
+ objType.props.set(propName, {
438
+ __is_atscript_annotated_type: true,
439
+ type: clonedType,
440
+ metadata: new Map(existing.metadata),
441
+ id: existing.id,
442
+ optional: existing.optional,
443
+ validator(opts) {
444
+ return new Validator(this, opts);
445
+ }
446
+ });
447
+ }
448
+ function cloneTypeDef(type) {
449
+ if (type.kind === "object") {
450
+ const obj = type;
451
+ return {
452
+ kind: "object",
453
+ props: new Map(Array.from(obj.props.entries()).map(([k, v]) => [k, {
454
+ __is_atscript_annotated_type: true,
455
+ type: v.type,
456
+ metadata: new Map(v.metadata),
457
+ id: v.id,
458
+ optional: v.optional,
459
+ validator(opts) {
460
+ return new Validator(this, opts);
461
+ }
462
+ }])),
463
+ propsPatterns: [...obj.propsPatterns],
464
+ tags: new Set(obj.tags)
465
+ };
466
+ }
467
+ if (type.kind === "array") {
468
+ const arr = type;
469
+ return {
470
+ kind: "array",
471
+ of: arr.of,
472
+ tags: new Set(arr.tags)
473
+ };
474
+ }
475
+ if (type.kind === "union" || type.kind === "intersection" || type.kind === "tuple") {
476
+ const complex = type;
477
+ return {
478
+ kind: type.kind,
479
+ items: [...complex.items],
480
+ tags: new Set(complex.tags)
481
+ };
482
+ }
483
+ return {
484
+ ...type,
485
+ tags: new Set(type.tags)
486
+ };
487
+ }
431
488
  function defineAnnotatedType(_kind, base) {
432
489
  const kind = _kind || "";
433
490
  const type = base?.type || {};
@@ -519,6 +576,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
519
576
  __is_atscript_annotated_type: true,
520
577
  type: newBase.type,
521
578
  metadata,
579
+ id: newBase.id,
522
580
  validator(opts) {
523
581
  return new Validator(this, opts);
524
582
  }
@@ -529,6 +587,10 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
529
587
  annotate(key, value, asArray) {
530
588
  annotate(this.$metadata, key, value, asArray);
531
589
  return this;
590
+ },
591
+ id(value) {
592
+ this.$type.id = value;
593
+ return this;
532
594
  }
533
595
  };
534
596
  return handle;
@@ -552,40 +614,112 @@ function isAnnotatedTypeOfPrimitive(t) {
552
614
 
553
615
  //#endregion
554
616
  //#region packages/typescript/src/json-schema.ts
617
+ /**
618
+ * Detects a discriminator property across union items.
619
+ *
620
+ * Scans all items for object-typed members that share a common property
621
+ * with distinct const/literal values. If exactly one such property exists,
622
+ * it is returned as the discriminator.
623
+ */ function detectDiscriminator(items) {
624
+ if (items.length < 2) return null;
625
+ for (const item of items) if (item.type.kind !== "object") return null;
626
+ const firstObj = items[0].type;
627
+ const candidates = [];
628
+ for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
629
+ const validCandidates = [];
630
+ for (const candidate of candidates) {
631
+ const values = new Set();
632
+ const mapping = {};
633
+ let valid = true;
634
+ for (let i = 0; i < items.length; i++) {
635
+ const obj = items[i].type;
636
+ const prop = obj.props.get(candidate);
637
+ if (!prop || prop.type.kind !== "" || prop.type.value === undefined) {
638
+ valid = false;
639
+ break;
640
+ }
641
+ const val = prop.type.value;
642
+ if (values.has(val)) {
643
+ valid = false;
644
+ break;
645
+ }
646
+ values.add(val);
647
+ mapping[String(val)] = `#/oneOf/${i}`;
648
+ }
649
+ if (valid) validCandidates.push({
650
+ propertyName: candidate,
651
+ mapping
652
+ });
653
+ }
654
+ if (validCandidates.length === 1) return validCandidates[0];
655
+ return null;
656
+ }
555
657
  function buildJsonSchema(type) {
658
+ const defs = {};
659
+ let isRoot = true;
660
+ const buildObject = (d) => {
661
+ const properties = {};
662
+ const required = [];
663
+ for (const [key, val] of d.type.props.entries()) {
664
+ if (isPhantomType(val)) continue;
665
+ properties[key] = build$1(val);
666
+ if (!val.optional) required.push(key);
667
+ }
668
+ const schema$1 = {
669
+ type: "object",
670
+ properties
671
+ };
672
+ if (required.length > 0) schema$1.required = required;
673
+ return schema$1;
674
+ };
556
675
  const build$1 = (def) => {
676
+ if (def.id && def.type.kind === "object" && !isRoot) {
677
+ const name = def.id;
678
+ if (!defs[name]) {
679
+ defs[name] = {};
680
+ defs[name] = buildObject(def);
681
+ }
682
+ return { $ref: `#/$defs/${name}` };
683
+ }
684
+ isRoot = false;
557
685
  const meta = def.metadata;
558
686
  return forAnnotatedType(def, {
559
687
  phantom() {
560
688
  return {};
561
689
  },
562
690
  object(d) {
563
- const properties = {};
564
- const required = [];
565
- for (const [key, val] of d.type.props.entries()) {
566
- if (isPhantomType(val)) continue;
567
- properties[key] = build$1(val);
568
- if (!val.optional) required.push(key);
569
- }
570
- const schema = {
571
- type: "object",
572
- properties
573
- };
574
- if (required.length > 0) schema.required = required;
575
- return schema;
691
+ return buildObject(d);
576
692
  },
577
693
  array(d) {
578
- const schema = {
694
+ const schema$1 = {
579
695
  type: "array",
580
696
  items: build$1(d.type.of)
581
697
  };
582
698
  const minLength = meta.get("expect.minLength");
583
- if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
699
+ if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
584
700
  const maxLength = meta.get("expect.maxLength");
585
- if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
586
- return schema;
701
+ if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
702
+ return schema$1;
587
703
  },
588
704
  union(d) {
705
+ const disc = detectDiscriminator(d.type.items);
706
+ if (disc) {
707
+ const oneOf = d.type.items.map(build$1);
708
+ const mapping = {};
709
+ for (const [val, origPath] of Object.entries(disc.mapping)) {
710
+ const idx = Number.parseInt(origPath.split("/").pop());
711
+ const item = d.type.items[idx];
712
+ if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
713
+ else mapping[val] = origPath;
714
+ }
715
+ return {
716
+ oneOf,
717
+ discriminator: {
718
+ propertyName: disc.propertyName,
719
+ mapping
720
+ }
721
+ };
722
+ }
589
723
  return { anyOf: d.type.items.map(build$1) };
590
724
  },
591
725
  intersection(d) {
@@ -599,38 +733,54 @@ function buildJsonSchema(type) {
599
733
  };
600
734
  },
601
735
  final(d) {
602
- const schema = {};
603
- if (d.type.value !== undefined) schema.const = d.type.value;
736
+ const schema$1 = {};
737
+ if (d.type.value !== undefined) schema$1.const = d.type.value;
604
738
  if (d.type.designType && d.type.designType !== "any") {
605
- schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
606
- if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
739
+ schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
740
+ if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
607
741
  }
608
- if (schema.type === "string") {
609
- if (meta.get("meta.required")) schema.minLength = 1;
742
+ if (schema$1.type === "string") {
743
+ if (meta.get("meta.required")) schema$1.minLength = 1;
610
744
  const minLength = meta.get("expect.minLength");
611
- if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
745
+ if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
612
746
  const maxLength = meta.get("expect.maxLength");
613
- if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
747
+ if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
614
748
  const patterns = meta.get("expect.pattern");
615
- if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
616
- else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
749
+ if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
750
+ else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
617
751
  }
618
- if (schema.type === "number" || schema.type === "integer") {
752
+ if (schema$1.type === "number" || schema$1.type === "integer") {
619
753
  const min = meta.get("expect.min");
620
- if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
754
+ if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
621
755
  const max = meta.get("expect.max");
622
- if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
756
+ if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
623
757
  }
624
- return schema;
758
+ return schema$1;
625
759
  }
626
760
  });
627
761
  };
628
- return build$1(type);
762
+ const schema = build$1(type);
763
+ if (Object.keys(defs).length > 0) return {
764
+ ...schema,
765
+ $defs: defs
766
+ };
767
+ return schema;
629
768
  }
630
769
  function fromJsonSchema(schema) {
770
+ const defsSource = schema.$defs || schema.definitions || {};
771
+ const resolved = new Map();
631
772
  const convert = (s) => {
632
773
  if (!s || Object.keys(s).length === 0) return defineAnnotatedType().designType("any").$type;
633
- if (s.$ref) throw new Error("$ref is not supported by fromJsonSchema. Dereference the schema first.");
774
+ if (s.$ref) {
775
+ const refName = s.$ref.replace(/^#\/(\$defs|definitions)\//, "");
776
+ if (resolved.has(refName)) return resolved.get(refName);
777
+ if (defsSource[refName]) {
778
+ const type = convert(defsSource[refName]);
779
+ resolved.set(refName, type);
780
+ return type;
781
+ }
782
+ throw new Error(`Unresolvable $ref: ${s.$ref}`);
783
+ }
634
784
  if ("const" in s) {
635
785
  const val = s.const;
636
786
  const dt = val === null ? "null" : typeof val;
@@ -718,6 +868,24 @@ function fromJsonSchema(schema) {
718
868
  };
719
869
  return convert(schema);
720
870
  }
871
+ function mergeJsonSchemas(types) {
872
+ const mergedDefs = {};
873
+ const schemas = {};
874
+ for (const type of types) {
875
+ const name = type.id;
876
+ if (!name) throw new Error("mergeJsonSchemas: all types must have an id");
877
+ const schema = buildJsonSchema(type);
878
+ if (schema.$defs) {
879
+ for (const [defName, defSchema] of Object.entries(schema.$defs)) if (!mergedDefs[defName]) mergedDefs[defName] = defSchema;
880
+ const { $defs: _,...rest } = schema;
881
+ schemas[name] = rest;
882
+ } else schemas[name] = schema;
883
+ }
884
+ return {
885
+ schemas,
886
+ $defs: mergedDefs
887
+ };
888
+ }
721
889
 
722
890
  //#endregion
723
891
  //#region packages/typescript/src/default-value.ts
@@ -911,6 +1079,7 @@ function serializeNode(def, path, options) {
911
1079
  metadata: serializeMetadata(def.metadata, path, def.type.kind, options)
912
1080
  };
913
1081
  if (def.optional) result.optional = true;
1082
+ if (def.id) result.id = def.id;
914
1083
  return result;
915
1084
  }
916
1085
  function serializeTypeDef(def, path, options) {
@@ -1014,6 +1183,7 @@ function deserializeNode(data) {
1014
1183
  }
1015
1184
  };
1016
1185
  if (data.optional) result.optional = true;
1186
+ if (data.id) result.id = data.id;
1017
1187
  return result;
1018
1188
  }
1019
1189
  function deserializeTypeDef(t) {
@@ -1064,6 +1234,7 @@ exports.Validator = Validator
1064
1234
  exports.ValidatorError = ValidatorError
1065
1235
  exports.annotate = annotate
1066
1236
  exports.buildJsonSchema = buildJsonSchema
1237
+ exports.cloneRefProp = cloneRefProp
1067
1238
  exports.createDataFromAnnotatedType = createDataFromAnnotatedType
1068
1239
  exports.defineAnnotatedType = defineAnnotatedType
1069
1240
  exports.deserializeAnnotatedType = deserializeAnnotatedType
@@ -1073,5 +1244,6 @@ exports.fromJsonSchema = fromJsonSchema
1073
1244
  exports.isAnnotatedType = isAnnotatedType
1074
1245
  exports.isAnnotatedTypeOfPrimitive = isAnnotatedTypeOfPrimitive
1075
1246
  exports.isPhantomType = isPhantomType
1247
+ exports.mergeJsonSchemas = mergeJsonSchemas
1076
1248
  exports.serializeAnnotatedType = serializeAnnotatedType
1077
1249
  exports.throwFeatureDisabled = throwFeatureDisabled