ascertain 2.1.0 → 3.1.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/build/index.d.ts CHANGED
@@ -1,16 +1,3 @@
1
- /**
2
- * Abstract base class for schema operators.
3
- *
4
- * Provides a common constructor that enforces having at least one schema.
5
- *
6
- * @template T - The type of data the operator validates.
7
- * @abstract
8
- * @internal
9
- */
10
- declare abstract class Operator<T> {
11
- readonly schemas: Schema<T>[];
12
- constructor(schemas: Schema<T>[]);
13
- }
14
1
  /**
15
2
  * Symbol for validating object keys against a schema.
16
3
  */
@@ -23,6 +10,44 @@ export declare const $values: unique symbol;
23
10
  * Symbol for enforcing strict object validation (no extra properties allowed).
24
11
  */
25
12
  export declare const $strict: unique symbol;
13
+ declare const $op: unique symbol;
14
+ declare const OR: unique symbol;
15
+ declare const AND: unique symbol;
16
+ declare const OPTIONAL: unique symbol;
17
+ declare const TUPLE: unique symbol;
18
+ declare const DISCRIMINATED: unique symbol;
19
+ declare const CHECK: unique symbol;
20
+ interface OrShape<T> {
21
+ readonly schemas: Schema<T>[];
22
+ readonly [$op]: typeof OR;
23
+ }
24
+ interface AndShape<T> {
25
+ readonly schemas: Schema<T>[];
26
+ readonly [$op]: typeof AND;
27
+ }
28
+ interface OptionalShape<T> {
29
+ readonly schemas: Schema<T>[];
30
+ readonly [$op]: typeof OPTIONAL;
31
+ }
32
+ interface TupleShape<T> {
33
+ readonly schemas: Schema<T>[];
34
+ readonly [$op]: typeof TUPLE;
35
+ }
36
+ interface DiscriminatedShape<T> {
37
+ readonly schemas: Schema<T>[];
38
+ readonly [$op]: typeof DISCRIMINATED;
39
+ readonly key: string;
40
+ }
41
+ export interface CheckContext {
42
+ ref(value: unknown): string;
43
+ }
44
+ interface CheckShape {
45
+ readonly [$op]: typeof CHECK;
46
+ readonly compile: (value: string, ctx: CheckContext) => {
47
+ check: string;
48
+ message: string;
49
+ };
50
+ }
26
51
  /**
27
52
  * Represents a schema for validating data.
28
53
  *
@@ -39,165 +64,57 @@ export type Schema<T> = T extends Record<string | number | symbol, unknown> ? {
39
64
  } & {
40
65
  [$strict]?: boolean;
41
66
  } : T extends Array<infer A> ? Schema<A>[] | unknown : unknown;
42
- declare class Or<T> extends Operator<T> {
43
- }
44
67
  /**
45
68
  * Operator for validating data against any of the provided schemas (logical OR).
46
- *
47
- * Creates a schema that accepts data matching any one of the provided schemas.
48
- * This is useful for creating union types or alternative validation paths.
49
- *
50
- * @template T - The type of data the operator validates.
51
- * @param schemas - Multiple schemas where at least one must match the data.
52
- * @returns A schema that validates data against any of the provided schemas.
53
- *
54
- * @example
55
- * ```typescript
56
- * import { or, ascertain } from 'ascertain';
57
- *
58
- * // Create a schema that accepts either a string or number
59
- * const stringOrNumber = or(String, Number);
60
- *
61
- * ascertain(stringOrNumber, "hello", "value"); // ✓ Valid
62
- * ascertain(stringOrNumber, 42, "value"); // ✓ Valid
63
- * ascertain(stringOrNumber, true, "value"); // ✗ Throws error
64
- *
65
- * // Union of literal values
66
- * const statusSchema = or('pending', 'completed', 'failed');
67
- * ascertain(statusSchema, 'pending', "status"); // ✓ Valid
68
- *
69
- * // Complex schema combinations
70
- * const userIdSchema = or(Number, { id: Number, temp: Boolean });
71
- * ascertain(userIdSchema, 123, "userId"); // ✓ Valid
72
- * ascertain(userIdSchema, { id: 456, temp: true }, "userId"); // ✓ Valid
73
- * ```
74
69
  */
