@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.mjs CHANGED
@@ -116,6 +116,9 @@ else obj[key] = value;
116
116
  return obj;
117
117
  }
118
118
  var BaseRenderer = class extends CodePrinter {
119
+ get unused() {
120
+ return this._unused ?? (this._unused = new Set(this.doc.getUnusedTokens().map((t) => t.text)));
121
+ }
119
122
  pre() {}
120
123
  post() {}
121
124
  render() {
@@ -169,15 +172,14 @@ var BaseRenderer = class extends CodePrinter {
169
172
  }
170
173
  }
171
174
  constructor(doc) {
172
- super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "unused", void 0), this.doc = doc;
173
- this.unused = new Set(this.doc.getUnusedTokens().map((t) => t.text));
175
+ super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "_unused", void 0), this.doc = doc;
174
176
  }
175
177
  };
176
178
 
177
179
  //#endregion
178
180
  //#region packages/typescript/src/codegen/utils.ts
181
+ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
179
182
  function wrapProp(name) {
180
- const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
181
183
  if (!validIdentifier.test(name)) return `"${escapeQuotes(name)}"`;
182
184
  return name;
183
185
  }
@@ -264,13 +266,14 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
264
266
  }
265
267
  if (isPrimitive(def)) return this.write(renderPrimitiveTypeDef(def.config.type));
266
268
  }
267
- renderStructure(struct, asClass, interfaceNode) {
269
+ renderStructure(struct, asClass, interfaceNode, filterProps) {
268
270
  this.blockln("{}");
269
271
  const patterns = [];
270
272
  const propsDefs = new Set();
271
- for (const prop of Array.from(struct.props.values())) {
273
+ for (const prop of struct.props.values()) {
274
+ if (filterProps?.has(prop.id)) continue;
272
275
  if (prop.token("identifier")?.pattern) {
273
- patterns.push(prop);
276
+ if (!filterProps) patterns.push(prop);
274
277
  continue;
275
278
  }
276
279
  const phantomType = this.phantomPropType(prop.getDefinition());
@@ -286,34 +289,36 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
286
289
  }
287
290
  if (patterns.length > 0) {
288
291
  this.write(`[key: string]: `);
289
- if (patterns.length > 0) {
290
- for (const prop of patterns) propsDefs.add(this.renderTypeDefString(prop.getDefinition()));
291
- const defs = Array.from(propsDefs);
292
- if (defs.length > 1) {
293
- this.indent();
294
- for (const def of defs) {
295
- this.writeln();
296
- this.write("| ");
297
- def.split("\n").forEach((l) => this.write(l.trim()));
298
- }
299
- this.unindent();
292
+ for (const prop of patterns) propsDefs.add(this.renderTypeDefString(prop.getDefinition()));
293
+ const defs = Array.from(propsDefs);
294
+ if (defs.length > 1) {
295
+ this.indent();
296
+ for (const def of defs) {
300
297
  this.writeln();
301
- } else defs[0].split("\n").forEach((l) => this.writeln(l));
302
- }
303
- }
304
- if (asClass) {
305
- this.writeln("static __is_atscript_annotated_type: true");
306
- this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
307
- this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
308
- this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
309
- 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. */");
310
- this.writeln("static toJsonSchema: () => any");
311
- if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
312
- this.writeln("static toExampleData?: () => any");
313
- if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
298
+ this.write("| ");
299
+ def.split("\n").forEach((l) => this.write(l.trim()));
300
+ }
301
+ this.unindent();
302
+ this.writeln();
303
+ } else defs[0].split("\n").forEach((l) => this.writeln(l));
314
304
  }
305
+ if (asClass) this.renderStaticDeclarations(asClass, interfaceNode);
315
306
  this.pop();
316
307
  }
308
+ renderStaticDeclarations(asClass, interfaceNode) {
309
+ this.writeln("static __is_atscript_annotated_type: true");
310
+ this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
311
+ this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
312
+ this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
313
+ 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. */");
314
+ this.writeln("static toJsonSchema: () => any");
315
+ if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
316
+ this.writeln("static toExampleData?: () => any");
317
+ if (interfaceNode && this.hasDbTable(interfaceNode)) {
318
+ this.renderFlat(interfaceNode);
319
+ this.renderPk(interfaceNode);
320
+ }
321
+ }
317
322
  renderInterface(node) {
318
323
  this.writeln();
319
324
  const exported = node.token("export")?.text === "export";
@@ -337,7 +342,7 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
337
342
  }
338
343
  if (!firstParentProps && isStructure(fpDef)) firstParentProps = fpDef.props;
339
344
  }
340
- this.renderStructureFiltered(resolved, node.id, firstParentProps, node);
345
+ this.renderStructure(resolved, node.id, node, firstParentProps);
341
346
  } else this.writeln("{}");
