@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/index.d.ts CHANGED
@@ -2,9 +2,15 @@ import { TAtscriptPlugin } from '@atscript/core';
2
2
 
3
3
  interface TTsPluginOptions {
4
4
  /**
5
- * Render JSON schemas at build-time
5
+ * JSON Schema support mode:
6
+ * - `false` — No support. `toJsonSchema()` throws at runtime. No `buildJsonSchema` import. *(default)*
7
+ * - `'lazy'` — Import `buildJsonSchema`, compute on demand, cache in type object.
8
+ * - `'bundle'` — Pre-compute at build time, embed static JSON in output. No import.
9
+ *
10
+ * Individual interfaces can override this with `@emit.jsonSchema` annotation
11
+ * to force build-time embedding regardless of this setting.
6
12
  */
7
- preRenderJsonSchema?: boolean;
13
+ jsonSchema?: false | 'lazy' | 'bundle';
8
14
  }
9
15
  declare const tsPlugin: (opts?: TTsPluginOptions) => TAtscriptPlugin;
10
16
 
package/dist/index.mjs CHANGED
@@ -207,7 +207,7 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
207
207
  this.writeln(" * Do not edit this file!");
208
208
  this.writeln(" */");
209
209
  this.writeln();
210
- this.writeln("import type { TAtscriptTypeObject, TAtscriptTypeComplex, TAtscriptTypeFinal, TAtscriptTypeArray, TMetadataMap, Validator, TAtscriptAnnotatedTypeConstructor, TValidatorOptions } from \"@atscript/typescript/utils\"");
210
+ this.writeln("import type { TAtscriptTypeObject, TAtscriptTypeComplex, TAtscriptTypeFinal, TAtscriptTypeArray, TAtscriptAnnotatedType, TMetadataMap, Validator, TValidatorOptions } from \"@atscript/typescript/utils\"");
211
211
  }
212
212
  post() {
213
213
  this.writeln("// prettier-ignore-end");
@@ -271,6 +271,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
271
271
  patterns.push(prop);
272
272
  continue;
273
273
  }
274
+ if (this.isPhantomProp(prop.getDefinition())) {
275
+ this.writeln(`// ${prop.id}: phantom`);
276
+ continue;
277
+ }
274
278
  const optional = !!prop.token("optional");
275
279
  this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
276
280
  const renderedDef = this.renderTypeDefString(prop.getDefinition());
@@ -296,9 +300,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
296
300
  }
297
301
  if (asClass) {
298
302
  this.writeln("static __is_atscript_annotated_type: true");
299
- this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
303
+ this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
300
304
  this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
301
- this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = typeof ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
305
+ this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
306
+ 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. */");
302
307
  this.writeln("static toJsonSchema: () => any");
303
308
  }
304
309
  this.pop();
@@ -313,6 +318,12 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
313
318
  if (struct?.entity === "structure") this.renderStructure(struct, node.id);
314
319
  else this.writeln("{}");
315
320
  this.writeln();
321
+ const nsPrefix = exported ? "export declare" : "declare";
322
+ this.write(`${nsPrefix} namespace ${node.id} `);
323
+ this.blockln("{}");
324
+ this.writeln(`type DataType = ${node.id}`);
325
+ this.popln();
326
+ this.writeln();
316
327
  }
317
328
  renderType(node) {
318
329
  this.writeln();
@@ -339,6 +350,11 @@ else this.writeln("{}");
339
350
  } else {
340
351
  this.write(exported ? "export declare " : "declare ");
341
352
  this.writeln(`class ${node.id} extends ${targetName} {}`);
353
+ const nsPrefix = exported ? "export declare" : "declare";
354
+ this.write(`${nsPrefix} namespace ${node.id} `);
355
+ this.blockln("{}");
356
+ this.writeln(`type DataType = ${node.id}`);
357
+ this.popln();
342
358
  this.writeln();
343
359
  }
344
360
  }
@@ -353,18 +369,29 @@ else this.writeln("{}");
353
369
  let realDef = inputDef;
354
370
  if (isRef(inputDef)) realDef = this.doc.unwindType(inputDef.id, inputDef.chain)?.def || realDef;
