@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 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, TAtscriptAnnotatedTypeConstructor, TValidatorOptions } from \"@atscript/typescript/utils\"");
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: <TT extends TAtscriptAnnotatedTypeConstructor = typeof ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
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 = "TAtscriptTypeComplex";
385
- else if ((0, __atscript_core.isArray)(realDef)) typeDef = "TAtscriptTypeArray";
386
- else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = "TAtscriptTypeFinal";
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<TAtscriptAnnotatedTypeConstructor, ${name}>`);
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
- validate(value, safe) {
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
- switch (def.type.kind) {
506
- case "object": return this.validateObject(def, value);
507
- case "union": return this.validateUnion(def, value);
508
- case "intersection": return this.validateIntersection(def, value);
509
- case "tuple": return this.validateTuple(def, value);
510
- case "array": return this.validateArray(def, value);
511
- case "": return this.validatePrimitive(def, value);
512
- default: throw new Error(`Unknown type "${def.type.kind}"`);
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
- switch (t.kind) {
894
- case "object": {
895
- const obj = t;
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 obj.props.entries()) {
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
- case "array": {
910
- const arr = t;
968
+ },
969
+ array(d) {
911
970
  const schema = {
912
971
  type: "array",
913
- items: build$1(arr.of)
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
- case "union": {
922
- const grp = t;
923
- return { anyOf: grp.items.map(build$1) };
924
- }
925
- case "intersection": {
926
- const grp = t;
927
- return { allOf: grp.items.map(build$1) };
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: grp.items.map(build$1),
989
+ items: d.type.items.map(build$1),
934
990
  additionalItems: false
935
991
  };
936
- }
937
- case "": {
938
- const fin = t;
992
+ },
993
+ final(d) {
939
994
  const schema = {};
940
- if (fin.value !== undefined) schema.const = fin.value;
941
- if (fin.designType && fin.designType !== "any") {
942
- schema.type = fin.designType === "undefined" ? "null" : fin.designType;
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
- default: return {};
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 (!this.opts?.preRenderJsonSchema) imports.push("buildJsonSchema as $$");
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
- if (this.opts?.preRenderJsonSchema) {
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, TAtscriptAnnotatedTypeConstructor, TValidatorOptions } from \"@atscript/typescript/utils\"");
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: <TT extends TAtscriptAnnotatedTypeConstructor = typeof ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
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 = "TAtscriptTypeComplex";
382
- else if ((0, __atscript_core.isArray)(realDef)) typeDef = "TAtscriptTypeArray";
383
- else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = "TAtscriptTypeFinal";
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<TAtscriptAnnotatedTypeConstructor, ${name}>`);
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
- validate(value, safe) {
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
- switch (def.type.kind) {
503
- case "object": return this.validateObject(def, value);
504
- case "union": return this.validateUnion(def, value);
505
- case "intersection": return this.validateIntersection(def, value);
506
- case "tuple": return this.validateTuple(def, value);
507
- case "array": return this.validateArray(def, value);
508
- case "": return this.validatePrimitive(def, value);
509
- default: throw new Error(`Unknown type "${def.type.kind}"`);
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
- switch (t.kind) {
891
- case "object": {
892
- const obj = t;
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 obj.props.entries()) {
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
- case "array": {
907
- const arr = t;
965
+ },
966
+ array(d) {
908
967
  const schema = {
909
968
  type: "array",
910
- items: build(arr.of)
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
- case "union": {
919
- const grp = t;
920
- return { anyOf: grp.items.map(build) };
921
- }
922
- case "intersection": {
923
- const grp = t;
924
- return { allOf: grp.items.map(build) };
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: grp.items.map(build),
986
+ items: d.type.items.map(build),
931
987
  additionalItems: false
932
988
  };
933
- }
934
- case "": {
935
- const fin = t;
989
+ },
990
+ final(d) {
936
991
  const schema = {};
937
- if (fin.value !== undefined) schema.const = fin.value;
938
- if (fin.designType && fin.designType !== "any") {
939
- schema.type = fin.designType === "undefined" ? "null" : fin.designType;
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
- default: return {};
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 (!this.opts?.preRenderJsonSchema) imports.push("buildJsonSchema as $$");
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
- if (this.opts?.preRenderJsonSchema) {
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",