75
- export declare const or: <T>(...schemas: Schema<T>[]) => Or<T>;
76
- declare class And<T> extends Operator<T> {
77
- }
70
+ export declare const or: <T>(...schemas: Schema<T>[]) => OrShape<T>;
78
71
  /**
79
72
  * Operator for validating data against all provided schemas (logical AND).
80
- *
81
- * Creates a schema that requires data to match every one of the provided schemas.
82
- * This is useful for combining multiple validation requirements or adding constraints.
83
- *
84
- * @template T - The type of data the operator validates.
85
- * @param schemas - Multiple schemas that all must match the data.
86
- * @returns A schema that validates data against all of the provided schemas.
87
- *
88
- * @example
89
- * ```typescript
90
- * import { and, ascertain } from 'ascertain';
91
- *
92
- * // Combine object schema with additional constraints
93
- * const userSchema = and(
94
- * { name: String, age: Number },
95
- * { age: Number } // Additional constraint
96
- * );
97
- *
98
- * ascertain(userSchema, { name: "John", age: 25 }, "user"); // ✓ Valid
99
- *
100
- * // Ensure an object is both a Date and has specific methods
101
- * const validDateSchema = and(Date, { toISOString: Function });
102
- * ascertain(validDateSchema, new Date(), "date"); // ✓ Valid
103
- *
104
- * ```
105
73
  */
106
- export declare const and: <T>(...schemas: Schema<T>[]) => And<T>;
107
- declare class Optional<T> extends Operator<T> {
108
- constructor(schema: Schema<T>);
109
- }
74
+ export declare const and: <T>(...schemas: Schema<T>[]) => AndShape<T>;
110
75
  /**
111
76
  * Operator for making a schema optional (nullable).
112
- *
113
- * Creates a schema that accepts the provided schema or null/undefined values.
114
- * This is useful for optional object properties or nullable fields.
115
- *
116
- * @template T - The type of data the operator validates.
117
- * @param schema - The schema to make optional.
118
- * @returns A schema that validates data against the provided schema or accepts null/undefined.
119
- *
120
- * @example
121
- * ```typescript
122
- * import { optional, ascertain } from 'ascertain';
123
- *
124
- * // Optional string field
125
- * const userSchema = {
126
- * name: String,
127
- * nickname: optional(String),
128
- * age: Number
129
- * };
130
- *
131
- * // All of these are valid
132
- * ascertain(userSchema, {
133
- * name: "John",
134
- * nickname: "Johnny",
135
- * age: 30
136
- * }, "user"); // ✓ Valid
137
- *
138
- * ascertain(userSchema, {
139
- * name: "Jane",
140
- * nickname: null,
141
- * age: 25
142
- * }, "user"); // ✓ Valid
143
- *
144
- * ascertain(userSchema, {
145
- * name: "Bob",
146
- * age: 35
147
- * // nickname is undefined
148
- * }, "user"); // ✓ Valid
149
- *
150
- * // Optional complex objects
151
- * const profileSchema = {
152
- * id: Number,
153
- * settings: optional({
154
- * theme: String,
155
- * notifications: Boolean
156
- * })
157
- * };
158
- * ```
159
77
  */
160
- export declare const optional: <T>(schema: Schema<T>) => Optional<T>;
161
- declare class Tuple<T> extends Operator<T> {
162
- }
78
+ export declare const optional: <T>(schema: Schema<T>) => OptionalShape<T>;
163
79
  /**
164
80
  * Operator for validating data against a fixed-length tuple of schemas.
81
+ */
82
+ export declare const tuple: <T>(...schemas: Schema<T>[]) => TupleShape<T>;
83
+ /**
84
+ * Operator for validating data against a discriminated union.
165
85
  *
166
- * Creates a schema that validates arrays with a specific length and type for each position.
167
- * This is useful for coordinate pairs, RGB values, or any fixed-structure data.
86
+ * Optimizes validation by checking the discriminant field first and only
87
+ * validating the matching variant. More efficient than `or()` for unions
88
+ * where each variant has a common field with a unique literal value.
168
89
  *
169
- * @template T - The type of data the operator validates (a tuple of types).
170
- * @param schemas - Schemas for each position in the tuple, in order.
171
- * @returns A schema that validates data as a tuple with the specified structure.
90
+ * @param schemas - Array of object schemas, each with a discriminant field containing a literal value.
91
+ * @param key - The name of the discriminant field present in all variants.
172
92
  *
173
93
  * @example
174
94
  * ```typescript
175
- * import { tuple, ascertain } from 'ascertain';
176
- *
177
- * // 2D coordinate tuple
178
- * const pointSchema = tuple(Number, Number);
179
- * ascertain(pointSchema, [10, 20], "point"); // ✓ Valid
180
- * ascertain(pointSchema, [1.5, 2.7], "point"); // ✓ Valid
181
- * ascertain(pointSchema, [10], "point"); // ✗ Throws error (too short)
182
- * ascertain(pointSchema, [10, 20, 30], "point"); // ✗ Throws error (too long)
183
- *
184
- * // RGB color tuple
185
- * const colorSchema = tuple(Number, Number, Number);
186
- * ascertain(colorSchema, [255, 128, 0], "color"); // ✓ Valid
187
- *
188
- * // Mixed type tuple
189
- * const userInfoSchema = tuple(String, Number, Boolean);
190
- * ascertain(userInfoSchema, ["Alice", 25, true], "userInfo"); // ✓ Valid
191
- *
192
- * // Nested tuple
193
- * const lineSchema = tuple(
194
- * tuple(Number, Number), // start point
195
- * tuple(Number, Number) // end point
196
- * );
197
- * ascertain(lineSchema, [[0, 0], [10, 10]], "line"); // ✓ Valid
95
+ * const messageSchema = discriminated([
96
+ * { type: 'email', address: String },
97
+ * { type: 'sms', phone: String },
98
+ * { type: 'push', token: String },
99
+ * ], 'type');
198
100
  * ```
199
101
  */
