@atscript/typescript 0.0.27 → 0.0.28

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
@@ -29,7 +29,7 @@ const __atscript_core = __toESM(require("@atscript/core"));
29
29
  const fs = __toESM(require("fs"));
30
30
 
31
31
  //#region packages/typescript/src/codegen/code-printer.ts
32
- function _define_property$3(obj, key, value) {
32
+ function _define_property$5(obj, key, value) {
33
33
  if (key in obj) Object.defineProperty(obj, key, {
34
34
  value,
35
35
  enumerable: true,
@@ -122,17 +122,17 @@ else this.write(closing);
122
122
  return " ".repeat(this.indentLevel * this.indentSize);
123
123
  }
124
124
  constructor() {
125
- _define_property$3(this, "lines", []);
126
- _define_property$3(this, "currentLine", "");
127
- _define_property$3(this, "indentLevel", 0);
128
- _define_property$3(this, "indentSize", 2);
129
- _define_property$3(this, "blockStack", []);
125
+ _define_property$5(this, "lines", []);
126
+ _define_property$5(this, "currentLine", "");
127
+ _define_property$5(this, "indentLevel", 0);
128
+ _define_property$5(this, "indentSize", 2);
129
+ _define_property$5(this, "blockStack", []);
130
130
  }
131
131
  };
132
132
 
133
133
  //#endregion
134
134
  //#region packages/typescript/src/codegen/base-renderer.ts
135
- function _define_property$2(obj, key, value) {
135
+ function _define_property$4(obj, key, value) {
136
136
  if (key in obj) Object.defineProperty(obj, key, {
137
137
  value,
138
138
  enumerable: true,
@@ -191,7 +191,7 @@ var BaseRenderer = class extends CodePrinter {
191
191
  }
192
192
  }
193
193
  constructor(doc) {
194
- super(), _define_property$2(this, "doc", void 0), _define_property$2(this, "unused", void 0), this.doc = doc;
194
+ super(), _define_property$4(this, "doc", void 0), _define_property$4(this, "unused", void 0), this.doc = doc;
195
195
  this.unused = new Set(this.doc.getUnusedTokens().map((t) => t.text));
196
196
  }
197
197
  };
@@ -209,6 +209,16 @@ function escapeQuotes(str) {
209
209
 
210
210
  //#endregion
211
211
  //#region packages/typescript/src/codegen/type-renderer.ts
212
+ function _define_property$3(obj, key, value) {
213
+ if (key in obj) Object.defineProperty(obj, key, {
214
+ value,
215
+ enumerable: true,
216
+ configurable: true,
217
+ writable: true
218
+ });
219
+ else obj[key] = value;
220
+ return obj;
221
+ }
212
222
  var TypeRenderer = class extends BaseRenderer {
213
223
  pre() {
214
224
  this.writeln("// prettier-ignore-start");
@@ -306,6 +316,7 @@ else if (patterns.length === 1) {
306
316
  this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
307
317
  this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
308
318
  this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
319
+ this.writeln("static toJsonSchema: () => any");
309
320
  }
310
321
  this.pop();
311
322
  }
@@ -348,6 +359,7 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = "TAtscriptTypeFina
348
359
  this.writeln(`const type: ${typeDef}`);
349
360
  this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
350
361
  this.writeln(`const validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
362
+ if (this.opts?.jsonSchema) this.writeln("const toJsonSchema: () => any");
351
363
  this.popln();
352
364
  }
353
365
  renderJsDoc(node) {
@@ -358,6 +370,9 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = "TAtscriptTypeFina
358
370
  this.writeln(` * @see {@link ./${this.doc.name}${rangeStr}}`);
359
371
  this.writeln(` */`);
360
372
  }
373
+ constructor(doc, opts) {
374
+ super(doc), _define_property$3(this, "opts", void 0), this.opts = opts;
375
+ }
361
376
  };
362
377
  function renderPrimitiveTypeDef(def) {
363
378
  if (!def) return "unknown";
@@ -376,6 +391,537 @@ function renderPrimitiveTypeDef(def) {
376
391
  }
377
392
  }
378
393
 
394
+ //#endregion
395
+ //#region packages/typescript/src/validator.ts
396
+ function _define_property$2(obj, key, value) {
397
+ if (key in obj) Object.defineProperty(obj, key, {
398
+ value,
399
+ enumerable: true,
400
+ configurable: true,
401
+ writable: true
402
+ });
403
+ else obj[key] = value;
404
+ return obj;
405
+ }
406
+ const regexCache = new Map();
407
+ var Validator = class {
408
+ isLimitExceeded() {
409
+ if (this.stackErrors.length > 0) return this.stackErrors[this.stackErrors.length - 1].length >= this.opts.errorLimit;
410
+ return this.errors.length >= this.opts.errorLimit;
411
+ }
412
+ push(name) {
413
+ this.stackPath.push(name);
414
+ this.stackErrors.push([]);
415
+ }
416
+ pop(saveErrors) {
417
+ this.stackPath.pop();
418
+ const popped = this.stackErrors.pop();
419
+ if (saveErrors && popped?.length) popped.forEach((error) => {
420
+ this.error(error.message, error.path, error.details);
421
+ });
422
+ return popped;
423
+ }
424
+ clear() {
425
+ this.stackErrors[this.stackErrors.length - 1] = [];
426
+ }
427
+ error(message, path$3, details) {
428
+ const errors = this.stackErrors[this.stackErrors.length - 1] || this.errors;
429
+ const error = {
430
+ path: path$3 || this.path,
431
+ message
432
+ };
433
+ if (details?.length) error.details = details;
434
+ errors.push(error);
435
+ }
436
+ throw() {
437
+ throw new ValidatorError(this.errors);
438
+ }
439
+ validate(value, safe) {
440
+ this.push("");
441
+ this.errors = [];
442
+ this.stackErrors = [];
443
+ const passed = this.validateSafe(this.def, value);
444
+ this.pop(!passed);
445
+ if (!passed) {
446
+ if (safe) return false;
447
+ this.throw();
448
+ }
449
+ return true;
450
+ }
451
+ validateSafe(def, value) {
452
+ if (this.isLimitExceeded()) return false;
453
+ if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
454
+ if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.path);
455
+ if (def.optional && value === undefined) return true;
456
+ for (const plugin of this.opts.plugins) {
457
+ const result = plugin(this, def, value);
458
+ if (result === false || result === true) return result;
459
+ }
460
+ return this.validateAnnotatedType(def, value);
461
+ }
462
+ get path() {
463
+ return this.stackPath.slice(1).join(".");
464
+ }
465
+ validateAnnotatedType(def, value) {
466
+ switch (def.type.kind) {
467
+ case "object": return this.validateObject(def, value);
468
+ case "union": return this.validateUnion(def, value);
469
+ case "intersection": return this.validateIntersection(def, value);
470
+ case "tuple": return this.validateTuple(def, value);
471
+ case "array": return this.validateArray(def, value);
472
+ case "": return this.validatePrimitive(def, value);
473
+ default: throw new Error(`Unknown type "${def.type.kind}"`);
474
+ }
475
+ }
476
+ validateUnion(def, value) {
477
+ let i = 0;
478
+ const popped = [];
479
+ for (const item of def.type.items) {
480
+ this.push(`[${item.type.kind || item.type.designType}(${i})]`);
481
+ if (this.validateSafe(item, value)) {
482
+ this.pop(false);
483
+ return true;
484
+ }
485
+ const errors = this.pop(false);
486
+ if (errors) popped.push(...errors);
487
+ i++;
488
+ }
489
+ this.clear();
490
+ const expected = def.type.items.map((item, i$1) => `[${item.type.kind || item.type.designType}(${i$1})]`).join(", ");
491
+ this.error(`Value does not match any of the allowed types: ${expected}`, undefined, popped);
492
+ return false;
493
+ }
494
+ validateIntersection(def, value) {
495
+ for (const item of def.type.items) if (!this.validateSafe(item, value)) return false;
496
+ return true;
497
+ }
498
+ validateTuple(def, value) {
499
+ if (!Array.isArray(value) || value.length !== def.type.items.length) {
500
+ this.error("Expected array of length " + def.type.items.length);
501
+ return false;
502
+ }
503
+ let i = 0;
504
+ for (const item of def.type.items) {
505
+ this.push(`[${i}]`);
506
+ if (!this.validateSafe(item, value[i])) {
507
+ this.pop(true);
508
+ return false;
509
+ }
510
+ this.pop(false);
511
+ i++;
512
+ }
513
+ return true;
514
+ }
515
+ validateArray(def, value) {
516
+ if (!Array.isArray(value)) {
517
+ this.error("Expected array");
518
+ return false;
519
+ }
520
+ const minLength = def.metadata.get("expect.minLength");
521
+ if (typeof minLength === "number" && value.length < minLength) {
522
+ this.error(`Expected minimum length of ${minLength} items, got ${value.length} items`);
523
+ return false;
524
+ }
525
+ const maxLength = def.metadata.get("expect.maxLength");
526
+ if (typeof maxLength === "number" && value.length > maxLength) {
527
+ this.error(`Expected maximum length of ${maxLength} items, got ${value.length} items`);
528
+ return false;
529
+ }
530
+ let i = 0;
531
+ let passed = true;
532
+ for (const item of value) {
533
+ this.push(`[${i}]`);
534
+ if (!this.validateSafe(def.type.of, item)) {
535
+ passed = false;
536
+ this.pop(true);
537
+ if (this.isLimitExceeded()) return false;
538
+ } else this.pop(false);
539
+ i++;
540
+ }
541
+ return passed;
542
+ }
543
+ validateObject(def, value) {
544
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
545
+ this.error("Expected object");
546
+ return false;
547
+ }
548
+ let passed = true;
549
+ const valueKeys = new Set(Object.keys(value));
550
+ const typeKeys = new Set();
551
+ const skipList = new Set();
552
+ if (this.opts.skipList) {
553
+ const path$3 = this.stackPath.length > 1 ? this.path + "." : "";
554
+ this.opts.skipList.forEach((item) => {
555
+ if (item.startsWith(path$3)) {
556
+ const key = item.slice(path$3.length);
557
+ skipList.add(key);
558
+ valueKeys.delete(key);
559
+ }
560
+ });
561
+ }
562
+ let partialFunctionMatched = false;
563
+ if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
564
+ for (const [key, item] of def.type.props.entries()) {
565
+ if (skipList.has(key)) continue;
566
+ typeKeys.add(key);
567
+ if (value[key] === undefined) {
568
+ if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
569
+ }
570
+ this.push(key);
571
+ if (this.validateSafe(item, value[key])) this.pop(false);
572
+ else {
573
+ passed = false;
574
+ this.pop(true);
575
+ if (this.isLimitExceeded()) return false;
576
+ }
577
+ }
578
+ for (const key of valueKeys)
579
+ /** matched patterns for unknown keys */ if (!typeKeys.has(key)) {
580
+ const matched = [];
581
+ for (const { pattern, def: propDef } of def.type.propsPatterns) if (pattern.test(key)) matched.push({
582
+ pattern,
583
+ def: propDef
584
+ });
585
+ if (matched.length) {
586
+ let keyPassed = false;
587
+ for (const { def: def$1 } of matched) if (this.validateSafe(def$1, value[key])) {
588
+ this.pop(false);
589
+ keyPassed = true;
590
+ break;
591
+ }
592
+ if (!keyPassed) {
593
+ this.push(key);
594
+ this.validateSafe(matched[0].def, value[key]);
595
+ this.pop(true);
596
+ passed = false;
597
+ if (this.isLimitExceeded()) return false;
598
+ }
599
+ } else if (this.opts.unknwonProps !== "ignore") {
600
+ if (this.opts.unknwonProps === "error") {
601
+ this.push(key);
602
+ this.error(`Unexpected property`);
603
+ this.pop(true);
604
+ if (this.isLimitExceeded()) return false;
605
+ passed = false;
606
+ } else if (this.opts.unknwonProps === "strip") delete value[key];
607
+ }
608
+ }
609
+ return passed;
610
+ }
611
+ validatePrimitive(def, value) {
612
+ if (typeof def.type.value !== "undefined") {
613
+ if (value !== def.type.value) {
614
+ this.error(`Expected ${def.type.value}, got ${value}`);
615
+ return false;
616
+ }
617
+ return true;
618
+ }
619
+ const typeOfValue = Array.isArray(value) ? "array" : typeof value;
620
+ switch (def.type.designType) {
621
+ case "never":
622
+ this.error(`This type is impossible, must be an internal problem`);
623
+ return false;
624
+ case "any": return true;
625
+ case "string":
626
+ if (typeOfValue !== def.type.designType) {
627
+ this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
628
+ return false;
629
+ }
630
+ return this.validateString(def, value);
631
+ case "number":
632
+ if (typeOfValue !== def.type.designType) {
633
+ this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
634
+ return false;
635
+ }
636
+ return this.validateNumber(def, value);
637
+ case "boolean":
638
+ if (typeOfValue !== def.type.designType) {
639
+ this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
640
+ return false;
641
+ }
642
+ return true;
643
+ case "undefined":
644
+ if (value !== undefined) {
645
+ this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
646
+ return false;
647
+ }
648
+ return true;
649
+ case "null":
650
+ if (value !== null) {
651
+ this.error(`Expected ${def.type.designType}, got ${typeOfValue}`);
652
+ return false;
653
+ }
654
+ return true;
655
+ default: throw new Error(`Unknown type "${def.type.designType}"`);
656
+ }
657
+ }
658
+ validateString(def, value) {
659
+ const minLength = def.metadata.get("expect.minLength");
660
+ if (typeof minLength === "number" && value.length < minLength) {
661
+ this.error(`Expected minimum length of ${minLength} characters, got ${value.length} characters`);
662
+ return false;
663
+ }
664
+ const maxLength = def.metadata.get("expect.maxLength");
665
+ if (typeof maxLength === "number" && value.length > maxLength) {
666
+ this.error(`Expected maximum length of ${maxLength} characters, got ${value.length} characters`);
667
+ return false;
668
+ }
669
+ const patterns = def.metadata.get("expect.pattern");
670
+ for (const { pattern, flags, message } of patterns || []) {
671
+ if (!pattern) continue;
672
+ const cacheKey = `${pattern}//${flags || ""}`;
673
+ let regex = regexCache.get(cacheKey);
674
+ if (!regex) {
675
+ regex = new RegExp(pattern, flags);
676
+ regexCache.set(cacheKey, regex);
677
+ }
678
+ if (!regex.test(value)) {
679
+ this.error(message || `Value is expected to match pattern "${pattern}"`);
680
+ return false;
681
+ }
682
+ }
683
+ return true;
684
+ }
685
+ validateNumber(def, value) {
686
+ const int = def.metadata.get("expect.int");
687
+ if (typeof int === "boolean" && int && value % 1 !== 0) {
688
+ this.error(`Expected integer, got ${value}`);
689
+ return false;
690
+ }
691
+ const min = def.metadata.get("expect.min");
692
+ if (typeof min === "number" && value < min) {
693
+ this.error(`Expected minimum ${min}, got ${value}`);
694
+ return false;
695
+ }
696
+ const max = def.metadata.get("expect.max");
697
+ if (typeof max === "number" && value > max) {
698
+ this.error(`Expected maximum ${max}, got ${value}`);
699
+ return false;
700
+ }
701
+ return true;
702
+ }
703
+ constructor(def, opts) {
704
+ _define_property$2(this, "def", void 0);
705
+ _define_property$2(this, "opts", void 0);
706
+ _define_property$2(this, "errors", void 0);
707
+ _define_property$2(this, "stackErrors", void 0);
708
+ _define_property$2(this, "stackPath", void 0);
709
+ this.def = def;
710
+ this.errors = [];
711
+ this.stackErrors = [];
712
+ this.stackPath = [];
713
+ this.opts = {
714
+ partial: false,
715
+ unknwonProps: "error",
716
+ errorLimit: 10,
717
+ ...opts,
718
+ plugins: opts?.plugins || []
719
+ };
720
+ }
721
+ };
722
+ var ValidatorError = class extends Error {
723
+ constructor(errors) {
724
+ super(`${errors[0].path ? errors[0].path + ": " : ""}${errors[0].message}`), _define_property$2(this, "errors", void 0), _define_property$2(this, "name", void 0), this.errors = errors, this.name = "Validation Error";
725
+ }
726
+ };
727
+
728
+ //#endregion
729
+ //#region packages/typescript/src/annotated-type.ts
730
+ function isAnnotatedType(type) {
731
+ return type && type.__is_atscript_annotated_type;
732
+ }
733
+ function defineAnnotatedType(_kind, base) {
734
+ const kind = _kind || "";
735
+ const type = base?.type || {};
736
+ type.kind = kind;
737
+ if ([
738
+ "union",
739
+ "intersection",
740
+ "tuple"
741
+ ].includes(kind)) type.items = [];
742
+ if (kind === "object") {
743
+ type.props = new Map();
744
+ type.propsPatterns = [];
745
+ }
746
+ type.tags = new Set();
747
+ const metadata = base?.metadata || new Map();
748
+ if (base) Object.assign(base, {
749
+ __is_atscript_annotated_type: true,
750
+ metadata,
751
+ type,
752
+ validator(opts) {
753
+ return new Validator(this, opts);
754
+ }
755
+ });
756
+ else base = {
757
+ __is_atscript_annotated_type: true,
758
+ metadata,
759
+ type,
760
+ validator(opts) {
761
+ return new Validator(this, opts);
762
+ }
763
+ };
764
+ const handle = {
765
+ $type: base,
766
+ $def: type,
767
+ $metadata: metadata,
768
+ _existingObject: undefined,
769
+ tags(...tags) {
770
+ for (const tag of tags) this.$def.tags.add(tag);
771
+ return this;
772
+ },
773
+ designType(value) {
774
+ this.$def.designType = value;
775
+ return this;
776
+ },
777
+ value(value) {
778
+ this.$def.value = value;
779
+ return this;
780
+ },
781
+ of(value) {
782
+ this.$def.of = value;
783
+ return this;
784
+ },
785
+ item(value) {
786
+ this.$def.items.push(value);
787
+ return this;
788
+ },
789
+ prop(name, value) {
790
+ this.$def.props.set(name, value);
791
+ return this;
792
+ },
793
+ propPattern(pattern, def) {
794
+ this.$def.propsPatterns.push({
795
+ pattern,
796
+ def
797
+ });
798
+ return this;
799
+ },
800
+ optional(value = true) {
801
+ this.$type.optional = value;
802
+ return this;
803
+ },
804
+ copyMetadata(fromMetadata, ignore) {
805
+ for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
806
+ return this;
807
+ },
808
+ refTo(type$1, chain) {
809
+ let newBase = type$1;
810
+ const typeName = type$1.name || "Unknown";
811
+ if (isAnnotatedType(newBase)) {
812
+ let keys = "";
813
+ for (const c of chain || []) {
814
+ keys += `["${c}"]`;
815
+ if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
816
+ else throw new Error(`Can't find prop ${typeName}${keys}`);
817
+ }
818
+ if (!newBase && keys) throw new Error(`Can't find prop ${typeName}${keys}`);
819
+ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
820
+ this.$type = {
821
+ __is_atscript_annotated_type: true,
822
+ type: newBase.type,
823
+ metadata,
824
+ validator(opts) {
825
+ return new Validator(this, opts);
826
+ }
827
+ };
828
+ } else throw new Error(`${type$1} is not annotated type`);
829
+ return this;
830
+ },
831
+ annotate(key, value, asArray) {
832
+ if (asArray) if (this.$metadata.has(key)) {
833
+ const a = this.$metadata.get(key);
834
+ if (Array.isArray(a)) a.push(value);
835
+ else this.$metadata.set(key, [a, value]);
836
+ } else this.$metadata.set(key, [value]);
837
+ else this.$metadata.set(key, value);
838
+ return this;
839
+ }
840
+ };
841
+ return handle;
842
+ }
843
+
844
+ //#endregion
845
+ //#region packages/typescript/src/json-schema.ts
846
+ function buildJsonSchema(type) {
847
+ const build$1 = (def) => {
848
+ const t = def.type;
849
+ const meta = def.metadata;
850
+ switch (t.kind) {
851
+ case "object": {
852
+ const obj = t;
853
+ const properties = {};
854
+ const required = [];
855
+ for (const [key, val] of obj.props.entries()) {
856
+ properties[key] = build$1(val);
857
+ if (!val.optional) required.push(key);
858
+ }
859
+ const schema = {
860
+ type: "object",
861
+ properties
862
+ };
863
+ if (required.length) schema.required = required;
864
+ return schema;
865
+ }
866
+ case "array": {
867
+ const arr = t;
868
+ const schema = {
869
+ type: "array",
870
+ items: build$1(arr.of)
871
+ };
872
+ const minLength = meta.get("expect.minLength");
873
+ if (typeof minLength === "number") schema.minItems = minLength;
874
+ const maxLength = meta.get("expect.maxLength");
875
+ if (typeof maxLength === "number") schema.maxItems = maxLength;
876
+ return schema;
877
+ }
878
+ case "union": {
879
+ const grp = t;
880
+ return { anyOf: grp.items.map(build$1) };
881
+ }
882
+ case "intersection": {
883
+ const grp = t;
884
+ return { allOf: grp.items.map(build$1) };
885
+ }
886
+ case "tuple": {
887
+ const grp = t;
888
+ return {
889
+ type: "array",
890
+ items: grp.items.map(build$1),
891
+ additionalItems: false
892
+ };
893
+ }
894
+ case "": {
895
+ const fin = t;
896
+ const schema = {};
897
+ if (fin.value !== undefined) schema.const = fin.value;
898
+ if (fin.designType && fin.designType !== "any") {
899
+ schema.type = fin.designType === "undefined" ? "null" : fin.designType;
900
+ if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
901
+ }
902
+ if (schema.type === "string") {
903
+ const minLength = meta.get("expect.minLength");
904
+ if (typeof minLength === "number") schema.minLength = minLength;
905
+ const maxLength = meta.get("expect.maxLength");
906
+ if (typeof maxLength === "number") schema.maxLength = maxLength;
907
+ const patterns = meta.get("expect.pattern");
908
+ if (patterns?.length) if (patterns.length === 1) schema.pattern = patterns[0].pattern;
909
+ else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern: p.pattern })));
910
+ }
911
+ if (schema.type === "number" || schema.type === "integer") {
912
+ const min = meta.get("expect.min");
913
+ if (typeof min === "number") schema.minimum = min;
914
+ const max = meta.get("expect.max");
915
+ if (typeof max === "number") schema.maximum = max;
916
+ }
917
+ return schema;
918
+ }
919
+ default: return {};
920
+ }
921
+ };
922
+ return build$1(type);
923
+ }
924
+
379
925
  //#endregion
