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