342
347
  } else {
343
348
  this.write(`class ${node.id} `);
@@ -347,35 +352,6 @@ else this.writeln("{}");
347
352
  }
348
353
  this.writeln();
349
354
  }
350
- /**
351
- * Renders a structure block, optionally filtering out props that exist in a parent.
352
- */ renderStructureFiltered(struct, asClass, filterProps, interfaceNode) {
353
- if (!filterProps) return this.renderStructure(struct, asClass, interfaceNode);
354
- this.blockln("{}");
355
- for (const prop of Array.from(struct.props.values())) {
356
- if (filterProps.has(prop.id)) continue;
357
- if (prop.token("identifier")?.pattern) continue;
358
- const phantomType = this.phantomPropType(prop.getDefinition());
359
- if (phantomType) {
360
- this.writeln(`// ${prop.id}: ${phantomType}`);
361
- continue;
362
- }
363
- const optional = !!prop.token("optional");
364
- this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
365
- const renderedDef = this.renderTypeDefString(prop.getDefinition());
366
- renderedDef.split("\n").forEach((l) => this.writeln(l));
367
- }
368
- this.writeln("static __is_atscript_annotated_type: true");
369
- this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}, ${asClass}>`);
370
- this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
371
- this.writeln(`static validator: (opts?: Partial<TValidatorOptions>) => Validator<typeof ${asClass}>`);
372
- 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. */");
373
- this.writeln("static toJsonSchema: () => any");
374
- if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
375
- this.writeln("static toExampleData?: () => any");
376
- if (interfaceNode && this.hasDbTable(interfaceNode)) this.renderFlat(interfaceNode);
377
- this.pop();
378
- }
379
355
  renderType(node) {
380
356
  this.writeln();
381
357
  const exported = node.token("export")?.text === "export";
@@ -473,6 +449,43 @@ else {
473
449
  }
474
450
  this.pop();
475
451
  }
452
+ /**
453
+ * Renders the `static __pk` property — the primary key type for type-safe
454
+ * `deleteOne`/`findById` signatures on `AtscriptDbTable`.
455
+ *
456
+ * - **Single PK** (one `@meta.id`) → `static __pk: <scalar type>`
457
+ * - **Compound PK** (multiple `@meta.id`) → `static __pk: { field1: Type1; field2: Type2 }`
458
+ * - **No PK** → no `__pk` emitted
459
+ */ renderPk(node) {
460
+ let struct;
461
+ if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
462
+ if (!struct) struct = node.getDefinition();
463
+ if (!struct || !isStructure(struct)) return;
464
+ const pkProps = [];
465
+ for (const [name, prop] of struct.props) {
466
+ if (prop.token("identifier")?.pattern) continue;
467
+ if (prop.countAnnotations("meta.id") > 0) pkProps.push({
468
+ name,
469
+ prop
470
+ });
471
+ }
472
+ if (pkProps.length === 0) return;
473
+ this.writeln();
474
+ if (pkProps.length === 1) {
475
+ this.write("static __pk: ");
476
+ const renderedDef = this.renderTypeDefString(pkProps[0].prop.getDefinition());
477
+ renderedDef.split("\n").forEach((l) => this.writeln(l));
478
+ } else {
479
+ this.write("static __pk: ");
480
+ this.blockln("{}");
481
+ for (const { name, prop } of pkProps) {
482
+ this.write(wrapProp(name), ": ");
483
+ const renderedDef = this.renderTypeDefString(prop.getDefinition());
484
+ renderedDef.split("\n").forEach((l) => this.writeln(l));
485
+ }
486
+ this.pop();
487
+ }
488
+ }
476
489
  phantomPropType(def) {
477
490
  if (!def) return undefined;
478
491
  if (isPrimitive(def) && def.config.type === "phantom") return def.id;
@@ -521,24 +534,6 @@ function renderPrimitiveTypeDef(def) {
521
534
  }
522
535
  }
523
536
 
524
- //#endregion
525
- //#region packages/typescript/src/traverse.ts
526
- function forAnnotatedType(def, handlers) {
527
- switch (def.type.kind) {
528
- case "": {
529
- const typed = def;
530
- if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
531
- return handlers.final(typed);
532
- }
533
- case "object": return handlers.object(def);
534
- case "array": return handlers.array(def);
535
- case "union": return handlers.union(def);
536
- case "intersection": return handlers.intersection(def);
537
- case "tuple": return handlers.tuple(def);
538
- default: throw new Error(`Unknown type kind "${def.type.kind}"`);
539
- }
540
- }
541
-
542
537
  //#endregion
543
538
  //#region packages/typescript/src/validator.ts
544
539
  function _define_property$1(obj, key, value) {
@@ -554,28 +549,35 @@ else obj[key] = value;
554
549
  const regexCache = new Map();
555
550
  var Validator = class {
556
551
  isLimitExceeded() {
557
- if (this.stackErrors.length > 0) return this.stackErrors[this.stackErrors.length - 1].length >= this.opts.errorLimit;
552
+ if (this.stackErrors.length > 0) {
553
+ const top = this.stackErrors[this.stackErrors.length - 1];
554
+ return top !== null && top.length >= this.opts.errorLimit;
555
+ }
558
556
  return this.errors.length >= this.opts.errorLimit;
559
557
  }
560
558
  push(name) {
561
559
  this.stackPath.push(name);
562
- this.stackErrors.push([]);
560
+ this.stackErrors.push(null);
561
+ this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
563
562
  }
564
563
  pop(saveErrors) {
565
564
  this.stackPath.pop();
566
565
  const popped = this.stackErrors.pop();
567
- if (saveErrors && popped?.length) popped.forEach((error) => {
568
- this.error(error.message, error.path, error.details);
569
- });
566
+ if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
567
+ this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
570
568
  return popped;
571
569
  }
572
570
  clear() {
573
- this.stackErrors[this.stackErrors.length - 1] = [];
571
+ this.stackErrors[this.stackErrors.length - 1] = null;
574
572
  }
575
573
  error(message, path$1, details) {
576
- const errors = this.stackErrors[this.stackErrors.length - 1] || this.errors;
574
+ let errors = this.stackErrors[this.stackErrors.length - 1];
575
+ if (!errors) if (this.stackErrors.length > 0) {
576
+ errors = [];
577
+ this.stackErrors[this.stackErrors.length - 1] = errors;
578
+ } else errors = this.errors;
577
579
  const error = {
578
- path: path$1 || this.path,
580
+ path: path$1 || this.cachedPath,
579
581
  message
580
582
  };
581
583
  if (details?.length) error.details = details;
@@ -595,9 +597,10 @@ var Validator = class {
595
597
  * @returns `true` if the value matches the type definition.
596
598
  * @throws {ValidatorError} When validation fails and `safe` is not `true`.
597
599
  */ validate(value, safe, context) {
598
- this.push("");
599
600
  this.errors = [];
600
601
  this.stackErrors = [];
602
+ this.stackPath = [""];
603
+ this.cachedPath = "";
601
604
  this.context = context;
602
605
  const passed = this.validateSafe(this.def, value);
603
606
  this.pop(!passed);
@@ -611,7 +614,7 @@ var Validator = class {
611
614
  validateSafe(def, value) {
612
615
  if (this.isLimitExceeded()) return false;
613
616
  if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
614
- if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.path);
617
+ if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
615
618
  if (def.optional && value === undefined) return true;
616
619
  for (const plugin of this.opts.plugins) {
617
620
  const result = plugin(this, def, value);
@@ -620,18 +623,21 @@ var Validator = class {
620
623
  return this.validateAnnotatedType(def, value);
621
624
  }
622
625
  get path() {
623
- return this.stackPath.slice(1).join(".");
626
+ return this.cachedPath;
624
627
  }
625
628
  validateAnnotatedType(def, value) {
626
- return forAnnotatedType(def, {
627
- final: (d) => this.validatePrimitive(d, value),
628
- phantom: () => true,
629
- object: (d) => this.validateObject(d, value),
630
- array: (d) => this.validateArray(d, value),
631
- union: (d) => this.validateUnion(d, value),
632
- intersection: (d) => this.validateIntersection(d, value),
633
- tuple: (d) => this.validateTuple(d, value)
634
- });
629
+ switch (def.type.kind) {
630
+ case "": {
631
+ if (def.type.designType === "phantom") return true;
632
+ return this.validatePrimitive(def, value);
633
+ }
634
+ case "object": return this.validateObject(def, value);
635
+ case "array": return this.validateArray(def, value);
636
+ case "union": return this.validateUnion(def, value);
637
+ case "intersection": return this.validateIntersection(def, value);
638
+ case "tuple": return this.validateTuple(def, value);
639
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
640
+ }
635
641
  }
636
642
  validateUnion(def, value) {
637
643
  let i = 0;
@@ -695,6 +701,30 @@ var Validator = class {
695
701
  return false;
696
702
  }
697
703
  }
704
+ const uniqueItems = def.metadata.get("expect.array.uniqueItems");
705
+ if (uniqueItems) {
706
+ const separator = "▼↩";
707
+ const seen = new Set();
708
+ const keyProps = new Set();
709
+ if (def.type.of.type.kind === "object") {
710
+ for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
711
+ }
712
+ for (let idx = 0; idx < value.length; idx++) {
713
+ const item = value[idx];
714
+ let key;
715
+ if (keyProps.size > 0) {
716
+ key = "";
717
+ for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
718
+ } else key = JSON.stringify(item);
719
+ if (seen.has(key)) {
720
+ this.push(String(idx));
721
+ this.error(uniqueItems.message || "Duplicate items are not allowed");
722
+ this.pop(true);
723
+ return false;
724
+ }
725
+ seen.add(key);
726
+ }
727
+ }
698
728
  let i = 0;
699
729
  let passed = true;
700
730
  for (const item of value) {
@@ -716,21 +746,20 @@ var Validator = class {
716
746
  let passed = true;
717
747
  const valueKeys = new Set(Object.keys(value));
718
748
  const typeKeys = new Set();
719
- const skipList = new Set();
749
+ let skipList;
720
750
  if (this.opts.skipList) {
721
- const path$1 = this.stackPath.length > 1 ? `${this.path}.` : "";
722
- this.opts.skipList.forEach((item) => {
723
- if (item.startsWith(path$1)) {
724
- const key = item.slice(path$1.length);
725
- skipList.add(key);
726
- valueKeys.delete(key);
727
- }
728
- });
751
+ const path$1 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
752
+ for (const item of this.opts.skipList) if (item.startsWith(path$1)) {
753
+ const key = item.slice(path$1.length);
754
+ if (!skipList) skipList = new Set();
755
+ skipList.add(key);
756
+ valueKeys.delete(key);
757
+ }
729
758
  }
730
759
  let partialFunctionMatched = false;
731
- if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
760
+ if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
732
761
  for (const [key, item] of def.type.props.entries()) {
733
- if (skipList.has(key) || isPhantomType(item)) continue;
762
+ if (skipList && skipList.has(key) || isPhantomType(item)) continue;
734
763
  typeKeys.add(key);
735
764
  if (value[key] === undefined) {
736
765
  if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
@@ -751,19 +780,21 @@ else {
751
780
  def: propDef
752
781
  });
753
782
  if (matched.length > 0) {
783
+ this.push(key);
754
784
  let keyPassed = false;
755
- for (const { def: def$1 } of matched) if (this.validateSafe(def$1, value[key])) {
756
- this.pop(false);
757
- keyPassed = true;
758
- break;
785
+ for (const { def: propDef } of matched) {
786
+ if (this.validateSafe(propDef, value[key])) {
787
+ keyPassed = true;
788
+ break;
789
+ }
790
+ this.clear();
759
791
  }
760
792
  if (!keyPassed) {
761
- this.push(key);
762
793
  this.validateSafe(matched[0].def, value[key]);
763
794
  this.pop(true);
764
795
  passed = false;
765
796
  if (this.isLimitExceeded()) return false;
766
- }
797
+ } else this.pop(false);
767
798
  } else if (this.opts.unknownProps !== "ignore") {
768
799
  if (this.opts.unknownProps === "error") {
769
800
  this.push(key);
@@ -916,11 +947,13 @@ else {
916
947
  /** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
917
948
  _define_property$1(this, "stackErrors", void 0);
918
949
  _define_property$1(this, "stackPath", void 0);
950
+ _define_property$1(this, "cachedPath", void 0);
919
951
  _define_property$1(this, "context", void 0);
920
952
  this.def = def;
921
953
  this.errors = [];
922
954
  this.stackErrors = [];
923
955
  this.stackPath = [];
956
+ this.cachedPath = "";
924
957
  this.opts = {
925
958
  partial: false,
926
959
  unknownProps: "error",
@@ -1064,6 +1097,24 @@ function isPhantomType(def) {
1064
1097
  return def.type.kind === "" && def.type.designType === "phantom";
1065
1098
  }
1066
1099
 
1100
+ //#endregion
1101
+ //#region packages/typescript/src/traverse.ts
1102
+ function forAnnotatedType(def, handlers) {
1103
+ switch (def.type.kind) {
1104
+ case "": {
1105
+ const typed = def;
1106
+ if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
1107
+ return handlers.final(typed);
1108
+ }
1109
+ case "object": return handlers.object(def);
1110
+ case "array": return handlers.array(def);
1111
+ case "union": return handlers.union(def);
1112
+ case "intersection": return handlers.intersection(def);
1113
+ case "tuple": return handlers.tuple(def);
1114
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
1115
+ }
1116
+ }
1117
+
1067
1118
  //#endregion
1068
1119
  //#region packages/typescript/src/json-schema.ts
1069
1120
  /**
@@ -1236,24 +1287,25 @@ var JsRenderer = class extends BaseRenderer {
1236
1287
  this.writeln("// prettier-ignore-start");
1237
1288
  this.writeln("/* eslint-disable */");
1238
1289
  this.writeln("/* oxlint-disable */");
1290
+ let hasMutatingAnnotate = false;
1291
+ const nodesByName = new Map();
1292
+ for (const node of this.doc.nodes) {
1293
+ if (node.entity === "annotate" && node.isMutating) hasMutatingAnnotate = true;
1294
+ if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
1295
+ const name = node.id;
1296
+ if (!nodesByName.has(name)) nodesByName.set(name, []);
1297
+ nodesByName.get(name).push(node);
1298
+ }
1299
+ }
1300
+ for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
1301
+ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
1239
1302
  const imports = ["defineAnnotatedType as $", "annotate as $a"];
1240
- const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
1241
1303
  if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
1242
1304
  const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
1243
1305
  if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
1244
1306
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
1245
1307
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
1246
1308
  this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript/utils"`);
