@atscript/typescript 0.0.17 → 0.0.18

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
@@ -271,17 +271,41 @@ var TypeRenderer = class extends BaseRenderer {
271
271
  }
272
272
  renderStructure(struct, asClass) {
273
273
  this.blockln("{}");
274
+ const patterns = [];
275
+ let hasProp = false;
274
276
  for (const prop of Array.from(struct.props.values())) {
277
+ if (prop.token("identifier")?.pattern) {
278
+ patterns.push(prop);
279
+ continue;
280
+ }
281
+ hasProp = true;
275
282
  const optional = !!prop.token("optional");
276
283
  this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
277
284
  this.renderTypeDef(prop.getDefinition());
278
285
  this.writeln();
279
286
  }
287
+ if (patterns.length) {
288
+ 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) {
296
+ this.writeln();
297
+ this.write("| ");
298
+ this.renderTypeDef(prop.getDefinition());
299
+ }
300
+ this.unindent();
301
+ this.writeln();
302
+ }
303
+ }
280
304
  if (asClass) {
281
305
  this.writeln("static __is_atscript_annotated_type: true");
282
306
  this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
283
307
  this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
284
- this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: TValidatorOptions) => Validator<TT>`);
308
+ this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
285
309
  }
286
310
  this.pop();
287
311
  }
@@ -323,7 +347,7 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = "TAtscriptTypeFina
323
347
  this.writeln(`const __is_atscript_annotated_type: true`);
324
348
  this.writeln(`const type: ${typeDef}`);
325
349
  this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
326
- this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: TValidatorOptions) => Validator<TT>`);
350
+ this.writeln(`const validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
327
351
  this.popln();
328
352
  }
329
353
  renderJsDoc(node) {
@@ -508,6 +532,17 @@ var JsRenderer = class extends BaseRenderer {
508
532
  this.unindent();
509
533
  this.write(`)`);
510
534
  }
535
+ for (const [key, propDef] of Object.entries(def.propsPatterns)) {
536
+ const optional = typeof propDef === "object" && propDef.optional;
537
+ this.writeln(`.propPattern(`);
538
+ this.indent();
539
+ this.writeln(`${key},`);
540
+ this.renderPrimitiveDef(propDef);
541
+ if (optional) this.writeln(".optional()");
542
+ this.writeln(".$type");
543
+ this.unindent();
544
+ this.write(`)`);
545
+ }
511
546
  this.unindent();
512
547
  return;
513
548
  default: return this.writeln(`$(${d()}).designType("any")`);
@@ -516,10 +551,17 @@ var JsRenderer = class extends BaseRenderer {
516
551
  defineObject(node) {
517
552
  const props = Array.from(node.props.values());
518
553
  for (const prop of props) {
554
+ const pattern = prop.token("identifier")?.pattern;
519
555
  const optional = !!prop.token("optional");
520
- this.writeln(`.prop(`);
521
- this.indent();
522
- this.writeln(`"${escapeQuotes(prop.id)}",`);
556
+ if (pattern) {
557
+ this.writeln(`.propPattern(`);
558
+ this.indent();
559
+ this.writeln(`/${pattern.source}/${pattern.flags},`);
560
+ } else {
561
+ this.writeln(`.prop(`);
562
+ this.indent();
563
+ this.writeln(`"${escapeQuotes(prop.id)}",`);
564
+ }
523
565
  this.annotateType(prop.getDefinition());
524
566
  this.indent().defineMetadata(prop).unindent();
525
567
  if (optional) this.writeln(" .optional()");
package/dist/index.cjs CHANGED
@@ -269,17 +269,41 @@ var TypeRenderer = class extends BaseRenderer {
269
269
  }
270
270
  renderStructure(struct, asClass) {
271
271
  this.blockln("{}");
272
+ const patterns = [];
273
+ let hasProp = false;
272
274
  for (const prop of Array.from(struct.props.values())) {
275
+ if (prop.token("identifier")?.pattern) {
276
+ patterns.push(prop);
277
+ continue;
278
+ }
279
+ hasProp = true;
273
280
  const optional = !!prop.token("optional");
274
281
  this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
275
282
  this.renderTypeDef(prop.getDefinition());
276
283
  this.writeln();
277
284
  }
285
+ if (patterns.length) {
286
+ this.write(`[key: string]: `);
287
+ if (hasProp) this.writeln("any");
288
+ else if (patterns.length === 1) {
289
+ this.renderTypeDef(patterns[0].getDefinition());
290
+ this.writeln();
291
+ } else {
292
+ this.indent();
293
+ for (const prop of patterns) {
294
+ this.writeln();
295
+ this.write("| ");
296
+ this.renderTypeDef(prop.getDefinition());
297
+ }
298
+ this.unindent();
299
+ this.writeln();
300
+ }
301
+ }
278
302
  if (asClass) {
279
303
  this.writeln("static __is_atscript_annotated_type: true");
280
304
  this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
281
305
  this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
282
- this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: TValidatorOptions) => Validator<TT>`);
306
+ this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
283
307
  }
284
308
  this.pop();
285
309
  }
