@thalesfp/snapstate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/dist/form/index.cjs +1177 -0
- package/dist/form/index.cjs.map +1 -0
- package/dist/form/index.d.cts +197 -0
- package/dist/form/index.d.ts +197 -0
- package/dist/form/index.js +1153 -0
- package/dist/form/index.js.map +1 -0
- package/dist/index.cjs +528 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +109 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +498 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +693 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +130 -0
- package/dist/react/index.d.ts +130 -0
- package/dist/react/index.js +671 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.ts","../../src/react/store.ts","../../src/core/types.ts","../../src/core/trie.ts","../../src/core/structural.ts","../../src/core/computed.ts","../../src/core/store.ts","../../src/core/base.ts"],"sourcesContent":["export { ReactSnapStore as SnapStore } from \"./store.js\";\nexport { setHttpClient, setDefaultHeaders } from \"../core/base.js\";\nexport { asyncStatus } from \"../core/types.js\";\nexport type {\n AsyncStatus,\n AsyncStatusValue,\n OperationState,\n HttpClient,\n HttpRequestInit,\n ApiRequestOptions,\n} from \"../core/types.js\";\n","import {\n useSyncExternalStore,\n useCallback,\n useRef,\n useState,\n useEffect,\n createElement,\n forwardRef,\n} from \"react\";\nimport { SnapStore } from \"../core/base.js\";\nimport { asyncStatus } from \"../core/types.js\";\nimport type { StoreOptions, AsyncStatus, DotPaths, GetByPath } from \"../core/types.js\";\n\ninterface ConnectConfig<S, MappedProps> {\n props: (store: S) => MappedProps;\n fetch: (store: S) => Promise<void>;\n loading?: React.ComponentType;\n error?: React.ComponentType<{ error: string }>;\n}\n\ntype PickFn<T extends object> = <P extends DotPaths<T>>(path: P) => GetByPath<T, P>;\n\ninterface SelectConnectConfig<T extends object, MappedProps> {\n select: (pick: PickFn<T>) => MappedProps;\n}\n\nfunction shallowEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {\n const keysA = Object.keys(a);\n const keysB = Object.keys(b);\n if (keysA.length !== keysB.length) { return false; }\n for (const key of keysA) {\n if (a[key] !== b[key]) { return false; }\n }\n return true;\n}\n\nexport class ReactSnapStore<T extends object, K extends string = string> extends SnapStore<T, K> {\n constructor(initialState: T, options?: StoreOptions) {\n super(initialState, options);\n }\n\n /** Wire a component to the store, injecting props derived from state via `mapToProps`. */\n connect<P extends object, MappedProps extends Record<string, unknown>>(\n Component: React.ComponentType<P>,\n mapToProps: (store: this) => MappedProps,\n ): React.FC<Omit<P, keyof MappedProps>>;\n /** Wire a component to the store with async data fetching, loading, and error handling. */\n connect<P extends object, MappedProps extends Record<string, unknown>>(\n Component: React.ComponentType<P>,\n config: ConnectConfig<this, MappedProps>,\n ): React.FC<Omit<P, keyof MappedProps | \"status\" | \"error\">>;\n /** Wire a component to the store with granular path-based subscriptions via `select`.\n * Paths are captured once at connect-time — select must use a stable set of paths.\n * For conditional/dynamic path selection, use the `mapToProps` overload instead. */\n connect<P extends object, MappedProps extends Record<string, unknown>>(\n Component: React.ComponentType<P>,\n config: SelectConnectConfig<T, MappedProps>,\n ): React.FC<Omit<P, keyof MappedProps>>;\n connect<P extends object, MappedProps extends Record<string, unknown>>(\n Component: React.ComponentType<P>,\n configOrMapper:\n | ((store: this) => MappedProps)\n | ConnectConfig<this, MappedProps>\n | SelectConnectConfig<T, MappedProps>,\n ): React.FC<Omit<P, keyof MappedProps>> {\n const store = this;\n\n if (typeof configOrMapper === \"object\" && \"select\" in configOrMapper) {\n return this._connectWithSelect<P, MappedProps>(Component, configOrMapper.select);\n }\n\n const mapToProps =\n typeof configOrMapper === \"function\" ? configOrMapper : configOrMapper.props;\n const fetchFn =\n typeof configOrMapper === \"function\" ? undefined : configOrMapper.fetch;\n const loadingComponent =\n typeof configOrMapper === \"function\" ? undefined : configOrMapper.loading;\n const errorComponent =\n typeof configOrMapper === \"function\" ? undefined : configOrMapper.error;\n\n const Connected = forwardRef<unknown, Omit<P, keyof MappedProps>>(function Connected(ownProps, ref) {\n const cachedRef = useRef<{ revision: number; props: MappedProps } | null>(null);\n const revisionRef = useRef(0);\n\n const subscribe = useCallback(\n (cb: () => void) => store.subscribe(() => {\n revisionRef.current++;\n cb();\n }),\n [store],\n );\n\n const getSnapshot = useCallback(() => {\n const currentRevision = revisionRef.current;\n if (cachedRef.current && cachedRef.current.revision === currentRevision) {\n return cachedRef.current.props;\n }\n const next = mapToProps(store);\n if (cachedRef.current && shallowEqual(cachedRef.current.props, next)) {\n cachedRef.current = { revision: currentRevision, props: cachedRef.current.props };\n return cachedRef.current.props;\n }\n cachedRef.current = { revision: currentRevision, props: next };\n return next;\n }, [store]);\n\n const mappedProps = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n const [asyncState, setAsyncState] = useState<{\n status: AsyncStatus;\n error: string | null;\n }>({ status: asyncStatus(\"idle\"), error: null });\n\n const fetchGenRef = useRef(0);\n\n useEffect(() => {\n if (!fetchFn) { return; }\n let cancelled = false;\n const gen = ++fetchGenRef.current;\n setAsyncState({ status: asyncStatus(\"loading\"), error: null });\n Promise.resolve()\n .then(() => {\n if (cancelled) { return; }\n return fetchFn(store);\n })\n .then(() => {\n if (gen === fetchGenRef.current) {\n setAsyncState({ status: asyncStatus(\"ready\"), error: null });\n }\n })\n .catch((e) => {\n if (gen === fetchGenRef.current) {\n setAsyncState({\n status: asyncStatus(\"error\"),\n error: e instanceof Error ? e.message : \"Unknown error\",\n });\n }\n });\n return () => { cancelled = true; };\n }, []);\n\n if (fetchFn) {\n if (loadingComponent && (asyncState.status.isIdle || asyncState.status.isLoading)) {\n return createElement(loadingComponent);\n }\n if (errorComponent && asyncState.status.isError) {\n return createElement(errorComponent, { error: asyncState.error! });\n }\n }\n\n return createElement(Component, {\n ...ownProps,\n ...mappedProps,\n ...(fetchFn ? asyncState : {}),\n ref,\n } as unknown as P);\n });\n\n Connected.displayName = `Connect(${Component.displayName || Component.name || \"Component\"})`;\n return Connected as unknown as React.FC<Omit<P, keyof MappedProps>>;\n }\n\n private _connectWithSelect<P extends object, MappedProps extends Record<string, unknown>>(\n Component: React.ComponentType<P>,\n selectFn: (pick: PickFn<T>) => MappedProps,\n ): React.FC<Omit<P, keyof MappedProps>> {\n const store = this;\n\n const resolvePathValue = (path: string): any => {\n const segments = path.split(\".\");\n let val: any = store.getSnapshot();\n for (const seg of segments) {\n if (val == null) { return undefined; }\n val = val[seg];\n }\n return val;\n };\n\n const trackedPaths: string[] = [];\n const trackingPick: PickFn<T> = ((path: string) => {\n trackedPaths.push(path);\n return resolvePathValue(path);\n }) as PickFn<T>;\n selectFn(trackingPick);\n const paths = [...trackedPaths];\n\n const readPick: PickFn<T> = ((path: string) => {\n return resolvePathValue(path);\n }) as PickFn<T>;\n\n const Connected = forwardRef<unknown, Omit<P, keyof MappedProps>>(function Connected(ownProps, ref) {\n const cachedRef = useRef<{ revision: number; props: MappedProps } | null>(null);\n const revisionRef = useRef(0);\n\n const subscribe = useCallback(\n (cb: () => void) => {\n const unsubs = paths.map((p) =>\n store.subscribe(p, () => {\n revisionRef.current++;\n cb();\n }),\n );\n return () => unsubs.forEach((u) => u());\n },\n [store],\n );\n\n const getSnapshot = useCallback(() => {\n const currentRevision = revisionRef.current;\n if (cachedRef.current && cachedRef.current.revision === currentRevision) {\n return cachedRef.current.props;\n }\n const next = selectFn(readPick);\n if (cachedRef.current && shallowEqual(cachedRef.current.props, next)) {\n cachedRef.current = { revision: currentRevision, props: cachedRef.current.props };\n return cachedRef.current.props;\n }\n cachedRef.current = { revision: currentRevision, props: next };\n return next;\n }, [store]);\n\n const mappedProps = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n return createElement(Component, {\n ...ownProps,\n ...mappedProps,\n ref,\n } as unknown as P);\n });\n\n Connected.displayName = `Connect(${Component.displayName || Component.name || \"Component\"})`;\n return Connected as unknown as React.FC<Omit<P, keyof MappedProps>>;\n }\n}\n","export type AsyncStatusValue = \"idle\" | \"loading\" | \"ready\" | \"error\";\n\nexport interface AsyncStatus {\n readonly value: AsyncStatusValue;\n readonly isIdle: boolean;\n readonly isLoading: boolean;\n readonly isReady: boolean;\n readonly isError: boolean;\n}\n\nconst _statuses: Record<AsyncStatusValue, AsyncStatus> = {\n idle: Object.freeze({ value: \"idle\", isIdle: true, isLoading: false, isReady: false, isError: false }),\n loading: Object.freeze({ value: \"loading\", isIdle: false, isLoading: true, isReady: false, isError: false }),\n ready: Object.freeze({ value: \"ready\", isIdle: false, isLoading: false, isReady: true, isError: false }),\n error: Object.freeze({ value: \"error\", isIdle: false, isLoading: false, isReady: false, isError: true }),\n};\n\nexport function asyncStatus(value: AsyncStatusValue): AsyncStatus {\n return _statuses[value];\n}\n\n/** Async operation status and error. Tracks the lifecycle of an `api.fetch`/`api.get`/`api.post` call. */\nexport interface OperationState {\n status: AsyncStatus;\n error: string | null;\n}\n\nexport type Path = string & { readonly __brand?: \"Path\" };\n\nexport type Listener = () => void;\n\nexport type Unsubscribe = () => void;\n\n// Union of all valid dot-separated paths into T (for autocomplete)\nexport type DotPaths<T, Prefix extends string = \"\"> = T extends object\n ? { [K in keyof T & string]:\n | `${Prefix}${K}`\n | DotPaths<T[K], `${Prefix}${K}.`>\n }[keyof T & string]\n : never;\n\n// Extract a deeply nested type by dot-separated path\nexport type GetByPath<T, P extends string> = P extends \"\"\n ? T\n : P extends `${infer K}.${infer Rest}`\n ? K extends keyof T\n ? GetByPath<T[K], Rest>\n : never\n : P extends keyof T\n ? T[P]\n : never;\n\nexport type DeepPartial<T> = T extends object\n ? { [K in keyof T]?: DeepPartial<T[K]> }\n : T;\n\nexport type ArrayPaths<T> = {\n [K in keyof T & string]: T[K] extends any[] ? K : never;\n}[keyof T & string];\n\nexport type ObjectArrayPaths<T> = {\n [K in keyof T & string]: T[K] extends (infer V)[]\n ? V extends Date | RegExp | Map<any, any> | Set<any> | Function | any[]\n ? never\n : V extends Record<string, any> ? K : never\n : never;\n}[keyof T & string];\n\nexport type ElementOf<A> = A extends (infer V)[] ? V : never;\n\nexport type Updater<V> = V | ((prev: V) => V);\n\nexport interface StoreOptions {\n /** Auto-batch synchronous sets via microtask (default: true) */\n autoBatch?: boolean;\n}\n\n/** Handle to a computed (derived) value. Call `get()` to read, `destroy()` to stop tracking. */\nexport interface ComputedRef<V> {\n get(): V;\n destroy(): void;\n}\n\n/** Options for an HTTP request (method, body, headers). */\nexport interface HttpRequestInit {\n method?: string;\n body?: unknown;\n headers?: Record<string, string>;\n}\n\n/** Interface for the HTTP layer used by `api.get` and `api.post/put/patch/delete`. */\nexport interface HttpClient {\n request<R = unknown>(url: string, init?: HttpRequestInit): Promise<R>;\n}\n\n/** Options for HTTP verb methods (`api.post`, `api.put`, etc.). */\nexport interface ApiRequestOptions<R = unknown> {\n body?: unknown;\n headers?: Record<string, string>;\n onSuccess?: (data: R) => void;\n onError?: (error: Error) => void;\n}\n\nexport interface Subscribable<T extends object> {\n subscribe(callback: Listener): Unsubscribe;\n getSnapshot(): T;\n}\n\nexport interface StateAccessor<T extends object> {\n get(): T;\n get<P extends DotPaths<T>>(path: P): GetByPath<T, P>;\n set<P extends DotPaths<T>>(path: P, value: Updater<GetByPath<T, P>>): void;\n batch(fn: () => void): void;\n computed<V>(deps: (keyof T & string)[], fn: (state: T) => V): ComputedRef<V>;\n append<P extends ArrayPaths<T>>(path: P, ...items: ElementOf<T[P]>[]): void;\n prepend<P extends ArrayPaths<T>>(path: P, ...items: ElementOf<T[P]>[]): void;\n insertAt<P extends ArrayPaths<T>>(path: P, index: number, ...items: ElementOf<T[P]>[]): void;\n patch<P extends ObjectArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean, updates: Partial<ElementOf<T[P]>>): void;\n remove<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): void;\n removeAt<P extends ArrayPaths<T>>(path: P, index: number): void;\n at<P extends ArrayPaths<T>>(path: P, index: number): ElementOf<T[P]> | undefined;\n filter<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): ElementOf<T[P]>[];\n find<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): ElementOf<T[P]> | undefined;\n findIndexOf<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): number;\n count<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): number;\n}\n\nexport interface ApiAccessor<K extends string> {\n fetch(key: K, fn: () => Promise<void>): Promise<void>;\n get<R = unknown>(key: K, url: string, onSuccess?: (data: R) => void): Promise<void>;\n post<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;\n put<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;\n patch<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;\n delete<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;\n}\n\nexport interface RawStore<T extends object> extends Subscribable<T> {\n get(): T;\n get<P extends DotPaths<T>>(path: P): GetByPath<T, P>;\n\n set<P extends DotPaths<T>>(path: P, value: Updater<GetByPath<T, P>>): void;\n\n batch(fn: () => void): void;\n\n subscribe(callback: Listener): Unsubscribe;\n subscribe(path: string, callback: Listener): Unsubscribe;\n\n getSnapshot(): T;\n\n computed<V>(deps: (keyof T & string)[], fn: (state: T) => V): ComputedRef<V>;\n\n notify(): void;\n\n destroy(): void;\n}\n","import type { Listener, Unsubscribe } from \"./types.js\";\n\nfunction invokeAll(listeners: Set<Listener>): void {\n let firstError: unknown;\n for (const l of listeners) {\n try { l(); } catch (e) { firstError ??= e; }\n }\n if (firstError !== undefined) { throw firstError; }\n}\n\ninterface TrieNode {\n listeners: Set<Listener>;\n children: Map<string, TrieNode>;\n}\n\nfunction createNode(): TrieNode {\n return { listeners: new Set(), children: new Map() };\n}\n\nfunction parsePath(path: string): string[] {\n if (path === \"\") { return []; }\n return path.split(\".\");\n}\n\nexport class SubscriptionTrie {\n private root = createNode();\n private globalListeners = new Set<Listener>();\n\n /** Subscribe to a specific path. Returns unsubscribe function. */\n add(path: string, listener: Listener): Unsubscribe {\n const segments = parsePath(path);\n const parents: { parent: TrieNode; segment: string }[] = [];\n let node = this.root;\n for (const seg of segments) {\n if (!node.children.has(seg)) {\n node.children.set(seg, createNode());\n }\n parents.push({ parent: node, segment: seg });\n node = node.children.get(seg)!;\n }\n node.listeners.add(listener);\n return () => {\n node.listeners.delete(listener);\n for (let i = parents.length - 1; i >= 0; i--) {\n const { parent, segment } = parents[i];\n const child = parent.children.get(segment)!;\n if (child.listeners.size === 0 && child.children.size === 0) {\n parent.children.delete(segment);\n } else {\n break;\n }\n }\n };\n }\n\n /** Subscribe to all changes (no path filter). */\n addGlobal(listener: Listener): Unsubscribe {\n this.globalListeners.add(listener);\n return () => {\n this.globalListeners.delete(listener);\n };\n }\n\n /** Notify listeners for exact path, all ancestors, and all descendants. */\n notify(path: string): void {\n const segments = parsePath(path);\n const collected = new Set<Listener>();\n\n // Collect global listeners\n for (const l of this.globalListeners) collected.add(l);\n\n // Walk down to the target, collecting ancestor listeners\n let node = this.root;\n // Root-level listeners (subscribe to \"\")\n for (const l of node.listeners) collected.add(l);\n\n let matched = true;\n for (const seg of segments) {\n // Check wildcard sibling at this level\n const wildcard = node.children.get(\"*\");\n if (wildcard) {\n for (const l of wildcard.listeners) collected.add(l);\n this.collectDescendants(wildcard, collected);\n }\n\n const child = node.children.get(seg);\n if (!child) {\n matched = false;\n break;\n }\n node = child;\n for (const l of node.listeners) collected.add(l);\n }\n\n // Only collect descendants if we matched the full path\n if (matched) {\n this.collectDescendants(node, collected);\n }\n\n invokeAll(collected);\n }\n\n /** Notify all listeners in the trie. */\n notifyAll(): void {\n const collected = new Set<Listener>();\n for (const l of this.globalListeners) collected.add(l);\n this.collectDescendants(this.root, collected);\n for (const l of this.root.listeners) collected.add(l);\n invokeAll(collected);\n }\n\n private collectDescendants(node: TrieNode, out: Set<Listener>): void {\n for (const child of node.children.values()) {\n for (const l of child.listeners) out.add(l);\n this.collectDescendants(child, out);\n }\n }\n\n clear(): void {\n this.root = createNode();\n this.globalListeners.clear();\n }\n}\n","/**\n * Immutable update with structural sharing.\n * Only clones objects along the changed path; unchanged subtrees keep their references.\n */\nexport function applyUpdate<T extends object>(\n state: T,\n path: string,\n value: unknown,\n): T {\n const segments = path.split(\".\");\n return updateAtPath(state, segments, 0, value) as T;\n}\n\nfunction updateAtPath(\n current: unknown,\n segments: string[],\n index: number,\n value: unknown,\n): unknown {\n if (index === segments.length) {\n // Functional updater support\n if (typeof value === \"function\") {\n return (value as (prev: unknown) => unknown)(current);\n }\n return value;\n }\n\n const key = segments[index];\n\n if (Array.isArray(current)) {\n const i = Number(key);\n const next = updateAtPath(current[i], segments, index + 1, value);\n if (Object.is(next, current[i])) { return current; }\n const copy = current.slice();\n copy[i] = next;\n return copy;\n }\n\n if (current !== null && typeof current === \"object\") {\n const obj = current as Record<string, unknown>;\n const next = updateAtPath(obj[key], segments, index + 1, value);\n if (Object.is(next, obj[key])) { return current; }\n return { ...obj, [key]: next };\n }\n\n // Path doesn't exist yet — create nested objects\n const next = updateAtPath(undefined, segments, index + 1, value);\n return { [key]: next };\n}\n\n/** Read a value at a dot-separated path. */\nexport function getAtPath(state: unknown, path: string): unknown {\n if (path === \"\") { return state; }\n const segments = path.split(\".\");\n let current = state;\n for (const seg of segments) {\n if (current === null || current === undefined) { return undefined; }\n current = (current as Record<string, unknown>)[seg];\n }\n return current;\n}\n","import type { ComputedRef, Listener, Unsubscribe } from \"./types.js\";\n\ninterface ComputedHost {\n getSnapshot(): object;\n subscribe(path: string, callback: Listener): Unsubscribe;\n}\n\nexport function createComputed<T extends object, V>(\n host: ComputedHost,\n deps: string[],\n fn: (state: T) => V,\n): ComputedRef<V> {\n let cachedValue: V;\n let dirty = true;\n const unsubs: Unsubscribe[] = [];\n\n const markDirty = () => {\n dirty = true;\n };\n\n for (const dep of deps) {\n unsubs.push(host.subscribe(dep, markDirty));\n }\n\n // Compute initial value; clean up subscriptions if it throws\n try {\n cachedValue = fn(host.getSnapshot() as T);\n } catch (e) {\n for (const unsub of unsubs) unsub();\n unsubs.length = 0;\n throw e;\n }\n dirty = false;\n\n return {\n get(): V {\n if (dirty) {\n cachedValue = fn(host.getSnapshot() as T);\n dirty = false;\n }\n return cachedValue;\n },\n destroy(): void {\n for (const unsub of unsubs) unsub();\n unsubs.length = 0;\n },\n };\n}\n","import type {\n RawStore,\n StoreOptions,\n Listener,\n Unsubscribe,\n Updater,\n ComputedRef,\n GetByPath,\n} from \"./types.js\";\nimport { SubscriptionTrie } from \"./trie.js\";\nimport { applyUpdate, getAtPath } from \"./structural.js\";\nimport { createComputed } from \"./computed.js\";\n\nexport function createStore<T extends object>(\n initialState: T,\n options: StoreOptions = {},\n): RawStore<T> {\n const { autoBatch = true } = options;\n\n let state: T = initialState;\n const trie = new SubscriptionTrie();\n\n // Batching\n let batchDepth = 0;\n let pendingPaths = new Set<string>();\n let microtaskScheduled = false;\n\n function flushNotifications(): void {\n const paths = pendingPaths;\n pendingPaths = new Set();\n microtaskScheduled = false;\n\n if (paths.size === 0) { return; }\n\n // Deduplicate: if a parent path is present, skip its children\n const sorted = [...paths].sort();\n const deduped: string[] = [];\n for (const p of sorted) {\n const last = deduped[deduped.length - 1];\n if (last !== undefined && p.startsWith(last + \".\")) { continue; }\n deduped.push(p);\n }\n\n for (const path of deduped) {\n trie.notify(path);\n }\n }\n\n function scheduleFlush(): void {\n if (batchDepth > 0) { return; }\n if (autoBatch && !microtaskScheduled) {\n microtaskScheduled = true;\n queueMicrotask(flushNotifications);\n } else if (!autoBatch) {\n flushNotifications();\n }\n }\n\n function get(): T;\n function get<P extends string>(path: P): GetByPath<T, P>;\n function get(path?: string): unknown {\n if (path === undefined || path === \"\") { return state; }\n return getAtPath(state, path);\n }\n\n function set<P extends string>(\n path: P,\n value: Updater<GetByPath<T, P>>,\n ): void {\n if (path === \"\") {\n throw new Error(\"Cannot set with an empty path. Use a specific path to update state.\");\n }\n const prev = state;\n state = applyUpdate(state, path, value);\n if (state !== prev) {\n pendingPaths.add(path);\n scheduleFlush();\n }\n }\n\n function batch(fn: () => void): void {\n batchDepth++;\n try {\n fn();\n } finally {\n batchDepth--;\n if (batchDepth === 0) {\n flushNotifications();\n }\n }\n }\n\n function subscribe(callback: Listener): Unsubscribe;\n function subscribe(path: string, callback: Listener): Unsubscribe;\n function subscribe(\n pathOrCallback: string | Listener,\n callback?: Listener,\n ): Unsubscribe {\n if (typeof pathOrCallback === \"function\") {\n return trie.addGlobal(pathOrCallback);\n }\n return trie.add(pathOrCallback, callback!);\n }\n\n function getSnapshot(): T {\n return state;\n }\n\n function computed<V>(deps: (keyof T & string)[], fn: (state: T) => V): ComputedRef<V> {\n return createComputed<T, V>({ getSnapshot, subscribe }, deps, fn);\n }\n\n function notify(): void {\n trie.notifyAll();\n }\n\n function destroy(): void {\n trie.clear();\n pendingPaths.clear();\n }\n\n return {\n get,\n set,\n batch,\n subscribe,\n getSnapshot,\n computed,\n notify,\n destroy,\n } as RawStore<T>;\n}\n","import type {\n RawStore,\n StoreOptions,\n Listener,\n Unsubscribe,\n OperationState,\n HttpClient,\n StateAccessor,\n ApiAccessor,\n ApiRequestOptions,\n} from \"./types.js\";\nimport { asyncStatus } from \"./types.js\";\nimport { createStore } from \"./store.js\";\n\nconst IDLE_STATE: OperationState = { status: asyncStatus(\"idle\"), error: null };\n\nconst defaultHttpClient: HttpClient = {\n async request(url, init) {\n const fetchInit: RequestInit = { method: init?.method ?? \"GET\" };\n const merged = { ...defaultHeaders, ...init?.headers };\n if (Object.keys(merged).length) { fetchInit.headers = merged; }\n if (init?.body !== undefined) {\n fetchInit.body = JSON.stringify(init.body);\n fetchInit.headers = { \"Content-Type\": \"application/json\", ...merged };\n }\n const res = await fetch(url, fetchInit);\n if (!res.ok) {\n let message = `HTTP ${res.status}`;\n try {\n const text = await res.text();\n if (text) {\n const json = JSON.parse(text);\n message = json.error ?? json.message ?? message;\n }\n } catch {}\n throw new Error(message);\n }\n const text = await res.text();\n return text ? JSON.parse(text) : undefined;\n },\n};\n\nlet httpClient: HttpClient = defaultHttpClient;\nlet defaultHeaders: Record<string, string> = {};\n\n/** Replace the global HTTP client used by `api.get` and `api.post/put/patch/delete`. */\nexport function setHttpClient(client: HttpClient): void {\n httpClient = client;\n}\n\n/** Set default headers merged into every HTTP request. Per-request headers override defaults. */\nexport function setDefaultHeaders(headers: Record<string, string>): void {\n defaultHeaders = headers;\n}\n\nexport class SnapStore<T extends object, K extends string = string> {\n private _store: RawStore<T>;\n private _operations = new Map<K, OperationState>();\n private _generations = new Map<K, number>();\n\n protected readonly state: StateAccessor<T>;\n protected readonly api: ApiAccessor<K>;\n\n constructor(initialState: T, options?: StoreOptions) {\n this._store = createStore(initialState, options);\n\n const store = this._store;\n const operations = this._operations;\n const generations = this._generations;\n\n // takeLatest semantic: if a newer call starts for the same key, the older\n // call's promise resolves silently (no reject, no state update).\n const doFetch = async (key: K, fn: () => Promise<void>): Promise<void> => {\n const gen = (generations.get(key) ?? 0) + 1;\n generations.set(key, gen);\n operations.set(key, { status: asyncStatus(\"loading\"), error: null });\n store.notify();\n try {\n await fn();\n if (generations.get(key) !== gen) { return; }\n operations.set(key, { status: asyncStatus(\"ready\"), error: null });\n } catch (e) {\n if (generations.get(key) !== gen) { return; }\n operations.set(key, {\n status: asyncStatus(\"error\"),\n error: e instanceof Error ? e.message : \"Unknown error\",\n });\n store.notify();\n throw e;\n }\n store.notify();\n };\n\n const doSend = async <R>(key: K, method: string, url: string, options?: ApiRequestOptions<R>): Promise<void> => {\n await doFetch(key, async () => {\n try {\n const data = await httpClient.request<R>(url, {\n method,\n body: options?.body,\n headers: options?.headers,\n });\n options?.onSuccess?.(data);\n } catch (e) {\n options?.onError?.(e instanceof Error ? e : new Error(\"Unknown error\"));\n throw e;\n }\n });\n };\n\n this.state = {\n get: ((path?: string): unknown => {\n if (path === undefined) { return store.get(); }\n return (store.get as (p: string) => unknown)(path);\n }) as StateAccessor<T>[\"get\"],\n\n set: (path, value) => store.set(path, value),\n batch: (fn) => store.batch(fn),\n computed: (deps, fn) => store.computed(deps, fn),\n\n append: (path, ...items) => {\n store.set(path as any, ((prev: any) => [...(prev as any[]), ...items]) as any);\n },\n prepend: (path, ...items) => {\n store.set(path as any, ((prev: any) => [...items, ...(prev as any[])]) as any);\n },\n insertAt: (path, index, ...items) => {\n store.set(path as any, ((prev: any) => {\n const arr = prev as any[];\n return [...arr.slice(0, index), ...items, ...arr.slice(index)];\n }) as any);\n },\n patch: (path, predicate, updates) => {\n store.set(path as any, ((prev: any) => {\n const arr = prev as any[];\n let changed = false;\n const result = arr.map((item: any) => {\n if (item == null) { return item; }\n if (predicate(item)) {\n changed = true;\n return Object.assign(Object.create(Object.getPrototypeOf(item)), item, updates);\n }\n return item;\n });\n return changed ? result : arr;\n }) as any);\n },\n remove: (path, predicate) => {\n store.set(path as any, ((prev: any) => {\n const arr = prev as any[];\n const result = arr.filter((item: any) => !predicate(item));\n return result.length === arr.length ? arr : result;\n }) as any);\n },\n removeAt: (path, index) => {\n store.set(path as any, ((prev: any) => {\n const arr = prev as any[];\n const i = index < 0 ? arr.length + index : index;\n if (i < 0 || i >= arr.length) {\n throw new RangeError(`Index ${index} out of bounds for array of length ${arr.length}`);\n }\n return [...arr.slice(0, i), ...arr.slice(i + 1)];\n }) as any);\n },\n at: (path, index) => {\n return (store.get(path as any) as any[]).at(index);\n },\n filter: (path, predicate) => {\n return (store.get(path as any) as any[]).filter(predicate);\n },\n find: (path, predicate) => {\n return (store.get(path as any) as any[]).find(predicate);\n },\n findIndexOf: (path, predicate) => {\n return (store.get(path as any) as any[]).findIndex(predicate);\n },\n count: (path, predicate) => {\n return (store.get(path as any) as any[]).filter(predicate).length;\n },\n };\n\n this.api = {\n fetch: doFetch,\n get: async <R>(key: K, url: string, onSuccess?: (data: R) => void): Promise<void> => {\n await doFetch(key, async () => {\n const data = await httpClient.request<R>(url);\n onSuccess?.(data);\n });\n },\n post: (key, url, options?) => doSend(key, \"POST\", url, options),\n put: (key, url, options?) => doSend(key, \"PUT\", url, options),\n patch: (key, url, options?) => doSend(key, \"PATCH\", url, options),\n delete: (key, url, options?) => doSend(key, \"DELETE\", url, options),\n };\n }\n\n /** Subscribe to all state changes. Returns an unsubscribe function. */\n subscribe(callback: Listener): Unsubscribe;\n /** Subscribe to changes at a specific dot-separated path. */\n subscribe(path: string, callback: Listener): Unsubscribe;\n subscribe(pathOrCallback: string | Listener, callback?: Listener): Unsubscribe {\n if (typeof pathOrCallback === \"function\") {\n return this._store.subscribe(pathOrCallback);\n }\n return this._store.subscribe(pathOrCallback, callback!);\n }\n\n /** Return a snapshot of the current state. Compatible with React's `useSyncExternalStore`. */\n getSnapshot = (): T => {\n return this._store.getSnapshot();\n };\n\n /** Get the async status of an operation by key. Returns `idle` if never started. */\n getStatus(key: K): OperationState {\n return { ...(this._operations.get(key) ?? IDLE_STATE) };\n }\n\n /** Tear down subscriptions and cleanup. */\n destroy(): void {\n this._store.destroy();\n }\n}\n"],"mappings":"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","names":["next","text","options","Connected"]}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
type AsyncStatusValue = "idle" | "loading" | "ready" | "error";
|
|
2
|
+
interface AsyncStatus {
|
|
3
|
+
readonly value: AsyncStatusValue;
|
|
4
|
+
readonly isIdle: boolean;
|
|
5
|
+
readonly isLoading: boolean;
|
|
6
|
+
readonly isReady: boolean;
|
|
7
|
+
readonly isError: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare function asyncStatus(value: AsyncStatusValue): AsyncStatus;
|
|
10
|
+
/** Async operation status and error. Tracks the lifecycle of an `api.fetch`/`api.get`/`api.post` call. */
|
|
11
|
+
interface OperationState {
|
|
12
|
+
status: AsyncStatus;
|
|
13
|
+
error: string | null;
|
|
14
|
+
}
|
|
15
|
+
type Listener = () => void;
|
|
16
|
+
type Unsubscribe = () => void;
|
|
17
|
+
type DotPaths<T, Prefix extends string = ""> = T extends object ? {
|
|
18
|
+
[K in keyof T & string]: `${Prefix}${K}` | DotPaths<T[K], `${Prefix}${K}.`>;
|
|
19
|
+
}[keyof T & string] : never;
|
|
20
|
+
type GetByPath<T, P extends string> = P extends "" ? T : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? GetByPath<T[K], Rest> : never : P extends keyof T ? T[P] : never;
|
|
21
|
+
type ArrayPaths<T> = {
|
|
22
|
+
[K in keyof T & string]: T[K] extends any[] ? K : never;
|
|
23
|
+
}[keyof T & string];
|
|
24
|
+
type ObjectArrayPaths<T> = {
|
|
25
|
+
[K in keyof T & string]: T[K] extends (infer V)[] ? V extends Date | RegExp | Map<any, any> | Set<any> | Function | any[] ? never : V extends Record<string, any> ? K : never : never;
|
|
26
|
+
}[keyof T & string];
|
|
27
|
+
type ElementOf<A> = A extends (infer V)[] ? V : never;
|
|
28
|
+
type Updater<V> = V | ((prev: V) => V);
|
|
29
|
+
interface StoreOptions {
|
|
30
|
+
/** Auto-batch synchronous sets via microtask (default: true) */
|
|
31
|
+
autoBatch?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Handle to a computed (derived) value. Call `get()` to read, `destroy()` to stop tracking. */
|
|
34
|
+
interface ComputedRef<V> {
|
|
35
|
+
get(): V;
|
|
36
|
+
destroy(): void;
|
|
37
|
+
}
|
|
38
|
+
/** Options for an HTTP request (method, body, headers). */
|
|
39
|
+
interface HttpRequestInit {
|
|
40
|
+
method?: string;
|
|
41
|
+
body?: unknown;
|
|
42
|
+
headers?: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
/** Interface for the HTTP layer used by `api.get` and `api.post/put/patch/delete`. */
|
|
45
|
+
interface HttpClient {
|
|
46
|
+
request<R = unknown>(url: string, init?: HttpRequestInit): Promise<R>;
|
|
47
|
+
}
|
|
48
|
+
/** Options for HTTP verb methods (`api.post`, `api.put`, etc.). */
|
|
49
|
+
interface ApiRequestOptions<R = unknown> {
|
|
50
|
+
body?: unknown;
|
|
51
|
+
headers?: Record<string, string>;
|
|
52
|
+
onSuccess?: (data: R) => void;
|
|
53
|
+
onError?: (error: Error) => void;
|
|
54
|
+
}
|
|
55
|
+
interface StateAccessor<T extends object> {
|
|
56
|
+
get(): T;
|
|
57
|
+
get<P extends DotPaths<T>>(path: P): GetByPath<T, P>;
|
|
58
|
+
set<P extends DotPaths<T>>(path: P, value: Updater<GetByPath<T, P>>): void;
|
|
59
|
+
batch(fn: () => void): void;
|
|
60
|
+
computed<V>(deps: (keyof T & string)[], fn: (state: T) => V): ComputedRef<V>;
|
|
61
|
+
append<P extends ArrayPaths<T>>(path: P, ...items: ElementOf<T[P]>[]): void;
|
|
62
|
+
prepend<P extends ArrayPaths<T>>(path: P, ...items: ElementOf<T[P]>[]): void;
|
|
63
|
+
insertAt<P extends ArrayPaths<T>>(path: P, index: number, ...items: ElementOf<T[P]>[]): void;
|
|
64
|
+
patch<P extends ObjectArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean, updates: Partial<ElementOf<T[P]>>): void;
|
|
65
|
+
remove<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): void;
|
|
66
|
+
removeAt<P extends ArrayPaths<T>>(path: P, index: number): void;
|
|
67
|
+
at<P extends ArrayPaths<T>>(path: P, index: number): ElementOf<T[P]> | undefined;
|
|
68
|
+
filter<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): ElementOf<T[P]>[];
|
|
69
|
+
find<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): ElementOf<T[P]> | undefined;
|
|
70
|
+
findIndexOf<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): number;
|
|
71
|
+
count<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): number;
|
|
72
|
+
}
|
|
73
|
+
interface ApiAccessor<K extends string> {
|
|
74
|
+
fetch(key: K, fn: () => Promise<void>): Promise<void>;
|
|
75
|
+
get<R = unknown>(key: K, url: string, onSuccess?: (data: R) => void): Promise<void>;
|
|
76
|
+
post<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;
|
|
77
|
+
put<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;
|
|
78
|
+
patch<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;
|
|
79
|
+
delete<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Replace the global HTTP client used by `api.get` and `api.post/put/patch/delete`. */
|
|
83
|
+
declare function setHttpClient(client: HttpClient): void;
|
|
84
|
+
/** Set default headers merged into every HTTP request. Per-request headers override defaults. */
|
|
85
|
+
declare function setDefaultHeaders(headers: Record<string, string>): void;
|
|
86
|
+
declare class SnapStore<T extends object, K extends string = string> {
|
|
87
|
+
private _store;
|
|
88
|
+
private _operations;
|
|
89
|
+
private _generations;
|
|
90
|
+
protected readonly state: StateAccessor<T>;
|
|
91
|
+
protected readonly api: ApiAccessor<K>;
|
|
92
|
+
constructor(initialState: T, options?: StoreOptions);
|
|
93
|
+
/** Subscribe to all state changes. Returns an unsubscribe function. */
|
|
94
|
+
subscribe(callback: Listener): Unsubscribe;
|
|
95
|
+
/** Subscribe to changes at a specific dot-separated path. */
|
|
96
|
+
subscribe(path: string, callback: Listener): Unsubscribe;
|
|
97
|
+
/** Return a snapshot of the current state. Compatible with React's `useSyncExternalStore`. */
|
|
98
|
+
getSnapshot: () => T;
|
|
99
|
+
/** Get the async status of an operation by key. Returns `idle` if never started. */
|
|
100
|
+
getStatus(key: K): OperationState;
|
|
101
|
+
/** Tear down subscriptions and cleanup. */
|
|
102
|
+
destroy(): void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface ConnectConfig<S, MappedProps> {
|
|
106
|
+
props: (store: S) => MappedProps;
|
|
107
|
+
fetch: (store: S) => Promise<void>;
|
|
108
|
+
loading?: React.ComponentType;
|
|
109
|
+
error?: React.ComponentType<{
|
|
110
|
+
error: string;
|
|
111
|
+
}>;
|
|
112
|
+
}
|
|
113
|
+
type PickFn<T extends object> = <P extends DotPaths<T>>(path: P) => GetByPath<T, P>;
|
|
114
|
+
interface SelectConnectConfig<T extends object, MappedProps> {
|
|
115
|
+
select: (pick: PickFn<T>) => MappedProps;
|
|
116
|
+
}
|
|
117
|
+
declare class ReactSnapStore<T extends object, K extends string = string> extends SnapStore<T, K> {
|
|
118
|
+
constructor(initialState: T, options?: StoreOptions);
|
|
119
|
+
/** Wire a component to the store, injecting props derived from state via `mapToProps`. */
|
|
120
|
+
connect<P extends object, MappedProps extends Record<string, unknown>>(Component: React.ComponentType<P>, mapToProps: (store: this) => MappedProps): React.FC<Omit<P, keyof MappedProps>>;
|
|
121
|
+
/** Wire a component to the store with async data fetching, loading, and error handling. */
|
|
122
|
+
connect<P extends object, MappedProps extends Record<string, unknown>>(Component: React.ComponentType<P>, config: ConnectConfig<this, MappedProps>): React.FC<Omit<P, keyof MappedProps | "status" | "error">>;
|
|
123
|
+
/** Wire a component to the store with granular path-based subscriptions via `select`.
|
|
124
|
+
* Paths are captured once at connect-time — select must use a stable set of paths.
|
|
125
|
+
* For conditional/dynamic path selection, use the `mapToProps` overload instead. */
|
|
126
|
+
connect<P extends object, MappedProps extends Record<string, unknown>>(Component: React.ComponentType<P>, config: SelectConnectConfig<T, MappedProps>): React.FC<Omit<P, keyof MappedProps>>;
|
|
127
|
+
private _connectWithSelect;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export { type ApiRequestOptions, type AsyncStatus, type AsyncStatusValue, type HttpClient, type HttpRequestInit, type OperationState, ReactSnapStore as SnapStore, asyncStatus, setDefaultHeaders, setHttpClient };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
type AsyncStatusValue = "idle" | "loading" | "ready" | "error";
|
|
2
|
+
interface AsyncStatus {
|
|
3
|
+
readonly value: AsyncStatusValue;
|
|
4
|
+
readonly isIdle: boolean;
|
|
5
|
+
readonly isLoading: boolean;
|
|
6
|
+
readonly isReady: boolean;
|
|
7
|
+
readonly isError: boolean;
|
|
8
|
+
}
|
|
9
|
+
declare function asyncStatus(value: AsyncStatusValue): AsyncStatus;
|
|
10
|
+
/** Async operation status and error. Tracks the lifecycle of an `api.fetch`/`api.get`/`api.post` call. */
|
|
11
|
+
interface OperationState {
|
|
12
|
+
status: AsyncStatus;
|
|
13
|
+
error: string | null;
|
|
14
|
+
}
|
|
15
|
+
type Listener = () => void;
|
|
16
|
+
type Unsubscribe = () => void;
|
|
17
|
+
type DotPaths<T, Prefix extends string = ""> = T extends object ? {
|
|
18
|
+
[K in keyof T & string]: `${Prefix}${K}` | DotPaths<T[K], `${Prefix}${K}.`>;
|
|
19
|
+
}[keyof T & string] : never;
|
|
20
|
+
type GetByPath<T, P extends string> = P extends "" ? T : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? GetByPath<T[K], Rest> : never : P extends keyof T ? T[P] : never;
|
|
21
|
+
type ArrayPaths<T> = {
|
|
22
|
+
[K in keyof T & string]: T[K] extends any[] ? K : never;
|
|
23
|
+
}[keyof T & string];
|
|
24
|
+
type ObjectArrayPaths<T> = {
|
|
25
|
+
[K in keyof T & string]: T[K] extends (infer V)[] ? V extends Date | RegExp | Map<any, any> | Set<any> | Function | any[] ? never : V extends Record<string, any> ? K : never : never;
|
|
26
|
+
}[keyof T & string];
|
|
27
|
+
type ElementOf<A> = A extends (infer V)[] ? V : never;
|
|
28
|
+
type Updater<V> = V | ((prev: V) => V);
|
|
29
|
+
interface StoreOptions {
|
|
30
|
+
/** Auto-batch synchronous sets via microtask (default: true) */
|
|
31
|
+
autoBatch?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** Handle to a computed (derived) value. Call `get()` to read, `destroy()` to stop tracking. */
|
|
34
|
+
interface ComputedRef<V> {
|
|
35
|
+
get(): V;
|
|
36
|
+
destroy(): void;
|
|
37
|
+
}
|
|
38
|
+
/** Options for an HTTP request (method, body, headers). */
|
|
39
|
+
interface HttpRequestInit {
|
|
40
|
+
method?: string;
|
|
41
|
+
body?: unknown;
|
|
42
|
+
headers?: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
/** Interface for the HTTP layer used by `api.get` and `api.post/put/patch/delete`. */
|
|
45
|
+
interface HttpClient {
|
|
46
|
+
request<R = unknown>(url: string, init?: HttpRequestInit): Promise<R>;
|
|
47
|
+
}
|
|
48
|
+
/** Options for HTTP verb methods (`api.post`, `api.put`, etc.). */
|
|
49
|
+
interface ApiRequestOptions<R = unknown> {
|
|
50
|
+
body?: unknown;
|
|
51
|
+
headers?: Record<string, string>;
|
|
52
|
+
onSuccess?: (data: R) => void;
|
|
53
|
+
onError?: (error: Error) => void;
|
|
54
|
+
}
|
|
55
|
+
interface StateAccessor<T extends object> {
|
|
56
|
+
get(): T;
|
|
57
|
+
get<P extends DotPaths<T>>(path: P): GetByPath<T, P>;
|
|
58
|
+
set<P extends DotPaths<T>>(path: P, value: Updater<GetByPath<T, P>>): void;
|
|
59
|
+
batch(fn: () => void): void;
|
|
60
|
+
computed<V>(deps: (keyof T & string)[], fn: (state: T) => V): ComputedRef<V>;
|
|
61
|
+
append<P extends ArrayPaths<T>>(path: P, ...items: ElementOf<T[P]>[]): void;
|
|
62
|
+
prepend<P extends ArrayPaths<T>>(path: P, ...items: ElementOf<T[P]>[]): void;
|
|
63
|
+
insertAt<P extends ArrayPaths<T>>(path: P, index: number, ...items: ElementOf<T[P]>[]): void;
|
|
64
|
+
patch<P extends ObjectArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean, updates: Partial<ElementOf<T[P]>>): void;
|
|
65
|
+
remove<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): void;
|
|
66
|
+
removeAt<P extends ArrayPaths<T>>(path: P, index: number): void;
|
|
67
|
+
at<P extends ArrayPaths<T>>(path: P, index: number): ElementOf<T[P]> | undefined;
|
|
68
|
+
filter<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): ElementOf<T[P]>[];
|
|
69
|
+
find<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): ElementOf<T[P]> | undefined;
|
|
70
|
+
findIndexOf<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): number;
|
|
71
|
+
count<P extends ArrayPaths<T>>(path: P, predicate: (item: ElementOf<T[P]>) => boolean): number;
|
|
72
|
+
}
|
|
73
|
+
interface ApiAccessor<K extends string> {
|
|
74
|
+
fetch(key: K, fn: () => Promise<void>): Promise<void>;
|
|
75
|
+
get<R = unknown>(key: K, url: string, onSuccess?: (data: R) => void): Promise<void>;
|
|
76
|
+
post<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;
|
|
77
|
+
put<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;
|
|
78
|
+
patch<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;
|
|
79
|
+
delete<R = unknown>(key: K, url: string, options?: ApiRequestOptions<R>): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Replace the global HTTP client used by `api.get` and `api.post/put/patch/delete`. */
|
|
83
|
+
declare function setHttpClient(client: HttpClient): void;
|
|
84
|
+
/** Set default headers merged into every HTTP request. Per-request headers override defaults. */
|
|
85
|
+
declare function setDefaultHeaders(headers: Record<string, string>): void;
|
|
86
|
+
declare class SnapStore<T extends object, K extends string = string> {
|
|
87
|
+
private _store;
|
|
88
|
+
private _operations;
|
|
89
|
+
private _generations;
|
|
90
|
+
protected readonly state: StateAccessor<T>;
|
|
91
|
+
protected readonly api: ApiAccessor<K>;
|
|
92
|
+
constructor(initialState: T, options?: StoreOptions);
|
|
93
|
+
/** Subscribe to all state changes. Returns an unsubscribe function. */
|
|
94
|
+
subscribe(callback: Listener): Unsubscribe;
|
|
95
|
+
/** Subscribe to changes at a specific dot-separated path. */
|
|
96
|
+
subscribe(path: string, callback: Listener): Unsubscribe;
|
|
97
|
+
/** Return a snapshot of the current state. Compatible with React's `useSyncExternalStore`. */
|
|
98
|
+
getSnapshot: () => T;
|
|
99
|
+
/** Get the async status of an operation by key. Returns `idle` if never started. */
|
|
100
|
+
getStatus(key: K): OperationState;
|
|
101
|
+
/** Tear down subscriptions and cleanup. */
|
|
102
|
+
destroy(): void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface ConnectConfig<S, MappedProps> {
|
|
106
|
+
props: (store: S) => MappedProps;
|
|
107
|
+
fetch: (store: S) => Promise<void>;
|
|
108
|
+
loading?: React.ComponentType;
|
|
109
|
+
error?: React.ComponentType<{
|
|
110
|
+
error: string;
|
|
111
|
+
}>;
|
|
112
|
+
}
|
|
113
|
+
type PickFn<T extends object> = <P extends DotPaths<T>>(path: P) => GetByPath<T, P>;
|
|
114
|
+
interface SelectConnectConfig<T extends object, MappedProps> {
|
|
115
|
+
select: (pick: PickFn<T>) => MappedProps;
|
|
116
|
+
}
|
|
117
|
+
declare class ReactSnapStore<T extends object, K extends string = string> extends SnapStore<T, K> {
|
|
118
|
+
constructor(initialState: T, options?: StoreOptions);
|
|
119
|
+
/** Wire a component to the store, injecting props derived from state via `mapToProps`. */
|
|
120
|
+
connect<P extends object, MappedProps extends Record<string, unknown>>(Component: React.ComponentType<P>, mapToProps: (store: this) => MappedProps): React.FC<Omit<P, keyof MappedProps>>;
|
|
121
|
+
/** Wire a component to the store with async data fetching, loading, and error handling. */
|
|
122
|
+
connect<P extends object, MappedProps extends Record<string, unknown>>(Component: React.ComponentType<P>, config: ConnectConfig<this, MappedProps>): React.FC<Omit<P, keyof MappedProps | "status" | "error">>;
|
|
123
|
+
/** Wire a component to the store with granular path-based subscriptions via `select`.
|
|
124
|
+
* Paths are captured once at connect-time — select must use a stable set of paths.
|
|
125
|
+
* For conditional/dynamic path selection, use the `mapToProps` overload instead. */
|
|
126
|
+
connect<P extends object, MappedProps extends Record<string, unknown>>(Component: React.ComponentType<P>, config: SelectConnectConfig<T, MappedProps>): React.FC<Omit<P, keyof MappedProps>>;
|
|
127
|
+
private _connectWithSelect;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export { type ApiRequestOptions, type AsyncStatus, type AsyncStatusValue, type HttpClient, type HttpRequestInit, type OperationState, ReactSnapStore as SnapStore, asyncStatus, setDefaultHeaders, setHttpClient };
|