@atscript/typescript 0.1.2 → 0.1.4
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 +118 -54
- package/dist/index.cjs +118 -54
- package/dist/index.d.ts +8 -2
- package/dist/index.mjs +118 -54
- package/dist/utils.cjs +330 -42
- package/dist/utils.d.ts +286 -12
- package/dist/utils.mjs +324 -42
- package/package.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -234,7 +234,7 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
234
234
|
this.writeln(" * Do not edit this file!");
|
|
235
235
|
this.writeln(" */");
|
|
236
236
|
this.writeln();
|
|
237
|
-
this.writeln("import type { TAtscriptTypeObject, TAtscriptTypeComplex, TAtscriptTypeFinal, TAtscriptTypeArray, TMetadataMap, Validator,
|
|
237
|
+
this.writeln("import type { TAtscriptTypeObject, TAtscriptTypeComplex, TAtscriptTypeFinal, TAtscriptTypeArray, TAtscriptAnnotatedType, TMetadataMap, Validator, TValidatorOptions } from \"@atscript/typescript/utils\"");
|
|
238
238
|
}
|
|
239
239
|
post() {
|
|
240
240
|
this.writeln("// prettier-ignore-end");
|
|
@@ -298,6 +298,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
298
298
|
patterns.push(prop);
|
|
299
299
|
continue;
|
|
300
300
|
}
|
|
301
|
+
if (this.isPhantomProp(prop.getDefinition())) {
|
|
302
|
+
this.writeln(`// ${prop.id}: phantom`);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
301
305
|
const optional = !!prop.token("optional");
|
|
302
306
|
this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
|
|
303
307
|
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
@@ -323,9 +327,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
323
327
|
}
|
|
324
328
|
if (asClass) {
|
|
325
329
|
this.writeln("static __is_atscript_annotated_type: true");
|
|
326
|
-
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
|
|
330
|
+
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
327
331
|
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
328
|
-
this.writeln(`static validator:
|
|
332
|
+
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
333
|
+
if (resolveJsonSchemaMode(this.opts) === false) this.writeln("/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */");
|
|
329
334
|
this.writeln("static toJsonSchema: () => any");
|
|
330
335
|
}
|
|
331
336
|
this.pop();
|
|
@@ -340,6 +345,12 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
340
345
|
if (struct?.entity === "structure") this.renderStructure(struct, node.id);
|
|
341
346
|
else this.writeln("{}");
|
|
342
347
|
this.writeln();
|
|
348
|
+
const nsPrefix = exported ? "export declare" : "declare";
|
|
349
|
+
this.write(`${nsPrefix} namespace ${node.id} `);
|
|
350
|
+
this.blockln("{}");
|
|
351
|
+
this.writeln(`type DataType = ${node.id}`);
|
|
352
|
+
this.popln();
|
|
353
|
+
this.writeln();
|
|
343
354
|
}
|
|
344
355
|
renderType(node) {
|
|
345
356
|
this.writeln();
|
|
@@ -366,6 +377,11 @@ else this.writeln("{}");
|
|
|
366
377
|
} else {
|
|
367
378
|
this.write(exported ? "export declare " : "declare ");
|
|
368
379
|
this.writeln(`class ${node.id} extends ${targetName} {}`);
|
|
380
|
+
const nsPrefix = exported ? "export declare" : "declare";
|
|
381
|
+
this.write(`${nsPrefix} namespace ${node.id} `);
|
|
382
|
+
this.blockln("{}");
|
|
383
|
+
this.writeln(`type DataType = ${node.id}`);
|
|
384
|
+
this.popln();
|
|
369
385
|
this.writeln();
|
|
370
386
|
}
|
|
371
387
|
}
|
|
@@ -380,18 +396,29 @@ else this.writeln("{}");
|
|
|
380
396
|
let realDef = inputDef;
|
|
381
397
|
if ((0, __atscript_core.isRef)(inputDef)) realDef = this.doc.unwindType(inputDef.id, inputDef.chain)?.def || realDef;
|
|
382
398
|
realDef = this.doc.mergeIntersection(realDef);
|
|
383
|
-
if ((0, __atscript_core.isStructure)(realDef) || (0, __atscript_core.isInterface)(realDef)) typeDef = `TAtscriptTypeObject<keyof ${name}>`;
|
|
384
|
-
else if ((0, __atscript_core.isGroup)(realDef)) typeDef =
|
|
385
|
-
else if ((0, __atscript_core.isArray)(realDef)) typeDef =
|
|
386
|
-
else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef =
|
|
399
|
+
if ((0, __atscript_core.isStructure)(realDef) || (0, __atscript_core.isInterface)(realDef)) typeDef = `TAtscriptTypeObject<keyof ${name}, ${name}>`;
|
|
400
|
+
else if ((0, __atscript_core.isGroup)(realDef)) typeDef = `TAtscriptTypeComplex<${name}>`;
|
|
401
|
+
else if ((0, __atscript_core.isArray)(realDef)) typeDef = `TAtscriptTypeArray<${name}>`;
|
|
402
|
+
else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
|
|
387
403
|
}
|
|
404
|
+
this.writeln(`type DataType = ${name}`);
|
|
388
405
|
this.writeln(`const __is_atscript_annotated_type: true`);
|
|
389
406
|
this.writeln(`const type: ${typeDef}`);
|
|
390
407
|
this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
|
|
391
|
-
this.writeln(`const validator: (opts?: Partial<TValidatorOptions>) => Validator<
|
|
408
|
+
this.writeln(`const validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${name}, ${name}>`);
|
|
409
|
+
if (resolveJsonSchemaMode(this.opts) === false) this.writeln("/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */");
|
|
392
410
|
this.writeln("const toJsonSchema: () => any");
|
|
393
411
|
this.popln();
|
|
394
412
|
}
|
|
413
|
+
isPhantomProp(def) {
|
|
414
|
+
if (!def) return false;
|
|
415
|
+
if ((0, __atscript_core.isPrimitive)(def) && def.id === "phantom") return true;
|
|
416
|
+
if ((0, __atscript_core.isRef)(def)) {
|
|
417
|
+
const unwound = this.doc.unwindType(def.id, def.chain)?.def;
|
|
418
|
+
return (0, __atscript_core.isPrimitive)(unwound) && unwound.id === "phantom";
|
|
419
|
+
}
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
395
422
|
isTypeTarget(name, doc) {
|
|
396
423
|
const d = doc || this.doc;
|
|
397
424
|
const decl = d.getDeclarationOwnerNode(name);
|
|
@@ -430,6 +457,24 @@ function renderPrimitiveTypeDef(def) {
|
|
|
430
457
|
}
|
|
431
458
|
}
|
|
432
459
|
|
|
460
|
+
//#endregion
|
|
461
|
+
//#region packages/typescript/src/traverse.ts
|
|
462
|
+
function forAnnotatedType(def, handlers) {
|
|
463
|
+
switch (def.type.kind) {
|
|
464
|
+
case "": {
|
|
465
|
+
const typed = def;
|
|
466
|
+
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
467
|
+
return handlers.final(typed);
|
|
468
|
+
}
|
|
469
|
+
case "object": return handlers.object(def);
|
|
470
|
+
case "array": return handlers.array(def);
|
|
471
|
+
case "union": return handlers.union(def);
|
|
472
|
+
case "intersection": return handlers.intersection(def);
|
|
473
|
+
case "tuple": return handlers.tuple(def);
|
|
474
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
433
478
|
//#endregion
|
|
434
479
|
//#region packages/typescript/src/validator.ts
|
|
435
480
|
function _define_property$2(obj, key, value) {
|
|
@@ -475,7 +520,17 @@ var Validator = class {
|
|
|
475
520
|
throw() {
|
|
476
521
|
throw new ValidatorError(this.errors);
|
|
477
522
|
}
|
|
478
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Validates a value against the type definition.
|
|
525
|
+
*
|
|
526
|
+
* Acts as a TypeScript type guard — when it returns `true`, the value
|
|
527
|
+
* is narrowed to `DataType`.
|
|
528
|
+
*
|
|
529
|
+
* @param value - The value to validate.
|
|
530
|
+
* @param safe - If `true`, returns `false` on failure instead of throwing.
|
|
531
|
+
* @returns `true` if the value matches the type definition.
|
|
532
|
+
* @throws {ValidatorError} When validation fails and `safe` is not `true`.
|
|
533
|
+
*/ validate(value, safe) {
|
|
479
534
|
this.push("");
|
|
480
535
|
this.errors = [];
|
|
481
536
|
this.stackErrors = [];
|
|
@@ -502,15 +557,15 @@ var Validator = class {
|
|
|
502
557
|
return this.stackPath.slice(1).join(".");
|
|
503
558
|
}
|
|
504
559
|
validateAnnotatedType(def, value) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
560
|
+
return forAnnotatedType(def, {
|
|
561
|
+
final: (d) => this.validatePrimitive(d, value),
|
|
562
|
+
phantom: () => true,
|
|
563
|
+
object: (d) => this.validateObject(d, value),
|
|
564
|
+
array: (d) => this.validateArray(d, value),
|
|
565
|
+
union: (d) => this.validateUnion(d, value),
|
|
566
|
+
intersection: (d) => this.validateIntersection(d, value),
|
|
567
|
+
tuple: (d) => this.validateTuple(d, value)
|
|
568
|
+
});
|
|
514
569
|
}
|
|
515
570
|
validateUnion(def, value) {
|
|
516
571
|
let i = 0;
|
|
@@ -601,7 +656,7 @@ var Validator = class {
|
|
|
601
656
|
let partialFunctionMatched = false;
|
|
602
657
|
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
|
|
603
658
|
for (const [key, item] of def.type.props.entries()) {
|
|
604
|
-
if (skipList.has(key)) continue;
|
|
659
|
+
if (skipList.has(key) || isPhantomType(item)) continue;
|
|
605
660
|
typeKeys.add(key);
|
|
606
661
|
if (value[key] === undefined) {
|
|
607
662
|
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
@@ -742,7 +797,7 @@ else {
|
|
|
742
797
|
constructor(def, opts) {
|
|
743
798
|
_define_property$2(this, "def", void 0);
|
|
744
799
|
_define_property$2(this, "opts", void 0);
|
|
745
|
-
_define_property$2(this, "errors", void 0);
|
|
800
|
+
/** Validation errors collected during the last {@link validate} call. */ _define_property$2(this, "errors", void 0);
|
|
746
801
|
_define_property$2(this, "stackErrors", void 0);
|
|
747
802
|
_define_property$2(this, "stackPath", void 0);
|
|
748
803
|
this.def = def;
|
|
@@ -883,19 +938,24 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
|
|
|
883
938
|
};
|
|
884
939
|
return handle;
|
|
885
940
|
}
|
|
941
|
+
function isPhantomType(def) {
|
|
942
|
+
return def.type.kind === "" && def.type.designType === "phantom";
|
|
943
|
+
}
|
|
886
944
|
|
|
887
945
|
//#endregion
|
|
888
946
|
//#region packages/typescript/src/json-schema.ts
|
|
889
947
|
function buildJsonSchema(type) {
|
|
890
948
|
const build$1 = (def) => {
|
|
891
|
-
const t = def.type;
|
|
892
949
|
const meta = def.metadata;
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
950
|
+
return forAnnotatedType(def, {
|
|
951
|
+
phantom() {
|
|
952
|
+
return {};
|
|
953
|
+
},
|
|
954
|
+
object(d) {
|
|
896
955
|
const properties = {};
|
|
897
956
|
const required = [];
|
|
898
|
-
for (const [key, val] of
|
|
957
|
+
for (const [key, val] of d.type.props.entries()) {
|
|
958
|
+
if (isPhantomType(val)) continue;
|
|
899
959
|
properties[key] = build$1(val);
|
|
900
960
|
if (!val.optional) required.push(key);
|
|
901
961
|
}
|
|
@@ -905,41 +965,36 @@ function buildJsonSchema(type) {
|
|
|
905
965
|
};
|
|
906
966
|
if (required.length) schema.required = required;
|
|
907
967
|
return schema;
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
const arr = t;
|
|
968
|
+
},
|
|
969
|
+
array(d) {
|
|
911
970
|
const schema = {
|
|
912
971
|
type: "array",
|
|
913
|
-
items: build$1(
|
|
972
|
+
items: build$1(d.type.of)
|
|
914
973
|
};
|
|
915
974
|
const minLength = meta.get("expect.minLength");
|
|
916
975
|
if (typeof minLength === "number") schema.minItems = minLength;
|
|
917
976
|
const maxLength = meta.get("expect.maxLength");
|
|
918
977
|
if (typeof maxLength === "number") schema.maxItems = maxLength;
|
|
919
978
|
return schema;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
}
|
|
929
|
-
case "tuple": {
|
|
930
|
-
const grp = t;
|
|
979
|
+
},
|
|
980
|
+
union(d) {
|
|
981
|
+
return { anyOf: d.type.items.map(build$1) };
|
|
982
|
+
},
|
|
983
|
+
intersection(d) {
|
|
984
|
+
return { allOf: d.type.items.map(build$1) };
|
|
985
|
+
},
|
|
986
|
+
tuple(d) {
|
|
931
987
|
return {
|
|
932
988
|
type: "array",
|
|
933
|
-
items:
|
|
989
|
+
items: d.type.items.map(build$1),
|
|
934
990
|
additionalItems: false
|
|
935
991
|
};
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
const fin = t;
|
|
992
|
+
},
|
|
993
|
+
final(d) {
|
|
939
994
|
const schema = {};
|
|
940
|
-
if (
|
|
941
|
-
if (
|
|
942
|
-
schema.type =
|
|
995
|
+
if (d.type.value !== undefined) schema.const = d.type.value;
|
|
996
|
+
if (d.type.designType && d.type.designType !== "any") {
|
|
997
|
+
schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
|
|
943
998
|
if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
|
|
944
999
|
}
|
|
945
1000
|
if (schema.type === "string") {
|
|
@@ -959,8 +1014,7 @@ else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern:
|
|
|
959
1014
|
}
|
|
960
1015
|
return schema;
|
|
961
1016
|
}
|
|
962
|
-
|
|
963
|
-
}
|
|
1017
|
+
});
|
|
964
1018
|
};
|
|
965
1019
|
return build$1(type);
|
|
966
1020
|
}
|
|
@@ -982,7 +1036,7 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
982
1036
|
this.writeln("// prettier-ignore-start");
|
|
983
1037
|
this.writeln("/* eslint-disable */");
|
|
984
1038
|
const imports = ["defineAnnotatedType as $", "annotate as $a"];
|
|
985
|
-
if (
|
|
1039
|
+
if (resolveJsonSchemaMode(this.opts) === "lazy") imports.push("buildJsonSchema as $$");
|
|
986
1040
|
this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
|
|
987
1041
|
}
|
|
988
1042
|
buildAdHocMap(annotateNodes) {
|
|
@@ -1070,15 +1124,21 @@ else {
|
|
|
1070
1124
|
this.writeln();
|
|
1071
1125
|
}
|
|
1072
1126
|
renderJsonSchemaMethod(node) {
|
|
1073
|
-
|
|
1127
|
+
const mode = resolveJsonSchemaMode(this.opts);
|
|
1128
|
+
const hasAnnotation = node.countAnnotations("emit.jsonSchema") > 0;
|
|
1129
|
+
if (hasAnnotation || mode === "bundle") {
|
|
1074
1130
|
const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
|
|
1075
1131
|
this.writeln("static toJsonSchema() {");
|
|
1076
1132
|
this.indent().writeln(`return ${schema}`).unindent();
|
|
1077
1133
|
this.writeln("}");
|
|
1078
|
-
} else {
|
|
1134
|
+
} else if (mode === "lazy") {
|
|
1079
1135
|
this.writeln("static toJsonSchema() {");
|
|
1080
1136
|
this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
|
|
1081
1137
|
this.writeln("}");
|
|
1138
|
+
} else {
|
|
1139
|
+
this.writeln("static toJsonSchema() {");
|
|
1140
|
+
this.indent().writeln("throw new Error(\"JSON Schema support is disabled. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add @emit.jsonSchema annotation to individual interfaces.\")").unindent();
|
|
1141
|
+
this.writeln("}");
|
|
1082
1142
|
}
|
|
1083
1143
|
}
|
|
1084
1144
|
toAnnotatedType(node) {
|
|
@@ -1112,7 +1172,7 @@ else {
|
|
|
1112
1172
|
case "primitive": {
|
|
1113
1173
|
const prim = node;
|
|
1114
1174
|
const handle = defineAnnotatedType();
|
|
1115
|
-
handle.designType(prim.id === "never" ? "never" : prim.config.type);
|
|
1175
|
+
handle.designType(prim.id === "never" ? "never" : prim.id === "phantom" ? "phantom" : prim.config.type);
|
|
1116
1176
|
if (!skipAnnotations) this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1117
1177
|
return handle;
|
|
1118
1178
|
}
|
|
@@ -1239,7 +1299,7 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1239
1299
|
return this;
|
|
1240
1300
|
}
|
|
1241
1301
|
definePrimitive(node, name) {
|
|
1242
|
-
this.renderPrimitiveDef(node.id === "never" ? "never" : node.config.type, name);
|
|
1302
|
+
this.renderPrimitiveDef(node.id === "never" ? "never" : node.id === "phantom" ? "phantom" : node.config.type, name);
|
|
1243
1303
|
this.writeln(` .tags(${Array.from(node.tags).map((f) => `"${escapeQuotes(f)}"`).join(", ")})`);
|
|
1244
1304
|
return this;
|
|
1245
1305
|
}
|
|
@@ -1521,6 +1581,10 @@ else {
|
|
|
1521
1581
|
|
|
1522
1582
|
//#endregion
|
|
1523
1583
|
//#region packages/typescript/src/plugin.ts
|
|
1584
|
+
function resolveJsonSchemaMode(opts) {
|
|
1585
|
+
if (opts?.jsonSchema !== undefined) return opts.jsonSchema;
|
|
1586
|
+
return false;
|
|
1587
|
+
}
|
|
1524
1588
|
const tsPlugin = (opts) => {
|
|
1525
1589
|
return {
|
|
1526
1590
|
name: "typesccript",
|
package/dist/index.cjs
CHANGED
|
@@ -231,7 +231,7 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
231
231
|
this.writeln(" * Do not edit this file!");
|
|
232
232
|
this.writeln(" */");
|
|
233
233
|
this.writeln();
|
|
234
|
-
this.writeln("import type { TAtscriptTypeObject, TAtscriptTypeComplex, TAtscriptTypeFinal, TAtscriptTypeArray, TMetadataMap, Validator,
|
|
234
|
+
this.writeln("import type { TAtscriptTypeObject, TAtscriptTypeComplex, TAtscriptTypeFinal, TAtscriptTypeArray, TAtscriptAnnotatedType, TMetadataMap, Validator, TValidatorOptions } from \"@atscript/typescript/utils\"");
|
|
235
235
|
}
|
|
236
236
|
post() {
|
|
237
237
|
this.writeln("// prettier-ignore-end");
|
|
@@ -295,6 +295,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
295
295
|
patterns.push(prop);
|
|
296
296
|
continue;
|
|
297
297
|
}
|
|
298
|
+
if (this.isPhantomProp(prop.getDefinition())) {
|
|
299
|
+
this.writeln(`// ${prop.id}: phantom`);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
298
302
|
const optional = !!prop.token("optional");
|
|
299
303
|
this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
|
|
300
304
|
const renderedDef = this.renderTypeDefString(prop.getDefinition());
|
|
@@ -320,9 +324,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
320
324
|
}
|
|
321
325
|
if (asClass) {
|
|
322
326
|
this.writeln("static __is_atscript_annotated_type: true");
|
|
323
|
-
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
|
|
327
|
+
this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
|
|
324
328
|
this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
|
|
325
|
-
this.writeln(`static validator:
|
|
329
|
+
this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
|
|
330
|
+
if (resolveJsonSchemaMode(this.opts) === false) this.writeln("/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */");
|
|
326
331
|
this.writeln("static toJsonSchema: () => any");
|
|
327
332
|
}
|
|
328
333
|
this.pop();
|
|
@@ -337,6 +342,12 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
|
|
|
337
342
|
if (struct?.entity === "structure") this.renderStructure(struct, node.id);
|
|
338
343
|
else this.writeln("{}");
|
|
339
344
|
this.writeln();
|
|
345
|
+
const nsPrefix = exported ? "export declare" : "declare";
|
|
346
|
+
this.write(`${nsPrefix} namespace ${node.id} `);
|
|
347
|
+
this.blockln("{}");
|
|
348
|
+
this.writeln(`type DataType = ${node.id}`);
|
|
349
|
+
this.popln();
|
|
350
|
+
this.writeln();
|
|
340
351
|
}
|
|
341
352
|
renderType(node) {
|
|
342
353
|
this.writeln();
|
|
@@ -363,6 +374,11 @@ else this.writeln("{}");
|
|
|
363
374
|
} else {
|
|
364
375
|
this.write(exported ? "export declare " : "declare ");
|
|
365
376
|
this.writeln(`class ${node.id} extends ${targetName} {}`);
|
|
377
|
+
const nsPrefix = exported ? "export declare" : "declare";
|
|
378
|
+
this.write(`${nsPrefix} namespace ${node.id} `);
|
|
379
|
+
this.blockln("{}");
|
|
380
|
+
this.writeln(`type DataType = ${node.id}`);
|
|
381
|
+
this.popln();
|
|
366
382
|
this.writeln();
|
|
367
383
|
}
|
|
368
384
|
}
|
|
@@ -377,18 +393,29 @@ else this.writeln("{}");
|
|
|
377
393
|
let realDef = inputDef;
|
|
378
394
|
if ((0, __atscript_core.isRef)(inputDef)) realDef = this.doc.unwindType(inputDef.id, inputDef.chain)?.def || realDef;
|
|
379
395
|
realDef = this.doc.mergeIntersection(realDef);
|
|
380
|
-
if ((0, __atscript_core.isStructure)(realDef) || (0, __atscript_core.isInterface)(realDef)) typeDef = `TAtscriptTypeObject<keyof ${name}>`;
|
|
381
|
-
else if ((0, __atscript_core.isGroup)(realDef)) typeDef =
|
|
382
|
-
else if ((0, __atscript_core.isArray)(realDef)) typeDef =
|
|
383
|
-
else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef =
|
|
396
|
+
if ((0, __atscript_core.isStructure)(realDef) || (0, __atscript_core.isInterface)(realDef)) typeDef = `TAtscriptTypeObject<keyof ${name}, ${name}>`;
|
|
397
|
+
else if ((0, __atscript_core.isGroup)(realDef)) typeDef = `TAtscriptTypeComplex<${name}>`;
|
|
398
|
+
else if ((0, __atscript_core.isArray)(realDef)) typeDef = `TAtscriptTypeArray<${name}>`;
|
|
399
|
+
else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
|
|
384
400
|
}
|
|
401
|
+
this.writeln(`type DataType = ${name}`);
|
|
385
402
|
this.writeln(`const __is_atscript_annotated_type: true`);
|
|
386
403
|
this.writeln(`const type: ${typeDef}`);
|
|
387
404
|
this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
|
|
388
|
-
this.writeln(`const validator: (opts?: Partial<TValidatorOptions>) => Validator<
|
|
405
|
+
this.writeln(`const validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${name}, ${name}>`);
|
|
406
|
+
if (resolveJsonSchemaMode(this.opts) === false) this.writeln("/** @deprecated JSON Schema support is disabled. Calling this method will throw a runtime error. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add `@emit.jsonSchema` annotation to individual interfaces. */");
|
|
389
407
|
this.writeln("const toJsonSchema: () => any");
|
|
390
408
|
this.popln();
|
|
391
409
|
}
|
|
410
|
+
isPhantomProp(def) {
|
|
411
|
+
if (!def) return false;
|
|
412
|
+
if ((0, __atscript_core.isPrimitive)(def) && def.id === "phantom") return true;
|
|
413
|
+
if ((0, __atscript_core.isRef)(def)) {
|
|
414
|
+
const unwound = this.doc.unwindType(def.id, def.chain)?.def;
|
|
415
|
+
return (0, __atscript_core.isPrimitive)(unwound) && unwound.id === "phantom";
|
|
416
|
+
}
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
392
419
|
isTypeTarget(name, doc) {
|
|
393
420
|
const d = doc || this.doc;
|
|
394
421
|
const decl = d.getDeclarationOwnerNode(name);
|
|
@@ -427,6 +454,24 @@ function renderPrimitiveTypeDef(def) {
|
|
|
427
454
|
}
|
|
428
455
|
}
|
|
429
456
|
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region packages/typescript/src/traverse.ts
|
|
459
|
+
function forAnnotatedType(def, handlers) {
|
|
460
|
+
switch (def.type.kind) {
|
|
461
|
+
case "": {
|
|
462
|
+
const typed = def;
|
|
463
|
+
if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
|
|
464
|
+
return handlers.final(typed);
|
|
465
|
+
}
|
|
466
|
+
case "object": return handlers.object(def);
|
|
467
|
+
case "array": return handlers.array(def);
|
|
468
|
+
case "union": return handlers.union(def);
|
|
469
|
+
case "intersection": return handlers.intersection(def);
|
|
470
|
+
case "tuple": return handlers.tuple(def);
|
|
471
|
+
default: throw new Error(`Unknown type kind "${def.type.kind}"`);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
430
475
|
//#endregion
|
|
431
476
|
//#region packages/typescript/src/validator.ts
|
|
432
477
|
function _define_property$1(obj, key, value) {
|
|
@@ -472,7 +517,17 @@ var Validator = class {
|
|
|
472
517
|
throw() {
|
|
473
518
|
throw new ValidatorError(this.errors);
|
|
474
519
|
}
|
|
475
|
-
|
|
520
|
+
/**
|
|
521
|
+
* Validates a value against the type definition.
|
|
522
|
+
*
|
|
523
|
+
* Acts as a TypeScript type guard — when it returns `true`, the value
|
|
524
|
+
* is narrowed to `DataType`.
|
|
525
|
+
*
|
|
526
|
+
* @param value - The value to validate.
|
|
527
|
+
* @param safe - If `true`, returns `false` on failure instead of throwing.
|
|
528
|
+
* @returns `true` if the value matches the type definition.
|
|
529
|
+
* @throws {ValidatorError} When validation fails and `safe` is not `true`.
|
|
530
|
+
*/ validate(value, safe) {
|
|
476
531
|
this.push("");
|
|
477
532
|
this.errors = [];
|
|
478
533
|
this.stackErrors = [];
|
|
@@ -499,15 +554,15 @@ var Validator = class {
|
|
|
499
554
|
return this.stackPath.slice(1).join(".");
|
|
500
555
|
}
|
|
501
556
|
validateAnnotatedType(def, value) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
557
|
+
return forAnnotatedType(def, {
|
|
558
|
+
final: (d) => this.validatePrimitive(d, value),
|
|
559
|
+
phantom: () => true,
|
|
560
|
+
object: (d) => this.validateObject(d, value),
|
|
561
|
+
array: (d) => this.validateArray(d, value),
|
|
562
|
+
union: (d) => this.validateUnion(d, value),
|
|
563
|
+
intersection: (d) => this.validateIntersection(d, value),
|
|
564
|
+
tuple: (d) => this.validateTuple(d, value)
|
|
565
|
+
});
|
|
511
566
|
}
|
|
512
567
|
validateUnion(def, value) {
|
|
513
568
|
let i = 0;
|
|
@@ -598,7 +653,7 @@ var Validator = class {
|
|
|
598
653
|
let partialFunctionMatched = false;
|
|
599
654
|
if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
|
|
600
655
|
for (const [key, item] of def.type.props.entries()) {
|
|
601
|
-
if (skipList.has(key)) continue;
|
|
656
|
+
if (skipList.has(key) || isPhantomType(item)) continue;
|
|
602
657
|
typeKeys.add(key);
|
|
603
658
|
if (value[key] === undefined) {
|
|
604
659
|
if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
|
|
@@ -739,7 +794,7 @@ else {
|
|
|
739
794
|
constructor(def, opts) {
|
|
740
795
|
_define_property$1(this, "def", void 0);
|
|
741
796
|
_define_property$1(this, "opts", void 0);
|
|
742
|
-
_define_property$1(this, "errors", void 0);
|
|
797
|
+
/** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
|
|
743
798
|
_define_property$1(this, "stackErrors", void 0);
|
|
744
799
|
_define_property$1(this, "stackPath", void 0);
|
|
745
800
|
this.def = def;
|
|
@@ -880,19 +935,24 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
|
|
|
880
935
|
};
|
|
881
936
|
return handle;
|
|
882
937
|
}
|
|
938
|
+
function isPhantomType(def) {
|
|
939
|
+
return def.type.kind === "" && def.type.designType === "phantom";
|
|
940
|
+
}
|
|
883
941
|
|
|
884
942
|
//#endregion
|
|
885
943
|
//#region packages/typescript/src/json-schema.ts
|
|
886
944
|
function buildJsonSchema(type) {
|
|
887
945
|
const build = (def) => {
|
|
888
|
-
const t = def.type;
|
|
889
946
|
const meta = def.metadata;
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
947
|
+
return forAnnotatedType(def, {
|
|
948
|
+
phantom() {
|
|
949
|
+
return {};
|
|
950
|
+
},
|
|
951
|
+
object(d) {
|
|
893
952
|
const properties = {};
|
|
894
953
|
const required = [];
|
|
895
|
-
for (const [key, val] of
|
|
954
|
+
for (const [key, val] of d.type.props.entries()) {
|
|
955
|
+
if (isPhantomType(val)) continue;
|
|
896
956
|
properties[key] = build(val);
|
|
897
957
|
if (!val.optional) required.push(key);
|
|
898
958
|
}
|
|
@@ -902,41 +962,36 @@ function buildJsonSchema(type) {
|
|
|
902
962
|
};
|
|
903
963
|
if (required.length) schema.required = required;
|
|
904
964
|
return schema;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
const arr = t;
|
|
965
|
+
},
|
|
966
|
+
array(d) {
|
|
908
967
|
const schema = {
|
|
909
968
|
type: "array",
|
|
910
|
-
items: build(
|
|
969
|
+
items: build(d.type.of)
|
|
911
970
|
};
|
|
912
971
|
const minLength = meta.get("expect.minLength");
|
|
913
972
|
if (typeof minLength === "number") schema.minItems = minLength;
|
|
914
973
|
const maxLength = meta.get("expect.maxLength");
|
|
915
974
|
if (typeof maxLength === "number") schema.maxItems = maxLength;
|
|
916
975
|
return schema;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
-
case "tuple": {
|
|
927
|
-
const grp = t;
|
|
976
|
+
},
|
|
977
|
+
union(d) {
|
|
978
|
+
return { anyOf: d.type.items.map(build) };
|
|
979
|
+
},
|
|
980
|
+
intersection(d) {
|
|
981
|
+
return { allOf: d.type.items.map(build) };
|
|
982
|
+
},
|
|
983
|
+
tuple(d) {
|
|
928
984
|
return {
|
|
929
985
|
type: "array",
|
|
930
|
-
items:
|
|
986
|
+
items: d.type.items.map(build),
|
|
931
987
|
additionalItems: false
|
|
932
988
|
};
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
const fin = t;
|
|
989
|
+
},
|
|
990
|
+
final(d) {
|
|
936
991
|
const schema = {};
|
|
937
|
-
if (
|
|
938
|
-
if (
|
|
939
|
-
schema.type =
|
|
992
|
+
if (d.type.value !== undefined) schema.const = d.type.value;
|
|
993
|
+
if (d.type.designType && d.type.designType !== "any") {
|
|
994
|
+
schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
|
|
940
995
|
if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
|
|
941
996
|
}
|
|
942
997
|
if (schema.type === "string") {
|
|
@@ -956,8 +1011,7 @@ else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern:
|
|
|
956
1011
|
}
|
|
957
1012
|
return schema;
|
|
958
1013
|
}
|
|
959
|
-
|
|
960
|
-
}
|
|
1014
|
+
});
|
|
961
1015
|
};
|
|
962
1016
|
return build(type);
|
|
963
1017
|
}
|
|
@@ -979,7 +1033,7 @@ var JsRenderer = class extends BaseRenderer {
|
|
|
979
1033
|
this.writeln("// prettier-ignore-start");
|
|
980
1034
|
this.writeln("/* eslint-disable */");
|
|
981
1035
|
const imports = ["defineAnnotatedType as $", "annotate as $a"];
|
|
982
|
-
if (
|
|
1036
|
+
if (resolveJsonSchemaMode(this.opts) === "lazy") imports.push("buildJsonSchema as $$");
|
|
983
1037
|
this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
|
|
984
1038
|
}
|
|
985
1039
|
buildAdHocMap(annotateNodes) {
|
|
@@ -1067,15 +1121,21 @@ else {
|
|
|
1067
1121
|
this.writeln();
|
|
1068
1122
|
}
|
|
1069
1123
|
renderJsonSchemaMethod(node) {
|
|
1070
|
-
|
|
1124
|
+
const mode = resolveJsonSchemaMode(this.opts);
|
|
1125
|
+
const hasAnnotation = node.countAnnotations("emit.jsonSchema") > 0;
|
|
1126
|
+
if (hasAnnotation || mode === "bundle") {
|
|
1071
1127
|
const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
|
|
1072
1128
|
this.writeln("static toJsonSchema() {");
|
|
1073
1129
|
this.indent().writeln(`return ${schema}`).unindent();
|
|
1074
1130
|
this.writeln("}");
|
|
1075
|
-
} else {
|
|
1131
|
+
} else if (mode === "lazy") {
|
|
1076
1132
|
this.writeln("static toJsonSchema() {");
|
|
1077
1133
|
this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
|
|
1078
1134
|
this.writeln("}");
|
|
1135
|
+
} else {
|
|
1136
|
+
this.writeln("static toJsonSchema() {");
|
|
1137
|
+
this.indent().writeln("throw new Error(\"JSON Schema support is disabled. To enable, set `jsonSchema: 'lazy'` or `jsonSchema: 'bundle'` in tsPlugin options, or add @emit.jsonSchema annotation to individual interfaces.\")").unindent();
|
|
1138
|
+
this.writeln("}");
|
|
1079
1139
|
}
|
|
1080
1140
|
}
|
|
1081
1141
|
toAnnotatedType(node) {
|
|
@@ -1109,7 +1169,7 @@ else {
|
|
|
1109
1169
|
case "primitive": {
|
|
1110
1170
|
const prim = node;
|
|
1111
1171
|
const handle = defineAnnotatedType();
|
|
1112
|
-
handle.designType(prim.id === "never" ? "never" : prim.config.type);
|
|
1172
|
+
handle.designType(prim.id === "never" ? "never" : prim.id === "phantom" ? "phantom" : prim.config.type);
|
|
1113
1173
|
if (!skipAnnotations) this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
|
|
1114
1174
|
return handle;
|
|
1115
1175
|
}
|
|
@@ -1236,7 +1296,7 @@ else handle.prop(prop.id, propHandle.$type);
|
|
|
1236
1296
|
return this;
|
|
1237
1297
|
}
|
|
1238
1298
|
definePrimitive(node, name) {
|
|
1239
|
-
this.renderPrimitiveDef(node.id === "never" ? "never" : node.config.type, name);
|
|
1299
|
+
this.renderPrimitiveDef(node.id === "never" ? "never" : node.id === "phantom" ? "phantom" : node.config.type, name);
|
|
1240
1300
|
this.writeln(` .tags(${Array.from(node.tags).map((f) => `"${escapeQuotes(f)}"`).join(", ")})`);
|
|
1241
1301
|
return this;
|
|
1242
1302
|
}
|
|
@@ -1518,6 +1578,10 @@ else {
|
|
|
1518
1578
|
|
|
1519
1579
|
//#endregion
|
|
1520
1580
|
//#region packages/typescript/src/plugin.ts
|
|
1581
|
+
function resolveJsonSchemaMode(opts) {
|
|
1582
|
+
if (opts?.jsonSchema !== undefined) return opts.jsonSchema;
|
|
1583
|
+
return false;
|
|
1584
|
+
}
|
|
1521
1585
|
const tsPlugin = (opts) => {
|
|
1522
1586
|
return {
|
|
1523
1587
|
name: "typesccript",
|