@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/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
|
-
|
|
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
|
-
|
|
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)
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
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
|
|
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
|
|