UNPKG

stunk

Version:

Stunk is a lightweight, framework-agnostic state management library for JavaScript and TypeScript. It uses chunk-based state units for efficient updates, reactivity, and performance optimization in React, Vue(WIP), Svelte(Coming soon), and Vanilla JS/TS a

213 lines (206 loc) 9.58 kB
import { C as Chunk, R as ReadOnlyChunk } from '../core-DAc7hZla.js'; import { P as PaginatedAsyncChunk, a as PaginationState, A as AsyncChunk, I as InfiniteAsyncChunk, M as Mutation, b as MutationResult } from '../mutation-DLvwAQ6L.js'; /** * Subscribes to a chunk and returns its current value along with setters, * reset, and destroy. Accepts both writable `Chunk<T>` and read-only * `ReadOnlyChunk<T>` (e.g. derived chunks from `.derive()` or `select()`). * * For read-only chunks, the returned `set` and `reset` will be no-ops at * runtime — prefer `useChunkValue` for derived/read-only chunks. * * @example * const count = chunk(0); * const [value, setValue] = useChunk(count); * * @example * // Works with derived chunks too * const doubled = count.derive(n => n * 2); * const [value] = useChunk(doubled); */ declare function useChunk<T, S = T>(chunk: Chunk<T> | ReadOnlyChunk<T>, selector?: (value: T) => S): readonly [S, (valueOrUpdater: T | ((currentValue: T) => T)) => void, () => void, () => void]; /** * Subscribes to a chunk and returns only its current value. * Accepts both writable `Chunk<T>` and read-only `ReadOnlyChunk<T>` * (e.g. derived chunks from `.derive()`, `select()`, or `computed()`). * * Prefer this over `useChunk` when you only need to read — it makes * the read-only intent explicit and works correctly with derived chunks. * * @example * const isAuthenticated = userChunk.derive(u => u !== null); * const auth = useChunkValue(isAuthenticated); // ✅ no type error * * @example * const total = computed(() => price.get() * qty.get()); * const value = useChunkValue(total); // ✅ no type error */ declare function useChunkValue<T, S = T>(chunk: Chunk<T> | ReadOnlyChunk<T>, selector?: (value: T) => S): S; interface UseAsyncChunkResult<T, E extends Error, P extends Record<string, any>> { data: T | null; loading: boolean; error: E | null; lastFetched?: number; isPlaceholderData: boolean; reload: (params?: Partial<P>) => Promise<void>; refresh: (params?: Partial<P>) => Promise<void>; mutate: (mutator: (currentData: T | null) => T | null) => void; reset: (refetch?: boolean) => void; } interface UseAsyncChunkResultWithParams<T, E extends Error, P extends Record<string, any>> extends UseAsyncChunkResult<T, E, P> { setParams: (params: Partial<Record<keyof P, P[keyof P] | null>>) => void; clearParams: () => void; } interface UseAsyncChunkResultWithPagination<T, E extends Error, P extends Record<string, any>> extends UseAsyncChunkResult<T, E, P> { pagination?: PaginationState; nextPage: () => Promise<void>; prevPage: () => Promise<void>; goToPage: (page: number) => Promise<void>; resetPagination: () => Promise<void>; } interface UseAsyncChunkResultWithParamsAndPagination<T, E extends Error, P extends Record<string, any>> extends UseAsyncChunkResultWithParams<T, E, P>, Omit<UseAsyncChunkResultWithPagination<T, E, P>, keyof UseAsyncChunkResult<T, E, P>> { } interface UseAsyncChunkOptions<T = any, E extends Error = Error, P extends Record<string, any> = {}> { /** * Parameters to pass to the fetcher. When these change between renders, * the chunk automatically re-fetches with the new values. */ params?: Partial<P>; /** * Force a fetch on mount even when the chunk has no params. * Ignored if params is provided. * (default: false) */ fetchOnMount?: boolean; /** * Whether the chunk is enabled. * If false, the chunk will not fetch data. * (default: true) */ enabled?: boolean; /** * Called after every successful fetch at the hook level. * Has full access to React context — safe to call navigate(), setState(), etc. */ onSuccess?: (data: T) => void; /** * Called when a fetch fails at the hook level. * Has full access to React context — safe to call navigate(), setState(), etc. */ onError?: (error: E) => void; } declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T, E> & { setParams: (params: Partial<P>) => void; }, options?: UseAsyncChunkOptions<T, E, P>): UseAsyncChunkResultWithParamsAndPagination<T, E, P>; declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T, E>, options?: UseAsyncChunkOptions<T, E, P>): UseAsyncChunkResultWithPagination<T, E, P>; declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: AsyncChunk<T, E> & { setParams: (params: Partial<P>) => void; }, options?: UseAsyncChunkOptions<T, E, P>): UseAsyncChunkResultWithParams<T, E, P>; declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: AsyncChunk<T, E>, options?: UseAsyncChunkOptions<T, E, P>): UseAsyncChunkResult<T, E, P>; interface UseInfiniteAsyncChunkOptions<P extends Record<string, any>> extends Omit<UseAsyncChunkOptions<P>, 'initialParams' | 'params'> { /** * @deprecated Use `params` instead. Will be removed in v3 stable. */ initialParams?: Omit<Partial<P>, 'page' | 'pageSize'>; /** Reactive parameters — page and pageSize are managed automatically. Re-fetches from page 1 when these change. */ params?: Omit<Partial<P>, 'page' | 'pageSize'>; /** Automatically load next page when sentinel enters viewport (default: true) */ autoLoad?: boolean; /** IntersectionObserver threshold — 0.0 to 1.0 (default: 1.0) */ threshold?: number; } interface UseInfiniteAsyncChunkResult<T, E extends Error, P extends Record<string, any>> { data: T[] | null; loading: boolean; error: E | null; lastFetched?: number; isPlaceholderData: boolean; /** True when fetching a new page while existing data is already loaded */ isFetchingMore: boolean; /** True if more pages are available */ hasMore: boolean; reload: (params?: Partial<P>) => Promise<void>; refresh: (params?: Partial<P>) => Promise<void>; mutate: (mutator: (currentData: T[] | null) => T[]) => void; reset: () => void; nextPage: () => Promise<void>; prevPage: () => Promise<void>; goToPage: (page: number) => Promise<void>; resetPagination: () => Promise<void>; /** Manually trigger loading the next page */ loadMore: () => void; /** Attach this ref to a sentinel element at the bottom of your list */ observerTarget: React.RefObject<HTMLElement>; } /** * Subscribes to an infinite async chunk and wires up automatic infinite scroll. * * Attach `observerTarget` to a sentinel element at the bottom of your list — * the next page loads automatically when it enters the viewport. * Use `loadMore()` for manual triggering. * * @param chunk - An `InfiniteAsyncChunk` instance. * @param options.autoLoad - Auto-load on scroll (default: true). * @param options.threshold - IntersectionObserver threshold 0.0–1.0 (default: 1.0). * @param options.params - Reactive params excluding `page` and `pageSize`. Resets to page 1 and refetches when changed. * * @example * const { data, loading, hasMore, observerTarget, loadMore } = useInfiniteAsyncChunk(postsChunk); * * @example * // Reactive search — resets to page 1 automatically when searchTerm changes * const { data } = useInfiniteAsyncChunk(companiesChunk, { * params: { search: searchTerm }, * }); * * return ( * <> * {data?.map(post => <Post key={post.id} {...post} />)} * <div ref={observerTarget} /> * </> * ); */ declare function useInfiniteAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(chunk: InfiniteAsyncChunk<T, E, P>, options?: UseInfiniteAsyncChunkOptions<P>): UseInfiniteAsyncChunkResult<T, E, P>; interface UseMutationResult<TData, TError extends Error = Error, TVariables = void> { /** Current mutation state */ loading: boolean; data: TData | null; error: TError | null; isSuccess: boolean; /** * Execute the mutation. Always resolves — never throws. * Safe to fire and forget, or await for local UI control. * * @example * // Fire and forget * mutate({ title: 'Hello' }); * * // Await for local control — no try/catch needed * const { data, error } = await mutate({ title: 'Hello' }); * if (!error) router.push('/posts'); */ mutate: TVariables extends void ? () => Promise<MutationResult<TData, TError>> : (variables: TVariables) => Promise<MutationResult<TData, TError>>; /** Reset mutation state back to initial */ reset: () => void; } /** * Subscribes to a mutation instance and returns its reactive state with `mutate` and `reset`. * * @param mutation - A `mutation()` instance from `stunk/query`. * * @example * const createPost = mutation( * async (data: NewPost) => fetchAPI('/posts', { method: 'POST', body: data }), * { invalidates: [postsChunk] } * ); * * function CreatePostForm() { * const { mutate, loading, error, isSuccess } = useMutation(createPost); * * const handleSubmit = async (data: NewPost) => { * const { error } = await mutate(data); * if (!error) router.push('/posts'); * }; * } */ declare function useMutation<TData, TError extends Error = Error, TVariables = void>(mutation: Mutation<TData, TError, TVariables>): UseMutationResult<TData, TError, TVariables>; export { type UseMutationResult, useAsyncChunk, useChunk, useChunkValue, useInfiniteAsyncChunk, useMutation };