nana 1.2.0 → 2.0.0
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/CHANGELOG.md +7 -0
- package/README.md +43 -1
- package/dist/cjs/index.cjs +202 -192
- package/dist/cjs/index.d.cts +70 -0
- package/dist/esm/index.d.mts +70 -0
- package/dist/esm/index.mjs +209 -0
- package/package.json +23 -14
- package/dist/esm/index.js +0 -179
- package/types/index.d.ts +0 -69
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
A minimal validator for any JavaScript environment.
|
|
8
8
|
|
|
9
|
-
- ~
|
|
9
|
+
- ~360 lines of code, 0 dependencies
|
|
10
10
|
- Only 3 concepts: `pipe`, `transform`, `check`
|
|
11
11
|
- Rich error context: `expected/actual/path/key/parent/root`
|
|
12
12
|
|
|
@@ -48,6 +48,7 @@ console.log(
|
|
|
48
48
|
actual: 'God',
|
|
49
49
|
path: '$.job.title',
|
|
50
50
|
key: 'title',
|
|
51
|
+
message: 'You cannot be God',
|
|
51
52
|
parent: { title: 'God' },
|
|
52
53
|
root: { name: 'nana', job: [Object] }
|
|
53
54
|
},
|
|
@@ -173,6 +174,47 @@ console.log(
|
|
|
173
174
|
*/
|
|
174
175
|
```
|
|
175
176
|
|
|
177
|
+
## validateAll (collect all errors)
|
|
178
|
+
|
|
179
|
+
Unlike `validate` which stops at the first error, `validateAll` collects all validation errors:
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
import { string, number, object, validateAll } from 'nana'
|
|
183
|
+
|
|
184
|
+
const userSchema = object({
|
|
185
|
+
name: string(),
|
|
186
|
+
age: number()
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const result = validateAll(userSchema, { name: 2, age: 'nana' })
|
|
190
|
+
|
|
191
|
+
/*
|
|
192
|
+
{
|
|
193
|
+
valid: false,
|
|
194
|
+
errors: [
|
|
195
|
+
Error: ($.name: 2) ✖ string,
|
|
196
|
+
Error: ($.age: nana) ✖ number
|
|
197
|
+
],
|
|
198
|
+
result: { name: 2, age: 'nana' }
|
|
199
|
+
}
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
## TypeScript
|
|
203
|
+
|
|
204
|
+
Use `InferOutput` to extract the output type from a schema:
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { string, number, object, InferOutput } from 'nana'
|
|
208
|
+
|
|
209
|
+
const userSchema = object({
|
|
210
|
+
name: string(),
|
|
211
|
+
age: number()
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
type User = InferOutput<typeof userSchema>
|
|
215
|
+
// type User = { name: string; age: number }
|
|
216
|
+
```
|
|
217
|
+
|
|
176
218
|
## With third party validator
|
|
177
219
|
|
|
178
220
|
```js
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,217 +1,227 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
makeCtx: () => makeCtx,
|
|
27
|
-
number: () => number,
|
|
28
|
-
object: () => object,
|
|
29
|
-
optional: () => optional,
|
|
30
|
-
pipe: () => pipe,
|
|
31
|
-
required: () => required,
|
|
32
|
-
string: () => string,
|
|
33
|
-
symbol: () => symbol,
|
|
34
|
-
transform: () => transform,
|
|
35
|
-
validate: () => validate
|
|
36
|
-
});
|
|
37
|
-
module.exports = __toCommonJS(index_exports);
|
|
38
|
-
function makeCtx(parentCtx, key, value) {
|
|
39
|
-
let path;
|
|
40
|
-
if (!parentCtx) path = "$";
|
|
41
|
-
else if (typeof key === "number") path = `${parentCtx.path}[${key}]`;
|
|
42
|
-
else path = `${parentCtx.path}.${key}`;
|
|
43
|
-
return {
|
|
44
|
-
path,
|
|
45
|
-
key,
|
|
46
|
-
parent: parentCtx ? parentCtx.value : null,
|
|
47
|
-
root: parentCtx ? parentCtx.root : value,
|
|
48
|
-
value
|
|
49
|
-
};
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
|
|
3
|
+
//#region index.ts
|
|
4
|
+
const DEFAULT_ROOT_PATH = "$";
|
|
5
|
+
function collectError(ctx, error) {
|
|
6
|
+
if (!ctx.__errors) ctx.__errors = [];
|
|
7
|
+
ctx.__errors.push(error);
|
|
8
|
+
}
|
|
9
|
+
function attachCollector(parentCtx, childCtx) {
|
|
10
|
+
if (!parentCtx.__collectErrors) return;
|
|
11
|
+
childCtx.__collectErrors = true;
|
|
12
|
+
childCtx.__errors = parentCtx.__errors;
|
|
13
|
+
}
|
|
14
|
+
function makeCtx(parentCtx, key, value, root) {
|
|
15
|
+
let path;
|
|
16
|
+
if (!parentCtx) path = DEFAULT_ROOT_PATH;
|
|
17
|
+
else if (typeof key === "number") path = `${parentCtx.path}[${key}]`;
|
|
18
|
+
else path = `${parentCtx.path}.${key}`;
|
|
19
|
+
return {
|
|
20
|
+
path,
|
|
21
|
+
key,
|
|
22
|
+
parent: parentCtx ? parentCtx.value : null,
|
|
23
|
+
root: parentCtx ? parentCtx.root : value,
|
|
24
|
+
value
|
|
25
|
+
};
|
|
50
26
|
}
|
|
51
27
|
function formatValue(value) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (value instanceof Date) return value.toISOString();
|
|
63
|
-
if (Array.isArray(value) || value && Object.getPrototypeOf(value) === Object.prototype) {
|
|
64
|
-
return JSON.stringify(value);
|
|
65
|
-
}
|
|
66
|
-
return Object.prototype.toString.call(value);
|
|
28
|
+
if (value === null || value === void 0) return String(value);
|
|
29
|
+
const type = typeof value;
|
|
30
|
+
if (type === "string") return value;
|
|
31
|
+
if (type === "number" || type === "boolean" || type === "bigint") return String(value);
|
|
32
|
+
if (type === "symbol") return value.toString();
|
|
33
|
+
if (type === "function") return `[Function${value.name ? `: ${value.name}` : ""}]`;
|
|
34
|
+
if (value instanceof RegExp) return value.toString();
|
|
35
|
+
if (value instanceof Date) return value.toISOString();
|
|
36
|
+
if (Array.isArray(value) || value && Object.getPrototypeOf(value) === Object.prototype) return JSON.stringify(value);
|
|
37
|
+
return Object.prototype.toString.call(value);
|
|
67
38
|
}
|
|
68
39
|
function createValidator(name, handler) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
40
|
+
return function validatorFactory(...args) {
|
|
41
|
+
return function validatorFn(value, ctx) {
|
|
42
|
+
try {
|
|
43
|
+
return handler(value, ctx, args);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
46
|
+
if (!error.expected) {
|
|
47
|
+
error.expected = name;
|
|
48
|
+
error.actual = value;
|
|
49
|
+
error.path = ctx.path;
|
|
50
|
+
error.key = ctx.key;
|
|
51
|
+
error.parent = ctx.parent;
|
|
52
|
+
error.root = ctx.root;
|
|
53
|
+
}
|
|
54
|
+
if (ctx.__collectErrors) {
|
|
55
|
+
collectError(ctx, error);
|
|
56
|
+
ctx.__abortPipe = true;
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
};
|
|
87
63
|
}
|
|
88
64
|
function pipe(...validators) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
65
|
+
return createValidator("pipe", (value, ctx) => {
|
|
66
|
+
for (const validator of validators) {
|
|
67
|
+
if (ctx.__abortPipe) break;
|
|
68
|
+
value = validator(value, ctx);
|
|
69
|
+
}
|
|
70
|
+
delete ctx.__abortPipe;
|
|
71
|
+
return value;
|
|
72
|
+
})();
|
|
97
73
|
}
|
|
98
74
|
function transform(fn) {
|
|
99
|
-
|
|
75
|
+
return createValidator("transform", (value, ctx) => fn(value, ctx))();
|
|
100
76
|
}
|
|
101
77
|
function check(fn, msg) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
})();
|
|
78
|
+
const expected = fn.name || "check";
|
|
79
|
+
return createValidator(expected, (value, ctx) => {
|
|
80
|
+
if (fn(value, ctx) === true) return value;
|
|
81
|
+
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ ${expected}`);
|
|
82
|
+
})();
|
|
108
83
|
}
|
|
109
|
-
function validate(schema, value, rootPath =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
84
|
+
function validate(schema, value, rootPath = DEFAULT_ROOT_PATH) {
|
|
85
|
+
const ctx = {
|
|
86
|
+
path: rootPath,
|
|
87
|
+
key: null,
|
|
88
|
+
parent: null,
|
|
89
|
+
root: value,
|
|
90
|
+
value
|
|
91
|
+
};
|
|
92
|
+
try {
|
|
93
|
+
return {
|
|
94
|
+
valid: true,
|
|
95
|
+
result: schema(value, ctx)
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return {
|
|
99
|
+
valid: false,
|
|
100
|
+
error,
|
|
101
|
+
result: value
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function validateAll(schema, value, rootPath = DEFAULT_ROOT_PATH) {
|
|
106
|
+
const ctx = {
|
|
107
|
+
path: rootPath,
|
|
108
|
+
key: null,
|
|
109
|
+
parent: null,
|
|
110
|
+
root: value,
|
|
111
|
+
value,
|
|
112
|
+
__collectErrors: true,
|
|
113
|
+
__errors: []
|
|
114
|
+
};
|
|
115
|
+
let result = value;
|
|
116
|
+
try {
|
|
117
|
+
result = schema(value, ctx);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
collectError(ctx, error);
|
|
120
|
+
}
|
|
121
|
+
const errors = ctx.__errors ?? [];
|
|
122
|
+
if (errors.length > 0) return {
|
|
123
|
+
valid: false,
|
|
124
|
+
errors,
|
|
125
|
+
result: value
|
|
126
|
+
};
|
|
127
|
+
return {
|
|
128
|
+
valid: true,
|
|
129
|
+
result,
|
|
130
|
+
errors: []
|
|
131
|
+
};
|
|
127
132
|
}
|
|
128
133
|
const required = createValidator("required", (value, ctx, args) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
return value;
|
|
134
|
+
const [msg] = args;
|
|
135
|
+
if (value == null) throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ required`);
|
|
136
|
+
return value;
|
|
134
137
|
});
|
|
135
|
-
const optional = createValidator("optional", (value, ctx
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
const optional = createValidator("optional", (value, ctx) => {
|
|
139
|
+
if (value == null) {
|
|
140
|
+
ctx.__abortPipe = true;
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
141
144
|
});
|
|
142
145
|
const string = createValidator("string", (value, ctx, args) => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
return value;
|
|
146
|
+
const [msg] = args;
|
|
147
|
+
if (typeof value !== "string") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ string`);
|
|
148
|
+
return value;
|
|
148
149
|
});
|
|
149
150
|
const number = createValidator("number", (value, ctx, args) => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
return value;
|
|
151
|
+
const [msg] = args;
|
|
152
|
+
if (typeof value !== "number") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ number`);
|
|
153
|
+
return value;
|
|
155
154
|
});
|
|
156
155
|
const bigint = createValidator("bigint", (value, ctx, args) => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
return value;
|
|
156
|
+
const [msg] = args;
|
|
157
|
+
if (typeof value !== "bigint") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ bigint`);
|
|
158
|
+
return value;
|
|
162
159
|
});
|
|
163
160
|
const boolean = createValidator("boolean", (value, ctx, args) => {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
return value;
|
|
161
|
+
const [msg] = args;
|
|
162
|
+
if (typeof value !== "boolean") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ boolean`);
|
|
163
|
+
return value;
|
|
169
164
|
});
|
|
170
165
|
const symbol = createValidator("symbol", (value, ctx, args) => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
return value;
|
|
176
|
-
});
|
|
177
|
-
const object = createValidator("object", (value, ctx, args) => {
|
|
178
|
-
const [obj, msg] = args;
|
|
179
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
180
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 object`);
|
|
181
|
-
}
|
|
182
|
-
const result = {};
|
|
183
|
-
for (const key in obj) {
|
|
184
|
-
const childCtx = makeCtx(ctx, key, value[key]);
|
|
185
|
-
result[key] = obj[key](value[key], childCtx);
|
|
186
|
-
}
|
|
187
|
-
return result;
|
|
188
|
-
});
|
|
189
|
-
const array = createValidator("array", (value, ctx, args) => {
|
|
190
|
-
const [validator, msg] = args;
|
|
191
|
-
if (!Array.isArray(value)) {
|
|
192
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 array`);
|
|
193
|
-
}
|
|
194
|
-
return value.map((item, i) => {
|
|
195
|
-
const childCtx = makeCtx(ctx, i, item);
|
|
196
|
-
return validator(item, childCtx);
|
|
197
|
-
});
|
|
198
|
-
});
|
|
199
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
200
|
-
0 && (module.exports = {
|
|
201
|
-
array,
|
|
202
|
-
bigint,
|
|
203
|
-
boolean,
|
|
204
|
-
check,
|
|
205
|
-
createValidator,
|
|
206
|
-
formatValue,
|
|
207
|
-
makeCtx,
|
|
208
|
-
number,
|
|
209
|
-
object,
|
|
210
|
-
optional,
|
|
211
|
-
pipe,
|
|
212
|
-
required,
|
|
213
|
-
string,
|
|
214
|
-
symbol,
|
|
215
|
-
transform,
|
|
216
|
-
validate
|
|
166
|
+
const [msg] = args;
|
|
167
|
+
if (typeof value !== "symbol") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ symbol`);
|
|
168
|
+
return value;
|
|
217
169
|
});
|
|
170
|
+
function object(shape, msg) {
|
|
171
|
+
return createValidator("object", (value, ctx) => {
|
|
172
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ object`);
|
|
173
|
+
const result = {};
|
|
174
|
+
for (const key in shape) {
|
|
175
|
+
const childCtx = makeCtx(ctx, key, value[key]);
|
|
176
|
+
attachCollector(ctx, childCtx);
|
|
177
|
+
try {
|
|
178
|
+
result[key] = shape[key](value[key], childCtx);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (ctx.__collectErrors) {
|
|
181
|
+
collectError(ctx, error);
|
|
182
|
+
result[key] = value[key];
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
})();
|
|
190
|
+
}
|
|
191
|
+
function array(validator, msg) {
|
|
192
|
+
return createValidator("array", (value, ctx) => {
|
|
193
|
+
if (!Array.isArray(value)) throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ array`);
|
|
194
|
+
return value.map((item, i) => {
|
|
195
|
+
const childCtx = makeCtx(ctx, i, item);
|
|
196
|
+
attachCollector(ctx, childCtx);
|
|
197
|
+
try {
|
|
198
|
+
return validator(item, childCtx);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
if (ctx.__collectErrors) {
|
|
201
|
+
collectError(ctx, error);
|
|
202
|
+
return item;
|
|
203
|
+
}
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
})();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
211
|
+
exports.array = array;
|
|
212
|
+
exports.bigint = bigint;
|
|
213
|
+
exports.boolean = boolean;
|
|
214
|
+
exports.check = check;
|
|
215
|
+
exports.createValidator = createValidator;
|
|
216
|
+
exports.formatValue = formatValue;
|
|
217
|
+
exports.makeCtx = makeCtx;
|
|
218
|
+
exports.number = number;
|
|
219
|
+
exports.object = object;
|
|
220
|
+
exports.optional = optional;
|
|
221
|
+
exports.pipe = pipe;
|
|
222
|
+
exports.required = required;
|
|
223
|
+
exports.string = string;
|
|
224
|
+
exports.symbol = symbol;
|
|
225
|
+
exports.transform = transform;
|
|
226
|
+
exports.validate = validate;
|
|
227
|
+
exports.validateAll = validateAll;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//#region index.d.ts
|
|
2
|
+
interface Ctx {
|
|
3
|
+
path: string;
|
|
4
|
+
key: string | number | null;
|
|
5
|
+
parent: any;
|
|
6
|
+
root: any;
|
|
7
|
+
__abortPipe?: boolean;
|
|
8
|
+
__collectErrors?: boolean;
|
|
9
|
+
__errors?: ValidationError[];
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
type Validator<Input = any, Output = Input> = (value: Input, ctx: Ctx) => Output;
|
|
13
|
+
interface ValidationError extends Error {
|
|
14
|
+
expected?: string;
|
|
15
|
+
actual?: any;
|
|
16
|
+
path?: string;
|
|
17
|
+
key?: string | number | null;
|
|
18
|
+
parent?: any;
|
|
19
|
+
root?: any;
|
|
20
|
+
}
|
|
21
|
+
type ValidatorFactory<Input = any, Output = Input, Args extends any[] = any[]> = (...args: Args) => Validator<Input, Output>;
|
|
22
|
+
declare function makeCtx(parentCtx: Ctx | null, key: string | number | null, value: any, root?: any): Ctx;
|
|
23
|
+
declare function formatValue(value: any): string;
|
|
24
|
+
declare function createValidator<I = unknown, O = I, Args extends any[] = any[]>(name: string, handler: (value: I, ctx: Ctx, args: Args) => O): (...args: Args) => Validator<I, O>;
|
|
25
|
+
interface ControlValidator {
|
|
26
|
+
__control?: true;
|
|
27
|
+
}
|
|
28
|
+
type IsControl<V> = V extends ControlValidator ? true : false;
|
|
29
|
+
type InputOf<V> = V extends ((value: infer I, ctx: Ctx) => any) ? I : never;
|
|
30
|
+
type OutputOf<V> = V extends ((value: any, ctx: Ctx) => infer O) ? O : never;
|
|
31
|
+
type Simplify<T> = { [K in keyof T]: T[K] } & {};
|
|
32
|
+
type DeepSimplify<T> = T extends string | number | boolean | bigint | symbol | null | undefined ? T : T extends Function ? T : T extends (infer U)[] ? Array<DeepSimplify<U>> : T extends object ? Simplify<{ [K in keyof T]: DeepSimplify<T[K]> }> : T;
|
|
33
|
+
type InferOutput<V> = V extends ((value: any, ctx: Ctx) => infer O) ? DeepSimplify<O> : never;
|
|
34
|
+
type Last<T extends unknown[]> = T extends [...any[], infer L] ? L : never;
|
|
35
|
+
type ValidateChain<Vs extends unknown[]> = Vs extends [infer V1, infer V2, ...infer Rest] ? IsControl<V1> extends true ? [V1, ...ValidateChain<[V2, ...Rest]>] : OutputOf<V1> extends InputOf<V2> ? [V1, ...ValidateChain<[V2, ...Rest]>] : never : Vs;
|
|
36
|
+
declare function pipe<Vs extends [Validator<any, any>, ...(Validator<any, any>)[]]>(...validators: ValidateChain<Vs>): Validator<InputOf<Vs[0]>, OutputOf<Last<Vs>>>;
|
|
37
|
+
declare function transform<I = any, O = any>(fn: (value: I, ctx: Ctx) => O): Validator<I, O>;
|
|
38
|
+
declare function check<I = any>(fn: (value: I, ctx: Ctx) => boolean, msg?: string): Validator<I, I>;
|
|
39
|
+
type ValidateResult<O> = {
|
|
40
|
+
valid: true;
|
|
41
|
+
result: O;
|
|
42
|
+
error?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
valid: false;
|
|
45
|
+
error: ValidationError;
|
|
46
|
+
result: any;
|
|
47
|
+
};
|
|
48
|
+
declare function validate<I, O>(schema: Validator<I, O>, value: I, rootPath?: string): ValidateResult<O>;
|
|
49
|
+
type ValidateAllResult<O> = {
|
|
50
|
+
valid: true;
|
|
51
|
+
result: O;
|
|
52
|
+
errors: [];
|
|
53
|
+
} | {
|
|
54
|
+
valid: false;
|
|
55
|
+
result: any;
|
|
56
|
+
errors: ValidationError[];
|
|
57
|
+
};
|
|
58
|
+
declare function validateAll<I, O>(schema: Validator<I, O>, value: I, rootPath?: string): ValidateAllResult<O>;
|
|
59
|
+
declare const required: (args_0?: string | undefined) => Validator<any, any>;
|
|
60
|
+
declare const optional: () => (Validator<any, any> & ControlValidator);
|
|
61
|
+
declare const string: (args_0?: string | undefined) => Validator<string, string>;
|
|
62
|
+
declare const number: (args_0?: string | undefined) => Validator<number, number>;
|
|
63
|
+
declare const bigint: (args_0?: string | undefined) => Validator<bigint, bigint>;
|
|
64
|
+
declare const boolean: (args_0?: string | undefined) => Validator<boolean, boolean>;
|
|
65
|
+
declare const symbol: (args_0?: string | undefined) => Validator<symbol, symbol>;
|
|
66
|
+
type InferShape<S> = { [K in keyof S]: S[K] extends Validator<any, infer O> ? O : never };
|
|
67
|
+
declare function object<S extends Record<string, Validator<any, any>>>(shape: S, msg?: string): Validator<unknown, InferShape<S>>;
|
|
68
|
+
declare function array<T>(validator: Validator<any, T>, msg?: string): Validator<T[], T[]>;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { Ctx, InferOutput, InferShape, ValidateAllResult, ValidateResult, ValidationError, Validator, ValidatorFactory, array, bigint, boolean, check, createValidator, formatValue, makeCtx, number, object, optional, pipe, required, string, symbol, transform, validate, validateAll };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//#region index.d.ts
|
|
2
|
+
interface Ctx {
|
|
3
|
+
path: string;
|
|
4
|
+
key: string | number | null;
|
|
5
|
+
parent: any;
|
|
6
|
+
root: any;
|
|
7
|
+
__abortPipe?: boolean;
|
|
8
|
+
__collectErrors?: boolean;
|
|
9
|
+
__errors?: ValidationError[];
|
|
10
|
+
[key: string]: any;
|
|
11
|
+
}
|
|
12
|
+
type Validator<Input = any, Output = Input> = (value: Input, ctx: Ctx) => Output;
|
|
13
|
+
interface ValidationError extends Error {
|
|
14
|
+
expected?: string;
|
|
15
|
+
actual?: any;
|
|
16
|
+
path?: string;
|
|
17
|
+
key?: string | number | null;
|
|
18
|
+
parent?: any;
|
|
19
|
+
root?: any;
|
|
20
|
+
}
|
|
21
|
+
type ValidatorFactory<Input = any, Output = Input, Args extends any[] = any[]> = (...args: Args) => Validator<Input, Output>;
|
|
22
|
+
declare function makeCtx(parentCtx: Ctx | null, key: string | number | null, value: any, root?: any): Ctx;
|
|
23
|
+
declare function formatValue(value: any): string;
|
|
24
|
+
declare function createValidator<I = unknown, O = I, Args extends any[] = any[]>(name: string, handler: (value: I, ctx: Ctx, args: Args) => O): (...args: Args) => Validator<I, O>;
|
|
25
|
+
interface ControlValidator {
|
|
26
|
+
__control?: true;
|
|
27
|
+
}
|
|
28
|
+
type IsControl<V> = V extends ControlValidator ? true : false;
|
|
29
|
+
type InputOf<V> = V extends ((value: infer I, ctx: Ctx) => any) ? I : never;
|
|
30
|
+
type OutputOf<V> = V extends ((value: any, ctx: Ctx) => infer O) ? O : never;
|
|
31
|
+
type Simplify<T> = { [K in keyof T]: T[K] } & {};
|
|
32
|
+
type DeepSimplify<T> = T extends string | number | boolean | bigint | symbol | null | undefined ? T : T extends Function ? T : T extends (infer U)[] ? Array<DeepSimplify<U>> : T extends object ? Simplify<{ [K in keyof T]: DeepSimplify<T[K]> }> : T;
|
|
33
|
+
type InferOutput<V> = V extends ((value: any, ctx: Ctx) => infer O) ? DeepSimplify<O> : never;
|
|
34
|
+
type Last<T extends unknown[]> = T extends [...any[], infer L] ? L : never;
|
|
35
|
+
type ValidateChain<Vs extends unknown[]> = Vs extends [infer V1, infer V2, ...infer Rest] ? IsControl<V1> extends true ? [V1, ...ValidateChain<[V2, ...Rest]>] : OutputOf<V1> extends InputOf<V2> ? [V1, ...ValidateChain<[V2, ...Rest]>] : never : Vs;
|
|
36
|
+
declare function pipe<Vs extends [Validator<any, any>, ...(Validator<any, any>)[]]>(...validators: ValidateChain<Vs>): Validator<InputOf<Vs[0]>, OutputOf<Last<Vs>>>;
|
|
37
|
+
declare function transform<I = any, O = any>(fn: (value: I, ctx: Ctx) => O): Validator<I, O>;
|
|
38
|
+
declare function check<I = any>(fn: (value: I, ctx: Ctx) => boolean, msg?: string): Validator<I, I>;
|
|
39
|
+
type ValidateResult<O> = {
|
|
40
|
+
valid: true;
|
|
41
|
+
result: O;
|
|
42
|
+
error?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
valid: false;
|
|
45
|
+
error: ValidationError;
|
|
46
|
+
result: any;
|
|
47
|
+
};
|
|
48
|
+
declare function validate<I, O>(schema: Validator<I, O>, value: I, rootPath?: string): ValidateResult<O>;
|
|
49
|
+
type ValidateAllResult<O> = {
|
|
50
|
+
valid: true;
|
|
51
|
+
result: O;
|
|
52
|
+
errors: [];
|
|
53
|
+
} | {
|
|
54
|
+
valid: false;
|
|
55
|
+
result: any;
|
|
56
|
+
errors: ValidationError[];
|
|
57
|
+
};
|
|
58
|
+
declare function validateAll<I, O>(schema: Validator<I, O>, value: I, rootPath?: string): ValidateAllResult<O>;
|
|
59
|
+
declare const required: (args_0?: string | undefined) => Validator<any, any>;
|
|
60
|
+
declare const optional: () => (Validator<any, any> & ControlValidator);
|
|
61
|
+
declare const string: (args_0?: string | undefined) => Validator<string, string>;
|
|
62
|
+
declare const number: (args_0?: string | undefined) => Validator<number, number>;
|
|
63
|
+
declare const bigint: (args_0?: string | undefined) => Validator<bigint, bigint>;
|
|
64
|
+
declare const boolean: (args_0?: string | undefined) => Validator<boolean, boolean>;
|
|
65
|
+
declare const symbol: (args_0?: string | undefined) => Validator<symbol, symbol>;
|
|
66
|
+
type InferShape<S> = { [K in keyof S]: S[K] extends Validator<any, infer O> ? O : never };
|
|
67
|
+
declare function object<S extends Record<string, Validator<any, any>>>(shape: S, msg?: string): Validator<unknown, InferShape<S>>;
|
|
68
|
+
declare function array<T>(validator: Validator<any, T>, msg?: string): Validator<T[], T[]>;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { Ctx, InferOutput, InferShape, ValidateAllResult, ValidateResult, ValidationError, Validator, ValidatorFactory, array, bigint, boolean, check, createValidator, formatValue, makeCtx, number, object, optional, pipe, required, string, symbol, transform, validate, validateAll };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
//#region index.ts
|
|
2
|
+
const DEFAULT_ROOT_PATH = "$";
|
|
3
|
+
function collectError(ctx, error) {
|
|
4
|
+
if (!ctx.__errors) ctx.__errors = [];
|
|
5
|
+
ctx.__errors.push(error);
|
|
6
|
+
}
|
|
7
|
+
function attachCollector(parentCtx, childCtx) {
|
|
8
|
+
if (!parentCtx.__collectErrors) return;
|
|
9
|
+
childCtx.__collectErrors = true;
|
|
10
|
+
childCtx.__errors = parentCtx.__errors;
|
|
11
|
+
}
|
|
12
|
+
function makeCtx(parentCtx, key, value, root) {
|
|
13
|
+
let path;
|
|
14
|
+
if (!parentCtx) path = DEFAULT_ROOT_PATH;
|
|
15
|
+
else if (typeof key === "number") path = `${parentCtx.path}[${key}]`;
|
|
16
|
+
else path = `${parentCtx.path}.${key}`;
|
|
17
|
+
return {
|
|
18
|
+
path,
|
|
19
|
+
key,
|
|
20
|
+
parent: parentCtx ? parentCtx.value : null,
|
|
21
|
+
root: parentCtx ? parentCtx.root : value,
|
|
22
|
+
value
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function formatValue(value) {
|
|
26
|
+
if (value === null || value === void 0) return String(value);
|
|
27
|
+
const type = typeof value;
|
|
28
|
+
if (type === "string") return value;
|
|
29
|
+
if (type === "number" || type === "boolean" || type === "bigint") return String(value);
|
|
30
|
+
if (type === "symbol") return value.toString();
|
|
31
|
+
if (type === "function") return `[Function${value.name ? `: ${value.name}` : ""}]`;
|
|
32
|
+
if (value instanceof RegExp) return value.toString();
|
|
33
|
+
if (value instanceof Date) return value.toISOString();
|
|
34
|
+
if (Array.isArray(value) || value && Object.getPrototypeOf(value) === Object.prototype) return JSON.stringify(value);
|
|
35
|
+
return Object.prototype.toString.call(value);
|
|
36
|
+
}
|
|
37
|
+
function createValidator(name, handler) {
|
|
38
|
+
return function validatorFactory(...args) {
|
|
39
|
+
return function validatorFn(value, ctx) {
|
|
40
|
+
try {
|
|
41
|
+
return handler(value, ctx, args);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
44
|
+
if (!error.expected) {
|
|
45
|
+
error.expected = name;
|
|
46
|
+
error.actual = value;
|
|
47
|
+
error.path = ctx.path;
|
|
48
|
+
error.key = ctx.key;
|
|
49
|
+
error.parent = ctx.parent;
|
|
50
|
+
error.root = ctx.root;
|
|
51
|
+
}
|
|
52
|
+
if (ctx.__collectErrors) {
|
|
53
|
+
collectError(ctx, error);
|
|
54
|
+
ctx.__abortPipe = true;
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function pipe(...validators) {
|
|
63
|
+
return createValidator("pipe", (value, ctx) => {
|
|
64
|
+
for (const validator of validators) {
|
|
65
|
+
if (ctx.__abortPipe) break;
|
|
66
|
+
value = validator(value, ctx);
|
|
67
|
+
}
|
|
68
|
+
delete ctx.__abortPipe;
|
|
69
|
+
return value;
|
|
70
|
+
})();
|
|
71
|
+
}
|
|
72
|
+
function transform(fn) {
|
|
73
|
+
return createValidator("transform", (value, ctx) => fn(value, ctx))();
|
|
74
|
+
}
|
|
75
|
+
function check(fn, msg) {
|
|
76
|
+
const expected = fn.name || "check";
|
|
77
|
+
return createValidator(expected, (value, ctx) => {
|
|
78
|
+
if (fn(value, ctx) === true) return value;
|
|
79
|
+
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ ${expected}`);
|
|
80
|
+
})();
|
|
81
|
+
}
|
|
82
|
+
function validate(schema, value, rootPath = DEFAULT_ROOT_PATH) {
|
|
83
|
+
const ctx = {
|
|
84
|
+
path: rootPath,
|
|
85
|
+
key: null,
|
|
86
|
+
parent: null,
|
|
87
|
+
root: value,
|
|
88
|
+
value
|
|
89
|
+
};
|
|
90
|
+
try {
|
|
91
|
+
return {
|
|
92
|
+
valid: true,
|
|
93
|
+
result: schema(value, ctx)
|
|
94
|
+
};
|
|
95
|
+
} catch (error) {
|
|
96
|
+
return {
|
|
97
|
+
valid: false,
|
|
98
|
+
error,
|
|
99
|
+
result: value
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function validateAll(schema, value, rootPath = DEFAULT_ROOT_PATH) {
|
|
104
|
+
const ctx = {
|
|
105
|
+
path: rootPath,
|
|
106
|
+
key: null,
|
|
107
|
+
parent: null,
|
|
108
|
+
root: value,
|
|
109
|
+
value,
|
|
110
|
+
__collectErrors: true,
|
|
111
|
+
__errors: []
|
|
112
|
+
};
|
|
113
|
+
let result = value;
|
|
114
|
+
try {
|
|
115
|
+
result = schema(value, ctx);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
collectError(ctx, error);
|
|
118
|
+
}
|
|
119
|
+
const errors = ctx.__errors ?? [];
|
|
120
|
+
if (errors.length > 0) return {
|
|
121
|
+
valid: false,
|
|
122
|
+
errors,
|
|
123
|
+
result: value
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
valid: true,
|
|
127
|
+
result,
|
|
128
|
+
errors: []
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const required = createValidator("required", (value, ctx, args) => {
|
|
132
|
+
const [msg] = args;
|
|
133
|
+
if (value == null) throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ required`);
|
|
134
|
+
return value;
|
|
135
|
+
});
|
|
136
|
+
const optional = createValidator("optional", (value, ctx) => {
|
|
137
|
+
if (value == null) {
|
|
138
|
+
ctx.__abortPipe = true;
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
});
|
|
143
|
+
const string = createValidator("string", (value, ctx, args) => {
|
|
144
|
+
const [msg] = args;
|
|
145
|
+
if (typeof value !== "string") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ string`);
|
|
146
|
+
return value;
|
|
147
|
+
});
|
|
148
|
+
const number = createValidator("number", (value, ctx, args) => {
|
|
149
|
+
const [msg] = args;
|
|
150
|
+
if (typeof value !== "number") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ number`);
|
|
151
|
+
return value;
|
|
152
|
+
});
|
|
153
|
+
const bigint = createValidator("bigint", (value, ctx, args) => {
|
|
154
|
+
const [msg] = args;
|
|
155
|
+
if (typeof value !== "bigint") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ bigint`);
|
|
156
|
+
return value;
|
|
157
|
+
});
|
|
158
|
+
const boolean = createValidator("boolean", (value, ctx, args) => {
|
|
159
|
+
const [msg] = args;
|
|
160
|
+
if (typeof value !== "boolean") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ boolean`);
|
|
161
|
+
return value;
|
|
162
|
+
});
|
|
163
|
+
const symbol = createValidator("symbol", (value, ctx, args) => {
|
|
164
|
+
const [msg] = args;
|
|
165
|
+
if (typeof value !== "symbol") throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ symbol`);
|
|
166
|
+
return value;
|
|
167
|
+
});
|
|
168
|
+
function object(shape, msg) {
|
|
169
|
+
return createValidator("object", (value, ctx) => {
|
|
170
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ object`);
|
|
171
|
+
const result = {};
|
|
172
|
+
for (const key in shape) {
|
|
173
|
+
const childCtx = makeCtx(ctx, key, value[key]);
|
|
174
|
+
attachCollector(ctx, childCtx);
|
|
175
|
+
try {
|
|
176
|
+
result[key] = shape[key](value[key], childCtx);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
if (ctx.__collectErrors) {
|
|
179
|
+
collectError(ctx, error);
|
|
180
|
+
result[key] = value[key];
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
})();
|
|
188
|
+
}
|
|
189
|
+
function array(validator, msg) {
|
|
190
|
+
return createValidator("array", (value, ctx) => {
|
|
191
|
+
if (!Array.isArray(value)) throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) ✖ array`);
|
|
192
|
+
return value.map((item, i) => {
|
|
193
|
+
const childCtx = makeCtx(ctx, i, item);
|
|
194
|
+
attachCollector(ctx, childCtx);
|
|
195
|
+
try {
|
|
196
|
+
return validator(item, childCtx);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (ctx.__collectErrors) {
|
|
199
|
+
collectError(ctx, error);
|
|
200
|
+
return item;
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
})();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
//#endregion
|
|
209
|
+
export { array, bigint, boolean, check, createValidator, formatValue, makeCtx, number, object, optional, pipe, required, string, symbol, transform, validate, validateAll };
|
package/package.json
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nana",
|
|
3
3
|
"description": "A minimal validator for any JavaScript environment.",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cjs/index.cjs",
|
|
7
|
-
"module": "dist/esm/index.
|
|
8
|
-
"types": "
|
|
7
|
+
"module": "dist/esm/index.mjs",
|
|
8
|
+
"types": "dist/esm/index.d.mts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/esm/index.d.mts",
|
|
13
|
+
"default": "./dist/esm/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/cjs/index.d.cts",
|
|
17
|
+
"default": "./dist/cjs/index.cjs"
|
|
18
|
+
}
|
|
15
19
|
}
|
|
16
20
|
},
|
|
17
21
|
"files": [
|
|
@@ -23,9 +27,10 @@
|
|
|
23
27
|
"scripts": {
|
|
24
28
|
"lint": "eslint .",
|
|
25
29
|
"lint:fix": "eslint . --fix",
|
|
26
|
-
"build": "
|
|
30
|
+
"build": "tsdown",
|
|
31
|
+
"type-check": "tsc --noEmit",
|
|
27
32
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
28
|
-
"prepublishOnly": "npm run lint && npm run test && npm run build",
|
|
33
|
+
"prepublishOnly": "npm run type-check && npm run lint && npm run test && npm run build",
|
|
29
34
|
"prepare": "husky"
|
|
30
35
|
},
|
|
31
36
|
"author": "nswbmw",
|
|
@@ -40,16 +45,20 @@
|
|
|
40
45
|
"schema",
|
|
41
46
|
"json-schema",
|
|
42
47
|
"zod",
|
|
43
|
-
"valibot"
|
|
48
|
+
"valibot",
|
|
49
|
+
"typescript"
|
|
44
50
|
],
|
|
45
51
|
"devDependencies": {
|
|
46
|
-
"@commitlint/cli": "20.
|
|
47
|
-
"@commitlint/config-conventional": "20.
|
|
52
|
+
"@commitlint/cli": "20.4.1",
|
|
53
|
+
"@commitlint/config-conventional": "20.4.1",
|
|
54
|
+
"@jest/globals": "^30.2.0",
|
|
48
55
|
"eslint": "9.39.2",
|
|
49
|
-
"globals": "
|
|
56
|
+
"globals": "17.3.0",
|
|
50
57
|
"husky": "9.1.7",
|
|
51
58
|
"jest": "30.2.0",
|
|
52
59
|
"neostandard": "0.12.2",
|
|
53
|
-
"
|
|
60
|
+
"ts-jest": "^29.4.6",
|
|
61
|
+
"tsdown": "^0.20.3",
|
|
62
|
+
"typescript": "^5.9.3"
|
|
54
63
|
}
|
|
55
64
|
}
|
package/dist/esm/index.js
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
function makeCtx(parentCtx, key, value) {
|
|
2
|
-
let path;
|
|
3
|
-
if (!parentCtx) path = "$";
|
|
4
|
-
else if (typeof key === "number") path = `${parentCtx.path}[${key}]`;
|
|
5
|
-
else path = `${parentCtx.path}.${key}`;
|
|
6
|
-
return {
|
|
7
|
-
path,
|
|
8
|
-
key,
|
|
9
|
-
parent: parentCtx ? parentCtx.value : null,
|
|
10
|
-
root: parentCtx ? parentCtx.root : value,
|
|
11
|
-
value
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
function formatValue(value) {
|
|
15
|
-
if (value === null || value === void 0) return String(value);
|
|
16
|
-
const type = typeof value;
|
|
17
|
-
if (type === "string") return value;
|
|
18
|
-
if (type === "number" || type === "boolean" || type === "bigint") return String(value);
|
|
19
|
-
if (type === "symbol") return value.toString();
|
|
20
|
-
if (type === "function") {
|
|
21
|
-
const name = value.name ? `: ${value.name}` : "";
|
|
22
|
-
return `[Function${name}]`;
|
|
23
|
-
}
|
|
24
|
-
if (value instanceof RegExp) return value.toString();
|
|
25
|
-
if (value instanceof Date) return value.toISOString();
|
|
26
|
-
if (Array.isArray(value) || value && Object.getPrototypeOf(value) === Object.prototype) {
|
|
27
|
-
return JSON.stringify(value);
|
|
28
|
-
}
|
|
29
|
-
return Object.prototype.toString.call(value);
|
|
30
|
-
}
|
|
31
|
-
function createValidator(name, handler) {
|
|
32
|
-
return function validatorFactory(...args) {
|
|
33
|
-
return function validatorFn(value, ctx) {
|
|
34
|
-
try {
|
|
35
|
-
return handler(value, ctx, args);
|
|
36
|
-
} catch (err) {
|
|
37
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
38
|
-
if (!error.expected) {
|
|
39
|
-
error.expected = name;
|
|
40
|
-
error.actual = value;
|
|
41
|
-
error.path = ctx.path;
|
|
42
|
-
error.key = ctx.key;
|
|
43
|
-
error.parent = ctx.parent;
|
|
44
|
-
error.root = ctx.root;
|
|
45
|
-
}
|
|
46
|
-
throw error;
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function pipe(...validators) {
|
|
52
|
-
return createValidator("pipe", (value, ctx) => {
|
|
53
|
-
for (const validator of validators) {
|
|
54
|
-
if (ctx.__abortPipe) break;
|
|
55
|
-
value = validator(value, ctx);
|
|
56
|
-
}
|
|
57
|
-
delete ctx.__abortPipe;
|
|
58
|
-
return value;
|
|
59
|
-
})();
|
|
60
|
-
}
|
|
61
|
-
function transform(fn) {
|
|
62
|
-
return createValidator("transform", (value, ctx) => fn(value, ctx))();
|
|
63
|
-
}
|
|
64
|
-
function check(fn, msg) {
|
|
65
|
-
const expected = fn.name || "check";
|
|
66
|
-
return createValidator(expected, (value, ctx) => {
|
|
67
|
-
const result = fn(value, ctx);
|
|
68
|
-
if (result === true) return value;
|
|
69
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 ${expected}`);
|
|
70
|
-
})();
|
|
71
|
-
}
|
|
72
|
-
function validate(schema, value, rootPath = "$") {
|
|
73
|
-
const ctx = {
|
|
74
|
-
path: rootPath,
|
|
75
|
-
key: null,
|
|
76
|
-
parent: null,
|
|
77
|
-
root: value,
|
|
78
|
-
value
|
|
79
|
-
};
|
|
80
|
-
try {
|
|
81
|
-
const result = schema(value, ctx);
|
|
82
|
-
return { valid: true, result };
|
|
83
|
-
} catch (error) {
|
|
84
|
-
return {
|
|
85
|
-
valid: false,
|
|
86
|
-
error,
|
|
87
|
-
result: value
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const required = createValidator("required", (value, ctx, args) => {
|
|
92
|
-
const [msg] = args;
|
|
93
|
-
if (value == null) {
|
|
94
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 required`);
|
|
95
|
-
}
|
|
96
|
-
return value;
|
|
97
|
-
});
|
|
98
|
-
const optional = createValidator("optional", (value, ctx, args) => {
|
|
99
|
-
if (value == null) {
|
|
100
|
-
ctx.__abortPipe = true;
|
|
101
|
-
return value;
|
|
102
|
-
}
|
|
103
|
-
return value;
|
|
104
|
-
});
|
|
105
|
-
const string = createValidator("string", (value, ctx, args) => {
|
|
106
|
-
const [msg] = args;
|
|
107
|
-
if (typeof value !== "string") {
|
|
108
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 string`);
|
|
109
|
-
}
|
|
110
|
-
return value;
|
|
111
|
-
});
|
|
112
|
-
const number = createValidator("number", (value, ctx, args) => {
|
|
113
|
-
const [msg] = args;
|
|
114
|
-
if (typeof value !== "number") {
|
|
115
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 number`);
|
|
116
|
-
}
|
|
117
|
-
return value;
|
|
118
|
-
});
|
|
119
|
-
const bigint = createValidator("bigint", (value, ctx, args) => {
|
|
120
|
-
const [msg] = args;
|
|
121
|
-
if (typeof value !== "bigint") {
|
|
122
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 bigint`);
|
|
123
|
-
}
|
|
124
|
-
return value;
|
|
125
|
-
});
|
|
126
|
-
const boolean = createValidator("boolean", (value, ctx, args) => {
|
|
127
|
-
const [msg] = args;
|
|
128
|
-
if (typeof value !== "boolean") {
|
|
129
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 boolean`);
|
|
130
|
-
}
|
|
131
|
-
return value;
|
|
132
|
-
});
|
|
133
|
-
const symbol = createValidator("symbol", (value, ctx, args) => {
|
|
134
|
-
const [msg] = args;
|
|
135
|
-
if (typeof value !== "symbol") {
|
|
136
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 symbol`);
|
|
137
|
-
}
|
|
138
|
-
return value;
|
|
139
|
-
});
|
|
140
|
-
const object = createValidator("object", (value, ctx, args) => {
|
|
141
|
-
const [obj, msg] = args;
|
|
142
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
143
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 object`);
|
|
144
|
-
}
|
|
145
|
-
const result = {};
|
|
146
|
-
for (const key in obj) {
|
|
147
|
-
const childCtx = makeCtx(ctx, key, value[key]);
|
|
148
|
-
result[key] = obj[key](value[key], childCtx);
|
|
149
|
-
}
|
|
150
|
-
return result;
|
|
151
|
-
});
|
|
152
|
-
const array = createValidator("array", (value, ctx, args) => {
|
|
153
|
-
const [validator, msg] = args;
|
|
154
|
-
if (!Array.isArray(value)) {
|
|
155
|
-
throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 array`);
|
|
156
|
-
}
|
|
157
|
-
return value.map((item, i) => {
|
|
158
|
-
const childCtx = makeCtx(ctx, i, item);
|
|
159
|
-
return validator(item, childCtx);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
export {
|
|
163
|
-
array,
|
|
164
|
-
bigint,
|
|
165
|
-
boolean,
|
|
166
|
-
check,
|
|
167
|
-
createValidator,
|
|
168
|
-
formatValue,
|
|
169
|
-
makeCtx,
|
|
170
|
-
number,
|
|
171
|
-
object,
|
|
172
|
-
optional,
|
|
173
|
-
pipe,
|
|
174
|
-
required,
|
|
175
|
-
string,
|
|
176
|
-
symbol,
|
|
177
|
-
transform,
|
|
178
|
-
validate
|
|
179
|
-
};
|
package/types/index.d.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
export interface Ctx {
|
|
2
|
-
path: string
|
|
3
|
-
key: string | number | null
|
|
4
|
-
parent: any
|
|
5
|
-
root: any
|
|
6
|
-
value: any
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export type Validator<T = any> = (value: T, ctx: Ctx) => T
|
|
10
|
-
|
|
11
|
-
export type ValidatorFactory<Args extends any[] = any[], T = any> = (
|
|
12
|
-
...args: Args
|
|
13
|
-
) => Validator<T>
|
|
14
|
-
|
|
15
|
-
export function makeCtx (parentCtx: Ctx | null, key: string | number | null, value: any): Ctx
|
|
16
|
-
|
|
17
|
-
export function formatValue (value: any): string
|
|
18
|
-
|
|
19
|
-
export function createValidator<Args extends any[] = any[], T = any> (
|
|
20
|
-
name: string,
|
|
21
|
-
handler: (value: any, ctx: Ctx, args: Args) => T
|
|
22
|
-
): ValidatorFactory<Args, T>
|
|
23
|
-
|
|
24
|
-
export function pipe<T = any> (...validators: Validator[]): Validator<T>
|
|
25
|
-
|
|
26
|
-
export function transform<TIn = any, TOut = any> (
|
|
27
|
-
fn: (value: TIn, ctx: Ctx) => TOut
|
|
28
|
-
): Validator<TOut>
|
|
29
|
-
|
|
30
|
-
export function check<T = any> (
|
|
31
|
-
fn: (value: T, ctx: Ctx) => boolean,
|
|
32
|
-
msg?: string
|
|
33
|
-
): Validator<T>
|
|
34
|
-
|
|
35
|
-
export interface ValidateResult<T = any> {
|
|
36
|
-
valid: boolean
|
|
37
|
-
result: T
|
|
38
|
-
error?: Error
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function validate<T = any> (
|
|
42
|
-
schema: Validator<T>,
|
|
43
|
-
value: any,
|
|
44
|
-
rootPath?: string
|
|
45
|
-
): ValidateResult<T>
|
|
46
|
-
|
|
47
|
-
export const required: (msg?: string) => Validator<any>
|
|
48
|
-
|
|
49
|
-
export const optional: () => Validator<any>
|
|
50
|
-
|
|
51
|
-
export const string: (msg?: string) => Validator<string>
|
|
52
|
-
|
|
53
|
-
export const number: (msg?: string) => Validator<number>
|
|
54
|
-
|
|
55
|
-
export const bigint: (msg?: string) => Validator<bigint>
|
|
56
|
-
|
|
57
|
-
export const boolean: (msg?: string) => Validator<boolean>
|
|
58
|
-
|
|
59
|
-
export const symbol: (msg?: string) => Validator<symbol>
|
|
60
|
-
|
|
61
|
-
export const object: (
|
|
62
|
-
obj: { [key: string]: Validator<any> },
|
|
63
|
-
msg?: string
|
|
64
|
-
) => Validator<any>
|
|
65
|
-
|
|
66
|
-
export const array: (
|
|
67
|
-
validator: Validator<any>,
|
|
68
|
-
msg?: string
|
|
69
|
-
) => Validator<any[]>
|