@atscript/typescript 0.1.1 → 0.1.3

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.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");
@@ -320,9 +320,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
320
320
  }
321
321
  if (asClass) {
322
322
  this.writeln("static __is_atscript_annotated_type: true");
323
- this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
323
+ this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
324
324
  this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
325
- this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
325
+ this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
326
+ 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
327
  this.writeln("static toJsonSchema: () => any");
327
328
  }
328
329
  this.pop();
@@ -337,6 +338,12 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
337
338
  if (struct?.entity === "structure") this.renderStructure(struct, node.id);
338
339
  else this.writeln("{}");
339
340
  this.writeln();
341
+ const nsPrefix = exported ? "export declare" : "declare";
342
+ this.write(`${nsPrefix} namespace ${node.id} `);
343
+ this.blockln("{}");
344
+ this.writeln(`type DataType = ${node.id}`);
345
+ this.popln();
346
+ this.writeln();
340
347
  }
341
348
  renderType(node) {
342
349
  this.writeln();
@@ -351,44 +358,60 @@ else this.writeln("{}");
351
358
  renderAnnotate(node) {
352
359
  if (node.isMutating) return;
353
360
  const targetName = node.targetName;
354
- const unwound = this.doc.unwindType(targetName);
355
- if (!unwound?.def) return;
356
- const def = this.doc.mergeIntersection(unwound.def);
357
361
  this.writeln();
358
362
  const exported = node.token("export")?.text === "export";
359
363
  this.renderJsDoc(node);
360
- if ((0, __atscript_core.isStructure)(def) || (0, __atscript_core.isInterface)(def)) {
361
- this.write(exported ? "export declare " : "declare ");
362
- this.write(`class ${node.id} `);
363
- this.renderStructure(def, node.id);
364
- } else {
364
+ if (this.isTypeTarget(targetName)) {
365
365
  this.write(exported ? "export " : "declare ");
366
- this.write(`type ${node.id} = `);
367
- this.renderTypeDef(def);
366
+ this.write(`type ${node.id} = ${targetName}`);
367
+ this.writeln();
368
+ const unwound = this.doc.unwindType(targetName);
369
+ this.renderTypeNamespaceFor(node.id, unwound?.def);
370
+ } else {
371
+ this.write(exported ? "export declare " : "declare ");
372
+ this.writeln(`class ${node.id} extends ${targetName} {}`);
373
+ const nsPrefix = exported ? "export declare" : "declare";
374
+ this.write(`${nsPrefix} namespace ${node.id} `);
375
+ this.blockln("{}");
376
+ this.writeln(`type DataType = ${node.id}`);
377
+ this.popln();
378
+ this.writeln();
368
379
  }
369
- this.writeln();
370
380
  }
371
381
  renderTypeNamespace(node) {
372
- this.write(`declare namespace ${node.id} `);
382
+ this.renderTypeNamespaceFor(node.id, node.getDefinition());
383
+ }
384
+ renderTypeNamespaceFor(name, inputDef) {
385
+ this.write(`declare namespace ${name} `);
373
386
  this.blockln("{}");
374
- const def = node.getDefinition();
375
387
  let typeDef = "TAtscriptTypeDef";
376
- if (def) {
377
- let realDef = def;
378
- if ((0, __atscript_core.isRef)(def)) realDef = this.doc.unwindType(def.id, def.chain)?.def || realDef;
388
+ if (inputDef) {
389
+ let realDef = inputDef;
390
+ if ((0, __atscript_core.isRef)(inputDef)) realDef = this.doc.unwindType(inputDef.id, inputDef.chain)?.def || realDef;
379
391
  realDef = this.doc.mergeIntersection(realDef);
380
- if ((0, __atscript_core.isStructure)(realDef) || (0, __atscript_core.isInterface)(realDef)) typeDef = `TAtscriptTypeObject<keyof ${node.id}>`;
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";
392
+ if ((0, __atscript_core.isStructure)(realDef) || (0, __atscript_core.isInterface)(realDef)) typeDef = `TAtscriptTypeObject<keyof ${name}, ${name}>`;
393
+ else if ((0, __atscript_core.isGroup)(realDef)) typeDef = `TAtscriptTypeComplex<${name}>`;
394
+ else if ((0, __atscript_core.isArray)(realDef)) typeDef = `TAtscriptTypeArray<${name}>`;
395
+ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
384
396
  }
397
+ this.writeln(`type DataType = ${name}`);
385
398
  this.writeln(`const __is_atscript_annotated_type: true`);
386
399
  this.writeln(`const type: ${typeDef}`);
387
400
  this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
388
- this.writeln(`const validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
401
+ this.writeln(`const validator: (opts?: Partial<TValidatorOptions>) => Validator<TAtscriptAnnotatedType, ${name}>`);
402
+ 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
403
  this.writeln("const toJsonSchema: () => any");
390
404
  this.popln();
391
405
  }
406
+ isTypeTarget(name, doc) {
407
+ const d = doc || this.doc;
408
+ const decl = d.getDeclarationOwnerNode(name);
409
+ if (!decl?.node) return false;
410
+ if (decl.node.entity === "type") return true;
411
+ if (decl.node.entity === "interface") return false;
412
+ if (decl.node.entity === "annotate") return this.isTypeTarget(decl.node.targetName, decl.doc);
413
+ return false;
414
+ }
392
415
  renderJsDoc(node) {
393
416
  const range = node.token("identifier")?.range;
394
417
  const rangeStr = range ? `:${range.start.line + 1}:${range.start.character + 1}` : "";
@@ -418,6 +441,20 @@ function renderPrimitiveTypeDef(def) {
418
441
  }
419
442
  }
420
443
 
444
+ //#endregion
445
+ //#region packages/typescript/src/traverse.ts
446
+ function forAnnotatedType(def, handlers) {
447
+ switch (def.type.kind) {
448
+ case "": return handlers.final(def);
449
+ case "object": return handlers.object(def);
450
+ case "array": return handlers.array(def);
451
+ case "union": return handlers.union(def);
452
+ case "intersection": return handlers.intersection(def);
453
+ case "tuple": return handlers.tuple(def);
454
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
455
+ }
456
+ }
457
+
421
458
  //#endregion
422
459
  //#region packages/typescript/src/validator.ts
423
460
  function _define_property$1(obj, key, value) {
@@ -463,7 +500,17 @@ var Validator = class {
463
500
  throw() {
464
501
  throw new ValidatorError(this.errors);
465
502
  }
466
- validate(value, safe) {
503
+ /**
504
+ * Validates a value against the type definition.
505
+ *
506
+ * Acts as a TypeScript type guard — when it returns `true`, the value
507
+ * is narrowed to `DataType`.
508
+ *
509
+ * @param value - The value to validate.
510
+ * @param safe - If `true`, returns `false` on failure instead of throwing.
511
+ * @returns `true` if the value matches the type definition.
512
+ * @throws {ValidatorError} When validation fails and `safe` is not `true`.
513
+ */ validate(value, safe) {
467
514
  this.push("");
468
515
  this.errors = [];
469
516
  this.stackErrors = [];
@@ -490,15 +537,14 @@ var Validator = class {
490
537
  return this.stackPath.slice(1).join(".");
491
538
  }
492
539
  validateAnnotatedType(def, value) {
493
- switch (def.type.kind) {
494
- case "object": return this.validateObject(def, value);
495
- case "union": return this.validateUnion(def, value);
496
- case "intersection": return this.validateIntersection(def, value);
497
- case "tuple": return this.validateTuple(def, value);
498
- case "array": return this.validateArray(def, value);
499
- case "": return this.validatePrimitive(def, value);
500
- default: throw new Error(`Unknown type "${def.type.kind}"`);
501
- }
540
+ return forAnnotatedType(def, {
541
+ final: (d) => this.validatePrimitive(d, value),
542
+ object: (d) => this.validateObject(d, value),
543
+ array: (d) => this.validateArray(d, value),
544
+ union: (d) => this.validateUnion(d, value),
545
+ intersection: (d) => this.validateIntersection(d, value),
546
+ tuple: (d) => this.validateTuple(d, value)
547
+ });
502
548
  }
503
549
  validateUnion(def, value) {
504
550
  let i = 0;
@@ -730,7 +776,7 @@ else {
730
776
  constructor(def, opts) {
731
777
  _define_property$1(this, "def", void 0);
732
778
  _define_property$1(this, "opts", void 0);
733
- _define_property$1(this, "errors", void 0);
779
+ /** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
734
780
  _define_property$1(this, "stackErrors", void 0);
735
781
  _define_property$1(this, "stackPath", void 0);
736
782
  this.def = def;
@@ -757,6 +803,15 @@ var ValidatorError = class extends Error {
757
803
  function isAnnotatedType(type) {
758
804
  return type && type.__is_atscript_annotated_type;
759
805
  }
806
+ function annotate(metadata, key, value, asArray) {
807
+ if (!metadata) return;
808
+ if (asArray) if (metadata.has(key)) {
809
+ const a = metadata.get(key);
810
+ if (Array.isArray(a)) a.push(value);
811
+ else metadata.set(key, [a, value]);
812
+ } else metadata.set(key, [value]);
813
+ else metadata.set(key, value);
814
+ }
760
815
  function defineAnnotatedType(_kind, base) {
761
816
  const kind = _kind || "";
762
817
  const type = base?.type || {};
@@ -856,12 +911,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
856
911
  return this;
857
912
  },
858
913
  annotate(key, value, asArray) {
859
- if (asArray) if (this.$metadata.has(key)) {
860
- const a = this.$metadata.get(key);
861
- if (Array.isArray(a)) a.push(value);
862
- else this.$metadata.set(key, [a, value]);
863
- } else this.$metadata.set(key, [value]);
864
- else this.$metadata.set(key, value);
914
+ annotate(this.$metadata, key, value, asArray);
865
915
  return this;
866
916
  }
867
917
  };
@@ -872,14 +922,12 @@ else this.$metadata.set(key, value);
872
922
  //#region packages/typescript/src/json-schema.ts
873
923
  function buildJsonSchema(type) {
874
924
  const build = (def) => {
875
- const t = def.type;
876
925
  const meta = def.metadata;
877
- switch (t.kind) {
878
- case "object": {
879
- const obj = t;
926
+ return forAnnotatedType(def, {
927
+ object(d) {
880
928
  const properties = {};
881
929
  const required = [];
882
- for (const [key, val] of obj.props.entries()) {
930
+ for (const [key, val] of d.type.props.entries()) {
883
931
  properties[key] = build(val);
884
932
  if (!val.optional) required.push(key);
885
933
  }
@@ -889,41 +937,36 @@ function buildJsonSchema(type) {
889
937
  };
890
938
  if (required.length) schema.required = required;
891
939
  return schema;
892
- }
893
- case "array": {
894
- const arr = t;
940
+ },
941
+ array(d) {
895
942
  const schema = {
896
943
  type: "array",
897
- items: build(arr.of)
944
+ items: build(d.type.of)
898
945
  };
899
946
  const minLength = meta.get("expect.minLength");
900
947
  if (typeof minLength === "number") schema.minItems = minLength;
901
948
  const maxLength = meta.get("expect.maxLength");
902
949
  if (typeof maxLength === "number") schema.maxItems = maxLength;
903
950
  return schema;
904
- }
905
- case "union": {
906
- const grp = t;
907
- return { anyOf: grp.items.map(build) };
908
- }
909
- case "intersection": {
910
- const grp = t;
911
- return { allOf: grp.items.map(build) };
912
- }
913
- case "tuple": {
914
- const grp = t;
951
+ },
952
+ union(d) {
953
+ return { anyOf: d.type.items.map(build) };
954
+ },
955
+ intersection(d) {
956
+ return { allOf: d.type.items.map(build) };
957
+ },
958
+ tuple(d) {
915
959
  return {
916
960
  type: "array",
917
- items: grp.items.map(build),
961
+ items: d.type.items.map(build),
918
962
  additionalItems: false
919
963
  };
920
- }
921
- case "": {
922
- const fin = t;
964
+ },
965
+ final(d) {
923
966
  const schema = {};
924
- if (fin.value !== undefined) schema.const = fin.value;
925
- if (fin.designType && fin.designType !== "any") {
926
- schema.type = fin.designType === "undefined" ? "null" : fin.designType;
967
+ if (d.type.value !== undefined) schema.const = d.type.value;
968
+ if (d.type.designType && d.type.designType !== "any") {
969
+ schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
927
970
  if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
928
971
  }
929
972
  if (schema.type === "string") {
@@ -943,8 +986,7 @@ else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern:
943
986
  }
944
987
  return schema;
945
988
  }
946
- default: return {};
947
- }
989
+ });
948
990
  };
949
991
  return build(type);
950
992
  }
@@ -965,8 +1007,8 @@ var JsRenderer = class extends BaseRenderer {
965
1007
  pre() {
966
1008
  this.writeln("// prettier-ignore-start");
967
1009
  this.writeln("/* eslint-disable */");
968
- const imports = ["defineAnnotatedType as $"];
969
- if (!this.opts?.preRenderJsonSchema) imports.push("buildJsonSchema as $$");
1010
+ const imports = ["defineAnnotatedType as $", "annotate as $a"];
1011
+ if (resolveJsonSchemaMode(this.opts) === "lazy") imports.push("buildJsonSchema as $$");
970
1012
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
971
1013
  }
972
1014
  buildAdHocMap(annotateNodes) {
@@ -984,27 +1026,26 @@ var JsRenderer = class extends BaseRenderer {
984
1026
  post() {
985
1027
  for (const node of this.postAnnotate) if (node.entity === "annotate") {
986
1028
  const annotateNode = node;
987
- const unwound = this.doc.unwindType(annotateNode.targetName);
988
- if (unwound?.def) {
989
- let def = this.doc.mergeIntersection(unwound.def);
990
- if ((0, __atscript_core.isInterface)(def)) def = def.getDefinition() || def;
991
- this._adHocAnnotations = this.buildAdHocMap([annotateNode]);
992
- this.annotateType(def, node.id);
993
- this._adHocAnnotations = null;
994
- this.indent();
995
- this.defineMetadataForAnnotateAlias(annotateNode);
996
- this.unindent();
997
- this.writeln();
1029
+ if (annotateNode.isMutating) this.renderMutatingAnnotateNode(annotateNode);
1030
+ else {
1031
+ const unwound = this.doc.unwindType(annotateNode.targetName);
1032
+ if (unwound?.def) {
1033
+ let def = this.doc.mergeIntersection(unwound.def);
1034
+ if ((0, __atscript_core.isInterface)(def)) def = def.getDefinition() || def;
1035
+ this._adHocAnnotations = this.buildAdHocMap([annotateNode]);
1036
+ this.annotateType(def, node.id);
1037
+ this._adHocAnnotations = null;
1038
+ this.indent();
1039
+ this.defineMetadataForAnnotateAlias(annotateNode);
1040
+ this.unindent();
1041
+ this.writeln();
1042
+ }
998
1043
  }
999
1044
  } else {
1000
- const mutatingNodes = this.doc.getAnnotateNodesFor(node.id).filter((n) => n.isMutating);
1001
- this._adHocAnnotations = this.buildAdHocMap(mutatingNodes);
1002
1045
  this.annotateType(node.getDefinition(), node.id);
1003
- this._adHocAnnotations = null;
1004
1046
  this.indent().defineMetadata(node).unindent();
1005
1047
  this.writeln();
1006
1048
  }
1007
- this.renderMutatingAnnotates();
1008
1049
  this.writeln("// prettier-ignore-end");
1009
1050
  super.post();
1010
1051
  }
@@ -1038,7 +1079,7 @@ var JsRenderer = class extends BaseRenderer {
1038
1079
  }
1039
1080
  renderAnnotate(node) {
1040
1081
  if (node.isMutating) {
1041
- this.mutatingAnnotates.push(node);
1082
+ this.postAnnotate.push(node);
1042
1083
  return;
1043
1084
  }
1044
1085
  const targetName = node.targetName;
@@ -1055,15 +1096,21 @@ var JsRenderer = class extends BaseRenderer {
1055
1096
  this.writeln();
1056
1097
  }
1057
1098
  renderJsonSchemaMethod(node) {
1058
- if (this.opts?.preRenderJsonSchema) {
1099
+ const mode = resolveJsonSchemaMode(this.opts);
1100
+ const hasAnnotation = node.countAnnotations("emit.jsonSchema") > 0;
1101
+ if (hasAnnotation || mode === "bundle") {
1059
1102
  const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
1060
1103
  this.writeln("static toJsonSchema() {");
1061
1104
  this.indent().writeln(`return ${schema}`).unindent();
1062
1105
  this.writeln("}");
1063
- } else {
1106
+ } else if (mode === "lazy") {
1064
1107
  this.writeln("static toJsonSchema() {");
1065
1108
  this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
1066
1109
  this.writeln("}");
1110
+ } else {
1111
+ this.writeln("static toJsonSchema() {");
1112
+ 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();
1113
+ this.writeln("}");
1067
1114
  }
1068
1115
  }
1069
1116
  toAnnotatedType(node) {
@@ -1326,18 +1373,13 @@ else handle.prop(prop.id, propHandle.$type);
1326
1373
  return this;
1327
1374
  }
1328
1375
  defineMetadata(node) {
1329
- const annotations = this.doc.evalAnnotationsForNode(node);
1330
- let adHocNames;
1331
- let adHoc;
1376
+ let annotations = this.doc.evalAnnotationsForNode(node);
1332
1377
  if (this._adHocAnnotations && this._propPath.length > 0) {
1333
1378
  const path$2 = this._propPath.join(".");
1334
- adHoc = this._adHocAnnotations.get(path$2);
1335
- if (adHoc) adHocNames = new Set(adHoc.map((a) => a.name));
1379
+ const adHoc = this._adHocAnnotations.get(path$2);
1380
+ if (adHoc) annotations = this.doc.mergeNodesAnnotations(annotations, adHoc);
1336
1381
  }
1337
1382
  annotations?.forEach((an) => {
1338
- if (!adHocNames || !adHocNames.has(an.name)) this.resolveAnnotationValue(node, an);
1339
- });
1340
- adHoc?.forEach((an) => {
1341
1383
  this.resolveAnnotationValue(node, an);
1342
1384
  });
1343
1385
  return this;
@@ -1349,11 +1391,8 @@ else handle.prop(prop.id, propHandle.$type);
1349
1391
  const annotateAnnotations = this.doc.evalAnnotationsForNode(annotateNode);
1350
1392
  const targetDecl = this.doc.getDeclarationOwnerNode(annotateNode.targetName);
1351
1393
  const targetAnnotations = targetDecl?.node ? targetDecl.doc.evalAnnotationsForNode(targetDecl.node) : undefined;
1352
- const overriddenNames = new Set(annotateAnnotations?.map((a) => a.name));
1353
- targetAnnotations?.forEach((an) => {
1354
- if (!overriddenNames.has(an.name)) this.resolveAnnotationValue(annotateNode, an);
1355
- });
1356
- annotateAnnotations?.forEach((an) => {
1394
+ const merged = this.doc.mergeNodesAnnotations(targetAnnotations, annotateAnnotations);
1395
+ merged.forEach((an) => {
1357
1396
  this.resolveAnnotationValue(annotateNode, an);
1358
1397
  });
1359
1398
  return this;
@@ -1392,52 +1431,132 @@ else targetValue = "true";
1392
1431
  multiple: !!multiple
1393
1432
  };
1394
1433
  }
1395
- renderMutatingAnnotates() {
1396
- for (const node of this.mutatingAnnotates) {
1397
- const targetName = node.targetName;
1398
- for (const entry of node.entries) {
1399
- const anns = entry.annotations;
1400
- if (!anns || anns.length === 0) continue;
1401
- const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
1402
- let accessor = targetName;
1403
- for (const part of parts) accessor += `.type.props.get("${escapeQuotes(part)}")?`;
1434
+ renderMutatingAnnotateNode(node) {
1435
+ const targetName = node.targetName;
1436
+ const targetDef = this.resolveTargetDef(targetName);
1437
+ this.writeln("// Ad-hoc annotations for ", targetName);
1438
+ for (const entry of node.entries) {
1439
+ const anns = entry.annotations;
1440
+ if (!anns || anns.length === 0) continue;
1441
+ const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
1442
+ const accessors = this.buildMutatingAccessors(targetName, targetDef, parts);
1443
+ for (const accessor of accessors) {
1444
+ const cleared = new Set();
1404
1445
  for (const an of anns) {
1405
1446
  const { value, multiple } = this.computeAnnotationValue(entry, an);
1406
1447
  if (multiple) {
1407
- this.writeln(`{`);
1408
- this.indent();
1409
- this.writeln(`const __t = ${accessor}.metadata`);
1410
- this.writeln(`const __k = "${escapeQuotes(an.name)}"`);
1411
- this.writeln(`const __v = ${value}`);
1412
- this.writeln(`if (__t) { const __e = __t.get(__k); __t.set(__k, Array.isArray(__e) ? [...__e, __v] : __e !== undefined ? [__e, __v] : [__v]) }`);
1413
- this.unindent();
1414
- this.writeln(`}`);
1415
- } else this.writeln(`${accessor}.metadata.set("${escapeQuotes(an.name)}", ${value})`);
1448
+ if (!cleared.has(an.name)) {
1449
+ const spec = this.doc.resolveAnnotation(an.name);
1450
+ if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
1451
+ cleared.add(an.name);
1452
+ }
1453
+ this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1454
+ } else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1416
1455
  }
1417
1456
  }
1418
- const topAnnotations = node.annotations;
1419
- if (topAnnotations && topAnnotations.length > 0) for (const an of topAnnotations) {
1457
+ }
1458
+ const topAnnotations = node.annotations;
1459
+ if (topAnnotations && topAnnotations.length > 0) {
1460
+ const cleared = new Set();
1461
+ for (const an of topAnnotations) {
1420
1462
  const { value, multiple } = this.computeAnnotationValue(node, an);
1421
1463
  if (multiple) {
1422
- this.writeln(`{`);
1423
- this.indent();
1424
- this.writeln(`const __t = ${targetName}.metadata`);
1425
- this.writeln(`const __k = "${escapeQuotes(an.name)}"`);
1426
- this.writeln(`const __v = ${value}`);
1427
- this.writeln(`if (__t) { const __e = __t.get(__k); __t.set(__k, Array.isArray(__e) ? [...__e, __v] : __e !== undefined ? [__e, __v] : [__v]) }`);
1428
- this.unindent();
1429
- this.writeln(`}`);
1430
- } else this.writeln(`${targetName}.metadata.set("${escapeQuotes(an.name)}", ${value})`);
1464
+ if (!cleared.has(an.name)) {
1465
+ const spec = this.doc.resolveAnnotation(an.name);
1466
+ if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${targetName}.metadata.delete("${escapeQuotes(an.name)}")`);
1467
+ cleared.add(an.name);
1468
+ }
1469
+ this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1470
+ } else this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1471
+ }
1472
+ }
1473
+ this.writeln();
1474
+ }
1475
+ resolveTargetDef(targetName) {
1476
+ const unwound = this.doc.unwindType(targetName);
1477
+ if (!unwound?.def) return undefined;
1478
+ let def = unwound.def;
1479
+ if ((0, __atscript_core.isInterface)(def)) def = def.getDefinition() || def;
1480
+ return def;
1481
+ }
1482
+ /**
1483
+ * Builds the runtime accessor paths for mutating annotate entries.
1484
+ * Computes exact paths at compile time by walking the AST,
1485
+ * so the generated JS accesses props directly without runtime search.
1486
+ * Returns multiple paths when a property appears in multiple union branches.
1487
+ */ buildMutatingAccessors(targetName, targetDef, parts) {
1488
+ let accessors = [{
1489
+ prefix: targetName + ".type",
1490
+ def: targetDef
1491
+ }];
1492
+ for (let i = 0; i < parts.length; i++) {
1493
+ const nextAccessors = [];
1494
+ for (const { prefix, def } of accessors) {
1495
+ const results = this.buildPropPaths(def, parts[i]);
1496
+ if (results.length > 0) for (const result of results) if (i < parts.length - 1) nextAccessors.push({
1497
+ prefix: prefix + result.path + "?.type",
1498
+ def: result.propDef
1499
+ });
1500
+ else nextAccessors.push({
1501
+ prefix: prefix + result.path + "?",
1502
+ def: result.propDef
1503
+ });
1504
+ else {
1505
+ const suffix = `.props.get("${escapeQuotes(parts[i])}")` + (i < parts.length - 1 ? "?.type" : "?");
1506
+ nextAccessors.push({
1507
+ prefix: prefix + suffix,
1508
+ def: undefined
1509
+ });
1510
+ }
1431
1511
  }
1512
+ accessors = nextAccessors;
1513
+ }
1514
+ return accessors.map((a) => a.prefix);
1515
+ }
1516
+ /**
1517
+ * Finds a property in a type tree at compile time, returning all
1518
+ * matching runtime path strings and prop definitions for further chaining.
1519
+ * Returns multiple results when the same property appears in different union branches.
1520
+ */ buildPropPaths(def, propName) {
1521
+ if (!def) return [];
1522
+ def = this.doc.mergeIntersection(def);
1523
+ if ((0, __atscript_core.isRef)(def)) {
1524
+ const ref = def;
1525
+ const unwound = this.doc.unwindType(ref.id, ref.chain)?.def;
1526
+ return this.buildPropPaths(unwound, propName);
1527
+ }
1528
+ if ((0, __atscript_core.isInterface)(def)) return this.buildPropPaths(def.getDefinition(), propName);
1529
+ if ((0, __atscript_core.isStructure)(def)) {
1530
+ const prop = def.props.get(propName);
1531
+ if (prop) return [{
1532
+ path: `.props.get("${escapeQuotes(propName)}")`,
1533
+ propDef: prop.getDefinition()
1534
+ }];
1535
+ return [];
1432
1536
  }
1537
+ if ((0, __atscript_core.isGroup)(def)) {
1538
+ const group = def;
1539
+ const items = group.unwrap();
1540
+ const results = [];
1541
+ for (let i = 0; i < items.length; i++) for (const result of this.buildPropPaths(items[i], propName)) results.push({
1542
+ path: `.items[${i}].type${result.path}`,
1543
+ propDef: result.propDef
1544
+ });
1545
+ return results;
1546
+ }
1547
+ return [];
1433
1548
  }
1434
1549
  constructor(doc, opts) {
1435
- super(doc), _define_property(this, "opts", void 0), _define_property(this, "postAnnotate", void 0), _define_property(this, "mutatingAnnotates", void 0), _define_property(this, "_adHocAnnotations", void 0), _define_property(this, "_propPath", void 0), this.opts = opts, this.postAnnotate = [], this.mutatingAnnotates = [], this._adHocAnnotations = null, this._propPath = [];
1550
+ 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 = [];
1436
1551
  }
1437
1552
  };
1438
1553
 
1439
1554
  //#endregion
1440
1555
  //#region packages/typescript/src/plugin.ts
1556
+ function resolveJsonSchemaMode(opts) {
1557
+ if (opts?.jsonSchema !== undefined) return opts.jsonSchema;
1558
+ return false;
1559
+ }
1441
1560
  const tsPlugin = (opts) => {
1442
1561
  return {
1443
1562
  name: "typesccript",
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