ascertain 2.1.0 → 3.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/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,33 @@ 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
+ interface OrShape<T> {
20
+ readonly schemas: Schema<T>[];
21
+ readonly [$op]: typeof OR;
22
+ }
23
+ interface AndShape<T> {
24
+ readonly schemas: Schema<T>[];
25
+ readonly [$op]: typeof AND;
26
+ }
27
+ interface OptionalShape<T> {
28
+ readonly schemas: Schema<T>[];
29
+ readonly [$op]: typeof OPTIONAL;
30
+ }
31
+ interface TupleShape<T> {
32
+ readonly schemas: Schema<T>[];
33
+ readonly [$op]: typeof TUPLE;
34
+ }
35
+ interface DiscriminatedShape<T> {
36
+ readonly schemas: Schema<T>[];
37
+ readonly [$op]: typeof DISCRIMINATED;
38
+ readonly key: string;
39
+ }
26
40
  /**
27
41
  * Represents a schema for validating data.
28
42
  *
@@ -39,165 +53,42 @@ export type Schema<T> = T extends Record<string | number | symbol, unknown> ? {
39
53
  } & {
40
54
  [$strict]?: boolean;
41
55
  } : T extends Array<infer A> ? Schema<A>[] | unknown : unknown;
42
- declare class Or<T> extends Operator<T> {
43
- }
44
56
  /**
45
57
  * 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
58
  */
75
- export declare const or: <T>(...schemas: Schema<T>[]) => Or<T>;
76
- declare class And<T> extends Operator<T> {
77
- }
59
+ export declare const or: <T>(...schemas: Schema<T>[]) => OrShape<T>;
78
60
  /**
79
61
  * 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
62
  */
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
- }
63
+ export declare const and: <T>(...schemas: Schema<T>[]) => AndShape<T>;
110
64
  /**
111
65
  * 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
66
  */
160
- export declare const optional: <T>(schema: Schema<T>) => Optional<T>;
161
- declare class Tuple<T> extends Operator<T> {
162
- }
67
+ export declare const optional: <T>(schema: Schema<T>) => OptionalShape<T>;
163
68
  /**
164
69
  * Operator for validating data against a fixed-length tuple of schemas.
70
+ */
71
+ export declare const tuple: <T>(...schemas: Schema<T>[]) => TupleShape<T>;
72
+ /**
73
+ * Operator for validating data against a discriminated union.
165
74
  *
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.
75
+ * Optimizes validation by checking the discriminant field first and only
76
+ * validating the matching variant. More efficient than `or()` for unions
77
+ * where each variant has a common field with a unique literal value.
168
78
  *
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.
79
+ * @param schemas - Array of object schemas, each with a discriminant field containing a literal value.
80
+ * @param key - The name of the discriminant field present in all variants.
172
81
  *
173
82
  * @example
174
83
  * ```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
84
+ * const messageSchema = discriminated([
85
+ * { type: 'email', address: String },
86
+ * { type: 'sms', phone: String },
87
+ * { type: 'push', token: String },
88
+ * ], 'type');
198
89
  * ```
199
90
  */
