@atscript/typescript 0.1.34 → 0.1.35

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
@@ -1,8 +1,9 @@
1
+ import { buildJsonSchema, defineAnnotatedType } from "./json-schema-0UUPoHud.mjs";
1
2
  import path from "path";
2
- import { DEFAULT_FORMAT, flattenInterfaceNode, isArray, isConst, isGroup, isInterface, isPrimitive, isRef, isStructure } from "@atscript/core";
3
+ import { DEFAULT_FORMAT, flattenInterfaceNode, hasNavPropAnnotation, isArray, isConst, isGroup, isInterface, isPrimitive, isQueryLogical, isRef, isStructure } from "@atscript/core";
3
4
 
4
5
  //#region packages/typescript/src/codegen/code-printer.ts
5
- function _define_property$4(obj, key, value) {
6
+ function _define_property$3(obj, key, value) {
6
7
  if (key in obj) Object.defineProperty(obj, key, {
7
8
  value,
8
9
  enumerable: true,
@@ -95,17 +96,17 @@ else this.write(closing);
95
96
  return " ".repeat(this.indentLevel * this.indentSize);
96
97
  }
97
98
  constructor() {
98
- _define_property$4(this, "lines", []);
99
- _define_property$4(this, "currentLine", "");
100
- _define_property$4(this, "indentLevel", 0);
101
- _define_property$4(this, "indentSize", 2);
102
- _define_property$4(this, "blockStack", []);
99
+ _define_property$3(this, "lines", []);
100
+ _define_property$3(this, "currentLine", "");
101
+ _define_property$3(this, "indentLevel", 0);
102
+ _define_property$3(this, "indentSize", 2);
103
+ _define_property$3(this, "blockStack", []);
103
104
  }
104
105
  };
105
106
 
106
107
  //#endregion
107
108
  //#region packages/typescript/src/codegen/base-renderer.ts
108
- function _define_property$3(obj, key, value) {
109
+ function _define_property$2(obj, key, value) {
109
110
  if (key in obj) Object.defineProperty(obj, key, {
110
111
  value,
111
112
  enumerable: true,
@@ -172,7 +173,7 @@ var BaseRenderer = class extends CodePrinter {
172
173
  }
173
174
  }
174
175
  constructor(doc) {
175
- super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "_unused", void 0), this.doc = doc;
176
+ super(), _define_property$2(this, "doc", void 0), _define_property$2(this, "_unused", void 0), this.doc = doc;
176
177
  }
177
178
  };
178
179
 
@@ -189,7 +190,7 @@ function escapeQuotes(str) {
189
190
 
190
191
  //#endregion
191
192
  //#region packages/typescript/src/codegen/type-renderer.ts
192
- function _define_property$2(obj, key, value) {
193
+ function _define_property$1(obj, key, value) {
193
194
  if (key in obj) Object.defineProperty(obj, key, {
194
195
  value,
195
196
  enumerable: true,
@@ -314,8 +315,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
314
315
  this.writeln("static toJsonSchema: () => any");
315
316
  if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
316
317
  this.writeln("static toExampleData?: () => any");
317
- if (interfaceNode && this.hasDbTable(interfaceNode)) {
318
+ if (interfaceNode && this.hasDbEntity(interfaceNode)) {
318
319
  this.renderFlat(interfaceNode);
320
+ this.renderOwnProps(interfaceNode);
321
+ this.renderNavProps(interfaceNode);
319
322
  this.renderPk(interfaceNode);
320
323
  }
321
324
  }
@@ -406,14 +409,13 @@ else if (isPrimitive(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
406
409
  this.popln();
407
410
  }
408
411
  /**
409
- * Checks whether an interface has the `@db.table` annotation.
412
+ * Checks whether an interface is a DB entity (`@db.table` or `@db.view`).
410
413
  *
411
- * NOTE: Only `@db.table` interfaces get the `__flat` static property.
412
- * This is intentionally hardcoded `__flat` exists solely to improve
413
- * type-safety for filter expressions and `$select`/`$sort` operations
414
- * in the DB layer. Non-DB interfaces have no use for dot-notation path types.
415
- */ hasDbTable(node) {
416
- return !!node.annotations?.some((a) => a.name === "db.table");
414
+ * Only DB entities get `__flat`, `__pk`, `__ownProps`, and `__navProps` static properties.
415
+ * These exist solely to improve type-safety for filter expressions, `$select`/`$sort`,
416
+ * and `$with` operations in the DB layer.
417
+ */ hasDbEntity(node) {
418
+ return !!node.annotations?.some((a) => a.name === "db.table" || a.name === "db.view" || a.name === "db.view.for");
417
419
  }
418
420
  /**
419
421
  * Renders the `static __flat` property — a map of all dot-notation paths
@@ -430,9 +432,27 @@ else if (isPrimitive(realDef)) typeDef = `TAtscriptTypeFinal<${name}>`;
430
432
  * not individually queryable)
431
433
  * - **Leaf fields** → their original TypeScript type
432
434
  */ renderFlat(node) {
433
- const flatMap = flattenInterfaceNode(this.doc, node);
435
+ this.renderFlatMap("__flat", flattenInterfaceNode(this.doc, node));
436
+ }
437
+ /**
438
+ * Renders the `static __ownProps` property — table-owned fields only (no nav props).
439
+ */ renderOwnProps(node) {
440
+ this.renderFlatMap("__ownProps", flattenInterfaceNode(this.doc, node, { skipNavProps: true }), {
441
+ leadingNewline: true,
442
+ trailingNewline: true
443
+ });
444
+ }
445
+ /**
446
+ * Shared renderer for flat-map static properties (`__flat`, `__ownProps`).
447
+ *
448
+ * Special rendering rules:
449
+ * - **Intermediate paths** (structures, arrays of structures) → `never`
450
+ * - **`@db.json` fields** → `string` (stored as serialized JSON in DB)
451
+ * - **Leaf fields** → their original TypeScript type
452
+ */ renderFlatMap(propName, flatMap, opts) {
434
453
  if (flatMap.size === 0) return;
435
- this.write("static __flat: ");
454
+ if (opts?.leadingNewline) this.writeln();
455
+ this.write(`static ${propName}: `);
436
456
  this.blockln("{}");
437
457
  for (const [path$1, descriptor] of flatMap) {
438
458
  this.write(`"${escapeQuotes(path$1)}"`);
@@ -447,19 +467,46 @@ else {
447
467
  renderedDef.split("\n").forEach((l) => this.writeln(l));
448
468
  }
449
469
  }
450
- this.pop();
470
+ if (opts?.trailingNewline) this.popln();
471
+ else this.pop();
451
472
  }
452
473
  /**
453
- * Renders the `static __pk` property — the primary key type for type-safe
454
- * `deleteOne`/`findById` signatures on `AtscriptDbTable`.
474
+ * Renders the `static __navProps` property — a map of navigation property names
475
+ * to their declared TypeScript types.
455
476
  *
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 (unless unique indexes exist)
459
- * - **Unique indexes** (`@db.index.unique`) → appended as union members
460
- * - **Mongo collection** → always includes `string` (ObjectId) in the union;
461
- * if no `@meta.id` fields, `__pk` is just `string`
462
- */ renderPk(node) {
477
+ * This enables type-safe `$with` in queries: `name` is constrained to known
478
+ * navigation property keys, and nested filter/controls are typed to the target entity.
479
+ */ renderNavProps(node) {
480
+ let struct;
481
+ if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
482
+ if (!struct) struct = node.getDefinition();
483
+ if (!struct || !isStructure(struct)) return;
484
+ const structNode = struct;
485
+ const navProps = [];
486
+ for (const [name, prop] of structNode.props) {
487
+ if (prop.token("identifier")?.pattern) continue;
488
+ if (hasNavPropAnnotation(prop)) navProps.push({
489
+ name,
490
+ prop
491
+ });
492
+ }
493
+ if (navProps.length === 0) return;
494
+ this.writeln();
495
+ this.write("static __navProps: ");
496
+ this.blockln("{}");
497
+ for (const { name, prop } of navProps) {
498
+ this.write(`"${escapeQuotes(name)}"`);
499
+ if (prop.token("optional")) this.write("?");
500
+ this.write(": ");
501
+ const propDef = prop.getDefinition();
502
+ if (propDef) {
503
+ const renderedDef = this.renderTypeDefString(propDef);
504
+ renderedDef.split("\n").forEach((l) => this.writeln(l));
505
+ } else this.writeln("unknown");
506
+ }
507
+ this.popln();
508
+ }
509
+ renderPk(node) {
463
510
  const isMongoCollection = !!node.annotations?.some((a) => a.name === "db.mongo.collection");
464
511
  let struct;
465
512
  if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
@@ -559,7 +606,7 @@ else if (pkProps.length === 1) {
559
606
  this.writeln(` */`);
560
607
  }
561
608
  constructor(doc, opts) {
562
- super(doc), _define_property$2(this, "opts", void 0), this.opts = opts;
609
+ super(doc), _define_property$1(this, "opts", void 0), this.opts = opts;
563
610
  }
564
611
  };
565
612
  function renderPrimitiveTypeDef(def) {
@@ -579,777 +626,6 @@ function renderPrimitiveTypeDef(def) {
579
626
  }
580
627
  }
581
628
 
582
- //#endregion
583
- //#region packages/typescript/src/validator.ts
584
- function _define_property$1(obj, key, value) {
585
- if (key in obj) Object.defineProperty(obj, key, {
586
- value,
587
- enumerable: true,
588
- configurable: true,
589
- writable: true
590
- });
591
- else obj[key] = value;
592
- return obj;
593
- }
594
- const regexCache = new Map();
595
- var Validator = class {
596
- isLimitExceeded() {
597
- if (this.stackErrors.length > 0) {
598
- const top = this.stackErrors[this.stackErrors.length - 1];
599
- return top !== null && top.length >= this.opts.errorLimit;
600
- }
601
- return this.errors.length >= this.opts.errorLimit;
602
- }
603
- push(name) {
604
- this.stackPath.push(name);
605
- this.stackErrors.push(null);
606
- this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
607
- }
608
- pop(saveErrors) {
609
- this.stackPath.pop();
610
- const popped = this.stackErrors.pop();
611
- if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
612
- this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
613
- return popped;
614
- }
615
- clear() {
616
- this.stackErrors[this.stackErrors.length - 1] = null;
617
- }
618
- error(message, path$1, details) {
619
- let errors = this.stackErrors[this.stackErrors.length - 1];
620
- if (!errors) if (this.stackErrors.length > 0) {
621
- errors = [];
622
- this.stackErrors[this.stackErrors.length - 1] = errors;
623
- } else errors = this.errors;
624
- const error = {
625
- path: path$1 || this.cachedPath,
626
- message
627
- };
628
- if (details?.length) error.details = details;
629
- errors.push(error);
630
- }
631
- throw() {
632
- throw new ValidatorError(this.errors);
633
- }
634
- /**
635
- * Validates a value against the type definition.
636
- *
637
- * Acts as a TypeScript type guard — when it returns `true`, the value
638
- * is narrowed to `DataType`.
639
- *
640
- * @param value - The value to validate.
641
- * @param safe - If `true`, returns `false` on failure instead of throwing.
642
- * @returns `true` if the value matches the type definition.
643
- * @throws {ValidatorError} When validation fails and `safe` is not `true`.
644
- */ validate(value, safe, context) {
645
- this.errors = [];
646
- this.stackErrors = [];
647
- this.stackPath = [""];
648
- this.cachedPath = "";
649
- this.context = context;
650
- const passed = this.validateSafe(this.def, value);
651
- this.pop(!passed);
652
- this.context = undefined;
653
- if (!passed) {
654
- if (safe) return false;
655
- this.throw();
656
- }
657
- return true;
658
- }
659
- validateSafe(def, value) {
660
- if (this.isLimitExceeded()) return false;
661
- if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
662
- if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
663
- if (def.optional && value === undefined) return true;
664
- for (const plugin of this.opts.plugins) {
665
- const result = plugin(this, def, value);
666
- if (result === false || result === true) return result;
667
- }
668
- return this.validateAnnotatedType(def, value);
669
- }
670
- get path() {
671
- return this.cachedPath;
672
- }
673
- validateAnnotatedType(def, value) {
674
- switch (def.type.kind) {
675
- case "": {
676
- if (def.type.designType === "phantom") return true;
677
- return this.validatePrimitive(def, value);
678
- }
679
- case "object": return this.validateObject(def, value);
680
- case "array": return this.validateArray(def, value);
681
- case "union": return this.validateUnion(def, value);
682
- case "intersection": return this.validateIntersection(def, value);
683
- case "tuple": return this.validateTuple(def, value);
684
- default: throw new Error(`Unknown type kind "${def.type.kind}"`);
685
- }
686
- }
687
- validateUnion(def, value) {
688
- let i = 0;
689
- const popped = [];
690
- for (const item of def.type.items) {
691
- this.push(`[${item.type.kind || item.type.designType}(${i})]`);
692
- if (this.validateSafe(item, value)) {
693
- this.pop(false);
694
- return true;
695
- }
696
- const errors = this.pop(false);
697
- if (errors) popped.push(...errors);
698
- i++;
699
- }
700
- this.clear();
701
- const expected = def.type.items.map((item, i$1) => `[${item.type.kind || item.type.designType}(${i$1})]`).join(", ");
702
- this.error(`Value does not match any of the allowed types: ${expected}`, undefined, popped);
703
- return false;
704
- }
705
- validateIntersection(def, value) {
706
- for (const item of def.type.items) if (!this.validateSafe(item, value)) return false;
707
- return true;
708
- }
709
- validateTuple(def, value) {
710
- if (!Array.isArray(value) || value.length !== def.type.items.length) {
711
- this.error(`Expected array of length ${def.type.items.length}`);
712
- return false;
713
- }
714
- let i = 0;
715
- for (const item of def.type.items) {
716
- this.push(String(i));
717
- if (!this.validateSafe(item, value[i])) {
718
- this.pop(true);
719
- return false;
720
- }
721
- this.pop(false);
722
- i++;
723
- }
724
- return true;
725
- }
726
- validateArray(def, value) {
727
- if (!Array.isArray(value)) {
728
- this.error("Expected array");
729
- return false;
730
- }
731
- const minLength = def.metadata.get("expect.minLength");
732
- if (minLength) {
733
- const length = typeof minLength === "number" ? minLength : minLength.length;
734
- if (value.length < length) {
735
- const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} items, got ${value.length} items`;
736
- this.error(message);
737
- return false;
738
- }
739
- }
740
- const maxLength = def.metadata.get("expect.maxLength");
741
- if (maxLength) {
742
- const length = typeof maxLength === "number" ? maxLength : maxLength.length;
743
- if (value.length > length) {
744
- const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} items, got ${value.length} items`;
745
- this.error(message);
746
- return false;
747
- }
748
- }
749
- const uniqueItems = def.metadata.get("expect.array.uniqueItems");
750
- if (uniqueItems) {
751
- const separator = "▼↩";
752
- const seen = new Set();
753
- const keyProps = new Set();
754
- if (def.type.of.type.kind === "object") {
755
- for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
756
- }
757
- for (let idx = 0; idx < value.length; idx++) {
758
- const item = value[idx];
759
- let key;
760
- if (keyProps.size > 0) {
761
- key = "";
762
- for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
763
- } else key = JSON.stringify(item);
764
- if (seen.has(key)) {
765
- this.push(String(idx));
766
- this.error(uniqueItems.message || "Duplicate items are not allowed");
767
- this.pop(true);
768
- return false;
769
- }
770
- seen.add(key);
771
- }
772
- }
773
- let i = 0;
774
- let passed = true;
775
- for (const item of value) {
776
- this.push(String(i));
777
- if (!this.validateSafe(def.type.of, item)) {
778
- passed = false;
779
- this.pop(true);
780
- if (this.isLimitExceeded()) return false;
781
- } else this.pop(false);
782
- i++;
783
- }
784
- return passed;
785
- }
786
- validateObject(def, value) {
787
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
788
- this.error("Expected object");
789
- return false;
790
- }
791
- let passed = true;
792
- const valueKeys = new Set(Object.keys(value));
793
- const typeKeys = new Set();
794
- let skipList;
795
- if (this.opts.skipList) {
796
- const path$1 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
797
- for (const item of this.opts.skipList) if (item.startsWith(path$1)) {
798
- const key = item.slice(path$1.length);
799
- if (!skipList) skipList = new Set();
800
- skipList.add(key);
801
- valueKeys.delete(key);
802
- }
803
- }
804
- let partialFunctionMatched = false;
805
- if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
806
- for (const [key, item] of def.type.props.entries()) {
807
- if (skipList && skipList.has(key) || isPhantomType(item)) continue;
808
- typeKeys.add(key);
809
- if (value[key] === undefined) {
810
- if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
811
- }
812
- this.push(key);
813
- if (this.validateSafe(item, value[key])) this.pop(false);
814
- else {
815
- passed = false;
816
- this.pop(true);
817
- if (this.isLimitExceeded()) return false;
818
- }
819
- }
820
- for (const key of valueKeys)
821
- /** matched patterns for unknown keys */ if (!typeKeys.has(key)) {
822
- const matched = [];
823
- for (const { pattern, def: propDef } of def.type.propsPatterns) if (pattern.test(key)) matched.push({
824
- pattern,
825
- def: propDef
826
- });
827
- if (matched.length > 0) {
828
- this.push(key);
829
- let keyPassed = false;
830
- for (const { def: propDef } of matched) {
831
- if (this.validateSafe(propDef, value[key])) {
832
- keyPassed = true;
833
- break;
834
- }
835
- this.clear();
836
- }
837
- if (!keyPassed) {
838
- this.validateSafe(matched[0].def, value[key]);
839
- this.pop(true);
840
- passed = false;
841
- if (this.isLimitExceeded()) return false;
842
- } else this.pop(false);
843
- } else if (this.opts.unknownProps !== "ignore") {
844
- if (this.opts.unknownProps === "error") {
845
- this.push(key);
846
- this.error(`Unexpected property`);
847
- this.pop(true);
848
- if (this.isLimitExceeded()) return false;
849
- passed = false;
850
- } else if (this.opts.unknownProps === "strip") delete value[key];
851
- }
852
- }
853
- return passed;
854
- }
855
- validatePrimitive(def, value) {
856
- if (def.type.value !== undefined) {
857
- if (value !== def.type.value) {
858
- this.error(`Expected ${def.type.value}, got ${value}`);
859
- return false;
860
- }
861
- return true;
862
- }
863
- const typeOfValue = Array.isArray(value) ? "array" : typeof value;
864
- switch (def.type.designType) {
865
- case "never": {
866
- this.error(`This type is impossible, must be an internal problem`);
867
- return false;
868
- }
869
- case "any": return true;
870
- case "string": {
871
- if (typeOfValue !== def.type.designType) {
872
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
873
- return false;
874
- }
875
- return this.validateString(def, value);
876
- }
877
- case "number": {
878
- if (typeOfValue !== def.type.designType) {
879
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
880
- return false;
881
- }
882
- return this.validateNumber(def, value);
883
- }
884
- case "boolean": {
885
- if (typeOfValue !== def.type.designType) {
886
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
887
- return false;
888
- }
889
- return this.validateBoolean(def, value);
890
- }
891
- case "undefined": {
892
- if (value !== undefined) {
893
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
894
- return false;
895
- }
896
- return true;
897
- }
898
- case "null": {
899
- if (value !== null) {
900
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
901
- return false;
902
- }
903
- return true;
904
- }
905
- default: throw new Error(`Unknown type "${def.type.designType}"`);
906
- }
907
- }
908
- validateString(def, value) {
909
- const filled = def.metadata.get("meta.required");
910
- if (filled) {
911
- if (value.trim().length === 0) {
912
- const message = typeof filled === "object" && filled.message ? filled.message : `Must not be empty`;
913
- this.error(message);
914
- return false;
915
- }
916
- }
917
- const minLength = def.metadata.get("expect.minLength");
918
- if (minLength) {
919
- const length = typeof minLength === "number" ? minLength : minLength.length;
920
- if (value.length < length) {
921
- const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} characters, got ${value.length} characters`;
922
- this.error(message);
923
- return false;
924
- }
925
- }
926
- const maxLength = def.metadata.get("expect.maxLength");
927
- if (maxLength) {
928
- const length = typeof maxLength === "number" ? maxLength : maxLength.length;
929
- if (value.length > length) {
930
- const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} characters, got ${value.length} characters`;
931
- this.error(message);
932
- return false;
933
- }
934
- }
935
- const patterns = def.metadata.get("expect.pattern");
936
- for (const { pattern, flags, message } of patterns || []) {
937
- if (!pattern) continue;
938
- const cacheKey = `${pattern}//${flags || ""}`;
939
- let regex = regexCache.get(cacheKey);
940
- if (!regex) {
941
- regex = new RegExp(pattern, flags);
942
- regexCache.set(cacheKey, regex);
943
- }
944
- if (!regex.test(value)) {
945
- this.error(message || `Value is expected to match pattern "${pattern}"`);
946
- return false;
947
- }
948
- }
949
- return true;
950
- }
951
- validateNumber(def, value) {
952
- const int = def.metadata.get("expect.int");
953
- if (int && value % 1 !== 0) {
954
- const message = typeof int === "object" && int.message ? int.message : `Expected integer, got ${value}`;
955
- this.error(message);
956
- return false;
957
- }
958
- const min = def.metadata.get("expect.min");
959
- if (min) {
960
- const minValue = typeof min === "number" ? min : min.minValue;
961
- if (value < minValue) {
962
- const message = typeof min === "object" && min.message ? min.message : `Expected minimum ${minValue}, got ${value}`;
963
- this.error(message);
964
- return false;
965
- }
966
- }
967
- const max = def.metadata.get("expect.max");
968
- if (max) {
969
- const maxValue = typeof max === "number" ? max : max.maxValue;
970
- if (value > maxValue) {
971
- const message = typeof max === "object" && max.message ? max.message : `Expected maximum ${maxValue}, got ${value}`;
972
- this.error(message);
973
- return false;
974
- }
975
- }
976
- return true;
977
- }
978
- validateBoolean(def, value) {
979
- const filled = def.metadata.get("meta.required");
980
- if (filled) {
981
- if (value !== true) {
982
- const message = typeof filled === "object" && filled.message ? filled.message : `Must be checked`;
983
- this.error(message);
984
- return false;
985
- }
986
- }
987
- return true;
988
- }
989
- constructor(def, opts) {
990
- _define_property$1(this, "def", void 0);
991
- _define_property$1(this, "opts", void 0);
992
- /** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
993
- _define_property$1(this, "stackErrors", void 0);
994
- _define_property$1(this, "stackPath", void 0);
995
- _define_property$1(this, "cachedPath", void 0);
996
- _define_property$1(this, "context", void 0);
997
- this.def = def;
998
- this.errors = [];
999
- this.stackErrors = [];
1000
- this.stackPath = [];
1001
- this.cachedPath = "";
1002
- this.opts = {
1003
- partial: false,
1004
- unknownProps: "error",
1005
- errorLimit: 10,
1006
- ...opts,
1007
- plugins: opts?.plugins || []
1008
- };
1009
- }
1010
- };
1011
- var ValidatorError = class extends Error {
1012
- constructor(errors) {
1013
- super(`${errors[0].path ? errors[0].path + ": " : ""}${errors[0].message}`), _define_property$1(this, "errors", void 0), _define_property$1(this, "name", void 0), this.errors = errors, this.name = "Validation Error";
1014
- }
1015
- };
1016
-
1017
- //#endregion
1018
- //#region packages/typescript/src/annotated-type.ts
1019
- const COMPLEX_KINDS = new Set([
1020
- "union",
1021
- "intersection",
1022
- "tuple"
1023
- ]);
1024
- /** Shared validator method reused by all annotated type nodes. */ function validatorMethod(opts) {
1025
- return new Validator(this, opts);
1026
- }
1027
- function createAnnotatedTypeNode(type, metadata, opts) {
1028
- return {
1029
- __is_atscript_annotated_type: true,
1030
- type,
1031
- metadata,
1032
- validator: validatorMethod,
1033
- id: opts?.id,
1034
- optional: opts?.optional,
1035
- ref: opts?.ref
1036
- };
1037
- }
1038
- function isAnnotatedType(type) {
1039
- return type && type.__is_atscript_annotated_type;
1040
- }
1041
- function annotate(metadata, key, value, asArray) {
1042
- if (!metadata) return;
1043
- if (asArray) if (metadata.has(key)) {
1044
- const a = metadata.get(key);
1045
- if (Array.isArray(a)) a.push(value);
1046
- else metadata.set(key, [a, value]);
1047
- } else metadata.set(key, [value]);
1048
- else metadata.set(key, value);
1049
- }
1050
- function defineAnnotatedType(_kind, base) {
1051
- const kind = _kind || "";
1052
- const type = base?.type || {};
1053
- type.kind = kind;
1054
- if (COMPLEX_KINDS.has(kind)) type.items = [];
1055
- if (kind === "object") {
1056
- type.props = new Map();
1057
- type.propsPatterns = [];
1058
- }
1059
- type.tags = new Set();
1060
- const metadata = base?.metadata || new Map();
1061
- const payload = {
1062
- __is_atscript_annotated_type: true,
1063
- metadata,
1064
- type,
1065
- validator: validatorMethod
1066
- };
1067
- base = base ? Object.assign(base, payload) : payload;
1068
- const handle = {
1069
- $type: base,
1070
- $def: type,
1071
- $metadata: metadata,
1072
- tags(...tags) {
1073
- for (const tag of tags) this.$def.tags.add(tag);
1074
- return this;
1075
- },
1076
- designType(value) {
1077
- this.$def.designType = value;
1078
- return this;
1079
- },
1080
- value(value) {
1081
- this.$def.value = value;
1082
- return this;
1083
- },
1084
- of(value) {
1085
- this.$def.of = value;
1086
- return this;
1087
- },
1088
- item(value) {
1089
- this.$def.items.push(value);
1090
- return this;
1091
- },
1092
- prop(name, value) {
1093
- this.$def.props.set(name, value);
1094
- return this;
1095
- },
1096
- propPattern(pattern, def) {
1097
- this.$def.propsPatterns.push({
1098
- pattern,
1099
- def
1100
- });
1101
- return this;
1102
- },
1103
- optional(value = true) {
1104
- this.$type.optional = value;
1105
- return this;
1106
- },
1107
- copyMetadata(fromMetadata, ignore) {
1108
- for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
1109
- return this;
1110
- },
1111
- refTo(type$1, chain) {
1112
- if (isAnnotatedType(type$1)) {
1113
- let newBase = type$1;
1114
- const typeName = type$1.name || "Unknown";
1115
- if (chain) for (let i = 0; i < chain.length; i++) {
1116
- const c = chain[i];
1117
- if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
1118
- else {
1119
- const keys = chain.slice(0, i + 1).map((k) => `["${k}"]`).join("");
1120
- throw new Error(`Can't find prop ${typeName}${keys}`);
1121
- }
1122
- }
1123
- this.$type = createAnnotatedTypeNode(newBase.type, metadata, {
1124
- id: newBase.id,
1125
- ref: chain && chain.length > 0 ? {
1126
- type: () => type$1,
1127
- field: chain.join(".")
1128
- } : undefined
1129
- });
1130
- } else if (typeof type$1 === "function") {
1131
- const lazyType = type$1;
1132
- this.$type = createAnnotatedTypeNode({ kind: "" }, metadata, { ref: {
1133
- type: lazyType,
1134
- field: chain ? chain.join(".") : ""
1135
- } });
1136
- const node = this.$type;
1137
- const placeholder = node.type;
1138
- Object.defineProperty(node, "type", {
1139
- get() {
1140
- const t = lazyType();
1141
- if (!isAnnotatedType(t)) {
1142
- Object.defineProperty(node, "type", {
1143
- value: placeholder,
1144
- writable: false,
1145
- configurable: true
1146
- });
1147
- return placeholder;
1148
- }
1149
- let target = t;
1150
- if (chain) for (const c of chain) if (target.type.kind === "object" && target.type.props.has(c)) target = target.type.props.get(c);
1151
- else return t.type;
1152
- node.id = target.id || t.id;
1153
- Object.defineProperty(node, "type", {
1154
- value: target.type,
1155
- writable: false,
1156
- configurable: true
1157
- });
1158
- return target.type;
1159
- },
1160
- configurable: true
1161
- });
1162
- } else throw new Error(`${type$1} is not annotated type`);
1163
- return this;
1164
- },
1165
- annotate(key, value, asArray) {
1166
- annotate(this.$metadata, key, value, asArray);
1167
- return this;
1168
- },
1169
- id(value) {
1170
- this.$type.id = value;
1171
- return this;
1172
- }
1173
- };
1174
- return handle;
1175
- }
1176
- function isPhantomType(def) {
1177
- return def.type.kind === "" && def.type.designType === "phantom";
1178
- }
1179
-
1180
- //#endregion
1181
- //#region packages/typescript/src/traverse.ts
1182
- function forAnnotatedType(def, handlers) {
1183
- switch (def.type.kind) {
1184
- case "": {
1185
- const typed = def;
1186
- if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
1187
- return handlers.final(typed);
1188
- }
1189
- case "object": return handlers.object(def);
1190
- case "array": return handlers.array(def);
1191
- case "union": return handlers.union(def);
1192
- case "intersection": return handlers.intersection(def);
1193
- case "tuple": return handlers.tuple(def);
1194
- default: throw new Error(`Unknown type kind "${def.type.kind}"`);
1195
- }
1196
- }
1197
-
1198
- //#endregion
1199
- //#region packages/typescript/src/json-schema.ts
1200
- /**
1201
- * Detects a discriminator property across union items.
1202
- *
1203
- * Scans all items for object-typed members that share a common property
1204
- * with distinct const/literal values. If exactly one such property exists,
1205
- * it is returned as the discriminator.
1206
- */ function detectDiscriminator(items) {
1207
- if (items.length < 2) return null;
1208
- for (const item of items) if (item.type.kind !== "object") return null;
1209
- const firstObj = items[0].type;
1210
- const candidates = [];
1211
- for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
1212
- let result = null;
1213
- for (const candidate of candidates) {
1214
- const values = new Set();
1215
- const indexMapping = {};
1216
- let valid = true;
1217
- for (let i = 0; i < items.length; i++) {
1218
- const obj = items[i].type;
1219
- const prop = obj.props.get(candidate);
1220
- if (!prop || prop.type.kind !== "" || prop.type.value === undefined) {
1221
- valid = false;
1222
- break;
1223
- }
1224
- const val = prop.type.value;
1225
- if (values.has(val)) {
1226
- valid = false;
1227
- break;
1228
- }
1229
- values.add(val);
1230
- indexMapping[String(val)] = i;
1231
- }
1232
- if (valid) {
1233
- if (result) return null;
1234
- result = {
1235
- propertyName: candidate,
1236
- indexMapping
1237
- };
1238
- }
1239
- }
1240
- return result;
1241
- }
1242
- function buildJsonSchema(type) {
1243
- const defs = {};
1244
- let hasDefs = false;
1245
- const buildObject = (d) => {
1246
- const properties = {};
1247
- const required = [];
1248
- for (const [key, val] of d.type.props.entries()) {
1249
- if (isPhantomType(val)) continue;
1250
- properties[key] = build(val);
1251
- if (!val.optional) required.push(key);
1252
- }
1253
- const schema$1 = {
1254
- type: "object",
1255
- properties
1256
- };
1257
- if (required.length > 0) schema$1.required = required;
1258
- return schema$1;
1259
- };
1260
- const build = (def) => {
1261
- if (def.id && def.type.kind === "object" && def !== type) {
1262
- const name = def.id;
1263
- if (!defs[name]) {
1264
- hasDefs = true;
1265
- defs[name] = {};
1266
- defs[name] = buildObject(def);
1267
- }
1268
- return { $ref: `#/$defs/${name}` };
1269
- }
1270
- const meta = def.metadata;
1271
- return forAnnotatedType(def, {
1272
- phantom() {
1273
- return {};
1274
- },
1275
- object(d) {
1276
- return buildObject(d);
1277
- },
1278
- array(d) {
1279
- const schema$1 = {
1280
- type: "array",
1281
- items: build(d.type.of)
1282
- };
1283
- const minLength = meta.get("expect.minLength");
1284
- if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
1285
- const maxLength = meta.get("expect.maxLength");
1286
- if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1287
- return schema$1;
1288
- },
1289
- union(d) {
1290
- const disc = detectDiscriminator(d.type.items);
1291
- if (disc) {
1292
- const oneOf = d.type.items.map(build);
1293
- const mapping = {};
1294
- for (const [val, idx] of Object.entries(disc.indexMapping)) {
1295
- const item = d.type.items[idx];
1296
- mapping[val] = item.id && defs[item.id] ? `#/$defs/${item.id}` : `#/oneOf/${idx}`;
1297
- }
1298
- return {
1299
- oneOf,
1300
- discriminator: {
1301
- propertyName: disc.propertyName,
1302
- mapping
1303
- }
1304
- };
1305
- }
1306
- return { anyOf: d.type.items.map(build) };
1307
- },
1308
- intersection(d) {
1309
- return { allOf: d.type.items.map(build) };
1310
- },
1311
- tuple(d) {
1312
- return {
1313
- type: "array",
1314
- items: d.type.items.map(build),
1315
- additionalItems: false
1316
- };
1317
- },
1318
- final(d) {
1319
- const schema$1 = {};
1320
- if (d.type.value !== undefined) schema$1.const = d.type.value;
1321
- if (d.type.designType && d.type.designType !== "any") {
1322
- schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1323
- if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
1324
- }
1325
- if (schema$1.type === "string") {
1326
- if (meta.get("meta.required")) schema$1.minLength = 1;
1327
- const minLength = meta.get("expect.minLength");
1328
- if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
1329
- const maxLength = meta.get("expect.maxLength");
1330
- if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1331
- const patterns = meta.get("expect.pattern");
1332
- if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
1333
- else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1334
- }
1335
- if (schema$1.type === "number" || schema$1.type === "integer") {
1336
- const min = meta.get("expect.min");
1337
- if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
1338
- const max = meta.get("expect.max");
1339
- if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
1340
- }
1341
- return schema$1;
1342
- }
1343
- });
1344
- };
1345
- const schema = build(type);
1346
- if (hasDefs) return {
1347
- ...schema,
1348
- $defs: defs
1349
- };
1350
- return schema;
1351
- }
1352
-
1353
629
  //#endregion
1354
630
  //#region packages/typescript/src/codegen/js-renderer.ts
1355
631
  function _define_property(obj, key, value) {
@@ -1362,6 +638,19 @@ function _define_property(obj, key, value) {
1362
638
  else obj[key] = value;
1363
639
  return obj;
1364
640
  }
641
+ const QUERY_OP_MAP = {
642
+ "=": "$eq",
643
+ "!=": "$ne",
644
+ ">": "$gt",
645
+ ">=": "$gte",
646
+ "<": "$lt",
647
+ "<=": "$lte",
648
+ "in": "$in",
649
+ "not in": "$nin",
650
+ "matches": "$regex",
651
+ "exists": "$exists",
652
+ "not exists": "$exists"
653
+ };
1365
654
  var JsRenderer = class extends BaseRenderer {
1366
655
  pre() {
1367
656
  this.writeln("// prettier-ignore-start");
@@ -1857,6 +1146,68 @@ else if (resolved && isPrimitive(resolved)) {
1857
1146
  if (multiple) this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value}, true)`);
1858
1147
  else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value})`);
1859
1148
  }
1149
+ emitRefValue(text) {
1150
+ const dotIdx = text.indexOf(".");
1151
+ if (dotIdx === -1) return `() => ${text}`;
1152
+ const typeName = text.slice(0, dotIdx);
1153
+ const field = text.slice(dotIdx + 1);
1154
+ return `{ type: () => ${typeName}, field: "${escapeQuotes(field)}" }`;
1155
+ }
1156
+ emitArgValue(aSpec, argToken) {
1157
+ if (aSpec.type === "ref") return this.emitRefValue(argToken.text);
1158
+ if (aSpec.type === "query" && argToken.queryNode) return this.emitQueryTree(argToken.queryNode);
1159
+ return aSpec.type === "string" ? `"${escapeQuotes(argToken.text)}"` : argToken.text;
1160
+ }
1161
+ emitQueryTree(queryNode) {
1162
+ return this.emitQueryExpr(queryNode.expression);
1163
+ }
1164
+ emitQueryExpr(node) {
1165
+ if (isQueryLogical(node)) return this.emitQueryLogical(node);
1166
+ return this.emitQueryComparison(node);
1167
+ }
1168
+ emitQueryLogical(node) {
1169
+ if (node.operator === "not") return `{ "$not": ${this.emitQueryExpr(node.operands[0])} }`;
1170
+ const key = node.operator === "and" ? "$and" : "$or";
1171
+ const items = node.operands.map((op) => this.emitQueryExpr(op)).join(", ");
1172
+ return `{ "${key}": [${items}] }`;
1173
+ }
1174
+ emitQueryComparison(node) {
1175
+ const left = this.emitQueryFieldRef(node.left);
1176
+ const mappedOp = QUERY_OP_MAP[node.operator] || node.operator;
1177
+ const parts = [`left: ${left}`, `op: "${mappedOp}"`];
1178
+ if (node.right) {
1179
+ if ("fieldRef" in node.right && node.right.fieldRef) parts.push(`right: ${this.emitQueryFieldRef(node.right)}`);
1180
+ else if ("values" in node.right && node.right.values) {
1181
+ const values = node.right.values.map((v) => this.emitQueryLiteral(v)).join(", ");
1182
+ parts.push(`right: [${values}]`);
1183
+ } else if ("valueToken" in node.right) parts.push(`right: ${this.emitQueryLiteral(node.right)}`);
1184
+ } else if (node.operator === "exists") parts.push("right: true");
1185
+ else if (node.operator === "not exists") parts.push("right: false");
1186
+ return `{ ${parts.join(", ")} }`;
1187
+ }
1188
+ emitQueryFieldRef(node) {
1189
+ const parts = [];
1190
+ if (node.typeRef) parts.push(`type: () => ${node.typeRef.text}`);
1191
+ parts.push(`field: "${escapeQuotes(node.fieldRef.text)}"`);
1192
+ return `{ ${parts.join(", ")} }`;
1193
+ }
1194
+ emitQueryLiteral(node) {
1195
+ const token = node.valueToken;
1196
+ switch (token.type) {
1197
+ case "text": return `"${escapeQuotes(token.text)}"`;
1198
+ case "number": return token.text;
1199
+ case "regexp": {
1200
+ const match = /^\/(.*)\/[a-z]*$/.exec(token.text);
1201
+ return `"${escapeQuotes(match ? match[1] : token.text)}"`;
1202
+ }
1203
+ case "identifier": {
1204
+ if (token.text === "true" || token.text === "false") return token.text;
1205
+ if (token.text === "null" || token.text === "undefined") return "null";
1206
+ return token.text;
1207
+ }
1208
+ default: return token.text;
1209
+ }
1210
+ }
1860
1211
  computeAnnotationValue(node, an) {
1861
1212
  const spec = this.doc.resolveAnnotation(an.name);
1862
1213
  let targetValue = "true";
@@ -1868,13 +1219,13 @@ else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value})`);
1868
1219
  targetValue = "{ ";
1869
1220
  let i = 0;
1870
1221
  for (const aSpec of spec.arguments) {
1871
- if (an.args[i]) targetValue += `${wrapProp(aSpec.name)}: ${aSpec.type === "string" ? `"${escapeQuotes(an.args[i]?.text)}"` : an.args[i]?.text}${i === length - 1 ? "" : ", "} `;
1222
+ if (an.args[i]) targetValue += `${wrapProp(aSpec.name)}: ${this.emitArgValue(aSpec, an.args[i])}${i === length - 1 ? "" : ", "} `;
1872
1223
  i++;
1873
1224
  }
1874
1225
  targetValue += "}";
1875
1226
  } else {
1876
1227
  const aSpec = spec.arguments[0];
1877
- if (an.args[0]) targetValue = aSpec.type === "string" ? `"${escapeQuotes(an.args[0]?.text)}"` : an.args[0]?.text;
1228
+ if (an.args[0]) targetValue = this.emitArgValue(aSpec, an.args[0]);
1878
1229
  else targetValue = "true";
1879
1230
  }
1880
1231
  } else {
@@ -2045,8 +1396,8 @@ const tsPlugin = (opts) => {
2045
1396
  for (const [key, val] of Object.entries(annotations)) {
2046
1397
  const multiple = val.multiple;
2047
1398
  const typeLine = Array.from(val.types).map((t) => {
2048
- if (t.type === "object") return `{ ${Object.entries(t.props).map(([k, v]) => `${wrapProp(k)}${v.optional ? "?" : ""}: ${v.type}`).join(", ")} }`;
2049
- else return t.optional ? `${t.type} | true` : t.type;
1399
+ if (t.type === "object" && "props" in t) return `{ ${Object.entries(t.props).map(([k, v]) => `${wrapProp(k)}${v.optional ? "?" : ""}: ${v.type}`).join(", ")} }`;
1400
+ else return "optional" in t && t.optional ? `${t.type} | true` : t.type;
2050
1401
  }).join(" | ");
2051
1402
  rendered.push(`${wrapProp(key)}: ${multiple ? "(" : ""}${typeLine}${multiple ? ")[]" : ""}`);
2052
1403
  }