@spoosh/angular 0.9.1 → 0.10.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @spoosh/angular
2
2
 
3
- Angular signals integration for Spoosh - `injectRead`, `injectWrite`, and `injectInfiniteRead`.
3
+ Angular signals integration for Spoosh - `injectRead`, `injectWrite`, and `injectPages`.
4
4
 
5
5
  **[Documentation](https://spoosh.dev/docs/angular)** · **Requirements:** TypeScript >= 5.0, Angular >= 16.0
6
6
 
@@ -23,7 +23,7 @@ const spoosh = new Spoosh<ApiSchema, Error>("/api").use([
23
23
  cachePlugin({ staleTime: 5000 }),
24
24
  ]);
25
25
 
26
- export const { injectRead, injectWrite, injectInfiniteRead } = create(spoosh);
26
+ export const { injectRead, injectWrite, injectPages } = create(spoosh);
27
27
  ```
28
28
 
29
29
  ### injectRead
@@ -115,7 +115,7 @@ async updateUserName(userId: number, name: string) {
115
115
  }
116
116
  ```
117
117
 
118
- ### injectInfiniteRead
118
+ ### injectPages
119
119
 
120
120
  Bidirectional paginated data fetching with infinite scroll support.
121
121
 
@@ -140,29 +140,26 @@ Bidirectional paginated data fetching with infinite scroll support.
140
140
  `,
141
141
  })
142
142
  export class PostListComponent {
143
- posts = injectInfiniteRead(
144
- (api) => api("posts").GET({ query: { page: 1 } }),
145
- {
146
- // Required: Check if next page exists
147
- canFetchNext: ({ response }) => response?.meta.hasMore ?? false,
143
+ posts = injectPages((api) => api("posts").GET({ query: { page: 1 } }), {
144
+ // Required: Check if next page exists
145
+ canFetchNext: ({ lastPage }) => lastPage?.data?.meta.hasMore ?? false,
148
146
 
149
- // Required: Build request for next page
150
- nextPageRequest: ({ response, request }) => ({
151
- query: { ...request.query, page: (response?.meta.page ?? 0) + 1 },
152
- }),
147
+ // Required: Build request for next page
148
+ nextPageRequest: ({ lastPage }) => ({
149
+ query: { page: (lastPage?.data?.meta.page ?? 0) + 1 },
150
+ }),
153
151
 
154
- // Required: Merge all responses into items
155
- merger: (allResponses) => allResponses.flatMap((r) => r.items),
152
+ // Required: Merge all pages into items
153
+ merger: (pages) => pages.flatMap((p) => p.data?.items ?? []),
156
154
 
157
- // Optional: Check if previous page exists
158
- canFetchPrev: ({ response }) => (response?.meta.page ?? 1) > 1,
155
+ // Optional: Check if previous page exists
156
+ canFetchPrev: ({ firstPage }) => (firstPage?.data?.meta.page ?? 1) > 1,
159
157
 
160
- // Optional: Build request for previous page
161
- prevPageRequest: ({ response, request }) => ({
162
- query: { ...request.query, page: (response?.meta.page ?? 2) - 1 },
163
- }),
164
- }
165
- );
158
+ // Optional: Build request for previous page
159
+ prevPageRequest: ({ firstPage }) => ({
160
+ query: { page: (firstPage?.data?.meta.page ?? 2) - 1 },
161
+ }),
162
+ });
166
163
  }
167
164
  ```
168
165
 
@@ -203,42 +200,58 @@ export class PostListComponent {
203
200
  | `input` | `TriggerOptions \| undefined` | Last trigger input |
204
201
  | `abort` | `() => void` | Abort current request |
205
202
 
206
- ### injectInfiniteRead(readFn, options)
203
+ ### injectPages(readFn, options)
207
204
 
208
- | Option | Type | Required | Description |
209
- | ----------------- | --------------------------------------------- | -------- | ------------------------------- |
210
- | `canFetchNext` | `(ctx) => boolean` | Yes | Check if next page exists |
211
- | `nextPageRequest` | `(ctx) => Partial<TRequest>` | Yes | Build request for next page |
212
- | `merger` | `(allResponses) => TItem[]` | Yes | Merge all responses into items |
213
- | `canFetchPrev` | `(ctx) => boolean` | No | Check if previous page exists |
214
- | `prevPageRequest` | `(ctx) => Partial<TRequest>` | No | Build request for previous page |
215
- | `enabled` | `boolean \| Signal<boolean> \| () => boolean` | No | Whether to fetch automatically |
205
+ | Option | Type | Required | Description |
206
+ | ----------------- | --------------------------------------------- | -------- | ------------------------------------------------- |
207
+ | `merger` | `(pages) => TItem[]` | Yes | Merge all pages into items |
208
+ | `canFetchNext` | `(ctx) => boolean` | No | Check if next page exists. Default: `() => false` |
209
+ | `nextPageRequest` | `(ctx) => Partial<TRequest>` | No | Build request for next page |
210
+ | `canFetchPrev` | `(ctx) => boolean` | No | Check if previous page exists |
211
+ | `prevPageRequest` | `(ctx) => Partial<TRequest>` | No | Build request for previous page |
212
+ | `enabled` | `boolean \| Signal<boolean> \| () => boolean` | No | Whether to fetch automatically |
216
213
 
217
214
  **Context object passed to callbacks:**
218
215
 
219
216
  ```typescript
220
- type Context<TData, TRequest> = {
221
- response: TData | undefined; // Latest response
222
- allResponses: TData[]; // All fetched responses
223
- request: TRequest; // Current request options
217
+ // For canFetchNext and nextPageRequest
218
+ type NextContext<TData, TRequest> = {
219
+ lastPage: InfinitePage<TData> | undefined;
220
+ pages: InfinitePage<TData>[];
221
+ request: TRequest;
222
+ };
223
+
224
+ // For canFetchPrev and prevPageRequest
225
+ type PrevContext<TData, TRequest> = {
226
+ firstPage: InfinitePage<TData> | undefined;
227
+ pages: InfinitePage<TData>[];
228
+ request: TRequest;
229
+ };
230
+
231
+ // Each page in the pages array
232
+ type InfinitePage<TData> = {
233
+ status: "pending" | "loading" | "success" | "error" | "stale";
234
+ data?: TData;
235
+ error?: TError;
236
+ meta?: TMeta;
237
+ input?: { query?; params?; body? };
224
238
  };
225
239
  ```
226
240
 
227
241
  **Returns:**
228
242
 
229
- | Property | Type | Description |
230
- | -------------- | ------------------------------ | ------------------------------- |
231
- | `data` | `Signal<TItem[] \| undefined>` | Merged items from all responses |
232
- | `allResponses` | `Signal<TData[] \| undefined>` | Array of all raw responses |
233
- | `loading` | `Signal<boolean>` | True during initial load |
234
- | `fetching` | `Signal<boolean>` | True during any fetch |
235
- | `fetchingNext` | `Signal<boolean>` | True while fetching next page |
236
- | `fetchingPrev` | `Signal<boolean>` | True while fetching previous |
237
- | `canFetchNext` | `Signal<boolean>` | Whether next page exists |
238
- | `canFetchPrev` | `Signal<boolean>` | Whether previous page exists |
239
- | `meta` | `Signal<PluginResults>` | Plugin metadata |
240
- | `fetchNext` | `() => Promise<void>` | Fetch the next page |
241
- | `fetchPrev` | `() => Promise<void>` | Fetch the previous page |
242
- | `refetch` | `() => Promise<void>` | Refetch all pages |
243
- | `abort` | `() => void` | Abort current request |
244
- | `error` | `Signal<TError \| undefined>` | Error if request failed |
243
+ | Property | Type | Description |
244
+ | -------------- | ------------------------------- | ----------------------------------------------- |
245
+ | `data` | `Signal<TItem[] \| undefined>` | Merged items from all pages |
246
+ | `pages` | `Signal<InfinitePage<TData>[]>` | Array of all pages with status, data, and meta |
247
+ | `loading` | `Signal<boolean>` | True during initial load |
248
+ | `fetching` | `Signal<boolean>` | True during any fetch |
249
+ | `fetchingNext` | `Signal<boolean>` | True while fetching next page |
250
+ | `fetchingPrev` | `Signal<boolean>` | True while fetching previous |
251
+ | `canFetchNext` | `Signal<boolean>` | Whether next page exists |
252
+ | `canFetchPrev` | `Signal<boolean>` | Whether previous page exists |
253
+ | `fetchNext` | `() => Promise<void>` | Fetch the next page |
254
+ | `fetchPrev` | `() => Promise<void>` | Fetch the previous page |
255
+ | `trigger` | `(options?) => Promise<void>` | Trigger fetch with optional new request options |
256
+ | `abort` | `() => void` | Abort current request |
257
+ | `error` | `Signal<TError \| undefined>` | Error if request failed |
package/dist/index.d.mts CHANGED
@@ -1,7 +1,115 @@
1
1
  import * as _spoosh_core from '@spoosh/core';
2
- import { PluginArray, StateManager, EventEmitter, PluginExecutor, MethodOptionsMap, CoreRequestOptionsBase, ReadClient, TagOptions, SpooshPlugin, PluginTypeConfig, SpooshResponse, InfiniteRequestOptions, ResolveTypes, ResolverContext, WriteSelectorClient, SpooshBody, ResolveResultTypes, MergePluginInstanceApi } from '@spoosh/core';
2
+ import { QueueSelectorClient, SpooshResponse, QueueItem, QueueStats, SpooshBody, PluginArray, StateManager, EventEmitter, PluginExecutor, MethodOptionsMap, CoreRequestOptionsBase, SpooshPlugin, PluginTypeConfig, ResolveTypes, ResolverContext, ResolveResultTypes, ReadClient, TagOptions, InfiniteNextContext, InfinitePrevContext, InfinitePage, ExtractTriggerQuery as ExtractTriggerQuery$2, ExtractTriggerBody as ExtractTriggerBody$2, ExtractTriggerParams as ExtractTriggerParams$2, InfiniteRequestOptions, WriteSelectorClient, MergePluginInstanceApi } from '@spoosh/core';
3
3
  import { Signal } from '@angular/core';
4
4
 
5
+ type TriggerAwaitedReturn$2<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;
6
+ type ExtractInputFromResponse$3<T> = T extends {
7
+ input: infer I;
8
+ } ? I : never;
9
+ type ExtractTriggerQuery$1<I> = I extends {
10
+ query: infer Q;
11
+ } ? undefined extends Q ? {
12
+ query?: Exclude<Q, undefined>;
13
+ } : {
14
+ query: Q;
15
+ } : unknown;
16
+ type ExtractTriggerBody$1<I> = I extends {
17
+ body: infer B;
18
+ } ? undefined extends B ? {
19
+ body?: Exclude<B, undefined> | SpooshBody<Exclude<B, undefined>>;
20
+ } : {
21
+ body: B | SpooshBody<B>;
22
+ } : unknown;
23
+ type ExtractTriggerParams$1<I> = I extends {
24
+ params: infer P;
25
+ } ? {
26
+ params: P;
27
+ } : unknown;
28
+ type QueueTriggerBase = {
29
+ /** Custom ID for this queue item. If not provided, one will be auto-generated. */
30
+ id?: string;
31
+ };
32
+ type QueueTriggerInput<T> = ExtractInputFromResponse$3<TriggerAwaitedReturn$2<T>> extends infer I ? [I] extends [never] ? QueueTriggerBase : QueueTriggerBase & ExtractTriggerQuery$1<I> & ExtractTriggerBody$1<I> & ExtractTriggerParams$1<I> : QueueTriggerBase;
33
+ /**
34
+ * Options for injectQueue.
35
+ */
36
+ interface InjectQueueOptions {
37
+ /** Maximum concurrent operations. Defaults to 3. */
38
+ concurrency?: number;
39
+ }
40
+ /**
41
+ * Result returned by injectQueue.
42
+ *
43
+ * @template TData - The response data type
44
+ * @template TError - The error type
45
+ * @template TTriggerInput - The trigger input type
46
+ * @template TMeta - Plugin-contributed metadata on queue items
47
+ */
48
+ interface BaseQueueResult<TData, TError, TTriggerInput, TMeta = object> {
49
+ /** Add item to queue and execute. Returns promise for this item. */
50
+ trigger: (input?: TTriggerInput) => Promise<SpooshResponse<TData, TError>>;
51
+ /** All tasks in queue with their current status */
52
+ tasks: Signal<QueueItem<TData, TError, TMeta>[]>;
53
+ /** Queue statistics (pending/running/settled/success/failed/total/percentage) */
54
+ stats: Signal<QueueStats>;
55
+ /** Abort task by ID, or all tasks if no ID */
56
+ abort: (id?: string) => void;
57
+ /** Retry failed task by ID, or all failed if no ID */
58
+ retry: (id?: string) => Promise<void>;
59
+ /** Remove specific task by ID (aborts if active) */
60
+ remove: (id: string) => void;
61
+ /** Remove all settled tasks (success, error, aborted). Keeps pending/running. */
62
+ removeSettled: () => void;
63
+ /** Abort all and clear queue */
64
+ clear: () => void;
65
+ /** Update concurrency limit */
66
+ setConcurrency: (concurrency: number) => void;
67
+ }
68
+ /**
69
+ * API client type for queue selector.
70
+ * Supports all HTTP methods (GET, POST, PUT, PATCH, DELETE).
71
+ */
72
+ type QueueApiClient<TSchema, TDefaultError> = QueueSelectorClient<TSchema, TDefaultError>;
73
+
74
+ type SuccessResponse<T> = Extract<T, {
75
+ data: unknown;
76
+ error?: undefined;
77
+ }>;
78
+ type ErrorResponse<T> = Extract<T, {
79
+ error: unknown;
80
+ data?: undefined;
81
+ }>;
82
+ type ExtractMethodData<T> = T extends (...args: never[]) => infer R ? SuccessResponse<Awaited<R>> extends {
83
+ data: infer D;
84
+ } ? D : unknown : unknown;
85
+ type ExtractMethodError<T> = T extends (...args: never[]) => infer R ? ErrorResponse<Awaited<R>> extends {
86
+ error: infer E;
87
+ } ? E : unknown : unknown;
88
+ type ExtractMethodOptions<T> = T extends (options?: infer O) => Promise<unknown> ? O : never;
89
+ type AwaitedReturnType<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
90
+ type SuccessReturnType<T> = SuccessResponse<AwaitedReturnType<T>>;
91
+ type ExtractResponseQuery<T> = SuccessReturnType<T> extends {
92
+ input: {
93
+ query: infer Q;
94
+ };
95
+ } ? Q : never;
96
+ type ExtractResponseBody<T> = SuccessReturnType<T> extends {
97
+ input: {
98
+ body: infer B;
99
+ };
100
+ } ? B : never;
101
+ type ExtractResponseParamNames<T> = SuccessReturnType<T> extends {
102
+ input: {
103
+ params: Record<infer K, unknown>;
104
+ };
105
+ } ? K extends string ? K : never : never;
106
+ type ExtractMethodQuery<T> = ExtractMethodOptions<T> extends {
107
+ query: infer Q;
108
+ } ? Q : never;
109
+ type ExtractMethodBody<T> = ExtractMethodOptions<T> extends {
110
+ body: infer B;
111
+ } ? B : never;
112
+
5
113
  interface SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins extends PluginArray> {
6
114
  api: TApi;
7
115
  stateManager: StateManager;
@@ -18,48 +126,214 @@ type QueryRequestOptions = CoreRequestOptionsBase;
18
126
  type MutationRequestOptions = CoreRequestOptionsBase;
19
127
  type AngularOptionsMap = MethodOptionsMap<QueryRequestOptions, MutationRequestOptions>;
20
128
 
21
- type PageContext<TData, TRequest> = {
22
- response: TData | undefined;
23
- allResponses: TData[];
24
- request: TRequest;
129
+ declare function createInjectQueue<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TQueueFn extends (api: QueueApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>>(queueFn: TQueueFn, queueOptions?: ResolveTypes<((TPlugins[number] extends infer T ? T extends TPlugins[number] ? T extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
130
+ queueOptions: infer Q;
131
+ } ? Q : object : object : never : never) extends infer T_1 ? T_1 extends (TPlugins[number] extends infer T_2 ? T_2 extends TPlugins[number] ? T_2 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
132
+ queueOptions: infer Q;
133
+ } ? Q : object : object : never : never) ? T_1 extends unknown ? (x: T_1) => void : never : never : never) extends (x: infer I) => void ? I : never, ResolverContext<TSchema, TQueueFn extends (...args: any[]) => infer R ? Extract<Awaited<R>, {
134
+ data: unknown;
135
+ error?: undefined;
136
+ }> extends {
137
+ data: infer D;
138
+ } ? D : unknown : unknown, [TQueueFn extends (...args: any[]) => infer R_1 ? Extract<Awaited<R_1>, {
139
+ error: unknown;
140
+ data?: undefined;
141
+ }> extends {
142
+ error: infer E;
143
+ } ? E : unknown : unknown] extends [unknown] ? TDefaultError : TQueueFn extends (...args: any[]) => infer R_1 ? Extract<Awaited<R_1>, {
144
+ error: unknown;
145
+ data?: undefined;
146
+ }> extends {
147
+ error: infer E;
148
+ } ? E : unknown : unknown, ExtractMethodQuery<TQueueFn>, ExtractMethodBody<TQueueFn>, ExtractResponseParamNames<TQueueFn> extends never ? never : Record<ExtractResponseParamNames<TQueueFn>, string | number>>> & InjectQueueOptions) => BaseQueueResult<TQueueFn extends (...args: any[]) => infer R ? Extract<Awaited<R>, {
149
+ data: unknown;
150
+ error?: undefined;
151
+ }> extends {
152
+ data: infer D;
153
+ } ? D : unknown : unknown, [TQueueFn extends (...args: any[]) => infer R_1 ? Extract<Awaited<R_1>, {
154
+ error: unknown;
155
+ data?: undefined;
156
+ }> extends {
157
+ error: infer E;
158
+ } ? E : unknown : unknown] extends [unknown] ? TDefaultError : TQueueFn extends (...args: any[]) => infer R_1 ? Extract<Awaited<R_1>, {
159
+ error: unknown;
160
+ data?: undefined;
161
+ }> extends {
162
+ error: infer E;
163
+ } ? E : unknown : unknown, QueueTriggerInput<TQueueFn> & ResolveTypes<((TPlugins[number] extends infer T_3 ? T_3 extends TPlugins[number] ? T_3 extends SpooshPlugin<infer Types_1 extends PluginTypeConfig> ? Types_1 extends {
164
+ queueTriggerOptions: infer Q_1;
165
+ } ? Q_1 : object : object : never : never) extends infer T_4 ? T_4 extends (TPlugins[number] extends infer T_5 ? T_5 extends TPlugins[number] ? T_5 extends SpooshPlugin<infer Types_1 extends PluginTypeConfig> ? Types_1 extends {
166
+ queueTriggerOptions: infer Q_1;
167
+ } ? Q_1 : object : object : never : never) ? T_4 extends unknown ? (x: T_4) => void : never : never : never) extends (x: infer I) => void ? I : never, ResolverContext<TSchema, TQueueFn extends (...args: any[]) => infer R ? Extract<Awaited<R>, {
168
+ data: unknown;
169
+ error?: undefined;
170
+ }> extends {
171
+ data: infer D;
172
+ } ? D : unknown : unknown, [TQueueFn extends (...args: any[]) => infer R_1 ? Extract<Awaited<R_1>, {
173
+ error: unknown;
174
+ data?: undefined;
175
+ }> extends {
176
+ error: infer E;
177
+ } ? E : unknown : unknown] extends [unknown] ? TDefaultError : TQueueFn extends (...args: any[]) => infer R_1 ? Extract<Awaited<R_1>, {
178
+ error: unknown;
179
+ data?: undefined;
180
+ }> extends {
181
+ error: infer E;
182
+ } ? E : unknown : unknown, ExtractMethodQuery<TQueueFn>, ExtractMethodBody<TQueueFn>, ExtractResponseParamNames<TQueueFn> extends never ? never : Record<ExtractResponseParamNames<TQueueFn>, string | number>>>, ResolveResultTypes<((TPlugins[number] extends infer T_6 ? T_6 extends TPlugins[number] ? T_6 extends SpooshPlugin<infer Types_2 extends PluginTypeConfig> ? Types_2 extends {
183
+ queueResult: infer Q_2;
184
+ } ? Q_2 : object : object : never : never) extends infer T_7 ? T_7 extends (TPlugins[number] extends infer T_8 ? T_8 extends TPlugins[number] ? T_8 extends SpooshPlugin<infer Types_2 extends PluginTypeConfig> ? Types_2 extends {
185
+ queueResult: infer Q_2;
186
+ } ? Q_2 : object : object : never : never) ? T_7 extends unknown ? (x: T_7) => void : never : never : never) extends (x: infer I) => void ? I : never, ResolveTypes<((TPlugins[number] extends infer T_9 ? T_9 extends TPlugins[number] ? T_9 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
187
+ queueOptions: infer Q;
188
+ } ? Q : object : object : never : never) extends infer T_10 ? T_10 extends (TPlugins[number] extends infer T_11 ? T_11 extends TPlugins[number] ? T_11 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
189
+ queueOptions: infer Q;
190
+ } ? Q : object : object : never : never) ? T_10 extends unknown ? (x: T_10) => void : never : never : never) extends (x: infer I) => void ? I : never, ResolverContext<TSchema, TQueueFn extends (...args: any[]) => infer R ? Extract<Awaited<R>, {
191
+ data: unknown;
192
+ error?: undefined;
193
+ }> extends {
194
+ data: infer D;
195
+ } ? D : unknown : unknown, [TQueueFn extends (...args: any[]) => infer R_1 ? Extract<Awaited<R_1>, {
196
+ error: unknown;
197
+ data?: undefined;
198
+ }> extends {
199
+ error: infer E;
200
+ } ? E : unknown : unknown] extends [unknown] ? TDefaultError : TQueueFn extends (...args: any[]) => infer R_1 ? Extract<Awaited<R_1>, {
201
+ error: unknown;
202
+ data?: undefined;
203
+ }> extends {
204
+ error: infer E;
205
+ } ? E : unknown : unknown, ExtractMethodQuery<TQueueFn>, ExtractMethodBody<TQueueFn>, ExtractResponseParamNames<TQueueFn> extends never ? never : Record<ExtractResponseParamNames<TQueueFn>, string | number>>> & InjectQueueOptions>>;
206
+
207
+ type TriggerAwaitedReturn$1<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
208
+ type ExtractInputFromResponse$2<T> = T extends {
209
+ input: infer I;
210
+ } ? I : never;
211
+ type BaseTriggerOptions = {
212
+ /** Bypass cache and force refetch. Default: true */
213
+ force?: boolean;
25
214
  };
26
- interface BaseInfiniteReadOptions<TData, TItem, TRequest> extends TagOptions {
215
+ type PagesTriggerOptions<TReadFn> = ExtractInputFromResponse$2<TriggerAwaitedReturn$1<TReadFn>> extends infer I ? [I] extends [never] ? BaseTriggerOptions : ExtractTriggerQuery$2<I> & ExtractTriggerBody$2<I> & ExtractTriggerParams$2<I> & BaseTriggerOptions : BaseTriggerOptions;
216
+ /**
217
+ * Options for `injectPages`.
218
+ *
219
+ * @template TData - The response data type for each page
220
+ * @template TItem - The item type after merging all pages
221
+ * @template TError - The error type
222
+ * @template TRequest - The request options type
223
+ * @template TMeta - Plugin metadata type
224
+ */
225
+ interface BasePagesOptions<TData, TItem, TError = unknown, TRequest = object, TMeta = Record<string, unknown>> extends TagOptions {
27
226
  enabled?: EnabledOption;
28
- canFetchNext: (ctx: PageContext<TData, TRequest>) => boolean;
29
- canFetchPrev?: (ctx: PageContext<TData, TRequest>) => boolean;
30
- nextPageRequest: (ctx: PageContext<TData, TRequest>) => Partial<TRequest>;
31
- prevPageRequest?: (ctx: PageContext<TData, TRequest>) => Partial<TRequest>;
32
- merger: (responses: TData[]) => TItem[];
227
+ /**
228
+ * Callback to determine if there's a next page to fetch.
229
+ * Receives the last page to check for pagination indicators.
230
+ * Default: `() => false` (no next page fetching)
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * canFetchNext: ({ lastPage }) => lastPage?.data?.nextCursor != null
235
+ * ```
236
+ */
237
+ canFetchNext?: (ctx: InfiniteNextContext<TData, TError, TRequest, TMeta>) => boolean;
238
+ /**
239
+ * Callback to determine if there's a previous page to fetch.
240
+ * Receives the first page to check for pagination indicators.
241
+ * Default: `() => false` (no previous page fetching)
242
+ */
243
+ canFetchPrev?: (ctx: InfinitePrevContext<TData, TError, TRequest, TMeta>) => boolean;
244
+ /**
245
+ * Callback to build the request options for the next page.
246
+ * Return only the fields that change - they will be **merged** with the initial request.
247
+ * Default: `() => ({})` (no changes to request)
248
+ *
249
+ * @example
250
+ * ```ts
251
+ * // Initial: { query: { cursor: 0, limit: 10 } }
252
+ * // Only return cursor - limit is preserved automatically
253
+ * nextPageRequest: ({ lastPage }) => ({
254
+ * query: { cursor: lastPage?.data?.nextCursor }
255
+ * })
256
+ * ```
257
+ */
258
+ nextPageRequest?: (ctx: InfiniteNextContext<TData, TError, TRequest, TMeta>) => Partial<TRequest>;
259
+ /**
260
+ * Callback to build the request options for the previous page.
261
+ * Return only the fields that change - they will be **merged** with the initial request.
262
+ */
263
+ prevPageRequest?: (ctx: InfinitePrevContext<TData, TError, TRequest, TMeta>) => Partial<TRequest>;
264
+ /**
265
+ * Callback to merge all pages into a single array of items.
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * merger: (pages) => pages.flatMap(p => p.data?.items ?? [])
270
+ * ```
271
+ */
272
+ merger: (pages: InfinitePage<TData, TError, TMeta>[]) => TItem[];
33
273
  }
34
- interface BaseInfiniteReadResult<TData, TError, TItem, TPluginResult = Record<string, unknown>> {
274
+ /**
275
+ * Result returned by `injectPages`.
276
+ *
277
+ * @template TData - The response data type for each page
278
+ * @template TError - The error type
279
+ * @template TItem - The item type after merging all pages
280
+ * @template TPluginResult - Plugin-provided result fields
281
+ * @template TTriggerOptions - Options that can be passed to trigger()
282
+ */
283
+ interface BasePagesResult<TData, TError, TItem, TPluginResult = Record<string, unknown>, TTriggerOptions = object> {
284
+ /** Merged items from all fetched pages */
35
285
  data: Signal<TItem[] | undefined>;
36
- allResponses: Signal<TData[] | undefined>;
286
+ /** Array of all pages with status, data, error, and meta per page */
287
+ pages: Signal<InfinitePage<TData, TError, TPluginResult>[]>;
288
+ /** Error from the last failed request */
37
289
  error: Signal<TError | undefined>;
290
+ /** True during the initial load (no data yet) */
38
291
  loading: Signal<boolean>;
292
+ /** True during any fetch operation */
39
293
  fetching: Signal<boolean>;
294
+ /** True while fetching the next page */
40
295
  fetchingNext: Signal<boolean>;
296
+ /** True while fetching the previous page */
41
297
  fetchingPrev: Signal<boolean>;
298
+ /** Whether there's a next page available to fetch */
42
299
  canFetchNext: Signal<boolean>;
300
+ /** Whether there's a previous page available to fetch */
43
301
  canFetchPrev: Signal<boolean>;
44
- meta: Signal<TPluginResult>;
302
+ /** Fetch the next page */
45
303
  fetchNext: () => Promise<void>;
304
+ /** Fetch the previous page */
46
305
  fetchPrev: () => Promise<void>;
47
- trigger: () => Promise<void>;
306
+ /** Trigger refetch of all pages from the beginning, optionally with new request options */
307
+ trigger: (options?: TTriggerOptions) => Promise<void>;
308
+ /** Abort the current fetch operation */
48
309
  abort: () => void;
49
310
  }
50
- type InfiniteReadApiClient<TSchema, TDefaultError> = ReadClient<TSchema, TDefaultError>;
311
+ type PagesApiClient<TSchema, TDefaultError> = ReadClient<TSchema, TDefaultError>;
51
312
 
52
- type AnyInfiniteRequestOptions = InfiniteRequestOptions;
53
- declare function createInjectInfiniteRead<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TReadFn extends (api: InfiniteReadApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>, TRequest extends AnyInfiniteRequestOptions = AnyInfiniteRequestOptions, TItem = unknown>(readFn: TReadFn, readOptions: BaseInfiniteReadOptions<TReadFn extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
313
+ declare function createInjectPages<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TReadFn extends (api: PagesApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions, TItem = unknown>(readFn: TReadFn, readOptions: BasePagesOptions<TReadFn extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
54
314
  data: unknown;
55
315
  error?: undefined;
56
316
  }> extends {
57
317
  data: infer D;
58
- } ? D : unknown : unknown, TItem, TRequest> & ResolveTypes<((TPlugins[number] extends infer T_3 ? T_3 extends TPlugins[number] ? T_3 extends SpooshPlugin<infer Types_1 extends PluginTypeConfig> ? Types_1 extends {
59
- readOptions: infer R_3;
60
- } ? R_3 : object : object : never : never) extends infer T_4 ? T_4 extends (TPlugins[number] extends infer T_5 ? T_5 extends TPlugins[number] ? T_5 extends SpooshPlugin<infer Types_1 extends PluginTypeConfig> ? Types_1 extends {
61
- readOptions: infer R_3;
62
- } ? R_3 : object : object : never : never) ? T_4 extends unknown ? (x: T_4) => void : never : never : never) extends (x: infer I) => void ? I : never, ResolverContext<TSchema, TReadFn extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
318
+ } ? D : unknown : unknown, TItem, [TReadFn extends (...args: never[]) => infer R_1 ? Extract<Awaited<R_1>, {
319
+ error: unknown;
320
+ data?: undefined;
321
+ }> extends {
322
+ error: infer E;
323
+ } ? E : unknown : unknown] extends [unknown] ? TDefaultError : TReadFn extends (...args: never[]) => infer R_1 ? Extract<Awaited<R_1>, {
324
+ error: unknown;
325
+ data?: undefined;
326
+ }> extends {
327
+ error: infer E;
328
+ } ? E : unknown : unknown, TRequest, ((TPlugins[number] extends infer T_3 ? T_3 extends TPlugins[number] ? T_3 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
329
+ readResult: infer R_2;
330
+ } ? R_2 : object : object : never : never) extends infer T_4 ? T_4 extends (TPlugins[number] extends infer T_5 ? T_5 extends TPlugins[number] ? T_5 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
331
+ readResult: infer R_2;
332
+ } ? R_2 : object : object : never : never) ? T_4 extends unknown ? (x: T_4) => void : never : never : never) extends (x: infer I) => void ? I : never> & ResolveTypes<((TPlugins[number] extends infer T_6 ? T_6 extends TPlugins[number] ? T_6 extends SpooshPlugin<infer Types_1 extends PluginTypeConfig> ? Types_1 extends {
333
+ pagesOptions: infer I_1;
334
+ } ? I_1 : object : object : never : never) extends infer T_7 ? T_7 extends (TPlugins[number] extends infer T_8 ? T_8 extends TPlugins[number] ? T_8 extends SpooshPlugin<infer Types_1 extends PluginTypeConfig> ? Types_1 extends {
335
+ pagesOptions: infer I_1;
336
+ } ? I_1 : object : object : never : never) ? T_7 extends unknown ? (x: T_7) => void : never : never : never) extends (x: infer I) => void ? I : never, ResolverContext<TSchema, TReadFn extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
63
337
  data: unknown;
64
338
  error?: undefined;
65
339
  }> extends {
@@ -74,7 +348,7 @@ declare function createInjectInfiniteRead<TSchema, TDefaultError, TPlugins exten
74
348
  data?: undefined;
75
349
  }> extends {
76
350
  error: infer E;
77
- } ? E : unknown : unknown>>) => BaseInfiniteReadResult<TReadFn extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
351
+ } ? E : unknown : unknown>>) => BasePagesResult<TReadFn extends (...args: never[]) => infer R ? Extract<Awaited<R>, {
78
352
  data: unknown;
79
353
  error?: undefined;
80
354
  }> extends {
@@ -93,7 +367,7 @@ declare function createInjectInfiniteRead<TSchema, TDefaultError, TPlugins exten
93
367
  readResult: infer R_2;
94
368
  } ? R_2 : object : object : never : never) extends infer T_1 ? T_1 extends (TPlugins[number] extends infer T_2 ? T_2 extends TPlugins[number] ? T_2 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
95
369
  readResult: infer R_2;
96
- } ? R_2 : object : object : never : never) ? T_1 extends unknown ? (x: T_1) => void : never : never : never) extends (x: infer I) => void ? I : never>;
370
+ } ? R_2 : object : object : never : never) ? T_1 extends unknown ? (x: T_1) => void : never : never : never) extends (x: infer I) => void ? I : never, PagesTriggerOptions<TReadFn>>;
97
371
 
