@atscript/typescript 0.1.1 → 0.1.3

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,18 @@
1
1
 
2
+ //#region packages/typescript/src/traverse.ts
3
+ function forAnnotatedType(def, handlers) {
4
+ switch (def.type.kind) {
5
+ case "": return handlers.final(def);
6
+ case "object": return handlers.object(def);
7
+ case "array": return handlers.array(def);
8
+ case "union": return handlers.union(def);
9
+ case "intersection": return handlers.intersection(def);
10
+ case "tuple": return handlers.tuple(def);
11
+ default: throw new Error(`Unknown type kind "${def.type.kind}"`);
12
+ }
13
+ }
14
+
15
+ //#endregion
2
16
  //#region packages/typescript/src/validator.ts
3
17
  function _define_property(obj, key, value) {
4
18
  if (key in obj) Object.defineProperty(obj, key, {
@@ -43,7 +57,17 @@ var Validator = class {
43
57
  throw() {
44
58
  throw new ValidatorError(this.errors);
45
59
  }
46
- validate(value, safe) {
60
+ /**
61
+ * Validates a value against the type definition.
62
+ *
63
+ * Acts as a TypeScript type guard — when it returns `true`, the value
64
+ * is narrowed to `DataType`.
65
+ *
66
+ * @param value - The value to validate.
67
+ * @param safe - If `true`, returns `false` on failure instead of throwing.
68
+ * @returns `true` if the value matches the type definition.
69
+ * @throws {ValidatorError} When validation fails and `safe` is not `true`.
70
+ */ validate(value, safe) {
47
71
  this.push("");
48
72
  this.errors = [];
49
73
  this.stackErrors = [];
@@ -70,15 +94,14 @@ var Validator = class {
70
94
  return this.stackPath.slice(1).join(".");
71
95
  }
72
96
  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
- }
97
+ return forAnnotatedType(def, {
98
+ final: (d) => this.validatePrimitive(d, value),
99
+ object: (d) => this.validateObject(d, value),
100
+ array: (d) => this.validateArray(d, value),
101
+ union: (d) => this.validateUnion(d, value),
102
+ intersection: (d) => this.validateIntersection(d, value),
103
+ tuple: (d) => this.validateTuple(d, value)
104
+ });
82
105
  }
83
106
  validateUnion(def, value) {
84
107
  let i = 0;
@@ -310,7 +333,7 @@ else {
310
333
  constructor(def, opts) {
311
334
  _define_property(this, "def", void 0);
312
335
  _define_property(this, "opts", void 0);
313
- _define_property(this, "errors", void 0);
336
+ /** Validation errors collected during the last {@link validate} call. */ _define_property(this, "errors", void 0);
314
337
  _define_property(this, "stackErrors", void 0);
315
338
  _define_property(this, "stackPath", void 0);
316
339
  this.def = def;
@@ -337,6 +360,15 @@ var ValidatorError = class extends Error {
337
360
  function isAnnotatedType(type) {
338
361
  return type && type.__is_atscript_annotated_type;
339
362
  }
363
+ function annotate(metadata, key, value, asArray) {
364
+ if (!metadata) return;
365
+ if (asArray) if (metadata.has(key)) {
366
+ const a = metadata.get(key);
367
+ if (Array.isArray(a)) a.push(value);
368
+ else metadata.set(key, [a, value]);
369
+ } else metadata.set(key, [value]);
370
+ else metadata.set(key, value);
371
+ }
340
372
  function defineAnnotatedType(_kind, base) {
341
373
  const kind = _kind || "";
342
374
  const type = base?.type || {};
@@ -436,12 +468,7 @@ else if (!newBase) throw new Error(`"${typeName}" is not annotated type`);
436
468
  return this;
437
469
  },
438
470
  annotate(key, value, asArray) {
439
- if (asArray) if (this.$metadata.has(key)) {
440
- const a = this.$metadata.get(key);
441
- if (Array.isArray(a)) a.push(value);
442
- else this.$metadata.set(key, [a, value]);
443
- } else this.$metadata.set(key, [value]);
444
- else this.$metadata.set(key, value);
471
+ annotate(this.$metadata, key, value, asArray);
445
472
  return this;
446
473
  }
447
474
  };
@@ -465,14 +492,12 @@ function isAnnotatedTypeOfPrimitive(t) {
465
492
  //#region packages/typescript/src/json-schema.ts
466
493
  function buildJsonSchema(type) {
467
494
  const build = (def) => {
468
- const t = def.type;
469
495
  const meta = def.metadata;
470
- switch (t.kind) {
471
- case "object": {
472
- const obj = t;
496
+ return forAnnotatedType(def, {
497
+ object(d) {
473
498
  const properties = {};
474
499
  const required = [];
475
- for (const [key, val] of obj.props.entries()) {
500
+ for (const [key, val] of d.type.props.entries()) {
476
501
  properties[key] = build(val);
477
502
  if (!val.optional) required.push(key);
478
503
  }
@@ -482,41 +507,36 @@ function buildJsonSchema(type) {
482
507
  };
483
508
  if (required.length) schema.required = required;
484
509
  return schema;
485
- }
486
- case "array": {
487
- const arr = t;
510
+ },
511
+ array(d) {
488
512
  const schema = {
489
513
  type: "array",
490
- items: build(arr.of)
514
+ items: build(d.type.of)
491
515
  };
492
516
  const minLength = meta.get("expect.minLength");
493
517
  if (typeof minLength === "number") schema.minItems = minLength;
494
518
  const maxLength = meta.get("expect.maxLength");
495
519
  if (typeof maxLength === "number") schema.maxItems = maxLength;
496
520
  return schema;
497
- }
498
- case "union": {
499
- const grp = t;
500
- return { anyOf: grp.items.map(build) };
501
- }
502
- case "intersection": {
503
- const grp = t;
504
- return { allOf: grp.items.map(build) };
505
- }
506
- case "tuple": {
507
- const grp = t;
521
+ },
522
+ union(d) {
523
+ return { anyOf: d.type.items.map(build) };
524
+ },
525
+ intersection(d) {
526
+ return { allOf: d.type.items.map(build) };
527
+ },
528
+ tuple(d) {
508
529
  return {
509
530
  type: "array",
510
- items: grp.items.map(build),
531
+ items: d.type.items.map(build),
511
532
  additionalItems: false
512
533
  };
513
- }
514
- case "": {
515
- const fin = t;
534
+ },
535
+ final(d) {
516
536
  const schema = {};
517
- if (fin.value !== undefined) schema.const = fin.value;
518
- if (fin.designType && fin.designType !== "any") {
519
- schema.type = fin.designType === "undefined" ? "null" : fin.designType;
537
+ if (d.type.value !== undefined) schema.const = d.type.value;
538
+ if (d.type.designType && d.type.designType !== "any") {
539
+ schema.type = d.type.designType === "undefined" ? "null" : d.type.designType;
520
540
  if (schema.type === "number" && meta.get("expect.int")) schema.type = "integer";
521
541
  }
522
542
  if (schema.type === "string") {
@@ -536,11 +556,255 @@ else schema.allOf = (schema.allOf || []).concat(patterns.map((p) => ({ pattern:
536
556
  }
537
557
  return schema;
538
558
  }
539
- default: return {};
540
- }
559
+ });
541
560
  };
542
561
  return build(type);
543
562
  }
563
+ function fromJsonSchema(schema) {
564
+ const convert = (s) => {
565
+ if (!s || Object.keys(s).length === 0) return defineAnnotatedType().designType("any").$type;
566
+ if (s.$ref) throw new Error("$ref is not supported by fromJsonSchema. Dereference the schema first.");
567
+ if ("const" in s) {
568
+ const val = s.const;
569
+ const dt = val === null ? "null" : typeof val;
570
+ return defineAnnotatedType().designType(dt).value(val).$type;
571
+ }
572
+ if (s.enum) {
573
+ const handle = defineAnnotatedType("union");
574
+ for (const val of s.enum) {
575
+ const dt = val === null ? "null" : typeof val;
576
+ handle.item(defineAnnotatedType().designType(dt).value(val).$type);
577
+ }
578
+ return handle.$type;
579
+ }
580
+ if (s.anyOf) {
581
+ const handle = defineAnnotatedType("union");
582
+ for (const item of s.anyOf) handle.item(convert(item));
583
+ return handle.$type;
584
+ }
585
+ if (s.oneOf) {
586
+ const handle = defineAnnotatedType("union");
587
+ for (const item of s.oneOf) handle.item(convert(item));
588
+ return handle.$type;
589
+ }
590
+ if (s.allOf && !s.type) {
591
+ const handle = defineAnnotatedType("intersection");
592
+ for (const item of s.allOf) handle.item(convert(item));
593
+ return handle.$type;
594
+ }
595
+ if (Array.isArray(s.type)) {
596
+ const handle = defineAnnotatedType("union");
597
+ for (const t of s.type) handle.item(convert({
598
+ ...s,
599
+ type: t
600
+ }));
601
+ return handle.$type;
602
+ }
603
+ if (s.type === "object") {
604
+ const handle = defineAnnotatedType("object");
605
+ const required = new Set(s.required || []);
606
+ if (s.properties) for (const [key, propSchema] of Object.entries(s.properties)) {
607
+ const propType = convert(propSchema);
608
+ if (!required.has(key)) propType.optional = true;
609
+ handle.prop(key, propType);
610
+ }
611
+ return handle.$type;
612
+ }
613
+ if (s.type === "array") {
614
+ if (Array.isArray(s.items)) {
615
+ const handle$1 = defineAnnotatedType("tuple");
616
+ for (const item of s.items) handle$1.item(convert(item));
617
+ return handle$1.$type;
618
+ }
619
+ const itemType = s.items ? convert(s.items) : defineAnnotatedType().designType("any").$type;
620
+ const handle = defineAnnotatedType("array").of(itemType);
621
+ if (typeof s.minItems === "number") handle.annotate("expect.minLength", s.minItems);
622
+ if (typeof s.maxItems === "number") handle.annotate("expect.maxLength", s.maxItems);
623
+ return handle.$type;
624
+ }
625
+ if (s.type === "string") {
626
+ const handle = defineAnnotatedType().designType("string").tags("string");
627
+ if (typeof s.minLength === "number") handle.annotate("expect.minLength", s.minLength);
628
+ if (typeof s.maxLength === "number") handle.annotate("expect.maxLength", s.maxLength);
629
+ if (s.pattern) handle.annotate("expect.pattern", { pattern: s.pattern }, true);
630
+ if (s.allOf) {
631
+ for (const item of s.allOf) if (item.pattern) handle.annotate("expect.pattern", { pattern: item.pattern }, true);
632
+ }
633
+ return handle.$type;
634
+ }
635
+ if (s.type === "integer") {
636
+ const handle = defineAnnotatedType().designType("number").tags("number");
637
+ handle.annotate("expect.int", true);
638
+ if (typeof s.minimum === "number") handle.annotate("expect.min", s.minimum);
639
+ if (typeof s.maximum === "number") handle.annotate("expect.max", s.maximum);
640
+ return handle.$type;
641
+ }
642
+ if (s.type === "number") {
643
+ const handle = defineAnnotatedType().designType("number").tags("number");
644
+ if (typeof s.minimum === "number") handle.annotate("expect.min", s.minimum);
645
+ if (typeof s.maximum === "number") handle.annotate("expect.max", s.maximum);
646
+ return handle.$type;
647
+ }
648
+ if (s.type === "boolean") return defineAnnotatedType().designType("boolean").tags("boolean").$type;
649
+ if (s.type === "null") return defineAnnotatedType().designType("null").tags("null").$type;
650
+ return defineAnnotatedType().designType("any").$type;
651
+ };
652
+ return convert(schema);
653
+ }
654
+
655
+ //#endregion
656
+ //#region packages/typescript/src/serialize.ts
657
+ const SERIALIZE_VERSION = 1;
658
+ function serializeAnnotatedType(type, options) {
659
+ const result = serializeNode(type, [], options);
660
+ result.$v = SERIALIZE_VERSION;
661
+ return result;
662
+ }
663
+ function serializeNode(def, path, options) {
664
+ const result = {
665
+ type: serializeTypeDef(def, path, options),
666
+ metadata: serializeMetadata(def.metadata, path, def.type.kind, options)
667
+ };
668
+ if (def.optional) result.optional = true;
669
+ return result;
670
+ }
671
+ function serializeTypeDef(def, path, options) {
672
+ return forAnnotatedType(def, {
673
+ final(d) {
674
+ const result = {
675
+ kind: "",
676
+ designType: d.type.designType,
677
+ tags: Array.from(d.type.tags)
678
+ };
679
+ if (d.type.value !== undefined) result.value = d.type.value;
680
+ return result;
681
+ },
682
+ object(d) {
683
+ const props = {};
684
+ for (const [key, val] of d.type.props.entries()) props[key] = serializeNode(val, [...path, key], options);
685
+ const propsPatterns = d.type.propsPatterns.map((pp) => ({
686
+ pattern: {
687
+ source: pp.pattern.source,
688
+ flags: pp.pattern.flags
689
+ },
690
+ def: serializeNode(pp.def, path, options)
691
+ }));
692
+ return {
693
+ kind: "object",
694
+ props,
695
+ propsPatterns,
696
+ tags: Array.from(d.type.tags)
697
+ };
698
+ },
699
+ array(d) {
700
+ return {
701
+ kind: "array",
702
+ of: serializeNode(d.type.of, path, options),
703
+ tags: Array.from(d.type.tags)
704
+ };
705
+ },
706
+ union(d) {
707
+ return {
708
+ kind: "union",
709
+ items: d.type.items.map((item) => serializeNode(item, path, options)),
710
+ tags: Array.from(d.type.tags)
711
+ };
712
+ },
713
+ intersection(d) {
714
+ return {
715
+ kind: "intersection",
716
+ items: d.type.items.map((item) => serializeNode(item, path, options)),
717
+ tags: Array.from(d.type.tags)
718
+ };
719
+ },
720
+ tuple(d) {
721
+ return {
722
+ kind: "tuple",
723
+ items: d.type.items.map((item) => serializeNode(item, path, options)),
724
+ tags: Array.from(d.type.tags)
725
+ };
726
+ }
727
+ });
728
+ }
729
+ function serializeMetadata(metadata, path, kind, options) {
730
+ const result = {};
731
+ const ignoreSet = options?.ignoreAnnotations ? new Set(options.ignoreAnnotations) : undefined;
732
+ for (const [key, value] of metadata.entries()) {
733
+ if (ignoreSet?.has(key)) continue;
734
+ if (options?.processAnnotation) {
735
+ const processed = options.processAnnotation({
736
+ key,
737
+ value,
738
+ path,
739
+ kind
740
+ });
741
+ if (processed === undefined || processed === null) continue;
742
+ result[processed.key] = processed.value;
743
+ continue;
744
+ }
745
+ result[key] = value;
746
+ }
747
+ return result;
748
+ }
749
+ function deserializeAnnotatedType(data) {
750
+ if (data.$v !== SERIALIZE_VERSION) throw new Error(`Unsupported serialized type version: ${data.$v} (expected ${SERIALIZE_VERSION})`);
751
+ return deserializeNode(data);
752
+ }
753
+ function deserializeNode(data) {
754
+ const metadata = new Map(Object.entries(data.metadata));
755
+ const type = deserializeTypeDef(data.type);
756
+ const result = {
757
+ __is_atscript_annotated_type: true,
758
+ type,
759
+ metadata,
760
+ validator(opts) {
761
+ return new Validator(this, opts);
762
+ }
763
+ };
764
+ if (data.optional) result.optional = true;
765
+ return result;
766
+ }
767
+ function deserializeTypeDef(t) {
768
+ const tags = new Set(t.tags);
769
+ switch (t.kind) {
770
+ case "": {
771
+ const result = {
772
+ kind: "",
773
+ designType: t.designType,
774
+ tags
775
+ };
776
+ if (t.value !== undefined) result.value = t.value;
777
+ return result;
778
+ }
779
+ case "object": {
780
+ const props = new Map();
781
+ for (const [key, val] of Object.entries(t.props)) props.set(key, deserializeNode(val));
782
+ const propsPatterns = t.propsPatterns.map((pp) => ({
783
+ pattern: new RegExp(pp.pattern.source, pp.pattern.flags),
784
+ def: deserializeNode(pp.def)
785
+ }));
786
+ return {
787
+ kind: "object",
788
+ props,
789
+ propsPatterns,
790
+ tags
791
+ };
792
+ }
793
+ case "array": return {
794
+ kind: "array",
795
+ of: deserializeNode(t.of),
796
+ tags
797
+ };
798
+ case "union":
799
+ case "intersection":
800
+ case "tuple": return {
801
+ kind: t.kind,
802
+ items: t.items.map((item) => deserializeNode(item)),
803
+ tags
804
+ };
805
+ default: throw new Error(`Unknown serialized type kind "${t.kind}"`);
806
+ }
807
+ }
544
808
 
545
809
  //#endregion
546
- export { Validator, ValidatorError, buildJsonSchema, defineAnnotatedType, isAnnotatedType, isAnnotatedTypeOfPrimitive };
810
+ export { SERIALIZE_VERSION, Validator, ValidatorError, annotate, buildJsonSchema, defineAnnotatedType, deserializeAnnotatedType, forAnnotatedType, fromJsonSchema, isAnnotatedType, isAnnotatedTypeOfPrimitive, serializeAnnotatedType };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/typescript",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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.1"
73
+ "@atscript/core": "^0.1.3"
74
74
  },
75
75
  "dependencies": {
76
76
  "@moostjs/event-cli": "^0.5.32",