380
926
  //#region packages/typescript/src/codegen/js-renderer.ts
381
927
  function _define_property$1(obj, key, value) {
@@ -392,7 +938,9 @@ var JsRenderer = class extends BaseRenderer {
392
938
  pre() {
393
939
  this.writeln("// prettier-ignore-start");
394
940
  this.writeln("/* eslint-disable */");
395
- this.writeln("import { defineAnnotatedType as $ } from \"@atscript/typescript\"");
941
+ const imports = ["defineAnnotatedType as $"];
942
+ if (!this.opts?.preRenderJsonSchema) imports.push("buildJsonSchema as $$");
943
+ this.writeln(`import { ${imports.join(", ")} } from "@atscript/typescript"`);
396
944
  }
397
945
  post() {
398
946
  for (const node of this.postAnnotate) {
@@ -412,6 +960,18 @@ var JsRenderer = class extends BaseRenderer {
412
960
  this.writeln("static __is_atscript_annotated_type = true");
413
961
  this.writeln("static type = {}");
414
962
  this.writeln("static metadata = new Map()");
963
+ if (this.opts?.preRenderJsonSchema) {
964
+ const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
965
+ this.writeln(`static _jsonSchema = ${schema}`);
966
+ this.writeln("static toJsonSchema() {");
967
+ this.indent().writeln("return this._jsonSchema").unindent();
968
+ this.writeln("}");
969
+ } else {
970
+ this.writeln("static _jsonSchema");
971
+ this.writeln("static toJsonSchema() {");
972
+ this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
973
+ this.writeln("}");
974
+ }
415
975
  this.popln();
416
976
  this.postAnnotate.push(node);
417
977
  this.writeln();
@@ -425,10 +985,125 @@ var JsRenderer = class extends BaseRenderer {
425
985
  this.writeln("static __is_atscript_annotated_type = true");
426
986
  this.writeln("static type = {}");
427
987
  this.writeln("static metadata = new Map()");
988
+ if (this.opts?.jsonSchema) if (typeof this.opts.jsonSchema === "object" && this.opts.jsonSchema.preRender) {
989
+ const schema = JSON.stringify(buildJsonSchema(this.toAnnotatedType(node)));
990
+ this.writeln(`static _jsonSchema = ${schema}`);
991
+ this.writeln("static toJsonSchema() {");
992
+ this.indent().writeln("return this._jsonSchema").unindent();
993
+ this.writeln("}");
994
+ } else {
995
+ this.writeln("static _jsonSchema");
996
+ this.writeln("static toJsonSchema() {");
997
+ this.indent().writeln("return this._jsonSchema ?? (this._jsonSchema = $$(this))").unindent();
998
+ this.writeln("}");
999
+ }
428
1000
  this.popln();
429
1001
  this.postAnnotate.push(node);
430
1002
  this.writeln();
431
1003
  }
1004
+ toAnnotatedType(node) {
1005
+ return this.toAnnotatedHandle(node).$type;
1006
+ }
1007
+ toAnnotatedHandle(node, skipAnnotations = false) {
1008
+ if (!node) return defineAnnotatedType();
1009
+ switch (node.entity) {
1010
+ case "interface":
1011
+ case "type": {
1012
+ const def = node.getDefinition();
1013
+ const handle = this.toAnnotatedHandle(def, true);
1014
+ return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1015
+ }
1016
+ case "prop": {
1017
+ const prop = node;
1018
+ const def = prop.getDefinition();
1019
+ const handle = this.toAnnotatedHandle(def, true);
1020
+ if (!skipAnnotations) {
1021
+ this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(prop));
1022
+ if (prop.token("optional")) handle.optional();
1023
+ }
1024
+ return handle;
1025
+ }
1026
+ case "ref": {
1027
+ const ref = node;
1028
+ const decl = this.doc.unwindType(ref.id, ref.chain)?.def;
1029
+ const handle = this.toAnnotatedHandle(decl, true);
1030
+ return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1031
+ }
1032
+ case "primitive": {
1033
+ const prim = node;
1034
+ const handle = defineAnnotatedType();
1035
+ handle.designType(prim.id === "never" ? "never" : prim.config.type);
1036
+ if (!skipAnnotations) this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1037
+ return handle;
1038
+ }
1039
+ case "const": {
1040
+ const c = node;
1041
+ const handle = defineAnnotatedType();
1042
+ const t = c.token("identifier")?.type;
1043
+ handle.designType(t === "number" ? "number" : "string");
1044
+ handle.value(t === "number" ? Number(c.id) : c.id);
1045
+ return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1046
+ }
1047
+ case "structure": {
1048
+ const struct = node;
1049
+ const handle = defineAnnotatedType("object");
1050
+ for (const prop of Array.from(struct.props.values())) {
1051
+ const propHandle = this.toAnnotatedHandle(prop);
1052
+ const pattern = prop.token("identifier")?.pattern;
1053
+ if (pattern) handle.propPattern(pattern, propHandle.$type);
1054
+ else handle.prop(prop.id, propHandle.$type);
1055
+ }
1056
+ return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1057
+ }
1058
+ case "group": {
1059
+ const group = node;
1060
+ const kind = group.op === "|" ? "union" : "intersection";
1061
+ const handle = defineAnnotatedType(kind);
1062
+ for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
1063
+ return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1064
+ }
1065
+ case "tuple": {
1066
+ const group = node;
1067
+ const handle = defineAnnotatedType("tuple");
1068
+ for (const item of group.unwrap()) handle.item(this.toAnnotatedHandle(item).$type);
1069
+ return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1070
+ }
1071
+ case "array": {
1072
+ const arr = node;
1073
+ const handle = defineAnnotatedType("array");
1074
+ handle.of(this.toAnnotatedHandle(arr.getDefinition()).$type);
1075
+ return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1076
+ }
1077
+ default: {
1078
+ const handle = defineAnnotatedType();
1079
+ return skipAnnotations ? handle : this.applyExpectAnnotations(handle, this.doc.evalAnnotationsForNode(node));
1080
+ }
1081
+ }
1082
+ }
1083
+ applyExpectAnnotations(handle, annotations) {
1084
+ annotations?.forEach((a) => {
1085
+ switch (a.name) {
1086
+ case "expect.minLength":
1087
+ case "expect.maxLength":
1088
+ case "expect.min":
1089
+ case "expect.max":
1090
+ if (a.args[0]) handle.annotate(a.name, Number(a.args[0].text));
1091
+ break;
1092
+ case "expect.pattern":
1093
+ handle.annotate(a.name, {
1094
+ pattern: a.args[0]?.text || "",
1095
+ flags: a.args[1]?.text,
1096
+ message: a.args[2]?.text
1097
+ }, true);
1098
+ break;
1099
+ case "expect.int":
1100
+ handle.annotate(a.name, true);
1101
+ break;
1102
+ default:
1103
+ }
1104
+ });
1105
+ return handle;
1106
+ }
432
1107
  annotateType(_node, name) {
433
1108
  if (!_node) return this;
434
1109
  const node = this.doc.mergeIntersection(_node);
@@ -479,7 +1154,6 @@ var JsRenderer = class extends BaseRenderer {
479
1154
  defineConst(node) {
480
1155
  const t = node.token("identifier")?.type;
481
1156
  const designType = t === "text" ? "string" : t === "number" ? "number" : "unknown";
482
- const type = t === "text" ? "String" : t === "number" ? "Number" : "undefined";
483
1157
  this.writeln(`.designType("${escapeQuotes(designType)}")`);
484
1158
  this.writeln(`.value(${t === "text" ? `"${escapeQuotes(node.id)}"` : node.id})`);
485
1159
  return this;
@@ -619,24 +1293,24 @@ else targetValue = "true";
619
1293
  if (multiple) this.writeln(`.annotate("${escapeQuotes(an.name)}", ${targetValue}, true)`);
620
1294
  else this.writeln(`.annotate("${escapeQuotes(an.name)}", ${targetValue})`);
621
1295
  }
622
- constructor(...args) {
623
- super(...args), _define_property$1(this, "postAnnotate", []);
1296
+ constructor(doc, opts) {
1297
+ super(doc), _define_property$1(this, "opts", void 0), _define_property$1(this, "postAnnotate", void 0), this.opts = opts, this.postAnnotate = [];
624
1298
  }
625
1299
  };
626
1300
 
627
1301
  //#endregion
628
1302
  //#region packages/typescript/src/plugin.ts
629
- const tsPlugin = () => {
1303
+ const tsPlugin = (opts) => {
630
1304
  return {
631
1305
  name: "typesccript",
632
1306
  render(doc, format) {
633
1307
  if (format === "dts") return [{
634
1308
  fileName: `${doc.name}.d.ts`,
635
- content: new TypeRenderer(doc).render()
1309
+ content: new TypeRenderer(doc, opts).render()
636
1310
  }];
637
1311
  if (format === "js") return [{
638
1312
  fileName: `${doc.name}.js`,
639
- content: new JsRenderer(doc).render()
1313
+ content: new JsRenderer(doc, opts).render()
640
1314
  }];
641
1315
  },
642
1316
  async buildEnd(output, format, repo) {