stunk 2.8.1 → 3.0.0-beta.1

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.
@@ -1,98 +1,52 @@
1
- import { C as Chunk } from '../core-DMY69lzg.cjs';
1
+ import { C as Chunk } from '../core-DsoxfUCH.cjs';
2
+ import { P as PaginatedAsyncChunk, a as PaginationState, A as AsyncChunk, I as InfiniteAsyncChunk, M as Mutation, b as MutationResult } from '../mutation-Txd2ni6w.cjs';
2
3
 
3
4
  /**
4
- * A lightweight hook that subscribes to a chunk and returns its current value, along with setters, selector, reset and destroy.
5
- * Ensures reactivity and prevents unnecessary re-renders.
5
+ * Subscribes to a chunk and returns its current value along with `set`, `reset`, and `destroy`.
6
+ *
7
+ * Pass an optional `selector` to derive a slice of the value and avoid
8
+ * unnecessary re-renders when unrelated fields change.
9
+ *
10
+ * @param chunk - The chunk to subscribe to.
11
+ * @param selector - Optional function to select a derived value.
12
+ * @returns `[value, set, reset, destroy]`
13
+ *
14
+ * @example
15
+ * const [count, setCount, reset] = useChunk(countChunk);
16
+ *
17
+ * @example
18
+ * // Only re-renders when `name` changes
19
+ * const [name, setUser] = useChunk(userChunk, u => u.name);
6
20
  */
7
21
  declare function useChunk<T, S = T>(chunk: Chunk<T>, selector?: (value: T) => S): readonly [S, (valueOrUpdater: T | ((currentValue: T) => T)) => void, () => void, () => void];
8
22
 
9
23
  /**
10
- * A hook for creating a read-only derived value from a chunk.
11
- * Ensures reactivity and updates when the source chunk changes.
12
- */
13
- declare function useDerive<T, D>(chunk: Chunk<T>, fn: (value: T) => D): D;
14
-
15
- type ChunkValue<T> = T extends Chunk<infer U> ? U : never;
16
- type DependencyValues<T extends Chunk<any>[]> = {
17
- [K in keyof T]: T[K] extends Chunk<any> ? ChunkValue<T[K]> : never;
18
- };
19
-
20
- /**
21
- * A hook that computes a value based on multiple chunks.
22
- * Automatically re-computes when any dependency changes.
23
- */
24
- declare function useComputed<TDeps extends Chunk<any>[], TResult>(dependencies: [...TDeps], computeFn: (...args: DependencyValues<TDeps>) => TResult): TResult;
25
-
26
- /**
27
- * A lightweight hook that subscribes to a chunk and returns only its current value.
28
- * Useful for read-only components that don't need to update the chunk.
24
+ * Subscribes to a chunk and returns only its current value.
25
+ * Use this in read-only components that never need to call `set`.
26
+ *
27
+ * @param chunk - The chunk to subscribe to.
28
+ * @param selector - Optional function to select a derived value.
29
+ *
30
+ * @example
31
+ * const name = useChunkValue(userChunk, u => u.name);
29
32
  */
30
33
  declare function useChunkValue<T, S = T>(chunk: Chunk<T>, selector?: (value: T) => S): S;
31
34
 
32
- /**
33
- * A hook that subscribes to a specific property of a chunk.
34
- * This optimizes renders by only updating when the selected property changes.
35
- */
36
- declare function useChunkProperty<T, K extends keyof T>(chunk: Chunk<T>, property: K): T[K];
37
-
38
- /**
39
- * Hook to read values from multiple chunks at once.
40
- * Only re-renders when any of the chunk values change.
41
- */
42
- declare function useChunkValues<T extends Chunk<any>[]>(chunks: [...T]): {
43
- [K in keyof T]: T[K] extends Chunk<infer U> ? U : never;
44
- };
45
-
46
- interface AsyncState<T, E extends Error> {
47
- loading: boolean;
48
- error: E | null;
49
- data: T | null;
50
- lastFetched?: number;
51
- }
52
- interface PaginationState {
53
- page: number;
54
- pageSize: number;
55
- total?: number;
56
- hasMore?: boolean;
57
- }
58
- interface AsyncStateWithPagination<T, E extends Error> extends AsyncState<T, E> {
59
- pagination?: PaginationState;
60
- }
61
- interface AsyncChunk<T, E extends Error = Error> extends Chunk<AsyncStateWithPagination<T, E>> {
62
- /** Force reload data */
63
- reload: (params?: any) => Promise<void>;
64
- /** Smart refresh - respects stale time */
65
- refresh: (params?: any) => Promise<void>;
66
- /** Mutate data directly */
67
- mutate: (mutator: (currentData: T | null) => T) => void;
68
- /** Reset to initial state */
69
- reset: () => void;
70
- /** Clean up intervals */
71
- cleanup: () => void;
72
- }
73
- interface PaginatedAsyncChunk<T, E extends Error = Error> extends AsyncChunk<T, E> {
74
- /** Load next page */
75
- nextPage: () => Promise<void>;
76
- /** Load previous page */
77
- prevPage: () => Promise<void>;
78
- /** Go to specific page */
79
- goToPage: (page: number) => Promise<void>;
80
- /** Reset pagination to first page */
81
- resetPagination: () => Promise<void>;
82
- }
83
-
84
35
  interface UseAsyncChunkResult<T, E extends Error, P extends Record<string, any>> {
85
36
  data: T | null;
86
37
  loading: boolean;
87
38
  error: E | null;
88
39
  lastFetched?: number;
40
+ /** True when showing stale data while a new fetch is in progress (keepPreviousData: true) */
41
+ isPlaceholderData: boolean;
89
42
  reload: (params?: Partial<P>) => Promise<void>;
90
43
  refresh: (params?: Partial<P>) => Promise<void>;
91
44
  mutate: (mutator: (currentData: T | null) => T) => void;
92
45
  reset: () => void;
93
46
  }