355
371
  realDef = this.doc.mergeIntersection(realDef);
356
- if (isStructure(realDef) || isInterface(realDef)) typeDef = `TAtscriptTypeObject<keyof ${name}>`;
357
- else if (isGroup(realDef)) typeDef = "TAtscriptTypeComplex";
358
- else if (isArray(realDef)) typeDef = "TAtscriptTypeArray";
359
- else if (isPrimitive(realDef)) typeDef = "TAtscriptTypeFinal";
372
+ if (isStructure(realDef) || isInterface(realDef)) typeDef = `TAtscriptTypeObject<keyof ${name}, ${name}>`;
373
+ else if (isGroup(realDef)) typeDef = `TAtscriptTypeComplex<${name}>`;
374
+ else if (isArray(realDef)) typeDef = `TAtscriptTypeArray<${name}>`;
375
+ else if (isPrimitive(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
360
376
  }
377
+ this.writeln(`type DataType = ${name}`);
361
378
  this.writeln(`const __is_atscript_annotated_type: true`);
362
379
  this.writeln(`const type: ${typeDef}`);
363
380
  this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
364
- this.writeln(`const validator: (opts?: Partial<TValidatorOptions>) => Validator<TAtscriptAnnotatedTypeConstructor, ${name}>`);
381
+ this.writeln(`const validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${name}, ${name}>`);
382
+ 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. */");
365
383
  this.writeln("const toJsonSchema: () => any");
366
384
  this.popln();
367
385
  }
386
+ isPhantomProp(def) {
387
+ if (!def) return false;
388
+ if (isPrimitive(def) && def.id === "phantom") return true;
389
+ if (isRef(def)) {
390
+ const unwound = this.doc.unwindType(def.id, def.chain)?.def;
391
+ return isPrimitive(unwound) && unwound.id === "phantom";
392
+ }
393
+ return false;
394
+ }
368
395
  isTypeTarget(name, doc) {
369
396
  const d = doc || this.doc;
370
397
  const decl = d.getDeclarationOwnerNode(name);
@@ -403,6 +430,24 @@ function renderPrimitiveTypeDef(def) {
403
430
  }
404
431
  }
405
432
 
433
+ //#endregion
434
+ //#region packages/typescript/src/traverse.ts
435
+ function forAnnotatedType(def, handlers) {
436
+ switch (def.type.kind) {
437
+ case "": {
438
+ const typed = def;
439
+ if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
440
+ return handlers.final(typed);
441
+ }
442
+ case "object": return handlers.object(def);
443
+ case "array": return handlers.array(def);
444
+ case "union": return handlers.union(def);
445
+ case "intersection": return handlers.intersection(def);
446
+ case "tuple": return handlers.tuple(def);
447
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
448
+ }
449
+ }
450
+
406
451
  //#endregion
407
452
  //#region packages/typescript/src/validator.ts
