@atscript/typescript 0.1.25 → 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;
@@ -1005,47 +1010,71 @@ function isPhantomType(def) {
1005
1010
  return null;
1006
1011
  }
1007
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
+ };
1008
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;
1009
1040
  const meta = def.metadata;
1010
1041
  return forAnnotatedType(def, {
1011
1042
  phantom() {
1012
1043
  return {};
1013
1044
  },
1014
1045
  object(d) {
1015
- const properties = {};
1016
- const required = [];
1017
- for (const [key, val] of d.type.props.entries()) {
1018
- if (isPhantomType(val)) continue;
1019
- properties[key] = build(val);
1020
- if (!val.optional) required.push(key);
1021
- }
1022
- const schema = {
1023
- type: "object",
1024
- properties
1025
- };
1026
- if (required.length > 0) schema.required = required;
1027
- return schema;
1046
+ return buildObject(d);
1028
1047
  },
1029
1048
  array(d) {
1030
- const schema = {
1049
+ const schema$1 = {
1031
1050
  type: "array",
1032
1051
  items: build(d.type.of)
1033
1052
  };
1034
1053
  const minLength = meta.get("expect.minLength");
1035
- if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
1054
+ if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
1036
1055
  const maxLength = meta.get("expect.maxLength");
1037
- if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1038
- return schema;
1056
+ if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1057
+ return schema$1;
1039
1058
  },
1040
1059
  union(d) {
1041
1060
  const disc = detectDiscriminator(d.type.items);
1042
- if (disc) return {
1043
- oneOf: d.type.items.map(build),
1044
- discriminator: {
1045
- propertyName: disc.propertyName,
1046
- mapping: disc.mapping
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;
1047
1069
  }
1048
- };
1070
+ return {
1071
+ oneOf,
1072
+ discriminator: {
1073
+ propertyName: disc.propertyName,
1074
+ mapping
1075
+ }
1076
+ };
1077
+ }
1049
1078
  return { anyOf: d.type.items.map(build) };
1050
1079
  },
1051
1080
  intersection(d) {
@@ -1059,33 +1088,38 @@ function buildJsonSchema(type) {
1059
1088
  };
1060
1089
  },
1061
1090
  final(d) {
1062
- const schema = {};
1063
- 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;
1064
1093
  if (d.type.designType && d.type.designType !== "any") {
1065
- schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1066
- 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";
1067
1096
  }
1068
- if (schema.type === "string") {
1069
- if (meta.get("meta.required")) schema.minLength = 1;
1097
+ if (schema$1.type === "string") {
1098
+ if (meta.get("meta.required")) schema$1.minLength = 1;
1070
1099
  const minLength = meta.get("expect.minLength");
1071
- if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
1100
+ if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
1072
1101
  const maxLength = meta.get("expect.maxLength");
1073
- if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1102
+ if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1074
1103
  const patterns = meta.get("expect.pattern");
1075
- if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
1076
- 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 })));
1077
1106
  }
1078
- if (schema.type === "number" || schema.type === "integer") {
1107
+ if (schema$1.type === "number" || schema$1.type === "integer") {
1079
1108
  const min = meta.get("expect.min");
1080
- if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
1109
+ if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
1081
1110
  const max = meta.get("expect.max");
1082
- if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
1111
+ if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
1083
1112
  }
1084
- return schema;
1113
+ return schema$1;
1085
1114
  }
1086
1115
  });
1087
1116
  };
1088
- 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;
1089
1123
  }
1090
1124
 
1091
1125
  //#endregion
