@spoosh/react 0.11.0 → 0.13.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 +143 -41
- package/dist/index.d.mts +306 -109
- package/dist/index.d.ts +306 -109
- package/dist/index.js +379 -56
- package/dist/index.mjs +390 -57
- package/package.json +11 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReadClient, TagMode, SpooshResponse, SpooshPlugin, PluginTypeConfig, MergePluginResults, WriteSelectorClient, SpooshBody, QueueSelectorClient, QueueItem, QueueStats, StateManager, EventEmitter, PluginExecutor, PluginArray, ResolveTypes, MergePluginOptions, ResolverContext, ResolveResultTypes, MergePluginInstanceApi, SpooshOptions } from '@spoosh/core';
|
|
1
|
+
import { ReadClient, TagMode, SpooshResponse, ExtractTriggerQuery as ExtractTriggerQuery$3, ExtractTriggerBody as ExtractTriggerBody$3, ExtractTriggerParams as ExtractTriggerParams$3, SpooshPlugin, PluginTypeConfig, MergePluginResults, WriteSelectorClient, SpooshBody, QueueSelectorClient, QueueItem, QueueStats, InfiniteRequestOptions, InfiniteNextContext, InfinitePage, InfinitePrevContext, SubscriptionClient, SubscriptionAdapter, OperationType, StateManager, EventEmitter, PluginExecutor, SpooshTransport, PluginArray, ResolveTypes, MergePluginOptions, ResolverContext, ResolveResultTypes, MergePluginInstanceApi, SpooshOptions } from '@spoosh/core';
|
|
2
2
|
|
|
3
3
|
type SuccessResponse<T> = Extract<T, {
|
|
4
4
|
data: unknown;
|
|
@@ -43,6 +43,31 @@ type ExtractResponseParamNames<T> = SuccessReturnType<T> extends {
|
|
|
43
43
|
params: Record<infer K, unknown>;
|
|
44
44
|
};
|
|
45
45
|
} ? K extends string ? K : never : never;
|
|
46
|
+
type SubscriptionReturnType<T> = T extends (...args: never[]) => infer R ? R : never;
|
|
47
|
+
type ExtractSubscriptionEvents<T> = SubscriptionReturnType<T> extends {
|
|
48
|
+
events: infer E;
|
|
49
|
+
requestedEvents: infer RequestedEvents;
|
|
50
|
+
} ? RequestedEvents extends readonly (keyof E)[] ? E extends Record<string, unknown> ? {
|
|
51
|
+
[K in RequestedEvents[number]]: E[K] extends {
|
|
52
|
+
data: infer EventData;
|
|
53
|
+
} ? EventData : unknown;
|
|
54
|
+
} : unknown : unknown : unknown;
|
|
55
|
+
type ExtractSubscriptionQuery<T> = SubscriptionReturnType<T> extends {
|
|
56
|
+
query: infer Q;
|
|
57
|
+
} ? Q : never;
|
|
58
|
+
type ExtractSubscriptionBody<T> = SubscriptionReturnType<T> extends {
|
|
59
|
+
body: infer B;
|
|
60
|
+
} ? B : never;
|
|
61
|
+
type ExtractAllSubscriptionEventKeys<T> = SubscriptionReturnType<T> extends {
|
|
62
|
+
events: infer E;
|
|
63
|
+
} ? E extends Record<string, unknown> ? keyof E : never : never;
|
|
64
|
+
type ExtractAllSubscriptionEvents<T> = SubscriptionReturnType<T> extends {
|
|
65
|
+
events: infer E;
|
|
66
|
+
} ? E extends Record<string, unknown> ? {
|
|
67
|
+
[K in keyof E]: E[K] extends {
|
|
68
|
+
data: infer EventData;
|
|
69
|
+
} ? EventData : unknown;
|
|
70
|
+
} : unknown : unknown;
|
|
46
71
|
|
|
47
72
|
type TagModeInArray$1 = "all" | "self";
|
|
48
73
|
/**
|
|
@@ -81,28 +106,13 @@ type ResponseInputFields<TQuery, TBody, TParamNames extends string> = [
|
|
|
81
106
|
] extends [never, never, never] ? object : {
|
|
82
107
|
input: ReadInputFields<TQuery, TBody, TParamNames>;
|
|
83
108
|
};
|
|
84
|
-
type TriggerAwaitedReturn$
|
|
85
|
-
type ExtractInputFromResponse$
|
|
109
|
+
type TriggerAwaitedReturn$3<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
|
|
110
|
+
type ExtractInputFromResponse$3<T> = T extends {
|
|
86
111
|
input: infer I;
|
|
87
112
|
} ? I : never;
|
|
88
|
-
type
|
|
89
|
-
query: infer Q;
|
|
90
|
-
} ? {
|
|
91
|
-
query?: Q;
|
|
92
|
-
} : unknown;
|
|
93
|
-
type ExtractTriggerBody$2<I> = I extends {
|
|
94
|
-
body: infer B;
|
|
95
|
-
} ? {
|
|
96
|
-
body?: B;
|
|
97
|
-
} : unknown;
|
|
98
|
-
type ExtractTriggerParams$2<I> = I extends {
|
|
99
|
-
params: infer P;
|
|
100
|
-
} ? {
|
|
101
|
-
params?: P;
|
|
102
|
-
} : unknown;
|
|
103
|
-
type TriggerOptions<T> = ExtractInputFromResponse$2<TriggerAwaitedReturn$2<T>> extends infer I ? [I] extends [never] ? {
|
|
113
|
+
type TriggerOptions<T> = ExtractInputFromResponse$3<TriggerAwaitedReturn$3<T>> extends infer I ? [I] extends [never] ? {
|
|
104
114
|
force?: boolean;
|
|
105
|
-
} : ExtractTriggerQuery$
|
|
115
|
+
} : ExtractTriggerQuery$3<I> & ExtractTriggerBody$3<I> & ExtractTriggerParams$3<I> & {
|
|
106
116
|
/** Force refetch even if data is cached */
|
|
107
117
|
force?: boolean;
|
|
108
118
|
} : {
|
|
@@ -160,30 +170,30 @@ type InputFields<TQuery, TBody, TParamNames extends string> = OptionalQueryField
|
|
|
160
170
|
type WriteResponseInputFields<TQuery, TBody, TParamNames extends string> = [TQuery, TBody, TParamNames] extends [never, never, never] ? object : {
|
|
161
171
|
input: InputFields<TQuery, TBody, TParamNames> | undefined;
|
|
162
172
|
};
|
|
163
|
-
type TriggerAwaitedReturn$
|
|
164
|
-
type ExtractInputFromResponse$
|
|
173
|
+
type TriggerAwaitedReturn$2<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;
|
|
174
|
+
type ExtractInputFromResponse$2<T> = T extends {
|
|
165
175
|
input: infer I;
|
|
166
176
|
} ? I : never;
|
|
167
|
-
type ExtractTriggerQuery$
|
|
177
|
+
type ExtractTriggerQuery$2<I> = I extends {
|
|
168
178
|
query: infer Q;
|
|
169
179
|
} ? undefined extends Q ? {
|
|
170
180
|
query?: Exclude<Q, undefined>;
|
|
171
181
|
} : {
|
|
172
182
|
query: Q;
|
|
173
183
|
} : unknown;
|
|
174
|
-
type ExtractTriggerBody$
|
|
184
|
+
type ExtractTriggerBody$2<I> = I extends {
|
|
175
185
|
body: infer B;
|
|
176
186
|
} ? undefined extends B ? {
|
|
177
187
|
body?: Exclude<B, undefined> | SpooshBody<Exclude<B, undefined>>;
|
|
178
188
|
} : {
|
|
179
189
|
body: B | SpooshBody<B>;
|
|
180
190
|
} : unknown;
|
|
181
|
-
type ExtractTriggerParams$
|
|
191
|
+
type ExtractTriggerParams$2<I> = I extends {
|
|
182
192
|
params: infer P;
|
|
183
193
|
} ? {
|
|
184
194
|
params: P;
|
|
185
195
|
} : unknown;
|
|
186
|
-
type WriteTriggerInput<T> = ExtractInputFromResponse$
|
|
196
|
+
type WriteTriggerInput<T> = ExtractInputFromResponse$2<TriggerAwaitedReturn$2<T>> extends infer I ? [I] extends [never] ? object : ExtractTriggerQuery$2<I> & ExtractTriggerBody$2<I> & ExtractTriggerParams$2<I> : object;
|
|
187
197
|
/**
|
|
188
198
|
* Result returned by `useWrite` hook.
|
|
189
199
|
*
|
|
@@ -209,25 +219,25 @@ type BaseWriteResult<TData, TError, TOptions, TMeta = Record<string, unknown>> =
|
|
|
209
219
|
type UseWriteResult<TData, TError, TOptions, TMeta, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]> = BaseWriteResult<TData, TError, TOptions, TMeta> & MergePluginResults<TPlugins>["write"];
|
|
210
220
|
type WriteApiClient<TSchema, TDefaultError> = WriteSelectorClient<TSchema, TDefaultError>;
|
|
211
221
|
|
|
212
|
-
type TriggerAwaitedReturn<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;
|
|
213
|
-
type ExtractInputFromResponse<T> = T extends {
|
|
222
|
+
type TriggerAwaitedReturn$1<T> = T extends (...args: any[]) => infer R ? Awaited<R> : never;
|
|
223
|
+
type ExtractInputFromResponse$1<T> = T extends {
|
|
214
224
|
input: infer I;
|
|
215
225
|
} ? I : never;
|
|
216
|
-
type ExtractTriggerQuery<I> = I extends {
|
|
226
|
+
type ExtractTriggerQuery$1<I> = I extends {
|
|
217
227
|
query: infer Q;
|
|
218
228
|
} ? undefined extends Q ? {
|
|
219
229
|
query?: Exclude<Q, undefined>;
|
|
220
230
|
} : {
|
|
221
231
|
query: Q;
|
|
222
232
|
} : unknown;
|
|
223
|
-
type ExtractTriggerBody<I> = I extends {
|
|
233
|
+
type ExtractTriggerBody$1<I> = I extends {
|
|
224
234
|
body: infer B;
|
|
225
235
|
} ? undefined extends B ? {
|
|
226
236
|
body?: Exclude<B, undefined> | SpooshBody<Exclude<B, undefined>>;
|
|
227
237
|
} : {
|
|
228
238
|
body: B | SpooshBody<B>;
|
|
229
239
|
} : unknown;
|
|
230
|
-
type ExtractTriggerParams<I> = I extends {
|
|
240
|
+
type ExtractTriggerParams$1<I> = I extends {
|
|
231
241
|
params: infer P;
|
|
232
242
|
} ? {
|
|
233
243
|
params: P;
|
|
@@ -236,7 +246,7 @@ type QueueTriggerBase = {
|
|
|
236
246
|
/** Custom ID for this queue item. If not provided, one will be auto-generated. */
|
|
237
247
|
id?: string;
|
|
238
248
|
};
|
|
239
|
-
type QueueTriggerInput<T> = ExtractInputFromResponse<TriggerAwaitedReturn<T>> extends infer I ? [I] extends [never] ? QueueTriggerBase : QueueTriggerBase & ExtractTriggerQuery<I> & ExtractTriggerBody<I> & ExtractTriggerParams<I> : QueueTriggerBase;
|
|
249
|
+
type QueueTriggerInput<T> = ExtractInputFromResponse$1<TriggerAwaitedReturn$1<T>> extends infer I ? [I] extends [never] ? QueueTriggerBase : QueueTriggerBase & ExtractTriggerQuery$1<I> & ExtractTriggerBody$1<I> & ExtractTriggerParams$1<I> : QueueTriggerBase;
|
|
240
250
|
/**
|
|
241
251
|
* Options for useQueue hook.
|
|
242
252
|
*/
|
|
@@ -277,41 +287,25 @@ type UseQueueResult<TData, TError, TTriggerInput, TMeta = object> = {
|
|
|
277
287
|
type QueueApiClient<TSchema, TDefaultError> = QueueSelectorClient<TSchema, TDefaultError>;
|
|
278
288
|
|
|
279
289
|
type TagModeInArray = "all" | "self";
|
|
280
|
-
type
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
|
|
287
|
-
*/
|
|
288
|
-
type InfiniteNextContext<TData, TRequest> = {
|
|
289
|
-
/** The latest fetched response data */
|
|
290
|
-
response: TData | undefined;
|
|
291
|
-
/** All responses fetched so far */
|
|
292
|
-
allResponses: TData[];
|
|
293
|
-
/** The current request options (query, params, body) */
|
|
294
|
-
request: TRequest;
|
|
295
|
-
};
|
|
296
|
-
/**
|
|
297
|
-
* Context passed to `canFetchPrev` and `prevPageRequest` callbacks.
|
|
298
|
-
*/
|
|
299
|
-
type InfinitePrevContext<TData, TRequest> = {
|
|
300
|
-
/** The latest fetched response data */
|
|
301
|
-
response: TData | undefined;
|
|
302
|
-
/** All responses fetched so far */
|
|
303
|
-
allResponses: TData[];
|
|
304
|
-
/** The current request options (query, params, body) */
|
|
305
|
-
request: TRequest;
|
|
290
|
+
type TriggerAwaitedReturn<T> = T extends (...args: never[]) => infer R ? Awaited<R> : never;
|
|
291
|
+
type ExtractInputFromResponse<T> = T extends {
|
|
292
|
+
input: infer I;
|
|
293
|
+
} ? I : never;
|
|
294
|
+
type BaseTriggerOptions = {
|
|
295
|
+
/** Bypass cache and force refetch. Default: true */
|
|
296
|
+
force?: boolean;
|
|
306
297
|
};
|
|
298
|
+
type PagesTriggerOptions<TReadFn> = ExtractInputFromResponse<TriggerAwaitedReturn<TReadFn>> extends infer I ? [I] extends [never] ? BaseTriggerOptions : ExtractTriggerQuery$3<I> & ExtractTriggerBody$3<I> & ExtractTriggerParams$3<I> & BaseTriggerOptions : BaseTriggerOptions;
|
|
307
299
|
/**
|
|
308
|
-
* Options for `
|
|
300
|
+
* Options for `usePages` hook.
|
|
309
301
|
*
|
|
310
302
|
* @template TData - The response data type for each page
|
|
311
303
|
* @template TItem - The item type after merging all responses
|
|
304
|
+
* @template TError - The error type
|
|
312
305
|
* @template TRequest - The request options type (query, params, body)
|
|
306
|
+
* @template TMeta - Plugin metadata type
|
|
313
307
|
*/
|
|
314
|
-
type
|
|
308
|
+
type BasePagesOptions<TData, TItem, TError = unknown, TRequest = InfiniteRequestOptions, TMeta = Record<string, unknown>> = {
|
|
315
309
|
/** Whether to fetch automatically on mount. Default: true */
|
|
316
310
|
enabled?: boolean;
|
|
317
311
|
/**
|
|
@@ -322,30 +316,66 @@ type BaseInfiniteReadOptions<TData, TItem, TRequest = AnyInfiniteRequestOptions>
|
|
|
322
316
|
* - 'none' should only be used as string (use `tags: 'none'` not in array)
|
|
323
317
|
*/
|
|
324
318
|
tags?: TagMode | (TagModeInArray | (string & {}))[];
|
|
325
|
-
/**
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
319
|
+
/**
|
|
320
|
+
* Callback to determine if there's a next page to fetch.
|
|
321
|
+
* Receives the last page to check for pagination indicators.
|
|
322
|
+
* Default: `() => false` (no next page fetching)
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* canFetchNext: ({ lastPage }) => lastPage?.data?.nextCursor != null
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
canFetchNext?: (ctx: InfiniteNextContext<TData, TError, TRequest, TMeta>) => boolean;
|
|
330
|
+
/**
|
|
331
|
+
* Callback to build the request options for the next page.
|
|
332
|
+
* Return only the fields that change - they will be **merged** with the initial request.
|
|
333
|
+
* Default: `() => ({})` (no changes to request)
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* ```ts
|
|
337
|
+
* // Initial: { query: { cursor: 0, limit: 10 } }
|
|
338
|
+
* // Only return cursor - limit is preserved automatically
|
|
339
|
+
* nextPageRequest: ({ lastPage }) => ({
|
|
340
|
+
* query: { cursor: lastPage?.data?.nextCursor }
|
|
341
|
+
* })
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
nextPageRequest?: (ctx: InfiniteNextContext<TData, TError, TRequest, TMeta>) => Partial<TRequest>;
|
|
345
|
+
/**
|
|
346
|
+
* Callback to merge all pages into a single array of items.
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```ts
|
|
350
|
+
* merger: (pages) => pages.flatMap(p => p.data?.items ?? [])
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
merger: (pages: InfinitePage<TData, TError, TMeta>[]) => TItem[];
|
|
354
|
+
/**
|
|
355
|
+
* Callback to determine if there's a previous page to fetch.
|
|
356
|
+
* Receives the first page to check for pagination indicators.
|
|
357
|
+
*/
|
|
358
|
+
canFetchPrev?: (ctx: InfinitePrevContext<TData, TError, TRequest, TMeta>) => boolean;
|
|
359
|
+
/**
|
|
360
|
+
* Callback to build the request options for the previous page.
|
|
361
|
+
* Return only the fields that change - they will be **merged** with the initial request.
|
|
362
|
+
*/
|
|
363
|
+
prevPageRequest?: (ctx: InfinitePrevContext<TData, TError, TRequest, TMeta>) => Partial<TRequest>;
|
|
335
364
|
};
|
|
336
365
|
/**
|
|
337
|
-
* Result returned by `
|
|
366
|
+
* Result returned by `usePages` hook.
|
|
338
367
|
*
|
|
339
368
|
* @template TData - The response data type for each page
|
|
340
369
|
* @template TError - The error type
|
|
341
370
|
* @template TItem - The item type after merging all responses
|
|
342
371
|
* @template TPluginResult - Plugin-provided result fields
|
|
372
|
+
* @template TTriggerOptions - Options that can be passed to trigger()
|
|
343
373
|
*/
|
|
344
|
-
type
|
|
345
|
-
/** Merged items from all fetched
|
|
374
|
+
type BasePagesResult<TData, TError, TItem, TPluginResult = Record<string, unknown>, TTriggerOptions = object> = {
|
|
375
|
+
/** Merged items from all fetched pages */
|
|
346
376
|
data: TItem[] | undefined;
|
|
347
|
-
/** Array of all
|
|
348
|
-
|
|
377
|
+
/** Array of all pages with status, data, error, and meta per page */
|
|
378
|
+
pages: InfinitePage<TData, TError, TPluginResult>[];
|
|
349
379
|
/** True during the initial load (no data yet) */
|
|
350
380
|
loading: boolean;
|
|
351
381
|
/** True during any fetch operation */
|
|
@@ -358,23 +388,121 @@ type BaseInfiniteReadResult<TData, TError, TItem, TPluginResult = Record<string,
|
|
|
358
388
|
canFetchNext: boolean;
|
|
359
389
|
/** Whether there's a previous page available to fetch */
|
|
360
390
|
canFetchPrev: boolean;
|
|
361
|
-
/** Plugin-provided metadata */
|
|
362
|
-
meta: TPluginResult;
|
|
363
391
|
/** Fetch the next page */
|
|
364
392
|
fetchNext: () => Promise<void>;
|
|
365
393
|
/** Fetch the previous page */
|
|
366
394
|
fetchPrev: () => Promise<void>;
|
|
367
|
-
/** Trigger refetch of all pages from the beginning */
|
|
368
|
-
trigger: () => Promise<void>;
|
|
395
|
+
/** Trigger refetch of all pages from the beginning, optionally with new request options */
|
|
396
|
+
trigger: (options?: TTriggerOptions) => Promise<void>;
|
|
369
397
|
/** Abort the current fetch operation */
|
|
370
398
|
abort: () => void;
|
|
371
399
|
/** Error from the last failed request */
|
|
372
400
|
error: TError | undefined;
|
|
373
401
|
};
|
|
374
|
-
type
|
|
375
|
-
type
|
|
402
|
+
type UsePagesResult<TData, TError, TItem, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[], TTriggerOptions = object> = BasePagesResult<TData, TError, TItem, MergePluginResults<TPlugins>["read"], TTriggerOptions>;
|
|
403
|
+
type PagesApiClient<TSchema, TDefaultError> = ReadClient<TSchema, TDefaultError>;
|
|
376
404
|
|
|
377
|
-
|
|
405
|
+
interface BaseSubscriptionOptions {
|
|
406
|
+
enabled?: boolean;
|
|
407
|
+
/** Subscription adapter instance created by the transport-specific hook */
|
|
408
|
+
adapter: SubscriptionAdapter;
|
|
409
|
+
/** Operation type for the subscription (e.g., "sse", "websocket") */
|
|
410
|
+
operationType: OperationType;
|
|
411
|
+
}
|
|
412
|
+
type ExtractTriggerQuery<TQuery> = [TQuery] extends [never] ? unknown : undefined extends TQuery ? {
|
|
413
|
+
query?: Exclude<TQuery, undefined>;
|
|
414
|
+
} : {
|
|
415
|
+
query: TQuery;
|
|
416
|
+
};
|
|
417
|
+
type ExtractTriggerBody<TBody> = [TBody] extends [never] ? unknown : undefined extends TBody ? {
|
|
418
|
+
body?: Exclude<TBody, undefined> | SpooshBody<Exclude<TBody, undefined>>;
|
|
419
|
+
} : {
|
|
420
|
+
body: TBody | SpooshBody<TBody>;
|
|
421
|
+
};
|
|
422
|
+
type ExtractTriggerParams<TParams> = [TParams] extends [never] ? unknown : {
|
|
423
|
+
params: TParams;
|
|
424
|
+
};
|
|
425
|
+
type SubscriptionTriggerInput<TQuery, TBody, TParams> = [
|
|
426
|
+
TQuery,
|
|
427
|
+
TBody,
|
|
428
|
+
TParams
|
|
429
|
+
] extends [never, never, never] ? object : ExtractTriggerQuery<TQuery> & ExtractTriggerBody<TBody> & ExtractTriggerParams<TParams>;
|
|
430
|
+
interface BaseSubscriptionResult<TData, TError, TPluginResult, TTriggerOptions> {
|
|
431
|
+
data: TData | undefined;
|
|
432
|
+
error: TError | undefined;
|
|
433
|
+
isConnected: boolean;
|
|
434
|
+
loading: boolean;
|
|
435
|
+
meta: TPluginResult;
|
|
436
|
+
/** @internal Query key for devtool integration */
|
|
437
|
+
_queryKey: string;
|
|
438
|
+
/** @internal Subscription version for tracking resubscriptions */
|
|
439
|
+
_subscriptionVersion: number;
|
|
440
|
+
trigger: (options?: TTriggerOptions) => Promise<void>;
|
|
441
|
+
disconnect: () => void;
|
|
442
|
+
}
|
|
443
|
+
type SubscriptionApiClient<TSchema, TDefaultError = unknown> = SubscriptionClient<TSchema, TDefaultError>;
|
|
444
|
+
|
|
445
|
+
type ParseStrategy = "auto" | "json" | "text" | "json-done";
|
|
446
|
+
type AccumulateStrategy = "replace" | "merge";
|
|
447
|
+
type AccumulatorFn<T = unknown> = (previous: T | undefined, current: T) => T;
|
|
448
|
+
type AccumulateFieldConfig<T> = T extends Record<string, unknown> ? {
|
|
449
|
+
[K in keyof T]?: AccumulateStrategy;
|
|
450
|
+
} : Record<string, AccumulateStrategy>;
|
|
451
|
+
interface UseSSEOptionsBase {
|
|
452
|
+
/** Whether the subscription is enabled. Defaults to true. */
|
|
453
|
+
enabled?: boolean;
|
|
454
|
+
/** Max retry attempts on connection failure */
|
|
455
|
+
maxRetries?: number;
|
|
456
|
+
/** Delay between retries in ms */
|
|
457
|
+
retryDelay?: number;
|
|
458
|
+
}
|
|
459
|
+
type ParseFn = (data: string) => unknown;
|
|
460
|
+
type TypedParseConfig<TEventKeys extends string> = ParseStrategy | ParseFn | Partial<Record<TEventKeys, ParseStrategy | ParseFn>>;
|
|
461
|
+
type TypedAccumulateConfig<TEvents extends Record<string, unknown>> = AccumulateStrategy | {
|
|
462
|
+
[K in keyof TEvents]?: AccumulateStrategy | AccumulatorFn<TEvents[K]> | AccumulateFieldConfig<TEvents[K]>;
|
|
463
|
+
};
|
|
464
|
+
interface TypedUseSSEOptions<TEventKeys extends string, TEvents extends Record<string, unknown>, TSelectedEvents extends readonly TEventKeys[] = readonly TEventKeys[]> extends UseSSEOptionsBase {
|
|
465
|
+
/** Event types to listen for. If not provided, listens to all events. */
|
|
466
|
+
events?: TSelectedEvents;
|
|
467
|
+
/** Parse strategy for SSE event data. Defaults to 'auto'. */
|
|
468
|
+
parse?: TypedParseConfig<TEventKeys>;
|
|
469
|
+
/** Accumulate strategy for combining events. Defaults to 'replace'. */
|
|
470
|
+
accumulate?: TypedAccumulateConfig<TEvents>;
|
|
471
|
+
}
|
|
472
|
+
interface UseSSEOptions extends UseSSEOptionsBase {
|
|
473
|
+
/** Event types to listen for. If not provided, listens to all events. */
|
|
474
|
+
events?: string[];
|
|
475
|
+
/** Parse strategy for SSE event data. Defaults to 'auto'. */
|
|
476
|
+
parse?: ParseStrategy | Record<string, ParseStrategy>;
|
|
477
|
+
/** Accumulate strategy for combining events. Defaults to 'replace'. */
|
|
478
|
+
accumulate?: AccumulateStrategy | Record<string, AccumulateStrategy | AccumulatorFn>;
|
|
479
|
+
}
|
|
480
|
+
interface UseSSEResult<TEvents, TError> {
|
|
481
|
+
/** Accumulated data keyed by event type */
|
|
482
|
+
data: Partial<TEvents> | undefined;
|
|
483
|
+
/** Connection or parse error */
|
|
484
|
+
error: TError | undefined;
|
|
485
|
+
/** Whether currently connected to the SSE endpoint */
|
|
486
|
+
isConnected: boolean;
|
|
487
|
+
/** Whether connection is in progress */
|
|
488
|
+
loading: boolean;
|
|
489
|
+
/** Plugin metadata */
|
|
490
|
+
meta: Record<string, never>;
|
|
491
|
+
/**
|
|
492
|
+
* Manually trigger connection with optional body/query overrides
|
|
493
|
+
* @param options - Optional body and query parameters
|
|
494
|
+
*/
|
|
495
|
+
trigger: (options?: {
|
|
496
|
+
body?: unknown;
|
|
497
|
+
query?: unknown;
|
|
498
|
+
}) => Promise<void>;
|
|
499
|
+
/** Disconnect from the SSE endpoint */
|
|
500
|
+
disconnect: () => void;
|
|
501
|
+
/** Reset accumulated data */
|
|
502
|
+
reset: () => void;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
type InferError<T, TDefaultError> = unknown extends T ? TDefaultError : T;
|
|
378
506
|
type WriteResolverContext<TSchema, TMethod, TDefaultError> = ResolverContext<TSchema, ExtractMethodData<TMethod>, InferError<ExtractMethodError<TMethod>, TDefaultError>, ExtractMethodQuery<TMethod>, ExtractMethodBody<TMethod>, ExtractResponseParamNames<TMethod> extends never ? never : Record<ExtractResponseParamNames<TMethod>, string | number>>;
|
|
379
507
|
type ResolvedWriteOptions<TSchema, TPlugins extends PluginArray, TMethod, TDefaultError> = ResolveTypes<MergePluginOptions<TPlugins>["write"], WriteResolverContext<TSchema, TMethod, TDefaultError>>;
|
|
380
508
|
type ResolvedWriteTriggerOptions<TSchema, TPlugins extends PluginArray, TMethod, TDefaultError> = ResolveTypes<MergePluginOptions<TPlugins>["writeTrigger"], WriteResolverContext<TSchema, TMethod, TDefaultError>>;
|
|
@@ -391,23 +519,30 @@ type UseWriteFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
391
519
|
type UseQueueFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
392
520
|
<TQueueFn extends (api: QueueApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>>(queueFn: TQueueFn, queueOptions?: ResolvedQueueOptions<TSchema, TPlugins, TQueueFn, TDefaultError> & UseQueueOptions): UseQueueResult<ExtractMethodData<TQueueFn>, InferError<ExtractMethodError<TQueueFn>, TDefaultError>, QueueTriggerInput<TQueueFn> & ResolvedQueueTriggerOptions<TSchema, TPlugins, TQueueFn, TDefaultError>, ResolveResultTypes<MergePluginResults<TPlugins>["queue"], ResolvedQueueOptions<TSchema, TPlugins, TQueueFn, TDefaultError> & UseQueueOptions>>;
|
|
393
521
|
};
|
|
394
|
-
type
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
522
|
+
type UsePagesFn<TDefaultError, TSchema, TPlugins extends PluginArray> = <TReadFn extends (api: PagesApiClient<TSchema, TDefaultError>) => Promise<SpooshResponse<unknown, unknown>>, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions, TItem = unknown>(readFn: TReadFn, readOptions: BasePagesOptions<ExtractMethodData<TReadFn>, TItem, InferError<ExtractMethodError<TReadFn>, TDefaultError>, TRequest, MergePluginResults<TPlugins>["read"]> & ResolveTypes<MergePluginOptions<TPlugins>["pages"], ResolverContext<TSchema, ExtractMethodData<TReadFn>, InferError<ExtractMethodError<TReadFn>, TDefaultError>>>) => BasePagesResult<ExtractMethodData<TReadFn>, InferError<ExtractMethodError<TReadFn>, TDefaultError>, TItem, MergePluginResults<TPlugins>["read"], PagesTriggerOptions<TReadFn>>;
|
|
523
|
+
type UseSubscriptionFn<TDefaultError, TSchema, TPlugins extends PluginArray> = <TSubFn extends (api: SubscriptionApiClient<TSchema, TDefaultError>) => unknown>(subFn: TSubFn, subOptions?: BaseSubscriptionOptions) => BaseSubscriptionResult<ExtractSubscriptionEvents<TSubFn>, InferError<ExtractMethodError<TSubFn>, TDefaultError>, MergePluginResults<TPlugins>["subscribe"], SubscriptionTriggerInput<ExtractSubscriptionQuery<TSubFn>, ExtractSubscriptionBody<TSubFn>, never>>;
|
|
524
|
+
type InferSSEEvents<T> = ExtractAllSubscriptionEvents<T> extends Record<string, unknown> ? ExtractAllSubscriptionEvents<T> : Record<string, unknown>;
|
|
525
|
+
type FilteredEvents<TAllEvents extends Record<string, unknown>, TSelectedEvents extends readonly string[]> = TSelectedEvents[number] extends keyof TAllEvents ? Pick<TAllEvents, TSelectedEvents[number]> : TAllEvents;
|
|
526
|
+
type UseSSEFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
527
|
+
<TSubFn extends (api: SubscriptionApiClient<TSchema, TDefaultError>) => {
|
|
528
|
+
_subscription: true;
|
|
529
|
+
events: Record<string, {
|
|
530
|
+
data: unknown;
|
|
531
|
+
}>;
|
|
532
|
+
}, const TSelectedEvents extends readonly Extract<ExtractAllSubscriptionEventKeys<TSubFn>, string>[]>(subFn: TSubFn, sseOptions: TypedUseSSEOptions<Extract<ExtractAllSubscriptionEventKeys<TSubFn>, string>, InferSSEEvents<TSubFn>, TSelectedEvents> & {
|
|
533
|
+
events: TSelectedEvents;
|
|
534
|
+
}): UseSSEResult<FilteredEvents<InferSSEEvents<TSubFn>, TSelectedEvents>, InferError<ExtractMethodError<TSubFn>, TDefaultError>>;
|
|
535
|
+
<TSubFn extends (api: SubscriptionApiClient<TSchema, TDefaultError>) => {
|
|
536
|
+
_subscription: true;
|
|
537
|
+
events: Record<string, {
|
|
538
|
+
data: unknown;
|
|
539
|
+
}>;
|
|
540
|
+
}>(subFn: TSubFn, sseOptions?: Omit<TypedUseSSEOptions<Extract<ExtractAllSubscriptionEventKeys<TSubFn>, string>, InferSSEEvents<TSubFn>>, "events">): UseSSEResult<InferSSEEvents<TSubFn>, InferError<ExtractMethodError<TSubFn>, TDefaultError>>;
|
|
541
|
+
};
|
|
403
542
|
/**
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
* @template TDefaultError - The default error type
|
|
407
|
-
* @template TSchema - The API schema type
|
|
408
|
-
* @template TPlugins - The plugins array type
|
|
543
|
+
* Base hooks that are always available.
|
|
409
544
|
*/
|
|
410
|
-
type
|
|
545
|
+
type BaseSpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
411
546
|
/**
|
|
412
547
|
* React hook for fetching data from an API endpoint with automatic caching and revalidation.
|
|
413
548
|
*
|
|
@@ -448,21 +583,21 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
448
583
|
*
|
|
449
584
|
* @param readFn - Function that selects the API endpoint to call
|
|
450
585
|
* @param readOptions - Configuration including `canFetchNext`, `nextPageRequest`, `merger`, and optional `canFetchPrev`/`prevPageRequest`
|
|
451
|
-
* @returns Object containing `data`, `
|
|
586
|
+
* @returns Object containing `data`, `pages`, `fetchNext`, `fetchPrev`, `canFetchNext`, `canFetchPrev`, `loading`, `fetching`, and pagination states
|
|
452
587
|
*
|
|
453
588
|
* @example
|
|
454
589
|
* ```tsx
|
|
455
|
-
* const { data, fetchNext, canFetchNext, loading } =
|
|
590
|
+
* const { data, fetchNext, canFetchNext, loading } = usePages(
|
|
456
591
|
* (api) => api("posts").GET(),
|
|
457
592
|
* {
|
|
458
|
-
* canFetchNext: ({
|
|
459
|
-
* nextPageRequest: ({
|
|
460
|
-
* merger: (
|
|
593
|
+
* canFetchNext: ({ lastPage }) => !!lastPage?.data?.nextCursor,
|
|
594
|
+
* nextPageRequest: ({ lastPage }) => ({ query: { cursor: lastPage?.data?.nextCursor } }),
|
|
595
|
+
* merger: (pages) => pages.flatMap(p => p.data?.items ?? [])
|
|
461
596
|
* }
|
|
462
597
|
* );
|
|
463
598
|
* ```
|
|
464
599
|
*/
|
|
465
|
-
|
|
600
|
+
usePages: UsePagesFn<TDefaultError, TSchema, TPlugins>;
|
|
466
601
|
/**
|
|
467
602
|
* React hook for queued operations with concurrency control.
|
|
468
603
|
*
|
|
@@ -483,11 +618,62 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
|
483
618
|
* ```
|
|
484
619
|
*/
|
|
485
620
|
useQueue: UseQueueFn<TDefaultError, TSchema, TPlugins>;
|
|
621
|
+
/**
|
|
622
|
+
* React hook for subscribing to real-time data streams (SSE, WebSocket, etc.).
|
|
623
|
+
*
|
|
624
|
+
* @param subFn - Function that selects the subscription endpoint
|
|
625
|
+
* @param subOptions - Optional configuration including `enabled`, `tags`
|
|
626
|
+
* @returns Object containing `data`, `error`, `loading`, `isSubscribed`, `emit`, `unsubscribe`
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* ```tsx
|
|
630
|
+
* const { data } = useSubscription((api) =>
|
|
631
|
+
* api("notifications").GET({ events: ["alert", "message"] })
|
|
632
|
+
* );
|
|
633
|
+
* ```
|
|
634
|
+
*/
|
|
635
|
+
useSubscription: UseSubscriptionFn<TDefaultError, TSchema, TPlugins>;
|
|
486
636
|
} & MergePluginInstanceApi<TPlugins, TSchema>;
|
|
637
|
+
/**
|
|
638
|
+
* SSE hooks available when SSE transport is registered.
|
|
639
|
+
*/
|
|
640
|
+
type SSEHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
|
|
641
|
+
/**
|
|
642
|
+
* React hook for SSE streams with per-hook parsing and accumulation.
|
|
643
|
+
*
|
|
644
|
+
* @param subFn - Function that selects the SSE endpoint
|
|
645
|
+
* @param sseOptions - Configuration including `events`, `parse`, `accumulate`, `maxRetries`, `retryDelay`
|
|
646
|
+
* @returns Object containing `data`, `error`, `loading`, `isConnected`, `trigger`, `disconnect`, `reset`
|
|
647
|
+
*
|
|
648
|
+
* @example
|
|
649
|
+
* ```tsx
|
|
650
|
+
* const { data, reset } = useSSE(
|
|
651
|
+
* (api) => api("chat").POST(),
|
|
652
|
+
* {
|
|
653
|
+
* events: ["chunk", "done"],
|
|
654
|
+
* parse: "json-done",
|
|
655
|
+
* accumulate: { chunk: "merge" },
|
|
656
|
+
* maxRetries: 5,
|
|
657
|
+
* }
|
|
658
|
+
* );
|
|
659
|
+
* ```
|
|
660
|
+
*/
|
|
661
|
+
useSSE: UseSSEFn<TDefaultError, TSchema, TPlugins>;
|
|
662
|
+
};
|
|
663
|
+
/**
|
|
664
|
+
* Spoosh React hooks interface containing useRead, useWrite, and usePages.
|
|
665
|
+
* useSSE is only available when SSE transport is registered via withTransports([sse()]).
|
|
666
|
+
*
|
|
667
|
+
* @template TDefaultError - The default error type
|
|
668
|
+
* @template TSchema - The API schema type
|
|
669
|
+
* @template TPlugins - The plugins array type
|
|
670
|
+
* @template TTransports - The registered transport names
|
|
671
|
+
*/
|
|
672
|
+
type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray, TTransports extends string = never> = BaseSpooshReactHooks<TDefaultError, TSchema, TPlugins> & ([TTransports] extends [never] ? object : "sse" extends TTransports ? SSEHooks<TDefaultError, TSchema, TPlugins> : object);
|
|
487
673
|
/**
|
|
488
674
|
* Shape of a Spoosh instance required for creating React hooks.
|
|
489
675
|
*/
|
|
490
|
-
type SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins> = {
|
|
676
|
+
type SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins, TTransports extends string = never> = {
|
|
491
677
|
/** The API instance */
|
|
492
678
|
api: TApi;
|
|
493
679
|
/** State manager for caching and state */
|
|
@@ -496,27 +682,38 @@ type SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins> = {
|
|
|
496
682
|
eventEmitter: EventEmitter;
|
|
497
683
|
/** Plugin executor for running plugins */
|
|
498
684
|
pluginExecutor: PluginExecutor;
|
|
685
|
+
/** Registered transports for subscriptions */
|
|
686
|
+
transports: Map<string, SpooshTransport>;
|
|
687
|
+
/** Config with baseUrl and default options */
|
|
688
|
+
config: {
|
|
689
|
+
baseUrl: string;
|
|
690
|
+
defaultOptions: {
|
|
691
|
+
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
692
|
+
[key: string]: unknown;
|
|
693
|
+
};
|
|
694
|
+
};
|
|
499
695
|
/** Type information (not used at runtime) */
|
|
500
696
|
_types: {
|
|
501
697
|
schema: TSchema;
|
|
502
698
|
defaultError: TDefaultError;
|
|
503
699
|
plugins: TPlugins;
|
|
700
|
+
transports: TTransports;
|
|
504
701
|
};
|
|
505
702
|
};
|
|
506
703
|
|
|
507
704
|
/**
|
|
508
|
-
* Creates React hooks (useRead, useWrite,
|
|
705
|
+
* Creates React hooks (useRead, useWrite, usePages) from a Spoosh instance.
|
|
509
706
|
*
|
|
510
707
|
* @template TSchema - The API schema type
|
|
511
708
|
* @template TDefaultError - The default error type
|
|
512
709
|
* @template TPlugins - The plugins array type
|
|
513
710
|
* @template TApi - The API type
|
|
514
711
|
* @param instance - The Spoosh instance containing api, stateManager, eventEmitter, and pluginExecutor
|
|
515
|
-
* @returns An object containing useRead, useWrite,
|
|
712
|
+
* @returns An object containing useRead, useWrite, usePages hooks and plugin instance APIs
|
|
516
713
|
*
|
|
517
714
|
* @example
|
|
518
715
|
* ```ts
|
|
519
|
-
* const { useRead, useWrite,
|
|
716
|
+
* const { useRead, useWrite, usePages } = create(spooshInstance);
|
|
520
717
|
*
|
|
521
718
|
* function MyComponent() {
|
|
522
719
|
* const { data, loading } = useRead((api) => api("posts").GET());
|
|
@@ -524,7 +721,7 @@ type SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins> = {
|
|
|
524
721
|
* }
|
|
525
722
|
* ```
|
|
526
723
|
*/
|
|
527
|
-
declare function create<TSchema, TDefaultError, TPlugins extends PluginArray, TApi>(instance: SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins>): SpooshReactHooks<TDefaultError, TSchema, TPlugins>;
|
|
724
|
+
declare function create<TSchema, TDefaultError, TPlugins extends PluginArray, TApi, TTransports extends string = never>(instance: SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins, TTransports>): SpooshReactHooks<TDefaultError, TSchema, TPlugins, TTransports>;
|
|
528
725
|
|
|
529
726
|
type PluginHooksConfig<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]> = {
|
|
530
727
|
baseUrl: string;
|
|
@@ -532,4 +729,4 @@ type PluginHooksConfig<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[
|
|
|
532
729
|
plugins: TPlugins;
|
|
533
730
|
};
|
|
534
731
|
|
|
535
|
-
export { type
|
|
732
|
+
export { type BasePagesOptions, type BasePagesResult, type BaseReadOptions, type BaseReadResult, type BaseWriteResult, type ExtractCoreMethodOptions, type ExtractMethodBody, type ExtractMethodData, type ExtractMethodError, type ExtractMethodOptions, type ExtractMethodQuery, type ExtractResponseBody, type ExtractResponseParamNames, type ExtractResponseQuery, type ExtractResponseRequestOptions, type PagesApiClient, type PagesTriggerOptions, type PluginHooksConfig, type QueueApiClient, type QueueTriggerInput, type ReadApiClient, type ResponseInputFields, type SpooshReactHooks, type TriggerOptions, type TypedAccumulateConfig, type TypedParseConfig, type TypedUseSSEOptions, type UsePagesResult, type UseQueueOptions, type UseQueueResult, type UseReadResult, type UseSSEOptions, type UseSSEResult, type UseWriteResult, type WriteApiClient, type WriteResponseInputFields, create };
|