epic-state
Version:
Reactive state management for frontend libraries.
161 lines (136 loc) • 4.81 kB
text/typescript
declare global {
// Avoids loading plugin multiple times in development mode.
var __epicState: boolean
}
export type Value = any
export type Getter = () => Value
export type RerenderMethod = () => void
export type AnyFunction = (...args: any[]) => any
export type AsRef = { $$valtioRef: true }
export type ProxyObject = object & {
root?: ProxyObject
parent?: ProxyObject
_plugin?: PluginActions[]
_id: number
addPlugin: (plugin: Plugin) => void
}
export type Property = string | symbol
export type Path = Property[] // TODO unused
export type Observation = [PluginAction, ProxyObject, Property, Value, Value?]
export type ObservationCallback = (observation: Observation) => void
export type Primitive = string | number | boolean | null | undefined | symbol | bigint
export type SnapshotIgnore =
| Date
| Map<any, any>
| Set<any>
| WeakMap<any, any>
| WeakSet<any>
| AsRef
| Error
| RegExp
| AnyFunction
| Primitive
export type Snapshot<T> = T extends SnapshotIgnore
? T
: T extends Promise<unknown>
? Awaited<T>
: T extends object
? { readonly [K in keyof T]: Snapshot<T[K]> }
: T
export type HandlePromise = <P extends Promise<any>>(promise: P) => Awaited<P>
export type ProxyState = readonly [target: object]
type ArrayElementType<E> = E extends (infer U)[] ? U : never
type SetElementType<E> = E extends Set<infer U> ? U : never
type MapElementType<E> = E extends Map<infer U, infer V> ? [U, V] : never
type ArrayWithParent<T, P, R> = {
parent: P
root: R
} & T[]
type SetWithParent<T, P, R> = {
parent: P
root: R
} & Set<T>
type MapWithParent<T, S, P, R> = {
parent: P
root: R
} & Map<T, S>
type ChildState<E, P, R> = E extends Function
? E
: E extends object
? {
parent: P
root: R
} & (E extends any[]
? ArrayWithParent<ArrayElementType<E>, P, R>
: E extends Set<any>
? SetWithParent<SetElementType<E>, P, R>
: E extends Map<any, any>
? MapWithParent<MapElementType<E>[0], MapElementType<E>[1], P, R>
: {
[F in keyof Omit<E, 'plugin'>]: E[F] extends object ? ChildState<E[F], ChildState<E, P, R>, R> : E[F]
})
: E
export type RootState<T, R> = T extends Set<any>
? Set<SetElementType<T>>
: {
[K in keyof Omit<T, 'plugin'>]: ChildState<T[K], T, R extends unknown ? T : R>
}
export type PluginActions = {
get?: (options: { property: Property; parent: ProxyObject; leaf: boolean; value: Value }) => void
set?: (options: { property: Property; parent: ProxyObject; leaf: boolean; value: Value; previousValue: Value }) => void
delete?: (options: { property: Property; parent: ProxyObject; leaf: boolean; previousValue: Value }) => void
all?: boolean
synchronous?: boolean // TODO disable batching.
}
export enum PluginAction {
Get = 'get',
Set = 'set',
Delete = 'delete',
}
export type ConfiguredPlugin<T extends any[]> = (...configuration: T) => Plugin
export type ConfigurablePlugin<T extends any[]> = (...configuration: T | ['initialize', ProxyObject?]) => Plugin | PluginActions
export type Plugin = (value: 'initialize', state?: ProxyObject) => PluginActions
export type ObservedProperties = TupleArrayMap<ProxyObject, Property, RerenderMethod>
export class TupleArrayMap<A, B, C> {
protected observedProperties: Map<A, Map<B, C[]>> = new Map()
has(firstKey: A, secondKey: B): boolean {
return !!(this.observedProperties.has(firstKey) && this.observedProperties.get(firstKey)?.has(secondKey))
}
get(firstKey: A, secondKey: B): C[] | undefined {
return this.has(firstKey, secondKey) ? this.observedProperties.get(firstKey)?.get(secondKey) : undefined
}
add(firstKey: A, secondKey: B, value: C): void {
if (!this.observedProperties.has(firstKey)) {
this.observedProperties.set(firstKey, new Map<B, C[]>())
}
if (!this.observedProperties.get(firstKey)?.has(secondKey)) {
this.observedProperties.get(firstKey)?.set(secondKey, [])
}
this.observedProperties.get(firstKey)?.get(secondKey)?.push(value)
}
delete(firstKey: A, secondKey: B): void {
if (this.observedProperties.has(firstKey)) {
const properties = this.observedProperties.get(firstKey)
if (properties?.has(secondKey)) {
properties.delete(secondKey)
}
}
}
clear() {
this.observedProperties.clear()
}
list() {
return [...this.observedProperties.entries()]
}
}
export type CallPluginOptions = {
type: PluginAction
// Use _plugin to access plugins internally (not exposed).
target: ProxyObject
initial?: boolean // Used for recursive calls to callPlugins.
property: Property
parent: ProxyObject // TODO is this truly the proxy??
leaf: boolean
value?: Value
previousValue?: Value
}