@spoosh/react 0.12.0 → 0.13.1-beta.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/react
2
2
 
3
- React hooks for Spoosh - `useRead`, `useWrite`, and `usePages`.
3
+ React hooks for Spoosh - `useRead`, `useWrite`, `usePages`, and `useSSE`.
4
4
 
5
5
  **[Documentation](https://spoosh.dev/docs/react)** · **Requirements:** TypeScript >= 5.0, React >= 18.0
6
6
 
@@ -158,6 +158,61 @@ function PostList() {
158
158
  }
159
159
  ```
160
160
 
161
+ ### useSSE
162
+
163
+ Subscribe to real-time data streams using Server-Sent Events (SSE).
164
+
165
+ ```typescript
166
+ import { sse } from "@spoosh/transport-sse";
167
+
168
+ // Setup with SSE transport
169
+ const spoosh = new Spoosh<ApiSchema, Error>("/api").withTransports([sse()]);
170
+ export const { useSSE } = create(spoosh);
171
+
172
+ // Basic subscription
173
+ function Notifications() {
174
+ const { data, isConnected, loading } = useSSE(
175
+ (api) => api("notifications").GET({ query: { userId: "user-123" } })
176
+ );
177
+
178
+ if (loading) return <div>Connecting...</div>;
179
+
180
+ return (
181
+ <div>
182
+ <span>{isConnected ? "Connected" : "Disconnected"}</span>
183
+ {data?.message && <p>{data.message.text}</p>}
184
+ </div>
185
+ );
186
+ }
187
+
188
+ // Subscribe to specific events only
189
+ const { data } = useSSE(
190
+ (api) => api("notifications").GET({
191
+ query: { userId: "user-123" },
192
+ }),
193
+ { events: ["alert"] } // Only alert events
194
+ );
195
+
196
+ // AI streaming with accumulation
197
+ const { data, trigger } = useSSE(
198
+ (api) => api("chat").POST(),
199
+ {
200
+ events: ["chunk", "done"],
201
+ parse: "json-done",
202
+ accumulate: {
203
+ chunk: (prev, curr) => ({
204
+ ...curr,
205
+ chunk: (prev?.chunk || "") + curr.chunk,
206
+ }),
207
+ },
208
+ enabled: false,
209
+ }
210
+ );
211
+
212
+ // Start streaming on demand
213
+ await trigger({ body: { message: "Hello" } });
214
+ ```
215
+
161
216
  ## API Reference
162
217
 
163
218
  ### useRead(readFn, options?)
@@ -247,3 +302,33 @@ type InfinitePage<TData> = {
247
302
  | `trigger` | `(options?) => Promise<void>` | Trigger fetch with optional new request options |
248
303
  | `abort` | `() => void` | Abort current request |
249
304
  | `error` | `TError \| undefined` | Error if request failed |
305
+
306
+ ### useSSE(subFn, options?)
307
+
308
+ | Option | Type | Default | Description |
309
+ | ------------ | ------------------ | ----------- | --------------------------------- |
310
+ | `enabled` | `boolean` | `true` | Whether to connect automatically |
311
+ | `events` | `string[]` | all events | Subscribe to specific events only |
312
+ | `parse` | `ParseConfig` | `"auto"` | How to parse raw event data |
313
+ | `accumulate` | `AccumulateConfig` | `"replace"` | How to combine events over time |
314
+
315
+ **Returns:**
316
+
317
+ | Property | Type | Description |
318
+ | ------------- | ----------------------- | ------------------------------ |
319
+ | `data` | `TEvents \| undefined` | Accumulated event data |
320
+ | `error` | `TError \| undefined` | Error if connection failed |
321
+ | `loading` | `boolean` | True during initial connection |
322
+ | `isConnected` | `boolean` | True when connected to stream |
323
+ | `trigger` | `(options?) => Promise` | Reconnect with new options |
324
+ | `disconnect` | `() => void` | Disconnect from stream |
325
+ | `reset` | `() => void` | Reset accumulated data |
326
+
327
+ **Connection Options:**
328
+
329
+ | Option | Type | Description |
330
+ | ------------- | -------------------- | ------------------------------------------- |
331
+ | `headers` | `HeadersInit` | Request headers |
332
+ | `credentials` | `RequestCredentials` | Credentials mode |
333
+ | `maxRetries` | `number` | Max retry attempts (default: 3) |
334
+ | `retryDelay` | `number` | Delay between retries in ms (default: 1000) |
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ReadClient, TagMode, SpooshResponse, ExtractTriggerQuery as ExtractTriggerQuery$2, ExtractTriggerBody as ExtractTriggerBody$2, ExtractTriggerParams as ExtractTriggerParams$2, SpooshPlugin, PluginTypeConfig, MergePluginResults, WriteSelectorClient, SpooshBody, QueueSelectorClient, QueueItem, QueueStats, InfiniteRequestOptions, InfiniteNextContext, InfinitePage, InfinitePrevContext, 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
  /**
@@ -87,7 +112,7 @@ type ExtractInputFromResponse$3<T> = T extends {
87
112
  } ? I : never;
88
113
  type TriggerOptions<T> = ExtractInputFromResponse$3<TriggerAwaitedReturn$3<T>> extends infer I ? [I] extends [never] ? {
89
114
  force?: boolean;
90
- } : ExtractTriggerQuery$2<I> & ExtractTriggerBody$2<I> & ExtractTriggerParams$2<I> & {
115
+ } : ExtractTriggerQuery$3<I> & ExtractTriggerBody$3<I> & ExtractTriggerParams$3<I> & {
91
116
  /** Force refetch even if data is cached */
92
117
  force?: boolean;
93
118
  } : {
@@ -149,26 +174,26 @@ type TriggerAwaitedReturn$2<T> = T extends (...args: any[]) => infer R ? Awaited
149
174
  type ExtractInputFromResponse$2<T> = T extends {
150
175
  input: infer I;
151
176
  } ? I : never;
152
- type ExtractTriggerQuery$1<I> = I extends {
177
+ type ExtractTriggerQuery$2<I> = I extends {
153
178
  query: infer Q;
154
179
  } ? undefined extends Q ? {
155
180
  query?: Exclude<Q, undefined>;
156
181
  } : {
157
182
  query: Q;
158
183
  } : unknown;
159
- type ExtractTriggerBody$1<I> = I extends {
184
+ type ExtractTriggerBody$2<I> = I extends {
160
185
  body: infer B;
161
186
  } ? undefined extends B ? {
162
187
  body?: Exclude<B, undefined> | SpooshBody<Exclude<B, undefined>>;
163
188
  } : {
164
189
  body: B | SpooshBody<B>;
165
190
  } : unknown;
166
- type ExtractTriggerParams$1<I> = I extends {
191
+ type ExtractTriggerParams$2<I> = I extends {
167
192
  params: infer P;
168
193
  } ? {
169
194
  params: P;
170
195
  } : unknown;
171
- type WriteTriggerInput<T> = ExtractInputFromResponse$2<TriggerAwaitedReturn$2<T>> extends infer I ? [I] extends [never] ? object : ExtractTriggerQuery$1<I> & ExtractTriggerBody$1<I> & ExtractTriggerParams$1<I> : object;
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;
172
197
  /**
173
198
  * Result returned by `useWrite` hook.
174
199
  *
@@ -198,21 +223,21 @@ type TriggerAwaitedReturn$1<T> = T extends (...args: any[]) => infer R ? Awaited
198
223
  type ExtractInputFromResponse$1<T> = T extends {
199
224
  input: infer I;
200
225
  } ? I : never;
201
- type ExtractTriggerQuery<I> = I extends {
226
+ type ExtractTriggerQuery$1<I> = I extends {
202
227
  query: infer Q;
203
228
  } ? undefined extends Q ? {
204
229
  query?: Exclude<Q, undefined>;
205
230
  } : {
206
231
  query: Q;
207
232
  } : unknown;
208
- type ExtractTriggerBody<I> = I extends {
233
+ type ExtractTriggerBody$1<I> = I extends {
209
234
  body: infer B;
210
235
  } ? undefined extends B ? {
211
236
  body?: Exclude<B, undefined> | SpooshBody<Exclude<B, undefined>>;
212
237
  } : {
213
238
  body: B | SpooshBody<B>;
214
239
  } : unknown;
215
- type ExtractTriggerParams<I> = I extends {
240
+ type ExtractTriggerParams$1<I> = I extends {
216
241
  params: infer P;
217
242
  } ? {
218
243
  params: P;
@@ -221,7 +246,7 @@ type QueueTriggerBase = {
221
246
  /** Custom ID for this queue item. If not provided, one will be auto-generated. */
222
247
  id?: string;
223
248
  };
224
- type QueueTriggerInput<T> = ExtractInputFromResponse$1<TriggerAwaitedReturn$1<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;
225
250
  /**
226
251
  * Options for useQueue hook.
227
252
  */
@@ -270,7 +295,7 @@ type BaseTriggerOptions = {
270
295
  /** Bypass cache and force refetch. Default: true */
271
296
  force?: boolean;
272
297
  };
273
- type PagesTriggerOptions<TReadFn> = ExtractInputFromResponse<TriggerAwaitedReturn<TReadFn>> extends infer I ? [I] extends [never] ? BaseTriggerOptions : ExtractTriggerQuery$2<I> & ExtractTriggerBody$2<I> & ExtractTriggerParams$2<I> & BaseTriggerOptions : BaseTriggerOptions;
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;
274
299
  /**
275
300
  * Options for `usePages` hook.
276
301
  *
@@ -377,7 +402,107 @@ type BasePagesResult<TData, TError, TItem, TPluginResult = Record<string, unknow
377
402
  type UsePagesResult<TData, TError, TItem, TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[], TTriggerOptions = object> = BasePagesResult<TData, TError, TItem, MergePluginResults<TPlugins>["read"], TTriggerOptions>;
378
403
  type PagesApiClient<TSchema, TDefaultError> = ReadClient<TSchema, TDefaultError>;
379
404
 
380
- type InferError<T, TDefaultError> = [T] extends [unknown] ? TDefaultError : T;
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;
381
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>>;
382
507
  type ResolvedWriteOptions<TSchema, TPlugins extends PluginArray, TMethod, TDefaultError> = ResolveTypes<MergePluginOptions<TPlugins>["write"], WriteResolverContext<TSchema, TMethod, TDefaultError>>;
383
508
  type ResolvedWriteTriggerOptions<TSchema, TPlugins extends PluginArray, TMethod, TDefaultError> = ResolveTypes<MergePluginOptions<TPlugins>["writeTrigger"], WriteResolverContext<TSchema, TMethod, TDefaultError>>;
@@ -395,14 +520,29 @@ type UseQueueFn<TDefaultError, TSchema, TPlugins extends PluginArray> = {
395
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>>;
396
521
  };
397
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
+ };
398
542
  /**
399
- * Spoosh React hooks interface containing useRead, useWrite, and usePages.
400
- *
401
- * @template TDefaultError - The default error type
402
- * @template TSchema - The API schema type
403
- * @template TPlugins - The plugins array type
543
+ * Base hooks that are always available.
404
544
  */
405
- type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
545
+ type BaseSpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
406
546
  /**
407
547
  * React hook for fetching data from an API endpoint with automatic caching and revalidation.
408
548
  *
@@ -478,11 +618,62 @@ type SpooshReactHooks<TDefaultError, TSchema, TPlugins extends PluginArray> = {
478
618
  * ```
479
619
  */
480
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>;
481
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);
482
673
  /**
483
674
  * Shape of a Spoosh instance required for creating React hooks.
484
675
  */
