ascertain 2.0.88 → 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,170 +53,61 @@ 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
- * // Multiple validation layers
105
- * const positiveNumberSchema = and(Number, (n: number) => n > 0);
106
- * ascertain(positiveNumberSchema, 42, "count"); // ✓ Valid
107
- * ascertain(positiveNumberSchema, -5, "count"); // ✗ Throws error
108
- * ```
109
62
  */
110
- export declare const and: <T>(...schemas: Schema<T>[]) => And<T>;
111
- declare class Optional<T> extends Operator<T> {
112
- constructor(schema: Schema<T>);
113
- }
63
+ export declare const and: <T>(...schemas: Schema<T>[]) => AndShape<T>;
114
64
  /**
115
65
  * Operator for making a schema optional (nullable).
66
+ */
67
+ export declare const optional: <T>(schema: Schema<T>) => OptionalShape<T>;
68
+ /**
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.
116
74
  *
117
- * Creates a schema that accepts the provided schema or null/undefined values.
118
- * This is useful for optional object properties or nullable fields.
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.
119
78
  *
120
- * @template T - The type of data the operator validates.
121
- * @param schema - The schema to make optional.
122
- * @returns A schema that validates data against the provided schema or accepts null/undefined.
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.
123
81
  *
124
82
  * @example
125
83
  * ```typescript
