@faasjs/react 8.0.0-beta.34 → 8.0.0-beta.36

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
@@ -6,7 +6,6 @@
6
6
  - [equal](functions/equal.md)
7
7
  - [faas](functions/faas.md)
8
8
  - [FaasReactClient](functions/FaasReactClient.md)
9
- - [generateId](functions/generateId.md)
10
9
  - [getClient](functions/getClient.md)
11
10
  - [OptionalWrapper](functions/OptionalWrapper.md)
12
11
  - [setMock](functions/setMock.md)
package/dist/index.d.ts CHANGED
@@ -1,28 +1,6 @@
1
- import * as _$react from "react";
2
1
  import { Component, ComponentProps, ComponentType, Dispatch, ErrorInfo, JSX, ReactElement, ReactNode, RefObject, SetStateAction } from "react";
3
- import * as _$react_jsx_runtime0 from "react/jsx-runtime";
4
2
  import { FaasActionPaths, FaasData, FaasParams } from "@faasjs/types";
5
3
 
6
- //#region src/generate-id/index.d.ts
7
- /**
8
- * Generate a random identifier with an optional prefix.
9
- *
10
- * @param {string} [prefix] - Prefix prepended to the generated identifier.
11
- * @param {number} [length] - Length of the generated identifier excluding `prefix`. Must be between `8` and `18`.
12
- * @returns {string} Generated identifier string.
13
- * @throws {Error} When `length` is outside the supported `8` to `18` range.
14
- *
15
- * @example
16
- * ```ts
17
- * import { generateId } from '@faasjs/react'
18
- *
19
- * const id = generateId('prefix-')
20
- *
21
- * id.startsWith('prefix-') // true
22
- * ```
23
- */
24
- declare function generateId(prefix?: string, length?: number): string;
25
- //#endregion
26
4
  //#region src/browser/response.d.ts
27
5
  /**
28
6
  * Wrapper class for HTTP responses from FaasJS functions.
@@ -119,7 +97,13 @@ type ResponseHeaders = {
119
97
  [key: string]: string;
120
98
  };
121
99
  /**
122
- * Type definition for the FaasBrowserClient.action method.
100
+ * Type signature for the {@link FaasBrowserClient.action} method.
101
+ *
102
+ * @template Path - Action path used to infer the request params and response data types.
103
+ * @param {Path} action - Action path to invoke.
104
+ * @param {FaasParams<Path>} [params] - Params sent to the action.
105
+ * @param {Options} [options] - Per-request overrides on top of client defaults.
106
+ * @returns {Promise<Response<FaasData<Path>> | Response>} FaasJS response or native fetch response when streaming.
123
107
  */
124
108
  type FaasBrowserClientAction = <Path extends FaasActionPaths>(action: Path, params?: FaasParams<Path>, options?: Options) => Promise<Response<FaasData<Path>> | Response>;
125
109
  /**
@@ -149,12 +133,55 @@ type MockHandler = (action: string, params: Record<string, any> | undefined, opt
149
133
  //#region src/browser/mock.d.ts
150
134
  /**
151
135
  * Set the global mock handler used by all {@link FaasBrowserClient} instances.
136
+ *
137
+ * When a mock handler is set, every {@link FaasBrowserClient.action} call will
138
+ * route through the mock instead of making a real network request, which is
139
+ * useful for testing and local development.
140
+ *
141
+ * @param {MockHandler | ResponseProps | Response | null | undefined} handler -
142
+ * A mock function that receives `(action, params, options)` and returns a
143
+ * response shape, or a static response/value, or `null`/`undefined` to
144
+ * disable mocking.
145
+ *
146
+ * @example
147
+ * ```ts
148
+ * import { setMock, Response } from '@faasjs/react'
149
+ *
150
+ * // Mock all requests with a static response
151
+ * setMock({ data: { name: 'test' } })
152
+ *
153
+ * // Mock with a handler function
154
+ * setMock(async (action, params) => {
155
+ * if (action === 'posts/get') {
156
+ * return { data: { title: 'Hello' } }
157
+ * }
158
+ * return new Error('Not found')
159
+ * })
160
+ *
161
+ * // Disable mocking
162
+ * setMock(null)
163
+ * ```
152
164
  */
153
165
  declare function setMock(handler: MockHandler | ResponseProps | Response | null | undefined): void;
