@atscript/typescript 0.1.31 → 0.1.33

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,88 @@ 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 (unless unique indexes exist)
486
+ * - **Unique indexes** (`@db.index.unique`) → appended as union members
487
+ * - **Mongo collection** → always includes `string` (ObjectId) in the union;
488
+ * if no `@meta.id` fields, `__pk` is just `string`
489
+ */ renderPk(node) {
490
+ const isMongoCollection = !!node.annotations?.some((a) => a.name === "db.mongo.collection");
491
+ let struct;
492
+ if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
493
+ if (!struct) struct = node.getDefinition();
494
+ if (!struct || !(0, __atscript_core.isStructure)(struct)) return;
495
+ const structNode = struct;
496
+ const pkProps = [];
497
+ const uniqueByIndex = new Map();
498
+ for (const [name, prop] of structNode.props) {
499
+ if (prop.token("identifier")?.pattern) continue;
500
+ if (isMongoCollection && name === "_id") continue;
501
+ if (prop.countAnnotations("meta.id") > 0) pkProps.push({
502
+ name,
503
+ prop
504
+ });
505
+ if (prop.annotations) {
506
+ for (const ann of prop.annotations) if (ann.name === "db.index.unique") {
507
+ const indexName = ann.args[0]?.text ?? name;
508
+ let group = uniqueByIndex.get(indexName);
509
+ if (!group) {
510
+ group = [];
511
+ uniqueByIndex.set(indexName, group);
512
+ }
513
+ group.push({
514
+ name,
515
+ prop
516
+ });
517
+ }
518
+ }
519
+ }
520
+ const uniqueProps = [];
521
+ const pkNames = new Set(pkProps.map((p) => p.name));
522
+ for (const fields of uniqueByIndex.values()) if (fields.length === 1 && !pkNames.has(fields[0].name)) uniqueProps.push(fields[0]);
523
+ if (pkProps.length === 0 && uniqueProps.length === 0 && !isMongoCollection) return;
524
+ let mongoIdType;
525
+ if (isMongoCollection) {
526
+ const idProp = structNode.props.get("_id");
527
+ if (idProp) mongoIdType = this.renderTypeDefString(idProp.getDefinition()).trim();
528
+ mongoIdType ?? (mongoIdType = "string");
529
+ }
530
+ const uniqueTypes = [];
531
+ const seenTypes = new Set();
532
+ for (const { prop } of uniqueProps) {
533
+ const rendered = this.renderTypeDefString(prop.getDefinition()).trim();
534
+ if (!seenTypes.has(rendered)) {
535
+ seenTypes.add(rendered);
536
+ uniqueTypes.push(rendered);
537
+ }
538
+ }
539
+ this.writeln();
540
+ const uniqueSuffix = uniqueTypes.length > 0 ? ` | ${uniqueTypes.join(" | ")}` : "";
541
+ if (pkProps.length === 0 && !isMongoCollection) this.writeln(`static __pk: ${uniqueTypes.join(" | ")}`);
542
+ else if (pkProps.length === 0) this.writeln(`static __pk: ${mongoIdType}${uniqueSuffix}`);
543
+ else if (pkProps.length === 1) {
544
+ this.write("static __pk: ");
545
+ if (isMongoCollection) this.write(`${mongoIdType} | `);
546
+ const renderedDef = this.renderTypeDefString(pkProps[0].prop.getDefinition()).trim();
547
+ this.writeln(`${renderedDef}${uniqueSuffix}`);
548
+ } else {
549
+ this.write("static __pk: ");
550
+ if (isMongoCollection) this.write(`${mongoIdType} | `);
551
+ this.blockln("{}");
552
+ for (const { name, prop } of pkProps) {
553
+ this.write(wrapProp(name), ": ");
554
+ const renderedDef = this.renderTypeDefString(prop.getDefinition());
555
+ renderedDef.split("\n").forEach((l) => this.writeln(l));
556
+ }
557
+ this.pop();
558
+ this.writeln(uniqueSuffix);
559
+ }
560
+ }
503
561
  phantomPropType(def) {
504
562
  if (!def) return undefined;
505
563
  if ((0, __atscript_core.isPrimitive)(def) && def.config.type === "phantom") return def.id;
@@ -548,24 +606,6 @@ function renderPrimitiveTypeDef(def) {
548
606
  }
549
607
  }
550
608
 
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
609
  //#endregion
570
610
  //#region packages/typescript/src/validator.ts
