@zeix/cause-effect
Version:
Cause & Effect - reactive state management primitives library for TypeScript.
181 lines (164 loc) • 4.41 kB
text/typescript
import { isFunction, valueString } from './util'
/* === Types === */
/**
* A type guard function that validates whether an unknown value is of type T.
* Used to ensure type safety when updating signals.
*
* @template T - The type to guard against
* @param value - The value to check
* @returns True if the value is of type T
*/
type Guard<T extends {}> = (value: unknown) => value is T
/* === Error Classes === */
/**
* Error thrown on re-entrance on an already running function.
*/
class CircularDependencyError extends Error {
/**
* Constructs a new CircularDependencyError.
*
* @param where - The location where the error occurred.
*/
constructor(where: string) {
super(`[${where}] Circular dependency detected`)
this.name = 'CircularDependencyError'
}
}
/**
* Error thrown when a signal value is null or undefined.
*/
class NullishSignalValueError extends TypeError {
/**
* Constructs a new NullishSignalValueError.
*
* @param where - The location where the error occurred.
*/
constructor(where: string) {
super(`[${where}] Signal value cannot be null or undefined`)
this.name = 'NullishSignalValueError'
}
}
/**
* Error thrown when a signal is read before it has a value.
*/
class UnsetSignalValueError extends Error {
/**
* Constructs a new UnsetSignalValueError.
*
* @param where - The location where the error occurred.
*/
constructor(where: string) {
super(`[${where}] Signal value is unset`)
this.name = 'UnsetSignalValueError'
}
}
/**
* Error thrown when a signal value is invalid.
*/
class InvalidSignalValueError extends TypeError {
/**
* Constructs a new InvalidSignalValueError.
*
* @param where - The location where the error occurred.
* @param value - The invalid value.
*/
constructor(where: string, value: unknown) {
super(`[${where}] Signal value ${valueString(value)} is invalid`)
this.name = 'InvalidSignalValueError'
}
}
/**
* Error thrown when a callback is invalid.
*/
class InvalidCallbackError extends TypeError {
/**
* Constructs a new InvalidCallbackError.
*
* @param where - The location where the error occurred.
* @param value - The invalid value.
*/
constructor(where: string, value: unknown) {
super(`[${where}] Callback ${valueString(value)} is invalid`)
this.name = 'InvalidCallbackError'
}
}
class ReadonlySignalError extends Error {
/**
* Constructs a new ReadonlySignalError.
*
* @param where - The location where the error occurred.
*/
constructor(where: string) {
super(`[${where}] Signal is read-only`)
this.name = 'ReadonlySignalError'
}
}
/**
* Error thrown when an API requiring an owner is called without one.
*/
class RequiredOwnerError extends Error {
/**
* Constructs a new RequiredOwnerError.
*
* @param where - The location where the error occurred.
*/
constructor(where: string) {
super(`[${where}] Active owner is required`)
this.name = 'RequiredOwnerError'
}
}
class DuplicateKeyError extends Error {
constructor(where: string, key: string, value?: unknown) {
super(
`[${where}] Could not add key "${key}"${
value ? ` with value ${JSON.stringify(value)}` : ''
} because it already exists`,
)
this.name = 'DuplicateKeyError'
}
}
/* === Validation Functions === */
function validateSignalValue<T extends {}>(
where: string,
value: unknown,
guard?: Guard<T>,
): asserts value is T {
if (value == null) throw new NullishSignalValueError(where)
if (guard && !guard(value)) throw new InvalidSignalValueError(where, value)
}
function validateReadValue<T extends {}>(
where: string,
value: T | null | undefined,
): asserts value is T {
if (value == null) throw new UnsetSignalValueError(where)
}
function validateCallback(
where: string,
value: unknown,
): asserts value is (...args: unknown[]) => unknown
function validateCallback<T>(
where: string,
value: unknown,
guard: (value: unknown) => value is T,
): asserts value is T
function validateCallback(
where: string,
value: unknown,
guard: (value: unknown) => boolean = isFunction,
): void {
if (!guard(value)) throw new InvalidCallbackError(where, value)
}
export {
type Guard,
CircularDependencyError,
NullishSignalValueError,
InvalidSignalValueError,
UnsetSignalValueError,
InvalidCallbackError,
ReadonlySignalError,
RequiredOwnerError,
DuplicateKeyError,
validateSignalValue,
validateReadValue,
validateCallback,
}