94
47
  interface UseAsyncChunkResultWithParams<T, E extends Error, P extends Record<string, any>> extends UseAsyncChunkResult<T, E, P> {
95
- setParams: (params: Partial<P>) => void;
48
+ setParams: (params: Partial<Record<keyof P, P[keyof P] | null>>) => void;
49
+ clearParams: () => void;
96
50
  }
97
51
  interface UseAsyncChunkResultWithPagination<T, E extends Error, P extends Record<string, any>> extends UseAsyncChunkResult<T, E, P> {
98
52
  pagination?: PaginationState;
@@ -103,39 +57,122 @@ interface UseAsyncChunkResultWithPagination<T, E extends Error, P extends Record
103
57
  }
104
58
  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>> {
105
59
  }
106
- /**
107
- * A hook that handles asynchronous state with built-in reactivity.
108
- * Provides loading, error, and data states with full asyncChunk functionality.
109
- */
110
60
  interface UseAsyncChunkOptions<P extends Record<string, any> = {}> {
111
- /** Initial parameters to pass to the fetcher */
61
+ /** Initial parameters to pass to the fetcher on mount */
112
62
  initialParams?: Partial<P>;
113
- /** Force fetch on mount, even without params (default: false) */
63
+ /**
64
+ * Force a fetch on mount even when the chunk has no params.
65
+ * Ignored if initialParams is provided.
66
+ * (default: false)
67
+ */
114
68
  fetchOnMount?: boolean;
115
69
  }
116
70
  declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T, E> & {
117
71
  setParams: (params: Partial<P>) => void;
118
- }, options?: UseAsyncChunkOptions<P> | Partial<P>): UseAsyncChunkResultWithParamsAndPagination<T, E, P>;
119
- declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T, E>, options?: UseAsyncChunkOptions<P> | Partial<P>): UseAsyncChunkResultWithPagination<T, E, P>;
72
+ }, options?: UseAsyncChunkOptions<P>): UseAsyncChunkResultWithParamsAndPagination<T, E, P>;
73
+ declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T, E>, options?: UseAsyncChunkOptions<P>): UseAsyncChunkResultWithPagination<T, E, P>;
120
74
  declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: AsyncChunk<T, E> & {
121
75
  setParams: (params: Partial<P>) => void;
122
- }, options?: UseAsyncChunkOptions<P> | Partial<P>): UseAsyncChunkResultWithParams<T, E, P>;
123
- declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: AsyncChunk<T, E>, options?: UseAsyncChunkOptions<P> | Partial<P>): UseAsyncChunkResult<T, E, P>;
76
+ }, options?: UseAsyncChunkOptions<P>): UseAsyncChunkResultWithParams<T, E, P>;
77
+ declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: AsyncChunk<T, E>, options?: UseAsyncChunkOptions<P>): UseAsyncChunkResult<T, E, P>;
124
78
 
125
79
  interface UseInfiniteAsyncChunkOptions<P extends Record<string, any>> extends Omit<UseAsyncChunkOptions<P>, 'initialParams'> {
126
- /** Initial parameters (page and pageSize added automatically) */
80
+ /** Initial parameters page and pageSize are managed automatically */
127
81
  initialParams?: Omit<Partial<P>, 'page' | 'pageSize'>;
128
- /** Enable auto-loading on scroll (default: true) */
82
+ /** Automatically load next page when sentinel enters viewport (default: true) */
129
83
  autoLoad?: boolean;
130
- /** Intersection observer threshold (default: 1.0) */
84
+ /** IntersectionObserver threshold — 0.0 to 1.0 (default: 1.0) */
131
85
  threshold?: number;
132
86
  }
