UNPKG

ascertain

Version:

0-Deps, simple, fast, for browser and node js object schema validator

554 lines (553 loc) 19.5 kB
/** * Symbol for validating object keys against a schema. */ export declare const $keys: unique symbol; /** * Symbol for validating object values against a schema. */ export declare const $values: unique symbol; /** * Symbol for enforcing strict object validation (no extra properties allowed). */ export declare const $strict: unique symbol; declare const $op: unique symbol; declare const OR: unique symbol; declare const AND: unique symbol; declare const OPTIONAL: unique symbol; declare const TUPLE: unique symbol; declare const DISCRIMINATED: unique symbol; declare const CHECK: unique symbol; interface OrShape<T> { readonly schemas: Schema<T>[]; readonly [$op]: typeof OR; } interface AndShape<T> { readonly schemas: Schema<T>[]; readonly [$op]: typeof AND; } interface OptionalShape<T> { readonly schemas: Schema<T>[]; readonly [$op]: typeof OPTIONAL; } interface TupleShape<T> { readonly schemas: Schema<T>[]; readonly [$op]: typeof TUPLE; } interface DiscriminatedShape<T> { readonly schemas: Schema<T>[]; readonly [$op]: typeof DISCRIMINATED; readonly key: string; } export interface CheckContext { ref(value: unknown): string; } interface CheckShape { readonly [$op]: typeof CHECK; readonly compile: (value: string, ctx: CheckContext) => { check: string; message: string; }; } /** * Represents a schema for validating data. * * Schemas can be defined for various data types, including objects, arrays, and primitives. * * @template T - The type of data the schema validates. */ export type Schema<T> = T extends Record<string | number | symbol, unknown> ? { [K in keyof T]?: Schema<T[K]> | unknown; } & { [$keys]?: Schema<keyof T>; } & { [$values]?: Schema<T[keyof T]>; } & { [$strict]?: boolean; } : T extends Array<infer A> ? Schema<A>[] | unknown : unknown; /** * Operator for validating data against any of the provided schemas (logical OR). */ export declare const or: <T>(...schemas: Schema<T>[]) => OrShape<T>; /** * Operator for validating data against all provided schemas (logical AND). */ export declare const and: <T>(...schemas: Schema<T>[]) => AndShape<T>; /** * Operator for making a schema optional (nullable). */ export declare const optional: <T>(schema: Schema<T>) => OptionalShape<T>; /** * Operator for validating data against a fixed-length tuple of schemas. */ export declare const tuple: <T>(...schemas: Schema<T>[]) => TupleShape<T>; /** * Operator for validating data against a discriminated union. * * Optimizes validation by checking the discriminant field first and only * validating the matching variant. More efficient than `or()` for unions * where each variant has a common field with a unique literal value. * * @param schemas - Array of object schemas, each with a discriminant field containing a literal value. * @param key - The name of the discriminant field present in all variants. * * @example * ```typescript * const messageSchema = discriminated([ * { type: 'email', address: String }, * { type: 'sms', phone: String }, * { type: 'push', token: String }, * ], 'type'); * ``` */ export declare const discriminated: <T>(schemas: Schema<T>[], key: string) => DiscriminatedShape<T>; /** * Creates a custom validation check. * Accepts a predicate function or an object with a compile method for inlined checks. * * @param fnOrOpts - A predicate function `(value) => boolean` or an object with a `compile` method for code-generating checks. * @param message - Optional custom error message. */ export declare const check: (fnOrOpts: ((v: unknown) => boolean) | { compile: (value: string, ctx: CheckContext) => { check: string; message: string; }; }, message?: string) => CheckShape; /** * Validates that a numeric value is greater than or equal to `n`. * * @param n - The minimum allowed value (inclusive). * @param message - Optional custom error message. */ export declare const min: (n: number, message?: string) => CheckShape; /** * Validates that a numeric value is less than or equal to `n`. * * @param n - The maximum allowed value (inclusive). * @param message - Optional custom error message. */ export declare const max: (n: number, message?: string) => CheckShape; /** * Validates that a value is an integer. * * @param message - Optional custom error message. */ export declare const integer: (message?: string) => CheckShape; /** * Validates that a value's length is greater than or equal to `n`. * * @param n - The minimum allowed length (inclusive). * @param message - Optional custom error message. */ export declare const minLength: (n: number, message?: string) => CheckShape; /** * Validates that a value's length is less than or equal to `n`. * * @param n - The maximum allowed length (inclusive). * @param message - Optional custom error message. */ export declare const maxLength: (n: number, message?: string) => CheckShape; /** * Validates that a numeric value is strictly greater than `n`. * * @param n - The exclusive lower bound. * @param message - Optional custom error message. */ export declare const gt: (n: number, message?: string) => CheckShape; /** * Validates that a numeric value is strictly less than `n`. * * @param n - The exclusive upper bound. * @param message - Optional custom error message. */ export declare const lt: (n: number, message?: string) => CheckShape; /** * Validates that a numeric value is a multiple of `n`. * * @param n - The divisor to check against. * @param message - Optional custom error message. */ export declare const multipleOf: (n: number, message?: string) => CheckShape; /** * Validates that an array contains only unique items. * * @param message - Optional custom error message. */ export declare const uniqueItems: (message?: string) => CheckShape; type EnumLike = { [k: string]: string | number; [n: number]: string; }; /** * Validates that a value is one of the allowed values. Accepts an array or an enum-like object. * * @param values - Array of allowed values or an enum-like object. * @param message - Optional custom error message. */ export declare const oneOf: <T extends EnumLike>(values: (string | number)[] | T, message?: string) => CheckShape; /** * Decodes a base64-encoded string to UTF-8. * * Uses `Buffer` in Node.js environments and `atob` in browsers. * * @param value - The base64-encoded string to decode. * @returns The decoded UTF-8 string. */ export declare const fromBase64: (value: string) => string; /** * Creates a TypeError with the given message, typed as T for deferred error handling. * * Used by `as.*` conversion utilities to return errors that can be caught * during schema validation rather than throwing immediately. * * @template T - The expected return type (for type compatibility with conversion functions). * @param message - The error message. * @returns A TypeError instance typed as T. */ export declare const asError: <T>(message: string) => T; /** * Type casting utilities for parsing strings into typed values. * Useful for environment variables, query parameters, and other string inputs. * Returns a TypeError for invalid values, enabling deferred validation with `ascertain()`. */ export declare const as: { /** * Attempts to convert a value to a string. * * @param value - The value to convert. * @returns The value as a string, or a TypeError if not a string. */ string: (value: string | undefined) => string; /** * Attempts to convert a value to a number. * * Supports integers, floats, scientific notation (1e10), and prefixed formats: * - Hexadecimal: `0x` or `0X` (e.g., `'0xFF'` → 255) * - Octal: `0o` or `0O` (e.g., `'0o77'` → 63) * - Binary: `0b` or `0B` (e.g., `'0b1010'` → 10) * * All formats support optional leading sign (`+` or `-`). * * @param value - The value to convert (expected to be a string representation of a number). * @returns The value as a number, or a TypeError if not a valid number. */ number: (value: string | undefined) => number; /** * Attempts to convert a value to a Date object. * * @param value - The value to convert (expected to be a string representation of a date). * @returns The value as a Date object, or a TypeError if not a valid date. */ date: (value: string | undefined) => Date; /** * Attempts to convert a value to a time duration in milliseconds. * * @param value - The value to convert (e.g., "5s" for 5 seconds). * @param conversionFactor - Optional factor to divide the result by (default is 1). * @returns The time duration in milliseconds, or a TypeError if the format is invalid. */ time: (value: string | undefined, conversionFactor?: number) => number; /** * Attempts to convert a value to a boolean. * * @param value - The boolean like value to convert (e.g., "true", "1", "enabled"). * @returns The value as a boolean, or a TypeError if it could not be converted to a boolean. */ boolean: (value: string | undefined) => boolean; /** * Attempts to convert a string into an array of strings by splitting it using the given delimiter. * * @param value - The string value to attempt to split into an array. * @param delimiter - The character or string used to separate elements in the input string. * @returns An array of strings if the conversion is successful, or a TypeError if the value is not a string. */ array: (value: string | undefined, delimiter: string) => string[]; /** * Attempts to parse a JSON string into a JavaScript object. * * @template T - The expected type of the parsed JSON object. * @param value - The JSON string to attempt to parse. * @returns The parsed JSON object if successful, or a TypeError if the value is not valid JSON. */ json: <T = object>(value: string | undefined) => T; /** * Attempts to decode a base64-encoded string. * * @param value - The base64-encoded string to attempt to decode. * @returns The decoded string if successful, or a TypeError if the value is not valid base64. */ base64: (value: string | undefined) => string; }; /** * String format validators for common patterns (RFC 3339 date-time, email, URI, UUID, etc.). * Each method returns a CheckShape that can be composed with `and()` for schema validation. */ export declare const format: { dateTime: (message?: string) => CheckShape; date: (message?: string) => CheckShape; time: (message?: string) => CheckShape; duration: (message?: string) => CheckShape; email: (message?: string) => CheckShape; idnEmail: (message?: string) => CheckShape; hostname: (message?: string) => CheckShape; idnHostname: (message?: string) => CheckShape; ipv4: (message?: string) => CheckShape; ipv6: (message?: string) => CheckShape; uri: (message?: string) => CheckShape; uriReference: (message?: string) => CheckShape; iri: (message?: string) => CheckShape; iriReference: (message?: string) => CheckShape; uuid: (message?: string) => CheckShape; uriTemplate: (message?: string) => CheckShape; jsonPointer: (message?: string) => CheckShape; relativeJsonPointer: (message?: string) => CheckShape; regex: (message?: string) => CheckShape; }; /** * Validator function returned by compile(). * Returns true if valid, false if invalid. * Access `.issues` property after validation to get error details. */ export interface Validator<T> { (data: T): boolean; issues: ReadonlyArray<StandardSchemaV1.Issue>; } /** * Options for the compile function. */ export interface CompileOptions { /** * When true, collects all validation errors instead of stopping at the first. * Default is false (first-error mode) for optimal performance. */ allErrors?: boolean; } /** * Compiles a schema into a high-performance validation function. * * By default uses first-error mode which stops at the first validation failure * and returns immediately. This provides optimal performance for invalid data. * * Set `allErrors: true` to collect all validation errors (slower but more informative). * * @template T - The type of data the schema validates. * @param schema - The schema to compile. * @param options - Optional configuration (allErrors: boolean). * @returns A validator function that returns boolean. Access `.issues` for error details. * * @example * ```typescript * import { compile, optional, or } from 'ascertain'; * * const userSchema = { * name: String, * age: Number, * email: optional(String), * role: or('admin', 'user', 'guest') * }; * * // First-error mode (default) - fastest for invalid data * const validate = compile(userSchema); * * // All-errors mode - collects all validation issues * const validateAll = compile(userSchema, { allErrors: true }); * * // Valid data * if (validate({ name: 'John', age: 30, role: 'user' })) { * console.log('Valid!'); * } * * // Invalid data - check .issues for details * if (!validate({ name: 123, age: 'thirty' })) { * console.log(validate.issues); // Array with first validation issue * } * ``` */ export declare const compile: <T>(schema: Schema<T>, options?: CompileOptions) => Validator<T>; /** * Asserts that data conforms to a given schema. * * This function is a convenient wrapper around `compile`. It compiles the schema * and immediately validates the provided data against it. * * @template T - The type of data the schema validates. * @param schema - The schema to validate against. * @param data - The data to validate. * @throws `{TypeError}` If the data does not conform to the schema. * * @example * ```typescript * import { ascertain, optional, and, or } from 'ascertain'; * * const userSchema = { * name: String, * age: Number, * email: optional(String), * active: Boolean * }; * * const userData = { * name: 'Alice', * age: 25, * email: 'alice@example.com', * active: true * }; * * // Validate data - throws if invalid, otherwise continues silently * ascertain(userSchema, userData); * console.log('User data is valid!'); * * // Example with invalid data * try { * ascertain(userSchema, { * name: 'Bob', * age: 'twenty-five', // Invalid: should be number * active: true * }); * } catch (error) { * console.error('Validation failed:', error.message); * } * ``` * * @example * ```typescript * // Array validation * const numbersSchema = [Number]; * const numbers = [1, 2, 3, 4, 5]; * * ascertain(numbersSchema, numbers); * * // Tuple validation * const coordinateSchema = tuple(Number, Number); * const point = [10, 20]; * * ascertain(coordinateSchema, point); * ``` */ export declare const ascertain: <T>(schema: Schema<T>, data: T) => void; /** * Extracts the shape of a config object based on the schema keys. * Recursively picks only the properties defined in the schema. */ export type ExtractShape<C, S> = { [K in keyof S & keyof C]: S[K] extends object ? (C[K] extends object ? ExtractShape<C[K], S[K]> : C[K]) : C[K]; }; /** * Creates a typed validator function for a config object. * * Returns a function that validates a schema against the config and returns * the same config reference with a narrowed type containing only the validated fields. * * @template C - The type of the config object. * @param config - The config object to validate against. * @returns A validator function that takes a schema and returns the typed config subset. * * @example * ```typescript * import { createValidator, as } from 'ascertain'; * * const config = { * app: { name: as.string(process.env.APP_NAME) }, * kafka: { brokers: as.array(process.env.BROKERS, ',') }, * redis: { host: as.string(process.env.REDIS_HOST) }, * }; * * const validate = createValidator(config); * * // Consumer only validates what it needs * const { app, kafka } = validate({ * app: { name: String }, * kafka: { brokers: [String] }, * }); * * // app.name is typed as string * // kafka.brokers is typed as string[] * // redis is not accessible - TypeScript error * ``` */ export declare const createValidator: <C>(config: C) => <S extends Schema<Partial<C>>>(schema: S) => ExtractShape<C, S>; export interface StandardSchemaV1<Input = unknown, Output = Input> { readonly '~standard': StandardSchemaV1.Props<Input, Output>; } export declare namespace StandardSchemaV1 { interface Props<Input = unknown, Output = Input> { readonly version: 1; readonly vendor: string; readonly validate: (value: unknown) => Result<Output> | Promise<Result<Output>>; readonly types?: { readonly input: Input; readonly output: Output; }; } type Result<Output> = SuccessResult<Output> | FailureResult; interface SuccessResult<Output> { readonly value: Output; readonly issues?: undefined; } interface FailureResult { readonly issues: ReadonlyArray<Issue>; } interface Issue { readonly message: string; readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined; } interface PathSegment { readonly key: PropertyKey; } } /** * Wraps an Ascertain schema to make it Standard Schema v1 compliant. * * Creates a validator that implements the Standard Schema specification, * enabling interoperability with tools like tRPC, TanStack Form, and other * ecosystem libraries that consume Standard Schema-compliant validators. * * The returned function can be used both as a regular Ascertain validator * (throws on error) and as a Standard Schema validator (returns result object). * * @template T - The type of data the schema validates. * @param schema - The Ascertain schema to wrap. * @returns A function that validates data, with a `~standard` property for Standard Schema compliance. * * @see https://standardschema.dev/ * * @example * ```typescript * import { standardSchema, or, optional } from 'ascertain'; * * // Create a Standard Schema-compliant validator * const userValidator = standardSchema({ * name: String, * age: Number, * role: or('admin', 'user'), * email: optional(String), * }); * * // Use as regular Ascertain validator (throws on error) * userValidator({ name: 'Alice', age: 30, role: 'admin' }); * * // Use Standard Schema interface (returns result object) * const result = userValidator['~standard'].validate(unknownData); * if (result.issues) { * console.log(result.issues); * } else { * console.log(result.value); // typed as User * } * * // Works with tRPC, TanStack Form, etc. * ``` */ interface StandardSchemaFn<T> { (data: T): void; '~standard': StandardSchemaV1.Props<T, T>; } /** * Wraps a schema for Standard Schema v1 compliance. * The returned function throws on invalid data and exposes a `~standard` interface * for interoperability with tRPC, TanStack Form, and other ecosystem tools. * * @param schema - The schema to wrap. * @returns A validator function with a `~standard` property conforming to Standard Schema v1. */ export declare const standardSchema: <T>(schema: Schema<T>) => StandardSchemaFn<T>; export {};