@atscript/typescript 0.1.2 → 0.1.4

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/utils.cjs CHANGED
@@ -1,5 +1,23 @@
1
1
  "use strict";
2
2
 
3
+ //#region packages/typescript/src/traverse.ts
4
+ function forAnnotatedType(def, handlers) {
5
+ switch (def.type.kind) {
6
+ case "": {
7
+ const typed = def;
8
+ if (handlers.phantom && typed.type.designType === "phantom") return handlers.phantom(typed);
9
+ return handlers.final(typed);
10
+ }
11
+ case "object": return handlers.object(def);
12
+ case "array": return handlers.array(def);
13
+ case "union": return handlers.union(def);
14
+ case "intersection": return handlers.intersection(def);
15
+ case "tuple": return handlers.tuple(def);
16
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
17
+ }
18
+ }
19
+
20
+ //#endregion
3
21
  //#region packages/typescript/src/validator.ts
4
22
  function _define_property(obj, key, value) {
5
23
  if (key in obj) Object.defineProperty(obj, key, {
@@ -44,7 +62,17 @@ var Validator = class {
44
62
  throw() {
45
63
  throw new ValidatorError(this.errors);
46
64
  }
47
- validate(value, safe) {
65
+ /**
66
+ * Validates a value against the type definition.
67
+ *
68
+ * Acts as a TypeScript type guard — when it returns `true`, the value
69
+ * is narrowed to `DataType`.
70
+ *
71
+ * @param value - The value to validate.
72
+ * @param safe - If `true`, returns `false` on failure instead of throwing.
73
+ * @returns `true` if the value matches the type definition.
74
+ * @throws {ValidatorError} When validation fails and `safe` is not `true`.
75
+ */ validate(value, safe) {
48
76
  this.push("");
49
77
  this.errors = [];
50
78
  this.stackErrors = [];
@@ -71,15 +99,15 @@ var Validator = class {
71
99
  return this.stackPath.slice(1).join(".");
72
100
  }
73
101
  validateAnnotatedType(def, value) {
74
- switch (def.type.kind) {
75
- case "object": return this.validateObject(def, value);
76
- case "union": return this.validateUnion(def, value);
77
- case "intersection": return this.validateIntersection(def, value);
78
- case "tuple": return this.validateTuple(def, value);
79
- case "array": return this.validateArray(def, value);
80
- case "": return this.validatePrimitive(def, value);
81
- default: throw new Error(`Unknown type "${def.type.kind}"`);
82
- }
102
+ return forAnnotatedType(def, {
103
+ final: (d) => this.validatePrimitive(d, value),
104
+ phantom: () => true,
105
+ object: (d) => this.validateObject(d, value),
106
+ array: (d) => this.validateArray(d, value),
107
+ union: (d) => this.validateUnion(d, value),
108
+ intersection: (d) => this.validateIntersection(d, value),
109
+ tuple: (d) => this.validateTuple(d, value)
110
+ });
83
111
  }
84
112
  validateUnion(def, value) {
85
113
  let i = 0;
@@ -170,7 +198,7 @@ var Validator = class {
170
198
  let partialFunctionMatched = false;
171
199
  if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
172
200
  for (const [key, item] of def.type.props.entries()) {
173
- if (skipList.has(key)) continue;
201
+ if (skipList.has(key) || isPhantomType(item)) continue;
174
202
  typeKeys.add(key);
175
203
  if (value[key] === undefined) {
176
204
  if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
@@ -311,7 +339,7 @@ else {
311
339
  constructor(def, opts) {
312
340
  _define_property(this, "def", void 0);
313
341
  _define_property(this, "opts", void 0);
314
- _define_property(this, "errors", void 0);
342
+ /** Validation errors collected during the last {@link validate} call. */ _define_property(this, "errors", void 0);
315
343
  _define_property(this, "stackErrors", void 0);
316
344
  _define_property(this, "stackPath", void 0);
317
345
  this.def = def;
@@ -452,6 +480,9 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
452
480
  };
453
481
  return handle;
454
482
  }
483
+ function isPhantomType(def) {
484
+ return def.type.kind === "" && def.type.designType === "phantom";
485
+ }
455
486
  function isAnnotatedTypeOfPrimitive(t) {
456
487
  if (["array", "object"].includes(t.type.kind)) return false;
457
488
  if (!t.type.kind) return true;
@@ -470,14 +501,16 @@ function isAnnotatedTypeOfPrimitive(t) {
470
501
  //#region packages/typescript/src/json-schema.ts
471
502
  function buildJsonSchema(type) {
472
503
  const build = (def) => {
473
- const t = def.type;
474
504
  const meta = def.metadata;
475
- switch (t.kind) {
476
- case "object": {
477
- const obj = t;
505
+ return forAnnotatedType(def, {
506
+ phantom() {
507
+ return {};
508
+ },
509
+ object(d) {
478
510
  const properties = {};
479
511
  const required = [];
480
- for (const [key, val] of obj.props.entries()) {
512
+ for (const [key, val] of d.type.props.entries()) {
513
+ if (isPhantomType(val)) continue;
481
514
  properties[key] = build(val);
482
515
  if (!val.optional) required.push(key);
483
516
  }
@@ -487,41 +520,36 @@ function buildJsonSchema(type) {
487
520
  };
488
521
  if (required.length) schema.required = required;
489
522
  return schema;
490
- }
491
- case "array": {
492
- const arr = t;
523
+ },
524
+ array(d) {
493
525
  const schema = {
494
526
  type: "array",
495
- items: build(arr.of)
527
+ items: build(d.type.of)
496
528
  };
497
529
  const minLength = meta.get("expect.minLength");
498
530
  if (typeof minLength === "number") schema.minItems = minLength;
499
531
  const maxLength = meta.get("expect.maxLength");
500
532
  if (typeof maxLength === "number") schema.maxItems = maxLength;
501
533
  return schema;
502
- }
503
- case "union": {
504
- const grp = t;
505
- return { anyOf: grp.items.map(build) };
506
- }
507
- case "intersection": {
508
- const grp = t;
509
- return { allOf: grp.items.map(build) };
510
- }
511
- case "tuple": {
512
- const grp = t;
534
+ },
535
+ union(d) {
536
+ return { anyOf: d.type.items.map(build) };
537
+ },
538
+ intersection(d) {
539
+ return { allOf: d.type.items.map(build) };
540
+ },
541
+ tuple(d) {
513
542
  return {
514
543
  type: "array",
515
- items: grp.items.map(build),
544
+ items: d.type.items.map(build),
516
545
  additionalItems: false
517
546
  };
518
- }
519
- case "": {
520
- const fin = t;
547
+ },
548
+ final(d) {
521
549
  const schema = {};
522
- if (fin.value !== undefined) schema.const = fin.value;
523
- if (fin.designType && fin.designType !== "any") {
524
- schema.type = fin.designType === "undefined" ? "null" : fin.designType;
550
+ if (d.type.value !== undefined) schema.const = d.type.value;
551
+ if (d.type.designType && d.type.designType !== "any") {
552
+ schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
525
553
  if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
526
554
  }
527
555
  if (schema.type === "string") {
@@ -541,17 +569,277 @@ else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern:
541
569
  }
542
570
  return schema;
543
571
  }
544
- default: return {};
545
- }
572
+ });
546
573
  };
547
574
  return build(type);
548
575
  }
576
+ function fromJsonSchema(schema) {
577
+ const convert = (s) => {
578
+ if (!s || Object.keys(s).length === 0) return defineAnnotatedType().designType("any").$type;
579
+ if (s.$ref) throw new Error("$ref is not supported by fromJsonSchema. Dereference the schema first.");
580
+ if ("const" in s) {
581
+ const val = s.const;
582
+ const dt = val === null ? "null" : typeof val;
583
+ return defineAnnotatedType().designType(dt).value(val).$type;
584
+ }
585
+ if (s.enum) {
586
+ const handle = defineAnnotatedType("union");
587
+ for (const val of s.enum) {
588
+ const dt = val === null ? "null" : typeof val;
589
+ handle.item(defineAnnotatedType().designType(dt).value(val).$type);
590
+ }
591
+ return handle.$type;
592
+ }
593
+ if (s.anyOf) {
594
+ const handle = defineAnnotatedType("union");
595
+ for (const item of s.anyOf) handle.item(convert(item));
596
+ return handle.$type;
597
+ }
598
+ if (s.oneOf) {
599
+ const handle = defineAnnotatedType("union");
600
+ for (const item of s.oneOf) handle.item(convert(item));
601
+ return handle.$type;
602
+ }
603
+ if (s.allOf && !s.type) {
604
+ const handle = defineAnnotatedType("intersection");
605
+ for (const item of s.allOf) handle.item(convert(item));
606
+ return handle.$type;
607
+ }
608
+ if (Array.isArray(s.type)) {
609
+ const handle = defineAnnotatedType("union");
610
+ for (const t of s.type) handle.item(convert({
611
+ ...s,
612
+ type: t
613
+ }));
614
+ return handle.$type;
615
+ }
616
+ if (s.type === "object") {
617
+ const handle = defineAnnotatedType("object");
618
+ const required = new Set(s.required || []);
619
+ if (s.properties) for (const [key, propSchema] of Object.entries(s.properties)) {
620
+ const propType = convert(propSchema);
621
+ if (!required.has(key)) propType.optional = true;
622
+ handle.prop(key, propType);
623
+ }
624
+ return handle.$type;
625
+ }
626
+ if (s.type === "array") {
627
+ if (Array.isArray(s.items)) {
628
+ const handle$1 = defineAnnotatedType("tuple");
629
+ for (const item of s.items) handle$1.item(convert(item));
630
+ return handle$1.$type;
631
+ }
632
+ const itemType = s.items ? convert(s.items) : defineAnnotatedType().designType("any").$type;
633
+ const handle = defineAnnotatedType("array").of(itemType);
634
+ if (typeof s.minItems === "number") handle.annotate("expect.minLength", s.minItems);
635
+ if (typeof s.maxItems === "number") handle.annotate("expect.maxLength", s.maxItems);
636
+ return handle.$type;
637
+ }
638
+ if (s.type === "string") {
639
+ const handle = defineAnnotatedType().designType("string").tags("string");
640
+ if (typeof s.minLength === "number") handle.annotate("expect.minLength", s.minLength);
641
+ if (typeof s.maxLength === "number") handle.annotate("expect.maxLength", s.maxLength);
642
+ if (s.pattern) handle.annotate("expect.pattern", { pattern: s.pattern }, true);
643
+ if (s.allOf) {
644
+ for (const item of s.allOf) if (item.pattern) handle.annotate("expect.pattern", { pattern: item.pattern }, true);
645
+ }
646
+ return handle.$type;
647
+ }
648
+ if (s.type === "integer") {
649
+ const handle = defineAnnotatedType().designType("number").tags("number");
650
+ handle.annotate("expect.int", true);
651
+ if (typeof s.minimum === "number") handle.annotate("expect.min", s.minimum);
652
+ if (typeof s.maximum === "number") handle.annotate("expect.max", s.maximum);
653
+ return handle.$type;
654
+ }
655
+ if (s.type === "number") {
656
+ const handle = defineAnnotatedType().designType("number").tags("number");
657
+ if (typeof s.minimum === "number") handle.annotate("expect.min", s.minimum);
658
+ if (typeof s.maximum === "number") handle.annotate("expect.max", s.maximum);
659
+ return handle.$type;
660
+ }
661
+ if (s.type === "boolean") return defineAnnotatedType().designType("boolean").tags("boolean").$type;
662
+ if (s.type === "null") return defineAnnotatedType().designType("null").tags("null").$type;
663
+ return defineAnnotatedType().designType("any").$type;
664
+ };
665
+ return convert(schema);
666
+ }
667
+
668
+ //#endregion
669
+ //#region packages/typescript/src/serialize.ts
670
+ const SERIALIZE_VERSION = 1;
671
+ function serializeAnnotatedType(type, options) {
672
+ const result = serializeNode(type, [], options);
673
+ result.$v = SERIALIZE_VERSION;
674
+ return result;
675
+ }
676
+ function serializeNode(def, path, options) {
677
+ const result = {
678
+ type: serializeTypeDef(def, path, options),
679
+ metadata: serializeMetadata(def.metadata, path, def.type.kind, options)
680
+ };
681
+ if (def.optional) result.optional = true;
682
+ return result;
683
+ }
684
+ function serializeTypeDef(def, path, options) {
685
+ return forAnnotatedType(def, {
686
+ phantom(d) {
687
+ return {
688
+ kind: "",
689
+ designType: d.type.designType,
690
+ tags: Array.from(d.type.tags)
691
+ };
692
+ },
693
+ final(d) {
694
+ const result = {
695
+ kind: "",
696
+ designType: d.type.designType,
697
+ tags: Array.from(d.type.tags)
698
+ };
699
+ if (d.type.value !== undefined) result.value = d.type.value;
700
+ return result;
701
+ },
702
+ object(d) {
703
+ const props = {};
704
+ for (const [key, val] of d.type.props.entries()) {
705
+ if (isPhantomType(val)) continue;
706
+ props[key] = serializeNode(val, [...path, key], options);
707
+ }
708
+ const propsPatterns = d.type.propsPatterns.map((pp) => ({
709
+ pattern: {
710
+ source: pp.pattern.source,
711
+ flags: pp.pattern.flags
712
+ },
713
+ def: serializeNode(pp.def, path, options)
714
+ }));
715
+ return {
716
+ kind: "object",
717
+ props,
718
+ propsPatterns,
719
+ tags: Array.from(d.type.tags)
720
+ };
721
+ },
722
+ array(d) {
723
+ return {
724
+ kind: "array",
725
+ of: serializeNode(d.type.of, path, options),
726
+ tags: Array.from(d.type.tags)
727
+ };
728
+ },
729
+ union(d) {
730
+ return {
731
+ kind: "union",
732
+ items: d.type.items.map((item) => serializeNode(item, path, options)),
733
+ tags: Array.from(d.type.tags)
734
+ };
735
+ },
736
+ intersection(d) {
737
+ return {
738
+ kind: "intersection",
739
+ items: d.type.items.map((item) => serializeNode(item, path, options)),
740
+ tags: Array.from(d.type.tags)
741
+ };
742
+ },
743
+ tuple(d) {
744
+ return {
745
+ kind: "tuple",
746
+ items: d.type.items.map((item) => serializeNode(item, path, options)),
747
+ tags: Array.from(d.type.tags)
748
+ };
749
+ }
750
+ });
751
+ }
752
+ function serializeMetadata(metadata, path, kind, options) {
753
+ const result = {};
754
+ const ignoreSet = options?.ignoreAnnotations ? new Set(options.ignoreAnnotations) : undefined;
755
+ for (const [key, value] of metadata.entries()) {
756
+ if (ignoreSet?.has(key)) continue;
757
+ if (options?.processAnnotation) {
758
+ const processed = options.processAnnotation({
759
+ key,
760
+ value,
761
+ path,
762
+ kind
763
+ });
764
+ if (processed === undefined || processed === null) continue;
765
+ result[processed.key] = processed.value;
766
+ continue;
767
+ }
768
+ result[key] = value;
769
+ }
770
+ return result;
771
+ }
772
+ function deserializeAnnotatedType(data) {
773
+ if (data.$v !== SERIALIZE_VERSION) throw new Error(`Unsupported serialized type version: ${data.$v} (expected ${SERIALIZE_VERSION})`);
774
+ return deserializeNode(data);
775
+ }
776
+ function deserializeNode(data) {
777
+ const metadata = new Map(Object.entries(data.metadata));
778
+ const type = deserializeTypeDef(data.type);
779
+ const result = {
780
+ __is_atscript_annotated_type: true,
781
+ type,
782
+ metadata,
783
+ validator(opts) {
784
+ return new Validator(this, opts);
785
+ }
786
+ };
787
+ if (data.optional) result.optional = true;
788
+ return result;
789
+ }
790
+ function deserializeTypeDef(t) {
791
+ const tags = new Set(t.tags);
792
+ switch (t.kind) {
793
+ case "": {
794
+ const result = {
795
+ kind: "",
796
+ designType: t.designType,
797
+ tags
798
+ };
799
+ if (t.value !== undefined) result.value = t.value;
800
+ return result;
801
+ }
802
+ case "object": {
803
+ const props = new Map();
804
+ for (const [key, val] of Object.entries(t.props)) props.set(key, deserializeNode(val));
805
+ const propsPatterns = t.propsPatterns.map((pp) => ({
806
+ pattern: new RegExp(pp.pattern.source, pp.pattern.flags),
807
+ def: deserializeNode(pp.def)
808
+ }));
809
+ return {
810
+ kind: "object",
811
+ props,
812
+ propsPatterns,
813
+ tags
814
+ };
815
+ }
816
+ case "array": return {
817
+ kind: "array",
818
+ of: deserializeNode(t.of),
819
+ tags
820
+ };
821
+ case "union":
822
+ case "intersection":
823
+ case "tuple": return {
824
+ kind: t.kind,
825
+ items: t.items.map((item) => deserializeNode(item)),
826
+ tags
827
+ };
828
+ default: throw new Error(`Unknown serialized type kind "${t.kind}"`);
829
+ }
830
+ }
549
831
 
550
832
  //#endregion
833
+ exports.SERIALIZE_VERSION = SERIALIZE_VERSION
551
834
  exports.Validator = Validator
552
835
  exports.ValidatorError = ValidatorError
553
836
  exports.annotate = annotate
554
837
  exports.buildJsonSchema = buildJsonSchema
555
838
  exports.defineAnnotatedType = defineAnnotatedType
839
+ exports.deserializeAnnotatedType = deserializeAnnotatedType
840
+ exports.forAnnotatedType = forAnnotatedType
841
+ exports.fromJsonSchema = fromJsonSchema
556
842
  exports.isAnnotatedType = isAnnotatedType
557
- exports.isAnnotatedTypeOfPrimitive = isAnnotatedTypeOfPrimitive
843
+ exports.isAnnotatedTypeOfPrimitive = isAnnotatedTypeOfPrimitive
844
+ exports.isPhantomType = isPhantomType
845
+ exports.serializeAnnotatedType = serializeAnnotatedType