200
- export declare const tuple: <T>(...schemas: Schema<T>[]) => Tuple<T>;
102
+ export declare const discriminated: <T>(schemas: Schema<T>[], key: string) => DiscriminatedShape<T>;
103
+ export declare const check: (fnOrOpts: ((v: unknown) => boolean) | {
104
+ compile: (value: string, ctx: CheckContext) => {
105
+ check: string;
106
+ message: string;
107
+ };
108
+ }, message?: string) => CheckShape;
109
+ export declare const min: (n: number, message?: string) => CheckShape;
110
+ export declare const max: (n: number, message?: string) => CheckShape;
111
+ export declare const integer: (message?: string) => CheckShape;
112
+ export declare const minLength: (n: number, message?: string) => CheckShape;
113
+ export declare const maxLength: (n: number, message?: string) => CheckShape;
114
+ export declare const gt: (n: number, message?: string) => CheckShape;
115
+ export declare const lt: (n: number, message?: string) => CheckShape;
116
+ export declare const multipleOf: (n: number, message?: string) => CheckShape;
117
+ export declare const uniqueItems: (message?: string) => CheckShape;
201
118
  /**
202
119
  * Decodes a base64-encoded string to UTF-8.
203
120
  *
@@ -286,20 +203,62 @@ export declare const as: {
286
203
  */
287
204
  base64: (value: string | undefined) => string;
288
205
  };