126
- * import { optional, ascertain } from 'ascertain';
127
- *
128
- * // Optional string field
129
- * const userSchema = {
130
- * name: String,
131
- * nickname: optional(String),
132
- * age: Number
133
- * };
134
- *
135
- * // All of these are valid
136
- * ascertain(userSchema, {
137
- * name: "John",
138
- * nickname: "Johnny",
139
- * age: 30
140
- * }, "user"); // ✓ Valid
141
- *
142
- * ascertain(userSchema, {
143
- * name: "Jane",
144
- * nickname: null,
145
- * age: 25
146
- * }, "user"); // ✓ Valid
147
- *
148
- * ascertain(userSchema, {
149
- * name: "Bob",
150
- * age: 35
151
- * // nickname is undefined
152
- * }, "user"); // ✓ Valid
153
- *
154
- * // Optional complex objects
155
- * const profileSchema = {
156
- * id: Number,
157
- * settings: optional({
158
- * theme: String,
159
- * notifications: Boolean
160
- * })
161
- * };
84
+ * const messageSchema = discriminated([
85
+ * { type: 'email', address: String },
86
+ * { type: 'sms', phone: String },
87
+ * { type: 'push', token: String },
88
+ * ], 'type');
162
89
  * ```
163
90
  */
164
- export declare const optional: <T>(schema: Schema<T>) => Optional<T>;
165
- declare class Tuple<T> extends Operator<T> {
166
- }
91
+ export declare const discriminated: <T>(schemas: Schema<T>[], key: string) => DiscriminatedShape<T>;
167
92
  /**
168
- * Operator for validating data against a fixed-length tuple of schemas.
93
+ * Decodes a base64-encoded string to UTF-8.
169
94
  *
170
- * Creates a schema that validates arrays with a specific length and type for each position.
171
- * This is useful for coordinate pairs, RGB values, or any fixed-structure data.
95
+ * Uses `Buffer` in Node.js environments and `atob` in browsers.
172
96
  *
173
- * @template T - The type of data the operator validates (a tuple of types).
174
- * @param schemas - Schemas for each position in the tuple, in order.
175
- * @returns A schema that validates data as a tuple with the specified structure.
176
- *
177
- * @example
178
- * ```typescript
179
- * import { tuple, ascertain } from 'ascertain';
180
- *
181
- * // 2D coordinate tuple
182
- * const pointSchema = tuple(Number, Number);
183
- * ascertain(pointSchema, [10, 20], "point"); // ✓ Valid
184
- * ascertain(pointSchema, [1.5, 2.7], "point"); // ✓ Valid
185
- * ascertain(pointSchema, [10], "point"); // ✗ Throws error (too short)
186
- * ascertain(pointSchema, [10, 20, 30], "point"); // ✗ Throws error (too long)
187
- *
188
- * // RGB color tuple
189
- * const colorSchema = tuple(Number, Number, Number);
190
- * ascertain(colorSchema, [255, 128, 0], "color"); // ✓ Valid
191
- *
192
- * // Mixed type tuple
193
- * const userInfoSchema = tuple(String, Number, Boolean);
194
- * ascertain(userInfoSchema, ["Alice", 25, true], "userInfo"); // ✓ Valid
195
- *
196
- * // Nested tuple
197
- * const lineSchema = tuple(
198
- * tuple(Number, Number), // start point
199
- * tuple(Number, Number) // end point
200
- * );
201
- * ascertain(lineSchema, [[0, 0], [10, 10]], "line"); // ✓ Valid
202
- * ```
97
+ * @param value - The base64-encoded string to decode.
98
+ * @returns The decoded UTF-8 string.
203
99
  */
204
- export declare const tuple: <T>(...schemas: Schema<T>[]) => Tuple<T>;
205
100
  export declare const fromBase64: (value: string) => string;
101
+ /**
102
+ * Creates a TypeError with the given message, typed as T for deferred error handling.
103
+ *
104
+ * Used by `as.*` conversion utilities to return errors that can be caught
105
+ * during schema validation rather than throwing immediately.
106
+ *
107
+ * @template T - The expected return type (for type compatibility with conversion functions).
108
+ * @param message - The error message.
109
+ * @returns A TypeError instance typed as T.
110
+ */
206
111
  export declare const asError: <T>(message: string) => T;
207
112
  export declare const as: {
208
113
  /**
@@ -215,6 +120,13 @@ export declare const as: {
215
120
  /**
216
121
  * Attempts to convert a value to a number.
217
122
  *
123
+ * Supports integers, floats, scientific notation (1e10), and prefixed formats:
124
+ * - Hexadecimal: `0x` or `0X` (e.g., `'0xFF'` → 255)
125
+ * - Octal: `0o` or `0O` (e.g., `'0o77'` → 63)
126
+ * - Binary: `0b` or `0B` (e.g., `'0b1010'` → 10)
127
+ *
128
+ * All formats support optional leading sign (`+` or `-`).
129
+ *
218
130
  * @param value - The value to convert (expected to be a string representation of a number).
219
131
  * @returns The value as a number, or a TypeError if not a valid number.
220
132
  */
@@ -266,19 +178,40 @@ export declare const as: {
266
178
  base64: (value: string | undefined) => string;
267
179
  };
268
180
  /**
269
- * 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.
201
+ *
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.
270
204
  *
271
- * This function takes a schema definition and generates a JavaScript function
272
- * that can be used to validate data against the schema.
205
+ * Set `allErrors: true` to collect all validation errors (slower but more informative).
273
206
  *
274
207
  * @template T - The type of data the schema validates.
275
208
  * @param schema - The schema to compile.
276
- * @param rootName - A name for the root of the data structure (used in error messages).
277
- * @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.
278
211
  *
279
212
  * @example
280
213
  * ```typescript
281
- * import { compile, optional, and, or } from 'ascertain';
214
+ * import { compile, optional, or } from 'ascertain';
282
215
  *