@@ -321,7 +345,7 @@ else if ((0, __atscript_core.isPrimitive)(realDef)) typeDef = "TAtscriptTypeFina
321
345
  this.writeln(`const __is_atscript_annotated_type: true`);
322
346
  this.writeln(`const type: ${typeDef}`);
323
347
  this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
324
- this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: TValidatorOptions) => Validator<TT>`);
348
+ this.writeln(`const validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
325
349
  this.popln();
326
350
  }
327
351
  renderJsDoc(node) {
@@ -506,6 +530,17 @@ var JsRenderer = class extends BaseRenderer {
506
530
  this.unindent();
507
531
  this.write(`)`);
508
532
  }
533
+ for (const [key, propDef] of Object.entries(def.propsPatterns)) {
534
+ const optional = typeof propDef === "object" && propDef.optional;
535
+ this.writeln(`.propPattern(`);
536
+ this.indent();
537
+ this.writeln(`${key},`);
538
+ this.renderPrimitiveDef(propDef);
539
+ if (optional) this.writeln(".optional()");
540
+ this.writeln(".$type");
541
+ this.unindent();
542
+ this.write(`)`);
543
+ }
509
544
  this.unindent();
510
545
  return;
511
546
  default: return this.writeln(`$(${d()}).designType("any")`);
@@ -514,10 +549,17 @@ var JsRenderer = class extends BaseRenderer {
514
549
  defineObject(node) {
515
550
  const props = Array.from(node.props.values());
516
551
  for (const prop of props) {
552
+ const pattern = prop.token("identifier")?.pattern;
517
553
  const optional = !!prop.token("optional");
518
- this.writeln(`.prop(`);
519
- this.indent();
520
- this.writeln(`"${escapeQuotes(prop.id)}",`);
554
+ if (pattern) {
555
+ this.writeln(`.propPattern(`);
556
+ this.indent();
557
+ this.writeln(`/${pattern.source}/${pattern.flags},`);
558
+ } else {
559
+ this.writeln(`.prop(`);
560
+ this.indent();
561
+ this.writeln(`"${escapeQuotes(prop.id)}",`);
562
+ }
521
563
  this.annotateType(prop.getDefinition());
522
564
  this.indent().defineMetadata(prop).unindent();
523
565
  if (optional) this.writeln(" .optional()");
@@ -653,7 +695,7 @@ var Validator = class {
653
695
  error(message, path$2, details) {
654
696
  const errors = this.stackErrors[this.stackErrors.length - 1] || this.errors;
655
697
  const error = {
656
- path: path$2 || this.stackPath.join(".").slice(1),
698
+ path: path$2 || this.path,
657
699
  message
658
700
  };
659
701
  if (details?.length) error.details = details;
@@ -666,7 +708,7 @@ var Validator = class {
666
708
  this.push("");
667
709
  this.errors = [];
668
710
  this.stackErrors = [];
669
- const passed = this._validate(this.def, value);
711
+ const passed = this.validateSafe(this.def, value);
670
712
  this.pop(!passed);
671
713
  if (!passed) {
672
714
  if (safe) return false;
@@ -674,10 +716,21 @@ var Validator = class {
674
716
  }
675
717
  return true;
676
718
  }
677
- _validate(def, value) {
719
+ validateSafe(def, value) {
678
720
  if (this.isLimitExceeded()) return false;
679
721
  if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
722
+ if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.path);
680
723
  if (def.optional && value === undefined) return true;
724
+ for (const plugin of this.opts.plugins) {
725
+ const result = plugin(this, def, value);
726
+ if (result === false || result === true) return result;
727
+ }
728
+ return this.validateAnnotatedType(def, value);
729
+ }
730
+ get path() {
731
+ return this.stackPath.slice(1).join(".");
732
+ }
733
+ validateAnnotatedType(def, value) {
681
734
  switch (def.type.kind) {
682
735
  case "object": return this.validateObject(def, value);
683
736
  case "union": return this.validateUnion(def, value);
@@ -693,7 +746,7 @@ var Validator = class {
693
746
  const popped = [];
694
747
  for (const item of def.type.items) {
695
748
  this.push(`[${item.type.kind || item.type.designType}(${i})]`);
696
- if (this._validate(item, value)) {
749
+ if (this.validateSafe(item, value)) {
697
750
  this.pop(false);
698
751
  return true;
699
752
  }
@@ -707,7 +760,7 @@ var Validator = class {
707
760
  return false;
708
761
  }
709
762
  validateIntersection(def, value) {
710
- for (const item of def.type.items) if (!this._validate(item, value)) return false;
763
+ for (const item of def.type.items) if (!this.validateSafe(item, value)) return false;
711
764
  return true;
712
765
  }
713
766
  validateTuple(def, value) {
@@ -718,7 +771,7 @@ var Validator = class {
718
771
  let i = 0;
719
772
  for (const item of def.type.items) {
720
773
  this.push(`[${i}]`);
721
- if (!this._validate(item, value[i])) {
774
+ if (!this.validateSafe(item, value[i])) {
722
775
  this.pop(true);
723
776
  return false;
724
777
  }
@@ -746,7 +799,7 @@ var Validator = class {
746
799
  let passed = true;
747
800
  for (const item of value) {
748
801
  this.push(`[${i}]`);
749
- if (!this._validate(def.type.of, item)) {
802
+ if (!this.validateSafe(def.type.of, item)) {
750
803
  passed = false;
751
804
  this.pop(true);
752
805
  if (this.isLimitExceeded()) return false;
@@ -765,7 +818,7 @@ var Validator = class {
765
818
  const typeKeys = new Set();
766
819
  const skipList = new Set();
767
820
  if (this.opts.skipList) {
768
- const path$2 = this.stackPath.length > 1 ? this.stackPath.slice(1).join(".") + "." : "";
821
+ const path$2 = this.stackPath.length > 1 ? this.path + "." : "";
769
822
  this.opts.skipList.forEach((item) => {
770
823
  if (item.startsWith(path$2)) {
771
824
  const key = item.slice(path$2.length);
@@ -775,7 +828,7 @@ var Validator = class {
775
828
  });
776
829
  }
777
830
  let partialFunctionMatched = false;
778
- if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.stackPath.join(".").slice(1));
831
+ if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
779
832
  for (const [key, item] of def.type.props.entries()) {
780
833
  if (skipList.has(key)) continue;
781
834
  typeKeys.add(key);
@@ -783,15 +836,35 @@ var Validator = class {
783
836
  if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
784
837
  }
785
838
  this.push(key);
786
- if (this._validate(item, value[key])) this.pop(false);
839
+ if (this.validateSafe(item, value[key])) this.pop(false);
787
840
  else {
788
841
  passed = false;
789
842
  this.pop(true);
790
843
  if (this.isLimitExceeded()) return false;
791
844
  }
792
845
  }
793
- for (const key of valueKeys) if (this.opts.unknwonProps !== "ignore") {
794
- if (!typeKeys.has(key)) {
846
+ for (const key of valueKeys)
847
+ /** matched patterns for unknown keys */ if (!typeKeys.has(key)) {
848
+ const matched = [];
849
+ for (const { pattern, def: propDef } of def.type.propsPatterns) if (pattern.test(key)) matched.push({
850
+ pattern,
851
+ def: propDef
852
+ });
853
+ if (matched.length) {
854
+ let keyPassed = false;
855
+ for (const { def: def$1 } of matched) if (this.validateSafe(def$1, value[key])) {
856
+ this.pop(false);
857
+ keyPassed = true;
858
+ break;
859
+ }
860
+ if (!keyPassed) {
861
+ this.push(key);
862
+ this.validateSafe(matched[0].def, value[key]);
863
+ this.pop(true);
864
+ passed = false;
865
+ if (this.isLimitExceeded()) return false;
866
+ }
867
+ } else if (this.opts.unknwonProps !== "ignore") {
795
868
  if (this.opts.unknwonProps === "error") {
796
869
  this.push(key);
797
870
  this.error(`Unexpected property`);
@@ -909,7 +982,8 @@ else {
909
982
  partial: false,
910
983
  unknwonProps: "error",
911
984
  errorLimit: 10,
912
- ...opts
985
+ ...opts,
986
+ plugins: opts?.plugins || []
913
987
  };
914
988
  }
915
989
  };
@@ -933,7 +1007,10 @@ function defineAnnotatedType(_kind, base) {
933
1007
  "intersection",
934
1008
  "tuple"
935
1009
  ].includes(kind)) type.items = [];
936
- if (kind === "object") type.props = new Map();
1010
+ if (kind === "object") {
1011
+ type.props = new Map();
1012
+ type.propsPatterns = [];
1013
+ }
937
1014
  type.tags = new Set();
938
1015
  const metadata = base?.metadata || new Map();
939
1016
  if (base) Object.assign(base, {
@@ -981,8 +1058,19 @@ else base = {
981
1058
  this.$def.props.set(name, value);
982
1059
  return this;
983
1060
  },
984
- optional() {
985
- this.$type.optional = true;
1061
+ propPattern(pattern, def) {
1062
+ this.$def.propsPatterns.push({
1063
+ pattern,
1064
+ def
1065
+ });
1066
+ return this;
1067
+ },
1068
+ optional(value = true) {
1069
+ this.$type.optional = value;
1070
+ return this;
1071
+ },
1072
+ copyMetadata(fromMetadata, ignore) {
1073
+ for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
986
1074
  return this;
987
1075
  },
988
1076
  refTo(type$1, chain) {
@@ -992,7 +1080,7 @@ else base = {
992
1080
  let keys = "";
993
1081
  for (const c of chain || []) {
994
1082
  keys += `["${c}"]`;
995
- if (newBase.type.kind === "object") newBase = newBase.type.props.get(c);
1083
+ if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
996
1084
  else throw new Error(`Can't find prop ${typeName}${keys}`);
997
1085
  }
998
1086
  if (!newBase && keys) throw new Error(`Can't find prop ${typeName}${keys}`);
@@ -1020,6 +1108,19 @@ else this.$metadata.set(key, value);
1020
1108
  };
1021
1109
  return handle;
1022
1110
  }
1111
+ function isAnnotatedTypeOfPrimitive(t) {
1112
+ if (["array", "object"].includes(t.type.kind)) return false;
1113
+ if (!t.type.kind) return true;
1114
+ if ([
1115
+ "union",
1116
+ "tuple",
1117
+ "intersection"
1118
+ ].includes(t.type.kind)) {
1119
+ for (const item of t.type.items) if (!isAnnotatedTypeOfPrimitive(item)) return false;
1120
+ return true;
1121
+ }
1122
+ return false;
1123
+ }
1023
1124
 
1024
1125
  //#endregion
1025
1126
  //#region packages/typescript/src/index.ts
@@ -1030,4 +1131,5 @@ exports.Validator = Validator
1030
1131
  exports.ValidatorError = ValidatorError
1031
1132
  exports.default = src_default
1032
1133
  exports.defineAnnotatedType = defineAnnotatedType
1033
- exports.isAnnotatedType = isAnnotatedType
1134
+ exports.isAnnotatedType = isAnnotatedType
1135
+ exports.isAnnotatedTypeOfPrimitive = isAnnotatedTypeOfPrimitive
package/dist/index.d.ts CHANGED
@@ -7,16 +7,25 @@ interface TError {
7
7
  message: string;
8
8
  details?: TError[];
9
9
  }
10
+ type TValidatorPlugin = (ctx: TValidatorPluginContext, def: TAtscriptAnnotatedType, value: any) => boolean | undefined;
10
11
  interface TValidatorOptions {
11
12
  partial: boolean | 'deep' | ((type: TAtscriptAnnotatedType<TAtscriptTypeObject>, path: string) => boolean);
13
+ replace?: (type: TAtscriptAnnotatedType, path: string) => TAtscriptAnnotatedType;
14
+ plugins: TValidatorPlugin[];
12
15
  unknwonProps: 'strip' | 'ignore' | 'error';
13
16
  errorLimit: number;
14
17
  skipList?: Set<string>;
15
18
  }
19
+ interface TValidatorPluginContext {
20
+ opts: Validator<any>['opts'];
21
+ validateAnnotatedType: Validator<any>['validateAnnotatedType'];
22
+ error: Validator<any>['error'];
23
+ path: Validator<any>['path'];
24
+ }
16
25
  declare class Validator<T extends TAtscriptAnnotatedTypeConstructor> {
17
- protected readonly def: T;
26
+ protected readonly def: T | TAtscriptAnnotatedType<any>;
18
27
  protected opts: TValidatorOptions;
19
- constructor(def: T, opts?: Partial<TValidatorOptions>);
28
+ constructor(def: T | TAtscriptAnnotatedType<any>, opts?: Partial<TValidatorOptions>);
20
29
  errors: TError[];
21
30
  protected stackErrors: TError[][];
22
31
  protected stackPath: string[];
@@ -27,7 +36,9 @@ declare class Validator<T extends TAtscriptAnnotatedTypeConstructor> {
27
36
  protected error(message: string, path?: string, details?: TError[]): void;
28
37
  protected throw(): void;
29
38
  validate<TT = T>(value: any, safe?: boolean): value is TT;
30
- protected _validate(def: TAtscriptAnnotatedType, value: any): boolean;
39
+ protected validateSafe(def: TAtscriptAnnotatedType, value: any): boolean;
40
+ protected get path(): string;
41
+ protected validateAnnotatedType(def: TAtscriptAnnotatedType, value: any): boolean;
31
42
  protected validateUnion(def: TAtscriptAnnotatedType<TAtscriptTypeComplex>, value: any): boolean;
32
43
  protected validateIntersection(def: TAtscriptAnnotatedType<TAtscriptTypeComplex>, value: any): boolean;
33
44
  protected validateTuple(def: TAtscriptAnnotatedType<TAtscriptTypeComplex>, value: any): boolean;
@@ -56,11 +67,21 @@ interface TAtscriptTypeArray {
56
67
  interface TAtscriptTypeObject<K extends string = string> {
57
68
  kind: 'object';
58
69
  props: Map<K, TAtscriptAnnotatedType>;
70
+ propsPatterns: {
71
+ pattern: RegExp;
72
+ def: TAtscriptAnnotatedType;
73
+ }[];
59
74
  tags: Set<AtscriptPrimitiveTags>;
60
75
  }
61
76
  interface TAtscriptTypeFinal {
62
77
  kind: '';
78
+ /**
79
+ * design type
80
+ */
63
81
  designType: 'string' | 'number' | 'boolean' | 'undefined' | 'null' | 'object' | 'any' | 'never';
82
+ /**
83
+ * value for literals
84
+ */
64
85
  value?: string | number | boolean;
65
86
  tags: Set<AtscriptPrimitiveTags>;
66
87
  }
@@ -73,28 +94,40 @@ interface TAtscriptAnnotatedType<T = TAtscriptTypeDef> {
73
94
  optional?: boolean;
74
95
  }
75
96
  type TAtscriptAnnotatedTypeConstructor = TAtscriptAnnotatedType & (new (...args: any[]) => any);
97
+ /**
98
+ * Type Guard to check if a type is atscript-annotated
99
+ */
76
100
  declare function isAnnotatedType(type: any): type is TAtscriptAnnotatedType;
77
101
  type TKind = '' | 'array' | 'object' | 'union' | 'intersection' | 'tuple';
78
- declare function defineAnnotatedType(_kind?: TKind, base?: any): {
79
- $type: any;
80
- $def: {
81
- kind: TKind;
82
- } & Omit<TAtscriptTypeComplex, "kind"> & Omit<TAtscriptTypeFinal, "kind"> & Omit<TAtscriptTypeArray, "kind"> & Omit<TAtscriptTypeObject<string>, "kind">;
83
- $metadata: Map<string, unknown>;
84
- _existingObject: TAtscriptAnnotatedType | undefined;
85
- tags(...tags: string[]): any;
86
- designType(value: TAtscriptTypeFinal["designType"]): any;
87
- value(value: string | number | boolean): any;
88
- of(value: TAtscriptAnnotatedType): any;
89
- item(value: TAtscriptAnnotatedType): any;
90
- prop(name: string, value: TAtscriptAnnotatedType): any;
91
- optional(): any;
92
- refTo(type: any, chain?: string[]): any;
93
- annotate(key: string, value: any, asArray?: boolean): any;
94
- };
102
+ declare function defineAnnotatedType(_kind?: TKind, base?: any): TAnnotatedTypeHandle;
103
+ /**
104
+ * Atscript Metadata Map with typed setters/getters
105
+ */
95
106
  interface TMetadataMap<O extends object> extends Map<keyof O, O[keyof O]> {
96
107
  get<K extends keyof O>(key: K): O[K] | undefined;
97
108
  set<K extends keyof O>(key: K, value: O[K]): this;
98
109
  }
110
+ interface TAnnotatedTypeHandle {
111
+ $type: TAtscriptAnnotatedType;
112
+ $def: {
113
+ kind: TKind;
114
+ } & Omit<TAtscriptTypeComplex, 'kind'> & Omit<TAtscriptTypeFinal, 'kind'> & Omit<TAtscriptTypeArray, 'kind'> & Omit<TAtscriptTypeObject<string>, 'kind'>;
115
+ $metadata: TMetadataMap<AtscriptMetadata>;
116
+ _existingObject: TAtscriptAnnotatedType | undefined;
117
+ tags(...tags: string[]): TAnnotatedTypeHandle;
118
+ designType(value: TAtscriptTypeFinal['designType']): TAnnotatedTypeHandle;
119
+ value(value: string | number | boolean): TAnnotatedTypeHandle;
120
+ of(value: TAtscriptAnnotatedType): TAnnotatedTypeHandle;
121
+ item(value: TAtscriptAnnotatedType): TAnnotatedTypeHandle;
122
+ prop(name: string, value: TAtscriptAnnotatedType): TAnnotatedTypeHandle;
123
+ propPattern(pattern: RegExp, value: TAtscriptAnnotatedType): TAnnotatedTypeHandle;
124
+ optional(value?: boolean): TAnnotatedTypeHandle;
125
+ copyMetadata(fromMetadata: TMetadataMap<AtscriptMetadata>): TAnnotatedTypeHandle;
126
+ refTo(type: TAtscriptAnnotatedType & {
127
+ name?: string;
128
+ }, chain?: string[]): TAnnotatedTypeHandle;
129
+ annotate(key: keyof AtscriptMetadata, value: any, asArray?: boolean): TAnnotatedTypeHandle;
130
+ }
131
+ declare function isAnnotatedTypeOfPrimitive(t: TAtscriptAnnotatedType): boolean;
99
132
 
100
- export { type TAtscriptAnnotatedType, type TAtscriptAnnotatedTypeConstructor, type TAtscriptTypeArray, type TAtscriptTypeComplex, type TAtscriptTypeDef, type TAtscriptTypeFinal, type TAtscriptTypeObject, type TMetadataMap, type TValidatorOptions, Validator, ValidatorError, tsPlugin as default, defineAnnotatedType, isAnnotatedType };
133
+ export { type TAnnotatedTypeHandle, type TAtscriptAnnotatedType, type TAtscriptAnnotatedTypeConstructor, type TAtscriptTypeArray, type TAtscriptTypeComplex, type TAtscriptTypeDef, type TAtscriptTypeFinal, type TAtscriptTypeObject, type TMetadataMap, type TValidatorOptions, type TValidatorPlugin, type TValidatorPluginContext, Validator, ValidatorError, tsPlugin as default, defineAnnotatedType, isAnnotatedType, isAnnotatedTypeOfPrimitive };
package/dist/index.mjs CHANGED
@@ -244,17 +244,41 @@ var TypeRenderer = class extends BaseRenderer {
244
244
  }
245
245
  renderStructure(struct, asClass) {
246
246
  this.blockln("{}");
247
+ const patterns = [];
248
+ let hasProp = false;
247
249
  for (const prop of Array.from(struct.props.values())) {
250
+ if (prop.token("identifier")?.pattern) {
251
+ patterns.push(prop);
252
+ continue;
253
+ }
254
+ hasProp = true;
248
255
  const optional = !!prop.token("optional");
249
256
  this.write(wrapProp(prop.id), optional ? "?" : "", ": ");
250
257
  this.renderTypeDef(prop.getDefinition());
251
258
  this.writeln();
252
259
  }
260
+ if (patterns.length) {
261
+ this.write(`[key: string]: `);
262
+ if (hasProp) this.writeln("any");
263
+ else if (patterns.length === 1) {
264
+ this.renderTypeDef(patterns[0].getDefinition());
265
+ this.writeln();
266
+ } else {
267
+ this.indent();
268
+ for (const prop of patterns) {
269
+ this.writeln();
270
+ this.write("| ");
271
+ this.renderTypeDef(prop.getDefinition());
272
+ }
273
+ this.unindent();
274
+ this.writeln();
275
+ }
276
+ }
253
277
  if (asClass) {
254
278
  this.writeln("static __is_atscript_annotated_type: true");
255
279
  this.writeln(`static type: TAtscriptTypeObject<keyof ${asClass}>`);
256
280
  this.writeln(`static metadata: TMetadataMap<AtscriptMetadata>`);
257
- this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: TValidatorOptions) => Validator<TT>`);
281
+ this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${asClass}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
258
282
  }
259
283
  this.pop();
260
284
  }
@@ -296,7 +320,7 @@ else if (isPrimitive(realDef)) typeDef = "TAtscriptTypeFinal";
296
320
  this.writeln(`const __is_atscript_annotated_type: true`);
297
321
  this.writeln(`const type: ${typeDef}`);
298
322
  this.writeln(`const metadata: TMetadataMap<AtscriptMetadata>`);
299
- this.writeln(`static validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: TValidatorOptions) => Validator<TT>`);
323
+ this.writeln(`const validator: <TT extends TAtscriptAnnotatedTypeConstructor = ${node.id}>(opts?: Partial<TValidatorOptions>) => Validator<TT>`);
300
324
  this.popln();
301
325
  }
302
326
  renderJsDoc(node) {
@@ -481,6 +505,17 @@ var JsRenderer = class extends BaseRenderer {
481
505
  this.unindent();
482
506
  this.write(`)`);
483
507
  }
508
+ for (const [key, propDef] of Object.entries(def.propsPatterns)) {
509
+ const optional = typeof propDef === "object" && propDef.optional;
510
+ this.writeln(`.propPattern(`);
511
+ this.indent();
512
+ this.writeln(`${key},`);
513
+ this.renderPrimitiveDef(propDef);
514
+ if (optional) this.writeln(".optional()");
515
+ this.writeln(".$type");
516
+ this.unindent();
517
+ this.write(`)`);
518
+ }
484
519
  this.unindent();
485
520
  return;
486
521
  default: return this.writeln(`$(${d()}).designType("any")`);
@@ -489,10 +524,17 @@ var JsRenderer = class extends BaseRenderer {
489
524
  defineObject(node) {
490
525
  const props = Array.from(node.props.values());
491
526
  for (const prop of props) {
527
+ const pattern = prop.token("identifier")?.pattern;
492
528
  const optional = !!prop.token("optional");
493
- this.writeln(`.prop(`);
494
- this.indent();
495
- this.writeln(`"${escapeQuotes(prop.id)}",`);
529
+ if (pattern) {
530
+ this.writeln(`.propPattern(`);
531
+ this.indent();
532
+ this.writeln(`/${pattern.source}/${pattern.flags},`);
533
+ } else {
534
+ this.writeln(`.prop(`);
535
+ this.indent();
536
+ this.writeln(`"${escapeQuotes(prop.id)}",`);
537
+ }
496
538
  this.annotateType(prop.getDefinition());
497
539
  this.indent().defineMetadata(prop).unindent();
498
540
  if (optional) this.writeln(" .optional()");
@@ -628,7 +670,7 @@ var Validator = class {
628
670
  error(message, path$1, details) {
629
671
  const errors = this.stackErrors[this.stackErrors.length - 1] || this.errors;
630
672
  const error = {
631
- path: path$1 || this.stackPath.join(".").slice(1),
673
+ path: path$1 || this.path,
632
674
  message
633
675
  };
634
676
  if (details?.length) error.details = details;
@@ -641,7 +683,7 @@ var Validator = class {
641
683
  this.push("");
642
684
  this.errors = [];
643
685
  this.stackErrors = [];
644
- const passed = this._validate(this.def, value);
686
+ const passed = this.validateSafe(this.def, value);
645
687
  this.pop(!passed);
646
688
  if (!passed) {
647
689
  if (safe) return false;
@@ -649,10 +691,21 @@ var Validator = class {
649
691
  }
650
692
  return true;
651
693
  }
652
- _validate(def, value) {
694
+ validateSafe(def, value) {
653
695
  if (this.isLimitExceeded()) return false;
654
696
  if (!isAnnotatedType(def)) throw new Error("Can not validate not-annotated type");
697
+ if (typeof this.opts.replace === "function") def = this.opts.replace(def, this.path);
655
698
  if (def.optional && value === undefined) return true;
699
+ for (const plugin of this.opts.plugins) {
700
+ const result = plugin(this, def, value);
701
+ if (result === false || result === true) return result;
702
+ }
703
+ return this.validateAnnotatedType(def, value);
704
+ }
705
+ get path() {
706
+ return this.stackPath.slice(1).join(".");
707
+ }
708
+ validateAnnotatedType(def, value) {
656
709
  switch (def.type.kind) {
657
710
  case "object": return this.validateObject(def, value);
658
711
  case "union": return this.validateUnion(def, value);
@@ -668,7 +721,7 @@ var Validator = class {
668
721
  const popped = [];
669
722
  for (const item of def.type.items) {
670
723
  this.push(`[${item.type.kind || item.type.designType}(${i})]`);
671
- if (this._validate(item, value)) {
724
+ if (this.validateSafe(item, value)) {
672
725
  this.pop(false);
673
726
  return true;
674
727
  }
@@ -682,7 +735,7 @@ var Validator = class {
682
735
  return false;
683
736
  }
684
737
  validateIntersection(def, value) {
685
- for (const item of def.type.items) if (!this._validate(item, value)) return false;
738
+ for (const item of def.type.items) if (!this.validateSafe(item, value)) return false;
686
739
  return true;
687
740
  }
688
741
  validateTuple(def, value) {
@@ -693,7 +746,7 @@ var Validator = class {
693
746
  let i = 0;
694
747
  for (const item of def.type.items) {
695
748
  this.push(`[${i}]`);
696
- if (!this._validate(item, value[i])) {
749
+ if (!this.validateSafe(item, value[i])) {
697
750
  this.pop(true);
698
751
  return false;
699
752
  }
@@ -721,7 +774,7 @@ var Validator = class {
721
774
  let passed = true;
722
775
  for (const item of value) {
723
776
  this.push(`[${i}]`);
724
- if (!this._validate(def.type.of, item)) {
777
+ if (!this.validateSafe(def.type.of, item)) {
725
778
  passed = false;
726
779
  this.pop(true);
727
780
  if (this.isLimitExceeded()) return false;
@@ -740,7 +793,7 @@ var Validator = class {
740
793
  const typeKeys = new Set();
741
794
  const skipList = new Set();
742
795
  if (this.opts.skipList) {
743
- const path$1 = this.stackPath.length > 1 ? this.stackPath.slice(1).join(".") + "." : "";
796
+ const path$1 = this.stackPath.length > 1 ? this.path + "." : "";
744
797
  this.opts.skipList.forEach((item) => {
745
798
  if (item.startsWith(path$1)) {
746
799
  const key = item.slice(path$1.length);
@@ -750,7 +803,7 @@ var Validator = class {
750
803
  });
751
804
  }
752
805
  let partialFunctionMatched = false;
753
- if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.stackPath.join(".").slice(1));
806
+ if (typeof this.opts.partial === "function") partialFunctionMatched = this.opts.partial(def, this.path);
754
807
  for (const [key, item] of def.type.props.entries()) {
755
808
  if (skipList.has(key)) continue;
756
809
  typeKeys.add(key);
@@ -758,15 +811,35 @@ var Validator = class {
758
811
  if (partialFunctionMatched || this.opts.partial === "deep" || this.opts.partial === true && this.stackPath.length <= 1) continue;
759
812
  }
760
813
  this.push(key);
761
- if (this._validate(item, value[key])) this.pop(false);
814
+ if (this.validateSafe(item, value[key])) this.pop(false);
762
815
  else {
763
816
  passed = false;
764
817
  this.pop(true);
765
818
  if (this.isLimitExceeded()) return false;
766
819
  }
767
820
  }
768
- for (const key of valueKeys) if (this.opts.unknwonProps !== "ignore") {
769
- if (!typeKeys.has(key)) {
821
+ for (const key of valueKeys)
822
+ /** matched patterns for unknown keys */ if (!typeKeys.has(key)) {
823
+ const matched = [];
824
+ for (const { pattern, def: propDef } of def.type.propsPatterns) if (pattern.test(key)) matched.push({
825
+ pattern,
826
+ def: propDef
827
+ });
828
+ if (matched.length) {
829
+ let keyPassed = false;
830
+ for (const { def: def$1 } of matched) if (this.validateSafe(def$1, value[key])) {
831
+ this.pop(false);
832
+ keyPassed = true;
833
+ break;
834
+ }
835
+ if (!keyPassed) {
836
+ this.push(key);
837
+ this.validateSafe(matched[0].def, value[key]);
838
+ this.pop(true);
839
+ passed = false;
840
+ if (this.isLimitExceeded()) return false;
841
+ }
842
+ } else if (this.opts.unknwonProps !== "ignore") {
770
843
  if (this.opts.unknwonProps === "error") {
771
844
  this.push(key);
772
845
  this.error(`Unexpected property`);
@@ -884,7 +957,8 @@ else {
884
957
  partial: false,
885
958
  unknwonProps: "error",
886
959
  errorLimit: 10,
887
- ...opts
960
+ ...opts,
961
+ plugins: opts?.plugins || []
888
962
  };
889
963
  }
890
964
  };
@@ -908,7 +982,10 @@ function defineAnnotatedType(_kind, base) {
908
982
  "intersection",
909
983
  "tuple"
910
984
  ].includes(kind)) type.items = [];
911
- if (kind === "object") type.props = new Map();
985
+ if (kind === "object") {
986
+ type.props = new Map();
987
+ type.propsPatterns = [];
988
+ }
912
989
  type.tags = new Set();
913
990
  const metadata = base?.metadata || new Map();
914
991
  if (base) Object.assign(base, {
@@ -956,8 +1033,19 @@ else base = {
956
1033
  this.$def.props.set(name, value);
957
1034
  return this;
958
1035
  },
959
- optional() {
960
- this.$type.optional = true;
1036
+ propPattern(pattern, def) {
1037
+ this.$def.propsPatterns.push({
1038
+ pattern,
1039
+ def
1040
+ });
1041
+ return this;
1042
+ },
1043
+ optional(value = true) {
1044
+ this.$type.optional = value;
1045
+ return this;
1046
+ },
1047
+ copyMetadata(fromMetadata, ignore) {
1048
+ for (const [key, value] of fromMetadata.entries()) if (!ignore || !ignore.has(key)) this.$metadata.set(key, value);
961
1049
  return this;
962
1050
  },
963
1051
  refTo(type$1, chain) {
@@ -967,7 +1055,7 @@ else base = {
967
1055
  let keys = "";
968
1056
  for (const c of chain || []) {
969
1057
  keys += `["${c}"]`;
970
- if (newBase.type.kind === "object") newBase = newBase.type.props.get(c);
1058
+ if (newBase.type.kind === "object" && newBase.type.props.has(c)) newBase = newBase.type.props.get(c);
971
1059
  else throw new Error(`Can't find prop ${typeName}${keys}`);
972
1060
  }
973
1061
  if (!newBase && keys) throw new Error(`Can't find prop ${typeName}${keys}`);
@@ -995,10 +1083,23 @@ else this.$metadata.set(key, value);
995
1083
  };
996
1084
  return handle;
997
1085
  }
1086
+ function isAnnotatedTypeOfPrimitive(t) {
1087
+ if (["array", "object"].includes(t.type.kind)) return false;
1088
+ if (!t.type.kind) return true;
1089
+ if ([
1090
+ "union",
1091
+ "tuple",
1092
+ "intersection"
1093
+ ].includes(t.type.kind)) {
1094
+ for (const item of t.type.items) if (!isAnnotatedTypeOfPrimitive(item)) return false;
1095
+ return true;
1096
+ }
1097
+ return false;
1098
+ }
998
1099
 
999
1100
  //#endregion
1000
1101
  //#region packages/typescript/src/index.ts
1001
1102
  var src_default = tsPlugin;
1002
1103
 
1003
1104
  //#endregion
1004
- export { Validator, ValidatorError, src_default as default, defineAnnotatedType, isAnnotatedType };
1105
+ export { Validator, ValidatorError, src_default as default, defineAnnotatedType, isAnnotatedType, isAnnotatedTypeOfPrimitive };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/typescript",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "Atscript: typescript-gen support.",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -46,13 +46,15 @@
46
46
  },
47
47
  "homepage": "https://github.com/moostjs/atscript/tree/main/packages/typescript#readme",
48
48
  "license": "ISC",
49
+ "peerDependencies": {
50
+ "@atscript/core": "^0.0.18"
51
+ },
49
52
  "dependencies": {
50
- "@moostjs/event-cli": "^0.5.26",
51
- "moost": "^0.5.26",
52
- "@atscript/core": "^0.0.17"
53
+ "@moostjs/event-cli": "^0.5.30",
54
+ "moost": "^0.5.30"
53
55
  },
54
56
  "devDependencies": {
55
- "vitest": "^3.0.0"
57
+ "vitest": "3.2.4"
56
58
  },
57
59
  "scripts": {
58
60
  "pub": "pnpm publish --access public",