@atscript/typescript 0.1.31 → 0.1.32

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
@@ -143,6 +143,9 @@ else obj[key] = value;
143
143
  return obj;
144
144
  }
145
145
  var BaseRenderer = class extends CodePrinter {
146
+ get unused() {
147
+ return this._unused ?? (this._unused = new Set(this.doc.getUnusedTokens().map((t) => t.text)));
148
+ }
146
149
  pre() {}
147
150
  post() {}
148
151
  render() {
@@ -196,15 +199,14 @@ var BaseRenderer = class extends CodePrinter {
196
199
  }
197
200
  }
198
201
  constructor(doc) {
199
- super(), _define_property$4(this, "doc", void 0), _define_property$4(this, "unused", void 0), this.doc = doc;
200
- this.unused = new Set(this.doc.getUnusedTokens().map((t) => t.text));
202
+ super(), _define_property$4(this, "doc", void 0), _define_property$4(this, "_unused", void 0), this.doc = doc;
201
203
  }
202
204
  };
203
205
 
204
206
  //#endregion
205
207
  //#region packages/typescript/src/codegen/utils.ts
208
+ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
206
209
  function wrapProp(name) {
207
- const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
208
210
  if (!validIdentifier.test(name)) return `"${escapeQuotes(name)}"`;
209
211
  return name;
210
212
  }
@@ -291,13 +293,14 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
291
293
  }
292
294
  if ((0, __atscript_core.isPrimitive)(def)) return this.write(renderPrimitiveTypeDef(def.config.type));
293
295
  }
294
- renderStructure(struct, asClass, interfaceNode) {
296
+ renderStructure(struct, asClass, interfaceNode, filterProps) {
295
297
  this.blockln("{}");
296
298
  const patterns = [];
297
299
  const propsDefs = new Set();
298
- for (const prop of Array.from(struct.props.values())) {
300
+ for (const prop of struct.props.values()) {
301
+ if (filterProps?.has(prop.id)) continue;
299
302
  if (prop.token("identifier")?.pattern) {
300
- patterns.push(prop);
303
+ if (!filterProps) patterns.push(prop);
301
304
  continue;
302
305
  }
303
306
  const phantomType = this.phantomPropType(prop.getDefinition());
@@ -313,34 +316,36 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
313
316
  }
314
317
  if (patterns.length > 0) {
315
318
  this.write(`[key: string]: `);
316
- if (patterns.length > 0) {
317
- for (const prop of patterns) propsDefs.add(this.renderTypeDefString(prop.getDefinition()));
318
- const defs = Array.from(propsDefs);
319
- if (defs.length > 1) {
320
- this.indent();
321
- for (const def of defs) {
322
- this.writeln();
323
- this.write("| ");
324
- def.split("\n").forEach((l) => this.write(l.trim()));
325
- }
326
- this.unindent();
319
+ for (const prop of patterns) propsDefs.add(this.renderTypeDefString(prop.getDefinition()));
320
+ const defs = Array.from(propsDefs);
321
+ if (defs.length > 1) {
322
+ this.indent();
323
+ for (const def of defs) {
327
324
  this.writeln();
328
- } else defs[0].split("\n").forEach((l) => this.writeln(l));
329
- }
330
- }
331
- if (asClass) {
332
- this.writeln("static __is_atscript_annotated_type: true");
333
- this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
334
- this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
335
- this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
336
- 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. */");
337
- this.writeln("static toJsonSchema: () => any");
338
- if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
339
- this.writeln("static toExampleData?: () => any");
340
- if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
325
+ this.write("| ");
326
+ def.split("\n").forEach((l) => this.write(l.trim()));
327
+ }
328
+ this.unindent();
329
+ this.writeln();
330
+ } else defs[0].split("\n").forEach((l) => this.writeln(l));
341
331
  }
332
+ if (asClass) this.renderStaticDeclarations(asClass, interfaceNode);
342
333
  this.pop();
343
334
  }
335
+ renderStaticDeclarations(asClass, interfaceNode) {
336
+ this.writeln("static __is_atscript_annotated_type: true");
337
+ this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
338
+ this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
339
+ this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
340
+ 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. */");
341
+ this.writeln("static toJsonSchema: () => any");
342
+ if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
343
+ this.writeln("static toExampleData?: () => any");
344
+ if (interfaceNode && this.hasDbTable(interfaceNode)) {
345
+ this.renderFlat(interfaceNode);
346
+ this.renderPk(interfaceNode);
347
+ }
348
+ }
344
349
  renderInterface(node) {
345
350
  this.writeln();
346
351
  const exported = node.token("export")?.text === "export";
@@ -364,7 +369,7 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
364
369
  }
365
370
  if (!firstParentProps && (0, __atscript_core.isStructure)(fpDef)) firstParentProps = fpDef.props;
366
371
  }
367
- this.renderStructureFiltered(resolved, node.id, firstParentProps, node);
372
+ this.renderStructure(resolved, node.id, node, firstParentProps);
368
373
  } else this.writeln("{}");
