@atscript/typescript 0.1.25 → 0.1.27
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/README.md +50 -0
- package/dist/cli.cjs +140 -46
- package/dist/index.cjs +143 -51
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +141 -51
- package/dist/utils.cjs +163 -39
- package/dist/utils.d.ts +24 -2
- package/dist/utils.mjs +162 -40
- package/package.json +5 -5
- package/scripts/setup-skills.js +23 -13
- package/skills/atscript-typescript/SKILL.md +22 -14
- package/skills/atscript-typescript/annotations.md +51 -50
- package/skills/atscript-typescript/codegen.md +15 -10
- package/skills/atscript-typescript/core.md +23 -21
- package/skills/atscript-typescript/runtime.md +66 -52
- package/skills/atscript-typescript/syntax.md +31 -31
- package/skills/atscript-typescript/utilities.md +137 -72
- package/skills/atscript-typescript/validation.md +39 -39
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @atscript/typescript
|
|
2
|
+
|
|
3
|
+
TypeScript language extension for [Atscript](https://atscript.moost.org). Compiles `.as` files into `.d.ts` type declarations and `.js` runtime modules with full metadata, validation, and JSON Schema support.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @atscript/typescript @atscript/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For build-tool integration (Vite, Webpack, Rollup, esbuild, Rspack), also add:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add unplugin-atscript
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## AI Agent Skills
|
|
18
|
+
|
|
19
|
+
This package ships with structured skill files for AI coding agents (Claude Code, Cursor, Windsurf, Codex, etc.).
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Project-local (recommended — version-locked, commits with your repo)
|
|
23
|
+
npx atscript-typescript-skill
|
|
24
|
+
|
|
25
|
+
# Global (available across all your projects)
|
|
26
|
+
npx atscript-typescript-skill --global
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To keep skills automatically up-to-date, add a postinstall script to your `package.json`:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"scripts": {
|
|
34
|
+
"postinstall": "atscript-typescript-skill --postinstall"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
- **Type declarations** — `.d.ts` files from `.as` interfaces and types
|
|
42
|
+
- **Runtime metadata** — `.js` files with full metadata for validators and serializers
|
|
43
|
+
- **JSON Schema** — Build, parse, and merge JSON schemas from annotated types
|
|
44
|
+
- **Validation** — Validate data against types with plugin support
|
|
45
|
+
- **Serialization** — JSON-safe round-trip serialization/deserialization
|
|
46
|
+
- **CLI** — `asc` command for compiling `.as` files
|
|
47
|
+
|
|
48
|
+
## Documentation
|
|
49
|
+
|
|
50
|
+
Full documentation: [atscript.moost.org](https://atscript.moost.org)
|
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;
|
|
@@ -1032,47 +1037,71 @@ function isPhantomType(def) {
|
|
|
1032
1037
|
return null;
|
|
1033
1038
|
}
|
|
1034
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
|
+
};
|
|
1035
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;
|
|
1036
1067
|
const meta = def.metadata;
|
|
1037
1068
|
return forAnnotatedType(def, {
|
|
1038
1069
|
phantom() {
|
|
1039
1070
|
return {};
|
|
1040
1071
|
},
|
|
1041
1072
|
object(d) {
|
|
1042
|
-
|
|
1043
|
-
const required = [];
|
|
1044
|
-
for (const [key, val] of d.type.props.entries()) {
|
|
1045
|
-
if (isPhantomType(val)) continue;
|
|
1046
|
-
properties[key] = build$1(val);
|
|
1047
|
-
if (!val.optional) required.push(key);
|
|
1048
|
-
}
|
|
1049
|
-
const schema = {
|
|
1050
|
-
type: "object",
|
|
1051
|
-
properties
|
|
1052
|
-
};
|
|
1053
|
-
if (required.length > 0) schema.required = required;
|
|
1054
|
-
return schema;
|
|
1073
|
+
return buildObject(d);
|
|
1055
1074
|
},
|
|
1056
1075
|
array(d) {
|
|
1057
|
-
const schema = {
|
|
1076
|
+
const schema$1 = {
|
|
1058
1077
|
type: "array",
|
|
1059
1078
|
items: build$1(d.type.of)
|
|
1060
1079
|
};
|
|
1061
1080
|
const minLength = meta.get("expect.minLength");
|
|
1062
|
-
if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
|
|
1081
|
+
if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
|
|
1063
1082
|
const maxLength = meta.get("expect.maxLength");
|
|
1064
|
-
if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1065
|
-
return schema;
|
|
1083
|
+
if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1084
|
+
return schema$1;
|
|
1066
1085
|
},
|
|
1067
1086
|
union(d) {
|
|
1068
1087
|
const disc = detectDiscriminator(d.type.items);
|
|
1069
|
-
if (disc)
|
|
1070
|
-
oneOf
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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(), 10);
|
|
1093
|
+
const item = d.type.items[idx];
|
|
1094
|
+
if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
|
|
1095
|
+
else mapping[val] = origPath;
|
|
1074
1096
|
}
|
|
1075
|
-
|
|
1097
|
+
return {
|
|
1098
|
+
oneOf,
|
|
1099
|
+
discriminator: {
|
|
1100
|
+
propertyName: disc.propertyName,
|
|
1101
|
+
mapping
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1076
1105
|
return { anyOf: d.type.items.map(build$1) };
|
|
1077
1106
|
},
|
|
1078
1107
|
intersection(d) {
|
|
@@ -1086,33 +1115,38 @@ function buildJsonSchema(type) {
|
|
|
1086
1115
|
};
|
|
1087
1116
|
},
|
|
1088
1117
|
final(d) {
|
|
1089
|
-
const schema = {};
|
|
1090
|
-
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;
|
|
1091
1120
|
if (d.type.designType && d.type.designType !== "any") {
|
|
1092
|
-
schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
|
|
1093
|
-
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";
|
|
1094
1123
|
}
|
|
1095
|
-
if (schema.type === "string") {
|
|
1096
|
-
if (meta.get("meta.required")) schema.minLength = 1;
|
|
1124
|
+
if (schema$1.type === "string") {
|
|
1125
|
+
if (meta.get("meta.required")) schema$1.minLength = 1;
|
|
1097
1126
|
const minLength = meta.get("expect.minLength");
|
|
1098
|
-
if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
|
|
1127
|
+
if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
|
|
1099
1128
|
const maxLength = meta.get("expect.maxLength");
|
|
1100
|
-
if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1129
|
+
if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1101
1130
|
const patterns = meta.get("expect.pattern");
|
|
1102
|
-
if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
|
|
1103
|
-
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 })));
|
|
1104
1133
|
}
|
|
1105
|
-
if (schema.type === "number" || schema.type === "integer") {
|
|
1134
|
+
if (schema$1.type === "number" || schema$1.type === "integer") {
|
|
1106
1135
|
const min = meta.get("expect.min");
|
|
1107
|
-
if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
|
|
1136
|
+
if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
|
|
1108
1137
|
const max = meta.get("expect.max");
|
|
1109
|
-
if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
|
|
1138
|
+
if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
|
|
1110
1139
|
}
|
|
1111
|
-
return schema;
|
|
1140
|
+
return schema$1;
|
|
1112
1141
|
}
|
|
1113
1142
|
});
|
|
1114
1143
|
};
|
|
1115
|
-
|
|
1144
|
+
const schema = build$1(type);
|
|
1145
|
+
if (Object.keys(defs).length > 0) return {
|
|
1146
|
+
...schema,
|
|
1147
|
+
$defs: defs
|
|
1148
|
+
};
|
|
1149
|
+
return schema;
|
|
1116
1150
|
}
|
|
1117
1151
|
|
|
1118
1152
|
//#endregion
|
|
@@ -1133,11 +1167,23 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
1133
1167
|
this.writeln("/* eslint-disable */");
|
|
1134
1168
|
this.writeln("/* oxlint-disable */");
|
|
1135
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");
|
|
1136
1172
|
const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
|
|
1137
1173
|
if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
|
|
1138
1174
|
if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
|
|
1139
1175
|
if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
|
|
1140
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.__typeId !== undefined && 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}`);
|
|
1141
1187
|
}
|
|
1142
1188
|
buildAdHocMap(annotateNodes) {
|
|
1143
1189
|
const map = new Map();
|
|
@@ -1151,6 +1197,15 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
1151
1197
|
}
|
|
1152
1198
|
return map.size > 0 ? map : null;
|
|
1153
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
|
+
}
|
|
1154
1209
|
post() {
|
|
1155
1210
|
for (const node of this.postAnnotate) if (node.entity === "annotate") {
|
|
1156
1211
|
const annotateNode = node;
|
|
@@ -1181,6 +1236,8 @@ else {
|
|
|
1181
1236
|
this.writeln("static __is_atscript_annotated_type = true");
|
|
1182
1237
|
this.writeln("static type = {}");
|
|
1183
1238
|
this.writeln("static metadata = new Map()");
|
|
1239
|
+
const typeId = this.typeIds.get(node);
|
|
1240
|
+
if (typeId) this.writeln(`static id = "${typeId}"`);
|
|
1184
1241
|
this.renderJsonSchemaMethod(node);
|
|
1185
1242
|
this.renderExampleDataMethod(node);
|
|
1186
1243
|
}
|
|
@@ -1259,6 +1316,8 @@ else {
|
|
|
1259
1316
|
case "type": {
|
|
1260
1317
|
const def = node.getDefinition();
|
|
1261
1318
|
const handle = this.toAnnotatedHandle(def, true);
|
|
1319
|
+
const typeId = this.typeIds.get(node) ?? (node.__typeId !== null && node.__typeId !== undefined ? node.id : undefined);
|
|
1320
|
+
if (typeId) handle.id(typeId);
|
|
1262
1321
|
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1263
1322
|
}
|
|
1264
1323
|
case "prop": {
|
|
@@ -1385,6 +1444,14 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1385
1444
|
return this;
|
|
1386
1445
|
}
|
|
1387
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
|
+
}
|
|
1388
1455
|
const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
|
|
1389
1456
|
this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${ref.id}${chain})`);
|
|
1390
1457
|
if (!ref.hasChain) {
|
|
@@ -1618,11 +1685,29 @@ else targetValue = "true";
|
|
|
1618
1685
|
const targetName = node.targetName;
|
|
1619
1686
|
const targetDef = this.resolveTargetDef(targetName);
|
|
1620
1687
|
this.writeln("// Ad-hoc annotations for ", targetName);
|
|
1688
|
+
const allClones = [];
|
|
1689
|
+
const entryAccessors = [];
|
|
1621
1690
|
for (const entry of node.entries) {
|
|
1622
1691
|
const anns = entry.annotations;
|
|
1623
1692
|
if (!anns || anns.length === 0) continue;
|
|
1624
1693
|
const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
|
|
1625
|
-
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;
|
|
1626
1711
|
for (const accessor of accessors) {
|
|
1627
1712
|
const cleared = new Set();
|
|
1628
1713
|
for (const an of anns) {
|
|
@@ -1672,15 +1757,21 @@ else targetValue = "true";
|
|
|
1672
1757
|
prefix: `${targetName}.type`,
|
|
1673
1758
|
def: targetDef
|
|
1674
1759
|
}];
|
|
1760
|
+
const clones = [];
|
|
1675
1761
|
for (let i = 0; i < parts.length; i++) {
|
|
1676
1762
|
const nextAccessors = [];
|
|
1677
1763
|
for (const { prefix, def } of accessors) {
|
|
1678
1764
|
const results = this.buildPropPaths(def, parts[i]);
|
|
1679
|
-
if (results.length > 0) for (const result of results) if (i < parts.length - 1)
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
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({
|
|
1684
1775
|
prefix: `${prefix}${result.path}?`,
|
|
1685
1776
|
def: result.propDef
|
|
1686
1777
|
});
|
|
@@ -1694,7 +1785,10 @@ else {
|
|
|
1694
1785
|
}
|
|
1695
1786
|
accessors = nextAccessors;
|
|
1696
1787
|
}
|
|
1697
|
-
return
|
|
1788
|
+
return {
|
|
1789
|
+
accessors: accessors.map((a) => a.prefix),
|
|
1790
|
+
clones
|
|
1791
|
+
};
|
|
1698
1792
|
}
|
|
1699
1793
|
/**
|
|
1700
1794
|
* Finds a property in a type tree at compile time, returning all
|
|
@@ -1730,7 +1824,7 @@ else {
|
|
|
1730
1824
|
return [];
|
|
1731
1825
|
}
|
|
1732
1826
|
constructor(doc, opts) {
|
|
1733
|
-
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();
|
|
1734
1828
|
}
|
|
1735
1829
|
};
|
|
1736
1830
|
|