@@ -1106,11 +1140,23 @@ var JsRenderer = class extends BaseRenderer {
1106
1140
  this.writeln("/* eslint-disable */");
1107
1141
  this.writeln("/* oxlint-disable */");
1108
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");
1109
1145
  const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
1110
1146
  if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
1111
1147
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
1112
1148
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
1113
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}`);
1114
1160
  }
1115
1161
  buildAdHocMap(annotateNodes) {
1116
1162
  const map = new Map();
@@ -1124,6 +1170,15 @@ var JsRenderer = class extends BaseRenderer {
1124
1170
  }
1125
1171
  return map.size > 0 ? map : null;
1126
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
+ }
1127
1182
  post() {
1128
1183
  for (const node of this.postAnnotate) if (node.entity === "annotate") {
1129
1184
  const annotateNode = node;
@@ -1154,6 +1209,8 @@ else {
1154
1209
  this.writeln("static __is_atscript_annotated_type = true");
1155
1210
  this.writeln("static type = {}");
1156
1211
  this.writeln("static metadata = new Map()");
1212
+ const typeId = this.typeIds.get(node);
1213
+ if (typeId) this.writeln(`static id = "${typeId}"`);
1157
1214
  this.renderJsonSchemaMethod(node);
1158
1215
  this.renderExampleDataMethod(node);
1159
1216
  }
@@ -1232,6 +1289,8 @@ else {
1232
1289
  case "type": {
1233
1290
  const def = node.getDefinition();
1234
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);
1235
1294
  return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1236
1295
  }
1237
1296
  case "prop": {
@@ -1358,6 +1417,14 @@ else handle.prop(prop.id, propHandle.$type);
1358
1417
  return this;
1359
1418
  }
1360
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
+ }
1361
1428
  const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
1362
1429
  this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${ref.id}${chain})`);