485
- type SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins> = {
676
+ type SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins, TTransports extends string = never> = {
486
677
  /** The API instance */
487
678
  api: TApi;
488
679
  /** State manager for caching and state */
@@ -491,11 +682,22 @@ type SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins> = {
491
682
  eventEmitter: EventEmitter;
492
683
  /** Plugin executor for running plugins */
493
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
+ };
494
695
  /** Type information (not used at runtime) */
495
696
  _types: {
496
697
  schema: TSchema;
497
698
  defaultError: TDefaultError;
498
699
  plugins: TPlugins;
700
+ transports: TTransports;
499
701
  };
500
702
  };
501
703
 
@@ -519,7 +721,7 @@ type SpooshInstanceShape<TApi, TSchema, TDefaultError, TPlugins> = {
519
721
  * }
520
722
  * ```
521
723
  */
522
- 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>;
523
725
 
524
726
  type PluginHooksConfig<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]> = {
525
727
  baseUrl: string;
@@ -527,4 +729,4 @@ type PluginHooksConfig<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[
527
729
  plugins: TPlugins;
528
730
  };
529
731
 
530
- 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 UsePagesResult, type UseQueueOptions, type UseQueueResult, type UseReadResult, type UseWriteResult, type WriteApiClient, type WriteResponseInputFields, create };
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 };