87
+ interface UseInfiniteAsyncChunkResult<T, E extends Error, P extends Record<string, any>> {
88
+ data: T[] | null;
89
+ loading: boolean;
90
+ error: E | null;
91
+ lastFetched?: number;
92
+ isPlaceholderData: boolean;
93
+ /** True when fetching a new page while existing data is already loaded */
94
+ isFetchingMore: boolean;
95
+ /** True if more pages are available */
96
+ hasMore: boolean;
97
+ reload: (params?: Partial<P>) => Promise<void>;
98
+ refresh: (params?: Partial<P>) => Promise<void>;
99
+ mutate: (mutator: (currentData: T[] | null) => T[]) => void;
100
+ reset: () => void;
101
+ nextPage: () => Promise<void>;
102
+ prevPage: () => Promise<void>;
103
+ goToPage: (page: number) => Promise<void>;
104
+ resetPagination: () => Promise<void>;
105
+ /** Manually trigger loading the next page */
106
+ loadMore: () => void;
107
+ /** Attach this ref to a sentinel element at the bottom of your list */
108
+ observerTarget: React.RefObject<HTMLElement>;
109
+ }
133
110
  /**
134
- * Hook for infinite scroll functionality.
135
- * Automatically loads more data when scrolling to the bottom.
111
+ * Subscribes to an infinite async chunk and wires up automatic infinite scroll.
112
+ *
113
+ * Attach `observerTarget` to a sentinel element at the bottom of your list —
114
+ * the next page loads automatically when it enters the viewport.
115
+ * Use `loadMore()` for manual triggering.
116
+ *
117
+ * @param chunk - An `InfiniteAsyncChunk` instance.
118
+ * @param options.autoLoad - Auto-load on scroll (default: true).
119
+ * @param options.threshold - IntersectionObserver threshold 0.0–1.0 (default: 1.0).
120
+ * @param options.initialParams - Initial params excluding `page` and `pageSize`.
121
+ *
122
+ * @example
123
+ * const { data, loading, hasMore, observerTarget, loadMore } = useInfiniteAsyncChunk(postsChunk);
124
+ *
125
+ * return (
126
+ * <>
127
+ * {data?.map(post => <Post key={post.id} {...post} />)}
128
+ * <div ref={observerTarget} />
129
+ * </>
130
+ * );
136
131
  */
137
- declare function useInfiniteAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T[], E> & {
138
- setParams: (params: Partial<P>) => void;
139
- }, options?: UseInfiniteAsyncChunkOptions<P>): any;
132
+ 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>;
133
+
134
+ interface UseMutationResult<TData, TError extends Error = Error, TVariables = void> {
135
+ /** Current mutation state */
136
+ loading: boolean;
137
+ data: TData | null;
138
+ error: TError | null;
139
+ isSuccess: boolean;
140
+ /**
141
+ * Execute the mutation. Always resolves — never throws.
142
+ * Safe to fire and forget, or await for local UI control.
143
+ *
144
+ * @example
145
+ * // Fire and forget
146
+ * mutate({ title: 'Hello' });
147
+ *
148
+ * // Await for local control — no try/catch needed
149
+ * const { data, error } = await mutate({ title: 'Hello' });
150
+ * if (!error) router.push('/posts');
151
+ */
152
+ mutate: TVariables extends void ? () => Promise<MutationResult<TData, TError>> : (variables: TVariables) => Promise<MutationResult<TData, TError>>;
153
+ /** Reset mutation state back to initial */
154
+ reset: () => void;
155
+ }
156
+ /**
157
+ * Subscribes to a mutation instance and returns its reactive state with `mutate` and `reset`.
158
+ *
159
+ * @param mutation - A `mutation()` instance from `stunk/query`.
160
+ *
161
+ * @example
162
+ * const createPost = mutation(
163
+ * async (data: NewPost) => fetchAPI('/posts', { method: 'POST', body: data }),
164
+ * { invalidates: [postsChunk] }
165
+ * );
166
+ *
167
+ * function CreatePostForm() {
168
+ * const { mutate, loading, error, isSuccess } = useMutation(createPost);
169
+ *
170
+ * const handleSubmit = async (data: NewPost) => {
171
+ * const { error } = await mutate(data);
172
+ * if (!error) router.push('/posts');
173
+ * };
174
+ * }
175
+ */
176
+ declare function useMutation<TData, TError extends Error = Error, TVariables = void>(mutation: Mutation<TData, TError, TVariables>): UseMutationResult<TData, TError, TVariables>;
140
177
 
141
- export { useAsyncChunk, useChunk, useChunkProperty, useChunkValue, useChunkValues, useComputed, useDerive, useInfiniteAsyncChunk };
178
+ export { type UseMutationResult, useAsyncChunk, useChunk, useChunkValue, useInfiniteAsyncChunk, useMutation };
@@ -1,98 +1,52 @@
1
- import { C as Chunk } from '../core-DMY69lzg.js';
1
+ import { C as Chunk } from '../core-DsoxfUCH.js';
2
+ import { P as PaginatedAsyncChunk, a as PaginationState, A as AsyncChunk, I as InfiniteAsyncChunk, M as Mutation, b as MutationResult } from '../mutation-BGXZyZXA.js';
2
3
 
3
4
  /**
4
- * A lightweight hook that subscribes to a chunk and returns its current value, along with setters, selector, reset and destroy.
5
- * Ensures reactivity and prevents unnecessary re-renders.
5
+ * Subscribes to a chunk and returns its current value along with `set`, `reset`, and `destroy`.
6
+ *
7
+ * Pass an optional `selector` to derive a slice of the value and avoid
8
+ * unnecessary re-renders when unrelated fields change.
9
+ *
10
+ * @param chunk - The chunk to subscribe to.
11
+ * @param selector - Optional function to select a derived value.
12
+ * @returns `[value, set, reset, destroy]`
13
+ *
14
+ * @example
15
+ * const [count, setCount, reset] = useChunk(countChunk);
16
+ *
17
+ * @example
18
+ * // Only re-renders when `name` changes
19
+ * const [name, setUser] = useChunk(userChunk, u => u.name);
6
20
  */
