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