206
+ export declare const format: {
207
+ dateTime: (message?: string) => CheckShape;
208
+ date: (message?: string) => CheckShape;
209
+ time: (message?: string) => CheckShape;
210
+ duration: (message?: string) => CheckShape;
211
+ email: (message?: string) => CheckShape;
212
+ idnEmail: (message?: string) => CheckShape;
213
+ hostname: (message?: string) => CheckShape;
214
+ idnHostname: (message?: string) => CheckShape;
215
+ ipv4: (message?: string) => CheckShape;
216
+ ipv6: (message?: string) => CheckShape;
217
+ uri: (message?: string) => CheckShape;
218
+ uriReference: (message?: string) => CheckShape;
219
+ iri: (message?: string) => CheckShape;
220
+ iriReference: (message?: string) => CheckShape;
221
+ uuid: (message?: string) => CheckShape;
222
+ uriTemplate: (message?: string) => CheckShape;
223
+ jsonPointer: (message?: string) => CheckShape;
224
+ relativeJsonPointer: (message?: string) => CheckShape;
225
+ regex: (message?: string) => CheckShape;
226
+ };
227
+ /**
228
+ * Validator function returned by compile().
229
+ * Returns true if valid, false if invalid.
230
+ * Access `.issues` property after validation to get error details.
231
+ */
232
+ export interface Validator<T> {
233
+ (data: T): boolean;
234
+ issues: ReadonlyArray<StandardSchemaV1.Issue>;
235
+ }
289
236
  /**
290
- * Compiles a schema into a validation function.
237
+ * Options for the compile function.
238
+ */
239
+ export interface CompileOptions {
240
+ /**
241
+ * When true, collects all validation errors instead of stopping at the first.
242
+ * Default is false (first-error mode) for optimal performance.
243
+ */
244
+ allErrors?: boolean;
245
+ }
246
+ /**
247
+ * Compiles a schema into a high-performance validation function.
248
+ *
249
+ * By default uses first-error mode which stops at the first validation failure
250
+ * and returns immediately. This provides optimal performance for invalid data.
291
251
  *
292
- * This function takes a schema definition and generates a JavaScript function
293
- * that can be used to validate data against the schema.
252
+ * Set `allErrors: true` to collect all validation errors (slower but more informative).
294
253
  *
295
254
  * @template T - The type of data the schema validates.
296
255
  * @param schema - The schema to compile.
297
- * @param rootName - A name for the root of the data structure (used in error messages).
298
- * @returns A validation function that takes data as input and throws a TypeError if the data does not conform to the schema.
256
+ * @param options - Optional configuration (allErrors: boolean).
257
+ * @returns A validator function that returns boolean. Access `.issues` for error details.
299
258
  *
300
259
  * @example
301
260
  * ```typescript
302
- * import { compile, optional, and, or } from 'ascertain';
261
+ * import { compile, optional, or } from 'ascertain';
303
262
  *
304
263
  * const userSchema = {
305
264
  * name: String,
@@ -308,28 +267,24 @@ export declare const as: {
308
267
  * role: or('admin', 'user', 'guest')
309
268
  * };
310
269
  *
311
- * const validateUser = compile(userSchema, 'User');
270
+ * // First-error mode (default) - fastest for invalid data
271
+ * const validate = compile(userSchema);
312
272
  *
313
- * // Valid data - no error thrown
314
- * validateUser({
315
- * name: 'John Doe',
316
- * age: 30,
317
- * email: 'john@example.com',
318
- * role: 'user'
319
- * });
273
+ * // All-errors mode - collects all validation issues
274
+ * const validateAll = compile(userSchema, { allErrors: true });
320
275
  *
321
- * // Invalid data - throws TypeError
322
- * try {
323
- * validateUser({
324
- * name: 123, // Invalid: should be string
325
- * age: 'thirty' // Invalid: should be number
326
- * });
327
- * } catch (error) {
328
- * console.error(error.message); // Detailed validation errors
276
+ * // Valid data
277
+ * if (validate({ name: 'John', age: 30, role: 'user' })) {
278
+ * console.log('Valid!');
279
+ * }
280
+ *
281
+ * // Invalid data - check .issues for details
282
+ * if (!validate({ name: 123, age: 'thirty' })) {
283
+ * console.log(validate.issues); // Array with first validation issue
329
284
  * }
330
285
  * ```
331
286
  */
332
- export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data: T) => any;
287
+ export declare const compile: <T>(schema: Schema<T>, options?: CompileOptions) => Validator<T>;
333
288
  /**
334
289
  * Asserts that data conforms to a given schema.
335
290
  *
@@ -339,7 +294,6 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
339
294
  * @template T - The type of data the schema validates.
340
295
  * @param schema - The schema to validate against.
341
296
  * @param data - The data to validate.
342
- * @param rootName - A name for the root of the data structure (used in error messages, defaults to '[root]').
343
297
  * @throws `{TypeError}` If the data does not conform to the schema.
344
298
  *
345
299
  * @example
@@ -361,7 +315,7 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
361
315
  * };
362
316
  *
363
317
  * // Validate data - throws if invalid, otherwise continues silently
364
- * ascertain(userSchema, userData, 'UserData');
318
+ * ascertain(userSchema, userData);
365
319
  * console.log('User data is valid!');
366
320
  *
367
321
  * // Example with invalid data
@@ -370,7 +324,7 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
370
324
  * name: 'Bob',
371
325
  * age: 'twenty-five', // Invalid: should be number
372
326
  * active: true
373
- * }, 'UserData');
327
+ * });
374
328
  * } catch (error) {
375
329
  * console.error('Validation failed:', error.message);
376
330
  * }
@@ -382,16 +336,16 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
382
336
  * const numbersSchema = [Number];
383
337
  * const numbers = [1, 2, 3, 4, 5];
384
338
  *
385
- * ascertain(numbersSchema, numbers, 'Numbers');
339
+ * ascertain(numbersSchema, numbers);
386
340
  *
387
341
  * // Tuple validation
388
342
  * const coordinateSchema = tuple(Number, Number);
389
343
  * const point = [10, 20];
390
344
  *
391
- * ascertain(coordinateSchema, point, 'Point');
345
+ * ascertain(coordinateSchema, point);
392
346
  * ```
393
347
  */