7
21
  declare function useChunk<T, S = T>(chunk: Chunk<T>, selector?: (value: T) => S): readonly [S, (valueOrUpdater: T | ((currentValue: T) => T)) => void, () => void, () => void];
8
22
 
9
23
  /**
10
- * A hook for creating a read-only derived value from a chunk.
11
- * Ensures reactivity and updates when the source chunk changes.
12
- */
13
- declare function useDerive<T, D>(chunk: Chunk<T>, fn: (value: T) => D): D;
14
-
15
- type ChunkValue<T> = T extends Chunk<infer U> ? U : never;
16
- type DependencyValues<T extends Chunk<any>[]> = {
17
- [K in keyof T]: T[K] extends Chunk<any> ? ChunkValue<T[K]> : never;
18
- };
19
-
20
- /**
21
- * A hook that computes a value based on multiple chunks.
22
- * Automatically re-computes when any dependency changes.
23
- */
24
- declare function useComputed<TDeps extends Chunk<any>[], TResult>(dependencies: [...TDeps], computeFn: (...args: DependencyValues<TDeps>) => TResult): TResult;
25
-
26
- /**
27
- * A lightweight hook that subscribes to a chunk and returns only its current value.
28
- * Useful for read-only components that don't need to update the chunk.
24
+ * Subscribes to a chunk and returns only its current value.
25
+ * Use this in read-only components that never need to call `set`.
26
+ *
27
+ * @param chunk - The chunk to subscribe to.
28
+ * @param selector - Optional function to select a derived value.
29
+ *
30
+ * @example
31
+ * const name = useChunkValue(userChunk, u => u.name);
29
32
  */
30
33
  declare function useChunkValue<T, S = T>(chunk: Chunk<T>, selector?: (value: T) => S): S;
31
34
 
32
- /**
33
- * A hook that subscribes to a specific property of a chunk.
34
- * This optimizes renders by only updating when the selected property changes.
35
- */
36
- declare function useChunkProperty<T, K extends keyof T>(chunk: Chunk<T>, property: K): T[K];
37
-
38
- /**
39
- * Hook to read values from multiple chunks at once.
40
- * Only re-renders when any of the chunk values change.
41
- */
42
- declare function useChunkValues<T extends Chunk<any>[]>(chunks: [...T]): {
43
- [K in keyof T]: T[K] extends Chunk<infer U> ? U : never;
44
- };
45
-
46
- interface AsyncState<T, E extends Error> {
47
- loading: boolean;
48
- error: E | null;
49
- data: T | null;
50
- lastFetched?: number;
51
- }
52
- interface PaginationState {
53
- page: number;
54
- pageSize: number;
55
- total?: number;
56
- hasMore?: boolean;
57
- }
58
- interface AsyncStateWithPagination<T, E extends Error> extends AsyncState<T, E> {
59
- pagination?: PaginationState;
60
- }
61
- interface AsyncChunk<T, E extends Error = Error> extends Chunk<AsyncStateWithPagination<T, E>> {
62
- /** Force reload data */
63
- reload: (params?: any) => Promise<void>;
64
- /** Smart refresh - respects stale time */
65
- refresh: (params?: any) => Promise<void>;
66
- /** Mutate data directly */
67
- mutate: (mutator: (currentData: T | null) => T) => void;
68
- /** Reset to initial state */
69
- reset: () => void;
70
- /** Clean up intervals */
71
- cleanup: () => void;
72
- }
73
- interface PaginatedAsyncChunk<T, E extends Error = Error> extends AsyncChunk<T, E> {
74
- /** Load next page */
75
- nextPage: () => Promise<void>;
76
- /** Load previous page */
77
- prevPage: () => Promise<void>;
78
- /** Go to specific page */
79
- goToPage: (page: number) => Promise<void>;
80
- /** Reset pagination to first page */
81
- resetPagination: () => Promise<void>;
82
- }
83
-
84
35
  interface UseAsyncChunkResult<T, E extends Error, P extends Record<string, any>> {
85
36
  data: T | null;
86
37
  loading: boolean;
87
38
  error: E | null;
88
39
  lastFetched?: number;
40
+ /** True when showing stale data while a new fetch is in progress (keepPreviousData: true) */
41
+ isPlaceholderData: boolean;
89
42
  reload: (params?: Partial<P>) => Promise<void>;
90
43
  refresh: (params?: Partial<P>) => Promise<void>;
91
44
  mutate: (mutator: (currentData: T | null) => T) => void;
92
45
  reset: () => void;
93
46
  }
94
47
  interface UseAsyncChunkResultWithParams<T, E extends Error, P extends Record<string, any>> extends UseAsyncChunkResult<T, E, P> {
95
- setParams: (params: Partial<P>) => void;
48
+ setParams: (params: Partial<Record<keyof P, P[keyof P] | null>>) => void;
49
+ clearParams: () => void;
96
50
  }
97
51
  interface UseAsyncChunkResultWithPagination<T, E extends Error, P extends Record<string, any>> extends UseAsyncChunkResult<T, E, P> {
98
52
  pagination?: PaginationState;
@@ -103,39 +57,122 @@ interface UseAsyncChunkResultWithPagination<T, E extends Error, P extends Record
103
57
  }
