ascertain
Version:
0-Deps, simple, fast, for browser and node js object schema validator
375 lines (374 loc) • 12.7 kB
TypeScript
/**
* Abstract base class for schema operators.
*
* Provides a common constructor that enforces having at least one schema.
*
* @template T - The type of data the operator validates.
* @abstract
* @internal
*/
declare abstract class Operator<T> {
readonly schemas: Schema<T>[];
constructor(schemas: Schema<T>[]);
}
/**
* 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;
/**
* 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;
declare class Or<T> extends Operator<T> {
}
/**
* Operator for validating data against any of the provided schemas (logical OR).
*
* Creates a schema that accepts data matching any one of the provided schemas.
* This is useful for creating union types or alternative validation paths.
*
* @template T - The type of data the operator validates.
* @param schemas - Multiple schemas where at least one must match the data.
* @returns A schema that validates data against any of the provided schemas.
*
* @example
* ```typescript
* import { or, ascertain } from 'ascertain';
*
* // Create a schema that accepts either a string or number
* const stringOrNumber = or(String, Number);
*
* ascertain(stringOrNumber, "hello", "value"); // ✓ Valid
* ascertain(stringOrNumber, 42, "value"); // ✓ Valid
* ascertain(stringOrNumber, true, "value"); // ✗ Throws error
*
* // Union of literal values
* const statusSchema = or('pending', 'completed', 'failed');
* ascertain(statusSchema, 'pending', "status"); // ✓ Valid
*
* // Complex schema combinations
* const userIdSchema = or(Number, { id: Number, temp: Boolean });
* ascertain(userIdSchema, 123, "userId"); // ✓ Valid
* ascertain(userIdSchema, { id: 456, temp: true }, "userId"); // ✓ Valid
* ```
*/
export declare const or: <T>(...schemas: Schema<T>[]) => Or<T>;
declare class And<T> extends Operator<T> {
}
/**
* Operator for validating data against all provided schemas (logical AND).
*
* Creates a schema that requires data to match every one of the provided schemas.
* This is useful for combining multiple validation requirements or adding constraints.
*
* @template T - The type of data the operator validates.
* @param schemas - Multiple schemas that all must match the data.
* @returns A schema that validates data against all of the provided schemas.
*
* @example
* ```typescript
* import { and, ascertain } from 'ascertain';
*
* // Combine object schema with additional constraints
* const userSchema = and(
* { name: String, age: Number },
* { age: Number } // Additional constraint
* );
*
* ascertain(userSchema, { name: "John", age: 25 }, "user"); // ✓ Valid
*
* // Ensure an object is both a Date and has specific methods
* const validDateSchema = and(Date, { toISOString: Function });
* ascertain(validDateSchema, new Date(), "date"); // ✓ Valid
*
* // Multiple validation layers
* const positiveNumberSchema = and(Number, (n: number) => n > 0);
* ascertain(positiveNumberSchema, 42, "count"); // ✓ Valid
* ascertain(positiveNumberSchema, -5, "count"); // ✗ Throws error
* ```
*/
export declare const and: <T>(...schemas: Schema<T>[]) => And<T>;
declare class Optional<T> extends Operator<T> {
constructor(schema: Schema<T>);
}
/**
* Operator for making a schema optional (nullable).
*
* Creates a schema that accepts the provided schema or null/undefined values.
* This is useful for optional object properties or nullable fields.
*
* @template T - The type of data the operator validates.
* @param schema - The schema to make optional.
* @returns A schema that validates data against the provided schema or accepts null/undefined.
*
* @example
* ```typescript
* import { optional, ascertain } from 'ascertain';
*
* // Optional string field
* const userSchema = {
* name: String,
* nickname: optional(String),
* age: Number
* };
*
* // All of these are valid
* ascertain(userSchema, {
* name: "John",
* nickname: "Johnny",
* age: 30
* }, "user"); // ✓ Valid
*
* ascertain(userSchema, {
* name: "Jane",
* nickname: null,
* age: 25
* }, "user"); // ✓ Valid
*
* ascertain(userSchema, {
* name: "Bob",
* age: 35
* // nickname is undefined
* }, "user"); // ✓ Valid
*
* // Optional complex objects
* const profileSchema = {
* id: Number,
* settings: optional({
* theme: String,
* notifications: Boolean
* })
* };
* ```
*/
export declare const optional: <T>(schema: Schema<T>) => Optional<T>;
declare class Tuple<T> extends Operator<T> {
}
/**
* Operator for validating data against a fixed-length tuple of schemas.
*
* Creates a schema that validates arrays with a specific length and type for each position.
* This is useful for coordinate pairs, RGB values, or any fixed-structure data.
*
* @template T - The type of data the operator validates (a tuple of types).
* @param schemas - Schemas for each position in the tuple, in order.
* @returns A schema that validates data as a tuple with the specified structure.
*
* @example
* ```typescript
* import { tuple, ascertain } from 'ascertain';
*
* // 2D coordinate tuple
* const pointSchema = tuple(Number, Number);
* ascertain(pointSchema, [10, 20], "point"); // ✓ Valid
* ascertain(pointSchema, [1.5, 2.7], "point"); // ✓ Valid
* ascertain(pointSchema, [10], "point"); // ✗ Throws error (too short)
* ascertain(pointSchema, [10, 20, 30], "point"); // ✗ Throws error (too long)
*
* // RGB color tuple
* const colorSchema = tuple(Number, Number, Number);
* ascertain(colorSchema, [255, 128, 0], "color"); // ✓ Valid
*
* // Mixed type tuple
* const userInfoSchema = tuple(String, Number, Boolean);
* ascertain(userInfoSchema, ["Alice", 25, true], "userInfo"); // ✓ Valid
*
* // Nested tuple
* const lineSchema = tuple(
* tuple(Number, Number), // start point
* tuple(Number, Number) // end point
* );
* ascertain(lineSchema, [[0, 0], [10, 10]], "line"); // ✓ Valid
* ```
*/
export declare const tuple: <T>(...schemas: Schema<T>[]) => Tuple<T>;
export declare const fromBase64: (value: string) => string;
export declare const asError: <T>(message: string) => T;
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.
*
* @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;
};
/**
* Compiles a schema into a validation function.
*
* This function takes a schema definition and generates a JavaScript function
* that can be used to validate data against the schema.
*
* @template T - The type of data the schema validates.
* @param schema - The schema to compile.
* @param rootName - A name for the root of the data structure (used in error messages).
* @returns A validation function that takes data as input and throws a TypeError if the data does not conform to the schema.
*
* @example
* ```typescript
* import { compile, optional, and, or } from 'ascertain';
*
* const userSchema = {
* name: String,
* age: Number,
* email: optional(String),
* role: or('admin', 'user', 'guest')
* };
*
* const validateUser = compile(userSchema, 'User');
*
* // Valid data - no error thrown
* validateUser({
* name: 'John Doe',
* age: 30,
* email: 'john@example.com',
* role: 'user'
* });
*
* // Invalid data - throws TypeError
* try {
* validateUser({
* name: 123, // Invalid: should be string
* age: 'thirty' // Invalid: should be number
* });
* } catch (error) {
* console.error(error.message); // Detailed validation errors
* }
* ```
*/
export declare const compile: <T>(schema: Schema<T>, rootName: string) => (data: T) => any;
/**
* 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.
* @param rootName - A name for the root of the data structure (used in error messages, defaults to '[root]').
* @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, '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
* }, 'UserData');
* } 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, 'Numbers');
*
* // Tuple validation
* const coordinateSchema = tuple(Number, Number);
* const point = [10, 20];
*
* ascertain(coordinateSchema, point, 'Point');
* ```
*/
export declare const ascertain: <T>(schema: Schema<T>, data: T, rootName?: string) => void;
export {};