@point3/valdex 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 point3 (violetpay)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # valdex
2
+
3
+ **Runtime type validation with TypeScript type inference**
4
+
5
+ Validate unknown data at runtime and get automatic TypeScript type narrowing—without separate schema objects or class instances.
6
+
7
+ ## Why valdex?
8
+
9
+ Runtime validation libraries typically require you to define schemas separately and instantiate them before use. This creates distance between where you validate and where you consume the data.
10
+
11
+ Valdex takes a different approach: validate inline, exactly where you need it. No jumping between schema definitions and usage points. No maintaining separate DTO classes or validator instances.
12
+
13
+ ```typescript
14
+ // Traditional approach - schema defined elsewhere
15
+ const userSchema = z.object({ name: z.string(), age: z.number() });
16
+ const user = userSchema.parse(data);
17
+
18
+ // valdex - validate at point of use
19
+ validate(data, { name: String, age: Number });
20
+ // data is now typed as { name: string, age: number }
21
+ ```
22
+
23
+ This is particularly useful when working with:
24
+ - Database query results (mysql2, pg, etc.)
25
+ - External API responses (axios, fetch)
26
+ - Message queue payloads
27
+ - Any `unknown` or `any` typed data that needs runtime verification
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install valdex
33
+ ```
34
+
35
+ ## Features
36
+
37
+ - **Zero dependencies**: No external dependencies
38
+ - **Type inference**: Automatic TypeScript type narrowing after validation
39
+ - **Inline validation**: Validate where you use, not where you define
40
+ - **Nested structures**: Full support for nested objects and arrays
41
+ - **Optional/Nullable**: Flexible handling of optional and nullable fields
42
+
43
+ ## Usage
44
+
45
+ ### Basic Validation
46
+
47
+ ```typescript
48
+ import { validate } from 'valdex';
49
+
50
+ const data: unknown = await fetchData();
51
+
52
+ validate(data, {
53
+ name: String,
54
+ age: Number,
55
+ active: Boolean
56
+ });
57
+
58
+ // TypeScript now knows the exact type of data
59
+ data.name // string
60
+ data.age // number
61
+ data.active // boolean
62
+ ```
63
+
64
+ ### Nested Objects
65
+
66
+ ```typescript
67
+ validate(data, {
68
+ user: {
69
+ id: Number,
70
+ profile: {
71
+ name: String,
72
+ email: String
73
+ }
74
+ }
75
+ });
76
+
77
+ data.user.profile.name // string
78
+ ```
79
+
80
+ ### Arrays
81
+
82
+ ```typescript
83
+ validate(data, {
84
+ tags: [String], // string[]
85
+ items: [{ // { id: number, name: string }[]
86
+ id: Number,
87
+ name: String
88
+ }]
89
+ });
90
+
91
+ data.tags[0] // string
92
+ data.items[0].id // number
93
+ ```
94
+
95
+ ### Optional Fields
96
+
97
+ Use `Optional()` to allow `undefined` values:
98
+
99
+ ```typescript
100
+ import { validate, Optional } from 'valdex';
101
+
102
+ validate(data, {
103
+ required: String,
104
+ optional: Optional(String), // string | undefined
105
+ optionalObject: Optional({ // { id: number } | undefined
106
+ id: Number
107
+ }),
108
+ optionalArray: Optional([Number]) // number[] | undefined
109
+ });
110
+ ```
111
+
112
+ ### Nullable Fields
113
+
114
+ Use `Nullable()` to allow `null` values:
115
+
116
+ ```typescript
117
+ import { validate, Nullable } from 'valdex';
118
+
119
+ validate(data, {
120
+ required: String,
121
+ nullable: Nullable(String), // string | null
122
+ nullableObject: Nullable({ // { id: number } | null
123
+ id: Number
124
+ })
125
+ });
126
+ ```
127
+
128
+ ### Combining Optional and Nullable
129
+
130
+ ```typescript
131
+ import { validate, Optional, Nullable } from 'valdex';
132
+
133
+ validate(data, {
134
+ field: Optional(Nullable(String)) // string | undefined | null
135
+ });
136
+ ```
137
+
138
+ ## Supported Types
139
+
140
+ | Constructor | TypeScript Type |
141
+ |-------------|-----------------|
142
+ | `String` | `string` |
143
+ | `Number` | `number` |
144
+ | `Boolean` | `boolean` |
145
+ | `Array` | `any[]` |
146
+ | `Object` | `object` |
147
+ | `Date` | `Date` |
148
+
149
+ ## Error Handling
150
+
151
+ When validation fails, a `RuntimeTypeError` is thrown:
152
+
153
+ ```typescript
154
+ import { validate, RuntimeTypeError } from 'valdex';
155
+
156
+ try {
157
+ validate(data, { count: Number });
158
+ } catch (error) {
159
+ if (error instanceof RuntimeTypeError) {
160
+ console.error(error.message);
161
+ // "count must be Number, but got String. Actual value: hello"
162
+ }
163
+ }
164
+ ```
165
+
166
+ ## How It Works
167
+
168
+ - Fields not declared in the schema but present in data are ignored
169
+ - All declared fields are required by default (no `undefined` or `null`)
170
+ - Use `Optional()` to allow `undefined`
171
+ - Use `Nullable()` to allow `null`
172
+ - `NaN` is not considered a valid `Number`
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,102 @@
1
+ type OptionalMarker = {
2
+ __optional: true;
3
+ };
4
+ type NullableMarker = {
5
+ __nullable: true;
6
+ };
7
+ type MarkerKeys = '__optional' | '__nullable' | '__type';
8
+ type ArraySchema = [PrimitiveType | Expression];
9
+ type Expression = {
10
+ [key: string | number]: PrimitiveType | Expression | ArraySchema | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker) | true;
11
+ };
12
+ type PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor;
13
+ 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;
14
+ 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 ArraySchema & OptionalMarker & NullableMarker ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | undefined | null : T extends ArraySchema & OptionalMarker ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | undefined : T extends ArraySchema & NullableMarker ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | null : T extends [infer U] ? U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[] : T extends OptionalMarker & NullableMarker ? {
15
+ [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]>;
16
+ } | undefined | null : T extends OptionalMarker ? {
17
+ [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]>;
18
+ } | undefined : T extends NullableMarker ? {
19
+ [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]>;
20
+ } | null : T extends Record<string, any> ? {
21
+ [K in keyof T]: InferType<T[K]>;
22
+ } : never;
23
+ /**
24
+ * Marks a field as optional.
25
+ * Optional fields pass validation even if they are undefined.
26
+ * The type is inferred as T | undefined.
27
+ * @example
28
+ * ```ts
29
+ * validate(data, {
30
+ * required_field: String, // string (required)
31
+ * optional_field: Optional(String), // string | undefined
32
+ * optional_object: Optional({ // { id: string } | undefined
33
+ * id: String
34
+ * }),
35
+ * optional_array: Optional([String]) // string[] | undefined
36
+ * });
37
+ * ```
38
+ */
39
+ declare function Optional<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & OptionalMarker;
40
+ /**
41
+ * Marks a field as nullable.
42
+ * Nullable fields pass validation even if they are null.
43
+ * The type is inferred as T | null.
44
+ * @example
45
+ * ```ts
46
+ * validate(data, {
47
+ * required_field: String, // string (required)
48
+ * nullable_field: Nullable(String), // string | null
49
+ * nullable_object: Nullable({ // { id: string } | null
50
+ * id: String
51
+ * }),
52
+ * nullable_array: Nullable([String]) // string[] | null
53
+ * });
54
+ * ```
55
+ */
56
+ declare function Nullable<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & NullableMarker;
57
+ declare class RuntimeTypeError extends Error {
58
+ constructor(key: string, requiredType: string, actualType: string, actualValue: any, message?: string);
59
+ }
60
+ /**
61
+ * Asserts the type of target according to the expression schema.
62
+ * The expression should be represented using primitive type constructors.
63
+ * Supports nested types and array element type validation.
64
+ *
65
+ * Fields not declared in the expression but present in target are ignored.
66
+ *
67
+ * Fields declared in the expression do not allow undefined or null by default.
68
+ * Wrap with Optional() to allow undefined.
69
+ * Wrap with Nullable() to allow null.
70
+ *
71
+ * @param target - The value to validate
72
+ * @param expression - The schema expression to validate against
73
+ * @throws {RuntimeTypeError} When type mismatch occurs
74
+ * @throws {RuntimeTypeError} When value is undefined or null (unless optional/nullable)
75
+ * @example
76
+ * ```ts
77
+ * const data: unknown = getData();
78
+ *
79
+ * validate(data, {
80
+ * "a": Boolean,
81
+ * "b": {
82
+ * "c": Number,
83
+ * "d": Optional(String), // string | undefined
84
+ * "e": Nullable(String) // string | null
85
+ * },
86
+ * "items": [{
87
+ * "name": String,
88
+ * "value": Number
89
+ * }]
90
+ * });
91
+ *
92
+ * // From this point, data is type-asserted
93
+ * data.a // boolean
94
+ * data.b.c // number
95
+ * data.b.d // string | undefined
96
+ * data.b.e // string | null
97
+ * data.items[0].name // string
98
+ * ```
99
+ */
100
+ declare function validate<T extends Expression>(target: any, expression: T, path?: string): asserts target is InferType<T>;
101
+
102
+ export { type InferType, Nullable, Optional, RuntimeTypeError, validate };
@@ -0,0 +1,102 @@
1
+ type OptionalMarker = {
2
+ __optional: true;
3
+ };
4
+ type NullableMarker = {
5
+ __nullable: true;
6
+ };
7
+ type MarkerKeys = '__optional' | '__nullable' | '__type';
8
+ type ArraySchema = [PrimitiveType | Expression];
9
+ type Expression = {
10
+ [key: string | number]: PrimitiveType | Expression | ArraySchema | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker) | true;
11
+ };
12
+ type PrimitiveType = NumberConstructor | StringConstructor | BooleanConstructor | ArrayConstructor | ObjectConstructor | DateConstructor;
13
+ 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;
14
+ 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 ArraySchema & OptionalMarker & NullableMarker ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | undefined | null : T extends ArraySchema & OptionalMarker ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | undefined : T extends ArraySchema & NullableMarker ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | null : T extends [infer U] ? U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[] : T extends OptionalMarker & NullableMarker ? {
15
+ [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]>;
16
+ } | undefined | null : T extends OptionalMarker ? {
17
+ [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]>;
18
+ } | undefined : T extends NullableMarker ? {
19
+ [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]>;
20
+ } | null : T extends Record<string, any> ? {
21
+ [K in keyof T]: InferType<T[K]>;
22
+ } : never;
23
+ /**
24
+ * Marks a field as optional.
25
+ * Optional fields pass validation even if they are undefined.
26
+ * The type is inferred as T | undefined.
27
+ * @example
28
+ * ```ts
29
+ * validate(data, {
30
+ * required_field: String, // string (required)
31
+ * optional_field: Optional(String), // string | undefined
32
+ * optional_object: Optional({ // { id: string } | undefined
33
+ * id: String
34
+ * }),
35
+ * optional_array: Optional([String]) // string[] | undefined
36
+ * });
37
+ * ```
38
+ */
39
+ declare function Optional<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & OptionalMarker;
40
+ /**
41
+ * Marks a field as nullable.
42
+ * Nullable fields pass validation even if they are null.
43
+ * The type is inferred as T | null.
44
+ * @example
45
+ * ```ts
46
+ * validate(data, {
47
+ * required_field: String, // string (required)
48
+ * nullable_field: Nullable(String), // string | null
49
+ * nullable_object: Nullable({ // { id: string } | null
50
+ * id: String
51
+ * }),
52
+ * nullable_array: Nullable([String]) // string[] | null
53
+ * });
54
+ * ```
55
+ */
56
+ declare function Nullable<T extends PrimitiveType | Expression | ArraySchema>(ctor: T): T & NullableMarker;
57
+ declare class RuntimeTypeError extends Error {
58
+ constructor(key: string, requiredType: string, actualType: string, actualValue: any, message?: string);
59
+ }
60
+ /**
61
+ * Asserts the type of target according to the expression schema.
62
+ * The expression should be represented using primitive type constructors.
63
+ * Supports nested types and array element type validation.
64
+ *
65
+ * Fields not declared in the expression but present in target are ignored.
66
+ *
67
+ * Fields declared in the expression do not allow undefined or null by default.
68
+ * Wrap with Optional() to allow undefined.
69
+ * Wrap with Nullable() to allow null.
70
+ *
71
+ * @param target - The value to validate
72
+ * @param expression - The schema expression to validate against
73
+ * @throws {RuntimeTypeError} When type mismatch occurs
74
+ * @throws {RuntimeTypeError} When value is undefined or null (unless optional/nullable)
75
+ * @example
76
+ * ```ts
77
+ * const data: unknown = getData();
78
+ *
79
+ * validate(data, {
80
+ * "a": Boolean,
81
+ * "b": {
82
+ * "c": Number,
83
+ * "d": Optional(String), // string | undefined
84
+ * "e": Nullable(String) // string | null
85
+ * },
86
+ * "items": [{
87
+ * "name": String,
88
+ * "value": Number
89
+ * }]
90
+ * });
91
+ *
92
+ * // From this point, data is type-asserted
93
+ * data.a // boolean
94
+ * data.b.c // number
95
+ * data.b.d // string | undefined
96
+ * data.b.e // string | null
97
+ * data.items[0].name // string
98
+ * ```
99
+ */
100
+ declare function validate<T extends Expression>(target: any, expression: T, path?: string): asserts target is InferType<T>;
101
+
102
+ export { type InferType, Nullable, Optional, RuntimeTypeError, validate };
package/dist/index.js ADDED
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Nullable: () => Nullable,
24
+ Optional: () => Optional,
25
+ RuntimeTypeError: () => RuntimeTypeError,
26
+ validate: () => validate
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+ function Optional(ctor) {
30
+ return {
31
+ ...ctor,
32
+ __optional: true,
33
+ __type: ctor
34
+ };
35
+ }
36
+ function Nullable(ctor) {
37
+ return {
38
+ ...ctor,
39
+ __nullable: true,
40
+ __type: ctor
41
+ };
42
+ }
43
+ var RuntimeTypeError = class extends Error {
44
+ constructor(key, requiredType, actualType, actualValue, message) {
45
+ if (message) message += ": ";
46
+ super(`${message || ""}${key} must be ${requiredType}, but got ${actualType}. Actual value: ${actualValue}`);
47
+ }
48
+ };
49
+ function getTypeName(value) {
50
+ if (value === null) return "null";
51
+ if (value === void 0) return "undefined";
52
+ if (Array.isArray(value)) return "Array";
53
+ if (typeof value === "number" && Number.isNaN(value)) return "NaN";
54
+ if (typeof value === "object") return "Object";
55
+ const type = typeof value;
56
+ return type.charAt(0).toUpperCase() + type.slice(1);
57
+ }
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
+ ]);
66
+ function isValidateExpression(ctor) {
67
+ return typeof ctor === "object" && !Array.isArray(ctor);
68
+ }
69
+ function isArraySchema(ctor) {
70
+ return Array.isArray(ctor) && ctor.length === 1;
71
+ }
72
+ function isOptional(ctor) {
73
+ return ctor.__optional === true;
74
+ }
75
+ function isNullable(ctor) {
76
+ return ctor.__nullable === true;
77
+ }
78
+ function isBothOptionalAndNullable(ctor) {
79
+ return ctor.__optional === true && ctor.__nullable === true;
80
+ }
81
+ function unwrapOptionalNullable(ctor) {
82
+ while (ctor && typeof ctor === "object" && !Array.isArray(ctor) && ctor.__type) {
83
+ ctor = ctor.__type;
84
+ }
85
+ return ctor;
86
+ }
87
+ function assertType(key, expectedType, value, isValid) {
88
+ if (!isValid) {
89
+ throw new RuntimeTypeError(key, expectedType, getTypeName(value), value);
90
+ }
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
+ }
114
+ function validate(target, expression, path = "") {
115
+ for (const key in expression) {
116
+ if (!Object.prototype.hasOwnProperty.call(expression, key)) continue;
117
+ const ctor = expression[key];
118
+ const value = target[key];
119
+ const currentPath = path ? `${path}.${key}` : key;
120
+ if (isBothOptionalAndNullable(ctor)) {
121
+ if (value === void 0 || value === null) {
122
+ continue;
123
+ }
124
+ } else if (value === void 0 && isOptional(ctor)) {
125
+ continue;
126
+ } else if (value === null && isNullable(ctor)) {
127
+ continue;
128
+ } else {
129
+ assertType(currentPath, "not undefined or null", value, value !== void 0 && value !== null);
130
+ }
131
+ const unwrappedCtor = unwrapOptionalNullable(ctor);
132
+ if (isArraySchema(unwrappedCtor)) {
133
+ assertType(currentPath, "Array", value, Array.isArray(value));
134
+ const elementSchema = unwrappedCtor[0];
135
+ const arr = value;
136
+ for (let i = 0; i < arr.length; i++) {
137
+ validateArrayElement(arr[i], elementSchema, `${currentPath}[${i}]`);
138
+ }
139
+ continue;
140
+ }
141
+ if (typeof unwrappedCtor === "function") {
142
+ const validator = TYPE_VALIDATORS.get(unwrappedCtor);
143
+ if (!validator) {
144
+ throw new Error("Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.");
145
+ }
146
+ assertType(currentPath, unwrappedCtor.name, value, validator(value));
147
+ } else if (isValidateExpression(unwrappedCtor)) {
148
+ const objectValidator = TYPE_VALIDATORS.get(Object);
149
+ assertType(currentPath, "Object", value, objectValidator(value));
150
+ validate(value, unwrappedCtor, currentPath);
151
+ } else {
152
+ throw new Error("Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.");
153
+ }
154
+ }
155
+ }
156
+ // Annotate the CommonJS export names for ESM import in node:
157
+ 0 && (module.exports = {
158
+ Nullable,
159
+ Optional,
160
+ RuntimeTypeError,
161
+ validate
162
+ });
163
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["type OptionalMarker = { __optional: true };\ntype NullableMarker = { __nullable: true };\ntype MarkerKeys = '__optional' | '__nullable' | '__type';\n\ntype ArraySchema = [PrimitiveType | Expression];\n\ntype Expression = {\n [key: string | number]: PrimitiveType | Expression | ArraySchema | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker) | true\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\nexport type 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 ArraySchema & OptionalMarker & NullableMarker\n ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | undefined | null\n : T extends ArraySchema & OptionalMarker\n ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | undefined\n : T extends ArraySchema & NullableMarker\n ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | null\n : T extends [infer U]\n ? U extends PrimitiveType\n ? InferPrimitiveType<U>[]\n : InferType<U>[]\n : T extends OptionalMarker & NullableMarker\n ? { [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]> } | undefined | null\n : T extends OptionalMarker\n ? { [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]> } | undefined\n : T extends NullableMarker\n ? { [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]> } | null\n : T extends Record<string, any>\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkEO,SAAS,SAA6D,MAA6B;AACtG,SAAO;AAAA,IACH,GAAG;AAAA,IACH,YAAY;AAAA,IACZ,QAAQ;AAAA,EACZ;AACJ;AAkBO,SAAS,SAA6D,MAA6B;AACtG,SAAO;AAAA,IACH,GAAG;AAAA,IACH,YAAY;AAAA,IACZ,QAAQ;AAAA,EACZ;AACJ;AAEO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EACxC,YACI,KACA,cACA,YACA,aACA,SACF;AACE,QAAI,QAAS,YAAW;AACxB,UAAM,GAAG,WAAW,EAAE,GAAG,GAAG,YAAY,YAAY,aAAa,UAAU,mBAAmB,WAAW,EAAE;AAAA,EAC/G;AACJ;AAEA,SAAS,YAAY,OAAoB;AACrC,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,UAAU,OAAW,QAAO;AAChC,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,MAAI,OAAO,UAAU,YAAY,OAAO,MAAM,KAAK,EAAG,QAAO;AAC7D,MAAI,OAAO,UAAU,SAAU,QAAO;AAEtC,QAAM,OAAO,OAAO;AACpB,SAAO,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC;AACtD;AAEA,IAAM,kBAAkB,oBAAI,IAAuC;AAAA,EAC/D,CAAC,QAAQ,CAAC,UAAU,OAAO,UAAU,YAAY,CAAC,OAAO,MAAM,KAAK,CAAC;AAAA,EACrE,CAAC,QAAQ,CAAC,UAAU,OAAO,UAAU,QAAQ;AAAA,EAC7C,CAAC,SAAS,CAAC,UAAU,OAAO,UAAU,SAAS;AAAA,EAC/C,CAAC,OAAO,CAAC,UAAU,MAAM,QAAQ,KAAK,CAAC;AAAA,EACvC,CAAC,QAAQ,CAAC,UAAU,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,KAAK,EAAE,iBAAiB,KAAK;AAAA,EACpH,CAAC,MAAM,CAAC,UAAU,iBAAiB,QAAQ,CAAC,OAAO,MAAM,MAAM,QAAQ,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,qBAAqB,MAA+B;AACzD,SAAO,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI;AAC1D;AAEA,SAAS,cAAc,MAAiD;AACpE,SAAO,MAAM,QAAQ,IAAI,KAAK,KAAK,WAAW;AAClD;AAEA,SAAS,WAAW,MAAoB;AACpC,SAAO,KAAK,eAAe;AAC/B;AAEA,SAAS,WAAW,MAAoB;AACpC,SAAO,KAAK,eAAe;AAC/B;AAEA,SAAS,0BAA0B,MAAoB;AACnD,SAAO,KAAK,eAAe,QAAQ,KAAK,eAAe;AAC3D;AAEA,SAAS,uBAAuB,MAAgB;AAC5C,SAAO,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,KAAK,KAAK,QAAQ;AAC5E,WAAO,KAAK;AAAA,EAChB;AACA,SAAO;AACX;AAEA,SAAS,WAAW,KAAa,cAAsB,OAAY,SAAwB;AACvF,MAAI,CAAC,SAAS;AACV,UAAM,IAAI,iBAAiB,KAAK,cAAc,YAAY,KAAK,GAAG,KAAK;AAAA,EAC3E;AACJ;AAEA,SAAS,qBAAqB,MAAW,QAAa,MAAoB;AACtE,MAAI,cAAc,MAAM,GAAG;AACvB,eAAW,MAAM,SAAS,MAAM,MAAM,QAAQ,IAAI,CAAC;AACnD,UAAM,cAAc,OAAO,CAAC;AAC5B,UAAM,MAAM;AACZ,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACjC,2BAAqB,IAAI,CAAC,GAAG,aAAa,GAAG,IAAI,IAAI,CAAC,GAAG;AAAA,IAC7D;AAAA,EACJ,WAAW,qBAAqB,MAAM,GAAG;AACrC,UAAM,kBAAkB,gBAAgB,IAAI,MAAM;AAClD,eAAW,MAAM,UAAU,MAAM,kBAAkB,IAAI,KAAK,KAAK;AACjE,aAAS,MAAM,QAAQ,IAAI;AAAA,EAC/B,WAAW,OAAO,WAAW,YAAY;AACrC,UAAM,YAAY,gBAAgB,IAAI,MAAM;AAC5C,QAAI,CAAC,WAAW;AACZ,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACvD;AACA,eAAW,MAAM,OAAO,MAAM,MAAM,UAAU,IAAI,CAAC;AAAA,EACvD,OAAO;AACH,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACvD;AACJ;AA0CO,SAAS,SACZ,QACA,YACA,OAAe,IACe;AAC9B,aAAW,OAAO,YAAY;AAC1B,QAAI,CAAC,OAAO,UAAU,eAAe,KAAK,YAAY,GAAG,EAAG;AAE5D,UAAM,OAAO,WAAW,GAAG;AAC3B,UAAM,QAAQ,OAAO,GAAG;AACxB,UAAM,cAAc,OAAO,GAAG,IAAI,IAAI,GAAG,KAAK;AAE9C,QAAI,0BAA0B,IAAI,GAAG;AACjC,UAAI,UAAU,UAAa,UAAU,MAAM;AACvC;AAAA,MACJ;AAAA,IACJ,WAAW,UAAU,UAAa,WAAW,IAAI,GAAG;AAChD;AAAA,IACJ,WAAW,UAAU,QAAQ,WAAW,IAAI,GAAG;AAC3C;AAAA,IACJ,OAAO;AACH,iBAAW,aAAa,yBAAyB,OAAO,UAAU,UAAa,UAAU,IAAI;AAAA,IACjG;AAEA,UAAM,gBAAgB,uBAAuB,IAAI;AAEjD,QAAI,cAAc,aAAa,GAAG;AAC9B,iBAAW,aAAa,SAAS,OAAO,MAAM,QAAQ,KAAK,CAAC;AAC5D,YAAM,gBAAgB,cAAc,CAAC;AACrC,YAAM,MAAM;AAEZ,eAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACjC,6BAAqB,IAAI,CAAC,GAAG,eAAe,GAAG,WAAW,IAAI,CAAC,GAAG;AAAA,MACtE;AACA;AAAA,IACJ;AAEA,QAAI,OAAO,kBAAkB,YAAY;AACrC,YAAM,YAAY,gBAAgB,IAAI,aAAa;AACnD,UAAI,CAAC,WAAW;AACZ,cAAM,IAAI,MAAM,mFAAmF;AAAA,MACvG;AACA,iBAAW,aAAa,cAAc,MAAM,OAAO,UAAU,KAAK,CAAC;AAAA,IACvE,WAAW,qBAAqB,aAAa,GAAG;AAC5C,YAAM,kBAAkB,gBAAgB,IAAI,MAAM;AAClD,iBAAW,aAAa,UAAU,OAAO,gBAAgB,KAAK,CAAC;AAC/D,eAAS,OAAO,eAAe,WAAW;AAAA,IAC9C,OAAO;AACH,YAAM,IAAI,MAAM,mFAAmF;AAAA,IACvG;AAAA,EACJ;AACJ;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,135 @@
1
+ // src/index.ts
2
+ function Optional(ctor) {
3
+ return {
4
+ ...ctor,
5
+ __optional: true,
6
+ __type: ctor
7
+ };
8
+ }
9
+ function Nullable(ctor) {
10
+ return {
11
+ ...ctor,
12
+ __nullable: true,
13
+ __type: ctor
14
+ };
15
+ }
16
+ var RuntimeTypeError = class extends Error {
17
+ constructor(key, requiredType, actualType, actualValue, message) {
18
+ if (message) message += ": ";
19
+ super(`${message || ""}${key} must be ${requiredType}, but got ${actualType}. Actual value: ${actualValue}`);
20
+ }
21
+ };
22
+ function getTypeName(value) {
23
+ if (value === null) return "null";
24
+ if (value === void 0) return "undefined";
25
+ if (Array.isArray(value)) return "Array";
26
+ if (typeof value === "number" && Number.isNaN(value)) return "NaN";
27
+ if (typeof value === "object") return "Object";
28
+ const type = typeof value;
29
+ return type.charAt(0).toUpperCase() + type.slice(1);
30
+ }
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
+ ]);
39
+ function isValidateExpression(ctor) {
40
+ return typeof ctor === "object" && !Array.isArray(ctor);
41
+ }
42
+ function isArraySchema(ctor) {
43
+ return Array.isArray(ctor) && ctor.length === 1;
44
+ }
45
+ function isOptional(ctor) {
46
+ return ctor.__optional === true;
47
+ }
48
+ function isNullable(ctor) {
49
+ return ctor.__nullable === true;
50
+ }
51
+ function isBothOptionalAndNullable(ctor) {
52
+ return ctor.__optional === true && ctor.__nullable === true;
53
+ }
54
+ function unwrapOptionalNullable(ctor) {
55
+ while (ctor && typeof ctor === "object" && !Array.isArray(ctor) && ctor.__type) {
56
+ ctor = ctor.__type;
57
+ }
58
+ return ctor;
59
+ }
60
+ function assertType(key, expectedType, value, isValid) {
61
+ if (!isValid) {
62
+ throw new RuntimeTypeError(key, expectedType, getTypeName(value), value);
63
+ }
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
+ }
87
+ function validate(target, expression, path = "") {
88
+ for (const key in expression) {
89
+ if (!Object.prototype.hasOwnProperty.call(expression, key)) continue;
90
+ const ctor = expression[key];
91
+ const value = target[key];
92
+ const currentPath = path ? `${path}.${key}` : key;
93
+ if (isBothOptionalAndNullable(ctor)) {
94
+ if (value === void 0 || value === null) {
95
+ continue;
96
+ }
97
+ } else if (value === void 0 && isOptional(ctor)) {
98
+ continue;
99
+ } else if (value === null && isNullable(ctor)) {
100
+ continue;
101
+ } else {
102
+ assertType(currentPath, "not undefined or null", value, value !== void 0 && value !== null);
103
+ }
104
+ const unwrappedCtor = unwrapOptionalNullable(ctor);
105
+ if (isArraySchema(unwrappedCtor)) {
106
+ assertType(currentPath, "Array", value, Array.isArray(value));
107
+ const elementSchema = unwrappedCtor[0];
108
+ const arr = value;
109
+ for (let i = 0; i < arr.length; i++) {
110
+ validateArrayElement(arr[i], elementSchema, `${currentPath}[${i}]`);
111
+ }
112
+ continue;
113
+ }
114
+ if (typeof unwrappedCtor === "function") {
115
+ const validator = TYPE_VALIDATORS.get(unwrappedCtor);
116
+ if (!validator) {
117
+ throw new Error("Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.");
118
+ }
119
+ assertType(currentPath, unwrappedCtor.name, value, validator(value));
120
+ } else if (isValidateExpression(unwrappedCtor)) {
121
+ const objectValidator = TYPE_VALIDATORS.get(Object);
122
+ assertType(currentPath, "Object", value, objectValidator(value));
123
+ validate(value, unwrappedCtor, currentPath);
124
+ } else {
125
+ throw new Error("Invalid expression. Use 'Number' or 'String' or 'Boolean' or 'Array' or 'Object'.");
126
+ }
127
+ }
128
+ }
129
+ export {
130
+ Nullable,
131
+ Optional,
132
+ RuntimeTypeError,
133
+ validate
134
+ };
135
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["type OptionalMarker = { __optional: true };\ntype NullableMarker = { __nullable: true };\ntype MarkerKeys = '__optional' | '__nullable' | '__type';\n\ntype ArraySchema = [PrimitiveType | Expression];\n\ntype Expression = {\n [key: string | number]: PrimitiveType | Expression | ArraySchema | (PrimitiveType & OptionalMarker) | (PrimitiveType & NullableMarker) | (PrimitiveType & OptionalMarker & NullableMarker) | (Expression & OptionalMarker) | (Expression & NullableMarker) | (Expression & OptionalMarker & NullableMarker) | (ArraySchema & OptionalMarker) | (ArraySchema & NullableMarker) | (ArraySchema & OptionalMarker & NullableMarker) | true\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\nexport type 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 ArraySchema & OptionalMarker & NullableMarker\n ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | undefined | null\n : T extends ArraySchema & OptionalMarker\n ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | undefined\n : T extends ArraySchema & NullableMarker\n ? (T extends [infer U] ? (U extends PrimitiveType ? InferPrimitiveType<U>[] : InferType<U>[]) : never) | null\n : T extends [infer U]\n ? U extends PrimitiveType\n ? InferPrimitiveType<U>[]\n : InferType<U>[]\n : T extends OptionalMarker & NullableMarker\n ? { [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]> } | undefined | null\n : T extends OptionalMarker\n ? { [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]> } | undefined\n : T extends NullableMarker\n ? { [K in keyof T as K extends MarkerKeys ? never : K]: InferType<T[K]> } | null\n : T extends Record<string, any>\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 ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@point3/valdex",
3
+ "version": "1.0.4",
4
+ "description": "Runtime type validation with TypeScript type inference",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": {
11
+ "types": "./dist/index.d.mts",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "require": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "prepublishOnly": "npm run build",
26
+ "test": "jest",
27
+ "test:types": "tsd",
28
+ "test:all": "npm test && npm run test:types"
29
+ },
30
+ "tsd": {
31
+ "directory": "src",
32
+ "typingsFile": "src/index.ts"
33
+ },
34
+ "keywords": [
35
+ "typescript",
36
+ "validation",
37
+ "runtime",
38
+ "type-inference",
39
+ "schema",
40
+ "validator"
41
+ ],
42
+ "author": "point3 (violetpay)",
43
+ "license": "MIT",
44
+ "devDependencies": {
45
+ "@types/jest": "^30.0.0",
46
+ "jest": "^30.2.0",
47
+ "ts-jest": "^29.4.6",
48
+ "tsd": "^0.33.0",
49
+ "tsup": "^8.3.5",
50
+ "tsx": "^4.19.2",
51
+ "typescript": "^5.7.2"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/asheswook/valdex.git"
56
+ },
57
+ "bugs": {
58
+ "url": "https://github.com/asheswook/valdex/issues"
59
+ },
60
+ "homepage": "https://github.com/asheswook/valdex#readme"
61
+ }