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