1247
- const nameCounts = new Map();
1248
- const nodesByName = new Map();
1249
- for (const node of this.doc.nodes) if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
1250
- const name = node.id;
1251
- nameCounts.set(name, (nameCounts.get(name) || 0) + 1);
1252
- if (!nodesByName.has(name)) nodesByName.set(name, []);
1253
- nodesByName.get(name).push(node);
1254
- }
1255
- for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
1256
- else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
1257
1309
  }
1258
1310
  buildAdHocMap(annotateNodes) {
1259
1311
  const map = new Map();
@@ -1262,7 +1314,8 @@ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}_
1262
1314
  const anns = entry.annotations || [];
1263
1315
  if (anns.length > 0) {
1264
1316
  const existing = map.get(path$1);
1265
- map.set(path$1, existing ? [...existing, ...anns] : anns);
1317
+ if (existing) existing.push(...anns);
1318
+ else map.set(path$1, [...anns]);
1266
1319
  }
1267
1320
  }
1268
1321
  return map.size > 0 ? map : null;
@@ -1321,35 +1374,20 @@ else def = def.getDefinition() || def;
1321
1374
  this.renderExampleDataMethod(node);
1322
1375
  }
1323
1376
  renderInterface(node) {
1324
- this.writeln();
1325
- const exported = node.token("export")?.text === "export";
1326
- this.write(exported ? "export " : "");
1327
- this.write(`class ${node.id} `);
1328
- this.blockln("{}");
1329
- this.renderClassStatics(node);
1330
- this.popln();
1331
- this.postAnnotate.push(node);
1332
- this.writeln();
1377
+ this.renderDefinitionClass(node);
1333
1378
  }