408
453
  function _define_property$1(obj, key, value) {
@@ -448,7 +493,17 @@ var Validator = class {
448
493
  throw() {
449
494
  throw new ValidatorError(this.errors);
450
495
  }
451
- validate(value, safe) {
496
+ /**
497
+ * Validates a value against the type definition.
498
+ *
499
+ * Acts as a TypeScript type guard — when it returns `true`, the value
500
+ * is narrowed to `DataType`.
501
+ *
502
+ * @param value - The value to validate.
503
+ * @param safe - If `true`, returns `false` on failure instead of throwing.
504
+ * @returns `true` if the value matches the type definition.
505
+ * @throws {ValidatorError} When validation fails and `safe` is not `true`.
506
+ */ validate(value, safe) {
452
507
  this.push("");
453
508
  this.errors = [];
454
509
  this.stackErrors = [];
@@ -475,15 +530,15 @@ var Validator = class {
475
530
  return this.stackPath.slice(1).join(".");
476
531
  }
477
532
  validateAnnotatedType(def, value) {
478
- switch (def.type.kind) {
479
- case "object": return this.validateObject(def, value);
480
- case "union": return this.validateUnion(def, value);
481
- case "intersection": return this.validateIntersection(def, value);
482
- case "tuple": return this.validateTuple(def, value);
483
- case "array": return this.validateArray(def, value);
484
- case "": return this.validatePrimitive(def, value);
485
- default: throw new Error(`Unknown type "${def.type.kind}"`);
486
- }
533
+ return forAnnotatedType(def, {
534
+ final: (d) => this.validatePrimitive(d, value),
535
+ phantom: () => true,
536
+ object: (d) => this.validateObject(d, value),
537
+ array: (d) => this.validateArray(d, value),
538
+ union: (d) => this.validateUnion(d, value),
539
+ intersection: (d) => this.validateIntersection(d, value),
540
+ tuple: (d) => this.validateTuple(d, value)
541
+ });
487
542
  }
488
543
  validateUnion(def, value) {
489
544
  let i = 0;
@@ -574,7 +629,7 @@ var Validator = class {
574
629
  let partialFunctionMatched = false;
575
630
  if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
576
631
  for (const [key, item] of def.type.props.entries()) {
577
- if (skipList.has(key)) continue;
632
+ if (skipList.has(key) || isPhantomType(item)) continue;
578
633
  typeKeys.add(key);
579
634
  if (value[key] === undefined) {
580
635
  if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
@@ -715,7 +770,7 @@ else {
715
770
  constructor(def, opts) {
716
771
  _define_property$1(this, "def", void 0);
717
772
  _define_property$1(this, "opts", void 0);
718
- _define_property$1(this, "errors", void 0);
773
+ /** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
719
774
  _define_property$1(this, "stackErrors", void 0);
720
775
  _define_property$1(this, "stackPath", void 0);
721
776
  this.def = def;
@@ -856,19 +911,24 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
856
911
  };
857
912
  return handle;
858
913
  }
914
+ function isPhantomType(def) {
915
+ return def.type.kind === "" && def.type.designType === "phantom";
916
+ }
859
917
 
860
918
  //#endregion
861
919
  //#region packages/typescript/src/json-schema.ts
862
920
  function buildJsonSchema(type) {
863
921
  const build = (def) => {
864
- const t = def.type;
865
922
  const meta = def.metadata;
866
- switch (t.kind) {
867
- case "object": {
868
- const obj = t;
923
+ return forAnnotatedType(def, {
924
+ phantom() {
925
+ return {};
926
+ },
927
+ object(d) {
869
928
  const properties = {};
870
929
  const required = [];
871
- for (const [key, val] of obj.props.entries()) {
930
+ for (const [key, val] of d.type.props.entries()) {
931
+ if (isPhantomType(val)) continue;
872
932
  properties[key] = build(val);
873
933
  if (!val.optional) required.push(key);
874
934
  }
@@ -878,41 +938,36 @@ function buildJsonSchema(type) {
878
938
  };
879
939
  if (required.length) schema.required = required;
880
940
  return schema;
881
- }
882
- case "array": {
883
- const arr = t;
941
+ },
942
+ array(d) {
884
943
  const schema = {
885
944
  type: "array",
886
- items: build(arr.of)
945
+ items: build(d.type.of)
887
946
  };
888
947
  const minLength = meta.get("expect.minLength");
889
948
  if (typeof minLength === "number") schema.minItems = minLength;
890
949
  const maxLength = meta.get("expect.maxLength");
891
950
  if (typeof maxLength === "number") schema.maxItems = maxLength;
892
951
  return schema;
893
- }
894
- case "union": {
895
- const grp = t;
896
- return { anyOf: grp.items.map(build) };
897
- }
898
- case "intersection": {
899
- const grp = t;
900
- return { allOf: grp.items.map(build) };
901
- }
902
- case "tuple": {
903
- const grp = t;
952
+ },
953
+ union(d) {
954
+ return { anyOf: d.type.items.map(build) };
955
+ },
956
+ intersection(d) {
957
+ return { allOf: d.type.items.map(build) };
958
+ },
959
+ tuple(d) {
904
960
  return {
905
961
  type: "array",
906
- items: grp.items.map(build),
962
+ items: d.type.items.map(build),
907
963
  additionalItems: false
908
964
  };
909
- }
910
- case "": {
911
- const fin = t;
965
+ },
966
+ final(d) {
912
967
  const schema = {};
913
- if (fin.value !== undefined) schema.const = fin.value;
914
- if (fin.designType && fin.designType !== "any") {
915
- schema.type = fin.designType === "undefined" ? "null" : fin.designType;
968
+ if (d.type.value !== undefined) schema.const = d.type.value;
969
+ if (d.type.designType && d.type.designType !== "any") {
970
+ schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
916
971
  if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
917
972
  }
918
973
  if (schema.type === "string") {
@@ -932,8 +987,7 @@ else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern:
932
987
  }
933
988
  return schema;
934
989
  }
935
- default: return {};
936
- }
990
+ });
937
991
  };
938
992
  return build(type);
939
993
  }
@@ -955,7 +1009,7 @@ var JsRenderer = class extends BaseRenderer {
955
1009
  this.writeln("// prettier-ignore-start");
956
1010
  this.writeln("/* eslint-disable */");
957
1011
  const imports = ["defineAnnotatedType as $", "annotate as $a"];
958
- if (!this.opts?.preRenderJsonSchema) imports.push("buildJsonSchema as $$");
1012
+ if (resolveJsonSchemaMode(this.opts) === "lazy") imports.push("buildJsonSchema as $$");
959
1013
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
960
1014
  }
961
1015
  buildAdHocMap(annotateNodes) {
@@ -1043,15 +1097,21 @@ else {
1043
1097
  this.writeln();
1044
1098
  }
1045
1099
  renderJsonSchemaMethod(node) {
1046
- if (this.opts?.preRenderJsonSchema) {
1100
+ const mode = resolveJsonSchemaMode(this.opts);
1101
+ const hasAnnotation = node.countAnnotations("emit.jsonSchema") > 0;
1102
+ if (hasAnnotation || mode === "bundle") {
1047
1103
  const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
1048
1104
  this.writeln("static toJsonSchema() {");
1049
1105
  this.indent().writeln(`return ${schema}`).unindent();
1050
1106
  this.writeln("}");
1051
- } else {
1107
+ } else if (mode === "lazy") {
1052
1108
  this.writeln("static toJsonSchema() {");
1053
1109
  this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
1054
1110
  this.writeln("}");
1111
+ } else {
1112
+ this.writeln("static toJsonSchema() {");
1113
+ 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();
1114
+ this.writeln("}");
1055
1115
  }
1056
1116
  }
1057
1117
  toAnnotatedType(node) {
@@ -1085,7 +1145,7 @@ else {
1085
1145
  case "primitive": {
1086
1146
  const prim = node;
1087
1147
  const handle = defineAnnotatedType();
1088
- handle.designType(prim.id === "never" ? "never" : prim.config.type);
1148
+ handle.designType(prim.id === "never" ? "never" : prim.id === "phantom" ? "phantom" : prim.config.type);
1089
1149
  if (!skipAnnotations) this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1090
1150
  return handle;
1091
1151
  }
@@ -1212,7 +1272,7 @@ else handle.prop(prop.id, propHandle.$type);
1212
1272
  return this;
1213
1273
  }
1214
1274
  definePrimitive(node, name) {
1215
- this.renderPrimitiveDef(node.id === "never" ? "never" : node.config.type, name);
1275
+ this.renderPrimitiveDef(node.id === "never" ? "never" : node.id === "phantom" ? "phantom" : node.config.type, name);
1216
1276
  this.writeln(` .tags(${Array.from(node.tags).map((f) => `"${escapeQuotes(f)}"`).join(", ")})`);
1217
1277
  return this;
1218
1278
  }
@@ -1494,6 +1554,10 @@ else {
1494
1554
 
1495
1555
  //#endregion
1496
1556
  //#region packages/typescript/src/plugin.ts
1557
+ function resolveJsonSchemaMode(opts) {
1558
+ if (opts?.jsonSchema !== undefined) return opts.jsonSchema;
1559
+ return false;
1560
+ }
1497
1561
  const tsPlugin = (opts) => {
1498
1562
  return {
1499
1563
  name: "typesccript",