@atscript/typescript 0.1.25 → 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 +140 -46
- package/dist/index.cjs +140 -46
- package/dist/index.mjs +140 -46
- package/dist/utils.cjs +163 -39
- package/dist/utils.d.ts +24 -2
- package/dist/utils.mjs +162 -40
- 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 +44 -6
package/dist/index.mjs
CHANGED
|
@@ -944,6 +944,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
|
|
|
944
944
|
__is_atscript_annotated_type: true,
|
|
945
945
|
type: newBase.type,
|
|
946
946
|
metadata,
|
|
947
|
+
id: newBase.id,
|
|
947
948
|
validator(opts) {
|
|
948
949
|
return new Validator(this, opts);
|
|
949
950
|
}
|
|
@@ -954,6 +955,10 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
|
|
|
954
955
|
annotate(key, value, asArray) {
|
|
955
956
|
annotate(this.$metadata, key, value, asArray);
|
|
956
957
|
return this;
|
|
958
|
+
},
|
|
959
|
+
id(value) {
|
|
960
|
+
this.$type.id = value;
|
|
961
|
+
return this;
|
|
957
962
|
}
|
|
958
963
|
};
|
|
959
964
|
return handle;
|
|
@@ -1005,47 +1010,71 @@ function isPhantomType(def) {
|
|
|
1005
1010
|
return null;
|
|
1006
1011
|
}
|
|
1007
1012
|
function buildJsonSchema(type) {
|
|
1013
|
+
const defs = {};
|
|
1014
|
+
let isRoot = true;
|
|
1015
|
+
const buildObject = (d) => {
|
|
1016
|
+
const properties = {};
|
|
1017
|
+
const required = [];
|
|
1018
|
+
for (const [key, val] of d.type.props.entries()) {
|
|
1019
|
+
if (isPhantomType(val)) continue;
|
|
1020
|
+
properties[key] = build(val);
|
|
1021
|
+
if (!val.optional) required.push(key);
|
|
1022
|
+
}
|
|
1023
|
+
const schema$1 = {
|
|
1024
|
+
type: "object",
|
|
1025
|
+
properties
|
|
1026
|
+
};
|
|
1027
|
+
if (required.length > 0) schema$1.required = required;
|
|
1028
|
+
return schema$1;
|
|
1029
|
+
};
|
|
1008
1030
|
const build = (def) => {
|
|
1031
|
+
if (def.id && def.type.kind === "object" && !isRoot) {
|
|
1032
|
+
const name = def.id;
|
|
1033
|
+
if (!defs[name]) {
|
|
1034
|
+
defs[name] = {};
|
|
1035
|
+
defs[name] = buildObject(def);
|
|
1036
|
+
}
|
|
1037
|
+
return { $ref: `#/$defs/${name}` };
|
|
1038
|
+
}
|
|
1039
|
+
isRoot = false;
|
|
1009
1040
|
const meta = def.metadata;
|
|
1010
1041
|
return forAnnotatedType(def, {
|
|
1011
1042
|
phantom() {
|
|
1012
1043
|
return {};
|
|
1013
1044
|
},
|
|
1014
1045
|
object(d) {
|
|
1015
|
-
|
|
1016
|
-
const required = [];
|
|
1017
|
-
for (const [key, val] of d.type.props.entries()) {
|
|
1018
|
-
if (isPhantomType(val)) continue;
|
|
1019
|
-
properties[key] = build(val);
|
|
1020
|
-
if (!val.optional) required.push(key);
|
|
1021
|
-
}
|
|
1022
|
-
const schema = {
|
|
1023
|
-
type: "object",
|
|
1024
|
-
properties
|
|
1025
|
-
};
|
|
1026
|
-
if (required.length > 0) schema.required = required;
|
|
1027
|
-
return schema;
|
|
1046
|
+
return buildObject(d);
|
|
1028
1047
|
},
|
|
1029
1048
|
array(d) {
|
|
1030
|
-
const schema = {
|
|
1049
|
+
const schema$1 = {
|
|
1031
1050
|
type: "array",
|
|
1032
1051
|
items: build(d.type.of)
|
|
1033
1052
|
};
|
|
1034
1053
|
const minLength = meta.get("expect.minLength");
|
|
1035
|
-
if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
|
|
1054
|
+
if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
|
|
1036
1055
|
const maxLength = meta.get("expect.maxLength");
|
|
1037
|
-
if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1038
|
-
return schema;
|
|
1056
|
+
if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1057
|
+
return schema$1;
|
|
1039
1058
|
},
|
|
1040
1059
|
union(d) {
|
|
1041
1060
|
const disc = detectDiscriminator(d.type.items);
|
|
1042
|
-
if (disc)
|
|
1043
|
-
oneOf
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1061
|
+
if (disc) {
|
|
1062
|
+
const oneOf = d.type.items.map(build);
|
|
1063
|
+
const mapping = {};
|
|
1064
|
+
for (const [val, origPath] of Object.entries(disc.mapping)) {
|
|
1065
|
+
const idx = Number.parseInt(origPath.split("/").pop());
|
|
1066
|
+
const item = d.type.items[idx];
|
|
1067
|
+
if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
|
|
1068
|
+
else mapping[val] = origPath;
|
|
1047
1069
|
}
|
|
1048
|
-
|
|
1070
|
+
return {
|
|
1071
|
+
oneOf,
|
|
1072
|
+
discriminator: {
|
|
1073
|
+
propertyName: disc.propertyName,
|
|
1074
|
+
mapping
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1049
1078
|
return { anyOf: d.type.items.map(build) };
|
|
1050
1079
|
},
|
|
1051
1080
|
intersection(d) {
|
|
@@ -1059,33 +1088,38 @@ function buildJsonSchema(type) {
|
|
|
1059
1088
|
};
|
|
1060
1089
|
},
|
|
1061
1090
|
final(d) {
|
|
1062
|
-
const schema = {};
|
|
1063
|
-
if (d.type.value !== undefined) schema.const = d.type.value;
|
|
1091
|
+
const schema$1 = {};
|
|
1092
|
+
if (d.type.value !== undefined) schema$1.const = d.type.value;
|
|
1064
1093
|
if (d.type.designType && d.type.designType !== "any") {
|
|
1065
|
-
schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
|
|
1066
|
-
if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
|
|
1094
|
+
schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
|
|
1095
|
+
if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
|
|
1067
1096
|
}
|
|
1068
|
-
if (schema.type === "string") {
|
|
1069
|
-
if (meta.get("meta.required")) schema.minLength = 1;
|
|
1097
|
+
if (schema$1.type === "string") {
|
|
1098
|
+
if (meta.get("meta.required")) schema$1.minLength = 1;
|
|
1070
1099
|
const minLength = meta.get("expect.minLength");
|
|
1071
|
-
if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
|
|
1100
|
+
if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
|
|
1072
1101
|
const maxLength = meta.get("expect.maxLength");
|
|
1073
|
-
if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1102
|
+
if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
1074
1103
|
const patterns = meta.get("expect.pattern");
|
|
1075
|
-
if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
|
|
1076
|
-
else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
|
|
1104
|
+
if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
|
|
1105
|
+
else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
|
|
1077
1106
|
}
|
|
1078
|
-
if (schema.type === "number" || schema.type === "integer") {
|
|
1107
|
+
if (schema$1.type === "number" || schema$1.type === "integer") {
|
|
1079
1108
|
const min = meta.get("expect.min");
|
|
1080
|
-
if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
|
|
1109
|
+
if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
|
|
1081
1110
|
const max = meta.get("expect.max");
|
|
1082
|
-
if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
|
|
1111
|
+
if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
|
|
1083
1112
|
}
|
|
1084
|
-
return schema;
|
|
1113
|
+
return schema$1;
|
|
1085
1114
|
}
|
|
1086
1115
|
});
|
|
1087
1116
|
};
|
|
1088
|
-
|
|
1117
|
+
const schema = build(type);
|
|
1118
|
+
if (Object.keys(defs).length > 0) return {
|
|
1119
|
+
...schema,
|
|
1120
|
+
$defs: defs
|
|
1121
|
+
};
|
|
1122
|
+
return schema;
|
|
1089
1123
|
}
|
|
1090
1124
|
|
|
1091
1125
|
//#endregion
|
|
@@ -1106,11 +1140,23 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
1106
1140
|
this.writeln("/* eslint-disable */");
|
|
1107
1141
|
this.writeln("/* oxlint-disable */");
|
|
1108
1142
|
const imports = ["defineAnnotatedType as $", "annotate as $a"];
|
|
1143
|
+
const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
|
|
1144
|
+
if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
|
|
1109
1145
|
const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
|
|
1110
1146
|
if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
|
|
1111
1147
|
if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
|
|
1112
1148
|
if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
|
|
1113
1149
|
this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
|
|
1150
|
+
const nameCounts = new Map();
|
|
1151
|
+
const nodesByName = new Map();
|
|
1152
|
+
for (const node of this.doc.nodes) if (node.__typeId != null && node.id) {
|
|
1153
|
+
const name = node.id;
|
|
1154
|
+
nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
|
|
1155
|
+
if (!nodesByName.has(name)) nodesByName.set(name, []);
|
|
1156
|
+
nodesByName.get(name).push(node);
|
|
1157
|
+
}
|
|
1158
|
+
for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
|
|
1159
|
+
else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
|
|
1114
1160
|
}
|
|
1115
1161
|
buildAdHocMap(annotateNodes) {
|
|
1116
1162
|
const map = new Map();
|
|
@@ -1124,6 +1170,15 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
1124
1170
|
}
|
|
1125
1171
|
return map.size > 0 ? map : null;
|
|
1126
1172
|
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Checks if any ad-hoc annotation path extends beyond the current _propPath,
|
|
1175
|
+
* meaning annotations target properties inside a referenced type.
|
|
1176
|
+
*/ hasAdHocAnnotationsThroughRef() {
|
|
1177
|
+
if (!this._adHocAnnotations || this._propPath.length === 0) return false;
|
|
1178
|
+
const prefix = this._propPath.join(".") + ".";
|
|
1179
|
+
for (const key of this._adHocAnnotations.keys()) if (key.startsWith(prefix)) return true;
|
|
1180
|
+
return false;
|
|
1181
|
+
}
|
|
1127
1182
|
post() {
|
|
1128
1183
|
for (const node of this.postAnnotate) if (node.entity === "annotate") {
|
|
1129
1184
|
const annotateNode = node;
|
|
@@ -1154,6 +1209,8 @@ else {
|
|
|
1154
1209
|
this.writeln("static __is_atscript_annotated_type = true");
|
|
1155
1210
|
this.writeln("static type = {}");
|
|
1156
1211
|
this.writeln("static metadata = new Map()");
|
|
1212
|
+
const typeId = this.typeIds.get(node);
|
|
1213
|
+
if (typeId) this.writeln(`static id = "${typeId}"`);
|
|
1157
1214
|
this.renderJsonSchemaMethod(node);
|
|
1158
1215
|
this.renderExampleDataMethod(node);
|
|
1159
1216
|
}
|
|
@@ -1232,6 +1289,8 @@ else {
|
|
|
1232
1289
|
case "type": {
|
|
1233
1290
|
const def = node.getDefinition();
|
|
1234
1291
|
const handle = this.toAnnotatedHandle(def, true);
|
|
1292
|
+
const typeId = this.typeIds.get(node) ?? (node.__typeId != null ? node.id : undefined);
|
|
1293
|
+
if (typeId) handle.id(typeId);
|
|
1235
1294
|
return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1236
1295
|
}
|
|
1237
1296
|
case "prop": {
|
|
@@ -1358,6 +1417,14 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1358
1417
|
return this;
|
|
1359
1418
|
}
|
|
1360
1419
|
}
|
|
1420
|
+
if (this._adHocAnnotations && this.hasAdHocAnnotationsThroughRef()) {
|
|
1421
|
+
let resolved = decl ? this.doc.mergeIntersection(decl) : undefined;
|
|
1422
|
+
if (resolved && isInterface(resolved)) resolved = resolved.getDefinition() || resolved;
|
|
1423
|
+
if (resolved) {
|
|
1424
|
+
this.annotateType(resolved, name);
|
|
1425
|
+
return this;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1361
1428
|
const chain = ref.hasChain ? `, [${ref.chain.map((c) => `"${escapeQuotes(c.text)}"`).join(", ")}]` : "";
|
|
1362
1429
|
this.writeln(`$(${name ? `"", ${name}` : ""})`).indent().writeln(`.refTo(${ref.id}${chain})`);
|
|
1363
1430
|
if (!ref.hasChain) {
|
|
@@ -1591,11 +1658,29 @@ else targetValue = "true";
|
|
|
1591
1658
|
const targetName = node.targetName;
|
|
1592
1659
|
const targetDef = this.resolveTargetDef(targetName);
|
|
1593
1660
|
this.writeln("// Ad-hoc annotations for ", targetName);
|
|
1661
|
+
const allClones = [];
|
|
1662
|
+
const entryAccessors = [];
|
|
1594
1663
|
for (const entry of node.entries) {
|
|
1595
1664
|
const anns = entry.annotations;
|
|
1596
1665
|
if (!anns || anns.length === 0) continue;
|
|
1597
1666
|
const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
|
|
1598
|
-
const accessors = this.buildMutatingAccessors(targetName, targetDef, parts);
|
|
1667
|
+
const { accessors, clones } = this.buildMutatingAccessors(targetName, targetDef, parts);
|
|
1668
|
+
allClones.push(...clones);
|
|
1669
|
+
entryAccessors.push({
|
|
1670
|
+
entry,
|
|
1671
|
+
accessors
|
|
1672
|
+
});
|
|
1673
|
+
}
|
|
1674
|
+
const cloneKeys = new Set();
|
|
1675
|
+
for (const clone of allClones) {
|
|
1676
|
+
const key = `${clone.parentPath}|${clone.propName}`;
|
|
1677
|
+
if (!cloneKeys.has(key)) {
|
|
1678
|
+
cloneKeys.add(key);
|
|
1679
|
+
this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
for (const { entry, accessors } of entryAccessors) {
|
|
1683
|
+
const anns = entry.annotations;
|
|
1599
1684
|
for (const accessor of accessors) {
|
|
1600
1685
|
const cleared = new Set();
|
|
1601
1686
|
for (const an of anns) {
|
|
@@ -1645,15 +1730,21 @@ else targetValue = "true";
|
|
|
1645
1730
|
prefix: `${targetName}.type`,
|
|
1646
1731
|
def: targetDef
|
|
1647
1732
|
}];
|
|
1733
|
+
const clones = [];
|
|
1648
1734
|
for (let i = 0; i < parts.length; i++) {
|
|
1649
1735
|
const nextAccessors = [];
|
|
1650
1736
|
for (const { prefix, def } of accessors) {
|
|
1651
1737
|
const results = this.buildPropPaths(def, parts[i]);
|
|
1652
|
-
if (results.length > 0) for (const result of results) if (i < parts.length - 1)
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1738
|
+
if (results.length > 0) for (const result of results) if (i < parts.length - 1) {
|
|
1739
|
+
if (result.propDef && isRef(result.propDef)) clones.push({
|
|
1740
|
+
parentPath: prefix,
|
|
1741
|
+
propName: parts[i]
|
|
1742
|
+
});
|
|
1743
|
+
nextAccessors.push({
|
|
1744
|
+
prefix: `${prefix}${result.path}?.type`,
|
|
1745
|
+
def: result.propDef
|
|
1746
|
+
});
|
|
1747
|
+
} else nextAccessors.push({
|
|
1657
1748
|
prefix: `${prefix}${result.path}?`,
|
|
1658
1749
|
def: result.propDef
|
|
1659
1750
|
});
|
|
@@ -1667,7 +1758,10 @@ else {
|
|
|
1667
1758
|
}
|
|
1668
1759
|
accessors = nextAccessors;
|
|
1669
1760
|
}
|
|
1670
|
-
return
|
|
1761
|
+
return {
|
|
1762
|
+
accessors: accessors.map((a) => a.prefix),
|
|
1763
|
+
clones
|
|
1764
|
+
};
|
|
1671
1765
|
}
|
|
1672
1766
|
/**
|
|
1673
1767
|
* Finds a property in a type tree at compile time, returning all
|
|
@@ -1703,7 +1797,7 @@ else {
|
|
|
1703
1797
|
return [];
|
|
1704
1798
|
}
|
|
1705
1799
|
constructor(doc, opts) {
|
|
1706
|
-
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 = [];
|
|
1800
|
+
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();
|
|
1707
1801
|
}
|
|
1708
1802
|
};
|
|
1709
1803
|
|
package/dist/utils.cjs
CHANGED
|
@@ -428,6 +428,63 @@ else metadata.set(key, [a, value]);
|
|
|
428
428
|
} else metadata.set(key, [value]);
|
|
429
429
|
else metadata.set(key, value);
|
|
430
430
|
}
|
|
431
|
+
function cloneRefProp(parentType, propName) {
|
|
432
|
+
if (parentType.kind !== "object") return;
|
|
433
|
+
const objType = parentType;
|
|
434
|
+
const existing = objType.props.get(propName);
|
|
435
|
+
if (!existing) return;
|
|
436
|
+
const clonedType = cloneTypeDef(existing.type);
|
|
437
|
+
objType.props.set(propName, {
|
|
438
|
+
__is_atscript_annotated_type: true,
|
|
439
|
+
type: clonedType,
|
|
440
|
+
metadata: new Map(existing.metadata),
|
|
441
|
+
id: existing.id,
|
|
442
|
+
optional: existing.optional,
|
|
443
|
+
validator(opts) {
|
|
444
|
+
return new Validator(this, opts);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
function cloneTypeDef(type) {
|
|
449
|
+
if (type.kind === "object") {
|
|
450
|
+
const obj = type;
|
|
451
|
+
return {
|
|
452
|
+
kind: "object",
|
|
453
|
+
props: new Map(Array.from(obj.props.entries()).map(([k, v]) => [k, {
|
|
454
|
+
__is_atscript_annotated_type: true,
|
|
455
|
+
type: v.type,
|
|
456
|
+
metadata: new Map(v.metadata),
|
|
457
|
+
id: v.id,
|
|
458
|
+
optional: v.optional,
|
|
459
|
+
validator(opts) {
|
|
460
|
+
return new Validator(this, opts);
|
|
461
|
+
}
|
|
462
|
+
}])),
|
|
463
|
+
propsPatterns: [...obj.propsPatterns],
|
|
464
|
+
tags: new Set(obj.tags)
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (type.kind === "array") {
|
|
468
|
+
const arr = type;
|
|
469
|
+
return {
|
|
470
|
+
kind: "array",
|
|
471
|
+
of: arr.of,
|
|
472
|
+
tags: new Set(arr.tags)
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
if (type.kind === "union" || type.kind === "intersection" || type.kind === "tuple") {
|
|
476
|
+
const complex = type;
|
|
477
|
+
return {
|
|
478
|
+
kind: type.kind,
|
|
479
|
+
items: [...complex.items],
|
|
480
|
+
tags: new Set(complex.tags)
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return {
|
|
484
|
+
...type,
|
|
485
|
+
tags: new Set(type.tags)
|
|
486
|
+
};
|
|
487
|
+
}
|
|
431
488
|
function defineAnnotatedType(_kind, base) {
|
|
432
489
|
const kind = _kind || "";
|
|
433
490
|
const type = base?.type || {};
|
|
@@ -519,6 +576,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
|
|
|
519
576
|
__is_atscript_annotated_type: true,
|
|
520
577
|
type: newBase.type,
|
|
521
578
|
metadata,
|
|
579
|
+
id: newBase.id,
|
|
522
580
|
validator(opts) {
|
|
523
581
|
return new Validator(this, opts);
|
|
524
582
|
}
|
|
@@ -529,6 +587,10 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
|
|
|
529
587
|
annotate(key, value, asArray) {
|
|
530
588
|
annotate(this.$metadata, key, value, asArray);
|
|
531
589
|
return this;
|
|
590
|
+
},
|
|
591
|
+
id(value) {
|
|
592
|
+
this.$type.id = value;
|
|
593
|
+
return this;
|
|
532
594
|
}
|
|
533
595
|
};
|
|
534
596
|
return handle;
|
|
@@ -593,47 +655,71 @@ function isAnnotatedTypeOfPrimitive(t) {
|
|
|
593
655
|
return null;
|
|
594
656
|
}
|
|
595
657
|
function buildJsonSchema(type) {
|
|
658
|
+
const defs = {};
|
|
659
|
+
let isRoot = true;
|
|
660
|
+
const buildObject = (d) => {
|
|
661
|
+
const properties = {};
|
|
662
|
+
const required = [];
|
|
663
|
+
for (const [key, val] of d.type.props.entries()) {
|
|
664
|
+
if (isPhantomType(val)) continue;
|
|
665
|
+
properties[key] = build$1(val);
|
|
666
|
+
if (!val.optional) required.push(key);
|
|
667
|
+
}
|
|
668
|
+
const schema$1 = {
|
|
669
|
+
type: "object",
|
|
670
|
+
properties
|
|
671
|
+
};
|
|
672
|
+
if (required.length > 0) schema$1.required = required;
|
|
673
|
+
return schema$1;
|
|
674
|
+
};
|
|
596
675
|
const build$1 = (def) => {
|
|
676
|
+
if (def.id && def.type.kind === "object" && !isRoot) {
|
|
677
|
+
const name = def.id;
|
|
678
|
+
if (!defs[name]) {
|
|
679
|
+
defs[name] = {};
|
|
680
|
+
defs[name] = buildObject(def);
|
|
681
|
+
}
|
|
682
|
+
return { $ref: `#/$defs/${name}` };
|
|
683
|
+
}
|
|
684
|
+
isRoot = false;
|
|
597
685
|
const meta = def.metadata;
|
|
598
686
|
return forAnnotatedType(def, {
|
|
599
687
|
phantom() {
|
|
600
688
|
return {};
|
|
601
689
|
},
|
|
602
690
|
object(d) {
|
|
603
|
-
|
|
604
|
-
const required = [];
|
|
605
|
-
for (const [key, val] of d.type.props.entries()) {
|
|
606
|
-
if (isPhantomType(val)) continue;
|
|
607
|
-
properties[key] = build$1(val);
|
|
608
|
-
if (!val.optional) required.push(key);
|
|
609
|
-
}
|
|
610
|
-
const schema = {
|
|
611
|
-
type: "object",
|
|
612
|
-
properties
|
|
613
|
-
};
|
|
614
|
-
if (required.length > 0) schema.required = required;
|
|
615
|
-
return schema;
|
|
691
|
+
return buildObject(d);
|
|
616
692
|
},
|
|
617
693
|
array(d) {
|
|
618
|
-
const schema = {
|
|
694
|
+
const schema$1 = {
|
|
619
695
|
type: "array",
|
|
620
696
|
items: build$1(d.type.of)
|
|
621
697
|
};
|
|
622
698
|
const minLength = meta.get("expect.minLength");
|
|
623
|
-
if (minLength) schema.minItems = typeof minLength === "number" ? minLength : minLength.length;
|
|
699
|
+
if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
|
|
624
700
|
const maxLength = meta.get("expect.maxLength");
|
|
625
|
-
if (maxLength) schema.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
626
|
-
return schema;
|
|
701
|
+
if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
702
|
+
return schema$1;
|
|
627
703
|
},
|
|
628
704
|
union(d) {
|
|
629
705
|
const disc = detectDiscriminator(d.type.items);
|
|
630
|
-
if (disc)
|
|
631
|
-
oneOf
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
706
|
+
if (disc) {
|
|
707
|
+
const oneOf = d.type.items.map(build$1);
|
|
708
|
+
const mapping = {};
|
|
709
|
+
for (const [val, origPath] of Object.entries(disc.mapping)) {
|
|
710
|
+
const idx = Number.parseInt(origPath.split("/").pop());
|
|
711
|
+
const item = d.type.items[idx];
|
|
712
|
+
if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
|
|
713
|
+
else mapping[val] = origPath;
|
|
635
714
|
}
|
|
636
|
-
|
|
715
|
+
return {
|
|
716
|
+
oneOf,
|
|
717
|
+
discriminator: {
|
|
718
|
+
propertyName: disc.propertyName,
|
|
719
|
+
mapping
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
}
|
|
637
723
|
return { anyOf: d.type.items.map(build$1) };
|
|
638
724
|
},
|
|
639
725
|
intersection(d) {
|
|
@@ -647,38 +733,54 @@ function buildJsonSchema(type) {
|
|
|
647
733
|
};
|
|
648
734
|
},
|
|
649
735
|
final(d) {
|
|
650
|
-
const schema = {};
|
|
651
|
-
if (d.type.value !== undefined) schema.const = d.type.value;
|
|
736
|
+
const schema$1 = {};
|
|
737
|
+
if (d.type.value !== undefined) schema$1.const = d.type.value;
|
|
652
738
|
if (d.type.designType && d.type.designType !== "any") {
|
|
653
|
-
schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
|
|
654
|
-
if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
|
|
739
|
+
schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
|
|
740
|
+
if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
|
|
655
741
|
}
|
|
656
|
-
if (schema.type === "string") {
|
|
657
|
-
if (meta.get("meta.required")) schema.minLength = 1;
|
|
742
|
+
if (schema$1.type === "string") {
|
|
743
|
+
if (meta.get("meta.required")) schema$1.minLength = 1;
|
|
658
744
|
const minLength = meta.get("expect.minLength");
|
|
659
|
-
if (minLength) schema.minLength = typeof minLength === "number" ? minLength : minLength.length;
|
|
745
|
+
if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
|
|
660
746
|
const maxLength = meta.get("expect.maxLength");
|
|
661
|
-
if (maxLength) schema.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
747
|
+
if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
|
|
662
748
|
const patterns = meta.get("expect.pattern");
|
|
663
|
-
if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
|
|
664
|
-
else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
|
|
749
|
+
if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
|
|
750
|
+
else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
|
|
665
751
|
}
|
|
666
|
-
if (schema.type === "number" || schema.type === "integer") {
|
|
752
|
+
if (schema$1.type === "number" || schema$1.type === "integer") {
|
|
667
753
|
const min = meta.get("expect.min");
|
|
668
|
-
if (min) schema.minimum = typeof min === "number" ? min : min.minValue;
|
|
754
|
+
if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
|
|
669
755
|
const max = meta.get("expect.max");
|
|
670
|
-
if (max) schema.maximum = typeof max === "number" ? max : max.maxValue;
|
|
756
|
+
if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
|
|
671
757
|
}
|
|
672
|
-
return schema;
|
|
758
|
+
return schema$1;
|
|
673
759
|
}
|
|
674
760
|
});
|
|
675
761
|
};
|
|
676
|
-
|
|
762
|
+
const schema = build$1(type);
|
|
763
|
+
if (Object.keys(defs).length > 0) return {
|
|
764
|
+
...schema,
|
|
765
|
+
$defs: defs
|
|
766
|
+
};
|
|
767
|
+
return schema;
|
|
677
768
|
}
|
|
678
769
|
function fromJsonSchema(schema) {
|
|
770
|
+
const defsSource = schema.$defs || schema.definitions || {};
|
|
771
|
+
const resolved = new Map();
|
|
679
772
|
const convert = (s) => {
|
|
680
773
|
if (!s || Object.keys(s).length === 0) return defineAnnotatedType().designType("any").$type;
|
|
681
|
-
if (s.$ref)
|
|
774
|
+
if (s.$ref) {
|
|
775
|
+
const refName = s.$ref.replace(/^#\/(\$defs|definitions)\//, "");
|
|
776
|
+
if (resolved.has(refName)) return resolved.get(refName);
|
|
777
|
+
if (defsSource[refName]) {
|
|
778
|
+
const type = convert(defsSource[refName]);
|
|
779
|
+
resolved.set(refName, type);
|
|
780
|
+
return type;
|
|
781
|
+
}
|
|
782
|
+
throw new Error(`Unresolvable $ref: ${s.$ref}`);
|
|
783
|
+
}
|
|
682
784
|
if ("const" in s) {
|
|
683
785
|
const val = s.const;
|
|
684
786
|
const dt = val === null ? "null" : typeof val;
|
|
@@ -766,6 +868,24 @@ function fromJsonSchema(schema) {
|
|
|
766
868
|
};
|
|
767
869
|
return convert(schema);
|
|
768
870
|
}
|
|
871
|
+
function mergeJsonSchemas(types) {
|
|
872
|
+
const mergedDefs = {};
|
|
873
|
+
const schemas = {};
|
|
874
|
+
for (const type of types) {
|
|
875
|
+
const name = type.id;
|
|
876
|
+
if (!name) throw new Error("mergeJsonSchemas: all types must have an id");
|
|
877
|
+
const schema = buildJsonSchema(type);
|
|
878
|
+
if (schema.$defs) {
|
|
879
|
+
for (const [defName, defSchema] of Object.entries(schema.$defs)) if (!mergedDefs[defName]) mergedDefs[defName] = defSchema;
|
|
880
|
+
const { $defs: _,...rest } = schema;
|
|
881
|
+
schemas[name] = rest;
|
|
882
|
+
} else schemas[name] = schema;
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
schemas,
|
|
886
|
+
$defs: mergedDefs
|
|
887
|
+
};
|
|
888
|
+
}
|
|
769
889
|
|
|
770
890
|
//#endregion
|
|
771
891
|
//#region packages/typescript/src/default-value.ts
|
|
@@ -959,6 +1079,7 @@ function serializeNode(def, path, options) {
|
|
|
959
1079
|
metadata: serializeMetadata(def.metadata, path, def.type.kind, options)
|
|
960
1080
|
};
|
|
961
1081
|
if (def.optional) result.optional = true;
|
|
1082
|
+
if (def.id) result.id = def.id;
|
|
962
1083
|
return result;
|
|
963
1084
|
}
|
|
964
1085
|
function serializeTypeDef(def, path, options) {
|
|
@@ -1062,6 +1183,7 @@ function deserializeNode(data) {
|
|
|
1062
1183
|
}
|
|
1063
1184
|
};
|
|
1064
1185
|
if (data.optional) result.optional = true;
|
|
1186
|
+
if (data.id) result.id = data.id;
|
|
1065
1187
|
return result;
|
|
1066
1188
|
}
|
|
1067
1189
|
function deserializeTypeDef(t) {
|
|
@@ -1112,6 +1234,7 @@ exports.Validator = Validator
|
|
|
1112
1234
|
exports.ValidatorError = ValidatorError
|
|
1113
1235
|
exports.annotate = annotate
|
|
1114
1236
|
exports.buildJsonSchema = buildJsonSchema
|
|
1237
|
+
exports.cloneRefProp = cloneRefProp
|
|
1115
1238
|
exports.createDataFromAnnotatedType = createDataFromAnnotatedType
|
|
1116
1239
|
exports.defineAnnotatedType = defineAnnotatedType
|
|
1117
1240
|
exports.deserializeAnnotatedType = deserializeAnnotatedType
|
|
@@ -1121,5 +1244,6 @@ exports.fromJsonSchema = fromJsonSchema
|
|
|
1121
1244
|
exports.isAnnotatedType = isAnnotatedType
|
|
1122
1245
|
exports.isAnnotatedTypeOfPrimitive = isAnnotatedTypeOfPrimitive
|
|
1123
1246
|
exports.isPhantomType = isPhantomType
|
|
1247
|
+
exports.mergeJsonSchemas = mergeJsonSchemas
|
|
1124
1248
|
exports.serializeAnnotatedType = serializeAnnotatedType
|
|
1125
1249
|
exports.throwFeatureDisabled = throwFeatureDisabled
|
package/dist/utils.d.ts
CHANGED
|
@@ -181,6 +181,7 @@ interface TAtscriptAnnotatedType<T extends TAtscriptTypeDef = TAtscriptTypeDef,
|
|
|
181
181
|
validator(opts?: Partial<TValidatorOptions>): Validator<this, DataType>;
|
|
182
182
|
metadata: TMetadataMap<AtscriptMetadata>;
|
|
183
183
|
optional?: boolean;
|
|
184
|
+
id?: string;
|
|
184
185
|
}
|
|
185
186
|
/** An annotated type that is also a class constructor (i.e. a generated interface class). */
|
|
186
187
|
type TAtscriptAnnotatedTypeConstructor = TAtscriptAnnotatedType & (new (...args: any[]) => any);
|
|
@@ -193,6 +194,11 @@ declare function isAnnotatedType(type: any): type is TAtscriptAnnotatedType;
|
|
|
193
194
|
* Used by the handle's .annotate() method and by generated mutation statements.
|
|
194
195
|
*/
|
|
195
196
|
declare function annotate<K extends keyof AtscriptMetadata>(metadata: TMetadataMap<AtscriptMetadata> | undefined, key: K, value: AtscriptMetadata[K] extends Array<infer E> ? E : AtscriptMetadata[K], asArray?: boolean): void;
|
|
197
|
+
/**
|
|
198
|
+
* Clones a property's type tree in-place so mutations don't leak to shared refs.
|
|
199
|
+
* Used by mutating annotate codegen when paths cross ref boundaries.
|
|
200
|
+
*/
|
|
201
|
+
declare function cloneRefProp(parentType: TAtscriptTypeDef, propName: string): void;
|
|
196
202
|
type TKind = '' | 'array' | 'object' | 'union' | 'intersection' | 'tuple';
|
|
197
203
|
/**
|
|
198
204
|
* Creates a builder handle for constructing a {@link TAtscriptAnnotatedType} at runtime.
|
|
@@ -242,6 +248,7 @@ interface TAnnotatedTypeHandle {
|
|
|
242
248
|
name?: string;
|
|
243
249
|
}, chain?: string[]): TAnnotatedTypeHandle;
|
|
244
250
|
annotate(key: keyof AtscriptMetadata, value: any, asArray?: boolean): TAnnotatedTypeHandle;
|
|
251
|
+
id(value: string): TAnnotatedTypeHandle;
|
|
245
252
|
}
|
|
246
253
|
/**
|
|
247
254
|
* Checks whether an annotated type is a phantom type.
|
|
@@ -299,6 +306,20 @@ declare function buildJsonSchema(type: TAtscriptAnnotatedType): TJsonSchema;
|
|
|
299
306
|
* @returns An annotated type with full validator support.
|
|
300
307
|
*/
|
|
301
308
|
declare function fromJsonSchema(schema: TJsonSchema): TAtscriptAnnotatedType;
|
|
309
|
+
/**
|
|
310
|
+
* Merges multiple annotated types into a combined schema map with shared `$defs`.
|
|
311
|
+
*
|
|
312
|
+
* Each type must have an `id`. The returned `schemas` object contains individual
|
|
313
|
+
* schemas keyed by type id, and `$defs` contains all shared type definitions
|
|
314
|
+
* deduplicated across schemas.
|
|
315
|
+
*
|
|
316
|
+
* @param types - Array of annotated types, each with an `id`.
|
|
317
|
+
* @returns An object with `schemas` (keyed by id) and shared `$defs`.
|
|
318
|
+
*/
|
|
319
|
+
declare function mergeJsonSchemas(types: TAtscriptAnnotatedType[]): {
|
|
320
|
+
schemas: Record<string, TJsonSchema>;
|
|
321
|
+
$defs: Record<string, TJsonSchema>;
|
|
322
|
+
};
|
|
302
323
|
|
|
303
324
|
/**
|
|
304
325
|
* Type-safe dispatch over `TAtscriptAnnotatedType` by its `type.kind`.
|
|
@@ -417,6 +438,7 @@ interface TSerializedAnnotatedTypeInner {
|
|
|
417
438
|
type: TSerializedTypeDef;
|
|
418
439
|
metadata: Record<string, unknown>;
|
|
419
440
|
optional?: boolean;
|
|
441
|
+
id?: string;
|
|
420
442
|
}
|
|
421
443
|
interface TSerializedTypeFinal {
|
|
422
444
|
kind: '';
|
|
@@ -511,5 +533,5 @@ declare function serializeAnnotatedType(type: TAtscriptAnnotatedType, options?:
|
|
|
511
533
|
*/
|
|
512
534
|
declare function deserializeAnnotatedType(data: TSerializedAnnotatedType): TAtscriptAnnotatedType;
|
|
513
535
|
|
|
514
|
-
export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, serializeAnnotatedType, throwFeatureDisabled };
|
|
515
|
-
export type { InferDataType, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
|
|
536
|
+
export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, cloneRefProp, createDataFromAnnotatedType, defineAnnotatedType, deserializeAnnotatedType, flattenAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, isPhantomType, mergeJsonSchemas, serializeAnnotatedType, throwFeatureDisabled };
|
|
537
|
+
export type { InferDataType, TAnnotatedTypeHandle, TAtscriptAnnotatedType, TAtscriptAnnotatedTypeConstructor, TAtscriptDataType, TAtscriptTypeArray, TAtscriptTypeComplex, TAtscriptTypeDef, TAtscriptTypeFinal, TAtscriptTypeObject, TCreateDataOptions, TFlattenOptions, TJsonSchema, TMetadataMap, TProcessAnnotationContext, TSerializeOptions, TSerializedAnnotatedType, TSerializedAnnotatedTypeInner, TSerializedTypeArray, TSerializedTypeComplex, TSerializedTypeDef, TSerializedTypeFinal, TSerializedTypeObject, TValidatorOptions, TValidatorPlugin, TValidatorPluginContext, TValueResolver };
|