1334
1379
  renderType(node) {
1335
- this.writeln();
1336
- const exported = node.token("export")?.text === "export";
1337
- this.write(exported ? "export " : "");
1338
- this.write(`class ${node.id} `);
1339
- this.blockln("{}");
1340
- this.renderClassStatics(node);
1341
- this.popln();
1342
- this.postAnnotate.push(node);
1343
- this.writeln();
1380
+ this.renderDefinitionClass(node);
1344
1381
  }
1345
1382
  renderAnnotate(node) {
1346
1383
  if (node.isMutating) {
1347
1384
  this.postAnnotate.push(node);
1348
1385
  return;
1349
1386
  }
1350
- const targetName = node.targetName;
1351
- const unwound = this.doc.unwindType(targetName);
1352
- if (!unwound?.def) return;
1387
+ if (!this.doc.unwindType(node.targetName)?.def) return;
1388
+ this.renderDefinitionClass(node);
1389
+ }
1390
+ renderDefinitionClass(node) {
1353
1391
  this.writeln();
1354
1392
  const exported = node.token("export")?.text === "export";
1355
1393
  this.write(exported ? "export " : "");
@@ -1507,6 +1545,11 @@ else handle.prop(prop.id, propHandle.$type);
1507
1545
  handle.annotate(a.name, true);
1508
1546
  break;
1509
1547
  }
