@atscript/typescript 0.0.27 → 0.0.29

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