@zeix/cause-effect
Version:
Cause & Effect - reactive state management primitives library for TypeScript.
168 lines (154 loc) • 4.7 kB
text/typescript
import { InvalidSignalValueError } from './errors'
import {
type ComputedOptions,
type MemoCallback,
type Signal,
type TaskCallback,
TYPE_COLLECTION,
TYPE_LIST,
TYPE_MEMO,
TYPE_SENSOR,
TYPE_SLOT,
TYPE_STATE,
TYPE_STORE,
TYPE_TASK,
} from './graph'
import { createList, isList, type List, type UnknownRecord } from './nodes/list'
import { createMemo, isMemo, type Memo } from './nodes/memo'
import { createState, isState, type State } from './nodes/state'
import { createStore, isStore, type Store } from './nodes/store'
import { createTask, isTask, type Task } from './nodes/task'
import { isAsyncFunction, isFunction, isRecord, isUniformArray } from './util'
/* === Types === */
/**
* A readable and writable signal — the type union of `State`, `Store`, and `List`.
* Use as a parameter type for generic code that accepts any writable signal.
*
* @template T - The type of value held by the signal
*/
type MutableSignal<T extends {}> = {
get(): T
set(value: T): void
update(callback: (value: T) => T): void
}
/* === Constants === */
const SIGNAL_TYPES = new Set([
TYPE_STATE,
TYPE_MEMO,
TYPE_TASK,
TYPE_SENSOR,
TYPE_SLOT,
TYPE_LIST,
TYPE_COLLECTION,
TYPE_STORE,
])
/* === Factory Functions === */
/**
* Create a derived signal from existing signals
*
* @since 0.9.0
* @param callback - Computation callback function
* @param options - Optional configuration
*/
function createComputed<T extends {}>(
callback: TaskCallback<T>,
options?: ComputedOptions<T>,
): Task<T>
function createComputed<T extends {}>(
callback: MemoCallback<T>,
options?: ComputedOptions<T>,
): Memo<T>
function createComputed<T extends {}>(
callback: TaskCallback<T> | MemoCallback<T>,
options?: ComputedOptions<T>,
): Memo<T> | Task<T> {
return isAsyncFunction(callback)
? createTask(callback as TaskCallback<T>, options)
: createMemo(callback as MemoCallback<T>, options)
}
/**
* Convert a value to a Signal.
*
* @since 0.9.6
*/
function createSignal<T extends {}>(value: Signal<T>): Signal<T>
function createSignal<T extends {}>(value: readonly T[]): List<T>
function createSignal<T extends UnknownRecord>(value: T): Store<T>
function createSignal<T extends {}>(value: TaskCallback<T>): Task<T>
function createSignal<T extends {}>(value: MemoCallback<T>): Memo<T>
function createSignal<T extends {}>(value: T): State<T>
function createSignal(value: unknown): unknown {
if (isSignal(value)) return value
if (value == null) throw new InvalidSignalValueError('createSignal', value)
if (isAsyncFunction(value))
return createTask(value as TaskCallback<unknown & {}>)
if (isFunction(value))
return createMemo(value as MemoCallback<unknown & {}>)
if (isUniformArray<unknown & {}>(value)) return createList(value)
if (isRecord(value)) return createStore(value)
return createState(value as unknown & {})
}
/**
* Convert a value to a MutableSignal.
*
* @since 0.17.0
*/
function createMutableSignal<T extends {}>(
value: MutableSignal<T>,
): MutableSignal<T>
function createMutableSignal<T extends {}>(value: readonly T[]): List<T>
function createMutableSignal<T extends UnknownRecord>(value: T): Store<T>
function createMutableSignal<T extends {}>(value: T): State<T>
function createMutableSignal(value: unknown): unknown {
if (isMutableSignal(value)) return value
if (value == null || isFunction(value) || isSignal(value))
throw new InvalidSignalValueError('createMutableSignal', value)
if (isUniformArray<unknown & {}>(value)) return createList(value)
if (isRecord(value)) return createStore(value)
return createState(value as unknown & {})
}
/* === Guards === */
/**
* Check if a value is a computed signal
*
* @since 0.9.0
* @param value - Value to check
* @returns True if value is a computed signal, false otherwise
*/
function isComputed<T extends {}>(value: unknown): value is Memo<T> {
return isMemo(value) || isTask(value)
}
/**
* Check whether a value is a Signal
*
* @since 0.9.0
* @param value - Value to check
* @returns True if value is a Signal, false otherwise
*/
function isSignal<T extends {}>(value: unknown): value is Signal<T> {
return (
value != null &&
SIGNAL_TYPES.has(
(value as Record<symbol, unknown>)[Symbol.toStringTag] as string,
)
)
}
/**
* Check whether a value is a State, Store, or List
*
* @since 0.15.2
* @param value - Value to check
* @returns True if value is a State, Store, or List, false otherwise
*/
function isMutableSignal(value: unknown): value is MutableSignal<unknown & {}> {
return isState(value) || isStore(value) || isList(value)
}
export {
type MutableSignal,
createComputed,
createSignal,
createMutableSignal,
isComputed,
isSignal,
isMutableSignal,
}