369
374
  } else {
370
375
  this.write(`class ${node.id} `);
@@ -374,35 +379,6 @@ else this.writeln("{}");
374
379
  }
375
380
  this.writeln();
376
381
  }
377
- /**
378
- * Renders a structure block, optionally filtering out props that exist in a parent.
379
- */ renderStructureFiltered(struct, asClass, filterProps, interfaceNode) {
380
- if (!filterProps) return this.renderStructure(struct, asClass, interfaceNode);
381
- this.blockln("{}");
382
- for (const prop of Array.from(struct.props.values())) {
383
- if (filterProps.has(prop.id)) continue;
384
- if (prop.token("identifier")?.pattern) continue;
385
- const phantomType = this.phantomPropType(prop.getDefinition());
386
- if (phantomType) {
387
- this.writeln(`// ${prop.id}: ${phantomType}`);
388
- continue;
389
- }
390
- const optional = !!prop.token("optional");
391
- this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
392
- const renderedDef = this.renderTypeDefString(prop.getDefinition());
393
- renderedDef.split("\n").forEach((l) => this.writeln(l));
394
- }
395
- this.writeln("static __is_atscript_annotated_type: true");
396
- this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
397
- this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
398
- this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
399
- 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. */");
400
- this.writeln("static toJsonSchema: () => any");
401
- if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
402
- this.writeln("static toExampleData?: () => any");
403
- if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
404
- this.pop();
405
- }
406
382
  renderType(node) {
407
383
  this.writeln();
408
384
  const exported = node.token("export")?.text === "export";
@@ -500,6 +476,43 @@ else {
500
476
  }
501
477
  this.pop();
502
478
  }
479
+ /**
480
+ * Renders the `static __pk` property — the primary key type for type-safe
481
+ * `deleteOne`/`findById` signatures on `AtscriptDbTable`.
482
+ *
483
+ * - **Single PK** (one `@meta.id`) → `static __pk: <scalar type>`
484
+ * - **Compound PK** (multiple `@meta.id`) → `static __pk: { field1: Type1; field2: Type2 }`
485
+ * - **No PK** → no `__pk` emitted
486
+ */ renderPk(node) {
487
+ let struct;
488
+ if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
489
+ if (!struct) struct = node.getDefinition();
490
+ if (!struct || !(0, __atscript_core.isStructure)(struct)) return;
491
+ const pkProps = [];
492
+ for (const [name, prop] of struct.props) {
493
+ if (prop.token("identifier")?.pattern) continue;
494
+ if (prop.countAnnotations("meta.id") > 0) pkProps.push({
495
+ name,
496
+ prop
497
+ });
498
+ }
499
+ if (pkProps.length === 0) return;
500
+ this.writeln();
501
+ if (pkProps.length === 1) {
502
+ this.write("static __pk: ");
503
+ const renderedDef = this.renderTypeDefString(pkProps[0].prop.getDefinition());
504
+ renderedDef.split("\n").forEach((l) => this.writeln(l));
505
+ } else {
506
+ this.write("static __pk: ");
507
+ this.blockln("{}");
508
+ for (const { name, prop } of pkProps) {
509
+ this.write(wrapProp(name), ": ");
510
+ const renderedDef = this.renderTypeDefString(prop.getDefinition());
511
+ renderedDef.split("\n").forEach((l) => this.writeln(l));
512
+ }
513
+ this.pop();
514
+ }
515
+ }
503
516
  phantomPropType(def) {
504
517
  if (!def) return undefined;
505
518
  if ((0, __atscript_core.isPrimitive)(def) && def.config.type === "phantom") return def.id;
@@ -548,24 +561,6 @@ function renderPrimitiveTypeDef(def) {
548
561
  }
549
562
  }
