UNPKG

epic-state

Version:

Reactive state management for frontend libraries.

142 lines (117 loc) 4.57 kB
import { create } from 'logua' import type { ProxyObject, Value } from './types' export const log = create('epic-state', 'red') export const isObject = (x: unknown): x is object => typeof x === 'object' && x !== null export const listGetters = (input: object) => { const descriptors = Object.getOwnPropertyDescriptors(input) const getters: { [key: string]: PropertyDescriptor['get'] } = {} for (const [key, { get }] of Object.entries(descriptors)) { if (typeof get === 'function') { getters[key] = get } } return getters } // TODO probably not needed. export const isGetter = (input: object, property: string | symbol): boolean => { const descriptor = Object.getOwnPropertyDescriptor(input, property) return !!descriptor && typeof descriptor.get === 'function' } export const isSetter = (input: object, property: string | symbol): boolean => { const descriptor = Object.getOwnPropertyDescriptor(input, property) return !!descriptor && typeof descriptor.set === 'function' } export const reevaluateGetter = (target: { [key: string | symbol]: Value }, property: string | symbol) => { const temporaryValue = target[property] delete target[property] target[property] = temporaryValue return target[property] } export const newProxy = <T extends object>(target: T, handler: ProxyHandler<T>): T => new Proxy(target, handler) export const canProxy = (x: unknown, refSet: WeakSet<WeakKey>) => isObject(x) && !refSet.has(x) && (Array.isArray(x) || !(Symbol.iterator in x)) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer) export const canPolyfill = (x: unknown) => x instanceof Map || x instanceof Set export const defaultHandlePromise = <P extends Promise<any>>( promise: P & { status?: 'pending' | 'fulfilled' | 'rejected' value?: Awaited<P> reason?: unknown }, ) => { switch (promise.status) { case 'fulfilled': return promise.value as Awaited<P> case 'rejected': throw promise.reason default: throw promise } } export const isLeaf = (value: any) => typeof value !== 'object' || (value && Object.hasOwn(value, '_leaf')) export const needsRegister = (value: any) => typeof value === 'object' && value && Object.hasOwn(value, '_leaf') && Object.hasOwn(value, '_register') // NOTE copy is required for proper function. export const createBaseObject = (initialObject: object) => { if (Array.isArray(initialObject)) { return [] } return Object.create(Object.getPrototypeOf(initialObject)) } export const snapCache = new WeakMap<object, [version: number, snap: unknown]>() export function updateProxyValues(existingObject: ProxyObject, newObject: ProxyObject) { // Add or update properties from newObject to existingObject for (const key of Reflect.ownKeys(newObject)) { // @ts-ignore existingObject[key as keyof ProxyObject] = newObject[key as keyof ProxyObject] } // Remove properties from existingObject that are not in newObject for (const key of Reflect.ownKeys(existingObject)) { if (!(key in newObject)) { delete existingObject[key as keyof ProxyObject] } } } export function set<T extends object, K extends keyof T>(parent: T, property: K) { return (value: T[K]) => { parent[property] = value } } export function setTo<T extends object, K extends keyof T>(parent: T, property: K, value: T[K]) { return () => { parent[property] = value } } export function setValue<T extends object, K extends keyof T>(parent: T, property: K, cast?: (value: string) => T[K]) { return (event: { target: { value: string } }) => { parent[property] = cast ? cast(event.target.value) : (event.target.value as T[K]) } } export function toggle<T extends Record<K, boolean>, K extends keyof T>(parent: T, property: K, propagate = false) { return (event?: { stopPropagation: Function }) => { if (event && !propagate) { event.stopPropagation() } parent[property] = !parent[property] as T[K] } } export function multipleInstancesWarning() { if (process.env.NODE_ENV === 'production') { return } // Ensure plugin is only loaded once from a single source (will not work properly otherwise). if (typeof globalThis.__epicState !== 'undefined') { log('Multiple instances of epic-state have been loaded, plugin might not work as expected', 'warning') } else { globalThis.__epicState = true } }