@atscript/typescript 0.1.34 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -23,11 +23,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  }) : target, mod));
24
24
 
25
25
  //#endregion
26
+ const require_json_schema = require('./json-schema-S5-XAOrR.cjs');
26
27
  const path = __toESM(require("path"));
27
28
  const __atscript_core = __toESM(require("@atscript/core"));
28
29
 
29
30
  //#region packages/typescript/src/codegen/code-printer.ts
30
- function _define_property$4(obj, key, value) {
31
+ function _define_property$3(obj, key, value) {
31
32
  if (key in obj) Object.defineProperty(obj, key, {
32
33
  value,
33
34
  enumerable: true,
@@ -120,17 +121,17 @@ else this.write(closing);
120
121
  return " ".repeat(this.indentLevel * this.indentSize);
121
122
  }
122
123
  constructor() {
123
- _define_property$4(this, "lines", []);
124
- _define_property$4(this, "currentLine", "");
125
- _define_property$4(this, "indentLevel", 0);
126
- _define_property$4(this, "indentSize", 2);
127
- _define_property$4(this, "blockStack", []);
124
+ _define_property$3(this, "lines", []);
125
+ _define_property$3(this, "currentLine", "");
126
+ _define_property$3(this, "indentLevel", 0);
127
+ _define_property$3(this, "indentSize", 2);
128
+ _define_property$3(this, "blockStack", []);
128
129
  }
129
130
  };
130
131
 
131
132
  //#endregion
132
133
  //#region packages/typescript/src/codegen/base-renderer.ts
133
- function _define_property$3(obj, key, value) {
134
+ function _define_property$2(obj, key, value) {
134
135
  if (key in obj) Object.defineProperty(obj, key, {
135
136
  value,
136
137
  enumerable: true,
@@ -197,7 +198,7 @@ var BaseRenderer = class extends CodePrinter {
197
198
  }
198
199
  }
199
200
  constructor(doc) {
200
- super(), _define_property$3(this, "doc", void 0), _define_property$3(this, "_unused", void 0), this.doc = doc;
201
+ super(), _define_property$2(this, "doc", void 0), _define_property$2(this, "_unused", void 0), this.doc = doc;
201
202
  }
202
203
  };
203
204
 
@@ -214,7 +215,7 @@ function escapeQuotes(str) {
214
215
 
215
216
  //#endregion
216
217
  //#region packages/typescript/src/codegen/type-renderer.ts
217
- function _define_property$2(obj, key, value) {
218
+ function _define_property$1(obj, key, value) {
218
219
  if (key in obj) Object.defineProperty(obj, key, {
219
220
  value,
220
221
  enumerable: true,
@@ -339,8 +340,10 @@ var TypeRenderer = class TypeRenderer extends BaseRenderer {
339
340
  this.writeln("static toJsonSchema: () => any");
340
341
  if (!this.opts?.exampleData) this.writeln("/** @deprecated Example Data support is disabled. To enable, set `exampleData: true` in tsPlugin options. */");
341
342
  this.writeln("static toExampleData?: () => any");
342
- if (interfaceNode && this.hasDbTable(interfaceNode)) {
343
+ if (interfaceNode && this.hasDbEntity(interfaceNode)) {
343
344
  this.renderFlat(interfaceNode);
345
+ this.renderOwnProps(interfaceNode);
346
+ this.renderNavProps(interfaceNode);
344
347
  this.renderPk(interfaceNode);
345
348
  }
346
349
  }
@@ -431,14 +434,13 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFina
431
434
  this.popln();
432
435
  }
433
436
  /**
434
- * Checks whether an interface has the `@db.table` annotation.
437
+ * Checks whether an interface is a DB entity (`@db.table` or `@db.view`).
435
438
  *
436
- * NOTE: Only `@db.table` interfaces get the `__flat` static property.
437
- * This is intentionally hardcoded `__flat` exists solely to improve
438
- * type-safety for filter expressions and `$select`/`$sort` operations
439
- * in the DB layer. Non-DB interfaces have no use for dot-notation path types.
440
- */ hasDbTable(node) {
441
- return !!node.annotations?.some((a) => a.name === "db.table");
439
+ * Only DB entities get `__flat`, `__pk`, `__ownProps`, and `__navProps` static properties.
440
+ * These exist solely to improve type-safety for filter expressions, `$select`/`$sort`,
441
+ * and `$with` operations in the DB layer.
442
+ */ hasDbEntity(node) {
443
+ return !!node.annotations?.some((a) => a.name === "db.table" || a.name === "db.view" || a.name === "db.view.for");
442
444
  }
443
445
  /**
444
446
  * Renders the `static __flat` property — a map of all dot-notation paths
@@ -455,9 +457,27 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = `TAtscriptTypeFina
455
457
  * not individually queryable)
456
458
  * - **Leaf fields** → their original TypeScript type
457
459
  */ renderFlat(node) {
458
- const flatMap = (0, __atscript_core.flattenInterfaceNode)(this.doc, node);
460
+ this.renderFlatMap("__flat", (0, __atscript_core.flattenInterfaceNode)(this.doc, node));
461
+ }
462
+ /**
463
+ * Renders the `static __ownProps` property — table-owned fields only (no nav props).
464
+ */ renderOwnProps(node) {
465
+ this.renderFlatMap("__ownProps", (0, __atscript_core.flattenInterfaceNode)(this.doc, node, { skipNavProps: true }), {
466
+ leadingNewline: true,
467
+ trailingNewline: true
468
+ });
469
+ }
470
+ /**
471
+ * Shared renderer for flat-map static properties (`__flat`, `__ownProps`).
472
+ *
473
+ * Special rendering rules:
474
+ * - **Intermediate paths** (structures, arrays of structures) → `never`
475
+ * - **`@db.json` fields** → `string` (stored as serialized JSON in DB)
476
+ * - **Leaf fields** → their original TypeScript type
477
+ */ renderFlatMap(propName, flatMap, opts) {
459
478
  if (flatMap.size === 0) return;
460
- this.write("static __flat: ");
479
+ if (opts?.leadingNewline) this.writeln();
480
+ this.write(`static ${propName}: `);
461
481
  this.blockln("{}");
462
482
  for (const [path$2, descriptor] of flatMap) {
463
483
  this.write(`"${escapeQuotes(path$2)}"`);
@@ -472,19 +492,46 @@ else {
472
492
  renderedDef.split("\n").forEach((l) => this.writeln(l));
473
493
  }
474
494
  }
475
- this.pop();
495
+ if (opts?.trailingNewline) this.popln();
496
+ else this.pop();
476
497
  }
477
498
  /**
478
- * Renders the `static __pk` property — the primary key type for type-safe
479
- * `deleteOne`/`findById` signatures on `AtscriptDbTable`.
499
+ * Renders the `static __navProps` property — a map of navigation property names
500
+ * to their declared TypeScript types.
480
501
  *
481
- * - **Single PK** (one `@meta.id`) `static __pk: <scalar type>`
482
- * - **Compound PK** (multiple `@meta.id`) `static __pk: { field1: Type1; field2: Type2 }`
483
- * - **No PK** → no `__pk` emitted (unless unique indexes exist)
484
- * - **Unique indexes** (`@db.index.unique`) → appended as union members
485
- * - **Mongo collection** → always includes `string` (ObjectId) in the union;
486
- * if no `@meta.id` fields, `__pk` is just `string`
487
- */ renderPk(node) {
502
+ * This enables type-safe `$with` in queries: `name` is constrained to known
503
+ * navigation property keys, and nested filter/controls are typed to the target entity.
504
+ */ renderNavProps(node) {
505
+ let struct;
506
+ if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
507
+ if (!struct) struct = node.getDefinition();
508
+ if (!struct || !(0, __atscript_core.isStructure)(struct)) return;
509
+ const structNode = struct;
510
+ const navProps = [];
511
+ for (const [name, prop] of structNode.props) {
512
+ if (prop.token("identifier")?.pattern) continue;
513
+ if ((0, __atscript_core.hasNavPropAnnotation)(prop)) navProps.push({
514
+ name,
515
+ prop
516
+ });
517
+ }
518
+ if (navProps.length === 0) return;
519
+ this.writeln();
520
+ this.write("static __navProps: ");
521
+ this.blockln("{}");
522
+ for (const { name, prop } of navProps) {
523
+ this.write(`"${escapeQuotes(name)}"`);
524
+ if (prop.token("optional")) this.write("?");
525
+ this.write(": ");
526
+ const propDef = prop.getDefinition();
527
+ if (propDef) {
528
+ const renderedDef = this.renderTypeDefString(propDef);
529
+ renderedDef.split("\n").forEach((l) => this.writeln(l));
530
+ } else this.writeln("unknown");
531
+ }
532
+ this.popln();
533
+ }
534
+ renderPk(node) {
488
535
  const isMongoCollection = !!node.annotations?.some((a) => a.name === "db.mongo.collection");
489
536
  let struct;
490
537
  if (node.hasExtends) struct = this.doc.resolveInterfaceExtends(node);
@@ -584,7 +631,7 @@ else if (pkProps.length === 1) {
584
631
  this.writeln(` */`);
585
632
  }
586
633
  constructor(doc, opts) {
587
- super(doc), _define_property$2(this, "opts", void 0), this.opts = opts;
634
+ super(doc), _define_property$1(this, "opts", void 0), this.opts = opts;
588
635
  }
589
636
  };
590
637
  function renderPrimitiveTypeDef(def) {
@@ -604,777 +651,6 @@ function renderPrimitiveTypeDef(def) {
604
651
  }
605
652
  }
606
653
 
607
- //#endregion
608
- //#region packages/typescript/src/validator.ts
609
- function _define_property$1(obj, key, value) {
610
- if (key in obj) Object.defineProperty(obj, key, {
611
- value,
612
- enumerable: true,
613
- configurable: true,
614
- writable: true
615
- });
616
- else obj[key] = value;
617
- return obj;
618
- }
619
- const regexCache = new Map();
620
- var Validator = class {
621
- isLimitExceeded() {
622
- if (this.stackErrors.length > 0) {
623
- const top = this.stackErrors[this.stackErrors.length - 1];
624
- return top !== null && top.length >= this.opts.errorLimit;
625
- }
626
- return this.errors.length >= this.opts.errorLimit;
627
- }
628
- push(name) {
629
- this.stackPath.push(name);
630
- this.stackErrors.push(null);
631
- this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
632
- }
633
- pop(saveErrors) {
634
- this.stackPath.pop();
635
- const popped = this.stackErrors.pop();
636
- if (saveErrors && popped !== null && popped !== undefined && popped.length > 0) for (const err of popped) this.error(err.message, err.path, err.details);
637
- this.cachedPath = this.stackPath.length <= 1 ? "" : this.stackPath[1] + (this.stackPath.length > 2 ? "." + this.stackPath.slice(2).join(".") : "");
638
- return popped;
639
- }
640
- clear() {
641
- this.stackErrors[this.stackErrors.length - 1] = null;
642
- }
643
- error(message, path$2, details) {
644
- let errors = this.stackErrors[this.stackErrors.length - 1];
645
- if (!errors) if (this.stackErrors.length > 0) {
646
- errors = [];
647
- this.stackErrors[this.stackErrors.length - 1] = errors;
648
- } else errors = this.errors;
649
- const error = {
650
- path: path$2 || this.cachedPath,
651
- message
652
- };
653
- if (details?.length) error.details = details;
654
- errors.push(error);
655
- }
656
- throw() {
657
- throw new ValidatorError(this.errors);
658
- }
659
- /**
660
- * Validates a value against the type definition.
661
- *
662
- * Acts as a TypeScript type guard — when it returns `true`, the value
663
- * is narrowed to `DataType`.
664
- *
665
- * @param value - The value to validate.
666
- * @param safe - If `true`, returns `false` on failure instead of throwing.
667
- * @returns `true` if the value matches the type definition.
668
- * @throws {ValidatorError} When validation fails and `safe` is not `true`.
669
- */ validate(value, safe, context) {
670
- this.errors = [];
671
- this.stackErrors = [];
672
- this.stackPath = [""];
673
- this.cachedPath = "";
674
- this.context = context;
675
- const passed = this.validateSafe(this.def, value);
676
- this.pop(!passed);
677
- this.context = undefined;
678
- if (!passed) {
679
- if (safe) return false;
680
- this.throw();
681
- }
682
- return true;
683
- }
684
- validateSafe(def, value) {
685
- if (this.isLimitExceeded()) return false;
686
- if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
687
- if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.cachedPath);
688
- if (def.optional && value === undefined) return true;
689
- for (const plugin of this.opts.plugins) {
690
- const result = plugin(this, def, value);
691
- if (result === false || result === true) return result;
692
- }
693
- return this.validateAnnotatedType(def, value);
694
- }
695
- get path() {
696
- return this.cachedPath;
697
- }
698
- validateAnnotatedType(def, value) {
699
- switch (def.type.kind) {
700
- case "": {
701
- if (def.type.designType === "phantom") return true;
702
- return this.validatePrimitive(def, value);
703
- }
704
- case "object": return this.validateObject(def, value);
705
- case "array": return this.validateArray(def, value);
706
- case "union": return this.validateUnion(def, value);
707
- case "intersection": return this.validateIntersection(def, value);
708
- case "tuple": return this.validateTuple(def, value);
709
- default: throw new Error(`Unknown type kind "${def.type.kind}"`);
710
- }
711
- }
712
- validateUnion(def, value) {
713
- let i = 0;
714
- const popped = [];
715
- for (const item of def.type.items) {
716
- this.push(`[${item.type.kind || item.type.designType}(${i})]`);
717
- if (this.validateSafe(item, value)) {
718
- this.pop(false);
719
- return true;
720
- }
721
- const errors = this.pop(false);
722
- if (errors) popped.push(...errors);
723
- i++;
724
- }
725
- this.clear();
726
- const expected = def.type.items.map((item, i$1) => `[${item.type.kind || item.type.designType}(${i$1})]`).join(", ");
727
- this.error(`Value does not match any of the allowed types: ${expected}`, undefined, popped);
728
- return false;
729
- }
730
- validateIntersection(def, value) {
731
- for (const item of def.type.items) if (!this.validateSafe(item, value)) return false;
732
- return true;
733
- }
734
- validateTuple(def, value) {
735
- if (!Array.isArray(value) || value.length !== def.type.items.length) {
736
- this.error(`Expected array of length ${def.type.items.length}`);
737
- return false;
738
- }
739
- let i = 0;
740
- for (const item of def.type.items) {
741
- this.push(String(i));
742
- if (!this.validateSafe(item, value[i])) {
743
- this.pop(true);
744
- return false;
745
- }
746
- this.pop(false);
747
- i++;
748
- }
749
- return true;
750
- }
751
- validateArray(def, value) {
752
- if (!Array.isArray(value)) {
753
- this.error("Expected array");
754
- return false;
755
- }
756
- const minLength = def.metadata.get("expect.minLength");
757
- if (minLength) {
758
- const length = typeof minLength === "number" ? minLength : minLength.length;
759
- if (value.length < length) {
760
- const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} items, got ${value.length} items`;
761
- this.error(message);
762
- return false;
763
- }
764
- }
765
- const maxLength = def.metadata.get("expect.maxLength");
766
- if (maxLength) {
767
- const length = typeof maxLength === "number" ? maxLength : maxLength.length;
768
- if (value.length > length) {
769
- const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} items, got ${value.length} items`;
770
- this.error(message);
771
- return false;
772
- }
773
- }
774
- const uniqueItems = def.metadata.get("expect.array.uniqueItems");
775
- if (uniqueItems) {
776
- const separator = "▼↩";
777
- const seen = new Set();
778
- const keyProps = new Set();
779
- if (def.type.of.type.kind === "object") {
780
- for (const [key, val] of def.type.of.type.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
781
- }
782
- for (let idx = 0; idx < value.length; idx++) {
783
- const item = value[idx];
784
- let key;
785
- if (keyProps.size > 0) {
786
- key = "";
787
- for (const prop of keyProps) key += JSON.stringify(item[prop]) + separator;
788
- } else key = JSON.stringify(item);
789
- if (seen.has(key)) {
790
- this.push(String(idx));
791
- this.error(uniqueItems.message || "Duplicate items are not allowed");
792
- this.pop(true);
793
- return false;
794
- }
795
- seen.add(key);
796
- }
797
- }
798
- let i = 0;
799
- let passed = true;
800
- for (const item of value) {
801
- this.push(String(i));
802
- if (!this.validateSafe(def.type.of, item)) {
803
- passed = false;
804
- this.pop(true);
805
- if (this.isLimitExceeded()) return false;
806
- } else this.pop(false);
807
- i++;
808
- }
809
- return passed;
810
- }
811
- validateObject(def, value) {
812
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
813
- this.error("Expected object");
814
- return false;
815
- }
816
- let passed = true;
817
- const valueKeys = new Set(Object.keys(value));
818
- const typeKeys = new Set();
819
- let skipList;
820
- if (this.opts.skipList) {
821
- const path$2 = this.stackPath.length > 1 ? `${this.cachedPath}.` : "";
822
- for (const item of this.opts.skipList) if (item.startsWith(path$2)) {
823
- const key = item.slice(path$2.length);
824
- if (!skipList) skipList = new Set();
825
- skipList.add(key);
826
- valueKeys.delete(key);
827
- }
828
- }
829
- let partialFunctionMatched = false;
830
- if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.cachedPath);
831
- for (const [key, item] of def.type.props.entries()) {
832
- if (skipList && skipList.has(key) || isPhantomType(item)) continue;
833
- typeKeys.add(key);
834
- if (value[key] === undefined) {
835
- if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
836
- }
837
- this.push(key);
838
- if (this.validateSafe(item, value[key])) this.pop(false);
839
- else {
840
- passed = false;
841
- this.pop(true);
842
- if (this.isLimitExceeded()) return false;
843
- }
844
- }
845
- for (const key of valueKeys)
846
- /** matched patterns for unknown keys */ if (!typeKeys.has(key)) {
847
- const matched = [];
848
- for (const { pattern, def: propDef } of def.type.propsPatterns) if (pattern.test(key)) matched.push({
849
- pattern,
850
- def: propDef
851
- });
852
- if (matched.length > 0) {
853
- this.push(key);
854
- let keyPassed = false;
855
- for (const { def: propDef } of matched) {
856
- if (this.validateSafe(propDef, value[key])) {
857
- keyPassed = true;
858
- break;
859
- }
860
- this.clear();
861
- }
862
- if (!keyPassed) {
863
- this.validateSafe(matched[0].def, value[key]);
864
- this.pop(true);
865
- passed = false;
866
- if (this.isLimitExceeded()) return false;
867
- } else this.pop(false);
868
- } else if (this.opts.unknownProps !== "ignore") {
869
- if (this.opts.unknownProps === "error") {
870
- this.push(key);
871
- this.error(`Unexpected property`);
872
- this.pop(true);
873
- if (this.isLimitExceeded()) return false;
874
- passed = false;
875
- } else if (this.opts.unknownProps === "strip") delete value[key];
876
- }
877
- }
878
- return passed;
879
- }
880
- validatePrimitive(def, value) {
881
- if (def.type.value !== undefined) {
882
- if (value !== def.type.value) {
883
- this.error(`Expected ${def.type.value}, got ${value}`);
884
- return false;
885
- }
886
- return true;
887
- }
888
- const typeOfValue = Array.isArray(value) ? "array" : typeof value;
889
- switch (def.type.designType) {
890
- case "never": {
891
- this.error(`This type is impossible, must be an internal problem`);
892
- return false;
893
- }
894
- case "any": return true;
895
- case "string": {
896
- if (typeOfValue !== def.type.designType) {
897
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
898
- return false;
899
- }
900
- return this.validateString(def, value);
901
- }
902
- case "number": {
903
- if (typeOfValue !== def.type.designType) {
904
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
905
- return false;
906
- }
907
- return this.validateNumber(def, value);
908
- }
909
- case "boolean": {
910
- if (typeOfValue !== def.type.designType) {
911
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
912
- return false;
913
- }
914
- return this.validateBoolean(def, value);
915
- }
916
- case "undefined": {
917
- if (value !== undefined) {
918
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
919
- return false;
920
- }
921
- return true;
922
- }
923
- case "null": {
924
- if (value !== null) {
925
- this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
926
- return false;
927
- }
928
- return true;
929
- }
930
- default: throw new Error(`Unknown type "${def.type.designType}"`);
931
- }
932
- }
933
- validateString(def, value) {
934
- const filled = def.metadata.get("meta.required");
935
- if (filled) {
936
- if (value.trim().length === 0) {
937
- const message = typeof filled === "object" && filled.message ? filled.message : `Must not be empty`;
938
- this.error(message);
939
- return false;
940
- }
941
- }
942
- const minLength = def.metadata.get("expect.minLength");
943
- if (minLength) {
944
- const length = typeof minLength === "number" ? minLength : minLength.length;
945
- if (value.length < length) {
946
- const message = typeof minLength === "object" && minLength.message ? minLength.message : `Expected minimum length of ${length} characters, got ${value.length} characters`;
947
- this.error(message);
948
- return false;
949
- }
950
- }
951
- const maxLength = def.metadata.get("expect.maxLength");
952
- if (maxLength) {
953
- const length = typeof maxLength === "number" ? maxLength : maxLength.length;
954
- if (value.length > length) {
955
- const message = typeof maxLength === "object" && maxLength.message ? maxLength.message : `Expected maximum length of ${length} characters, got ${value.length} characters`;
956
- this.error(message);
957
- return false;
958
- }
959
- }
960
- const patterns = def.metadata.get("expect.pattern");
961
- for (const { pattern, flags, message } of patterns || []) {
962
- if (!pattern) continue;
963
- const cacheKey = `${pattern}//${flags || ""}`;
964
- let regex = regexCache.get(cacheKey);
965
- if (!regex) {
966
- regex = new RegExp(pattern, flags);
967
- regexCache.set(cacheKey, regex);
968
- }
969
- if (!regex.test(value)) {
970
- this.error(message || `Value is expected to match pattern "${pattern}"`);
971
- return false;
972
- }
973
- }
974
- return true;
975
- }
976
- validateNumber(def, value) {
977
- const int = def.metadata.get("expect.int");
978
- if (int && value % 1 !== 0) {
979
- const message = typeof int === "object" && int.message ? int.message : `Expected integer, got ${value}`;
980
- this.error(message);
981
- return false;
982
- }
983
- const min = def.metadata.get("expect.min");
984
- if (min) {
985
- const minValue = typeof min === "number" ? min : min.minValue;
986
- if (value < minValue) {
987
- const message = typeof min === "object" && min.message ? min.message : `Expected minimum ${minValue}, got ${value}`;
988
- this.error(message);
989
- return false;
990
- }
991
- }
992
- const max = def.metadata.get("expect.max");
993
- if (max) {
994
- const maxValue = typeof max === "number" ? max : max.maxValue;
995
- if (value > maxValue) {
996
- const message = typeof max === "object" && max.message ? max.message : `Expected maximum ${maxValue}, got ${value}`;
997
- this.error(message);
998
- return false;
999
- }
1000
- }
1001
- return true;
1002
- }
1003
- validateBoolean(def, value) {
1004
- const filled = def.metadata.get("meta.required");
1005
- if (filled) {
1006
- if (value !== true) {
1007
- const message = typeof filled === "object" && filled.message ? filled.message : `Must be checked`;
1008
- this.error(message);
1009
- return false;
1010
- }
1011
- }
1012
- return true;
1013
- }
1014
- constructor(def, opts) {
1015
- _define_property$1(this, "def", void 0);
1016
- _define_property$1(this, "opts", void 0);
1017
- /** Validation errors collected during the last {@link validate} call. */ _define_property$1(this, "errors", void 0);
1018
- _define_property$1(this, "stackErrors", void 0);
1019
- _define_property$1(this, "stackPath", void 0);
1020
- _define_property$1(this, "cachedPath", void 0);
1021
- _define_property$1(this, "context", void 0);
1022
- this.def = def;
1023
- this.errors = [];
1024
- this.stackErrors = [];
1025
- this.stackPath = [];
1026
- this.cachedPath = "";
1027
- this.opts = {
1028
- partial: false,
1029
- unknownProps: "error",
1030
- errorLimit: 10,
1031
- ...opts,
1032
- plugins: opts?.plugins || []
1033
- };
1034
- }
1035
- };
1036
- var ValidatorError = class extends Error {
1037
- constructor(errors) {
1038
- 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";
1039
- }
1040
- };
1041
-
1042
- //#endregion
1043
- //#region packages/typescript/src/annotated-type.ts
1044
- const COMPLEX_KINDS = new Set([
1045
- "union",
1046
- "intersection",
1047
- "tuple"
1048
- ]);
1049
- /** Shared validator method reused by all annotated type nodes. */ function validatorMethod(opts) {
1050
- return new Validator(this, opts);
1051
- }
1052
- function createAnnotatedTypeNode(type, metadata, opts) {
1053
- return {
1054
- __is_atscript_annotated_type: true,
1055
- type,
1056
- metadata,
1057
- validator: validatorMethod,
1058
- id: opts?.id,
1059
- optional: opts?.optional,
1060
- ref: opts?.ref
1061
- };
1062
- }
1063
- function isAnnotatedType(type) {
1064
- return type && type.__is_atscript_annotated_type;
1065
- }
1066
- function annotate(metadata, key, value, asArray) {
1067
- if (!metadata) return;
1068
- if (asArray) if (metadata.has(key)) {
1069
- const a = metadata.get(key);
1070
- if (Array.isArray(a)) a.push(value);
1071
- else metadata.set(key, [a, value]);
1072
- } else metadata.set(key, [value]);
1073
- else metadata.set(key, value);
1074
- }
1075
- function defineAnnotatedType(_kind, base) {
1076
- const kind = _kind || "";
1077
- const type = base?.type || {};
1078
- type.kind = kind;
1079
- if (COMPLEX_KINDS.has(kind)) type.items = [];
1080
- if (kind === "object") {
1081
- type.props = new Map();
1082
- type.propsPatterns = [];
1083
- }
1084
- type.tags = new Set();
1085
- const metadata = base?.metadata || new Map();
1086
- const payload = {
1087
- __is_atscript_annotated_type: true,
1088
- metadata,
1089
- type,
1090
- validator: validatorMethod
1091
- };
1092
- base = base ? Object.assign(base, payload) : payload;
1093
- const handle = {
1094
- $type: base,
1095
- $def: type,
1096
- $metadata: metadata,
1097
- tags(...tags) {
1098
- for (const tag of tags) this.$def.tags.add(tag);
1099
- return this;
1100
- },
1101
- designType(value) {
1102
- this.$def.designType = value;
1103
- return this;
1104
- },
1105
- value(value) {
1106
- this.$def.value = value;
1107
- return this;
1108
- },
1109
- of(value) {
1110
- this.$def.of = value;
1111
- return this;
1112
- },
1113
- item(value) {
1114
- this.$def.items.push(value);
1115
- return this;
1116
- },
1117
- prop(name, value) {
1118
- this.$def.props.set(name, value);
1119
- return this;
1120
- },
1121
- propPattern(pattern, def) {
1122
- this.$def.propsPatterns.push({
1123
- pattern,
1124
- def
1125
- });
1126
- return this;
1127
- },
1128
- optional(value = true) {
1129
- this.$type.optional = value;
1130
- return this;
1131
- },
1132
- copyMetadata(fromMetadata, ignore) {
1133
- for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
1134
- return this;
1135
- },
1136
- refTo(type$1, chain) {
1137
- if (isAnnotatedType(type$1)) {
1138
- let newBase = type$1;
1139
- const typeName = type$1.name || "Unknown";
1140
- if (chain) for (let i = 0; i < chain.length; i++) {
1141
- const c = chain[i];
1142
- if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
1143
- else {
1144
- const keys = chain.slice(0, i + 1).map((k) => `["${k}"]`).join("");
1145
- throw new Error(`Can't find prop ${typeName}${keys}`);
1146
- }
1147
- }
1148
- this.$type = createAnnotatedTypeNode(newBase.type, metadata, {
1149
- id: newBase.id,
1150
- ref: chain && chain.length > 0 ? {
1151
- type: () => type$1,
1152
- field: chain.join(".")
1153
- } : undefined
1154
- });
1155
- } else if (typeof type$1 === "function") {
1156
- const lazyType = type$1;
1157
- this.$type = createAnnotatedTypeNode({ kind: "" }, metadata, { ref: {
1158
- type: lazyType,
1159
- field: chain ? chain.join(".") : ""
1160
- } });
1161
- const node = this.$type;
1162
- const placeholder = node.type;
1163
- Object.defineProperty(node, "type", {
1164
- get() {
1165
- const t = lazyType();
1166
- if (!isAnnotatedType(t)) {
1167
- Object.defineProperty(node, "type", {
1168
- value: placeholder,
1169
- writable: false,
1170
- configurable: true
1171
- });
1172
- return placeholder;
1173
- }
1174
- let target = t;
1175
- if (chain) for (const c of chain) if (target.type.kind === "object" && target.type.props.has(c)) target = target.type.props.get(c);
1176
- else return t.type;
1177
- node.id = target.id || t.id;
1178
- Object.defineProperty(node, "type", {
1179
- value: target.type,
1180
- writable: false,
1181
- configurable: true
1182
- });
1183
- return target.type;
1184
- },
1185
- configurable: true
1186
- });
1187
- } else throw new Error(`${type$1} is not annotated type`);
1188
- return this;
1189
- },
1190
- annotate(key, value, asArray) {
1191
- annotate(this.$metadata, key, value, asArray);
1192
- return this;
1193
- },
1194
- id(value) {
1195
- this.$type.id = value;
1196
- return this;
1197
- }
1198
- };
1199
- return handle;
1200
- }
1201
- function isPhantomType(def) {
1202
- return def.type.kind === "" && def.type.designType === "phantom";
1203
- }
1204
-
1205
- //#endregion
1206
- //#region packages/typescript/src/traverse.ts
1207
- function forAnnotatedType(def, handlers) {
1208
- switch (def.type.kind) {
1209
- case "": {
1210
- const typed = def;
1211
- if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
1212
- return handlers.final(typed);
1213
- }
1214
- case "object": return handlers.object(def);
1215
- case "array": return handlers.array(def);
1216
- case "union": return handlers.union(def);
1217
- case "intersection": return handlers.intersection(def);
1218
- case "tuple": return handlers.tuple(def);
1219
- default: throw new Error(`Unknown type kind "${def.type.kind}"`);
1220
- }
1221
- }
1222
-
1223
- //#endregion
1224
- //#region packages/typescript/src/json-schema.ts
1225
- /**
1226
- * Detects a discriminator property across union items.
1227
- *
1228
- * Scans all items for object-typed members that share a common property
1229
- * with distinct const/literal values. If exactly one such property exists,
1230
- * it is returned as the discriminator.
1231
- */ function detectDiscriminator(items) {
1232
- if (items.length < 2) return null;
1233
- for (const item of items) if (item.type.kind !== "object") return null;
1234
- const firstObj = items[0].type;
1235
- const candidates = [];
1236
- for (const [propName, propType] of firstObj.props.entries()) if (propType.type.kind === "" && propType.type.value !== undefined) candidates.push(propName);
1237
- let result = null;
1238
- for (const candidate of candidates) {
1239
- const values = new Set();
1240
- const indexMapping = {};
1241
- let valid = true;
1242
- for (let i = 0; i < items.length; i++) {
1243
- const obj = items[i].type;
1244
- const prop = obj.props.get(candidate);
1245
- if (!prop || prop.type.kind !== "" || prop.type.value === undefined) {
1246
- valid = false;
1247
- break;
1248
- }
1249
- const val = prop.type.value;
1250
- if (values.has(val)) {
1251
- valid = false;
1252
- break;
1253
- }
1254
- values.add(val);
1255
- indexMapping[String(val)] = i;
1256
- }
1257
- if (valid) {
1258
- if (result) return null;
1259
- result = {
1260
- propertyName: candidate,
1261
- indexMapping
1262
- };
1263
- }
1264
- }
1265
- return result;
1266
- }
1267
- function buildJsonSchema(type) {
1268
- const defs = {};
1269
- let hasDefs = false;
1270
- const buildObject = (d) => {
1271
- const properties = {};
1272
- const required = [];
1273
- for (const [key, val] of d.type.props.entries()) {
1274
- if (isPhantomType(val)) continue;
1275
- properties[key] = build(val);
1276
- if (!val.optional) required.push(key);
1277
- }
1278
- const schema$1 = {
1279
- type: "object",
1280
- properties
1281
- };
1282
- if (required.length > 0) schema$1.required = required;
1283
- return schema$1;
1284
- };
1285
- const build = (def) => {
1286
- if (def.id && def.type.kind === "object" && def !== type) {
1287
- const name = def.id;
1288
- if (!defs[name]) {
1289
- hasDefs = true;
1290
- defs[name] = {};
1291
- defs[name] = buildObject(def);
1292
- }
1293
- return { $ref: `#/$defs/${name}` };
1294
- }
1295
- const meta = def.metadata;
1296
- return forAnnotatedType(def, {
1297
- phantom() {
1298
- return {};
1299
- },
1300
- object(d) {
1301
- return buildObject(d);
1302
- },
1303
- array(d) {
1304
- const schema$1 = {
1305
- type: "array",
1306
- items: build(d.type.of)
1307
- };
1308
- const minLength = meta.get("expect.minLength");
1309
- if (minLength) schema$1.minItems = typeof minLength === "number" ? minLength : minLength.length;
1310
- const maxLength = meta.get("expect.maxLength");
1311
- if (maxLength) schema$1.maxItems = typeof maxLength === "number" ? maxLength : maxLength.length;
1312
- return schema$1;
1313
- },
1314
- union(d) {
1315
- const disc = detectDiscriminator(d.type.items);
1316
- if (disc) {
1317
- const oneOf = d.type.items.map(build);
1318
- const mapping = {};
1319
- for (const [val, idx] of Object.entries(disc.indexMapping)) {
1320
- const item = d.type.items[idx];
1321
- mapping[val] = item.id && defs[item.id] ? `#/$defs/${item.id}` : `#/oneOf/${idx}`;
1322
- }
1323
- return {
1324
- oneOf,
1325
- discriminator: {
1326
- propertyName: disc.propertyName,
1327
- mapping
1328
- }
1329
- };
1330
- }
1331
- return { anyOf: d.type.items.map(build) };
1332
- },
1333
- intersection(d) {
1334
- return { allOf: d.type.items.map(build) };
1335
- },
1336
- tuple(d) {
1337
- return {
1338
- type: "array",
1339
- items: d.type.items.map(build),
1340
- additionalItems: false
1341
- };
1342
- },
1343
- final(d) {
1344
- const schema$1 = {};
1345
- if (d.type.value !== undefined) schema$1.const = d.type.value;
1346
- if (d.type.designType && d.type.designType !== "any") {
1347
- schema$1.type = d.type.designType === "undefined" ? "null" : d.type.designType;
1348
- if (schema$1.type === "number" && meta.get("expect.int")) schema$1.type = "integer";
1349
- }
1350
- if (schema$1.type === "string") {
1351
- if (meta.get("meta.required")) schema$1.minLength = 1;
1352
- const minLength = meta.get("expect.minLength");
1353
- if (minLength) schema$1.minLength = typeof minLength === "number" ? minLength : minLength.length;
1354
- const maxLength = meta.get("expect.maxLength");
1355
- if (maxLength) schema$1.maxLength = typeof maxLength === "number" ? maxLength : maxLength.length;
1356
- const patterns = meta.get("expect.pattern");
1357
- if (patterns?.length) if (patterns.length === 1) schema$1.pattern = patterns[0].pattern;
1358
- else schema$1.allOf = (schema$1.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
1359
- }
1360
- if (schema$1.type === "number" || schema$1.type === "integer") {
1361
- const min = meta.get("expect.min");
1362
- if (min) schema$1.minimum = typeof min === "number" ? min : min.minValue;
1363
- const max = meta.get("expect.max");
1364
- if (max) schema$1.maximum = typeof max === "number" ? max : max.maxValue;
1365
- }
1366
- return schema$1;
1367
- }
1368
- });
1369
- };
1370
- const schema = build(type);
1371
- if (hasDefs) return {
1372
- ...schema,
1373
- $defs: defs
1374
- };
1375
- return schema;
1376
- }
1377
-
1378
654
  //#endregion
1379
655
  //#region packages/typescript/src/codegen/js-renderer.ts
1380
656
  function _define_property(obj, key, value) {
@@ -1387,6 +663,19 @@ function _define_property(obj, key, value) {
1387
663
  else obj[key] = value;
1388
664
  return obj;
1389
665
  }
666
+ const QUERY_OP_MAP = {
667
+ "=": "$eq",
668
+ "!=": "$ne",
669
+ ">": "$gt",
670
+ ">=": "$gte",
671
+ "<": "$lt",
672
+ "<=": "$lte",
673
+ "in": "$in",
674
+ "not in": "$nin",
675
+ "matches": "$regex",
676
+ "exists": "$exists",
677
+ "not exists": "$exists"
678
+ };
1390
679
  var JsRenderer = class extends BaseRenderer {
1391
680
  pre() {
1392
681
  this.writeln("// prettier-ignore-start");
@@ -1507,7 +796,7 @@ else def = def.getDefinition() || def;
1507
796
  const mode = resolveJsonSchemaMode(this.opts);
1508
797
  const hasAnnotation = node.countAnnotations("emit.jsonSchema") > 0;
1509
798
  if (hasAnnotation || mode === "bundle") {
1510
- const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
799
+ const schema = JSON.stringify(require_json_schema.buildJsonSchema(this.toAnnotatedType(node)));
1511
800
  this.writeln("static toJsonSchema() {");
1512
801
  this.indent().writeln(`return ${schema}`).unindent();
1513
802
  this.writeln("}");
@@ -1532,7 +821,7 @@ else def = def.getDefinition() || def;
1532
821
  return this.toAnnotatedHandle(node).$type;
1533
822
  }
1534
823
  toAnnotatedHandle(node, skipAnnotations = false) {
1535
- if (!node) return defineAnnotatedType();
824
+ if (!node) return require_json_schema.defineAnnotatedType();
1536
825
  switch (node.entity) {
1537
826
  case "interface":
1538
827
  case "type": {
@@ -1564,14 +853,14 @@ else def = def.getDefinition() || def;
1564
853
  }
1565
854
  case "primitive": {
1566
855
  const prim = node;
1567
- const handle = defineAnnotatedType();
856
+ const handle = require_json_schema.defineAnnotatedType();
1568
857
  handle.designType(prim.id === "never" ? "never" : prim.config.type);
1569
858
  if (!skipAnnotations) this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1570
859
  return handle;
1571
860
  }
1572
861
  case "const": {
1573
862
  const c = node;
1574
- const handle = defineAnnotatedType();
863
+ const handle = require_json_schema.defineAnnotatedType();
1575
864
  const t = c.token("identifier")?.type;
1576
865
  handle.designType(t === "number" ? "number" : "string");
1577
866
  handle.value(t === "number" ? Number(c.id) : c.id);
@@ -1579,7 +868,7 @@ else def = def.getDefinition() || def;
1579
868
  }
1580
869
  case "structure": {
1581
870
  const struct = node;
1582
- const handle = defineAnnotatedType("object");
871
+ const handle = require_json_schema.defineAnnotatedType("object");
1583
872
  for (const prop of Array.from(struct.props.values())) {
1584
873
  const propHandle = this.toAnnotatedHandle(prop);
1585
874
  const pattern = prop.token("identifier")?.pattern;
@@ -1591,24 +880,24 @@ else handle.prop(prop.id, propHandle.$type);
1591
880
  case "group": {
1592
881
  const group = node;
1593
882
  const kind = group.op === "|" ? "union" : "intersection";
1594
- const handle = defineAnnotatedType(kind);
883
+ const handle = require_json_schema.defineAnnotatedType(kind);
1595
884
  for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
1596
885
  return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1597
886
  }
1598
887
  case "tuple": {
1599
888
  const group = node;
1600
- const handle = defineAnnotatedType("tuple");
889
+ const handle = require_json_schema.defineAnnotatedType("tuple");
1601
890
  for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
1602
891
  return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1603
892
  }
1604
893
  case "array": {
1605
894
  const arr = node;
1606
- const handle = defineAnnotatedType("array");
895
+ const handle = require_json_schema.defineAnnotatedType("array");
1607
896
  handle.of(this.toAnnotatedHandle(arr.getDefinition()).$type);
1608
897
  return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1609
898
  }
1610
899
  default: {
1611
- const handle = defineAnnotatedType();
900
+ const handle = require_json_schema.defineAnnotatedType();
1612
901
  return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1613
902
  }
1614
903
  }
@@ -1882,6 +1171,68 @@ else if (resolved && (0, __atscript_core.isPrimitive)(resolved)) {
1882
1171
  if (multiple) this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value}, true)`);
1883
1172
  else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value})`);
1884
1173
  }
1174
+ emitRefValue(text) {
1175
+ const dotIdx = text.indexOf(".");
1176
+ if (dotIdx === -1) return `() => ${text}`;
1177
+ const typeName = text.slice(0, dotIdx);
1178
+ const field = text.slice(dotIdx + 1);
1179
+ return `{ type: () => ${typeName}, field: "${escapeQuotes(field)}" }`;
1180
+ }
1181
+ emitArgValue(aSpec, argToken) {
1182
+ if (aSpec.type === "ref") return this.emitRefValue(argToken.text);
1183
+ if (aSpec.type === "query" && argToken.queryNode) return this.emitQueryTree(argToken.queryNode);
1184
+ return aSpec.type === "string" ? `"${escapeQuotes(argToken.text)}"` : argToken.text;
1185
+ }
1186
+ emitQueryTree(queryNode) {
1187
+ return this.emitQueryExpr(queryNode.expression);
1188
+ }
1189
+ emitQueryExpr(node) {
1190
+ if ((0, __atscript_core.isQueryLogical)(node)) return this.emitQueryLogical(node);
1191
+ return this.emitQueryComparison(node);
1192
+ }
1193
+ emitQueryLogical(node) {
1194
+ if (node.operator === "not") return `{ "$not": ${this.emitQueryExpr(node.operands[0])} }`;
1195
+ const key = node.operator === "and" ? "$and" : "$or";
1196
+ const items = node.operands.map((op) => this.emitQueryExpr(op)).join(", ");
1197
+ return `{ "${key}": [${items}] }`;
1198
+ }
1199
+ emitQueryComparison(node) {
1200
+ const left = this.emitQueryFieldRef(node.left);
1201
+ const mappedOp = QUERY_OP_MAP[node.operator] || node.operator;
1202
+ const parts = [`left: ${left}`, `op: "${mappedOp}"`];
1203
+ if (node.right) {
1204
+ if ("fieldRef" in node.right && node.right.fieldRef) parts.push(`right: ${this.emitQueryFieldRef(node.right)}`);
1205
+ else if ("values" in node.right && node.right.values) {
1206
+ const values = node.right.values.map((v) => this.emitQueryLiteral(v)).join(", ");
1207
+ parts.push(`right: [${values}]`);
1208
+ } else if ("valueToken" in node.right) parts.push(`right: ${this.emitQueryLiteral(node.right)}`);
1209
+ } else if (node.operator === "exists") parts.push("right: true");
1210
+ else if (node.operator === "not exists") parts.push("right: false");
1211
+ return `{ ${parts.join(", ")} }`;
1212
+ }
1213
+ emitQueryFieldRef(node) {
1214
+ const parts = [];
1215
+ if (node.typeRef) parts.push(`type: () => ${node.typeRef.text}`);
1216
+ parts.push(`field: "${escapeQuotes(node.fieldRef.text)}"`);
1217
+ return `{ ${parts.join(", ")} }`;
1218
+ }
1219
+ emitQueryLiteral(node) {
1220
+ const token = node.valueToken;
1221
+ switch (token.type) {
1222
+ case "text": return `"${escapeQuotes(token.text)}"`;
1223
+ case "number": return token.text;
1224
+ case "regexp": {
1225
+ const match = /^\/(.*)\/[a-z]*$/.exec(token.text);
1226
+ return `"${escapeQuotes(match ? match[1] : token.text)}"`;
1227
+ }
1228
+ case "identifier": {
1229
+ if (token.text === "true" || token.text === "false") return token.text;
1230
+ if (token.text === "null" || token.text === "undefined") return "null";
1231
+ return token.text;
1232
+ }
1233
+ default: return token.text;
1234
+ }
1235
+ }
1885
1236
  computeAnnotationValue(node, an) {
1886
1237
  const spec = this.doc.resolveAnnotation(an.name);
1887
1238
  let targetValue = "true";
@@ -1893,13 +1244,13 @@ else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${value})`);
1893
1244
  targetValue = "{ ";
1894
1245
  let i = 0;
1895
1246
  for (const aSpec of spec.arguments) {
1896
- if (an.args[i]) targetValue += `${wrapProp(aSpec.name)}: ${aSpec.type === "string" ? `"${escapeQuotes(an.args[i]?.text)}"` : an.args[i]?.text}${i === length - 1 ? "" : ", "} `;
1247
+ if (an.args[i]) targetValue += `${wrapProp(aSpec.name)}: ${this.emitArgValue(aSpec, an.args[i])}${i === length - 1 ? "" : ", "} `;
1897
1248
  i++;
1898
1249
  }
1899
1250
  targetValue += "}";
1900
1251
  } else {
1901
1252
  const aSpec = spec.arguments[0];
1902
- if (an.args[0]) targetValue = aSpec.type === "string" ? `"${escapeQuotes(an.args[0]?.text)}"` : an.args[0]?.text;
1253
+ if (an.args[0]) targetValue = this.emitArgValue(aSpec, an.args[0]);
1903
1254
  else targetValue = "true";
1904
1255
  }
1905
1256
  } else {
@@ -2070,8 +1421,8 @@ const tsPlugin = (opts) => {
2070
1421
  for (const [key, val] of Object.entries(annotations)) {
2071
1422
  const multiple = val.multiple;
2072
1423
  const typeLine = Array.from(val.types).map((t) => {
2073
- if (t.type === "object") return `{ ${Object.entries(t.props).map(([k, v]) => `${wrapProp(k)}${v.optional ? "?" : ""}: ${v.type}`).join(", ")} }`;
2074
- else return t.optional ? `${t.type} | true` : t.type;
1424
+ if (t.type === "object" && "props" in t) return `{ ${Object.entries(t.props).map(([k, v]) => `${wrapProp(k)}${v.optional ? "?" : ""}: ${v.type}`).join(", ")} }`;
1425
+ else return "optional" in t && t.optional ? `${t.type} | true` : t.type;
2075
1426
  }).join(" | ");
2076
1427
  rendered.push(`${wrapProp(key)}: ${multiple ? "(" : ""}${typeLine}${multiple ? ")[]" : ""}`);
2077
1428
  }