@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 +182 -40
- package/dist/index.cjs +182 -40
- package/dist/index.mjs +182 -40
- package/dist/utils.cjs +205 -33
- package/dist/utils.d.ts +24 -2
- package/dist/utils.mjs +204 -34
- package/package.json +2 -2
- package/skills/atscript-typescript/SKILL.md +1 -1
- package/skills/atscript-typescript/codegen.md +1 -0
- package/skills/atscript-typescript/runtime.md +3 -1
- package/skills/atscript-typescript/utilities.md +48 -6
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
|
-
|
|
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
|
-
|
|
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)
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
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
|
|
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
|
|