1548
+ case "expect.array.uniqueItems":
1549
+ case "expect.array.key": {
1550
+ handle.annotate(a.name, { message: a.args[0]?.text });
1551
+ break;
1552
+ }
1510
1553
  default:
1511
1554
  }
1512
1555
  });
@@ -1574,10 +1617,7 @@ else handle.prop(prop.id, propHandle.$type);
1574
1617
  this.writeln(`$("array"${name ? `, ${name}` : ""})`).indent().defineArray(node).unindent();
1575
1618
  return this;
1576
1619
  }
1577
- default: {
1578
- console.log("!!!!!!! UNKNOWN", node.entity);
1579
- return this;
1580
- }
1620
+ default: return this;
1581
1621
  }
1582
1622
  }
1583
1623
  defineConst(node) {
@@ -1789,40 +1829,25 @@ else targetValue = "true";
1789
1829
  this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
1790
1830
  }
1791
1831
  }
1792
- for (const { entry, accessors } of entryAccessors) {
1793
- const anns = entry.annotations;
1794
- for (const accessor of accessors) {
1795
- const cleared = new Set();
1796
- for (const an of anns) {
1797
- const { value, multiple } = this.computeAnnotationValue(entry, an);
1798
- if (multiple) {
1799
- if (!cleared.has(an.name)) {
1800
- const spec = this.doc.resolveAnnotation(an.name);
1801
- if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
1802
- cleared.add(an.name);
1803
- }
1804
- this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1805
- } else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1806
- }
1807
- }
1808
- }
1832
+ for (const { entry, accessors } of entryAccessors) for (const accessor of accessors) this.emitMutatingAnnotations(entry, entry.annotations, accessor);
1809
1833
  const topAnnotations = node.annotations;
1810
- if (topAnnotations && topAnnotations.length > 0) {
1811
- const cleared = new Set();
1812
- for (const an of topAnnotations) {
1813
- const { value, multiple } = this.computeAnnotationValue(node, an);
1814
- if (multiple) {
1815
- if (!cleared.has(an.name)) {
1816
- const spec = this.doc.resolveAnnotation(an.name);
1817
- if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${targetName}.metadata.delete("${escapeQuotes(an.name)}")`);
1818
- cleared.add(an.name);
1819
- }
1820
- this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1821
- } else this.writeln(`$a(${targetName}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1822
- }
1823
- }
1834
+ if (topAnnotations && topAnnotations.length > 0) this.emitMutatingAnnotations(node, topAnnotations, targetName);
1824
1835
  this.writeln();
1825
1836
  }
1837
+ emitMutatingAnnotations(node, annotations, accessor) {
1838
+ const cleared = new Set();
1839
+ for (const an of annotations) {
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(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
1845
+ cleared.add(an.name);
1846
+ }
1847
+ this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1848
+ } else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1849
+ }
1850
+ }
1826
1851
  resolveTargetDef(targetName) {
1827
1852
  const unwound = this.doc.unwindType(targetName);
1828
1853
  if (!unwound?.def) return undefined;