const expectedToBe = (type: string): string => `expected to be ${type}`; export type WeakAssert = (input: unknown, message?: string) => void; export type SubType = Output extends Input ? Output : never; export type Assert = ( input: Input, message?: string, ) => asserts input is SubType; export type Check = ( input: Input, ) => input is SubType; export const defaultAssert: WeakAssert = (condition, message) => { if (!condition) { throw new TypeError(message); } }; let baseAssert = defaultAssert; export const assert: Assert = (condition, message) => baseAssert(condition, message); export function setBaseAssert(assert?: WeakAssert): void { if (assert) { baseAssert = assert; } } export const safeJsonParse = (json: string): unknown => JSON.parse(json) as unknown; export function isUnknown(_input: unknown): _input is unknown { return true; } export function isNever( _input: never, message: string = expectedToBe("unreachable"), ): never { throw new TypeError(message) } export function isNotNull( input: null | T, message: string = expectedToBe("not null"), ): asserts input is T { assert(input !== null, message); } export function isNotUndefined( input: undefined | T, message: string = expectedToBe("not undefined"), ): asserts input is T { assert(input !== undefined, message); } export function isNotVoid( input: T, message: string = expectedToBe("neither null nor undefined"), ): asserts input is Exclude { assert(input !== null && input !== undefined, message); } export function isExactly( input: Input, value: Output, message = expectedToBe(`exactly ${value}`), ): asserts input is SubType { assert((input as unknown) === (value as unknown), message); } export function isBoolean( input: unknown, message: string = expectedToBe("a boolean"), ): asserts input is boolean { assert(typeof input === "boolean", message); } export function isNumber( input: unknown, message: string = expectedToBe("a number"), ): asserts input is number { assert(typeof input === "number", message); } export function isString( input: unknown, message: string = expectedToBe("a string"), ): asserts input is string { assert(typeof input === "string", message); } export function isDate( input: unknown, message: string = expectedToBe("a Date"), ): asserts input is Date { assert(input instanceof Date, message); } export function isRecord( input: unknown, message: string = expectedToBe("a record"), ): asserts input is Record { assert(typeof input === "object", message); isNotNull(input, message); for (const key of Object.keys(input as Record)) { isString(key, message); } } export function isRecordWithKeys( input: unknown, keys: K[], message = expectedToBe(`a record with keys ${keys.join(", ")}`), ): asserts input is { readonly [Key in K]: unknown; } { isRecord(input, message); for (const key of keys) { isNotUndefined(input[key]); } } export function isArray( input: unknown, message: string = expectedToBe("an array"), ): asserts input is unknown[] { assert(Array.isArray(input), message); } export function isRecordOfType( input: unknown, assertT: Assert, message = expectedToBe("a record of given type"), itemMessage = expectedToBe("of given type"), ): asserts input is Record { isRecord(input, message); for (const item of Object.values(input)) { assertT(item, itemMessage); } } export function isArrayOfType( input: unknown, assertT: Assert, message = expectedToBe("an array of given type"), itemMessage = expectedToBe("of given type"), ): asserts input is T[] { isArray(input, message); for (const item of input) { assertT(item, itemMessage); } } export function isOptionOfType( input: Input | undefined, assertT: Assert, message = expectedToBe("option of given type"), ): asserts input is SubType { if (input === undefined) { return; } assertT(input, message); } export function isOneOf( input: Input, values: readonly Output[], message: string = expectedToBe(`one of ${values.join(", ")}`), ): asserts input is SubType { assert(values.includes(input as SubType), message); } export function isOneOfType( input: unknown, assertT: Assert[], message: string = expectedToBe(`one of type`), itemMessage?: string, ): asserts input is T { for (const assert of assertT) { try { (assert as WeakAssert)(input as T, itemMessage); return; } catch (_) {} } throw new TypeError(message); } export function isInstanceOf( input: unknown, // eslint-disable-next-line @typescript-eslint/no-explicit-any constructor: new (...args: any[]) => T, message = expectedToBe("an instance of given constructor"), ): asserts input is T { assert(input instanceof constructor, message); } export function isPromise( input: unknown, message = expectedToBe("a promise"), ): asserts input is Promise { isInstanceOf(input, Promise, message); } export function check( assertT: Assert, ): Check { return (input: Input): input is SubType => { try { assertT(input); return true; } catch (_) { return false; } }; }