154
166
  //#endregion
155
167
  //#region src/browser/client.d.ts
156
168
  /**
157
169
  * Browser client for FaasJS - provides HTTP client functionality for making API requests from web applications.
170
+ *
171
+ * Handles request URL construction, default and per-request option merging,
172
+ * before-request hooks, mock resolution for testing, and native fetch dispatching.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * import { FaasBrowserClient } from '@faasjs/react'
177
+ *
178
+ * const client = new FaasBrowserClient('https://api.example.com/', {
179
+ * headers: { 'X-Custom-Header': 'value' },
180
+ * })
181
+ *
182
+ * const response = await client.action('posts/get', { id: 1 })
183
+ * console.log(response.data)
184
+ * ```
158
185
  */
159
186
  declare class FaasBrowserClient {
160
187
  /**
@@ -171,10 +198,26 @@ declare class FaasBrowserClient {
171
198
  defaultOptions: Options;
172
199
  /**
173
200
  * Creates a new FaasBrowserClient instance.
201
+ *
202
+ * @param {BaseUrl} [baseUrl='/'] - Base URL used to build action request URLs. Must end with `/`.
203
+ * @param {Options} [options={}] - Default request options merged into every request.
204
+ * @throws {Error} When `baseUrl` does not end with a forward slash.
174
205
  */
175
206
  constructor(baseUrl?: BaseUrl, options?: Options);
176
207
  /**
177
208
  * Makes a request to a FaasJS function.
209
+ *
210
+ * Builds the request URL and resolved options, runs `beforeRequest` hooks,
211
+ * checks for mock handlers, and dispatches via native `fetch` or custom `request`.
212
+ * When `stream` is enabled the raw fetch response is returned so callers can
213
+ * consume the body stream themselves.
214
+ *
215
+ * @template Path - Action path used to infer the request params and response data types.
216
+ * @param {Path} action - Action path to invoke. Must be non-empty.
217
+ * @param {FaasParams<Path>} [params] - Params sent to the action. Defaults to an empty object.
218
+ * @param {Options} [options] - Per-request overrides on top of client defaults.
219
+ * @returns {Promise<Response<FaasData<Path>>>} FaasJS response containing the parsed data, or native fetch response when streaming.
220
+ * @throws {Error} When `action` is empty or falsy.
178
221
  */
179
222
  action<Path extends FaasActionPaths>(action: Path, params: FaasParams<Path>, options?: Options): Promise<Response<FaasData<Path>>>;
180
223
  }