394
- export declare const ascertain: <T>(schema: Schema<T>, data: T, rootName?: string) => void;
348
+ export declare const ascertain: <T>(schema: Schema<T>, data: T) => void;
395
349
  /**
396
350
  * Extracts the shape of a config object based on the schema keys.
397
351
  * Recursively picks only the properties defined in the schema.
@@ -407,7 +361,6 @@ export type ExtractShape<C, S> = {
407
361
  *
408
362
  * @template C - The type of the config object.
409
363
  * @param config - The config object to validate against.
410
- * @param rootName - A name for the root of the data structure (used in error messages).
411
364
  * @returns A validator function that takes a schema and returns the typed config subset.
412
365
  *
413
366
  * @example
@@ -420,7 +373,7 @@ export type ExtractShape<C, S> = {
420
373
  * redis: { host: as.string(process.env.REDIS_HOST) },
421
374
  * };
422
375
  *
423
- * const validate = createValidator(config, '[CONFIG]');
376
+ * const validate = createValidator(config);
424
377
  *
425
378
  * // Consumer only validates what it needs
426
379
  * const { app, kafka } = validate({
@@ -433,5 +386,81 @@ export type ExtractShape<C, S> = {
433
386
  * // redis is not accessible - TypeScript error
434
387
  * ```
435
388
  */
436
- export declare const createValidator: <C>(config: C, rootName?: string) => <S extends Schema<Partial<C>>>(schema: S) => ExtractShape<C, S>;
389
+ export declare const createValidator: <C>(config: C) => <S extends Schema<Partial<C>>>(schema: S) => ExtractShape<C, S>;
390
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
391
+ readonly '~standard': StandardSchemaV1.Props<Input, Output>;
392
+ }
393
+ export declare namespace StandardSchemaV1 {
394
+ interface Props<Input = unknown, Output = Input> {
395
+ readonly version: 1;
396
+ readonly vendor: string;
397
+ readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
398
+ readonly types?: {
399
+ readonly input: Input;
400
+ readonly output: Output;
401
+ };
402
+ }
403
+ type Result<Output> = SuccessResult<Output> | FailureResult;
404
+ interface SuccessResult<Output> {
405
+ readonly value: Output;
406
+ readonly issues?: undefined;
407
+ }
408
+ interface FailureResult {
409
+ readonly issues: ReadonlyArray<Issue>;
410
+ }
411
+ interface Issue {
412
+ readonly message: string;
413
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
414
+ }
415
+ interface PathSegment {
416
+ readonly key: PropertyKey;
417
+ }
418
+ }
419
+ /**
420
+ * Wraps an Ascertain schema to make it Standard Schema v1 compliant.
421
+ *
422
+ * Creates a validator that implements the Standard Schema specification,
423
+ * enabling interoperability with tools like tRPC, TanStack Form, and other
424
+ * ecosystem libraries that consume Standard Schema-compliant validators.
425
+ *
426
+ * The returned function can be used both as a regular Ascertain validator
427
+ * (throws on error) and as a Standard Schema validator (returns result object).
428
+ *
429
+ * @template T - The type of data the schema validates.
430
+ * @param schema - The Ascertain schema to wrap.
431
+ * @returns A function that validates data, with a `~standard` property for Standard Schema compliance.
432
+ *
433
+ * @see https://standardschema.dev/
434
+ *
435
+ * @example
436
+ * ```typescript
437
+ * import { standardSchema, or, optional } from 'ascertain';
438
+ *
439
+ * // Create a Standard Schema-compliant validator
440
+ * const userValidator = standardSchema({
441
+ * name: String,
442
+ * age: Number,
443
+ * role: or('admin', 'user'),
444
+ * email: optional(String),
445
+ * });
446
+ *
447
+ * // Use as regular Ascertain validator (throws on error)
448
+ * userValidator({ name: 'Alice', age: 30, role: 'admin' });
449
+ *
450
+ * // Use Standard Schema interface (returns result object)
451
+ * const result = userValidator['~standard'].validate(unknownData);
452
+ * if (result.issues) {
453
+ * console.log(result.issues);
454
+ * } else {
455
+ * console.log(result.value); // typed as User
456
+ * }
457
+ *
458
+ * // Works with tRPC, TanStack Form, etc.
459
+ * ```
460
+ */
461
+ interface StandardSchemaFn<T> {
462
+ (data: T): void;
463
+ '~standard': StandardSchemaV1.Props<T, T>;
464
+ }
465
+ export declare const standardSchema: <T>(schema: Schema<T>) => StandardSchemaFn<T>;
437
466
  export {};