@faasjs/react 8.0.0-beta.14 → 8.0.0-beta.16

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/dist/index.mjs CHANGED
@@ -35,10 +35,7 @@ function generateId(prefix = "", length = 18) {
35
35
  * @property {T} [data] - The parsed JSON data from the response.
36
36
  * Optional property that contains the response payload when JSON is provided.
37
37
  *
38
- * @param {ResponseProps<T>} [props] - Response properties including status, headers, body, and data.
39
- * All properties are optional with sensible defaults.
40
- *
41
- * @remarks
38
+ * Notes:
42
39
  * - status defaults to 200 if data or body is present, 204 otherwise
43
40
  * - body is automatically populated from data if not explicitly provided
44
41
  * - headers defaults to an empty object if not provided
@@ -141,6 +138,12 @@ var Response = class {
141
138
  headers;
142
139
  body;
143
140
  data;
141
+ /**
142
+ * Create a wrapped response object.
143
+ *
144
+ * @param props - Response properties including status, headers, body, and data.
145
+ * @returns Wrapped response instance.
146
+ */
144
147
  constructor(props = {}) {
145
148
  this.status = props.status || (props.data || props.body ? 200 : 204);
146
149
  this.headers = props.headers || {};
@@ -155,17 +158,13 @@ var Response = class {
155
158
  * Extends the built-in Error class to provide additional information about failed requests,
156
159
  * including HTTP status code, response headers, response body, and the original error.
157
160
  *
158
- * @class ResponseError
159
- * @extends {Error}
161
+ * @augments Error
160
162
  *
161
163
  * @property {number} status - The HTTP status code of the failed response. Defaults to 500 if not provided.
162
164
  * @property {ResponseHeaders} headers - The response headers from the failed request.
163
165
  * @property {any} body - The response body containing error details or the original error if available.
164
166
  * @property {Error} [originalError] - The original Error object if this ResponseError was created from another Error.
165
167
  *
166
- * @param {string | Error | ResponseErrorProps} data - The error message, an Error object, or a ResponseErrorProps object.
167
- * @param {Omit<ResponseErrorProps, 'message' | 'originalError'>} [options] - Additional options for the error (status, headers, body).
168
- *
169
168
  * @example Basic error with message
170
169
  * ```ts
171
170
  * throw new ResponseError('User not found')
@@ -237,7 +236,7 @@ var Response = class {
237
236
  * })
238
237
  * ```
239
238
  *
240
- * @remarks
239
+ * Notes:
241
240
  * - ResponseError is automatically thrown by the action method when the server returns an error (status >= 400)
242
241
  * - The error message from server responses is extracted from body.error.message if available
243
242
  * - When created from an Error object, the original error is preserved in the originalError property
@@ -254,13 +253,20 @@ var ResponseError = class extends Error {
254
253
  headers;
255
254
  body;
256
255
  originalError;
256
+ /**
257
+ * Create a ResponseError from a message, Error, or structured response error payload.
258
+ *
259
+ * @param data - Error message, Error object, or structured response error props.
260
+ * @param options - Additional options such as status, headers, and body.
261
+ * @returns ResponseError instance.
262
+ */
257
263
  constructor(data, options) {
258
264
  let props;
259
265
  if (typeof data === "string") props = {
260
266
  message: data,
261
267
  ...options
262
268
  };
263
- else if (data instanceof Error || data?.constructor?.name?.includes("Error")) props = {
269
+ else if (data instanceof Error || typeof data === "object" && data !== null && typeof data.constructor?.name === "string" && data.constructor.name.includes("Error")) props = {
264
270
  message: data.message,
265
271
  originalError: data,
266
272
  ...options
@@ -343,7 +349,7 @@ function setMock(handler) {
343
349
  * - Streaming support for large responses
344
350
  * - Multiple instance support with unique IDs
345
351
  *
346
- * @remarks
352
+ * Notes:
347
353
  * - All requests are POST requests by default
348
354
  * - Automatically adds X-FaasJS-Request-Id header for request tracking
349
355
  * - baseUrl must end with '/' (will throw Error if not)
@@ -405,11 +411,8 @@ var FaasBrowserClient = class {
405
411
  /**
406
412
  * Creates a new FaasBrowserClient instance.
407
413
  *
408
- * @param baseUrl - Base URL for all API requests. Must end with '/'. Defaults to '/' for relative requests.
409
- * @throws {Error} If baseUrl does not end with '/'
410
- * @param options - Configuration options for the client.
411
- * Supports default headers, beforeRequest hook, custom request function,
412
- * baseUrl override, and streaming mode.
414
+ * @param baseUrl - Base URL for all API requests. Must end with `/`. Defaults to `/` for relative requests.
415
+ * @param options - Default request options such as headers, hooks, request override, or stream mode.
413
416
  *
414
417
  * @example Basic initialization
415
418
  * ```ts
@@ -460,7 +463,7 @@ var FaasBrowserClient = class {
460
463
  * })
461
464
  * ```
462
465
  *
463
- * @throws {Error} When baseUrl does not end with '/'
466
+ * @throws {Error} When `baseUrl` does not end with `/`
464
467
  */
465
468
  constructor(baseUrl = "/", options = Object.create(null)) {
466
469
  if (baseUrl && !baseUrl.endsWith("/")) throw Error("[FaasJS] baseUrl should end with /");
@@ -485,9 +488,9 @@ var FaasBrowserClient = class {
485
488
  *
486
489
  * @throws {Error} When action is not provided or is empty
487
490
  * @throws {ResponseError} When the server returns an error response (status >= 400 or body.error exists)
488
- * @throws {NetworkError} When network request fails
491
+ * @throws {Error} When the request fails before a response is received
489
492
  *
490
- * @remarks
493
+ * Notes:
491
494
  * - All requests are POST requests by default
492
495
  * - Action path is automatically converted to lowercase
493
496
  * - A unique request ID is generated for each request and sent in X-FaasJS-Request-Id header
@@ -539,11 +542,7 @@ var FaasBrowserClient = class {
539
542
  * email: string
540
543
  * }
541
544
  *
542
- * const response = await client.action<{
543
- * action: 'user'
544
- * params: { id: number }
545
- * data: UserData
546
- * }>('user', { id: 123 })
545
+ * const response = await client.action<UserData>('user', { id: 123 })
547
546
  * console.log(response.data.name) // TypeScript knows it's a string
548
547
  * ```
549
548
  *
@@ -555,7 +554,7 @@ var FaasBrowserClient = class {
555
554
  * } catch (error) {
556
555
  * if (error instanceof ResponseError) {
557
556
  * console.error(`Server error: ${error.message}`, error.status)
558
- * if (error.data) console.error('Error details:', error.data)
557
+ * if (error.body) console.error('Error details:', error.body)
559
558
  * } else {
560
559
  * console.error('Network error:', error)
561
560
  * }
@@ -646,7 +645,7 @@ var FaasBrowserClient = class {
646
645
  headers,
647
646
  body
648
647
  }));
649
- } catch (_) {
648
+ } catch {
650
649
  return Promise.reject(new ResponseError({
651
650
  message: res,
652
651
  status: response.status,
@@ -661,17 +660,20 @@ var FaasBrowserClient = class {
661
660
  //#endregion
662
661
  //#region src/faas.ts
663
662
  /**
664
- * Request faas server
663
+ * Call the currently configured FaasReactClient.
665
664
  *
666
- * @param action {string} action name
667
- * @param params {object} action params
668
- * @returns {Promise<Response<any>>}
665
+ * @param action - Action path to invoke.
666
+ * @param params - Parameters sent to the action.
667
+ * @param options - Optional per-request overrides such as headers or base URL.
668
+ * @returns Response returned by the active browser client.
669
669
  *
670
670
  * @example
671
671
  * ```ts
672
- * faas<{ title: string }>('post/get', { id: 1 }).then(res => {
673
- * console.log(res.data.title)
674
- * })
672
+ * import { faas } from '@faasjs/react'
673
+ *
674
+ * const response = await faas<{ title: string }>('post/get', { id: 1 })
675
+ *
676
+ * console.log(response.data.title)
675
677
  * ```
676
678
  */
677
679
  async function faas(action, params, options) {
@@ -732,13 +734,18 @@ function equal(a, b) {
732
734
  * @returns The memoized value.
733
735
  */
734
736
  function useEqualMemoize(value) {
737
+ const ref = useRef(value);
738
+ if (!equal(value, ref.current)) ref.current = value;
739
+ return ref.current;
740
+ }
741
+ function useEqualSignal(value) {
735
742
  const ref = useRef(value);
736
743
  const signalRef = useRef(0);
737
744
  if (!equal(value, ref.current)) {
738
745
  ref.current = value;
739
746
  signalRef.current += 1;
740
747
  }
741
- return useMemo(() => ref.current, [signalRef.current]);
748
+ return signalRef.current;
742
749
  }
743
750
  /**
744
751
  * Custom hook that works like `useEffect` but uses deep comparison on dependencies.
@@ -748,7 +755,7 @@ function useEqualMemoize(value) {
748
755
  * @returns The result of the `useEffect` hook with memoized dependencies.
749
756
  */
750
757
  function useEqualEffect(callback, dependencies) {
751
- return useEffect(callback, useEqualMemoize(dependencies));
758
+ return useEffect(callback, [useEqualSignal(dependencies)]);
752
759
  }
753
760
  /**
754
761
  * Custom hook that works like `useMemo` but uses deep comparison on dependencies.
@@ -758,7 +765,12 @@ function useEqualEffect(callback, dependencies) {
758
765
  * @returns The result of the `useMemo` hook with memoized dependencies.
759
766
  */
760
767
  function useEqualMemo(callback, dependencies) {
761
- return useMemo(callback, useEqualMemoize(dependencies));
768
+ const signal = useEqualSignal(dependencies);
769
+ const callbackRef = useRef(callback);
770
+ callbackRef.current = callback;
771
+ return useMemo(() => {
772
+ return callbackRef.current();
773
+ }, [signal]);
762
774
  }
763
775
  /**
764
776
  * Custom hook that works like `useCallback` but uses deep comparison on dependencies.
@@ -768,7 +780,7 @@ function useEqualMemo(callback, dependencies) {
768
780
  * @returns The result of the `useCallback` hook with memoized dependencies.
769
781
  */
770
782
  function useEqualCallback(callback, dependencies) {
771
- return useCallback((...args) => callback(...args), useEqualMemoize(dependencies));
783
+ return useCallback((...args) => callback(...args), [useEqualSignal(dependencies)]);
772
784
  }
773
785
  const FaasDataWrapper = forwardRef((props, ref) => {
774
786
  const requestOptions = {
@@ -817,16 +829,23 @@ function withFaasData(Component, faasProps) {
817
829
  //#endregion
818
830
  //#region src/useFaas.tsx
819
831
  /**
820
- * Request faas server with React hook
832
+ * Request FaasJS data and keep request state in React state.
833
+ *
834
+ * `useFaas` sends an initial request unless `skip` is enabled, and returns
835
+ * request state plus helpers for reloading, updating data, and handling errors.
821
836
  *
822
- * @param action {string} action name
823
- * @param defaultParams {object} initial action params
824
- * @returns {FaasDataInjection<any>}
837
+ * @param action - Action path to invoke.
838
+ * @param defaultParams - Params used for the initial request and future reloads.
839
+ * @param options - Optional hook configuration such as controlled data, debounce, and skip logic.
840
+ * @returns Request state and helper methods for the action.
825
841
  *
826
842
  * @example
827
843
  * ```tsx
828
- * function Post ({ id }) {
844
+ * import { useFaas } from '@faasjs/react'
845
+ *
846
+ * function Post({ id }: { id: number }) {
829
847
  * const { data } = useFaas<{ title: string }>('post/get', { id })
848
+ *
830
849
  * return <h1>{data.title}</h1>
831
850
  * }
832
851
  * ```
@@ -867,7 +886,8 @@ function useFaas(action, defaultParams, options = {}) {
867
886
  const nextData = r.data;
868
887
  setFails(0);
869
888
  setError(null);
870
- options.setData ? options.setData(nextData) : localSetData(nextData);
889
+ if (options.setData) options.setData(nextData);
890
+ else localSetData(nextData);
871
891
  setLoading(false);
872
892
  for (const { resolve } of pendingReloadsRef.current.values()) resolve(nextData);
873
893
  pendingReloadsRef.current.clear();
@@ -945,14 +965,20 @@ function useFaas(action, defaultParams, options = {}) {
945
965
  //#region src/client.tsx
946
966
  const clients = {};
947
967
  /**
948
- * Before use faas, you should initialize a FaasReactClient.
968
+ * Create and register a FaasReactClient instance.
969
+ *
970
+ * The returned client is stored by `baseUrl` and becomes the default client
971
+ * used by helpers such as {@link faas} and {@link useFaas}.
949
972
  *
950
- * @returns FaasReactClient instance.
973
+ * @param options - Client configuration including base URL, default request options, and error hooks.
974
+ * @returns Registered FaasReactClient instance.
951
975
  *
952
976
  * @example
953
977
  * ```ts
978
+ * import { FaasReactClient } from '@faasjs/react'
979
+ *
954
980
  * const client = FaasReactClient({
955
- * baseUrl: 'localhost:8080/api/'
981
+ * baseUrl: 'http://localhost:8080/api/',
956
982
  * })
957
983
  * ```
958
984
  */
@@ -981,16 +1007,20 @@ function FaasReactClient({ baseUrl, options: clientOptions, onError } = { baseUr
981
1007
  return reactClient;
982
1008
  }
983
1009
  /**
984
- * Get FaasReactClient instance
1010
+ * Get a registered FaasReactClient instance.
1011
+ *
1012
+ * When `host` is omitted, the first registered client is returned. If no client
1013
+ * has been created yet, a default client is initialized automatically.
985
1014
  *
986
- * @param host {string} empty string for default host
987
- * @returns {FaasReactClientInstance}
1015
+ * @param host - Registered base URL to look up. Omit it to use the default client.
1016
+ * @returns Registered or newly created FaasReactClient instance.
988
1017
  *
989
1018
  * @example
990
1019
  * ```ts
1020
+ * import { getClient } from '@faasjs/react'
1021
+ *
991
1022
  * getClient()
992
- * // or
993
- * getClient('another-host')
1023
+ * getClient('http://localhost:8080/api/')
994
1024
  * ```
995
1025
  */
996
1026
  function getClient(host) {
@@ -1051,23 +1081,25 @@ var ErrorBoundary = class extends Component {
1051
1081
  * Custom hook that returns a stateful value and a ref to that value.
1052
1082
  *
1053
1083
  * @template T - The type of the value.
1054
- * @param {T} initialValue - The initial value of the state.
1055
- * @returns {[T, (value: T) => void, RefObject<T>]} - The stateful value, a function to set the value, and a ref to the value.
1084
+ * @param initialValue - Initial state value. When omitted, state starts as `null`.
1085
+ * @returns Tuple containing the current state, the state setter, and a ref that always points at the latest state.
1056
1086
  *
1057
1087
  * @example
1058
1088
  * ```tsx
1059
1089
  * import { useStateRef } from '@faasjs/react'
1060
1090
  *
1061
1091
  * function MyComponent() {
1062
- * const [value, setValue, ref] = useStateRef(0)
1063
- *
1064
- * return (
1065
- * <div>
1066
- * <p>Value: {value}</p>
1067
- * <button onClick={() => setValue(value + 1)}>Increment</button>
1068
- * <button onClick={() => console.log(ref.current)}>Submit</button>
1069
- * </div>
1070
- * )
1092
+ * const [value, setValue, ref] = useStateRef(0)
1093
+ *
1094
+ * return (
1095
+ * <div>
1096
+ * <p>Value: {value}</p>
1097
+ * <button onClick={() => setValue(value + 1)}>Increment</button>
1098
+ * <button onClick={() => console.log(ref.current)}>Submit</button>
1099
+ * </div>
1100
+ * )
1101
+ * }
1102
+ * ```
1071
1103
  */
1072
1104
  function useStateRef(initialValue) {
1073
1105
  const [state, setState] = useState(initialValue ?? null);
@@ -1084,15 +1116,16 @@ function useStateRef(initialValue) {
1084
1116
  //#endregion
1085
1117
  //#region src/splittingState.tsx
1086
1118
  /**
1087
- * A hook that initializes and splits state variables and their corresponding setters.
1119
+ * Create local state entries and matching setters for each key in an object.
1088
1120
  *
1089
1121
  * @template T - A generic type that extends a record with string keys and any values.
1090
- * @param {T} initialStates - An object containing the initial states.
1122
+ * @param initialStates - Object whose keys become state values and `setXxx` setters.
1123
+ * @returns Object containing the original keys plus generated setter functions.
1091
1124
  *
1092
1125
  * @example
1093
1126
  * ```tsx
1094
1127
  * function Counter() {
1095
- * const { count, setCount, name, setName } = useSplittingState({ count: 0, name: 'John' });
1128
+ * const { count, setCount, name, setName } = useSplittingState({ count: 0, name: 'John' })
1096
1129
  *
1097
1130
  * return <>{name}: {count}</>
1098
1131
  * }
@@ -1345,34 +1378,30 @@ async function validValues(rules, items, values, lang) {
1345
1378
  //#region src/Form/Footer.tsx
1346
1379
  function FormFooter() {
1347
1380
  const { submitting, setSubmitting, onSubmit, valuesRef, Elements, items, setErrors, lang, rules } = useFormContext();
1348
- const handleSubmit = useCallback(async () => {
1349
- setSubmitting(true);
1350
- setErrors({});
1351
- const errors = await validValues(rules, items, valuesRef.current, lang);
1352
- if (Object.keys(errors).length) {
1353
- setErrors(errors);
1354
- setSubmitting(false);
1355
- return;
1356
- }
1357
- onSubmit(valuesRef.current).finally(() => setSubmitting(false));
1358
- }, [
1359
- setSubmitting,
1360
- setErrors,
1361
- rules,
1362
- items,
1363
- lang,
1364
- onSubmit
1365
- ]);
1366
- return useMemo(() => /* @__PURE__ */ jsx(Elements.Button, {
1381
+ const Button = Elements.Button;
1382
+ return /* @__PURE__ */ jsx(Button, {
1367
1383
  submitting,
1368
- submit: handleSubmit,
1384
+ submit: useCallback(async () => {
1385
+ setSubmitting(true);
1386
+ setErrors({});
1387
+ const errors = await validValues(rules, items, valuesRef.current, lang);
1388
+ if (Object.keys(errors).length) {
1389
+ setErrors(errors);
1390
+ setSubmitting(false);
1391
+ return;
1392
+ }
1393
+ onSubmit(valuesRef.current).finally(() => setSubmitting(false));
1394
+ }, [
1395
+ setSubmitting,
1396
+ setErrors,
1397
+ rules,
1398
+ items,
1399
+ valuesRef,
1400
+ lang,
1401
+ onSubmit
1402
+ ]),
1369
1403
  children: lang.submit
1370
- }), [
1371
- submitting,
1372
- handleSubmit,
1373
- lang.submit,
1374
- Elements.Button
1375
- ]);
1404
+ });
1376
1405
  }
1377
1406
  FormFooter.displayName = "FormFooter";
1378
1407
  //#endregion
@@ -1391,32 +1420,29 @@ function mergeValues(items, defaultValues = {}) {
1391
1420
  return values;
1392
1421
  }
1393
1422
  /**
1394
- * FormContainer component is a wrapper that provides context and state management for form elements.
1395
- * It initializes form states such as values, errors, submitting status, elements, language, and rules.
1423
+ * Render a form with context, default elements, and validation state.
1424
+ *
1425
+ * `FormContainer` merges provided elements, language strings, and rules with
1426
+ * the package defaults, then exposes them through form context.
1396
1427
  *
1397
1428
  * @template Values - The type of form values, defaults to Record<string, any>.
1398
1429
  * @template FormElements - The type of form elements, defaults to FormElementTypes.
1399
1430
  * @template Rules - The type of form rules, defaults to FormDefaultRules.
1400
- *
1401
- * @param {FormProps<Values, FormElements, Rules>} props - The properties for the FormContainer component.
1402
- * @param {Values} props.defaultValues - The default values for the form fields.
1403
- * @param {FormElements} props.Elements - The form elements to be used in the form.
1404
- * @param {Rules} props.rules - The validation rules for the form fields.
1405
- * @param {FormLang} props.lang - The language settings for the form.
1406
- * @param {Partial<FormContextProps>} props - Additional properties for the form context.
1407
- *
1408
- * @returns {JSX.Element} The FormContainer component.
1431
+ * @param props - Form items and optional overrides for defaults, language, rules, and submit behavior.
1432
+ * @returns React form container with shared form context.
1409
1433
  *
1410
1434
  * @example
1411
1435
  * ```tsx
1412
1436
  * import { Form } from '@faasjs/react'
1413
1437
  *
1414
1438
  * function MyForm() {
1415
- * return <Form
1416
- * items={[
1417
- * { name: 'name' },
1418
- * ]}
1419
- * />
1439
+ * return (
1440
+ * <Form
1441
+ * items={[
1442
+ * { name: 'name' },
1443
+ * ]}
1444
+ * />
1445
+ * )
1420
1446
  * }
1421
1447
  * ```
1422
1448
  */
@@ -1473,14 +1499,21 @@ OptionalWrapper.displayName = "OptionalWrapper";
1473
1499
  //#endregion
1474
1500
  //#region src/useFaasStream.tsx
1475
1501
  /**
1476
- * Stream faas server response with React hook
1502
+ * Stream a FaasJS response into React state.
1477
1503
  *
1478
- * @param action {string} action name
1479
- * @param defaultParams {object} initial action params
1480
- * @returns {UseFaasStreamResult}
1504
+ * The hook sends a streaming request, appends decoded text chunks to `data`,
1505
+ * and exposes reload helpers for retrying the same action.
1506
+ *
1507
+ * @param action - Action path to invoke.
1508
+ * @param defaultParams - Params used for the initial request and future reloads.
1509
+ * @param options - Optional hook configuration such as controlled data, debounce, and skip logic.
1510
+ * @returns Streaming request state and helper methods.
1481
1511
  *
1482
1512
  * @example
1483
1513
  * ```tsx
1514
+ * import { useState } from 'react'
1515
+ * import { useFaasStream } from '@faasjs/react'
1516
+ *
1484
1517
  * function Chat() {
1485
1518
  * const [prompt, setPrompt] = useState('')
1486
1519
  * const { data, loading, reload } = useFaasStream('chat', { prompt })
@@ -1621,8 +1654,8 @@ function useFaasStream(action, defaultParams, options = {}) {
1621
1654
  * Hook to store the previous value of a state or prop.
1622
1655
  *
1623
1656
  * @template T - The type of the value.
1624
- * @param {T} value - The current value to be stored.
1625
- * @returns {T | undefined} - The previous value, or undefined if there is no previous value.
1657
+ * @param value - The current value to track.
1658
+ * @returns Previous value from the prior render, or `undefined` on the first render.
1626
1659
  */
1627
1660
  function usePrevious(value) {
1628
1661
  const ref = useRef(void 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/react",
3
- "version": "8.0.0-beta.14",
3
+ "version": "8.0.0-beta.16",
4
4
  "homepage": "https://faasjs.com/doc/react/",
5
5
  "bugs": {
6
6
  "url": "https://github.com/faasjs/faasjs/issues"
@@ -26,16 +26,13 @@
26
26
  "require": "./dist/index.cjs"
27
27
  }
28
28
  },
29
- "scripts": {
30
- "build": "tsdown src/index.ts --config ../../tsdown.config.ts --external react --dts.eager --no-sourcemap --no-dts.sourcemap"
31
- },
32
29
  "devDependencies": {
33
- "@faasjs/types": ">=8.0.0-beta.14",
30
+ "@faasjs/types": ">=8.0.0-beta.16",
34
31
  "@types/react": "^19.0.0",
35
32
  "react": "^19.0.0"
36
33
  },
37
34
  "peerDependencies": {
38
- "@faasjs/types": ">=8.0.0-beta.14"
35
+ "@faasjs/types": ">=8.0.0-beta.16"
39
36
  },
40
37
  "engines": {
41
38
  "node": ">=24.0.0",