@@ -309,7 +352,7 @@ type FaasDataWrapperRef<Path extends FaasActionPaths> = FaasDataInjection<Path>;
309
352
  * export function UserProfile(props: { id: number }) {
310
353
  * return (
311
354
  * <FaasDataWrapper<User>
312
- * action="/pages/users/get"
355
+ * action="features/users/api/get"
313
356
  * params={{ id: props.id }}
314
357
  * fallback={<div>Loading user...</div>}
315
358
  * render={({ data, error, reload }) => {
@@ -334,7 +377,7 @@ type FaasDataWrapperRef<Path extends FaasActionPaths> = FaasDataInjection<Path>;
334
377
  * export function UserProfileWithChildren(props: { id: number }) {
335
378
  * return (
336
379
  * <FaasDataWrapper<User>
337
- * action="/pages/users/get"
380
+ * action="features/users/api/get"
338
381
  * params={{ id: props.id }}
339
382
  * fallback={<div>Loading user...</div>}
340
383
  * >
@@ -346,7 +389,7 @@ type FaasDataWrapperRef<Path extends FaasActionPaths> = FaasDataInjection<Path>;
346
389
  *
347
390
  * When a ref is provided, it exposes the current Faas request state imperatively.
348
391
  */
349
- declare const FaasDataWrapper: <Path extends FaasActionPaths>(props: FaasDataWrapperProps<Path> & _$react.RefAttributes<FaasDataWrapperRef<Path>>) => React.ReactElement | null;
392
+ declare const FaasDataWrapper: <Path extends FaasActionPaths>(props: FaasDataWrapperProps<Path> & import("react").RefAttributes<FaasDataWrapperRef<Path>>) => React.ReactElement | null;
350
393
  /**
351
394
  * Wrap a component with {@link FaasDataWrapper} and inject Faas request state as props.
352
395
  *
@@ -376,7 +419,7 @@ declare const FaasDataWrapper: <Path extends FaasActionPaths>(props: FaasDataWra
376
419
  *
377
420
  * return <div>{data.name}</div>
378
421
  * },
379
- * { action: '/pages/users/get', params: { id: 1 } },
422
+ * { action: 'features/users/api/get', params: { id: 1 } },
380
423
  * )
381
424
  * ```
382
425
  */
@@ -431,7 +474,7 @@ type UseFaasOptions<Path extends FaasActionPaths> = SharedUseFaasOptions<FaasPar
431
474
  * import { useFaas } from '@faasjs/react'
432
475
  *
433
476
  * function Profile({ id }: { id: number }) {
434
- * const { data, error, loading, reload } = useFaas('/pages/users/get', { id })
477
+ * const { data, error, loading, reload } = useFaas('features/users/api/get', { id })
435
478
  *
436
479
  * if (loading) return <div>Loading...</div>
437
480
  *
@@ -562,7 +605,7 @@ declare function FaasReactClient(options?: FaasReactClientOptions): FaasReactCli
562
605
  *
563
606
  * const client = getClient('https://service-b.example.com/api/')
564
607
  *
565
- * await client.faas('/pages/posts/get', { id: 1 })
608
+ * await client.faas('features/posts/api/get', { id: 1 })
566
609
  * ```
567
610
  */
568
611
  declare function getClient(host?: string): FaasReactClientInstance;
@@ -656,7 +699,7 @@ declare class ErrorBoundary extends Component<ErrorBoundaryProps, {
656
699
  /**
657
700
  * Render children or the configured fallback for the captured error.
658
701
  */
659
- render(): string | number | bigint | boolean | _$react_jsx_runtime0.JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | _$react.ReactPortal | ReactElement<unknown, string | _$react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null;
702
+ render(): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null;
660
703
  }
661
704
  //#endregion
662
705
  //#region src/equal/index.d.ts
@@ -803,7 +846,7 @@ type OptionalWrapperProps<TWrapper extends ComponentType<{
803
846
  * )
804
847
  * ```
805
848
  */
806
- declare function OptionalWrapper(props: OptionalWrapperProps): string | number | bigint | boolean | _$react_jsx_runtime0.JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | _$react.ReactPortal | _$react.ReactElement<unknown, string | _$react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
849
+ declare function OptionalWrapper(props: OptionalWrapperProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
807
850
  declare namespace OptionalWrapper {
808
851
  var displayName: string;
809
852
  }
@@ -958,6 +1001,12 @@ declare function useSplittingState<T extends Record<string, unknown>>(initialSta
958
1001
  //#region src/useFaasStream/index.d.ts
959
1002
  /**
960
1003
  * Options that customize the {@link useFaasStream} request lifecycle.
1004
+ *
1005
+ * Extends the shared request options so stream consumers can control params,
1006
+ * skip logic, debounce timing, polling, and base URL overrides the same way
1007
+ * {@link useFaas} does.
1008
+ *
1009
+ * @see {@link SharedUseFaasOptions} for a full description of each field.
961
1010
  */
962
1011
  type UseFaasStreamOptions = SharedUseFaasOptions<Record<string, any>, string>;
963
1012
  /**
@@ -998,7 +1047,7 @@ type UseFaasStreamResult<Path extends FaasActionPaths> = {
998
1047
  * import { useFaasStream } from '@faasjs/react'
999
1048
  *
1000
1049
  * function Chat({ prompt }: { prompt: string }) {
1001
- * const { data, error, loading, reload } = useFaasStream('/pages/chat/stream', { prompt })
1050
+ * const { data, error, loading, reload } = useFaasStream('features/chat/api/stream', { prompt })
1002
1051
  *
1003
1052
  * if (loading) return <div>Streaming...</div>
1004
1053
  *
@@ -1068,4 +1117,4 @@ declare function usePrevious<T = any>(value: T): T | undefined;
1068
1117
  declare function useStateRef<T>(initialValue: T | (() => T)): [T, Dispatch<SetStateAction<T>>, RefObject<T>];
1069
1118
  declare function useStateRef<T = undefined>(): [T | undefined, Dispatch<SetStateAction<T | undefined>>, RefObject<T | undefined>];
1070
1119
  //#endregion
1071
- export { type BaseUrl, ErrorBoundary, ErrorBoundaryProps, ErrorChildrenProps, FaasBrowserClient, type FaasBrowserClientAction, FaasDataInjection, FaasDataWrapper, FaasDataWrapperProps, FaasDataWrapperRef, FaasReactClient, FaasReactClientInstance, FaasReactClientOptions, type MockHandler, OnError, OptionalWrapper, OptionalWrapperProps, type Options, Response, ResponseError, type ResponseErrorProps, type ResponseHeaders, type ResponseProps, StateSetters, StatesWithSetters, UseFaasOptions, UseFaasStreamOptions, UseFaasStreamResult, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, usePrevious, useSplittingState, useStateRef, withFaasData };
1120
+ export { type BaseUrl, ErrorBoundary, ErrorBoundaryProps, ErrorChildrenProps, FaasBrowserClient, type FaasBrowserClientAction, FaasDataInjection, FaasDataWrapper, FaasDataWrapperProps, FaasDataWrapperRef, FaasReactClient, FaasReactClientInstance, FaasReactClientOptions, type MockHandler, OnError, OptionalWrapper, OptionalWrapperProps, type Options, Response, ResponseError, type ResponseErrorProps, type ResponseHeaders, type ResponseProps, StateSetters, StatesWithSetters, UseFaasOptions, UseFaasStreamOptions, UseFaasStreamResult, createSplittingContext, equal, faas, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, usePrevious, useSplittingState, useStateRef, withFaasData };
package/dist/index.mjs CHANGED
@@ -1,28 +1,6 @@
1
+ import { generateId } from "@faasjs/utils";
1
2
  import { Component, cloneElement, createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
2
3
  import { jsx, jsxs } from "react/jsx-runtime";
3
- //#region src/generate-id/index.ts
4
- /**
5
- * Generate a random identifier with an optional prefix.
6
- *
7
- * @param {string} [prefix] - Prefix prepended to the generated identifier.
8
- * @param {number} [length] - Length of the generated identifier excluding `prefix`. Must be between `8` and `18`.
9
- * @returns {string} Generated identifier string.
10
- * @throws {Error} When `length` is outside the supported `8` to `18` range.
11
- *
12
- * @example
13
- * ```ts
14
- * import { generateId } from '@faasjs/react'
15
- *
16
- * const id = generateId('prefix-')
17
- *
18
- * id.startsWith('prefix-') // true
19
- * ```
20
- */
21
- function generateId(prefix = "", length = 18) {
22
- if (length < 8 || length > 18) throw new Error("Length must be 8 ~ 18");
23
- return `${prefix}${Date.now().toString(36).padStart(8, "0")}${Math.random().toString(36).substring(2, length - 6).padEnd(length - 8, "0")}`;
24
- }
25
- //#endregion
26
4
  //#region src/browser/response.ts
27
5
  /**
28
6
  * Wrapper class for HTTP responses from FaasJS functions.
@@ -107,6 +85,34 @@ var ResponseError = class extends Error {
107
85
  let mock = null;
108
86
  /**
109
87
  * Set the global mock handler used by all {@link FaasBrowserClient} instances.
88
+ *
89
+ * When a mock handler is set, every {@link FaasBrowserClient.action} call will
90
+ * route through the mock instead of making a real network request, which is
91
+ * useful for testing and local development.
92
+ *
93
+ * @param {MockHandler | ResponseProps | Response | null | undefined} handler -
94
+ * A mock function that receives `(action, params, options)` and returns a
95
+ * response shape, or a static response/value, or `null`/`undefined` to
96
+ * disable mocking.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * import { setMock, Response } from '@faasjs/react'
101
+ *
102
+ * // Mock all requests with a static response
103
+ * setMock({ data: { name: 'test' } })
104
+ *
105
+ * // Mock with a handler function
106
+ * setMock(async (action, params) => {
107
+ * if (action === 'posts/get') {
108
+ * return { data: { title: 'Hello' } }
109
+ * }
110
+ * return new Error('Not found')
111
+ * })
112
+ *
113
+ * // Disable mocking
114
+ * setMock(null)
115
+ * ```
110
116
  */
111
117
  function setMock(handler) {
112
118
  mock = handler;
@@ -137,15 +143,50 @@ function normalizeMockResponse(response) {
137
143
  }
138
144
  return new Response(response || {});
139
145
  }
146
+ /**
147
+ * Resolve a mock response for the current request.
148
+ *
149
+ * If the global mock is a function it is invoked with the action, params, and
150
+ * resolved options. Otherwise the static mock value is normalized into a
151
+ * {@link Response} object.
152
+ *
153
+ * @template Path - Action path used for response data inference.
154
+ * @param {string} action - Action path being requested.
155
+ * @param {Record<string, any>} params - Params sent with the request.
156
+ * @param {ResolvedActionOptions} options - Fully resolved request options.
157
+ * @returns {Promise<Response<FaasData<Path>>>} Normalized mock response.
158
+ */
140
159
  async function resolveMockResponse(action, params, options) {
141
160
  if (typeof mock === "function") return normalizeMockResponse(await mock(action, params, options));
142
161
  return normalizeMockResponse(mock);
143
162
  }
144
163
  //#endregion
145
164
  //#region src/browser/helpers.ts
165
+ /**
166
+ * Build the full request URL for an action, with optional request-id query parameter.
167
+ *
168
+ * @param {string} action - Action path to append to the base URL.
169
+ * @param {BaseUrl} baseUrl - Default base URL used when the request options do not override it.
170
+ * @param {Options} [options] - Per-request options whose `baseUrl` field overrides `baseUrl`.
171
+ * @param {string} [requestId] - Unique request identifier appended as the `_` query parameter.
172
+ * @returns {string} Fully assembled request URL.
173
+ */
146
174
  function buildActionUrl(action, baseUrl, options, requestId) {
147
175
  return `${(options?.baseUrl || baseUrl) + action}?_=${requestId}`;
148
176
  }
177
+ /**
178
+ * Merge default and per-request options into a fully resolved request configuration.
179
+ *
180
+ * Sets method to `POST`, adds JSON content-type and CORS credentials, serializes params
181
+ * as JSON body, and injects the `X-FaasJS-Request-Id` header unless already present.
182
+ *
183
+ * @template Path - Action path used for params inference.
184
+ * @param {Options} defaultOptions - Client-wide default options merged into every request.
185
+ * @param {Options | undefined} options - Per-request overrides applied on top of defaults.
186
+ * @param {FaasParams<Path>} params - Params to serialize as the JSON request body.
187
+ * @param {string} requestId - Unique request identifier injected into headers.
188
+ * @returns {ResolvedActionOptions} Fully resolved request options ready for `fetch`.
189
+ */
149
190
  function buildActionOptions(defaultOptions, options, params, requestId) {
150
191
  const resolvedOptions = {
151
192
  method: "POST",
@@ -159,6 +200,15 @@ function buildActionOptions(defaultOptions, options, params, requestId) {
159
200
  if (!resolvedOptions.headers["X-FaasJS-Request-Id"] && !resolvedOptions.headers["x-faasjs-request-id"]) resolvedOptions.headers["X-FaasJS-Request-Id"] = requestId;
160
201
  return resolvedOptions;
161
202
  }
203
+ /**
204
+ * Invoke the `beforeRequest` hook if it is configured in the resolved options.
205
+ *
206
+ * @template Path - Action path used for params inference.
207
+ * @param {Path} action - Action path being requested.
208
+ * @param {FaasParams<Path>} params - Params being sent with the request.
209
+ * @param {ResolvedActionOptions} options - Resolved options that may carry a `beforeRequest` callback.
210
+ * @returns {Promise<void>} Resolves after the hook (if any) completes.
211
+ */
162
212
  async function runBeforeRequest(action, params, options) {
163
213
  if (!options.beforeRequest) return;
164
214
  await options.beforeRequest({
@@ -168,11 +218,30 @@ async function runBeforeRequest(action, params, options) {
168
218
  headers: options.headers
169
219
  });
170
220
  }
221
+ /**
222
+ * Convert fetch `Headers` iterable to a plain key-value object.
223
+ *
224
+ * @param {Iterable<[string, string]>} headers - Iterable of header name/value pairs.
225
+ * @returns {ResponseHeaders} Plain object keyed by lower-cased header names.
226
+ */
171
227
  function toResponseHeaders(headers) {
172
228
  const responseHeaders = {};
173
229
  for (const [key, value] of headers) responseHeaders[key] = value;
174
230
  return responseHeaders;
175
231
  }
232
+ /**
233
+ * Parse a successful (2xx) HTTP response body into a {@link Response} object.
234
+ *
235
+ * If the parsed body contains an `error.message` property the response is treated
236
+ * as a logical error and a {@link ResponseError} is thrown instead.
237
+ *
238
+ * @template Path - Action path used for response data inference.
239
+ * @param {number} status - HTTP status code.
240
+ * @param {ResponseHeaders} headers - Response headers.
241
+ * @param {string} text - Raw response body text.
242
+ * @returns {Response<FaasData<Path>>} Wrapped response containing the parsed data.
243
+ * @throws {ResponseError} When the body contains a logical `error.message`.
244
+ */
176
245
  function parseSuccessfulResponse(status, headers, text) {
177
246
  if (!text) return new Response({
178
247
  status,
@@ -192,6 +261,15 @@ function parseSuccessfulResponse(status, headers, text) {
192
261
  data: body.data
193
262
  });
194
263
  }
264
+ /**
265
+ * Parse a failed (non-2xx) HTTP response and always throw a {@link ResponseError}.
266
+ *
267
+ * @param {number} status - HTTP status code.
268
+ * @param {ResponseHeaders} headers - Response headers.
269
+ * @param {string} text - Raw response body text.
270
+ * @returns {never} Always throws – never returns normally.
271
+ * @throws {ResponseError} Wrapped error containing the HTTP status, headers, and body.
272
+ */
195
273
  function parseFailedResponse(status, headers, text) {
196
274
  try {
197
275
  const body = JSON.parse(text);
@@ -217,6 +295,17 @@ function parseFailedResponse(status, headers, text) {
217
295
  });
218
296
  }
219
297
  }
298
+ /**
299
+ * Read and parse a native fetch response into a FaasJS {@link Response} or throw on failure.
300
+ *
301
+ * Routes to {@link parseSuccessfulResponse} for 2xx status codes and
302
+ * {@link parseFailedResponse} for all others.
303
+ *
304
+ * @template Path - Action path used for response data inference.
305
+ * @param {ParsedFetchResponse} response - Parsed fetch response with headers, status, and text body.
306
+ * @returns {Promise<Response<FaasData<Path>>>} Wrapped FaasJS response on success.
307
+ * @throws {ResponseError} When the HTTP status indicates a failure.
308
+ */
220
309
  async function parseFetchResponse(response) {
221
310
  const headers = toResponseHeaders(response.headers);
222
311
  const text = await response.text();
@@ -227,6 +316,21 @@ async function parseFetchResponse(response) {
227
316
  //#region src/browser/client.ts
228
317
  /**
229
318
  * Browser client for FaasJS - provides HTTP client functionality for making API requests from web applications.
319
+ *
320
+ * Handles request URL construction, default and per-request option merging,
321
+ * before-request hooks, mock resolution for testing, and native fetch dispatching.
322
+ *
323
+ * @example
324
+ * ```ts
325
+ * import { FaasBrowserClient } from '@faasjs/react'
326
+ *
327
+ * const client = new FaasBrowserClient('https://api.example.com/', {
328
+ * headers: { 'X-Custom-Header': 'value' },
329
+ * })
330
+ *
331
+ * const response = await client.action('posts/get', { id: 1 })
332
+ * console.log(response.data)
333
+ * ```
230
334
  */
231
335
  var FaasBrowserClient = class {
232
336
  /**
@@ -243,6 +347,10 @@ var FaasBrowserClient = class {
243
347
  defaultOptions;
244
348
  /**
245
349
  * Creates a new FaasBrowserClient instance.
350
+ *
351
+ * @param {BaseUrl} [baseUrl='/'] - Base URL used to build action request URLs. Must end with `/`.
352
+ * @param {Options} [options={}] - Default request options merged into every request.
353
+ * @throws {Error} When `baseUrl` does not end with a forward slash.
246
354
  */
247
355
  constructor(baseUrl = "/", options = Object.create(null)) {
248
356
  if (baseUrl && !baseUrl.endsWith("/")) throw Error("[FaasJS] baseUrl should end with /");
@@ -252,6 +360,18 @@ var FaasBrowserClient = class {
252
360
  }
253
361
  /**
254
362
  * Makes a request to a FaasJS function.
363
+ *
364
+ * Builds the request URL and resolved options, runs `beforeRequest` hooks,
365
+ * checks for mock handlers, and dispatches via native `fetch` or custom `request`.
366
+ * When `stream` is enabled the raw fetch response is returned so callers can
367
+ * consume the body stream themselves.
368
+ *
369
+ * @template Path - Action path used to infer the request params and response data types.
370
+ * @param {Path} action - Action path to invoke. Must be non-empty.
371
+ * @param {FaasParams<Path>} [params] - Params sent to the action. Defaults to an empty object.
372
+ * @param {Options} [options] - Per-request overrides on top of client defaults.
373
+ * @returns {Promise<Response<FaasData<Path>>>} FaasJS response containing the parsed data, or native fetch response when streaming.
374
+ * @throws {Error} When `action` is empty or falsy.
255
375
  */
256
376
  async action(action, params, options) {
257
377
  if (!action) throw Error("[FaasJS] action required");
@@ -508,7 +628,7 @@ function useEqualCallback(callback, dependencies) {
508
628
  * export function UserProfile(props: { id: number }) {
509
629
  * return (
510
630
  * <FaasDataWrapper<User>
511
- * action="/pages/users/get"
631
+ * action="features/users/api/get"
512
632
  * params={{ id: props.id }}
513
633
  * fallback={<div>Loading user...</div>}
514
634
  * render={({ data, error, reload }) => {
@@ -533,7 +653,7 @@ function useEqualCallback(callback, dependencies) {
533
653
  * export function UserProfileWithChildren(props: { id: number }) {
534
654
  * return (
535
655
  * <FaasDataWrapper<User>
536
- * action="/pages/users/get"
656
+ * action="features/users/api/get"
537
657
  * params={{ id: props.id }}
538
658
  * fallback={<div>Loading user...</div>}
539
659
  * >
@@ -606,7 +726,7 @@ Object.assign(FaasDataWrapper, { displayName: "FaasDataWrapper" });
606
726
  *
607
727
  * return <div>{data.name}</div>
608
728
  * },
609
- * { action: '/pages/users/get', params: { id: 1 } },
729
+ * { action: 'features/users/api/get', params: { id: 1 } },
610
730
  * )
611
731
  * ```
612
732
  */
@@ -639,7 +759,7 @@ function withFaasData(Component, faasProps) {
639
759
  * ```ts
640
760
  * function useUserRequest(id: number) {
641
761
  * return useFaasRequest({
642
- * action: '/pages/users/get',
762
+ * action: 'features/users/api/get',
643
763
  * defaultParams: { id },
644
764
  * options: {},
645
765
  * send: async ({ action, params, signal, client, setPromise }) => {
@@ -672,6 +792,7 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
672
792
  const handledRequestTriggerTimesRef = useRef(-1);
673
793
  const pollingTimerRef = useRef(null);
674
794
  const hasLoadedRef = useRef(false);
795
+ const isOnlineRef = useRef(typeof navigator !== "undefined" ? navigator.onLine : true);
675
796
  const beforeSendRef = useRef(beforeSend);
676
797
  const onSuccessRef = useRef(onSuccess);
677
798
  const sendRef = useRef(send);
@@ -709,14 +830,34 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
709
830
  const schedulePolling = () => {
710
831
  clearPollingTimer();
711
832
  if (!options.polling || options.polling <= 0 || !isCurrentRequest()) return;
833
+ if (!isOnlineRef.current) return;
712
834
  pollingTimerRef.current = setTimeout(() => {
713
835
  if (!isCurrentRequest()) return;
836
+ if (!isOnlineRef.current) return;
714
837
  setRequestTrigger((prev) => ({
715
838
  times: prev.times + 1,
716
839
  silent: true
717
840
  }));
718
841
  }, options.polling);
719
842
  };
843
+ const handleOnline = () => {
844
+ isOnlineRef.current = true;
845
+ if (options.polling && options.polling > 0 && !skip) setRequestTrigger((prev) => ({
846
+ times: prev.times + 1,
847
+ silent: hasLoadedRef.current
848
+ }));
849
+ };
850
+ const handleOffline = () => {
851
+ isOnlineRef.current = false;
852
+ if (pollingTimerRef.current) {
853
+ clearTimeout(pollingTimerRef.current);
854
+ pollingTimerRef.current = null;
855
+ }
856
+ };
857
+ if (typeof window !== "undefined") {
858
+ window.addEventListener("online", handleOnline);
859
+ window.addEventListener("offline", handleOffline);
860
+ }
720
861
  const rejectPending = (reason) => {
721
862
  for (const { reject } of pendingReloadsRef.current.values()) reject(reason);
722
863
  pendingReloadsRef.current.clear();
@@ -773,6 +914,10 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
773
914
  return () => {
774
915
  clearTimeout(timeout);
775
916
  clearPollingTimer();
917
+ if (typeof window !== "undefined") {
918
+ window.removeEventListener("online", handleOnline);
919
+ window.removeEventListener("offline", handleOffline);
920
+ }
776
921
  if (controllerRef.current === controller) controllerRef.current = null;
777
922
  controller.abort();
778
923
  setLoading(false);
@@ -782,6 +927,10 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
782
927
  run();
783
928
  return () => {
784
929
  clearPollingTimer();
930
+ if (typeof window !== "undefined") {
931
+ window.removeEventListener("online", handleOnline);
932
+ window.removeEventListener("offline", handleOffline);
933
+ }
785
934
  if (controllerRef.current === controller) controllerRef.current = null;
786
935
  controller.abort();
787
936
  setLoading(false);
@@ -845,7 +994,7 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
845
994
  * import { useFaas } from '@faasjs/react'
846
995
  *
847
996
  * function Profile({ id }: { id: number }) {
848
- * const { data, error, loading, reload } = useFaas('/pages/users/get', { id })
997
+ * const { data, error, loading, reload } = useFaas('features/users/api/get', { id })
849
998
  *
850
999
  * if (loading) return <div>Loading...</div>
851
1000
  *
@@ -994,7 +1143,7 @@ function FaasReactClient(options = { baseUrl: "/" }) {
994
1143
  *
995
1144
  * const client = getClient('https://service-b.example.com/api/')
996
1145
  *
997
- * await client.faas('/pages/posts/get', { id: 1 })
1146
+ * await client.faas('features/posts/api/get', { id: 1 })
998
1147
  * ```
999
1148
  */
1000
1149
  function getClient(host) {
@@ -1277,7 +1426,7 @@ function createSplittingContext(defaultValue) {
1277
1426
  * import { useFaasStream } from '@faasjs/react'
1278
1427
  *
1279
1428
  * function Chat({ prompt }: { prompt: string }) {
1280
- * const { data, error, loading, reload } = useFaasStream('/pages/chat/stream', { prompt })
1429
+ * const { data, error, loading, reload } = useFaasStream('features/chat/api/stream', { prompt })
1281
1430
  *
1282
1431
  * if (loading) return <div>Streaming...</div>
1283
1432
  *
@@ -1400,4 +1549,4 @@ function useStateRef(initialValue) {
1400
1549
  ];
1401
1550
  }
1402
1551
  //#endregion
1403
- export { ErrorBoundary, FaasBrowserClient, FaasDataWrapper, FaasReactClient, OptionalWrapper, Response, ResponseError, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, usePrevious, useSplittingState, useStateRef, withFaasData };
1552
+ export { ErrorBoundary, FaasBrowserClient, FaasDataWrapper, FaasReactClient, OptionalWrapper, Response, ResponseError, createSplittingContext, equal, faas, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, usePrevious, useSplittingState, useStateRef, withFaasData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/react",
3
- "version": "8.0.0-beta.34",
3
+ "version": "8.0.0-beta.36",
4
4
  "homepage": "https://faasjs.com/doc/react/",
5
5
  "bugs": {
6
6
  "url": "https://github.com/faasjs/faasjs/issues"
@@ -26,12 +26,14 @@
26
26
  }
27
27
  },
28
28
  "devDependencies": {
29
- "@faasjs/types": ">=8.0.0-beta.34",
29
+ "@faasjs/types": ">=8.0.0-beta.36",
30
+ "@faasjs/utils": ">=8.0.0-beta.36",
30
31
  "@types/react": "^19.0.0",
31
32
  "react": "^19.0.0"
32
33
  },
33
34
  "peerDependencies": {
34
- "@faasjs/types": ">=8.0.0-beta.34"
35
+ "@faasjs/types": ">=8.0.0-beta.36",
36
+ "@faasjs/utils": ">=8.0.0-beta.36"
35
37
  },
36
38
  "engines": {
37
39
  "node": ">=26.0.0",