@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/cli.cjs CHANGED
@@ -971,6 +971,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
971
971
  __is_atscript_annotated_type: true,
972
972
  type: newBase.type,
973
973
  metadata,
974
+ id: newBase.id,
974
975
  validator(opts) {
975
976
  return new Validator(this, opts);
976
977
  }
@@ -981,6 +982,10 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
981
982
  annotate(key, value, asArray) {
982
983
  annotate(this.$metadata, key, value, asArray);
983
984
  return this;
985
+ },
986
+ id(value) {
987
+ this.$type.id = value;
988
+ return this;
984
989
  }
985
990
  };
986
991
  return handle;
@@ -991,40 +996,112 @@ function isPhantomType(def) {
991
996
 
992
997
  //#endregion
993
998
  //#region packages/typescript/src/json-schema.ts
999
+ /**
1000
+ * Detects a discriminator property across union items.
1001
+ *
1002
+ * Scans all items for object-typed members that share a common property
1003
+ * with distinct const/literal values. If exactly one such property exists,
1004
+ * it is returned as the discriminator.
1005
+ */ function detectDiscriminator(items) {
1006
+ if (items.length < 2) return null;
1007
+ for (const item of items) if (item.type.kind !== "object") return null;
1008
+ const firstObj = items[0].type;
1009
+ const candidates = [];
1010
+ for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
1011
+ const validCandidates = [];
1012
+ for (const candidate of candidates) {
1013
+ const values = new Set();
1014
+ const mapping = {};
1015
+ let valid = true;
1016
+ for (let i = 0; i < items.length; i++) {
1017
+ const obj = items[i].type;
1018
+ const prop = obj.props.get(candidate);
1019
+ if (!prop || prop.type.kind !== "" || prop.type.value === undefined) {
1020
+ valid = false;
1021
+ break;
1022
+ }
1023
+ const val = prop.type.value;
1024
+ if (values.has(val)) {
1025
+ valid = false;
1026
+ break;
1027
+ }
1028
+ values.add(val);
1029
+ mapping[String(val)] = `#/oneOf/${i}`;
1030
+ }
1031
+ if (valid) validCandidates.push({
1032
+ propertyName: candidate,
1033
+ mapping
1034
+ });
1035
+ }
1036
+ if (validCandidates.length === 1) return validCandidates[0];
1037
+ return null;
1038
+ }
994
1039
  function buildJsonSchema(type) {
1040
+ const defs = {};
1041
+ let isRoot = true;
1042
+ const buildObject = (d) => {
1043
+ const properties = {};
1044
+ const required = [];
1045
+ for (const [key, val] of d.type.props.entries()) {
1046
+ if (isPhantomType(val)) continue;
1047
+ properties[key] = build$1(val);
1048
+ if (!val.optional) required.push(key);
1049
+ }
1050
+ const schema$1 = {
1051
+ type: "object",
1052
+ properties
1053
+ };
1054
+ if (required.length > 0) schema$1.required = required;
1055
+ return schema$1;
1056
+ };
995
1057
  const build$1 = (def) => {
1058
+ if (def.id && def.type.kind === "object" && !isRoot) {
1059
+ const name = def.id;
1060
+ if (!defs[name]) {
1061
+ defs[name] = {};
1062
+ defs[name] = buildObject(def);
1063
+ }
1064
+ return { $ref: `#/$defs/${name}` };
1065
+ }
1066
+ isRoot = false;
996
1067
  const meta = def.metadata;
997
1068
  return forAnnotatedType(def, {
998
1069
  phantom() {
999
1070
  return {};
1000
1071
  },
1001
1072
  object(d) {
1002
- const properties = {};
1003
- const required = [];
1004
- for (const [key, val] of d.type.props.entries()) {
1005
- if (isPhantomType(val)) continue;
1006
- properties[key] = build$1(val);
1007
- if (!val.optional) required.push(key);
1008
- }
1009
- const schema = {
1010
- type: "object",
1011
- properties
1012
- };
1013
- if (required.length > 0) schema.required = required;
1014
- return schema;
1073
+ return buildObject(d);
1015
1074
  },
1016
1075
  array(d) {
1017
- const schema = {
1076
+ const schema$1 = {
1018
1077
  type: "array",
1019
1078
  items: build$1(d.type.of)
1020
1079
  };
1021
1080
  const minLength = meta.get("expect.minLength");
1022
- if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
1081
+ if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
1023
1082
  const maxLength = meta.get("expect.maxLength");
1024
- if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1025
- return schema;
1083
+ if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1084
+ return schema$1;
1026
1085
  },
1027
1086
  union(d) {
1087
+ const disc = detectDiscriminator(d.type.items);
1088
+ if (disc) {
1089
+ const oneOf = d.type.items.map(build$1);
1090
+ const mapping = {};
1091
+ for (const [val, origPath] of Object.entries(disc.mapping)) {
1092
+ const idx = Number.parseInt(origPath.split("/").pop());
1093
+ const item = d.type.items[idx];
1094
+ if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
1095
+ else mapping[val] = origPath;
1096
+ }
1097
+ return {
1098
+ oneOf,
1099
+ discriminator: {
1100
+ propertyName: disc.propertyName,
1101
+ mapping
1102
+ }
1103
+ };
1104
+ }
1028
1105
  return { anyOf: d.type.items.map(build$1) };
1029
1106
  },
1030
1107
  intersection(d) {
@@ -1038,33 +1115,38 @@ function buildJsonSchema(type) {
1038
1115
  };
1039
1116
  },
1040
1117
  final(d) {
1041
- const schema = {};
1042
- if (d.type.value !== undefined) schema.const = d.type.value;
1118
+ const schema$1 = {};
1119
+ if (d.type.value !== undefined) schema$1.const = d.type.value;
1043
1120
  if (d.type.designType && d.type.designType !== "any") {
1044
- schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1045
- if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
1121
+ schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1122
+ if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
1046
1123
  }
1047
- if (schema.type === "string") {
1048
- if (meta.get("meta.required")) schema.minLength = 1;
1124
+ if (schema$1.type === "string") {
1125
+ if (meta.get("meta.required")) schema$1.minLength = 1;
1049
1126
  const minLength = meta.get("expect.minLength");
1050
- if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
1127
+ if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
1051
1128
  const maxLength = meta.get("expect.maxLength");
1052
- if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1129
+ if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1053
1130
  const patterns = meta.get("expect.pattern");
1054
- if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
1055
- else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1131
+ if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
1132
+ else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1056
1133
  }
1057
- if (schema.type === "number" || schema.type === "integer") {
1134
+ if (schema$1.type === "number" || schema$1.type === "integer") {
1058
1135
  const min = meta.get("expect.min");
1059
- if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
1136
+ if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
1060
1137
  const max = meta.get("expect.max");
1061
- if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
1138
+ if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
1062
1139
  }
1063
- return schema;
1140
+ return schema$1;
1064
1141
  }
1065
1142
  });
1066
1143
  };
1067
- return build$1(type);
1144
+ const schema = build$1(type);
1145
+ if (Object.keys(defs).length > 0) return {
1146
+ ...schema,
1147
+ $defs: defs
1148
+ };
1149
+ return schema;
1068
1150
  }
1069
1151
 
1070
1152
  //#endregion
@@ -1085,11 +1167,23 @@ var JsRenderer = class extends BaseRenderer {
1085
1167
  this.writeln("/* eslint-disable */");
1086
1168
  this.writeln("/* oxlint-disable */");
1087
1169
  const imports = ["defineAnnotatedType as $", "annotate as $a"];
1170
+ const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
1171
+ if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
1088
1172
  const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
1089
1173
  if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
1090
1174
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
1091
1175
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
1092
1176
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
1177
+ const nameCounts = new Map();
1178
+ const nodesByName = new Map();
1179
+ for (const node of this.doc.nodes) if (node.__typeId != null && node.id) {
1180
+ const name = node.id;
1181
+ nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
1182
+ if (!nodesByName.has(name)) nodesByName.set(name, []);
1183
+ nodesByName.get(name).push(node);
1184
+ }
1185
+ for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
1186
+ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
1093
1187
  }
1094
1188
  buildAdHocMap(annotateNodes) {
1095
1189
  const map = new Map();
@@ -1103,6 +1197,15 @@ var JsRenderer = class extends BaseRenderer {
1103
1197
  }
1104
1198
  return map.size > 0 ? map : null;
1105
1199
  }
1200
+ /**
1201
+ * Checks if any ad-hoc annotation path extends beyond the current _propPath,
1202
+ * meaning annotations target properties inside a referenced type.
1203
+ */ hasAdHocAnnotationsThroughRef() {
1204
+ if (!this._adHocAnnotations || this._propPath.length === 0) return false;
1205
+ const prefix = this._propPath.join(".") + ".";
1206
+ for (const key of this._adHocAnnotations.keys()) if (key.startsWith(prefix)) return true;
1207
+ return false;
1208
+ }
1106
1209
  post() {
1107
1210
  for (const node of this.postAnnotate) if (node.entity === "annotate") {
1108
1211
  const annotateNode = node;
@@ -1133,6 +1236,8 @@ else {
1133
1236
  this.writeln("static __is_atscript_annotated_type = true");
1134
1237
  this.writeln("static type = {}");
1135
1238
  this.writeln("static metadata = new Map()");
1239
+ const typeId = this.typeIds.get(node);
1240
+ if (typeId) this.writeln(`static id = "${typeId}"`);
1136
1241
  this.renderJsonSchemaMethod(node);
1137
1242
  this.renderExampleDataMethod(node);
1138
1243
  }
@@ -1211,6 +1316,8 @@ else {
1211
1316
  case "type": {
1212
1317
  const def = node.getDefinition();
1213
1318
  const handle = this.toAnnotatedHandle(def, true);
1319
+ const typeId = this.typeIds.get(node) ?? (node.__typeId != null ? node.id : undefined);
1320
+ if (typeId) handle.id(typeId);
1214
1321
  return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1215
1322
  }
1216
1323
  case "prop": {
@@ -1337,6 +1444,14 @@ else handle.prop(prop.id, propHandle.$type);
1337
1444
  return this;
1338
1445
  }
1339
1446
  }
1447
+ if (this._adHocAnnotations && this.hasAdHocAnnotationsThroughRef()) {
1448
+ let resolved = decl ? this.doc.mergeIntersection(decl) : undefined;
1449
+ if (resolved && (0, __atscript_core.isInterface)(resolved)) resolved = resolved.getDefinition() || resolved;
1450
+ if (resolved) {
1451
+ this.annotateType(resolved, name);
1452
+ return this;
1453
+ }
1454
+ }
1340
1455
  const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
1341
1456
  this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${ref.id}${chain})`);
1342
1457
  if (!ref.hasChain) {
@@ -1570,11 +1685,29 @@ else targetValue = "true";
1570
1685
  const targetName = node.targetName;
1571
1686
  const targetDef = this.resolveTargetDef(targetName);
1572
1687
  this.writeln("// Ad-hoc annotations for ", targetName);
1688
+ const allClones = [];
1689
+ const entryAccessors = [];
1573
1690
  for (const entry of node.entries) {
1574
1691
  const anns = entry.annotations;
1575
1692
  if (!anns || anns.length === 0) continue;
1576
1693
  const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
1577
- const accessors = this.buildMutatingAccessors(targetName, targetDef, parts);
1694
+ const { accessors, clones } = this.buildMutatingAccessors(targetName, targetDef, parts);
1695
+ allClones.push(...clones);
1696
+ entryAccessors.push({
1697
+ entry,
1698
+ accessors
1699
+ });
1700
+ }
1701
+ const cloneKeys = new Set();
1702
+ for (const clone of allClones) {
1703
+ const key = `${clone.parentPath}|${clone.propName}`;
1704
+ if (!cloneKeys.has(key)) {
1705
+ cloneKeys.add(key);
1706
+ this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
1707
+ }
1708
+ }
1709
+ for (const { entry, accessors } of entryAccessors) {
1710
+ const anns = entry.annotations;
1578
1711
  for (const accessor of accessors) {
1579
1712
  const cleared = new Set();
1580
1713
  for (const an of anns) {
@@ -1624,15 +1757,21 @@ else targetValue = "true";
1624
1757
  prefix: `${targetName}.type`,
1625
1758
  def: targetDef
1626
1759
  }];
1760
+ const clones = [];
1627
1761
  for (let i = 0; i < parts.length; i++) {
1628
1762
  const nextAccessors = [];
1629
1763
  for (const { prefix, def } of accessors) {
1630
1764
  const results = this.buildPropPaths(def, parts[i]);
1631
- if (results.length > 0) for (const result of results) if (i < parts.length - 1) nextAccessors.push({
1632
- prefix: `${prefix}${result.path}?.type`,
1633
- def: result.propDef
1634
- });
1635
- else nextAccessors.push({
1765
+ if (results.length > 0) for (const result of results) if (i < parts.length - 1) {
1766
+ if (result.propDef && (0, __atscript_core.isRef)(result.propDef)) clones.push({
1767
+ parentPath: prefix,
1768
+ propName: parts[i]
1769
+ });
1770
+ nextAccessors.push({
1771
+ prefix: `${prefix}${result.path}?.type`,
1772
+ def: result.propDef
1773
+ });
1774
+ } else nextAccessors.push({
1636
1775
  prefix: `${prefix}${result.path}?`,
1637
1776
  def: result.propDef
1638
1777
  });
@@ -1646,7 +1785,10 @@ else {
1646
1785
  }
1647
1786
  accessors = nextAccessors;
1648
1787
  }
1649
- return accessors.map((a) => a.prefix);
1788
+ return {
1789
+ accessors: accessors.map((a) => a.prefix),
1790
+ clones
1791
+ };
1650
1792
  }
1651
1793
  /**
1652
1794
  * Finds a property in a type tree at compile time, returning all
@@ -1682,7 +1824,7 @@ else {
1682
1824
  return [];
1683
1825
  }
1684
1826
  constructor(doc, opts) {
1685
- super(doc), _define_property$1(this, "opts", void 0), _define_property$1(this, "postAnnotate", void 0), _define_property$1(this, "_adHocAnnotations", void 0), _define_property$1(this, "_propPath", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [];
1827
+ super(doc), _define_property$1(this, "opts", void 0), _define_property$1(this, "postAnnotate", void 0), _define_property$1(this, "_adHocAnnotations", void 0), _define_property$1(this, "_propPath", void 0), _define_property$1(this, "typeIds", void 0), this.opts = opts, this.postAnnotate = [], this._adHocAnnotations = null, this._propPath = [], this.typeIds = new Map();
1686
1828
  }
1687
1829
  };
1688
1830