200
- export declare const tuple: <T>(...schemas: Schema<T>[]) => Tuple<T>;
91
+ export declare const discriminated: <T>(schemas: Schema<T>[], key: string) => DiscriminatedShape<T>;
201
92
  /**
202
93
  * Decodes a base64-encoded string to UTF-8.
203
94
  *
@@ -287,19 +178,40 @@ export declare const as: {
287
178
  base64: (value: string | undefined) => string;
288
179
  };
289
180
  /**
290
- * Compiles a schema into a validation function.
181
+ * Validator function returned by compile().
182
+ * Returns true if valid, false if invalid.
183
+ * Access `.issues` property after validation to get error details.
184
+ */
185
+ export interface Validator<T> {
186
+ (data: T): boolean;
187
+ issues: ReadonlyArray<StandardSchemaV1.Issue>;
188
+ }
189
+ /**
190
+ * Options for the compile function.
191
+ */
192
+ export interface CompileOptions {
193
+ /**
194
+ * When true, collects all validation errors instead of stopping at the first.
195
+ * Default is false (first-error mode) for optimal performance.
196
+ */
197
+ allErrors?: boolean;
198
+ }
199
+ /**
200
+ * Compiles a schema into a high-performance validation function.
291
201
  *
292
- * This function takes a schema definition and generates a JavaScript function
293
- * that can be used to validate data against the schema.
202
+ * By default uses first-error mode which stops at the first validation failure
203
+ * and returns immediately. This provides optimal performance for invalid data.
204
+ *
205
+ * Set `allErrors: true` to collect all validation errors (slower but more informative).
294
206
  *
295
207
  * @template T - The type of data the schema validates.
296
208
  * @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.
209
+ * @param options - Optional configuration (allErrors: boolean).
210
+ * @returns A validator function that returns boolean. Access `.issues` for error details.
299
211
  *
300
212
  * @example
301
213
  * ```typescript
302
- * import { compile, optional, and, or } from 'ascertain';
214
+ * import { compile, optional, or } from 'ascertain';
303
215
  *
304
216
  * const userSchema = {
305
217
  * name: String,
@@ -308,28 +220,24 @@ export declare const as: {
308
220
  * role: or('admin', 'user', 'guest')
309
221
  * };
310
222
  *
311
- * const validateUser = compile(userSchema, 'User');
223
+ * // First-error mode (default) - fastest for invalid data
224
+ * const validate = compile(userSchema);
312
225
  *
313
- * // Valid data - no error thrown
314
- * validateUser({
315
- * name: 'John Doe',
316
- * age: 30,
317
- * email: 'john@example.com',
318
- * role: 'user'
319
- * });
226
+ * // All-errors mode - collects all validation issues
227
+ * const validateAll = compile(userSchema, { allErrors: true });
320
228
  *
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
229
+ * // Valid data
230
+ * if (validate({ name: 'John', age: 30, role: 'user' })) {
231
+ * console.log('Valid!');
232
+ * }
233
+ *
234
+ * // Invalid data - check .issues for details
235
+ * if (!validate({ name: 123, age: 'thirty' })) {
236
+ * console.log(validate.issues); // Array with first validation issue
329
237
  * }
330
238
  * ```
331
239
  */
332
- export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data: T) => any;
240
+ export declare const compile: <T>(schema: Schema<T>, options?: CompileOptions) => Validator<T>;
333
241
  /**
334
242
  * Asserts that data conforms to a given schema.
335
243
  *
@@ -339,7 +247,6 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
339
247
  * @template T - The type of data the schema validates.
340
248
  * @param schema - The schema to validate against.
341
249
  * @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
250
  * @throws `{TypeError}` If the data does not conform to the schema.
344
251
  *
345
252
  * @example
@@ -361,7 +268,7 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
361
268
  * };
362
269
  *
363
270
  * // Validate data - throws if invalid, otherwise continues silently
364
- * ascertain(userSchema, userData, 'UserData');
271
+ * ascertain(userSchema, userData);
365
272
  * console.log('User data is valid!');
366
273
  *
367
274
  * // Example with invalid data
@@ -370,7 +277,7 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
370
277
  * name: 'Bob',
371
278
  * age: 'twenty-five', // Invalid: should be number
372
279
  * active: true
373
- * }, 'UserData');
280
+ * });
374
281
  * } catch (error) {
375
282
  * console.error('Validation failed:', error.message);
376
283
  * }
@@ -382,16 +289,16 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
382
289
  * const numbersSchema = [Number];
383
290
  * const numbers = [1, 2, 3, 4, 5];
384
291
  *
385
- * ascertain(numbersSchema, numbers, 'Numbers');
292
+ * ascertain(numbersSchema, numbers);
386
293
  *
387
294
  * // Tuple validation
388
295
  * const coordinateSchema = tuple(Number, Number);
389
296
  * const point = [10, 20];
390
297
  *
391
- * ascertain(coordinateSchema, point, 'Point');
298
+ * ascertain(coordinateSchema, point);
392
299
  * ```
393
300
  */