104
58
  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>> {
105
59
  }
106
- /**
107
- * A hook that handles asynchronous state with built-in reactivity.
108
- * Provides loading, error, and data states with full asyncChunk functionality.
109
- */
110
60
  interface UseAsyncChunkOptions<P extends Record<string, any> = {}> {
111
- /** Initial parameters to pass to the fetcher */
61
+ /** Initial parameters to pass to the fetcher on mount */
112
62
  initialParams?: Partial<P>;
113
- /** Force fetch on mount, even without params (default: false) */
63
+ /**
64
+ * Force a fetch on mount even when the chunk has no params.
65
+ * Ignored if initialParams is provided.
66
+ * (default: false)
67
+ */
114
68
  fetchOnMount?: boolean;
115
69
  }
116
70
  declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T, E> & {
117
71
  setParams: (params: Partial<P>) => void;
118
- }, options?: UseAsyncChunkOptions<P> | Partial<P>): UseAsyncChunkResultWithParamsAndPagination<T, E, P>;
119
- declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T, E>, options?: UseAsyncChunkOptions<P> | Partial<P>): UseAsyncChunkResultWithPagination<T, E, P>;
72
+ }, options?: UseAsyncChunkOptions<P>): UseAsyncChunkResultWithParamsAndPagination<T, E, P>;
73
+ declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T, E>, options?: UseAsyncChunkOptions<P>): UseAsyncChunkResultWithPagination<T, E, P>;
120
74
  declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: AsyncChunk<T, E> & {
121
75
  setParams: (params: Partial<P>) => void;
122
- }, options?: UseAsyncChunkOptions<P> | Partial<P>): UseAsyncChunkResultWithParams<T, E, P>;
123
- declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: AsyncChunk<T, E>, options?: UseAsyncChunkOptions<P> | Partial<P>): UseAsyncChunkResult<T, E, P>;
76
+ }, options?: UseAsyncChunkOptions<P>): UseAsyncChunkResultWithParams<T, E, P>;
77
+ declare function useAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: AsyncChunk<T, E>, options?: UseAsyncChunkOptions<P>): UseAsyncChunkResult<T, E, P>;
124
78
 
125
79
  interface UseInfiniteAsyncChunkOptions<P extends Record<string, any>> extends Omit<UseAsyncChunkOptions<P>, 'initialParams'> {
126
- /** Initial parameters (page and pageSize added automatically) */
80
+ /** Initial parameters page and pageSize are managed automatically */
127
81
  initialParams?: Omit<Partial<P>, 'page' | 'pageSize'>;
128
- /** Enable auto-loading on scroll (default: true) */
82
+ /** Automatically load next page when sentinel enters viewport (default: true) */
129
83
  autoLoad?: boolean;
130
- /** Intersection observer threshold (default: 1.0) */
84
+ /** IntersectionObserver threshold — 0.0 to 1.0 (default: 1.0) */
131
85
  threshold?: number;
132
86
  }
87
+ interface UseInfiniteAsyncChunkResult<T, E extends Error, P extends Record<string, any>> {
88
+ data: T[] | null;
89
+ loading: boolean;
90
+ error: E | null;
91
+ lastFetched?: number;
92
+ isPlaceholderData: boolean;
93
+ /** True when fetching a new page while existing data is already loaded */
94
+ isFetchingMore: boolean;
95
+ /** True if more pages are available */
96
+ hasMore: boolean;
97
+ reload: (params?: Partial<P>) => Promise<void>;
98
+ refresh: (params?: Partial<P>) => Promise<void>;
99
+ mutate: (mutator: (currentData: T[] | null) => T[]) => void;
100
+ reset: () => void;
101
+ nextPage: () => Promise<void>;
102
+ prevPage: () => Promise<void>;
103
+ goToPage: (page: number) => Promise<void>;
104
+ resetPagination: () => Promise<void>;
105
+ /** Manually trigger loading the next page */
106
+ loadMore: () => void;
107
+ /** Attach this ref to a sentinel element at the bottom of your list */
108
+ observerTarget: React.RefObject<HTMLElement>;
109
+ }
133
110
  /**
134
- * Hook for infinite scroll functionality.
135
- * Automatically loads more data when scrolling to the bottom.
111
+ * Subscribes to an infinite async chunk and wires up automatic infinite scroll.
112
+ *
113
+ * Attach `observerTarget` to a sentinel element at the bottom of your list —
114
+ * the next page loads automatically when it enters the viewport.
115
+ * Use `loadMore()` for manual triggering.
116
+ *
117
+ * @param chunk - An `InfiniteAsyncChunk` instance.
118
+ * @param options.autoLoad - Auto-load on scroll (default: true).
119
+ * @param options.threshold - IntersectionObserver threshold 0.0–1.0 (default: 1.0).
120
+ * @param options.initialParams - Initial params excluding `page` and `pageSize`.
121
+ *
122
+ * @example
123
+ * const { data, loading, hasMore, observerTarget, loadMore } = useInfiniteAsyncChunk(postsChunk);
124
+ *
125
+ * return (
126
+ * <>
127
+ * {data?.map(post => <Post key={post.id} {...post} />)}
128
+ * <div ref={observerTarget} />
129
+ * </>
130
+ * );
136
131
  */
