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