394
- export declare const ascertain: <T>(schema: Schema<T>, data: T, rootName?: string) => void;
301
+ export declare const ascertain: <T>(schema: Schema<T>, data: T) => void;
395
302
  /**
396
303
  * Extracts the shape of a config object based on the schema keys.
397
304
  * Recursively picks only the properties defined in the schema.
@@ -407,7 +314,6 @@ export type ExtractShape<C, S> = {
407
314
  *
408
315
  * @template C - The type of the config object.
409
316
  * @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
317
  * @returns A validator function that takes a schema and returns the typed config subset.
412
318
  *
413
319
  * @example
@@ -420,7 +326,7 @@ export type ExtractShape<C, S> = {
420
326
  * redis: { host: as.string(process.env.REDIS_HOST) },
421
327
  * };
422
328
  *
423
- * const validate = createValidator(config, '[CONFIG]');
329
+ * const validate = createValidator(config);
424
330
  *
425
331
  * // Consumer only validates what it needs
426
332
  * const { app, kafka } = validate({
@@ -433,5 +339,81 @@ export type ExtractShape<C, S> = {
433
339
  * // redis is not accessible - TypeScript error
434
340
  * ```
435
341
  */
436
- export declare const createValidator: <C>(config: C, rootName?: string) => <S extends Schema<Partial<C>>>(schema: S) => ExtractShape<C, S>;
342
+ export declare const createValidator: <C>(config: C) => <S extends Schema<Partial<C>>>(schema: S) => ExtractShape<C, S>;
343
+ export interface StandardSchemaV1<Input = unknown, Output = Input> {
344
+ readonly '~standard': StandardSchemaV1.Props<Input, Output>;
345
+ }
346
+ export declare namespace StandardSchemaV1 {
347
+ interface Props<Input = unknown, Output = Input> {
348
+ readonly version: 1;
349
+ readonly vendor: string;
350
+ readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>;
351
+ readonly types?: {
352
+ readonly input: Input;
353
+ readonly output: Output;
354
+ };
355
+ }
356
+ type Result<Output> = SuccessResult<Output> | FailureResult;
357
+ interface SuccessResult<Output> {
358
+ readonly value: Output;
359
+ readonly issues?: undefined;
360
+ }
361
+ interface FailureResult {
362
+ readonly issues: ReadonlyArray<Issue>;
363
+ }
364
+ interface Issue {
365
+ readonly message: string;
366
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
367
+ }
368
+ interface PathSegment {
369
+ readonly key: PropertyKey;
370
+ }
371
+ }
372
+ /**
373
+ * Wraps an Ascertain schema to make it Standard Schema v1 compliant.
374
+ *
375
+ * Creates a validator that implements the Standard Schema specification,
376
+ * enabling interoperability with tools like tRPC, TanStack Form, and other
377
+ * ecosystem libraries that consume Standard Schema-compliant validators.
378
+ *
379
+ * The returned function can be used both as a regular Ascertain validator
380
+ * (throws on error) and as a Standard Schema validator (returns result object).
381
+ *
382
+ * @template T - The type of data the schema validates.
383
+ * @param schema - The Ascertain schema to wrap.
384
+ * @returns A function that validates data, with a `~standard` property for Standard Schema compliance.
385
+ *
386
+ * @see https://standardschema.dev/
387
+ *
388
+ * @example
389
+ * ```typescript
390
+ * import { standardSchema, or, optional } from 'ascertain';
391
+ *
392
+ * // Create a Standard Schema-compliant validator
393
+ * const userValidator = standardSchema({
394
+ * name: String,
395
+ * age: Number,
396
+ * role: or('admin', 'user'),
397
+ * email: optional(String),
398
+ * });
399
+ *
400
+ * // Use as regular Ascertain validator (throws on error)
401
+ * userValidator({ name: 'Alice', age: 30, role: 'admin' });
402
+ *
403
+ * // Use Standard Schema interface (returns result object)
404
+ * const result = userValidator['~standard'].validate(unknownData);
405
+ * if (result.issues) {
406
+ * console.log(result.issues);
407
+ * } else {
408
+ * console.log(result.value); // typed as User
409
+ * }
410
+ *
411
+ * // Works with tRPC, TanStack Form, etc.
412
+ * ```
413
+ */
414
+ interface StandardSchemaFn<T> {
415
+ (data: T): void;
416
+ '~standard': StandardSchemaV1.Props<T, T>;
417
+ }
418
+ export declare const standardSchema: <T>(schema: Schema<T>) => StandardSchemaFn<T>;
437
419
  export {};