571
611
  function _define_property$2(obj, key, value) {
@@ -581,28 +621,35 @@ else obj[key] = value;
581
621
  const regexCache = new Map();
582
622
  var Validator = class {
583
623
  isLimitExceeded() {
584
- if (this.stackErrors.length > 0) return this.stackErrors[this.stackErrors.length - 1].length >= this.opts.errorLimit;
624
+ if (this.stackErrors.length > 0) {
625
+ const top = this.stackErrors[this.stackErrors.length - 1];
626
+ return top !== null && top.length >= this.opts.errorLimit;
627
+ }
585
628
  return this.errors.length >= this.opts.errorLimit;
586
629
  }
587
630
  push(name) {
588
631
  this.stackPath.push(name);
589
- this.stackErrors.push([]);
632
+ this.stackErrors.push(null);
633
+ this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
590
634
  }
591
635
  pop(saveErrors) {
592
636
  this.stackPath.pop();
593
637
  const popped = this.stackErrors.pop();
594
- if (saveErrors && popped?.length) popped.forEach((error) => {
595
- this.error(error.message, error.path, error.details);
596
- });
638
+ if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
639
+ this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
597
640
  return popped;
598
641
  }
599
642
  clear() {
600
- this.stackErrors[this.stackErrors.length - 1] = [];
643
+ this.stackErrors[this.stackErrors.length - 1] = null;
601
644
  }
602
645
  error(message, path$3, details) {
603
- const errors = this.stackErrors[this.stackErrors.length - 1] || this.errors;
646
+ let errors = this.stackErrors[this.stackErrors.length - 1];
647
+ if (!errors) if (this.stackErrors.length > 0) {
648
+ errors = [];
649
+ this.stackErrors[this.stackErrors.length - 1] = errors;
650
+ } else errors = this.errors;
604
651
  const error = {
605
- path: path$3 || this.path,
652
+ path: path$3 || this.cachedPath,
606
653
  message
607
654
  };
608
655
  if (details?.length) error.details = details;
@@ -622,9 +669,10 @@ var Validator = class {
622
669
  * @returns `true` if the value matches the type definition.
623
670
  * @throws {ValidatorError} When validation fails and `safe` is not `true`.
624
671
  */ validate(value, safe, context) {
625
- this.push("");
626
672
  this.errors = [];
627
673
  this.stackErrors = [];
674
+ this.stackPath = [""];
675
+ this.cachedPath = "";
628
676
  this.context = context;
629
677
  const passed = this.validateSafe(this.def, value);
630
678
  this.pop(!passed);
@@ -638,7 +686,7 @@ var Validator = class {
638
686
  validateSafe(def, value) {
639
687
  if (this.isLimitExceeded()) return false;
640
688
  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);
689
+ if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
642
690
  if (def.optional && value === undefined) return true;
643
691
  for (const plugin of this.opts.plugins) {
644
692
  const result = plugin(this, def, value);
@@ -647,18 +695,21 @@ var Validator = class {
647
695
  return this.validateAnnotatedType(def, value);
648
696
  }
649
697
  get path() {
650
- return this.stackPath.slice(1).join(".");
698
+ return this.cachedPath;
651
699
  }
652
700
  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
- });
701
+ switch (def.type.kind) {
702
+ case "": {
703
+ if (def.type.designType === "phantom") return true;
704
+ return this.validatePrimitive(def, value);
705
+ }
706
+ case "object": return this.validateObject(def, value);
707
+ case "array": return this.validateArray(def, value);
708
+ case "union": return this.validateUnion(def, value);
709
+ case "intersection": return this.validateIntersection(def, value);
710
+ case "tuple": return this.validateTuple(def, value);
711
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
712
+ }
662
713
  }
663
714
  validateUnion(def, value) {
664
715
  let i = 0;
@@ -722,6 +773,30 @@ var Validator = class {
722
773
  return false;
723
774
  }
724
775
  }
776
+ const uniqueItems = def.metadata.get("expect.array.uniqueItems");
777
+ if (uniqueItems) {
778
+ const separator = "▼↩";
779
+ const seen = new Set();
780
+ const keyProps = new Set();
781
+ if (def.type.of.type.kind === "object") {
782
+ for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
783
+ }
784
+ for (let idx = 0; idx < value.length; idx++) {
785
+ const item = value[idx];
786
+ let key;
787
+ if (keyProps.size > 0) {
788
+ key = "";
789
+ for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
790
+ } else key = JSON.stringify(item);
791
+ if (seen.has(key)) {
792
+ this.push(String(idx));
793
+ this.error(uniqueItems.message || "Duplicate items are not allowed");
794
+ this.pop(true);
795
+ return false;
796
+ }
797
+ seen.add(key);
798
+ }
799
+ }
725
800
  let i = 0;
726
801
  let passed = true;
727
802
  for (const item of value) {
@@ -743,21 +818,20 @@ var Validator = class {
743
818
  let passed = true;
744
819
  const valueKeys = new Set(Object.keys(value));
745
820
  const typeKeys = new Set();
746
- const skipList = new Set();
821
+ let skipList;
747
822
  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
- });
823
+ const path$3 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
824
+ for (const item of this.opts.skipList) if (item.startsWith(path$3)) {
825
+ const key = item.slice(path$3.length);
826
+ if (!skipList) skipList = new Set();
827
+ skipList.add(key);
828
+ valueKeys.delete(key);
829
+ }
756
830
  }
757
831
  let partialFunctionMatched = false;
758
- if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
832
+ if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
759
833
  for (const [key, item] of def.type.props.entries()) {
760
- if (skipList.has(key) || isPhantomType(item)) continue;
834
+ if (skipList && skipList.has(key) || isPhantomType(item)) continue;
761
835
  typeKeys.add(key);
762
836
  if (value[key] === undefined) {
763
837
  if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
@@ -778,19 +852,21 @@ else {
778
852
  def: propDef
779
853
  });
780
854
  if (matched.length > 0) {
855
+ this.push(key);
781
856
  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;
857
+ for (const { def: propDef } of matched) {
858
+ if (this.validateSafe(propDef, value[key])) {
859
+ keyPassed = true;
860
+ break;
861
+ }
862
+ this.clear();
786
863
  }
787
864
  if (!keyPassed) {
788
- this.push(key);
789
865
  this.validateSafe(matched[0].def, value[key]);
790
866
  this.pop(true);
791
867
  passed = false;
792
868
  if (this.isLimitExceeded()) return false;
793
- }
869
+ } else this.pop(false);
794
870
  } else if (this.opts.unknownProps !== "ignore") {
795
871
  if (this.opts.unknownProps === "error") {
796
872
  this.push(key);
@@ -943,11 +1019,13 @@ else {
943
1019
  /** Validation errors collected during the last {@link validate} call. */ _define_property$2(this, "errors", void 0);
944
1020
  _define_property$2(this, "stackErrors", void 0);
945
1021
  _define_property$2(this, "stackPath", void 0);
1022
+ _define_property$2(this, "cachedPath", void 0);
946
1023
  _define_property$2(this, "context", void 0);
947
1024
  this.def = def;
948
1025
  this.errors = [];
949
1026
  this.stackErrors = [];
950
1027
  this.stackPath = [];
1028
+ this.cachedPath = "";
951
1029
  this.opts = {
952
1030
  partial: false,
953
1031
  unknownProps: "error",
@@ -965,6 +1043,24 @@ var ValidatorError = class extends Error {
965
1043
 
966
1044
  //#endregion
967
1045
  //#region packages/typescript/src/annotated-type.ts
1046
+ const COMPLEX_KINDS = new Set([
1047
+ "union",
1048
+ "intersection",
1049
+ "tuple"
1050
+ ]);
1051
+ /** Shared validator method reused by all annotated type nodes. */ function validatorMethod(opts) {
1052
+ return new Validator(this, opts);
1053
+ }
1054
+ function createAnnotatedTypeNode(type, metadata, opts) {
1055
+ return {
1056
+ __is_atscript_annotated_type: true,
1057
+ type,
1058
+ metadata,
1059
+ validator: validatorMethod,
1060
+ id: opts?.id,
1061
+ optional: opts?.optional
1062
+ };
1063
+ }
968
1064
  function isAnnotatedType(type) {
969
1065
  return type && type.__is_atscript_annotated_type;
970
1066
  }
@@ -981,38 +1077,24 @@ function defineAnnotatedType(_kind, base) {
981
1077
  const kind = _kind || "";
982
1078
  const type = base?.type || {};
983
1079
  type.kind = kind;
984
- if ([
985
- "union",
986
- "intersection",
987
- "tuple"
988
- ].includes(kind)) type.items = [];
1080
+ if (COMPLEX_KINDS.has(kind)) type.items = [];
989
1081
  if (kind === "object") {
990
1082
  type.props = new Map();
991
1083
  type.propsPatterns = [];
992
1084
  }
993
1085
  type.tags = new Set();
994
1086
  const metadata = base?.metadata || new Map();
995
- if (base) Object.assign(base, {
996
- __is_atscript_annotated_type: true,
997
- metadata,
998
- type,
999
- validator(opts) {
1000
- return new Validator(this, opts);
1001
- }
1002
- });
1003
- else base = {
1087
+ const payload = {
1004
1088
  __is_atscript_annotated_type: true,
1005
1089
  metadata,
1006
1090
  type,
1007
- validator(opts) {
1008
- return new Validator(this, opts);
1009
- }
1091
+ validator: validatorMethod
1010
1092
  };
1093
+ base = base ? Object.assign(base, payload) : payload;
1011
1094
  const handle = {
1012
1095
  $type: base,
1013
1096
  $def: type,
1014
1097
  $metadata: metadata,
1015
- _existingObject: undefined,
1016
1098
  tags(...tags) {
1017
1099
  for (const tag of tags) this.$def.tags.add(tag);
1018
1100
  return this;
@@ -1053,27 +1135,18 @@ else base = {
1053
1135
  return this;
1054
1136
  },
1055
1137
  refTo(type$1, chain) {
1138
+ if (!isAnnotatedType(type$1)) throw new Error(`${type$1} is not annotated type`);
1056
1139
  let newBase = type$1;
1057
1140
  const typeName = type$1.name || "Unknown";
1058
- if (isAnnotatedType(newBase)) {
1059
- let keys = "";
1060
- for (const c of chain || []) {
1061
- keys += `["${c}"]`;
1062
- if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
1063
- else throw new Error(`Can't find prop ${typeName}${keys}`);
1141
+ if (chain) for (let i = 0; i < chain.length; i++) {
1142
+ const c = chain[i];
1143
+ if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
1144
+ else {
1145
+ const keys = chain.slice(0, i + 1).map((k) => `["${k}"]`).join("");
1146
+ throw new Error(`Can't find prop ${typeName}${keys}`);
1064
1147
  }
1065
- if (!newBase && keys) throw new Error(`Can't find prop ${typeName}${keys}`);
1066
- else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
1067
- this.$type = {
1068
- __is_atscript_annotated_type: true,
1069
- type: newBase.type,
1070
- metadata,
1071
- id: newBase.id,
1072
- validator(opts) {
1073
- return new Validator(this, opts);
1074
- }
1075
- };
1076
- } else throw new Error(`${type$1} is not annotated type`);
1148
+ }
1149
+ this.$type = createAnnotatedTypeNode(newBase.type, metadata, { id: newBase.id });
1077
1150
  return this;
1078
1151
  },
1079
1152
  annotate(key, value, asArray) {
@@ -1091,6 +1164,24 @@ function isPhantomType(def) {
1091
1164
  return def.type.kind === "" && def.type.designType === "phantom";
1092
1165
  }
1093
1166
 
1167
+ //#endregion
1168
+ //#region packages/typescript/src/traverse.ts
1169
+ function forAnnotatedType(def, handlers) {
1170
+ switch (def.type.kind) {
1171
+ case "": {
1172
+ const typed = def;
1173
+ if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
1174
+ return handlers.final(typed);
1175
+ }
1176
+ case "object": return handlers.object(def);
1177
+ case "array": return handlers.array(def);
1178
+ case "union": return handlers.union(def);
1179
+ case "intersection": return handlers.intersection(def);
1180
+ case "tuple": return handlers.tuple(def);
1181
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
1182
+ }
1183
+ }
1184
+
1094
1185
  //#endregion
1095
1186
  //#region packages/typescript/src/json-schema.ts
1096
1187
  /**
@@ -1105,10 +1196,10 @@ function isPhantomType(def) {
1105
1196
  const firstObj = items[0].type;
1106
1197
  const candidates = [];
1107
1198
  for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
1108
- const validCandidates = [];
1199
+ let result = null;
1109
1200
  for (const candidate of candidates) {
1110
1201
  const values = new Set();
1111
- const mapping = {};
1202
+ const indexMapping = {};
1112
1203
  let valid = true;
1113
1204
  for (let i = 0; i < items.length; i++) {
1114
1205
  const obj = items[i].type;
@@ -1123,19 +1214,21 @@ function isPhantomType(def) {
1123
1214
  break;
1124
1215
  }
1125
1216
  values.add(val);
1126
- mapping[String(val)] = `#/oneOf/${i}`;
1217
+ indexMapping[String(val)] = i;
1218
+ }
1219
+ if (valid) {
1220
+ if (result) return null;
1221
+ result = {
1222
+ propertyName: candidate,
1223
+ indexMapping
1224
+ };
1127
1225
  }
1128
- if (valid) validCandidates.push({
1129
- propertyName: candidate,
1130
- mapping
1131
- });
1132
1226
  }
1133
- if (validCandidates.length === 1) return validCandidates[0];
1134
- return null;
1227
+ return result;
1135
1228
  }
1136
1229
  function buildJsonSchema(type) {
1137
1230
  const defs = {};
1138
- let isRoot = true;
1231
+ let hasDefs = false;
1139
1232
  const buildObject = (d) => {
1140
1233
  const properties = {};
1141
1234
  const required = [];
@@ -1152,15 +1245,15 @@ function buildJsonSchema(type) {
1152
1245
  return schema$1;
1153
1246
  };
1154
1247
  const build$1 = (def) => {
1155
- if (def.id && def.type.kind === "object" && !isRoot) {
1248
+ if (def.id && def.type.kind === "object" && def !== type) {
1156
1249
  const name = def.id;
1157
1250
  if (!defs[name]) {
1251
+ hasDefs = true;
1158
1252
  defs[name] = {};
1159
1253
  defs[name] = buildObject(def);
1160
1254
  }
1161
1255
  return { $ref: `#/$defs/${name}` };
1162
1256
  }
1163
- isRoot = false;
1164
1257
  const meta = def.metadata;
1165
1258
  return forAnnotatedType(def, {
1166
1259
  phantom() {
@@ -1185,11 +1278,9 @@ function buildJsonSchema(type) {
1185
1278
  if (disc) {
1186
1279
  const oneOf = d.type.items.map(build$1);
1187
1280
  const mapping = {};
1188
- for (const [val, origPath] of Object.entries(disc.mapping)) {
1189
- const idx = Number.parseInt(origPath.split("/").pop(), 10);
1281
+ for (const [val, idx] of Object.entries(disc.indexMapping)) {
1190
1282
  const item = d.type.items[idx];
1191
- if (item.id && defs[item.id]) mapping[val] = `#/$defs/${item.id}`;
1192
- else mapping[val] = origPath;
1283
+ mapping[val] = item.id && defs[item.id] ? `#/$defs/${item.id}` : `#/oneOf/${idx}`;
1193
1284
  }
1194
1285
  return {
1195
1286
  oneOf,
@@ -1239,7 +1330,7 @@ else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ patte
1239
1330
  });
1240
1331
  };
1241
1332
  const schema = build$1(type);
1242
- if (Object.keys(defs).length > 0) return {
1333
+ if (hasDefs) return {
1243
1334
  ...schema,
1244
1335
  $defs: defs
1245
1336
  };
@@ -1263,24 +1354,25 @@ var JsRenderer = class extends BaseRenderer {
1263
1354
  this.writeln("// prettier-ignore-start");
1264
1355
  this.writeln("/* eslint-disable */");
1265
1356
  this.writeln("/* oxlint-disable */");
1357
+ let hasMutatingAnnotate = false;
1358
+ const nodesByName = new Map();
1359
+ for (const node of this.doc.nodes) {
1360
+ if (node.entity === "annotate" && node.isMutating) hasMutatingAnnotate = true;
1361
+ if (node.__typeId !== null && node.__typeId !== undefined && node.id) {
1362
+ const name = node.id;
1363
+ if (!nodesByName.has(name)) nodesByName.set(name, []);
1364
+ nodesByName.get(name).push(node);
1365
+ }
1366
+ }
1367
+ for (const [name, nodes] of nodesByName) if (nodes.length === 1) this.typeIds.set(nodes[0], name);
1368
+ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}__${i + 1}`);
1266
1369
  const imports = ["defineAnnotatedType as $", "annotate as $a"];
1267
- const hasMutatingAnnotate = this.doc.nodes.some((n) => n.entity === "annotate" && n.isMutating);
1268
1370
  if (hasMutatingAnnotate) imports.push("cloneRefProp as $c");
1269
1371
  const jsonSchemaMode = resolveJsonSchemaMode(this.opts);
1270
1372
  if (jsonSchemaMode === "lazy") imports.push("buildJsonSchema as $$");
1271
1373
  if (this.opts?.exampleData) imports.push("createDataFromAnnotatedType as $e");
1272
1374
  if (jsonSchemaMode === false) imports.push("throwFeatureDisabled as $d");
1273
1375
  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
1376
  }
1285
1377
  buildAdHocMap(annotateNodes) {
1286
1378
  const map = new Map();
@@ -1289,7 +1381,8 @@ else for (let i = 0; i < nodes.length; i++) this.typeIds.set(nodes[i], `${name}_
1289
1381
  const anns = entry.annotations || [];
1290
1382
  if (anns.length > 0) {
1291
1383
  const existing = map.get(path$3);
1292
- map.set(path$3, existing ? [...existing, ...anns] : anns);
1384
+ if (existing) existing.push(...anns);
1385
+ else map.set(path$3, [...anns]);
1293
1386
  }
1294
1387
  }
1295
1388
  return map.size > 0 ? map : null;
@@ -1348,35 +1441,20 @@ else def = def.getDefinition() || def;
1348
1441
  this.renderExampleDataMethod(node);
1349
1442
  }
1350
1443
  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();
1444
+ this.renderDefinitionClass(node);
1360
1445
  }
1361
1446
  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();
1447
+ this.renderDefinitionClass(node);
1371
1448
  }
1372
1449
  renderAnnotate(node) {
1373
1450
  if (node.isMutating) {
1374
1451
  this.postAnnotate.push(node);
1375
1452
  return;
1376
1453
  }
1377
- const targetName = node.targetName;
1378
- const unwound = this.doc.unwindType(targetName);
1379
- if (!unwound?.def) return;
1454
+ if (!this.doc.unwindType(node.targetName)?.def) return;
1455
+ this.renderDefinitionClass(node);
1456
+ }
1457
+ renderDefinitionClass(node) {
1380
1458
  this.writeln();
1381
1459
  const exported = node.token("export")?.text === "export";
1382
1460
  this.write(exported ? "export " : "");
@@ -1534,6 +1612,11 @@ else handle.prop(prop.id, propHandle.$type);
1534
1612
  handle.annotate(a.name, true);
1535
1613
  break;
1536
1614
  }
1615
+ case "expect.array.uniqueItems":
1616
+ case "expect.array.key": {
1617
+ handle.annotate(a.name, { message: a.args[0]?.text });
1618
+ break;
1619
+ }
1537
1620
  default:
1538
1621
  }
1539
1622
  });
@@ -1601,10 +1684,7 @@ else handle.prop(prop.id, propHandle.$type);
1601
1684
  this.writeln(`$("array"${name ? `, ${name}` : ""})`).indent().defineArray(node).unindent();
1602
1685
  return this;
1603
1686
  }
1604
- default: {
1605
- console.log("!!!!!!! UNKNOWN", node.entity);
1606
- return this;
1607
- }
1687
+ default: return this;
1608
1688
  }
1609
1689
  }
1610
1690
  defineConst(node) {
@@ -1816,40 +1896,25 @@ else targetValue = "true";
1816
1896
  this.writeln(`$c(${clone.parentPath}, "${escapeQuotes(clone.propName)}")`);
1817
1897
  }
1818
1898
  }
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
- }
1899
+ for (const { entry, accessors } of entryAccessors) for (const accessor of accessors) this.emitMutatingAnnotations(entry, entry.annotations, accessor);
1836
1900
  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
- }
1901
+ if (topAnnotations && topAnnotations.length > 0) this.emitMutatingAnnotations(node, topAnnotations, targetName);
1851
1902
  this.writeln();
1852
1903
  }
1904
+ emitMutatingAnnotations(node, annotations, accessor) {
1905
+ const cleared = new Set();
1906
+ for (const an of annotations) {
1907
+ const { value, multiple } = this.computeAnnotationValue(node, an);
1908
+ if (multiple) {
1909
+ if (!cleared.has(an.name)) {
1910
+ const spec = this.doc.resolveAnnotation(an.name);
1911
+ if (!spec || spec.config.mergeStrategy !== "append") this.writeln(`${accessor}.metadata.delete("${escapeQuotes(an.name)}")`);
1912
+ cleared.add(an.name);
1913
+ }
1914
+ this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value}, true)`);
1915
+ } else this.writeln(`$a(${accessor}.metadata, "${escapeQuotes(an.name)}", ${value})`);
1916
+ }
1917
+ }
1853
1918
  resolveTargetDef(targetName) {
1854
1919
  const unwound = this.doc.unwindType(targetName);
1855
1920
  if (!unwound?.def) return undefined;