550
563
 
551
- //#endregion
552
- //#region packages/typescript/src/traverse.ts
553
- function forAnnotatedType(def, handlers) {
554
- switch (def.type.kind) {
555
- case "": {
556
- const typed = def;
557
- if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
558
- return handlers.final(typed);
559
- }
560
- case "object": return handlers.object(def);
561
- case "array": return handlers.array(def);
562
- case "union": return handlers.union(def);
563
- case "intersection": return handlers.intersection(def);
564
- case "tuple": return handlers.tuple(def);
565
- default: throw new Error(`Unknown type kind "${def.type.kind}"`);
566
- }
567
- }
568
-
569
564
  //#endregion
570
565
  //#region packages/typescript/src/validator.ts
571
566
  function _define_property$2(obj, key, value) {
@@ -581,28 +576,35 @@ else obj[key] = value;
581
576
  const regexCache = new Map();
582
577
  var Validator = class {
583
578
  isLimitExceeded() {
584
- if (this.stackErrors.length > 0) return this.stackErrors[this.stackErrors.length - 1].length >= this.opts.errorLimit;
579
+ if (this.stackErrors.length > 0) {
580
+ const top = this.stackErrors[this.stackErrors.length - 1];
581
+ return top !== null && top.length >= this.opts.errorLimit;
582
+ }
585
583
  return this.errors.length >= this.opts.errorLimit;
586
584
  }
587
585
  push(name) {
588
586
  this.stackPath.push(name);
589
- this.stackErrors.push([]);
587
+ this.stackErrors.push(null);
588
+ this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
590
589
  }
591
590
  pop(saveErrors) {
592
591
  this.stackPath.pop();
593
592
  const popped = this.stackErrors.pop();
594
- if (saveErrors && popped?.length) popped.forEach((error) => {
595
- this.error(error.message, error.path, error.details);
596
- });
593
+ if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
594
+ this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
597
595
  return popped;
598
596
  }
599
597
  clear() {
600
- this.stackErrors[this.stackErrors.length - 1] = [];
598
+ this.stackErrors[this.stackErrors.length - 1] = null;
601
599
  }
602
600
  error(message, path$3, details) {
603
- const errors = this.stackErrors[this.stackErrors.length - 1] || this.errors;
601
+ let errors = this.stackErrors[this.stackErrors.length - 1];
602
+ if (!errors) if (this.stackErrors.length > 0) {
603
+ errors = [];
604
+ this.stackErrors[this.stackErrors.length - 1] = errors;
605
+ } else errors = this.errors;
604
606
  const error = {
605
- path: path$3 || this.path,
607
+ path: path$3 || this.cachedPath,
606
608
  message
607
609
  };
608
610
  if (details?.length) error.details = details;
@@ -622,9 +624,10 @@ var Validator = class {
622
624
  * @returns `true` if the value matches the type definition.
623
625
  * @throws {ValidatorError} When validation fails and `safe` is not `true`.
624
626
  */ validate(value, safe, context) {
625
- this.push("");
626
627
  this.errors = [];
627
628
  this.stackErrors = [];
629
+ this.stackPath = [""];
630
+ this.cachedPath = "";
628
631
  this.context = context;
629
632
  const passed = this.validateSafe(this.def, value);
630
633
  this.pop(!passed);
@@ -638,7 +641,7 @@ var Validator = class {
638
641
  validateSafe(def, value) {
639
642
  if (this.isLimitExceeded()) return false;
640
643
  if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
641
- if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.path);
644
+ if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
642
645
  if (def.optional && value === undefined) return true;
643
646
  for (const plugin of this.opts.plugins) {
644
647
  const result = plugin(this, def, value);
@@ -647,18 +650,21 @@ var Validator = class {
647
650
  return this.validateAnnotatedType(def, value);
648
651
  }
649
652
  get path() {
650
- return this.stackPath.slice(1).join(".");
653
+ return this.cachedPath;
651
654
  }
652
655
  validateAnnotatedType(def, value) {
653
- return forAnnotatedType(def, {
654
- final: (d) => this.validatePrimitive(d, value),
655
- phantom: () => true,
656
- object: (d) => this.validateObject(d, value),
657
- array: (d) => this.validateArray(d, value),
658
- union: (d) => this.validateUnion(d, value),
659
- intersection: (d) => this.validateIntersection(d, value),
660
- tuple: (d) => this.validateTuple(d, value)
661
- });
656
+ switch (def.type.kind) {
657
+ case "": {
658
+ if (def.type.designType === "phantom") return true;
659
+ return this.validatePrimitive(def, value);
660
+ }
661
+ case "object": return this.validateObject(def, value);
662
+ case "array": return this.validateArray(def, value);
663
+ case "union": return this.validateUnion(def, value);
664
+ case "intersection": return this.validateIntersection(def, value);
665
+ case "tuple": return this.validateTuple(def, value);
666
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
667
+ }
662
668
  }
663
669
  validateUnion(def, value) {
664
670
  let i = 0;
@@ -722,6 +728,30 @@ var Validator = class {
722
728
  return false;
723
729
  }
724
730
  }
731
+ const uniqueItems = def.metadata.get("expect.array.uniqueItems");
732
+ if (uniqueItems) {
733
+ const separator = "▼↩";
734
+ const seen = new Set();
735
+ const keyProps = new Set();
736
+ if (def.type.of.type.kind === "object") {
737
+ for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
738
+ }
739
+ for (let idx = 0; idx < value.length; idx++) {
740
+ const item = value[idx];
741
+ let key;
742
+ if (keyProps.size > 0) {
743
+ key = "";
744
+ for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
745
+ } else key = JSON.stringify(item);
746
+ if (seen.has(key)) {
747
+ this.push(String(idx));
748
+ this.error(uniqueItems.message || "Duplicate items are not allowed");
749
+ this.pop(true);
750
+ return false;
751
+ }
752
+ seen.add(key);
753
+ }
754
+ }
725
755
  let i = 0;
726
756
  let passed = true;
727
757
  for (const item of value) {
@@ -743,21 +773,20 @@ var Validator = class {
743
773
  let passed = true;
744
774
  const valueKeys = new Set(Object.keys(value));
745
775
  const typeKeys = new Set();
746
- const skipList = new Set();
776
+ let skipList;
747
777
  if (this.opts.skipList) {
748
- const path$3 = this.stackPath.length > 1 ? `${this.path}.` : "";
749
- this.opts.skipList.forEach((item) => {
750
- if (item.startsWith(path$3)) {
751
- const key = item.slice(path$3.length);
752
- skipList.add(key);
753
- valueKeys.delete(key);
754
- }
755
- });
778
+ const path$3 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
779
+ for (const item of this.opts.skipList) if (item.startsWith(path$3)) {
780
+ const key = item.slice(path$3.length);
781
+ if (!skipList) skipList = new Set();
782
+ skipList.add(key);
783
+ valueKeys.delete(key);
784
+ }
756
785
  }
757
786
  let partialFunctionMatched = false;
758
- if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
787
+ if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
759
788
  for (const [key, item] of def.type.props.entries()) {
760
- if (skipList.has(key) || isPhantomType(item)) continue;
789
+ if (skipList && skipList.has(key) || isPhantomType(item)) continue;
761
790
  typeKeys.add(key);
762
791
  if (value[key] === undefined) {
763
792
  if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
@@ -778,19 +807,21 @@ else {
778
807
  def: propDef
779
808
  });
780
809
  if (matched.length > 0) {
810
+ this.push(key);
781
811
  let keyPassed = false;
782
- for (const { def: def$1 } of matched) if (this.validateSafe(def$1, value[key])) {
783
- this.pop(false);
784
- keyPassed = true;
785
- break;
812
+ for (const { def: propDef } of matched) {
813
+ if (this.validateSafe(propDef, value[key])) {
814
+ keyPassed = true;
815
+ break;
816
+ }
817
+ this.clear();
786
818
  }
787
819
  if (!keyPassed) {
788
- this.push(key);
789
820
  this.validateSafe(matched[0].def, value[key]);
790
821
  this.pop(true);
791
822
  passed = false;
792
823
  if (this.isLimitExceeded()) return false;
793
- }
824
+ } else this.pop(false);
794
825
  } else if (this.opts.unknownProps !== "ignore") {
795
826
  if (this.opts.unknownProps === "error") {
796
827
  this.push(key);
@@ -943,11 +974,13 @@ else {
943
974
  /** Validation errors collected during the last {@link validate} call. */ _define_property$2(this, "errors", void 0);
944
975
  _define_property$2(this, "stackErrors", void 0);
945
976
  _define_property$2(this, "stackPath", void 0);
977
+ _define_property$2(this, "cachedPath", void 0);
946
978
  _define_property$2(this, "context", void 0);
947
979
  this.def = def;
948
980
  this.errors = [];
949
981
  this.stackErrors = [];
950
982
  this.stackPath = [];
983
+ this.cachedPath = "";
951
984
  this.opts = {
952
985
  partial: false,
953
986
  unknownProps: "error",
@@ -1091,6 +1124,24 @@ function isPhantomType(def) {
1091
1124
  return def.type.kind === "" && def.type.designType === "phantom";
1092
1125
  }
1093
1126
 
1127
+ //#endregion
1128
+ //#region packages/typescript/src/traverse.ts
1129
+ function forAnnotatedType(def, handlers) {
1130
+ switch (def.type.kind) {
1131
+ case "": {
1132
+ const typed = def;
1133
+ if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
1134
+ return handlers.final(typed);
1135
+ }
1136
+ case "object": return handlers.object(def);
1137
+ case "array": return handlers.array(def);
1138
+ case "union": return handlers.union(def);
1139
+ case "intersection": return handlers.intersection(def);
1140
+ case "tuple": return handlers.tuple(def);
1141
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
1142
+ }
1143
+ }
1144
+
1094
1145
  //#endregion
1095
1146
  //#region packages/typescript/src/json-schema.ts
1096
1147
  /**
@@ -1263,24 +1314,25 @@ var JsRenderer = class extends BaseRenderer {
1263
1314
  this.writeln("// prettier-ignore-start");
1264
1315
  this.writeln("/* eslint-disable */");
1265
1316
  this.writeln("/* oxlint-disable */");
1317
+ let hasMutatingAnnotate = false;
1318
+ const nodesByName = new Map();
1319
+ for (const node of this.doc.nodes) {
1320
+ if (node.entity === "annotate" && node.isMutating) hasMutatingAnnotate = true;
1321
+ if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
1322
+ const name = node.id;
1323
+ if (!nodesByName.has(name)) nodesByName.set(name, []);
1324
+ nodesByName.get(name).push(node);
1325
+ }
1326
+ }
1327
+ for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
1328
+ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
1266
1329
  const imports = ["defineAnnotatedType as $", "annotate as $a"];
1267
- const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
1268
1330
  if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
1269
1331
  const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
1270
1332
  if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
1271
1333
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
1272
1334
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
1273
1335
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
1274
- const nameCounts = new Map();
1275
- const nodesByName = new Map();
1276
- for (const node of this.doc.nodes) if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
1277
- const name = node.id;
1278
- nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
1279
- if (!nodesByName.has(name)) nodesByName.set(name, []);
1280
- nodesByName.get(name).push(node);
1281
- }
1282
- for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
1283
- else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
1284
1336
  }
1285
1337
  buildAdHocMap(annotateNodes) {
1286
1338
  const map = new Map();
@@ -1289,7 +1341,8 @@ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}_
1289
1341
  const anns = entry.annotations || [];
1290
1342
  if (anns.length > 0) {
1291
1343
  const existing = map.get(path$3);
1292
- map.set(path$3, existing ? [...existing, ...anns] : anns);
1344
+ if (existing) existing.push(...anns);
1345
+ else map.set(path$3, [...anns]);
1293
1346
  }
1294
1347
  }
1295
1348
  return map.size > 0 ? map : null;
@@ -1348,35 +1401,20 @@ else def = def.getDefinition() || def;
1348
1401
  this.renderExampleDataMethod(node);
1349
1402
  }
1350
1403
  renderInterface(node) {
1351
- this.writeln();
1352
- const exported = node.token("export")?.text === "export";
1353
- this.write(exported ? "export " : "");
1354
- this.write(`class ${node.id} `);
1355
- this.blockln("{}");
1356
- this.renderClassStatics(node);
1357
- this.popln();
1358
- this.postAnnotate.push(node);
1359
- this.writeln();
1404
+ this.renderDefinitionClass(node);
1360
1405
  }
1361
1406
  renderType(node) {
1362
- this.writeln();
1363
- const exported = node.token("export")?.text === "export";
1364
- this.write(exported ? "export " : "");
1365
- this.write(`class ${node.id} `);
1366
- this.blockln("{}");
1367
- this.renderClassStatics(node);
1368
- this.popln();
1369
- this.postAnnotate.push(node);
1370
- this.writeln();
1407
+ this.renderDefinitionClass(node);
1371
1408
  }
1372
1409
  renderAnnotate(node) {
1373
1410
  if (node.isMutating) {
1374
1411
  this.postAnnotate.push(node);
1375
1412
  return;
1376
1413
  }
1377
- const targetName = node.targetName;
1378
- const unwound = this.doc.unwindType(targetName);
1379
- if (!unwound?.def) return;
1414
+ if (!this.doc.unwindType(node.targetName)?.def) return;
1415
+ this.renderDefinitionClass(node);
1416
+ }
1417
+ renderDefinitionClass(node) {
1380
1418
  this.writeln();
1381
1419
  const exported = node.token("export")?.text === "export";
1382
1420
  this.write(exported ? "export " : "");
@@ -1534,6 +1572,11 @@ else handle.prop(prop.id, propHandle.$type);
1534
1572
  handle.annotate(a.name, true);
1535
1573
  break;
1536
1574
  }
1575
+ case "expect.array.uniqueItems":
1576
+ case "expect.array.key": {
1577
+ handle.annotate(a.name, { message: a.args[0]?.text });
1578
+ break;
1579
+ }
1537
1580
  default:
1538
1581
  }
1539
1582
  });
@@ -1601,10 +1644,7 @@ else handle.prop(prop.id, propHandle.$type);
1601
1644
  this.writeln(`$("array"${name ? `, ${name}` : ""})`).indent().defineArray(node).unindent();
1602
1645
  return this;
1603
1646
  }
1604
- default: {
1605
- console.log("!!!!!!! UNKNOWN", node.entity);
1606
- return this;
1607
- }
1647
+ default: return this;
1608
1648
  }
1609
1649
  }
1610
1650
  defineConst(node) {
@@ -1816,40 +1856,25 @@ else targetValue = "true";
1816
1856
  this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
1817
1857
  }
1818
1858
  }
1819
- for (const { entry, accessors } of entryAccessors) {
1820
- const anns = entry.annotations;
1821
- for (const accessor of accessors) {
1822
- const cleared = new Set();
1823
- for (const an of anns) {
1824
- const { value, multiple } = this.computeAnnotationValue(entry, an);
1825
- if (multiple) {
1826
- if (!cleared.has(an.name)) {
1827
- const spec = this.doc.resolveAnnotation(an.name);
1828
- if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
1829
- cleared.add(an.name);
1830
- }
1831
- this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1832
- } else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1833
- }
1834
- }
1835
- }
1859
+ for (const { entry, accessors } of entryAccessors) for (const accessor of accessors) this.emitMutatingAnnotations(entry, entry.annotations, accessor);
1836
1860
  const topAnnotations = node.annotations;
1837
- if (topAnnotations && topAnnotations.length > 0) {
1838
- const cleared = new Set();
1839
- for (const an of topAnnotations) {
1840
- const { value, multiple } = this.computeAnnotationValue(node, an);
1841
- if (multiple) {
1842
- if (!cleared.has(an.name)) {
1843
- const spec = this.doc.resolveAnnotation(an.name);
1844
- if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${targetName}.metadata.delete("${escapeQuotes(an.name)}")`);
1845
- cleared.add(an.name);
1846
- }
1847
- this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1848
- } else this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1849
- }
1850
- }
1861
+ if (topAnnotations && topAnnotations.length > 0) this.emitMutatingAnnotations(node, topAnnotations, targetName);
1851
1862
  this.writeln();
1852
1863
  }
1864
+ emitMutatingAnnotations(node, annotations, accessor) {
1865
+ const cleared = new Set();
1866
+ for (const an of annotations) {
1867
+ const { value, multiple } = this.computeAnnotationValue(node, an);
1868
+ if (multiple) {
1869
+ if (!cleared.has(an.name)) {
1870
+ const spec = this.doc.resolveAnnotation(an.name);
1871
+ if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
1872
+ cleared.add(an.name);
1873
+ }
1874
+ this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1875
+ } else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1876
+ }
1877
+ }
1853
1878
  resolveTargetDef(targetName) {
1854
1879
  const unwound = this.doc.unwindType(targetName);
1855
1880
  if (!unwound?.def) return undefined;