valdex 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -120,6 +120,7 @@ validate(data, {
120
120
  | `Boolean` | `boolean` |
121
121
  | `Array` | `any[]` |
122
122
  | `Object` | `object` |
123
+ | `Date` | `Date` |
123
124
 
124
125
  ## Error Handling
125
126
 
package/dist/index.d.mts CHANGED
@@ -8,8 +8,8 @@ type ArraySchema = [PrimitiveType | Expression];
8
8
  type Expression = {
9
9
  [key: string | number]: PrimitiveType | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | Expression | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | ArraySchema | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker);
10
10
  };
11
- type PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor;
12
- type InferPrimitiveType<T> = T extends NumberConstructor ? number : T extends StringConstructor ? string : T extends BooleanConstructor ? boolean : T extends ArrayConstructor ? any[] : T extends ObjectConstructor ? object : never;
11
+ type PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor;
12
+ type InferPrimitiveType<T> = T extends NumberConstructor ? number : T extends StringConstructor ? string : T extends BooleanConstructor ? boolean : T extends ArrayConstructor ? any[] : T extends ObjectConstructor ? object : T extends DateConstructor ? Date : never;
13
13
  type InferType<T> = T extends PrimitiveType & OptionalMarker & NullableMarker ? InferPrimitiveType<T> | undefined | null : T extends PrimitiveType & OptionalMarker ? InferPrimitiveType<T> | undefined : T extends PrimitiveType & NullableMarker ? InferPrimitiveType<T> | null : T extends PrimitiveType ? InferPrimitiveType<T> : T extends Expression & OptionalMarker & NullableMarker ? {
14
14
  [K in keyof T]: InferType<T[K]>;
15
15
  } | undefined | null : T extends Expression & OptionalMarker ? {
package/dist/index.d.ts CHANGED
@@ -8,8 +8,8 @@ type ArraySchema = [PrimitiveType | Expression];
8
8
  type Expression = {
9
9
  [key: string | number]: PrimitiveType | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | Expression | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | ArraySchema | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker);
10
10
  };
11
- type PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor;
12
- type InferPrimitiveType<T> = T extends NumberConstructor ? number : T extends StringConstructor ? string : T extends BooleanConstructor ? boolean : T extends ArrayConstructor ? any[] : T extends ObjectConstructor ? object : never;
11
+ type PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor;
12
+ type InferPrimitiveType<T> = T extends NumberConstructor ? number : T extends StringConstructor ? string : T extends BooleanConstructor ? boolean : T extends ArrayConstructor ? any[] : T extends ObjectConstructor ? object : T extends DateConstructor ? Date : never;
13
13
  type InferType<T> = T extends PrimitiveType & OptionalMarker & NullableMarker ? InferPrimitiveType<T> | undefined | null : T extends PrimitiveType & OptionalMarker ? InferPrimitiveType<T> | undefined : T extends PrimitiveType & NullableMarker ? InferPrimitiveType<T> | null : T extends PrimitiveType ? InferPrimitiveType<T> : T extends Expression & OptionalMarker & NullableMarker ? {
14
14
  [K in keyof T]: InferType<T[K]>;
15
15
  } | undefined | null : T extends Expression & OptionalMarker ? {
package/dist/index.js CHANGED
@@ -55,13 +55,14 @@ function getTypeName(value) {
55
55
  const type = typeof value;
56
56
  return type.charAt(0).toUpperCase() + type.slice(1);
57
57
  }
58
- var TYPE_VALIDATORS = {
59
- Number: (value) => typeof value === "number" && !Number.isNaN(value),
60
- String: (value) => typeof value === "string",
61
- Boolean: (value) => typeof value === "boolean",
62
- Array: (value) => Array.isArray(value),
63
- Object: (value) => typeof value === "object" && value !== null && !Array.isArray(value)
64
- };
58
+ var TYPE_VALIDATORS = /* @__PURE__ */ new Map([
59
+ [Number, (value) => typeof value === "number" && !Number.isNaN(value)],
60
+ [String, (value) => typeof value === "string"],
61
+ [Boolean, (value) => typeof value === "boolean"],
62
+ [Array, (value) => Array.isArray(value)],
63
+ [Object, (value) => typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date)],
64
+ [Date, (value) => value instanceof Date && !Number.isNaN(value.getTime())]
65
+ ]);
65
66
  function isValidateExpression(ctor) {
66
67
  return typeof ctor === "object" && !Array.isArray(ctor);
67
68
  }
@@ -78,33 +79,42 @@ function isBothOptionalAndNullable(ctor) {
78
79
  return ctor.__optional === true && ctor.__nullable === true;
79
80
  }
80
81
  function unwrapOptionalNullable(ctor) {
81
- if (!ctor || typeof ctor !== "object") {
82
- return ctor;
83
- }
84
- if (Array.isArray(ctor)) {
85
- return ctor;
86
- }
87
- if (ctor.__type) {
88
- return ctor.__type;
89
- }
90
- const unwrapped = {};
91
- for (const key in ctor) {
92
- if (key !== "__optional" && key !== "__nullable" && key !== "__type") {
93
- unwrapped[key] = ctor[key];
94
- }
95
- }
96
- if (Object.keys(unwrapped).length === 0 && (ctor.__optional || ctor.__nullable)) {
97
- return ctor;
82
+ while (ctor && typeof ctor === "object" && !Array.isArray(ctor) && ctor.__type) {
83
+ ctor = ctor.__type;
98
84
  }
99
- return unwrapped;
85
+ return ctor;
100
86
  }
101
87
  function assertType(key, expectedType, value, isValid) {
102
88
  if (!isValid) {
103
89
  throw new RuntimeTypeError(key, expectedType, getTypeName(value), value);
104
90
  }
105
91
  }
92
+ function validateArrayElement(item, schema, path) {
93
+ if (isArraySchema(schema)) {
94
+ assertType(path, "Array", item, Array.isArray(item));
95
+ const innerSchema = schema[0];
96
+ const arr = item;
97
+ for (let i = 0; i < arr.length; i++) {
98
+ validateArrayElement(arr[i], innerSchema, `${path}[${i}]`);
99
+ }
100
+ } else if (isValidateExpression(schema)) {
101
+ const objectValidator = TYPE_VALIDATORS.get(Object);
102
+ assertType(path, "Object", item, objectValidator?.(item) ?? false);
103
+ validate(item, schema, path);
104
+ } else if (typeof schema === "function") {
105
+ const validator = TYPE_VALIDATORS.get(schema);
106
+ if (!validator) {
107
+ throw new Error("Invalid array element expression.");
108
+ }
109
+ assertType(path, schema.name, item, validator(item));
110
+ } else {
111
+ throw new Error("Invalid array element expression.");
112
+ }
113
+ }
106
114
  function validate(target, expression, path = "") {
107
- for (const [key, ctor] of Object.entries(expression)) {
115
+ for (const key in expression) {
116
+ if (!Object.prototype.hasOwnProperty.call(expression, key)) continue;
117
+ const ctor = expression[key];
108
118
  const value = target[key];
109
119
  const currentPath = path ? `${path}.${key}` : key;
110
120
  if (isBothOptionalAndNullable(ctor)) {
@@ -122,33 +132,20 @@ function validate(target, expression, path = "") {
122
132
  if (isArraySchema(unwrappedCtor)) {
123
133
  assertType(currentPath, "Array", value, Array.isArray(value));
124
134
  const elementSchema = unwrappedCtor[0];
125
- value.forEach((item, index) => {
126
- const arrayPath = `${currentPath}[${index}]`;
127
- if (isValidateExpression(elementSchema)) {
128
- const objectValidator = TYPE_VALIDATORS["Object"];
129
- assertType(arrayPath, "Object", item, objectValidator?.(item) ?? false);
130
- validate(item, elementSchema, arrayPath);
131
- } else {
132
- const validator = TYPE_VALIDATORS[elementSchema.name];
133
- if (!validator) {
134
- throw new Error("Invalid array element expression.");
135
- }
136
- assertType(arrayPath, elementSchema.name, item, validator(item));
137
- }
138
- });
135
+ const arr = value;
136
+ for (let i = 0; i < arr.length; i++) {
137
+ validateArrayElement(arr[i], elementSchema, `${currentPath}[${i}]`);
138
+ }
139
139
  continue;
140
140
  }
141
141
  if (typeof unwrappedCtor === "function") {
142
- const validator = TYPE_VALIDATORS[unwrappedCtor.name];
142
+ const validator = TYPE_VALIDATORS.get(unwrappedCtor);
143
143
  if (!validator) {
144
144
  throw new Error("Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.");
145
145
  }
146
146
  assertType(currentPath, unwrappedCtor.name, value, validator(value));
147
147
  } else if (isValidateExpression(unwrappedCtor)) {
148
- const objectValidator = TYPE_VALIDATORS["Object"];
149
- if (!objectValidator) {
150
- throw new Error("Object validator not found");
151
- }
148
+ const objectValidator = TYPE_VALIDATORS.get(Object);
152
149
  assertType(currentPath, "Object", value, objectValidator(value));
153
150
  validate(value, unwrappedCtor, currentPath);
154
151
  } else {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["type OptionalMarker = { __optional: true };\ntype NullableMarker = { __nullable: true };\n\ntype ArraySchema = [PrimitiveType | Expression];\n\ntype Expression = {\n [key: string | number]: PrimitiveType | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | Expression | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | ArraySchema | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker)\n}\n\ntype PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor;\n\ntype InferPrimitiveType<T> =\n T extends NumberConstructor ? number :\n T extends StringConstructor ? string :\n T extends BooleanConstructor ? boolean :\n T extends ArrayConstructor ? any[] :\n T extends ObjectConstructor ? object :\n never;\n\ntype InferType<T> =\n T extends PrimitiveType & OptionalMarker & NullableMarker\n ? InferPrimitiveType<T> | undefined | null\n : T extends PrimitiveType & OptionalMarker\n ? InferPrimitiveType<T> | undefined\n : T extends PrimitiveType & NullableMarker\n ? InferPrimitiveType<T> | null\n : T extends PrimitiveType\n ? InferPrimitiveType<T>\n : T extends Expression & OptionalMarker & NullableMarker\n ? { [K in keyof T]: InferType<T[K]> } | undefined | null\n : T extends Expression & OptionalMarker\n ? { [K in keyof T]: InferType<T[K]> } | undefined\n : T extends Expression & NullableMarker\n ? { [K in keyof T]: InferType<T[K]> } | null\n : T extends ArraySchema & OptionalMarker & NullableMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | undefined | null\n : T extends ArraySchema & OptionalMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | undefined\n : T extends ArraySchema & NullableMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | null\n : T extends [infer U]\n ? U extends Expression\n ? InferType<U>[]\n : U extends PrimitiveType\n ? InferPrimitiveType<U>[]\n : never\n : T extends Expression\n ? { [K in keyof T]: InferType<T[K]> }\n : never;\n\n/**\n * Marks a field as optional.\n * Optional fields pass validation even if they are undefined.\n * The type is inferred as T | undefined.\n * @example\n * ```ts\n * validate(data, {\n * required_field: String, // string (required)\n * optional_field: Optional(String), // string | undefined\n * optional_object: Optional({ // { id: string } | undefined\n * id: String\n * }),\n * optional_array: Optional([String]) // string[] | undefined\n * });\n * ```\n */\nexport function Optional<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & OptionalMarker {\n return {\n ...ctor as any,\n __optional: true,\n __type: ctor\n } as T & OptionalMarker;\n}\n\n/**\n * Marks a field as nullable.\n * Nullable fields pass validation even if they are null.\n * The type is inferred as T | null.\n * @example\n * ```ts\n * validate(data, {\n * required_field: String, // string (required)\n * nullable_field: Nullable(String), // string | null\n * nullable_object: Nullable({ // { id: string } | null\n * id: String\n * }),\n * nullable_array: Nullable([String]) // string[] | null\n * });\n * ```\n */\nexport function Nullable<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & NullableMarker {\n return {\n ...ctor as any,\n __nullable: true,\n __type: ctor\n } as T & NullableMarker;\n}\n\nexport class RuntimeTypeError extends Error {\n constructor(\n key: string,\n requiredType: string,\n actualType: string,\n actualValue: any,\n message?: string\n ) {\n if (message) message += \": \"\n super(`${message || \"\"}${key} must be ${requiredType}, but got ${actualType}. Actual value: ${actualValue}`);\n }\n}\n\nfunction getTypeName(value: any): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (Array.isArray(value)) return 'Array';\n if (typeof value === 'number' && Number.isNaN(value)) return 'NaN';\n if (typeof value === 'object') return 'Object';\n\n const type = typeof value;\n return type.charAt(0).toUpperCase() + type.slice(1);\n}\n\nconst TYPE_VALIDATORS: Record<string, (value: any) => boolean> = {\n Number: (value) => typeof value === 'number' && !Number.isNaN(value),\n String: (value) => typeof value === 'string',\n Boolean: (value) => typeof value === 'boolean',\n Array: (value) => Array.isArray(value),\n Object: (value) => typeof value === 'object' && value !== null && !Array.isArray(value)\n};\n\nfunction isValidateExpression(ctor: any): ctor is Expression {\n return typeof ctor === 'object' && !Array.isArray(ctor);\n}\n\nfunction isArraySchema(ctor: any): ctor is [PrimitiveType | Expression] {\n return Array.isArray(ctor) && ctor.length === 1;\n}\n\nfunction isOptional(ctor: any): boolean {\n return ctor.__optional === true;\n}\n\nfunction isNullable(ctor: any): boolean {\n return ctor.__nullable === true;\n}\n\nfunction isBothOptionalAndNullable(ctor: any): boolean {\n return ctor.__optional === true && ctor.__nullable === true;\n}\n\nfunction unwrapOptionalNullable(ctor: any): any {\n if (!ctor || typeof ctor !== 'object') {\n return ctor;\n }\n \n if (Array.isArray(ctor)) {\n return ctor;\n }\n \n if (ctor.__type) {\n return ctor.__type;\n }\n \n const unwrapped: any = {};\n for (const key in ctor) {\n if (key !== '__optional' && key !== '__nullable' && key !== '__type') {\n unwrapped[key] = ctor[key];\n }\n }\n \n if (Object.keys(unwrapped).length === 0 && (ctor.__optional || ctor.__nullable)) {\n return ctor;\n }\n \n return unwrapped;\n}\n\nfunction assertType(key: string, expectedType: string, value: any, isValid: boolean): void {\n if (!isValid) {\n throw new RuntimeTypeError(key, expectedType, getTypeName(value), value);\n }\n}\n\n/**\n * Asserts the type of target according to the expression schema.\n * The expression should be represented using primitive type constructors.\n * Supports nested types and array element type validation.\n *\n * Fields not declared in the expression but present in target are ignored.\n *\n * Fields declared in the expression do not allow undefined or null by default.\n * Wrap with Optional() to allow undefined.\n * Wrap with Nullable() to allow null.\n *\n * @param target - The value to validate\n * @param expression - The schema expression to validate against\n * @throws {RuntimeTypeError} When type mismatch occurs\n * @throws {RuntimeTypeError} When value is undefined or null (unless optional/nullable)\n * @example\n * ```ts\n * const data: unknown = getData();\n *\n * validate(data, {\n * \"a\": Boolean,\n * \"b\": {\n * \"c\": Number,\n * \"d\": Optional(String), // string | undefined\n * \"e\": Nullable(String) // string | null\n * },\n * \"items\": [{\n * \"name\": String,\n * \"value\": Number\n * }]\n * });\n *\n * // From this point, data is type-asserted\n * data.a // boolean\n * data.b.c // number\n * data.b.d // string | undefined\n * data.b.e // string | null\n * data.items[0].name // string\n * ```\n */\nexport function validate<T extends Expression>(\n target: any,\n expression: T,\n path: string = \"\"\n): asserts target is InferType<T> {\n for (const [key, ctor] of Object.entries(expression)) {\n const value = target[key];\n const currentPath = path ? `${path}.${key}` : key;\n\n if (isBothOptionalAndNullable(ctor)) {\n if (value === undefined || value === null) {\n continue\n }\n } else if (value === undefined && isOptional(ctor)) {\n continue;\n } else if (value === null && isNullable(ctor)) {\n continue;\n } else {\n assertType(currentPath, \"not undefined or null\", value, value !== undefined && value !== null);\n }\n\n const unwrappedCtor = unwrapOptionalNullable(ctor);\n\n if (isArraySchema(unwrappedCtor)) {\n assertType(currentPath, \"Array\", value, Array.isArray(value));\n const elementSchema = unwrappedCtor[0];\n\n (value as any[]).forEach((item, index) => {\n const arrayPath = `${currentPath}[${index}]`;\n if (isValidateExpression(elementSchema)) {\n const objectValidator = TYPE_VALIDATORS['Object'];\n assertType(arrayPath, \"Object\", item, objectValidator?.(item) ?? false);\n validate(item, elementSchema, arrayPath);\n } else {\n const validator = TYPE_VALIDATORS[elementSchema.name];\n if (!validator) {\n throw new Error(\"Invalid array element expression.\");\n }\n assertType(arrayPath, elementSchema.name, item, validator(item));\n }\n });\n continue;\n }\n\n if (typeof unwrappedCtor === 'function') {\n const validator = TYPE_VALIDATORS[unwrappedCtor.name];\n if (!validator) {\n throw new Error(\"Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.\");\n }\n assertType(currentPath, unwrappedCtor.name, value, validator(value));\n } else if (isValidateExpression(unwrappedCtor)) {\n const objectValidator = TYPE_VALIDATORS['Object'];\n if (!objectValidator) {\n throw new Error(\"Object validator not found\");\n }\n assertType(currentPath, \"Object\", value, objectValidator(value));\n validate(value, unwrappedCtor, currentPath);\n } else {\n throw new Error(\"Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.\");\n }\n }\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["type OptionalMarker = { __optional: true };\ntype NullableMarker = { __nullable: true };\n\ntype ArraySchema = [PrimitiveType | Expression];\n\ntype Expression = {\n [key: string | number]: PrimitiveType | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | Expression | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | ArraySchema | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker)\n}\n\ntype PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor;\n\ntype InferPrimitiveType<T> =\n T extends NumberConstructor ? number :\n T extends StringConstructor ? string :\n T extends BooleanConstructor ? boolean :\n T extends ArrayConstructor ? any[] :\n T extends ObjectConstructor ? object :\n T extends DateConstructor ? Date :\n never;\n\ntype InferType<T> =\n T extends PrimitiveType & OptionalMarker & NullableMarker\n ? InferPrimitiveType<T> | undefined | null\n : T extends PrimitiveType & OptionalMarker\n ? InferPrimitiveType<T> | undefined\n : T extends PrimitiveType & NullableMarker\n ? InferPrimitiveType<T> | null\n : T extends PrimitiveType\n ? InferPrimitiveType<T>\n : T extends Expression & OptionalMarker & NullableMarker\n ? { [K in keyof T]: InferType<T[K]> } | undefined | null\n : T extends Expression & OptionalMarker\n ? { [K in keyof T]: InferType<T[K]> } | undefined\n : T extends Expression & NullableMarker\n ? { [K in keyof T]: InferType<T[K]> } | null\n : T extends ArraySchema & OptionalMarker & NullableMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | undefined | null\n : T extends ArraySchema & OptionalMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | undefined\n : T extends ArraySchema & NullableMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | null\n : T extends [infer U]\n ? U extends Expression\n ? InferType<U>[]\n : U extends PrimitiveType\n ? InferPrimitiveType<U>[]\n : never\n : T extends Expression\n ? { [K in keyof T]: InferType<T[K]> }\n : never;\n\n/**\n * Marks a field as optional.\n * Optional fields pass validation even if they are undefined.\n * The type is inferred as T | undefined.\n * @example\n * ```ts\n * validate(data, {\n * required_field: String, // string (required)\n * optional_field: Optional(String), // string | undefined\n * optional_object: Optional({ // { id: string } | undefined\n * id: String\n * }),\n * optional_array: Optional([String]) // string[] | undefined\n * });\n * ```\n */\nexport function Optional<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & OptionalMarker {\n return {\n ...ctor as any,\n __optional: true,\n __type: ctor\n } as T & OptionalMarker;\n}\n\n/**\n * Marks a field as nullable.\n * Nullable fields pass validation even if they are null.\n * The type is inferred as T | null.\n * @example\n * ```ts\n * validate(data, {\n * required_field: String, // string (required)\n * nullable_field: Nullable(String), // string | null\n * nullable_object: Nullable({ // { id: string } | null\n * id: String\n * }),\n * nullable_array: Nullable([String]) // string[] | null\n * });\n * ```\n */\nexport function Nullable<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & NullableMarker {\n return {\n ...ctor as any,\n __nullable: true,\n __type: ctor\n } as T & NullableMarker;\n}\n\nexport class RuntimeTypeError extends Error {\n constructor(\n key: string,\n requiredType: string,\n actualType: string,\n actualValue: any,\n message?: string\n ) {\n if (message) message += \": \"\n super(`${message || \"\"}${key} must be ${requiredType}, but got ${actualType}. Actual value: ${actualValue}`);\n }\n}\n\nfunction getTypeName(value: any): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (Array.isArray(value)) return 'Array';\n if (typeof value === 'number' && Number.isNaN(value)) return 'NaN';\n if (typeof value === 'object') return 'Object';\n\n const type = typeof value;\n return type.charAt(0).toUpperCase() + type.slice(1);\n}\n\nconst TYPE_VALIDATORS = new Map<Function, (value: any) => boolean>([\n [Number, (value) => typeof value === 'number' && !Number.isNaN(value)],\n [String, (value) => typeof value === 'string'],\n [Boolean, (value) => typeof value === 'boolean'],\n [Array, (value) => Array.isArray(value)],\n [Object, (value) => typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)],\n [Date, (value) => value instanceof Date && !Number.isNaN(value.getTime())]\n]);\n\nfunction isValidateExpression(ctor: any): ctor is Expression {\n return typeof ctor === 'object' && !Array.isArray(ctor);\n}\n\nfunction isArraySchema(ctor: any): ctor is [PrimitiveType | Expression] {\n return Array.isArray(ctor) && ctor.length === 1;\n}\n\nfunction isOptional(ctor: any): boolean {\n return ctor.__optional === true;\n}\n\nfunction isNullable(ctor: any): boolean {\n return ctor.__nullable === true;\n}\n\nfunction isBothOptionalAndNullable(ctor: any): boolean {\n return ctor.__optional === true && ctor.__nullable === true;\n}\n\nfunction unwrapOptionalNullable(ctor: any): any {\n while (ctor && typeof ctor === 'object' && !Array.isArray(ctor) && ctor.__type) {\n ctor = ctor.__type;\n }\n return ctor;\n}\n\nfunction assertType(key: string, expectedType: string, value: any, isValid: boolean): void {\n if (!isValid) {\n throw new RuntimeTypeError(key, expectedType, getTypeName(value), value);\n }\n}\n\nfunction validateArrayElement(item: any, schema: any, path: string): void {\n if (isArraySchema(schema)) {\n assertType(path, \"Array\", item, Array.isArray(item));\n const innerSchema = schema[0];\n const arr = item as any[];\n for (let i = 0; i < arr.length; i++) {\n validateArrayElement(arr[i], innerSchema, `${path}[${i}]`);\n }\n } else if (isValidateExpression(schema)) {\n const objectValidator = TYPE_VALIDATORS.get(Object);\n assertType(path, \"Object\", item, objectValidator?.(item) ?? false);\n validate(item, schema, path);\n } else if (typeof schema === 'function') {\n const validator = TYPE_VALIDATORS.get(schema);\n if (!validator) {\n throw new Error(\"Invalid array element expression.\");\n }\n assertType(path, schema.name, item, validator(item));\n } else {\n throw new Error(\"Invalid array element expression.\");\n }\n}\n\n/**\n * Asserts the type of target according to the expression schema.\n * The expression should be represented using primitive type constructors.\n * Supports nested types and array element type validation.\n *\n * Fields not declared in the expression but present in target are ignored.\n *\n * Fields declared in the expression do not allow undefined or null by default.\n * Wrap with Optional() to allow undefined.\n * Wrap with Nullable() to allow null.\n *\n * @param target - The value to validate\n * @param expression - The schema expression to validate against\n * @throws {RuntimeTypeError} When type mismatch occurs\n * @throws {RuntimeTypeError} When value is undefined or null (unless optional/nullable)\n * @example\n * ```ts\n * const data: unknown = getData();\n *\n * validate(data, {\n * \"a\": Boolean,\n * \"b\": {\n * \"c\": Number,\n * \"d\": Optional(String), // string | undefined\n * \"e\": Nullable(String) // string | null\n * },\n * \"items\": [{\n * \"name\": String,\n * \"value\": Number\n * }]\n * });\n *\n * // From this point, data is type-asserted\n * data.a // boolean\n * data.b.c // number\n * data.b.d // string | undefined\n * data.b.e // string | null\n * data.items[0].name // string\n * ```\n */\nexport function validate<T extends Expression>(\n target: any,\n expression: T,\n path: string = \"\"\n): asserts target is InferType<T> {\n for (const key in expression) {\n if (!Object.prototype.hasOwnProperty.call(expression, key)) continue;\n\n const ctor = expression[key];\n const value = target[key];\n const currentPath = path ? `${path}.${key}` : key;\n\n if (isBothOptionalAndNullable(ctor)) {\n if (value === undefined || value === null) {\n continue\n }\n } else if (value === undefined && isOptional(ctor)) {\n continue;\n } else if (value === null && isNullable(ctor)) {\n continue;\n } else {\n assertType(currentPath, \"not undefined or null\", value, value !== undefined && value !== null);\n }\n\n const unwrappedCtor = unwrapOptionalNullable(ctor);\n\n if (isArraySchema(unwrappedCtor)) {\n assertType(currentPath, \"Array\", value, Array.isArray(value));\n const elementSchema = unwrappedCtor[0];\n const arr = value as any[];\n\n for (let i = 0; i < arr.length; i++) {\n validateArrayElement(arr[i], elementSchema, `${currentPath}[${i}]`);\n }\n continue;\n }\n\n if (typeof unwrappedCtor === 'function') {\n const validator = TYPE_VALIDATORS.get(unwrappedCtor);\n if (!validator) {\n throw new Error(\"Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.\");\n }\n assertType(currentPath, unwrappedCtor.name, value, validator(value));\n } else if (isValidateExpression(unwrappedCtor)) {\n const objectValidator = TYPE_VALIDATORS.get(Object)!;\n assertType(currentPath, \"Object\", value, objectValidator(value));\n validate(value, unwrappedCtor, currentPath);\n } else {\n throw new Error(\"Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.\");\n }\n }\n}"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -28,13 +28,14 @@ function getTypeName(value) {
28
28
  const type = typeof value;
29
29
  return type.charAt(0).toUpperCase() + type.slice(1);
30
30
  }
31
- var TYPE_VALIDATORS = {
32
- Number: (value) => typeof value === "number" && !Number.isNaN(value),
33
- String: (value) => typeof value === "string",
34
- Boolean: (value) => typeof value === "boolean",
35
- Array: (value) => Array.isArray(value),
36
- Object: (value) => typeof value === "object" && value !== null && !Array.isArray(value)
37
- };
31
+ var TYPE_VALIDATORS = /* @__PURE__ */ new Map([
32
+ [Number, (value) => typeof value === "number" && !Number.isNaN(value)],
33
+ [String, (value) => typeof value === "string"],
34
+ [Boolean, (value) => typeof value === "boolean"],
35
+ [Array, (value) => Array.isArray(value)],
36
+ [Object, (value) => typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date)],
37
+ [Date, (value) => value instanceof Date && !Number.isNaN(value.getTime())]
38
+ ]);
38
39
  function isValidateExpression(ctor) {
39
40
  return typeof ctor === "object" && !Array.isArray(ctor);
40
41
  }
@@ -51,33 +52,42 @@ function isBothOptionalAndNullable(ctor) {
51
52
  return ctor.__optional === true && ctor.__nullable === true;
52
53
  }
53
54
  function unwrapOptionalNullable(ctor) {
54
- if (!ctor || typeof ctor !== "object") {
55
- return ctor;
56
- }
57
- if (Array.isArray(ctor)) {
58
- return ctor;
59
- }
60
- if (ctor.__type) {
61
- return ctor.__type;
62
- }
63
- const unwrapped = {};
64
- for (const key in ctor) {
65
- if (key !== "__optional" && key !== "__nullable" && key !== "__type") {
66
- unwrapped[key] = ctor[key];
67
- }
68
- }
69
- if (Object.keys(unwrapped).length === 0 && (ctor.__optional || ctor.__nullable)) {
70
- return ctor;
55
+ while (ctor && typeof ctor === "object" && !Array.isArray(ctor) && ctor.__type) {
56
+ ctor = ctor.__type;
71
57
  }
72
- return unwrapped;
58
+ return ctor;
73
59
  }
74
60
  function assertType(key, expectedType, value, isValid) {
75
61
  if (!isValid) {
76
62
  throw new RuntimeTypeError(key, expectedType, getTypeName(value), value);
77
63
  }
78
64
  }
65
+ function validateArrayElement(item, schema, path) {
66
+ if (isArraySchema(schema)) {
67
+ assertType(path, "Array", item, Array.isArray(item));
68
+ const innerSchema = schema[0];
69
+ const arr = item;
70
+ for (let i = 0; i < arr.length; i++) {
71
+ validateArrayElement(arr[i], innerSchema, `${path}[${i}]`);
72
+ }
73
+ } else if (isValidateExpression(schema)) {
74
+ const objectValidator = TYPE_VALIDATORS.get(Object);
75
+ assertType(path, "Object", item, objectValidator?.(item) ?? false);
76
+ validate(item, schema, path);
77
+ } else if (typeof schema === "function") {
78
+ const validator = TYPE_VALIDATORS.get(schema);
79
+ if (!validator) {
80
+ throw new Error("Invalid array element expression.");
81
+ }
82
+ assertType(path, schema.name, item, validator(item));
83
+ } else {
84
+ throw new Error("Invalid array element expression.");
85
+ }
86
+ }
79
87
  function validate(target, expression, path = "") {
80
- for (const [key, ctor] of Object.entries(expression)) {
88
+ for (const key in expression) {
89
+ if (!Object.prototype.hasOwnProperty.call(expression, key)) continue;
90
+ const ctor = expression[key];
81
91
  const value = target[key];
82
92
  const currentPath = path ? `${path}.${key}` : key;
83
93
  if (isBothOptionalAndNullable(ctor)) {
@@ -95,33 +105,20 @@ function validate(target, expression, path = "") {
95
105
  if (isArraySchema(unwrappedCtor)) {
96
106
  assertType(currentPath, "Array", value, Array.isArray(value));
97
107
  const elementSchema = unwrappedCtor[0];
98
- value.forEach((item, index) => {
99
- const arrayPath = `${currentPath}[${index}]`;
100
- if (isValidateExpression(elementSchema)) {
101
- const objectValidator = TYPE_VALIDATORS["Object"];
102
- assertType(arrayPath, "Object", item, objectValidator?.(item) ?? false);
103
- validate(item, elementSchema, arrayPath);
104
- } else {
105
- const validator = TYPE_VALIDATORS[elementSchema.name];
106
- if (!validator) {
107
- throw new Error("Invalid array element expression.");
108
- }
109
- assertType(arrayPath, elementSchema.name, item, validator(item));
110
- }
111
- });
108
+ const arr = value;
109
+ for (let i = 0; i < arr.length; i++) {
110
+ validateArrayElement(arr[i], elementSchema, `${currentPath}[${i}]`);
111
+ }
112
112
  continue;
113
113
  }
114
114
  if (typeof unwrappedCtor === "function") {
115
- const validator = TYPE_VALIDATORS[unwrappedCtor.name];
115
+ const validator = TYPE_VALIDATORS.get(unwrappedCtor);
116
116
  if (!validator) {
117
117
  throw new Error("Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.");
118
118
  }
119
119
  assertType(currentPath, unwrappedCtor.name, value, validator(value));
120
120
  } else if (isValidateExpression(unwrappedCtor)) {
121
- const objectValidator = TYPE_VALIDATORS["Object"];
122
- if (!objectValidator) {
123
- throw new Error("Object validator not found");
124
- }
121
+ const objectValidator = TYPE_VALIDATORS.get(Object);
125
122
  assertType(currentPath, "Object", value, objectValidator(value));
126
123
  validate(value, unwrappedCtor, currentPath);
127
124
  } else {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["type OptionalMarker = { __optional: true };\ntype NullableMarker = { __nullable: true };\n\ntype ArraySchema = [PrimitiveType | Expression];\n\ntype Expression = {\n [key: string | number]: PrimitiveType | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | Expression | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | ArraySchema | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker)\n}\n\ntype PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor;\n\ntype InferPrimitiveType<T> =\n T extends NumberConstructor ? number :\n T extends StringConstructor ? string :\n T extends BooleanConstructor ? boolean :\n T extends ArrayConstructor ? any[] :\n T extends ObjectConstructor ? object :\n never;\n\ntype InferType<T> =\n T extends PrimitiveType & OptionalMarker & NullableMarker\n ? InferPrimitiveType<T> | undefined | null\n : T extends PrimitiveType & OptionalMarker\n ? InferPrimitiveType<T> | undefined\n : T extends PrimitiveType & NullableMarker\n ? InferPrimitiveType<T> | null\n : T extends PrimitiveType\n ? InferPrimitiveType<T>\n : T extends Expression & OptionalMarker & NullableMarker\n ? { [K in keyof T]: InferType<T[K]> } | undefined | null\n : T extends Expression & OptionalMarker\n ? { [K in keyof T]: InferType<T[K]> } | undefined\n : T extends Expression & NullableMarker\n ? { [K in keyof T]: InferType<T[K]> } | null\n : T extends ArraySchema & OptionalMarker & NullableMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | undefined | null\n : T extends ArraySchema & OptionalMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | undefined\n : T extends ArraySchema & NullableMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | null\n : T extends [infer U]\n ? U extends Expression\n ? InferType<U>[]\n : U extends PrimitiveType\n ? InferPrimitiveType<U>[]\n : never\n : T extends Expression\n ? { [K in keyof T]: InferType<T[K]> }\n : never;\n\n/**\n * Marks a field as optional.\n * Optional fields pass validation even if they are undefined.\n * The type is inferred as T | undefined.\n * @example\n * ```ts\n * validate(data, {\n * required_field: String, // string (required)\n * optional_field: Optional(String), // string | undefined\n * optional_object: Optional({ // { id: string } | undefined\n * id: String\n * }),\n * optional_array: Optional([String]) // string[] | undefined\n * });\n * ```\n */\nexport function Optional<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & OptionalMarker {\n return {\n ...ctor as any,\n __optional: true,\n __type: ctor\n } as T & OptionalMarker;\n}\n\n/**\n * Marks a field as nullable.\n * Nullable fields pass validation even if they are null.\n * The type is inferred as T | null.\n * @example\n * ```ts\n * validate(data, {\n * required_field: String, // string (required)\n * nullable_field: Nullable(String), // string | null\n * nullable_object: Nullable({ // { id: string } | null\n * id: String\n * }),\n * nullable_array: Nullable([String]) // string[] | null\n * });\n * ```\n */\nexport function Nullable<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & NullableMarker {\n return {\n ...ctor as any,\n __nullable: true,\n __type: ctor\n } as T & NullableMarker;\n}\n\nexport class RuntimeTypeError extends Error {\n constructor(\n key: string,\n requiredType: string,\n actualType: string,\n actualValue: any,\n message?: string\n ) {\n if (message) message += \": \"\n super(`${message || \"\"}${key} must be ${requiredType}, but got ${actualType}. Actual value: ${actualValue}`);\n }\n}\n\nfunction getTypeName(value: any): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (Array.isArray(value)) return 'Array';\n if (typeof value === 'number' && Number.isNaN(value)) return 'NaN';\n if (typeof value === 'object') return 'Object';\n\n const type = typeof value;\n return type.charAt(0).toUpperCase() + type.slice(1);\n}\n\nconst TYPE_VALIDATORS: Record<string, (value: any) => boolean> = {\n Number: (value) => typeof value === 'number' && !Number.isNaN(value),\n String: (value) => typeof value === 'string',\n Boolean: (value) => typeof value === 'boolean',\n Array: (value) => Array.isArray(value),\n Object: (value) => typeof value === 'object' && value !== null && !Array.isArray(value)\n};\n\nfunction isValidateExpression(ctor: any): ctor is Expression {\n return typeof ctor === 'object' && !Array.isArray(ctor);\n}\n\nfunction isArraySchema(ctor: any): ctor is [PrimitiveType | Expression] {\n return Array.isArray(ctor) && ctor.length === 1;\n}\n\nfunction isOptional(ctor: any): boolean {\n return ctor.__optional === true;\n}\n\nfunction isNullable(ctor: any): boolean {\n return ctor.__nullable === true;\n}\n\nfunction isBothOptionalAndNullable(ctor: any): boolean {\n return ctor.__optional === true && ctor.__nullable === true;\n}\n\nfunction unwrapOptionalNullable(ctor: any): any {\n if (!ctor || typeof ctor !== 'object') {\n return ctor;\n }\n \n if (Array.isArray(ctor)) {\n return ctor;\n }\n \n if (ctor.__type) {\n return ctor.__type;\n }\n \n const unwrapped: any = {};\n for (const key in ctor) {\n if (key !== '__optional' && key !== '__nullable' && key !== '__type') {\n unwrapped[key] = ctor[key];\n }\n }\n \n if (Object.keys(unwrapped).length === 0 && (ctor.__optional || ctor.__nullable)) {\n return ctor;\n }\n \n return unwrapped;\n}\n\nfunction assertType(key: string, expectedType: string, value: any, isValid: boolean): void {\n if (!isValid) {\n throw new RuntimeTypeError(key, expectedType, getTypeName(value), value);\n }\n}\n\n/**\n * Asserts the type of target according to the expression schema.\n * The expression should be represented using primitive type constructors.\n * Supports nested types and array element type validation.\n *\n * Fields not declared in the expression but present in target are ignored.\n *\n * Fields declared in the expression do not allow undefined or null by default.\n * Wrap with Optional() to allow undefined.\n * Wrap with Nullable() to allow null.\n *\n * @param target - The value to validate\n * @param expression - The schema expression to validate against\n * @throws {RuntimeTypeError} When type mismatch occurs\n * @throws {RuntimeTypeError} When value is undefined or null (unless optional/nullable)\n * @example\n * ```ts\n * const data: unknown = getData();\n *\n * validate(data, {\n * \"a\": Boolean,\n * \"b\": {\n * \"c\": Number,\n * \"d\": Optional(String), // string | undefined\n * \"e\": Nullable(String) // string | null\n * },\n * \"items\": [{\n * \"name\": String,\n * \"value\": Number\n * }]\n * });\n *\n * // From this point, data is type-asserted\n * data.a // boolean\n * data.b.c // number\n * data.b.d // string | undefined\n * data.b.e // string | null\n * data.items[0].name // string\n * ```\n */\nexport function validate<T extends Expression>(\n target: any,\n expression: T,\n path: string = \"\"\n): asserts target is InferType<T> {\n for (const [key, ctor] of Object.entries(expression)) {\n const value = target[key];\n const currentPath = path ? `${path}.${key}` : key;\n\n if (isBothOptionalAndNullable(ctor)) {\n if (value === undefined || value === null) {\n continue\n }\n } else if (value === undefined && isOptional(ctor)) {\n continue;\n } else if (value === null && isNullable(ctor)) {\n continue;\n } else {\n assertType(currentPath, \"not undefined or null\", value, value !== undefined && value !== null);\n }\n\n const unwrappedCtor = unwrapOptionalNullable(ctor);\n\n if (isArraySchema(unwrappedCtor)) {\n assertType(currentPath, \"Array\", value, Array.isArray(value));\n const elementSchema = unwrappedCtor[0];\n\n (value as any[]).forEach((item, index) => {\n const arrayPath = `${currentPath}[${index}]`;\n if (isValidateExpression(elementSchema)) {\n const objectValidator = TYPE_VALIDATORS['Object'];\n assertType(arrayPath, \"Object\", item, objectValidator?.(item) ?? false);\n validate(item, elementSchema, arrayPath);\n } else {\n const validator = TYPE_VALIDATORS[elementSchema.name];\n if (!validator) {\n throw new Error(\"Invalid array element expression.\");\n }\n assertType(arrayPath, elementSchema.name, item, validator(item));\n }\n });\n continue;\n }\n\n if (typeof unwrappedCtor === 'function') {\n const validator = TYPE_VALIDATORS[unwrappedCtor.name];\n if (!validator) {\n throw new Error(\"Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.\");\n }\n assertType(currentPath, unwrappedCtor.name, value, validator(value));\n } else if (isValidateExpression(unwrappedCtor)) {\n const objectValidator = TYPE_VALIDATORS['Object'];\n if (!objectValidator) {\n throw new Error(\"Object validator not found\");\n }\n assertType(currentPath, \"Object\", value, objectValidator(value));\n validate(value, unwrappedCtor, currentPath);\n } else {\n throw new Error(\"Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.\");\n }\n }\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["type OptionalMarker = { __optional: true };\ntype NullableMarker = { __nullable: true };\n\ntype ArraySchema = [PrimitiveType | Expression];\n\ntype Expression = {\n [key: string | number]: PrimitiveType | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | Expression | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | ArraySchema | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker)\n}\n\ntype PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor;\n\ntype InferPrimitiveType<T> =\n T extends NumberConstructor ? number :\n T extends StringConstructor ? string :\n T extends BooleanConstructor ? boolean :\n T extends ArrayConstructor ? any[] :\n T extends ObjectConstructor ? object :\n T extends DateConstructor ? Date :\n never;\n\ntype InferType<T> =\n T extends PrimitiveType & OptionalMarker & NullableMarker\n ? InferPrimitiveType<T> | undefined | null\n : T extends PrimitiveType & OptionalMarker\n ? InferPrimitiveType<T> | undefined\n : T extends PrimitiveType & NullableMarker\n ? InferPrimitiveType<T> | null\n : T extends PrimitiveType\n ? InferPrimitiveType<T>\n : T extends Expression & OptionalMarker & NullableMarker\n ? { [K in keyof T]: InferType<T[K]> } | undefined | null\n : T extends Expression & OptionalMarker\n ? { [K in keyof T]: InferType<T[K]> } | undefined\n : T extends Expression & NullableMarker\n ? { [K in keyof T]: InferType<T[K]> } | null\n : T extends ArraySchema & OptionalMarker & NullableMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | undefined | null\n : T extends ArraySchema & OptionalMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | undefined\n : T extends ArraySchema & NullableMarker\n ? (T extends [infer U] ? (U extends Expression ? InferType<U>[] : U extends PrimitiveType ? InferPrimitiveType<U>[] : never) : never) | null\n : T extends [infer U]\n ? U extends Expression\n ? InferType<U>[]\n : U extends PrimitiveType\n ? InferPrimitiveType<U>[]\n : never\n : T extends Expression\n ? { [K in keyof T]: InferType<T[K]> }\n : never;\n\n/**\n * Marks a field as optional.\n * Optional fields pass validation even if they are undefined.\n * The type is inferred as T | undefined.\n * @example\n * ```ts\n * validate(data, {\n * required_field: String, // string (required)\n * optional_field: Optional(String), // string | undefined\n * optional_object: Optional({ // { id: string } | undefined\n * id: String\n * }),\n * optional_array: Optional([String]) // string[] | undefined\n * });\n * ```\n */\nexport function Optional<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & OptionalMarker {\n return {\n ...ctor as any,\n __optional: true,\n __type: ctor\n } as T & OptionalMarker;\n}\n\n/**\n * Marks a field as nullable.\n * Nullable fields pass validation even if they are null.\n * The type is inferred as T | null.\n * @example\n * ```ts\n * validate(data, {\n * required_field: String, // string (required)\n * nullable_field: Nullable(String), // string | null\n * nullable_object: Nullable({ // { id: string } | null\n * id: String\n * }),\n * nullable_array: Nullable([String]) // string[] | null\n * });\n * ```\n */\nexport function Nullable<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & NullableMarker {\n return {\n ...ctor as any,\n __nullable: true,\n __type: ctor\n } as T & NullableMarker;\n}\n\nexport class RuntimeTypeError extends Error {\n constructor(\n key: string,\n requiredType: string,\n actualType: string,\n actualValue: any,\n message?: string\n ) {\n if (message) message += \": \"\n super(`${message || \"\"}${key} must be ${requiredType}, but got ${actualType}. Actual value: ${actualValue}`);\n }\n}\n\nfunction getTypeName(value: any): string {\n if (value === null) return 'null';\n if (value === undefined) return 'undefined';\n if (Array.isArray(value)) return 'Array';\n if (typeof value === 'number' && Number.isNaN(value)) return 'NaN';\n if (typeof value === 'object') return 'Object';\n\n const type = typeof value;\n return type.charAt(0).toUpperCase() + type.slice(1);\n}\n\nconst TYPE_VALIDATORS = new Map<Function, (value: any) => boolean>([\n [Number, (value) => typeof value === 'number' && !Number.isNaN(value)],\n [String, (value) => typeof value === 'string'],\n [Boolean, (value) => typeof value === 'boolean'],\n [Array, (value) => Array.isArray(value)],\n [Object, (value) => typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)],\n [Date, (value) => value instanceof Date && !Number.isNaN(value.getTime())]\n]);\n\nfunction isValidateExpression(ctor: any): ctor is Expression {\n return typeof ctor === 'object' && !Array.isArray(ctor);\n}\n\nfunction isArraySchema(ctor: any): ctor is [PrimitiveType | Expression] {\n return Array.isArray(ctor) && ctor.length === 1;\n}\n\nfunction isOptional(ctor: any): boolean {\n return ctor.__optional === true;\n}\n\nfunction isNullable(ctor: any): boolean {\n return ctor.__nullable === true;\n}\n\nfunction isBothOptionalAndNullable(ctor: any): boolean {\n return ctor.__optional === true && ctor.__nullable === true;\n}\n\nfunction unwrapOptionalNullable(ctor: any): any {\n while (ctor && typeof ctor === 'object' && !Array.isArray(ctor) && ctor.__type) {\n ctor = ctor.__type;\n }\n return ctor;\n}\n\nfunction assertType(key: string, expectedType: string, value: any, isValid: boolean): void {\n if (!isValid) {\n throw new RuntimeTypeError(key, expectedType, getTypeName(value), value);\n }\n}\n\nfunction validateArrayElement(item: any, schema: any, path: string): void {\n if (isArraySchema(schema)) {\n assertType(path, \"Array\", item, Array.isArray(item));\n const innerSchema = schema[0];\n const arr = item as any[];\n for (let i = 0; i < arr.length; i++) {\n validateArrayElement(arr[i], innerSchema, `${path}[${i}]`);\n }\n } else if (isValidateExpression(schema)) {\n const objectValidator = TYPE_VALIDATORS.get(Object);\n assertType(path, \"Object\", item, objectValidator?.(item) ?? false);\n validate(item, schema, path);\n } else if (typeof schema === 'function') {\n const validator = TYPE_VALIDATORS.get(schema);\n if (!validator) {\n throw new Error(\"Invalid array element expression.\");\n }\n assertType(path, schema.name, item, validator(item));\n } else {\n throw new Error(\"Invalid array element expression.\");\n }\n}\n\n/**\n * Asserts the type of target according to the expression schema.\n * The expression should be represented using primitive type constructors.\n * Supports nested types and array element type validation.\n *\n * Fields not declared in the expression but present in target are ignored.\n *\n * Fields declared in the expression do not allow undefined or null by default.\n * Wrap with Optional() to allow undefined.\n * Wrap with Nullable() to allow null.\n *\n * @param target - The value to validate\n * @param expression - The schema expression to validate against\n * @throws {RuntimeTypeError} When type mismatch occurs\n * @throws {RuntimeTypeError} When value is undefined or null (unless optional/nullable)\n * @example\n * ```ts\n * const data: unknown = getData();\n *\n * validate(data, {\n * \"a\": Boolean,\n * \"b\": {\n * \"c\": Number,\n * \"d\": Optional(String), // string | undefined\n * \"e\": Nullable(String) // string | null\n * },\n * \"items\": [{\n * \"name\": String,\n * \"value\": Number\n * }]\n * });\n *\n * // From this point, data is type-asserted\n * data.a // boolean\n * data.b.c // number\n * data.b.d // string | undefined\n * data.b.e // string | null\n * data.items[0].name // string\n * ```\n */\nexport function validate<T extends Expression>(\n target: any,\n expression: T,\n path: string = \"\"\n): asserts target is InferType<T> {\n for (const key in expression) {\n if (!Object.prototype.hasOwnProperty.call(expression, key)) continue;\n\n const ctor = expression[key];\n const value = target[key];\n const currentPath = path ? `${path}.${key}` : key;\n\n if (isBothOptionalAndNullable(ctor)) {\n if (value === undefined || value === null) {\n continue\n }\n } else if (value === undefined && isOptional(ctor)) {\n continue;\n } else if (value === null && isNullable(ctor)) {\n continue;\n } else {\n assertType(currentPath, \"not undefined or null\", value, value !== undefined && value !== null);\n }\n\n const unwrappedCtor = unwrapOptionalNullable(ctor);\n\n if (isArraySchema(unwrappedCtor)) {\n assertType(currentPath, \"Array\", value, Array.isArray(value));\n const elementSchema = unwrappedCtor[0];\n const arr = value as any[];\n\n for (let i = 0; i < arr.length; i++) {\n validateArrayElement(arr[i], elementSchema, `${currentPath}[${i}]`);\n }\n continue;\n }\n\n if (typeof unwrappedCtor === 'function') {\n const validator = TYPE_VALIDATORS.get(unwrappedCtor);\n if (!validator) {\n throw new Error(\"Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.\");\n }\n assertType(currentPath, unwrappedCtor.name, value, validator(value));\n } else if (isValidateExpression(unwrappedCtor)) {\n const objectValidator = TYPE_VALIDATORS.get(Object)!;\n assertType(currentPath, \"Object\", value, objectValidator(value));\n validate(value, unwrappedCtor, currentPath);\n } else {\n throw new Error(\"Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.\");\n }\n }\n}"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valdex",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Runtime type validation with TypeScript type inference",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -23,7 +23,7 @@
23
23
  "scripts": {
24
24
  "build": "tsup",
25
25
  "prepublishOnly": "npm run build",
26
- "test": "tsx test.ts"
26
+ "test": "jest"
27
27
  },
28
28
  "keywords": [
29
29
  "typescript",
@@ -36,9 +36,12 @@
36
36
  "author": "",
37
37
  "license": "MIT",
38
38
  "devDependencies": {
39
+ "@types/jest": "^30.0.0",
40
+ "jest": "^30.2.0",
41
+ "ts-jest": "^29.4.6",
39
42
  "tsup": "^8.3.5",
40
- "typescript": "^5.7.2",
41
- "tsx": "^4.19.2"
43
+ "tsx": "^4.19.2",
44
+ "typescript": "^5.7.2"
42
45
  },
43
46
  "repository": {
44
47
  "type": "git",