137
- declare function useInfiniteAsyncChunk<T, E extends Error = Error, P extends Record<string, any> = {}>(asyncChunk: PaginatedAsyncChunk<T[], E> & {
138
- setParams: (params: Partial<P>) => void;
139
- }, options?: UseInfiniteAsyncChunkOptions<P>): any;
132
+ 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>;
133
+
134
+ interface UseMutationResult<TData, TError extends Error = Error, TVariables = void> {
135
+ /** Current mutation state */
136
+ loading: boolean;
137
+ data: TData | null;
138
+ error: TError | null;
139
+ isSuccess: boolean;
140
+ /**
141
+ * Execute the mutation. Always resolves — never throws.
142
+ * Safe to fire and forget, or await for local UI control.
143
+ *
144
+ * @example
145
+ * // Fire and forget
146
+ * mutate({ title: 'Hello' });
147
+ *
148
+ * // Await for local control — no try/catch needed
149
+ * const { data, error } = await mutate({ title: 'Hello' });
150
+ * if (!error) router.push('/posts');
151
+ */
152
+ mutate: TVariables extends void ? () => Promise<MutationResult<TData, TError>> : (variables: TVariables) => Promise<MutationResult<TData, TError>>;
153
+ /** Reset mutation state back to initial */
154
+ reset: () => void;
155
+ }
156
+ /**
157
+ * Subscribes to a mutation instance and returns its reactive state with `mutate` and `reset`.
158
+ *
159
+ * @param mutation - A `mutation()` instance from `stunk/query`.
160
+ *
161
+ * @example
162
+ * const createPost = mutation(
163
+ * async (data: NewPost) => fetchAPI('/posts', { method: 'POST', body: data }),
164
+ * { invalidates: [postsChunk] }
165
+ * );
166
+ *
167
+ * function CreatePostForm() {
168
+ * const { mutate, loading, error, isSuccess } = useMutation(createPost);
169
+ *
170
+ * const handleSubmit = async (data: NewPost) => {
171
+ * const { error } = await mutate(data);
172
+ * if (!error) router.push('/posts');
173
+ * };
174
+ * }
175
+ */
176
+ declare function useMutation<TData, TError extends Error = Error, TVariables = void>(mutation: Mutation<TData, TError, TVariables>): UseMutationResult<TData, TError, TVariables>;
140
177
 
