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 CHANGED
@@ -1,3 +1,10 @@
1
+ ## v2.0.0 / 2026-02-25
2
+
3
+ - add validateAll to collect all validation errors
4
+ - add InferOutput to get schema type
5
+ - refactor: use tsdown instead of tsup
6
+ - chore(deps): update deps
7
+
1
8
  ## v1.2.0 / 2025-12-16
2
9
 
3
10
  - fix: rename __skipNext to __abortPipe
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  A minimal validator for any JavaScript environment.
8
8
 
9
- - ~200 lines of code, 0 dependencies
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
@@ -1,217 +1,227 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __export = (target, all) => {
6
- for (var name in all)
7
- __defProp(target, name, { get: all[name], enumerable: true });
8
- };
9
- var __copyProps = (to, from, except, desc) => {
10
- if (from && typeof from === "object" || typeof from === "function") {
11
- for (let key of __getOwnPropNames(from))
12
- if (!__hasOwnProp.call(to, key) && key !== except)
13
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
- }
15
- return to;
16
- };
17
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
- var index_exports = {};
19
- __export(index_exports, {
20
- array: () => array,
21
- bigint: () => bigint,
22
- boolean: () => boolean,
23
- check: () => check,
24
- createValidator: () => createValidator,
25
- formatValue: () => formatValue,
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
- if (value === null || value === void 0) return String(value);
53
- const type = typeof value;
54
- if (type === "string") return value;
55
- if (type === "number" || type === "boolean" || type === "bigint") return String(value);
56
- if (type === "symbol") return value.toString();
57
- if (type === "function") {
58
- const name = value.name ? `: ${value.name}` : "";
59
- return `[Function${name}]`;
60
- }
61
- if (value instanceof RegExp) return value.toString();
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
- return function validatorFactory(...args) {
70
- return function validatorFn(value, ctx) {
71
- try {
72
- return handler(value, ctx, args);
73
- } catch (err) {
74
- const error = err instanceof Error ? err : new Error(String(err));
75
- if (!error.expected) {
76
- error.expected = name;
77
- error.actual = value;
78
- error.path = ctx.path;
79
- error.key = ctx.key;
80
- error.parent = ctx.parent;
81
- error.root = ctx.root;
82
- }
83
- throw error;
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
- return createValidator("pipe", (value, ctx) => {
90
- for (const validator of validators) {
91
- if (ctx.__abortPipe) break;
92
- value = validator(value, ctx);
93
- }
94
- delete ctx.__abortPipe;
95
- return value;
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
- return createValidator("transform", (value, ctx) => fn(value, ctx))();
75
+ return createValidator("transform", (value, ctx) => fn(value, ctx))();
100
76
  }
101
77
  function check(fn, msg) {
102
- const expected = fn.name || "check";
103
- return createValidator(expected, (value, ctx) => {
104
- const result = fn(value, ctx);
105
- if (result === true) return value;
106
- throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 ${expected}`);
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
- const ctx = {
111
- path: rootPath,
112
- key: null,
113
- parent: null,
114
- root: value,
115
- value
116
- };
117
- try {
118
- const result = schema(value, ctx);
119
- return { valid: true, result };
120
- } catch (error) {
121
- return {
122
- valid: false,
123
- error,
124
- result: value
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
- const [msg] = args;
130
- if (value == null) {
131
- throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 required`);
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, args) => {
136
- if (value == null) {
137
- ctx.__abortPipe = true;
138
- return value;
139
- }
140
- return value;
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
- const [msg] = args;
144
- if (typeof value !== "string") {
145
- throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 string`);
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
- const [msg] = args;
151
- if (typeof value !== "number") {
152
- throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 number`);
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
- const [msg] = args;
158
- if (typeof value !== "bigint") {
159
- throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 bigint`);
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
- const [msg] = args;
165
- if (typeof value !== "boolean") {
166
- throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 boolean`);
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
- const [msg] = args;
172
- if (typeof value !== "symbol") {
173
- throw new Error(msg || `(${ctx.path}: ${formatValue(value)}) \u2716 symbol`);
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": "1.2.0",
4
+ "version": "2.0.0",
5
5
  "type": "module",
6
6
  "main": "dist/cjs/index.cjs",
7
- "module": "dist/esm/index.js",
8
- "types": "types/index.d.ts",
7
+ "module": "dist/esm/index.mjs",
8
+ "types": "dist/esm/index.d.mts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./types/index.d.ts",
12
- "import": "./dist/esm/index.js",
13
- "require": "./dist/cjs/index.cjs",
14
- "default": "./dist/esm/index.js"
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": "tsup",
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.2.0",
47
- "@commitlint/config-conventional": "20.2.0",
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": "16.5.0",
56
+ "globals": "17.3.0",
50
57
  "husky": "9.1.7",
51
58
  "jest": "30.2.0",
52
59
  "neostandard": "0.12.2",
53
- "tsup": "8.5.1"
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[]>