283
216
  * const userSchema = {
284
217
  * name: String,
@@ -287,28 +220,24 @@ export declare const as: {
287
220
  * role: or('admin', 'user', 'guest')
288
221
  * };
289
222
  *
290
- * const validateUser = compile(userSchema, 'User');
223
+ * // First-error mode (default) - fastest for invalid data
224
+ * const validate = compile(userSchema);
291
225
  *
292
- * // Valid data - no error thrown
293
- * validateUser({
294
- * name: 'John Doe',
295
- * age: 30,
296
- * email: 'john@example.com',
297
- * role: 'user'
298
- * });
226
+ * // All-errors mode - collects all validation issues
227
+ * const validateAll = compile(userSchema, { allErrors: true });
299
228
  *
300
- * // Invalid data - throws TypeError
301
- * try {
302
- * validateUser({
303
- * name: 123, // Invalid: should be string
304
- * age: 'thirty' // Invalid: should be number
305
- * });
306
- * } catch (error) {
307
- * 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
308
237
  * }
309
238
  * ```
310
239
  */
311
- 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>;
312
241
  /**
313
242
  * Asserts that data conforms to a given schema.
314
243
  *
@@ -318,7 +247,6 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
318
247
  * @template T - The type of data the schema validates.
319
248
  * @param schema - The schema to validate against.
320
249
  * @param data - The data to validate.
321
- * @param rootName - A name for the root of the data structure (used in error messages, defaults to '[root]').
322
250
  * @throws `{TypeError}` If the data does not conform to the schema.
323
251
  *
324
252
  * @example
@@ -340,7 +268,7 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
340
268
  * };
341
269
  *
342
270
  * // Validate data - throws if invalid, otherwise continues silently
343
- * ascertain(userSchema, userData, 'UserData');
271
+ * ascertain(userSchema, userData);
344
272
  * console.log('User data is valid!');
345
273
  *
346
274
  * // Example with invalid data
@@ -349,7 +277,7 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
349
277
  * name: 'Bob',
350
278
  * age: 'twenty-five', // Invalid: should be number
351
279
  * active: true
352
- * }, 'UserData');
280
+ * });
353
281
  * } catch (error) {
354
282
  * console.error('Validation failed:', error.message);
355
283
  * }
@@ -361,14 +289,131 @@ export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data:
361
289
  * const numbersSchema = [Number];
362
290
  * const numbers = [1, 2, 3, 4, 5];
363
291
  *
364
- * ascertain(numbersSchema, numbers, 'Numbers');
292
+ * ascertain(numbersSchema, numbers);
365
293
  *
366
294
  * // Tuple validation
367
295
  * const coordinateSchema = tuple(Number, Number);
368
296
  * const point = [10, 20];
369
297
  *
370
- * ascertain(coordinateSchema, point, 'Point');
298
+ * ascertain(coordinateSchema, point);
371
299
  * ```
372
300
  */
373
- 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;
302
+ /**
303
+ * Extracts the shape of a config object based on the schema keys.
304
+ * Recursively picks only the properties defined in the schema.
305
+ */
306
+ export type ExtractShape<C, S> = {
307
+ [K in keyof S & keyof C]: S[K] extends object ? (C[K] extends object ? ExtractShape<C[K], S[K]> : C[K]) : C[K];
308
+ };
309
+ /**
310
+ * Creates a typed validator function for a config object.
311
+ *
312
+ * Returns a function that validates a schema against the config and returns
313
+ * the same config reference with a narrowed type containing only the validated fields.
314
+ *
315
+ * @template C - The type of the config object.
316
+ * @param config - The config object to validate against.
317
+ * @returns A validator function that takes a schema and returns the typed config subset.
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * import { createValidator, as } from 'ascertain';
322
+ *
323
+ * const config = {
324
+ * app: { name: as.string(process.env.APP_NAME) },
325
+ * kafka: { brokers: as.array(process.env.BROKERS, ',') },
326
+ * redis: { host: as.string(process.env.REDIS_HOST) },
327
+ * };
328
+ *
329
+ * const validate = createValidator(config);
330
+ *
331
+ * // Consumer only validates what it needs
332
+ * const { app, kafka } = validate({
333
+ * app: { name: String },
334
+ * kafka: { brokers: [String] },
335
+ * });
336
+ *
337
+ * // app.name is typed as string
338
+ * // kafka.brokers is typed as string[]
339
+ * // redis is not accessible - TypeScript error
340
+ * ```
341
+ */
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>;
374
419
  export {};