141
- export { useAsyncChunk, useChunk, useChunkProperty, useChunkValue, useChunkValues, useComputed, useDerive, useInfiniteAsyncChunk };
178
+ export { type UseMutationResult, useAsyncChunk, useChunk, useChunkValue, useInfiniteAsyncChunk, useMutation };
@@ -1 +1 @@
1
- import {useState,useEffect,useCallback,useRef,useMemo}from'react';var U=new Set,V=new Map,N=0;function T(e,r=[]){if(e===null)throw new Error("Initial value cannot be null.");let t=e,n=new Set,s=N++,i=()=>{n.forEach(f=>f(t));};V.set(s,{notify:i});let o=()=>{n.size!==0&&(i());},u=()=>t,l=f=>{let h;typeof f=="function"?h=f(t):h=f,E(t,h);let p=j(h,r);p!==t&&(t=p,o());},a=f=>{if(typeof f!="function")throw new Error("Callback must be a function.");return n.add(f),f(t),()=>n.delete(f)};return {get:u,set:l,subscribe:a,derive:f=>{if(typeof f!="function")throw new Error("Derive function must be a function.");let h=f(t),p=T(h),b=a(()=>{let v=f(t);p.set(v);}),A=p.destroy;return p.destroy=()=>{b(),A();},p},reset:()=>{t=e,o();},destroy:()=>{n.clear(),t=e,U.delete(s),V.delete(s);}}}function j(e,r){if(e===null)throw new Error("Value cannot be null.");let t=e;for(let n=0;n<r.length;n++){let s=r[n],i=typeof s=="function"?s:s.fn,o=typeof s=="function"?`index ${n}`:s.name||`index ${n}`;try{let u=i(t);if(u===void 0)break;if(u===null)throw new Error(`Middleware "${o}" returned null value.`);t=u;}catch(u){let l=u instanceof Error?u.message:String(u);throw new Error(`Middleware "${o}" threw an error: ${l}`)}}return t}function x(e,r){if(e===r)return true;if(!e||!r||typeof e!=typeof r)return false;if(Array.isArray(e)&&Array.isArray(r)){if(e.length!==r.length)return false;for(let t=0;t<e.length;t++)if(e[t]!==r[t])return false;return true}if(typeof e=="object"&&typeof r=="object"){let t=Object.keys(e),n=Object.keys(r);if(t.length!==n.length)return false;for(let s of t)if(!Object.prototype.hasOwnProperty.call(r,s)||e[s]!==r[s])return false;return true}return false}function E(e,r,t=""){if(typeof e=="object"&&e!==null&&typeof r=="object"&&r!==null){if(Array.isArray(e)&&Array.isArray(r)){if(e.length>0&&typeof e[0]=="object")for(let n=0;n<r.length;n++)E(e[0],r[n],`${t}[${n}]`);}else if(!Array.isArray(e)&&!Array.isArray(r)){let n=Object.keys(e),s=Object.keys(r),i=s.filter(o=>!n.includes(o));i.length>0&&(console.error(`\u{1F6A8} Stunk: Unknown properties detected at '${t||"root"}': ${i.join(", ")}. This might cause bugs.`),console.error("Expected keys:",n),console.error("Received keys:",s));for(let o of n)E(e[o],r[o],t?`${t}.${o}`:o);}}}function R(e,r,t={}){let{useShallowEqual:n=false}=t,s=e.get(),i=r(s),o=T(i),u=()=>{let a=e.get(),c=r(a);s=a,(n?!x(c,i):c!==i)&&(i=c,o.set(c));},l=e.subscribe(u);return {get:()=>o.get(),set:()=>{throw new Error("Cannot set values directly on a selector. Modify the source chunk instead.")},subscribe:o.subscribe,derive:a=>R(o,a,t),reset:()=>{throw new Error("Cannot reset a selector chunk. Reset the source chunk instead.")},destroy:()=>{l(),o.destroy();}}}function k(e,r){let t=r?R(e,r):e,[n,s]=useState(()=>t.get());useEffect(()=>{let l=t.subscribe(a=>{s(()=>a);});return ()=>l()},[t]);let i=useCallback(l=>{e.set(l);},[e]),o=useCallback(()=>{e.reset();},[e]),u=useCallback(()=>{e.destroy();},[e]);return [n,i,o,u]}function J(e,r){let t=useRef(r);useEffect(()=>{t.current=r;},[r]);let n=useMemo(()=>e.derive(i=>t.current(i)),[e]),[s]=k(n);return s}function K(e,r){let t=e.map(a=>a.get()),n=r(...t),s=T(n),i=s.set,o=false,u=()=>{let a=false;for(let c=0;c<e.length;c++){let y=e[c].get();y!==t[c]&&(t[c]=y,a=true);}if(a){let c=r(...t);c!==n&&(typeof c!="object"||typeof n!="object"||!x(c,n))&&(n=c,i(c)),o=false;}},l=e.map(a=>a.subscribe(()=>{o=true,u();}));return {...s,get:()=>(o&&u(),n),recompute:u,isDirty:()=>o,set:()=>{throw new Error("Cannot set values directly on computed. Modify the source chunk instead.")},reset:()=>(e.forEach(a=>{typeof a.reset=="function"&&a.reset();}),o=true,u(),n),destroy:()=>{l.forEach(a=>a()),s.destroy?.();}}}function Z(e,r){let t=useMemo(()=>K(e,r),[...e]),[n,s]=useState(()=>t.get());return useEffect(()=>{let i=t.subscribe(o=>{s(o);});return ()=>{i();}},[t]),n}function D(e,r){let[t]=k(e,r);return t}function ee(e,r){let t=useMemo(()=>n=>n[r],[r]);return D(e,t)}function ne(e){let[r,t]=useState(()=>e.map(n=>n.get()));return useEffect(()=>{let n=e.map((s,i)=>s.subscribe(o=>{t(u=>{let l=[...u];return l[i]=o,l});}));return ()=>{n.forEach(s=>s());}},[e]),r}function C(e){return "nextPage"in e}function W(e){return "setParams"in e}function O(e,r){let{initialParams:t,fetchOnMount:n}=typeof r=="object"&&("initialParams"in r||"fetchOnMount"in r)?r:{initialParams:r,fetchOnMount:false},[s,i]=useState(()=>e.get()),o=useRef(null);(!o.current||o.current.chunk!==e)&&(o.current={chunk:e,initialParams:t,fetchOnMount:n}),useEffect(()=>e.subscribe(g=>{i(g);}),[e]),useEffect(()=>{let d=o.current,g=d?.initialParams,q=d?.fetchOnMount;g&&W(e)?e.setParams(g):q&&!g&&e.reload();},[e]),useEffect(()=>()=>{e.cleanup();},[e]);let u=useCallback(d=>e.reload(d),[e]),l=useCallback(d=>e.refresh(d),[e]),a=useCallback(d=>e.mutate(d),[e]),c=useCallback(()=>e.reset(),[e]),y=useCallback(d=>{"setParams"in e&&e.setParams(d);},[e]),P=useCallback(()=>C(e)?e.nextPage():Promise.resolve(),[e]),f=useCallback(()=>C(e)?e.prevPage():Promise.resolve(),[e]),h=useCallback(d=>C(e)?e.goToPage(d):Promise.resolve(),[e]),p=useCallback(()=>C(e)?e.resetPagination():Promise.resolve(),[e]),{data:b,loading:A,error:v,lastFetched:$,pagination:I}=s,w={data:b,loading:A,error:v,lastFetched:$,reload:u,refresh:l,mutate:a,reset:c};if(W(e)&&(w.setParams=y),C(e)){let d=w;d.pagination=I,d.nextPage=P,d.prevPage=f,d.goToPage=h,d.resetPagination=p;}return w}function ce(e,r={}){let{initialParams:t,autoLoad:n=true,threshold:s=1,...i}=r,o=useRef(null),u=O(e,{initialParams:{...t,page:1,pageSize:e.get().pagination?.pageSize||10},...i}),{loading:l,pagination:a,nextPage:c}=u;useEffect(()=>{if(!n)return;let P=new IntersectionObserver(h=>{h[0].isIntersecting&&!l&&a?.hasMore&&c();},{threshold:s}),f=o.current;return f&&P.observe(f),()=>{f&&P.unobserve(f);}},[l,a?.hasMore,c,n,s]);let y=useCallback(()=>{!l&&a?.hasMore&&c();},[l,a?.hasMore,c]);return {...u,loadMore:y,observerTarget:o,hasMore:a?.hasMore??false,isFetchingMore:l&&(u.data?.length??0)>0}}export{O as useAsyncChunk,k as useChunk,ee as useChunkProperty,D as useChunkValue,ne as useChunkValues,Z as useComputed,J as useDerive,ce as useInfiniteAsyncChunk};
1
+ import {a}from'../chunk-KWDC37TK.js';import {b}from'../chunk-3DOB632D.js';import {useState,useEffect,useCallback,useRef}from'react';function k(e,s,t={}){let{useShallowEqual:i=false}=t,u=e.get(),n=s(u),a$1=a(n),P=()=>{let m=e.get(),l=s(m);(i?!b(l,n):l!==n)&&(n=l,a$1.set(l));},o=e.subscribe(P),{set:d,reset:T,...E}=a$1;return {...E,derive:m=>k(a$1,m,t),destroy:()=>{o(),a$1.destroy();}}}function U(e,s){let t=s?k(e,s):e,[i,u]=useState(()=>t.get());useEffect(()=>{let o=t.subscribe(d=>{u(()=>d);});return ()=>o()},[t]);let n=useCallback(o=>{e.set(o);},[e]),a=useCallback(()=>{e.reset();},[e]),P=useCallback(()=>{e.destroy();},[e]);return [i,n,a,P]}function _(e,s){let[t]=U(e,s);return t}function x(e){return "nextPage"in e}function M(e){return "setParams"in e}function G(e){return "clearParams"in e}function O(e,s={}){let{initialParams:t,fetchOnMount:i=false}=s,[u,n]=useState(()=>e.get()),a=useRef({initialParams:t,fetchOnMount:i});a.current={initialParams:t,fetchOnMount:i},useEffect(()=>{n(e.get());let r=e.subscribe(q=>{n(q);}),{initialParams:D,fetchOnMount:F}=a.current;return D&&M(e)?e.setParams(D):F&&e.reload(),()=>{r(),e.cleanup();}},[e]);let P=useCallback(r=>e.reload(r),[e]),o=useCallback(r=>e.refresh(r),[e]),d=useCallback(r=>e.mutate(r),[e]),T=useCallback(()=>e.reset(),[e]),E=useCallback(r=>{M(e)&&e.setParams(r);},[e]),m=useCallback(()=>{G(e)&&e.clearParams();},[e]),l=useCallback(()=>x(e)?e.nextPage():Promise.resolve(),[e]),f=useCallback(()=>x(e)?e.prevPage():Promise.resolve(),[e]),h=useCallback(r=>x(e)?e.goToPage(r):Promise.resolve(),[e]),y=useCallback(()=>x(e)?e.resetPagination():Promise.resolve(),[e]),{data:b,loading:v,error:p,lastFetched:g,isPlaceholderData:C=false,pagination:w}=u,A={data:b,loading:v,error:p,lastFetched:g,isPlaceholderData:C,reload:P,refresh:o,mutate:d,reset:T};if(M(e)&&(A.setParams=E,A.clearParams=m),x(e)){let r=A;r.pagination=w,r.nextPage=l,r.prevPage=f,r.goToPage=h,r.resetPagination=y;}return A}function N(e,s={}){let{initialParams:t,autoLoad:i=true,threshold:u=1,fetchOnMount:n}=s,a=O(e,{...t&&{initialParams:t},fetchOnMount:n}),{loading:P,pagination:o,nextPage:d,data:T,error:E,isPlaceholderData:m}=a,l=useRef(P),f=useRef(o?.hasMore??false),h=useRef(d);l.current=P,f.current=o?.hasMore??false,h.current=d;let y=useRef(null);useEffect(()=>{if(!i||typeof window>"u"||!("IntersectionObserver"in window))return;let p=new IntersectionObserver(C=>{C[0].isIntersecting&&!l.current&&f.current&&h.current();},{threshold:u}),g=y.current;return g&&p.observe(g),()=>{g&&p.unobserve(g),p.disconnect();}},[i,u]);let b=useCallback(()=>{!l.current&&f.current&&h.current();},[]),v=P&&T!==null&&T.length>0&&(o?.page??1)>1;return {...a,data:T,error:E,isPlaceholderData:m??false,isFetchingMore:v,hasMore:o?.hasMore??false,loadMore:b,observerTarget:y}}function Y(e){let[s,t]=useState(()=>e.get());useEffect(()=>(t(e.get()),e.subscribe(a=>{t(a);})),[e]);let i=useCallback((...n)=>e.mutate(...n),[e]),u=useCallback(()=>e.reset(),[e]);return {loading:s.loading,data:s.data,error:s.error,isSuccess:s.isSuccess,mutate:i,reset:u}}export{O as useAsyncChunk,U as useChunk,_ as useChunkValue,N as useInfiniteAsyncChunk,Y as useMutation};