98
372
  type OptionalQueryField<TQuery> = [TQuery] extends [never] ? object : undefined extends TQuery ? {
99
373
  query?: Exclude<TQuery, undefined>;
@@ -126,67 +400,28 @@ type TriggerAwaitedReturn<T> = T extends (...args: any[]) => infer R ? Awaited<R
126
400
  type ExtractInputFromResponse$1<T> = T extends {
127
401
  input: infer I;
128
402
  } ? I : never;
129
- type ExtractTriggerQuery$1<I> = I extends {
403
+ type ExtractTriggerQuery<I> = I extends {
130
404
  query: infer Q;
131
405
  } ? undefined extends Q ? {
132
406
  query?: Exclude<Q, undefined>;
133
407
  } : {
134
408
  query: Q;
135
409
  } : unknown;
136
- type ExtractTriggerBody$1<I> = I extends {
410
+ type ExtractTriggerBody<I> = I extends {
137
411
  body: infer B;
138
412
  } ? undefined extends B ? {
139
413
  body?: Exclude<B, undefined> | SpooshBody<Exclude<B, undefined>>;
140
414
  } : {
141
415
  body: B | SpooshBody<B>;
142
416
  } : unknown;
143
- type ExtractTriggerParams$1<I> = I extends {
417
+ type ExtractTriggerParams<I> = I extends {
144
418
  params: infer P;
145
419
  } ? {
146
420
  params: P;
147
421
  } : unknown;
148
- type WriteTriggerInput<T> = ExtractInputFromResponse$1<TriggerAwaitedReturn<T>> extends infer I ? [I] extends [never] ? object : ExtractTriggerQuery$1<I> & ExtractTriggerBody$1<I> & ExtractTriggerParams$1<I> : object;
422
+ type WriteTriggerInput<T> = ExtractInputFromResponse$1<TriggerAwaitedReturn<T>> extends infer I ? [I] extends [never] ? object : ExtractTriggerQuery<I> & ExtractTriggerBody<I> & ExtractTriggerParams<I> : object;
149
423
  type WriteApiClient<TSchema, TDefaultError> = WriteSelectorClient<TSchema, TDefaultError>;
150
424
 
151
- type SuccessResponse<T> = Extract<T, {
152
- data: unknown;
153
- error?: undefined;
154
- }>;
155
- type ErrorResponse<T> = Extract<T, {
156
- error: unknown;
157
- data?: undefined;
158
- }>;
159
- type ExtractMethodData<T> = T extends (...args: never[]) => infer R ? SuccessResponse<Awaited<R>> extends {
160
- data: infer D;
161
- } ? D : unknown : unknown;
162
- type ExtractMethodError<T> = T extends (...args: never[]) => infer R ? ErrorResponse<Awaited<R>> extends {
163
- error: infer E;
164
- } ? E : unknown : unknown;
165
- type ExtractMethodOptions<T> = T extends (options?: infer O) => Promise<unknown> ? O : never;
166
- type AwaitedReturnType<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
167
- type SuccessReturnType<T> = SuccessResponse<AwaitedReturnType<T>>;
168
- type ExtractResponseQuery<T> = SuccessReturnType<T> extends {
169
- input: {
170
- query: infer Q;
171
- };
172
- } ? Q : never;
173
- type ExtractResponseBody<T> = SuccessReturnType<T> extends {
174
- input: {
175
- body: infer B;
176
- };
177
- } ? B : never;
178
- type ExtractResponseParamNames<T> = SuccessReturnType<T> extends {
179
- input: {
180
- params: Record<infer K, unknown>;
181
- };
182
- } ? K extends string ? K : never : never;
183
- type ExtractMethodQuery<T> = ExtractMethodOptions<T> extends {
184
- query: infer Q;
185
- } ? Q : never;
186
- type ExtractMethodBody<T> = ExtractMethodOptions<T> extends {
187
- body: infer B;
188
- } ? B : never;
189
-
190
425
  declare function createInjectWrite<TSchema, TDefaultError, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]>(options: Omit<SpooshInstanceShape<unknown, TSchema, TDefaultError, TPlugins>, "_types">): <TWriteFn extends (api: WriteApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>, TWriteOpts extends _spoosh_core.ResolveTypes<((TPlugins[number] extends infer T ? T extends TPlugins[number] ? T extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
191
426
  writeOptions: infer W;
192
427
  } ? W : object : object : never : never) extends infer T_1 ? T_1 extends (TPlugins[number] extends infer T_2 ? T_2 extends TPlugins[number] ? T_2 extends SpooshPlugin<infer Types extends PluginTypeConfig> ? Types extends {
@@ -293,24 +528,9 @@ type AwaitedReturnTypeTrigger<T> = T extends (...args: never[]) => infer R ? Awa
293
528
  type ExtractInputFromResponse<T> = T extends {
294
529
  input: infer I;
295
530
  } ? I : never;
296
- type ExtractTriggerQuery<I> = I extends {
297
- query: infer Q;
298
- } ? {
299
- query?: Q;
300
- } : unknown;
301
- type ExtractTriggerBody<I> = I extends {
302
- body: infer B;
303
- } ? {
304
- body?: B;
305
- } : unknown;
306
- type ExtractTriggerParams<I> = I extends {
307
- params: infer P;
308
- } ? {
309
- params?: P;
310
- } : unknown;
311
531
  type TriggerOptions<T> = ExtractInputFromResponse<AwaitedReturnTypeTrigger<T>> extends infer I ? [I] extends [never] ? {
312
532
  force?: boolean;
313
- } : ExtractTriggerQuery<I> & ExtractTriggerBody<I> & ExtractTriggerParams<I> & {
533
+ } : ExtractTriggerQuery$2<I> & ExtractTriggerBody$2<I> & ExtractTriggerParams$2<I> & {
314
534
  /** Force refetch even if data is cached */
315
535
  force?: boolean;
316
536
  } : {
@@ -399,9 +619,10 @@ declare function createInjectRead<TSchema, TDefaultError, TPlugins extends reado
399
619
  type SpooshAngularFunctions<TDefaultError, TSchema, TPlugins extends PluginArray> = {
400
620
  injectRead: ReturnType<typeof createInjectRead<TSchema, TDefaultError, TPlugins>>;
401
621
  injectWrite: ReturnType<typeof createInjectWrite<TSchema, TDefaultError, TPlugins>>;
402
- injectInfiniteRead: ReturnType<typeof createInjectInfiniteRead<TSchema, TDefaultError, TPlugins>>;
622
+ injectPages: ReturnType<typeof createInjectPages<TSchema, TDefaultError, TPlugins>>;
623
+ injectQueue: ReturnType<typeof createInjectQueue<TSchema, TDefaultError, TPlugins>>;
403
624
  } & MergePluginInstanceApi<TPlugins, TSchema>;
404
625
 
405
626
  declare function create<TSchema, TDefaultError, TPlugins extends PluginArray, TApi>(instance: SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins>): SpooshAngularFunctions<TDefaultError, TSchema, TPlugins>;
406
627
 
407
- export { type AngularOptionsMap, type BaseInfiniteReadOptions, type BaseInfiniteReadResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type EnabledOption, type ExtractMethodBody, type ExtractMethodData, type ExtractMethodError, type ExtractMethodOptions, type ExtractMethodQuery, type ExtractResponseBody, type ExtractResponseParamNames, type ExtractResponseQuery, type InfiniteReadApiClient, type PageContext, type ReadApiClient, type ResponseInputFields, type SpooshInstanceShape, type TriggerOptions, type WriteApiClient, type WriteResponseInputFields, create };
628
+ export { type AngularOptionsMap, type BasePagesOptions, type BasePagesResult, type BaseQueueResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type EnabledOption, type ExtractMethodBody, type ExtractMethodData, type ExtractMethodError, type ExtractMethodOptions, type ExtractMethodQuery, type ExtractResponseBody, type ExtractResponseParamNames, type ExtractResponseQuery, type InjectQueueOptions, type PagesApiClient, type PagesTriggerOptions, type QueueApiClient, type QueueTriggerInput, type ReadApiClient, type ResponseInputFields, type SpooshInstanceShape, type TriggerOptions, type WriteApiClient, type WriteResponseInputFields, create };