1363
1430
  if (!ref.hasChain) {
@@ -1591,11 +1658,29 @@ else targetValue = "true";
1591
1658
  const targetName = node.targetName;
1592
1659
  const targetDef = this.resolveTargetDef(targetName);
1593
1660
  this.writeln("// Ad-hoc annotations for ", targetName);
1661
+ const allClones = [];
1662
+ const entryAccessors = [];
1594
1663
  for (const entry of node.entries) {
1595
1664
  const anns = entry.annotations;
1596
1665
  if (!anns || anns.length === 0) continue;
1597
1666
  const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
1598
- 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;
1599
1684
  for (const accessor of accessors) {
1600
1685
  const cleared = new Set();
1601
1686
  for (const an of anns) {
@@ -1645,15 +1730,21 @@ else targetValue = "true";
1645
1730
  prefix: `${targetName}.type`,
1646
1731
  def: targetDef
1647
1732
  }];
1733
+ const clones = [];
1648
1734
  for (let i = 0; i < parts.length; i++) {
1649
1735
  const nextAccessors = [];
1650
1736
  for (const { prefix, def } of accessors) {
1651
1737
  const results = this.buildPropPaths(def, parts[i]);
1652
- if (results.length > 0) for (const result of results) if (i < parts.length - 1) nextAccessors.push({
1653
- prefix: `${prefix}${result.path}?.type`,
1654
- def: result.propDef
1655
- });
1656
- 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({
1657
1748
  prefix: `${prefix}${result.path}?`,
1658
1749
  def: result.propDef
1659
1750
  });
@@ -1667,7 +1758,10 @@ else {
1667
1758
  }
1668
1759
  accessors = nextAccessors;
1669
1760
  }
1670
- return accessors.map((a) => a.prefix);
1761
+ return {
1762
+ accessors: accessors.map((a) => a.prefix),
1763
+ clones
1764
+ };
1671
1765
  }
1672
1766
  /**
1673
1767
  * Finds a property in a type tree at compile time, returning all
@@ -1703,7 +1797,7 @@ else {
1703
1797
  return [];
1704
1798
  }
1705
1799
  constructor(doc, opts) {
1706
- 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();
1707
1801
  }
1708
1802
  };
1709
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;
@@ -593,47 +655,71 @@ function isAnnotatedTypeOfPrimitive(t) {
593
655
  return null;
594
656
  }
595
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
+ };
596
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;
597
685
  const meta = def.metadata;
598
686
  return forAnnotatedType(def, {
599
687
  phantom() {
600
688
  return {};
601
689
  },
602
690
  object(d) {
603
- const properties = {};
604
- const required = [];
605
- for (const [key, val] of d.type.props.entries()) {
606
- if (isPhantomType(val)) continue;
607
- properties[key] = build$1(val);
608
- if (!val.optional) required.push(key);
609
- }
610
- const schema = {
611
- type: "object",
612
- properties
613
- };
614
- if (required.length > 0) schema.required = required;
615
- return schema;
691
+ return buildObject(d);
616
692
  },
617
693
  array(d) {
618
- const schema = {
694
+ const schema$1 = {
619
695
  type: "array",
620
696
  items: build$1(d.type.of)
621
697
  };
622
698
  const minLength = meta.get("expect.minLength");
623
- if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
699
+ if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
624
700
  const maxLength = meta.get("expect.maxLength");
625
- if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
626
- return schema;
701
+ if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
702
+ return schema$1;
627
703
  },
628
704
  union(d) {
629
705
  const disc = detectDiscriminator(d.type.items);
630
- if (disc) return {
631
- oneOf: d.type.items.map(build$1),
632
- discriminator: {
633
- propertyName: disc.propertyName,
634
- mapping: disc.mapping
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;
635
714
  }
636
- };
715
+ return {
716
+ oneOf,
717
+ discriminator: {
718
+ propertyName: disc.propertyName,
719
+ mapping
720
+ }
721
+ };
722
+ }
637
723
  return { anyOf: d.type.items.map(build$1) };
638
724
  },
639
725
  intersection(d) {
@@ -647,38 +733,54 @@ function buildJsonSchema(type) {
647
733
  };
648
734
  },
649
735
  final(d) {
650
- const schema = {};
651
- 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;
652
738
  if (d.type.designType && d.type.designType !== "any") {
653
- schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
654
- 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";
655
741
  }
656
- if (schema.type === "string") {
657
- if (meta.get("meta.required")) schema.minLength = 1;
742
+ if (schema$1.type === "string") {
743
+ if (meta.get("meta.required")) schema$1.minLength = 1;
658
744
  const minLength = meta.get("expect.minLength");
659
- if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
745
+ if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
660
746
  const maxLength = meta.get("expect.maxLength");
661
- if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
747
+ if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
662
748
  const patterns = meta.get("expect.pattern");
663
- if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
664
- 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 })));
665
751
  }
666
- if (schema.type === "number" || schema.type === "integer") {
752
+ if (schema$1.type === "number" || schema$1.type === "integer") {
667
753
  const min = meta.get("expect.min");
668
- if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
754
+ if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
669
755
  const max = meta.get("expect.max");
670
- if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
756
+ if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
671
757
  }
672
- return schema;
758
+ return schema$1;
673
759
  }
674
760
  });
675
761
  };
676
- 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;
677
768
  }
678
769
  function fromJsonSchema(schema) {
770
+ const defsSource = schema.$defs || schema.definitions || {};
771
+ const resolved = new Map();
679
772
  const convert = (s) => {
680
773
  if (!s || Object.keys(s).length === 0) return defineAnnotatedType().designType("any").$type;
681
- 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
+ }
682
784
  if ("const" in s) {
683
785
  const val = s.const;
684
786
  const dt = val === null ? "null" : typeof val;
@@ -766,6 +868,24 @@ function fromJsonSchema(schema) {
766
868
  };
767
869
  return convert(schema);
768
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
+ }
769
889
 
770
890
  //#endregion
771
891
  //#region packages/typescript/src/default-value.ts
@@ -959,6 +1079,7 @@ function serializeNode(def, path, options) {
959
1079
  metadata: serializeMetadata(def.metadata, path, def.type.kind, options)
960
1080
  };
961
1081
  if (def.optional) result.optional = true;
1082
+ if (def.id) result.id = def.id;
962
1083
  return result;
963
1084
  }
964
1085
  function serializeTypeDef(def, path, options) {
@@ -1062,6 +1183,7 @@ function deserializeNode(data) {
1062
1183
  }
1063
1184
  };
1064
1185
  if (data.optional) result.optional = true;
1186
+ if (data.id) result.id = data.id;
1065
1187
  return result;
1066
1188
  }
1067
1189
  function deserializeTypeDef(t) {
@@ -1112,6 +1234,7 @@ exports.Validator = Validator
1112
1234
  exports.ValidatorError = ValidatorError
1113
1235
  exports.annotate = annotate
1114
1236
  exports.buildJsonSchema = buildJsonSchema
1237
+ exports.cloneRefProp = cloneRefProp
1115
1238
  exports.createDataFromAnnotatedType = createDataFromAnnotatedType
1116
1239
  exports.defineAnnotatedType = defineAnnotatedType
1117
1240
  exports.deserializeAnnotatedType = deserializeAnnotatedType
@@ -1121,5 +1244,6 @@ exports.fromJsonSchema = fromJsonSchema
1121
1244
  exports.isAnnotatedType = isAnnotatedType
1122
1245
  exports.isAnnotatedTypeOfPrimitive = isAnnotatedTypeOfPrimitive
1123
1246
  exports.isPhantomType = isPhantomType
1247
+ exports.mergeJsonSchemas = mergeJsonSchemas
1124
1248
  exports.serializeAnnotatedType = serializeAnnotatedType
1125
1249
  exports.throwFeatureDisabled = throwFeatureDisabled
package/dist/utils.d.ts CHANGED
@@ -181,6 +181,7 @@ interface TAtscriptAnnotatedType<T extends TAtscriptTypeDef = TAtscriptTypeDef,
181
181
  validator(opts?: Partial<TValidatorOptions>): Validator<this, DataType>;
182
182
  metadata: TMetadataMap<AtscriptMetadata>;
183
183
  optional?: boolean;
184
+ id?: string;
184
185
  }
185
186
  /** An annotated type that is also a class constructor (i.e. a generated interface class). */
186
187
  type TAtscriptAnnotatedTypeConstructor = TAtscriptAnnotatedType & (new (...args: any[]) => any);
@@ -193,6 +194,11 @@ declare function isAnnotatedType(type: any): type is TAtscriptAnnotatedType;
193
194
  * Used by the handle's .annotate() method and by generated mutation statements.
194
195
  */
195
196
  declare function annotate<K extends keyof AtscriptMetadata>(metadata: TMetadataMap<AtscriptMetadata> | undefined, key: K, value: AtscriptMetadata[K] extends Array<infer E> ? E : AtscriptMetadata[K], asArray?: boolean): void;
197
+ /**
198
+ * Clones a property's type tree in-place so mutations don't leak to shared refs.
199
+ * Used by mutating annotate codegen when paths cross ref boundaries.
200
+ */
201
+ declare function cloneRefProp(parentType: TAtscriptTypeDef, propName: string): void;
196
202
  type TKind = '' | 'array' | 'object' | 'union' | 'intersection' | 'tuple';
197
203
  /**
198
204
  * Creates a builder handle for constructing a {@link TAtscriptAnnotatedType} at runtime.
@@ -242,6 +248,7 @@ interface TAnnotatedTypeHandle {
242
248
  name?: string;
243
249
  }, chain?: string[]): TAnnotatedTypeHandle;
244
250
  annotate(key: keyof AtscriptMetadata, value: any, asArray?: boolean): TAnnotatedTypeHandle;
251
+ id(value: string): TAnnotatedTypeHandle;
245
252
  }
246
253
  /**
247
254
  * Checks whether an annotated type is a phantom type.
@@ -299,6 +306,20 @@ declare function buildJsonSchema(type: TAtscriptAnnotatedType): TJsonSchema;
299
306
  * @returns An annotated type with full validator support.
300
307
  */
301
308
  declare function fromJsonSchema(schema: TJsonSchema): TAtscriptAnnotatedType;
309
+ /**
310
+ * Merges multiple annotated types into a combined schema map with shared `$defs`.
311
+ *
312
+ * Each type must have an `id`. The returned `schemas` object contains individual
313
+ * schemas keyed by type id, and `$defs` contains all shared type definitions
314
+ * deduplicated across schemas.
315
+ *
316
+ * @param types - Array of annotated types, each with an `id`.
317
+ * @returns An object with `schemas` (keyed by id) and shared `$defs`.
318
+ */
319
+ declare function mergeJsonSchemas(types: TAtscriptAnnotatedType[]): {
320
+ schemas: Record<string, TJsonSchema>;
321
+ $defs: Record<string, TJsonSchema>;
322
+ };
302
323
 
303
324
  /**
304
325
  * Type-safe dispatch over `TAtscriptAnnotatedType` by its `type.kind`.
@@ -417,6 +438,7 @@ interface TSerializedAnnotatedTypeInner {
417
438
  type: TSerializedTypeDef;
418
439
  metadata: Record<string, unknown>;
419
440
  optional?: boolean;
441
+ id?: string;
420
442
  }
421
443
  interface TSerializedTypeFinal {
422
444
  kind: '';
@@ -511,5 +533,5 @@ declare function serializeAnnotatedType(type: TAtscriptAnnotatedType, options?:
511
533
  */
512
534
  declare function deserializeAnnotatedType(data: TSerializedAnnotatedType): TAtscriptAnnotatedType;
513
535
 
514
- export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, serializeAnnotatedType, throwFeatureDisabled };
515
- export type { InferDataType, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
536
+ export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
537
+ export type { InferDataType, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TJsonSchema, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };