@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.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");
@@ -296,9 +296,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
296
296
  }
297
297
  if (asClass) {
298
298
  this.writeln("static __is_atscript_annotated_type: true");
299
- this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
299
+ this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
300
300
  this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
301
- this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
301
+ this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
302
+ 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
303
  this.writeln("static toJsonSchema: () => any");
303
304
  }
304
305
  this.pop();
@@ -313,6 +314,12 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
313
314
  if (struct?.entity === "structure") this.renderStructure(struct, node.id);
314
315
  else this.writeln("{}");
315
316
  this.writeln();
317
+ const nsPrefix = exported ? "export declare" : "declare";
318
+ this.write(`${nsPrefix} namespace ${node.id} `);
319
+ this.blockln("{}");
320
+ this.writeln(`type DataType = ${node.id}`);
321
+ this.popln();
322
+ this.writeln();
316
323
  }
317
324
  renderType(node) {
318
325
  this.writeln();
@@ -327,44 +334,60 @@ else this.writeln("{}");
327
334
  renderAnnotate(node) {
328
335
  if (node.isMutating) return;
329
336
  const targetName = node.targetName;
330
- const unwound = this.doc.unwindType(targetName);
331
- if (!unwound?.def) return;
332
- const def = this.doc.mergeIntersection(unwound.def);
333
337
  this.writeln();
334
338
  const exported = node.token("export")?.text === "export";
335
339
  this.renderJsDoc(node);
336
- if (isStructure(def) || isInterface(def)) {
337
- this.write(exported ? "export declare " : "declare ");
338
- this.write(`class ${node.id} `);
339
- this.renderStructure(def, node.id);
340
- } else {
340
+ if (this.isTypeTarget(targetName)) {
341
341
  this.write(exported ? "export " : "declare ");
342
- this.write(`type ${node.id} = `);
343
- this.renderTypeDef(def);
342
+ this.write(`type ${node.id} = ${targetName}`);
343
+ this.writeln();
344
+ const unwound = this.doc.unwindType(targetName);
345
+ this.renderTypeNamespaceFor(node.id, unwound?.def);
346
+ } else {
347
+ this.write(exported ? "export declare " : "declare ");
348
+ this.writeln(`class ${node.id} extends ${targetName} {}`);
349
+ const nsPrefix = exported ? "export declare" : "declare";
350
+ this.write(`${nsPrefix} namespace ${node.id} `);
351
+ this.blockln("{}");
352
+ this.writeln(`type DataType = ${node.id}`);
353
+ this.popln();
354
+ this.writeln();
344
355
  }
345
- this.writeln();
346
356
  }
347
357
  renderTypeNamespace(node) {
348
- this.write(`declare namespace ${node.id} `);
358
+ this.renderTypeNamespaceFor(node.id, node.getDefinition());
359
+ }
360
+ renderTypeNamespaceFor(name, inputDef) {
361
+ this.write(`declare namespace ${name} `);
349
362
  this.blockln("{}");
350
- const def = node.getDefinition();
351
363
  let typeDef = "TAtscriptTypeDef";
352
- if (def) {
353
- let realDef = def;
354
- if (isRef(def)) realDef = this.doc.unwindType(def.id, def.chain)?.def || realDef;
364
+ if (inputDef) {
365
+ let realDef = inputDef;
366
+ if (isRef(inputDef)) realDef = this.doc.unwindType(inputDef.id, inputDef.chain)?.def || realDef;
355
367
  realDef = this.doc.mergeIntersection(realDef);
356
- if (isStructure(realDef) || isInterface(realDef)) typeDef = `TAtscriptTypeObject<keyof ${node.id}>`;
357
- else if (isGroup(realDef)) typeDef = "TAtscriptTypeComplex";
358
- else if (isArray(realDef)) typeDef = "TAtscriptTypeArray";
359
- else if (isPrimitive(realDef)) typeDef = "TAtscriptTypeFinal";
368
+ if (isStructure(realDef) || isInterface(realDef)) typeDef = `TAtscriptTypeObject<keyof ${name}, ${name}>`;
369
+ else if (isGroup(realDef)) typeDef = `TAtscriptTypeComplex<${name}>`;
370
+ else if (isArray(realDef)) typeDef = `TAtscriptTypeArray<${name}>`;
371
+ else if (isPrimitive(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
360
372
  }
373
+ this.writeln(`type DataType = ${name}`);
361
374
  this.writeln(`const __is_atscript_annotated_type: true`);
362
375
  this.writeln(`const type: ${typeDef}`);
363
376
  this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
364
- this.writeln(`const validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
377
+ this.writeln(`const validator: (opts?: Partial<TValidatorOptions>) => Validator<TAtscriptAnnotatedType, ${name}>`);
378
+ 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
379
  this.writeln("const toJsonSchema: () => any");
366
380
  this.popln();
367
381
  }
382
+ isTypeTarget(name, doc) {
383
+ const d = doc || this.doc;
384
+ const decl = d.getDeclarationOwnerNode(name);
385
+ if (!decl?.node) return false;
386
+ if (decl.node.entity === "type") return true;
387
+ if (decl.node.entity === "interface") return false;
388
+ if (decl.node.entity === "annotate") return this.isTypeTarget(decl.node.targetName, decl.doc);
389
+ return false;
390
+ }
368
391
  renderJsDoc(node) {
369
392
  const range = node.token("identifier")?.range;
370
393
  const rangeStr = range ? `:${range.start.line + 1}:${range.start.character + 1}` : "";
@@ -394,6 +417,20 @@ function renderPrimitiveTypeDef(def) {
394
417
  }
395
418
  }
396
419
 
420
+ //#endregion
421
+ //#region packages/typescript/src/traverse.ts
422
+ function forAnnotatedType(def, handlers) {
423
+ switch (def.type.kind) {
424
+ case "": return handlers.final(def);
425
+ case "object": return handlers.object(def);
426
+ case "array": return handlers.array(def);
427
+ case "union": return handlers.union(def);
428
+ case "intersection": return handlers.intersection(def);
429
+ case "tuple": return handlers.tuple(def);
430
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
431
+ }
432
+ }
433
+
397
434
  //#endregion
398
435
  //#region packages/typescript/src/validator.ts
399
436
  function _define_property$1(obj, key, value) {
@@ -439,7 +476,17 @@ var Validator = class {
439
476
  throw() {
440
477
  throw new ValidatorError(this.errors);
441
478
  }
442
- validate(value, safe) {
479
+ /**
480
+ * Validates a value against the type definition.
481
+ *
482
+ * Acts as a TypeScript type guard — when it returns `true`, the value
483
+ * is narrowed to `DataType`.
484
+ *
485
+ * @param value - The value to validate.
486
+ * @param safe - If `true`, returns `false` on failure instead of throwing.
487
+ * @returns `true` if the value matches the type definition.
488
+ * @throws {ValidatorError} When validation fails and `safe` is not `true`.
489
+ */ validate(value, safe) {
443
490
  this.push("");
444
491
  this.errors = [];
445
492
  this.stackErrors = [];
@@ -466,15 +513,14 @@ var Validator = class {
466
513
  return this.stackPath.slice(1).join(".");
467
514
  }
468
515
  validateAnnotatedType(def, value) {
469
- switch (def.type.kind) {
470
- case "object": return this.validateObject(def, value);
471
- case "union": return this.validateUnion(def, value);
472
- case "intersection": return this.validateIntersection(def, value);
473
- case "tuple": return this.validateTuple(def, value);
474
- case "array": return this.validateArray(def, value);
475
- case "": return this.validatePrimitive(def, value);
476
- default: throw new Error(`Unknown type "${def.type.kind}"`);
477
- }
516
+ return forAnnotatedType(def, {
517
+ final: (d) => this.validatePrimitive(d, value),
518
+ object: (d) => this.validateObject(d, value),
519
+ array: (d) => this.validateArray(d, value),
520
+ union: (d) => this.validateUnion(d, value),
521
+ intersection: (d) => this.validateIntersection(d, value),
522
+ tuple: (d) => this.validateTuple(d, value)
523
+ });
478
524
  }
479
525
  validateUnion(def, value) {
480
526
  let i = 0;
@@ -706,7 +752,7 @@ else {
706
752
  constructor(def, opts) {
707
753
  _define_property$1(this, "def", void 0);
708
754
  _define_property$1(this, "opts", void 0);
709
- _define_property$1(this, "errors", void 0);
755
+ /** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
710
756
  _define_property$1(this, "stackErrors", void 0);
711
757
  _define_property$1(this, "stackPath", void 0);
712
758
  this.def = def;
@@ -733,6 +779,15 @@ var ValidatorError = class extends Error {
733
779
  function isAnnotatedType(type) {
734
780
  return type && type.__is_atscript_annotated_type;
735
781
  }
782
+ function annotate(metadata, key, value, asArray) {
783
+ if (!metadata) return;
784
+ if (asArray) if (metadata.has(key)) {
785
+ const a = metadata.get(key);
786
+ if (Array.isArray(a)) a.push(value);
787
+ else metadata.set(key, [a, value]);
788
+ } else metadata.set(key, [value]);
789
+ else metadata.set(key, value);
790
+ }
736
791
  function defineAnnotatedType(_kind, base) {
737
792
  const kind = _kind || "";
738
793
  const type = base?.type || {};
@@ -832,12 +887,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
832
887
  return this;
833
888
  },
834
889
  annotate(key, value, asArray) {
835
- if (asArray) if (this.$metadata.has(key)) {
836
- const a = this.$metadata.get(key);
837
- if (Array.isArray(a)) a.push(value);
838
- else this.$metadata.set(key, [a, value]);
839
- } else this.$metadata.set(key, [value]);
840
- else this.$metadata.set(key, value);
890
+ annotate(this.$metadata, key, value, asArray);
841
891
  return this;
842
892
  }
843
893
  };
@@ -848,14 +898,12 @@ else this.$metadata.set(key, value);
848
898
  //#region packages/typescript/src/json-schema.ts
849
899
  function buildJsonSchema(type) {
850
900
  const build = (def) => {
851
- const t = def.type;
852
901
  const meta = def.metadata;
853
- switch (t.kind) {
854
- case "object": {
855
- const obj = t;
902
+ return forAnnotatedType(def, {
903
+ object(d) {
856
904
  const properties = {};
857
905
  const required = [];
858
- for (const [key, val] of obj.props.entries()) {
906
+ for (const [key, val] of d.type.props.entries()) {
859
907
  properties[key] = build(val);
860
908
  if (!val.optional) required.push(key);
861
909
  }
@@ -865,41 +913,36 @@ function buildJsonSchema(type) {
865
913
  };
866
914
  if (required.length) schema.required = required;
867
915
  return schema;
868
- }
869
- case "array": {
870
- const arr = t;
916
+ },
917
+ array(d) {
871
918
  const schema = {
872
919
  type: "array",
873
- items: build(arr.of)
920
+ items: build(d.type.of)
874
921
  };
875
922
  const minLength = meta.get("expect.minLength");
876
923
  if (typeof minLength === "number") schema.minItems = minLength;
877
924
  const maxLength = meta.get("expect.maxLength");
878
925
  if (typeof maxLength === "number") schema.maxItems = maxLength;
879
926
  return schema;
880
- }
881
- case "union": {
882
- const grp = t;
883
- return { anyOf: grp.items.map(build) };
884
- }
885
- case "intersection": {
886
- const grp = t;
887
- return { allOf: grp.items.map(build) };
888
- }
889
- case "tuple": {
890
- const grp = t;
927
+ },
928
+ union(d) {
929
+ return { anyOf: d.type.items.map(build) };
930
+ },
931
+ intersection(d) {
932
+ return { allOf: d.type.items.map(build) };
933
+ },
934
+ tuple(d) {
891
935
  return {
892
936
  type: "array",
893
- items: grp.items.map(build),
937
+ items: d.type.items.map(build),
894
938
  additionalItems: false
895
939
  };
896
- }
897
- case "": {
898
- const fin = t;
940
+ },
941
+ final(d) {
899
942
  const schema = {};
900
- if (fin.value !== undefined) schema.const = fin.value;
901
- if (fin.designType && fin.designType !== "any") {
902
- schema.type = fin.designType === "undefined" ? "null" : fin.designType;
943
+ if (d.type.value !== undefined) schema.const = d.type.value;
944
+ if (d.type.designType && d.type.designType !== "any") {
945
+ schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
903
946
  if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
904
947
  }
905
948
  if (schema.type === "string") {
@@ -919,8 +962,7 @@ else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern:
919
962
  }
920
963
  return schema;
921
964
  }
922
- default: return {};
923
- }
965
+ });
924
966
  };
925
967
  return build(type);
926
968
  }
@@ -941,8 +983,8 @@ var JsRenderer = class extends BaseRenderer {
941
983
  pre() {
942
984
  this.writeln("// prettier-ignore-start");
943
985
  this.writeln("/* eslint-disable */");
944
- const imports = ["defineAnnotatedType as $"];
945
- if (!this.opts?.preRenderJsonSchema) imports.push("buildJsonSchema as $$");
986
+ const imports = ["defineAnnotatedType as $", "annotate as $a"];
987
+ if (resolveJsonSchemaMode(this.opts) === "lazy") imports.push("buildJsonSchema as $$");
946
988
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
947
989
  }
948
990
  buildAdHocMap(annotateNodes) {
@@ -960,27 +1002,26 @@ var JsRenderer = class extends BaseRenderer {
960
1002
  post() {
961
1003
  for (const node of this.postAnnotate) if (node.entity === "annotate") {
962
1004
  const annotateNode = node;
963
- const unwound = this.doc.unwindType(annotateNode.targetName);
964
- if (unwound?.def) {
965
- let def = this.doc.mergeIntersection(unwound.def);
966
- if (isInterface(def)) def = def.getDefinition() || def;
967
- this._adHocAnnotations = this.buildAdHocMap([annotateNode]);
968
- this.annotateType(def, node.id);
969
- this._adHocAnnotations = null;
970
- this.indent();
971
- this.defineMetadataForAnnotateAlias(annotateNode);
972
- this.unindent();
973
- this.writeln();
1005
+ if (annotateNode.isMutating) this.renderMutatingAnnotateNode(annotateNode);
1006
+ else {
1007
+ const unwound = this.doc.unwindType(annotateNode.targetName);
1008
+ if (unwound?.def) {
1009
+ let def = this.doc.mergeIntersection(unwound.def);
1010
+ if (isInterface(def)) def = def.getDefinition() || def;
1011
+ this._adHocAnnotations = this.buildAdHocMap([annotateNode]);
1012
+ this.annotateType(def, node.id);
1013
+ this._adHocAnnotations = null;
1014
+ this.indent();
1015
+ this.defineMetadataForAnnotateAlias(annotateNode);
1016
+ this.unindent();
1017
+ this.writeln();
1018
+ }
974
1019
  }
975
1020
  } else {
976
- const mutatingNodes = this.doc.getAnnotateNodesFor(node.id).filter((n) => n.isMutating);
977
- this._adHocAnnotations = this.buildAdHocMap(mutatingNodes);
978
1021
  this.annotateType(node.getDefinition(), node.id);
979
- this._adHocAnnotations = null;
980
1022
  this.indent().defineMetadata(node).unindent();
981
1023
  this.writeln();
982
1024
  }
983
- this.renderMutatingAnnotates();
984
1025
  this.writeln("// prettier-ignore-end");
985
1026
  super.post();
986
1027
  }
@@ -1014,7 +1055,7 @@ var JsRenderer = class extends BaseRenderer {
1014
1055
  }
1015
1056
  renderAnnotate(node) {
1016
1057
  if (node.isMutating) {
1017
- this.mutatingAnnotates.push(node);
1058
+ this.postAnnotate.push(node);
1018
1059
  return;
1019
1060
  }
1020
1061
  const targetName = node.targetName;
@@ -1031,15 +1072,21 @@ var JsRenderer = class extends BaseRenderer {
1031
1072
  this.writeln();
1032
1073
  }
1033
1074
  renderJsonSchemaMethod(node) {
1034
- if (this.opts?.preRenderJsonSchema) {
1075
+ const mode = resolveJsonSchemaMode(this.opts);
1076
+ const hasAnnotation = node.countAnnotations("emit.jsonSchema") > 0;
1077
+ if (hasAnnotation || mode === "bundle") {
1035
1078
  const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
1036
1079
  this.writeln("static toJsonSchema() {");
1037
1080
  this.indent().writeln(`return ${schema}`).unindent();
1038
1081
  this.writeln("}");
1039
- } else {
1082
+ } else if (mode === "lazy") {
1040
1083
  this.writeln("static toJsonSchema() {");
1041
1084
  this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
1042
1085
  this.writeln("}");
1086
+ } else {
1087
+ this.writeln("static toJsonSchema() {");
1088
+ 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();
1089
+ this.writeln("}");
1043
1090
  }
1044
1091
  }
1045
1092
  toAnnotatedType(node) {
@@ -1302,18 +1349,13 @@ else handle.prop(prop.id, propHandle.$type);
1302
1349
  return this;
1303
1350
  }
1304
1351
  defineMetadata(node) {
1305
- const annotations = this.doc.evalAnnotationsForNode(node);
1306
- let adHocNames;
1307
- let adHoc;
1352
+ let annotations = this.doc.evalAnnotationsForNode(node);
1308
1353
  if (this._adHocAnnotations && this._propPath.length > 0) {
1309
1354
  const path$1 = this._propPath.join(".");
1310
- adHoc = this._adHocAnnotations.get(path$1);
1311
- if (adHoc) adHocNames = new Set(adHoc.map((a) => a.name));
1355
+ const adHoc = this._adHocAnnotations.get(path$1);
1356
+ if (adHoc) annotations = this.doc.mergeNodesAnnotations(annotations, adHoc);
1312
1357
  }
1313
1358
  annotations?.forEach((an) => {
1314
- if (!adHocNames || !adHocNames.has(an.name)) this.resolveAnnotationValue(node, an);
1315
- });
1316
- adHoc?.forEach((an) => {
1317
1359
  this.resolveAnnotationValue(node, an);
1318
1360
  });
1319
1361
  return this;
@@ -1325,11 +1367,8 @@ else handle.prop(prop.id, propHandle.$type);
1325
1367
  const annotateAnnotations = this.doc.evalAnnotationsForNode(annotateNode);
1326
1368
  const targetDecl = this.doc.getDeclarationOwnerNode(annotateNode.targetName);
1327
1369
  const targetAnnotations = targetDecl?.node ? targetDecl.doc.evalAnnotationsForNode(targetDecl.node) : undefined;
1328
- const overriddenNames = new Set(annotateAnnotations?.map((a) => a.name));
1329
- targetAnnotations?.forEach((an) => {
1330
- if (!overriddenNames.has(an.name)) this.resolveAnnotationValue(annotateNode, an);
1331
- });
1332
- annotateAnnotations?.forEach((an) => {
1370
+ const merged = this.doc.mergeNodesAnnotations(targetAnnotations, annotateAnnotations);
1371
+ merged.forEach((an) => {
1333
1372
  this.resolveAnnotationValue(annotateNode, an);
1334
1373
  });
1335
1374
  return this;
@@ -1368,52 +1407,132 @@ else targetValue = "true";
1368
1407
  multiple: !!multiple
1369
1408
  };
1370
1409
  }
1371
- renderMutatingAnnotates() {
1372
- for (const node of this.mutatingAnnotates) {
1373
- const targetName = node.targetName;
1374
- for (const entry of node.entries) {
1375
- const anns = entry.annotations;
1376
- if (!anns || anns.length === 0) continue;
1377
- const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
1378
- let accessor = targetName;
1379
- for (const part of parts) accessor += `.type.props.get("${escapeQuotes(part)}")?`;
1410
+ renderMutatingAnnotateNode(node) {
1411
+ const targetName = node.targetName;
1412
+ const targetDef = this.resolveTargetDef(targetName);
1413
+ this.writeln("// Ad-hoc annotations for ", targetName);
1414
+ for (const entry of node.entries) {
1415
+ const anns = entry.annotations;
1416
+ if (!anns || anns.length === 0) continue;
1417
+ const parts = entry.hasChain ? [entry.id, ...entry.chain.map((c) => c.text)] : [entry.id];
1418
+ const accessors = this.buildMutatingAccessors(targetName, targetDef, parts);
1419
+ for (const accessor of accessors) {
1420
+ const cleared = new Set();
1380
1421
  for (const an of anns) {
1381
1422
  const { value, multiple } = this.computeAnnotationValue(entry, an);
1382
1423
  if (multiple) {
1383
- this.writeln(`{`);
1384
- this.indent();
1385
- this.writeln(`const __t = ${accessor}.metadata`);
1386
- this.writeln(`const __k = "${escapeQuotes(an.name)}"`);
1387
- this.writeln(`const __v = ${value}`);
1388
- this.writeln(`if (__t) { const __e = __t.get(__k); __t.set(__k, Array.isArray(__e) ? [...__e, __v] : __e !== undefined ? [__e, __v] : [__v]) }`);
1389
- this.unindent();
1390
- this.writeln(`}`);
1391
- } else this.writeln(`${accessor}.metadata.set("${escapeQuotes(an.name)}", ${value})`);
1424
+ if (!cleared.has(an.name)) {
1425
+ const spec = this.doc.resolveAnnotation(an.name);
1426
+ if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
1427
+ cleared.add(an.name);
1428
+ }
1429
+ this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1430
+ } else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1392
1431
  }
1393
1432
  }
1394
- const topAnnotations = node.annotations;
1395
- if (topAnnotations && topAnnotations.length > 0) for (const an of topAnnotations) {
1433
+ }
1434
+ const topAnnotations = node.annotations;
1435
+ if (topAnnotations && topAnnotations.length > 0) {
1436
+ const cleared = new Set();
1437
+ for (const an of topAnnotations) {
1396
1438
  const { value, multiple } = this.computeAnnotationValue(node, an);
1397
1439
  if (multiple) {
1398
- this.writeln(`{`);
1399
- this.indent();
1400
- this.writeln(`const __t = ${targetName}.metadata`);
1401
- this.writeln(`const __k = "${escapeQuotes(an.name)}"`);
1402
- this.writeln(`const __v = ${value}`);
1403
- this.writeln(`if (__t) { const __e = __t.get(__k); __t.set(__k, Array.isArray(__e) ? [...__e, __v] : __e !== undefined ? [__e, __v] : [__v]) }`);
1404
- this.unindent();
1405
- this.writeln(`}`);
1406
- } else this.writeln(`${targetName}.metadata.set("${escapeQuotes(an.name)}", ${value})`);
1440
+ if (!cleared.has(an.name)) {
1441
+ const spec = this.doc.resolveAnnotation(an.name);
1442
+ if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${targetName}.metadata.delete("${escapeQuotes(an.name)}")`);
1443
+ cleared.add(an.name);
1444
+ }
1445
+ this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1446
+ } else this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1447
+ }
1448
+ }
1449
+ this.writeln();
1450
+ }
1451
+ resolveTargetDef(targetName) {
1452
+ const unwound = this.doc.unwindType(targetName);
1453
+ if (!unwound?.def) return undefined;
1454
+ let def = unwound.def;
1455
+ if (isInterface(def)) def = def.getDefinition() || def;
1456
+ return def;
1457
+ }
1458
+ /**
1459
+ * Builds the runtime accessor paths for mutating annotate entries.
1460
+ * Computes exact paths at compile time by walking the AST,
1461
+ * so the generated JS accesses props directly without runtime search.
1462
+ * Returns multiple paths when a property appears in multiple union branches.
1463
+ */ buildMutatingAccessors(targetName, targetDef, parts) {
1464
+ let accessors = [{
1465
+ prefix: targetName + ".type",
1466
+ def: targetDef
1467
+ }];
1468
+ for (let i = 0; i < parts.length; i++) {
1469
+ const nextAccessors = [];
1470
+ for (const { prefix, def } of accessors) {
1471
+ const results = this.buildPropPaths(def, parts[i]);
1472
+ if (results.length > 0) for (const result of results) if (i < parts.length - 1) nextAccessors.push({
1473
+ prefix: prefix + result.path + "?.type",
1474
+ def: result.propDef
1475
+ });
1476
+ else nextAccessors.push({
1477
+ prefix: prefix + result.path + "?",
1478
+ def: result.propDef
1479
+ });
1480
+ else {
1481
+ const suffix = `.props.get("${escapeQuotes(parts[i])}")` + (i < parts.length - 1 ? "?.type" : "?");
1482
+ nextAccessors.push({
1483
+ prefix: prefix + suffix,
1484
+ def: undefined
1485
+ });
1486
+ }
1407
1487
  }
1488
+ accessors = nextAccessors;
1489
+ }
1490
+ return accessors.map((a) => a.prefix);
1491
+ }
1492
+ /**
1493
+ * Finds a property in a type tree at compile time, returning all
1494
+ * matching runtime path strings and prop definitions for further chaining.
1495
+ * Returns multiple results when the same property appears in different union branches.
1496
+ */ buildPropPaths(def, propName) {
1497
+ if (!def) return [];
1498
+ def = this.doc.mergeIntersection(def);
1499
+ if (isRef(def)) {
1500
+ const ref = def;
1501
+ const unwound = this.doc.unwindType(ref.id, ref.chain)?.def;
1502
+ return this.buildPropPaths(unwound, propName);
1503
+ }
1504
+ if (isInterface(def)) return this.buildPropPaths(def.getDefinition(), propName);
1505
+ if (isStructure(def)) {
1506
+ const prop = def.props.get(propName);
1507
+ if (prop) return [{
1508
+ path: `.props.get("${escapeQuotes(propName)}")`,
1509
+ propDef: prop.getDefinition()
1510
+ }];
1511
+ return [];
1408
1512
  }
1513
+ if (isGroup(def)) {
1514
+ const group = def;
1515
+ const items = group.unwrap();
1516
+ const results = [];
1517
+ for (let i = 0; i < items.length; i++) for (const result of this.buildPropPaths(items[i], propName)) results.push({
1518
+ path: `.items[${i}].type${result.path}`,
1519
+ propDef: result.propDef
1520
+ });
1521
+ return results;
1522
+ }
1523
+ return [];
1409
1524
  }
1410
1525
  constructor(doc, opts) {
1411
- 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 = [];
1526
+ 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 = [];
1412
1527
  }
1413
1528
  };
1414
1529
 
1415
1530
  //#endregion
1416
1531
  //#region packages/typescript/src/plugin.ts
1532
+ function resolveJsonSchemaMode(opts) {
1533
+ if (opts?.jsonSchema !== undefined) return opts.jsonSchema;
1534
+ return false;
1535
+ }
1417
1536
  const tsPlugin = (opts) => {
1418
1537
  return {
1419
1538
  name: "typesccript",