@faasjs/react 8.0.0-beta.17 → 8.0.0-beta.19
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.cjs +459 -58
- package/dist/index.d.ts +605 -131
- package/dist/index.mjs +459 -58
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -3,14 +3,18 @@ let react = require("react");
|
|
|
3
3
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
4
|
//#region src/generateId.ts
|
|
5
5
|
/**
|
|
6
|
-
* Generate random
|
|
6
|
+
* Generate a random identifier with an optional prefix.
|
|
7
7
|
*
|
|
8
|
-
* @param prefix
|
|
9
|
-
* @param length
|
|
8
|
+
* @param prefix - Prefix prepended to the generated identifier.
|
|
9
|
+
* @param length - Length of the generated identifier excluding `prefix`. Must be between `8` and `18`.
|
|
10
|
+
* @returns Generated identifier string.
|
|
11
|
+
* @throws {Error} When `length` is outside the supported `8` to `18` range.
|
|
10
12
|
*
|
|
11
13
|
* @example
|
|
12
14
|
* ```ts
|
|
13
|
-
* generateId('prefix-')
|
|
15
|
+
* const id = generateId('prefix-')
|
|
16
|
+
*
|
|
17
|
+
* id.startsWith('prefix-') // true
|
|
14
18
|
* ```
|
|
15
19
|
*/
|
|
16
20
|
function generateId(prefix = "", length = 18) {
|
|
@@ -135,14 +139,30 @@ function generateId(prefix = "", length = 18) {
|
|
|
135
139
|
* @see FaasBrowserClient.action for method returning Response
|
|
136
140
|
*/
|
|
137
141
|
var Response = class {
|
|
142
|
+
/**
|
|
143
|
+
* HTTP status code exposed to callers.
|
|
144
|
+
*/
|
|
138
145
|
status;
|
|
146
|
+
/**
|
|
147
|
+
* Response headers keyed by header name.
|
|
148
|
+
*/
|
|
139
149
|
headers;
|
|
150
|
+
/**
|
|
151
|
+
* Raw response body.
|
|
152
|
+
*/
|
|
140
153
|
body;
|
|
154
|
+
/**
|
|
155
|
+
* Parsed response payload when JSON data is available.
|
|
156
|
+
*/
|
|
141
157
|
data;
|
|
142
158
|
/**
|
|
143
159
|
* Create a wrapped response object.
|
|
144
160
|
*
|
|
145
161
|
* @param props - Response properties including status, headers, body, and data.
|
|
162
|
+
* @param props.status - HTTP status code. Defaults to `200` when `data` or `body` exists, otherwise `204`.
|
|
163
|
+
* @param props.headers - Response headers keyed by header name.
|
|
164
|
+
* @param props.body - Raw response body to expose without additional parsing.
|
|
165
|
+
* @param props.data - Parsed response payload to expose on `response.data`.
|
|
146
166
|
* @returns Wrapped response instance.
|
|
147
167
|
*/
|
|
148
168
|
constructor(props = {}) {
|
|
@@ -250,17 +270,22 @@ var Response = class {
|
|
|
250
270
|
* @see setMock for mocking errors in tests
|
|
251
271
|
*/
|
|
252
272
|
var ResponseError = class extends Error {
|
|
273
|
+
/**
|
|
274
|
+
* HTTP status code reported for the failed request.
|
|
275
|
+
*/
|
|
253
276
|
status;
|
|
277
|
+
/**
|
|
278
|
+
* Response headers returned with the error.
|
|
279
|
+
*/
|
|
254
280
|
headers;
|
|
281
|
+
/**
|
|
282
|
+
* Raw error body or fallback error payload.
|
|
283
|
+
*/
|
|
255
284
|
body;
|
|
256
|
-
originalError;
|
|
257
285
|
/**
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* @param data - Error message, Error object, or structured response error props.
|
|
261
|
-
* @param options - Additional options such as status, headers, and body.
|
|
262
|
-
* @returns ResponseError instance.
|
|
286
|
+
* Original error used to construct this instance, when available.
|
|
263
287
|
*/
|
|
288
|
+
originalError;
|
|
264
289
|
constructor(data, options) {
|
|
265
290
|
let props;
|
|
266
291
|
if (typeof data === "string") props = {
|
|
@@ -277,11 +302,12 @@ var ResponseError = class extends Error {
|
|
|
277
302
|
this.status = props.status || 500;
|
|
278
303
|
this.headers = props.headers || {};
|
|
279
304
|
this.body = props.body || props.originalError || { error: { message: props.message } };
|
|
305
|
+
if (props.originalError) this.originalError = props.originalError;
|
|
280
306
|
}
|
|
281
307
|
};
|
|
282
308
|
let mock = null;
|
|
283
309
|
/**
|
|
284
|
-
* Set global mock handler
|
|
310
|
+
* Set the global mock handler used by all {@link FaasBrowserClient} instances.
|
|
285
311
|
*
|
|
286
312
|
* @param handler - Mock handler, can be:
|
|
287
313
|
* - MockHandler function: receives (action, params, options) and returns response data
|
|
@@ -289,24 +315,52 @@ let mock = null;
|
|
|
289
315
|
* - Response instance: pre-configured Response object
|
|
290
316
|
* - null or undefined: clear mock
|
|
291
317
|
*
|
|
318
|
+
* @example Reset in Vitest shared setup
|
|
319
|
+
* ```ts
|
|
320
|
+
* import { afterEach } from 'vitest'
|
|
321
|
+
*
|
|
322
|
+
* afterEach(() => {
|
|
323
|
+
* setMock(null)
|
|
324
|
+
* })
|
|
325
|
+
* ```
|
|
326
|
+
*
|
|
327
|
+
* @example Use ResponseProps object
|
|
328
|
+
* ```ts
|
|
329
|
+
* setMock({
|
|
330
|
+
* data: { name: 'FaasJS' },
|
|
331
|
+
* })
|
|
332
|
+
*
|
|
333
|
+
* setMock({
|
|
334
|
+
* status: 500,
|
|
335
|
+
* data: { message: 'Internal Server Error' },
|
|
336
|
+
* })
|
|
337
|
+
* ```
|
|
338
|
+
*
|
|
292
339
|
* @example Use MockHandler function
|
|
293
340
|
* ```ts
|
|
294
|
-
* setMock(async (action
|
|
295
|
-
* if (action === '
|
|
296
|
-
* return { data: { name: '
|
|
341
|
+
* setMock(async (action) => {
|
|
342
|
+
* if (action === '/pages/users/get') {
|
|
343
|
+
* return { data: { id: 1, name: 'FaasJS' } }
|
|
297
344
|
* }
|
|
298
|
-
*
|
|
345
|
+
*
|
|
346
|
+
* return { status: 404, data: { message: 'Not Found' } }
|
|
299
347
|
* })
|
|
300
348
|
*
|
|
301
|
-
* const response = await client.action('
|
|
349
|
+
* const response = await client.action('/pages/users/get')
|
|
302
350
|
* ```
|
|
303
351
|
*
|
|
304
|
-
* @example
|
|
352
|
+
* @example Branch by action and params
|
|
305
353
|
* ```ts
|
|
306
|
-
* setMock({
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
354
|
+
* setMock(async (action, params) => {
|
|
355
|
+
* if (action === '/pages/users/get' && params?.id === 1) {
|
|
356
|
+
* return { data: { id: 1, name: 'Admin' } }
|
|
357
|
+
* }
|
|
358
|
+
*
|
|
359
|
+
* if (action === '/pages/users/get' && params?.id === 2) {
|
|
360
|
+
* return { data: { id: 2, name: 'Editor' } }
|
|
361
|
+
* }
|
|
362
|
+
*
|
|
363
|
+
* return { status: 404, data: { message: 'User not found' } }
|
|
310
364
|
* })
|
|
311
365
|
* ```
|
|
312
366
|
*
|
|
@@ -318,11 +372,22 @@ let mock = null;
|
|
|
318
372
|
* }))
|
|
319
373
|
* ```
|
|
320
374
|
*
|
|
375
|
+
* @example Streaming response
|
|
376
|
+
* ```ts
|
|
377
|
+
* setMock({
|
|
378
|
+
* body: new ReadableStream({
|
|
379
|
+
* start(controller) {
|
|
380
|
+
* controller.enqueue(new TextEncoder().encode('hello'))
|
|
381
|
+
* controller.enqueue(new TextEncoder().encode(' world'))
|
|
382
|
+
* controller.close()
|
|
383
|
+
* },
|
|
384
|
+
* }),
|
|
385
|
+
* })
|
|
386
|
+
* ```
|
|
387
|
+
*
|
|
321
388
|
* @example Clear mock
|
|
322
389
|
* ```ts
|
|
323
390
|
* setMock(null)
|
|
324
|
-
* // or
|
|
325
|
-
* setMock(undefined)
|
|
326
391
|
* ```
|
|
327
392
|
*
|
|
328
393
|
* @example Handle errors
|
|
@@ -406,14 +471,25 @@ function setMock(handler) {
|
|
|
406
471
|
* @see ResponseError for error handling
|
|
407
472
|
*/
|
|
408
473
|
var FaasBrowserClient = class {
|
|
474
|
+
/**
|
|
475
|
+
* Unique identifier for this client instance.
|
|
476
|
+
*/
|
|
409
477
|
id;
|
|
478
|
+
/**
|
|
479
|
+
* Base URL used to build action request URLs.
|
|
480
|
+
*/
|
|
410
481
|
baseUrl;
|
|
482
|
+
/**
|
|
483
|
+
* Default request options merged into every request.
|
|
484
|
+
*/
|
|
411
485
|
defaultOptions;
|
|
412
486
|
/**
|
|
413
487
|
* Creates a new FaasBrowserClient instance.
|
|
414
488
|
*
|
|
415
489
|
* @param baseUrl - Base URL for all API requests. Must end with `/`. Defaults to `/` for relative requests.
|
|
416
490
|
* @param options - Default request options such as headers, hooks, request override, or stream mode.
|
|
491
|
+
* See {@link Options} for supported request fields such as `headers`, `beforeRequest`,
|
|
492
|
+
* `request`, `baseUrl`, and `stream`.
|
|
417
493
|
*
|
|
418
494
|
* @example Basic initialization
|
|
419
495
|
* ```ts
|
|
@@ -483,9 +559,11 @@ var FaasBrowserClient = class {
|
|
|
483
559
|
* Optional if the function accepts no parameters.
|
|
484
560
|
* @param options - Optional request options that override client defaults.
|
|
485
561
|
* Supports headers, beforeRequest hook, custom request function, baseUrl override, and streaming mode.
|
|
562
|
+
* See {@link Options} for supported request fields such as `headers`, `beforeRequest`,
|
|
563
|
+
* `request`, `baseUrl`, and `stream`.
|
|
486
564
|
*
|
|
487
|
-
* @returns A
|
|
488
|
-
*
|
|
565
|
+
* @returns A promise resolving to the wrapped FaasJS response. When `options.stream`
|
|
566
|
+
* is `true`, the runtime returns the native fetch response so callers can read the stream.
|
|
489
567
|
*
|
|
490
568
|
* @throws {Error} When action is not provided or is empty
|
|
491
569
|
* @throws {ResponseError} When the server returns an error response (status >= 400 or body.error exists)
|
|
@@ -663,16 +741,24 @@ var FaasBrowserClient = class {
|
|
|
663
741
|
/**
|
|
664
742
|
* Call the currently configured FaasReactClient.
|
|
665
743
|
*
|
|
744
|
+
* This helper forwards the request to `getClient`. When the registered
|
|
745
|
+
* client defines `onError`, the hook is invoked before the promise rejects.
|
|
746
|
+
*
|
|
747
|
+
* @template PathOrData - Action path or response data type used for inference.
|
|
748
|
+
*
|
|
666
749
|
* @param action - Action path to invoke.
|
|
667
750
|
* @param params - Parameters sent to the action.
|
|
668
751
|
* @param options - Optional per-request overrides such as headers or base URL.
|
|
752
|
+
* See the request `Options` type for supported fields such as `headers`, `beforeRequest`,
|
|
753
|
+
* `request`, `baseUrl`, and `stream`.
|
|
669
754
|
* @returns Response returned by the active browser client.
|
|
755
|
+
* @throws {ResponseError} When the request fails and the active client does not recover inside `onError`.
|
|
670
756
|
*
|
|
671
757
|
* @example
|
|
672
758
|
* ```ts
|
|
673
759
|
* import { faas } from '@faasjs/react'
|
|
674
760
|
*
|
|
675
|
-
* const response = await faas
|
|
761
|
+
* const response = await faas('posts/get', { id: 1 })
|
|
676
762
|
*
|
|
677
763
|
* console.log(response.data.title)
|
|
678
764
|
* ```
|
|
@@ -699,6 +785,14 @@ const AsyncFunction = (async () => {}).constructor;
|
|
|
699
785
|
* @param a - The first value to compare.
|
|
700
786
|
* @param b - The second value to compare.
|
|
701
787
|
* @returns `true` if the values are deeply equal, `false` otherwise.
|
|
788
|
+
*
|
|
789
|
+
* @example
|
|
790
|
+
* ```ts
|
|
791
|
+
* import { equal } from '@faasjs/react'
|
|
792
|
+
*
|
|
793
|
+
* equal({ page: 1, filters: ['a'] }, { page: 1, filters: ['a'] }) // true
|
|
794
|
+
* equal({ page: 1 }, { page: 2 }) // false
|
|
795
|
+
* ```
|
|
702
796
|
*/
|
|
703
797
|
function equal(a, b) {
|
|
704
798
|
if (a === b) return true;
|
|
@@ -733,6 +827,17 @@ function equal(a, b) {
|
|
|
733
827
|
*
|
|
734
828
|
* @param value - The value to be memoized.
|
|
735
829
|
* @returns The memoized value.
|
|
830
|
+
*
|
|
831
|
+
* @example
|
|
832
|
+
* ```tsx
|
|
833
|
+
* import { useEqualMemoize } from '@faasjs/react'
|
|
834
|
+
*
|
|
835
|
+
* function Filters({ filters }: { filters: Record<string, any> }) {
|
|
836
|
+
* const memoizedFilters = useEqualMemoize(filters)
|
|
837
|
+
*
|
|
838
|
+
* return <pre>{JSON.stringify(memoizedFilters)}</pre>
|
|
839
|
+
* }
|
|
840
|
+
* ```
|
|
736
841
|
*/
|
|
737
842
|
function useEqualMemoize(value) {
|
|
738
843
|
const ref = (0, react.useRef)(value);
|
|
@@ -754,6 +859,19 @@ function useEqualSignal(value) {
|
|
|
754
859
|
* @param callback - The effect callback function to run.
|
|
755
860
|
* @param dependencies - The list of dependencies for the effect.
|
|
756
861
|
* @returns The result of the `useEffect` hook with memoized dependencies.
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* ```tsx
|
|
865
|
+
* import { useEqualEffect } from '@faasjs/react'
|
|
866
|
+
*
|
|
867
|
+
* function Page({ filters }: { filters: Record<string, any> }) {
|
|
868
|
+
* useEqualEffect(() => {
|
|
869
|
+
* console.log('filters changed', filters)
|
|
870
|
+
* }, [filters])
|
|
871
|
+
*
|
|
872
|
+
* return null
|
|
873
|
+
* }
|
|
874
|
+
* ```
|
|
757
875
|
*/
|
|
758
876
|
function useEqualEffect(callback, dependencies) {
|
|
759
877
|
return (0, react.useEffect)(callback, [useEqualSignal(dependencies)]);
|
|
@@ -761,9 +879,22 @@ function useEqualEffect(callback, dependencies) {
|
|
|
761
879
|
/**
|
|
762
880
|
* Custom hook that works like `useMemo` but uses deep comparison on dependencies.
|
|
763
881
|
*
|
|
882
|
+
* @template T - Memoized value type returned by the callback.
|
|
883
|
+
*
|
|
764
884
|
* @param callback - The callback function to run.
|
|
765
885
|
* @param dependencies - The list of dependencies.
|
|
766
886
|
* @returns The result of the `useMemo` hook with memoized dependencies.
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* ```tsx
|
|
890
|
+
* import { useEqualMemo } from '@faasjs/react'
|
|
891
|
+
*
|
|
892
|
+
* function Page({ filters }: { filters: Record<string, any> }) {
|
|
893
|
+
* const queryString = useEqualMemo(() => JSON.stringify(filters), [filters])
|
|
894
|
+
*
|
|
895
|
+
* return <span>{queryString}</span>
|
|
896
|
+
* }
|
|
897
|
+
* ```
|
|
767
898
|
*/
|
|
768
899
|
function useEqualMemo(callback, dependencies) {
|
|
769
900
|
const signal = useEqualSignal(dependencies);
|
|
@@ -776,9 +907,24 @@ function useEqualMemo(callback, dependencies) {
|
|
|
776
907
|
/**
|
|
777
908
|
* Custom hook that works like `useCallback` but uses deep comparison on dependencies.
|
|
778
909
|
*
|
|
910
|
+
* @template T - Callback signature to memoize.
|
|
911
|
+
*
|
|
779
912
|
* @param callback - The callback function to run.
|
|
780
913
|
* @param dependencies - The list of dependencies.
|
|
781
914
|
* @returns The result of the `useCallback` hook with memoized dependencies.
|
|
915
|
+
*
|
|
916
|
+
* @example
|
|
917
|
+
* ```tsx
|
|
918
|
+
* import { useEqualCallback } from '@faasjs/react'
|
|
919
|
+
*
|
|
920
|
+
* function Search({ filters }: { filters: Record<string, any> }) {
|
|
921
|
+
* const handleSubmit = useEqualCallback(() => {
|
|
922
|
+
* console.log(filters)
|
|
923
|
+
* }, [filters])
|
|
924
|
+
*
|
|
925
|
+
* return <button onClick={handleSubmit}>Search</button>
|
|
926
|
+
* }
|
|
927
|
+
* ```
|
|
782
928
|
*/
|
|
783
929
|
function useEqualCallback(callback, dependencies) {
|
|
784
930
|
return (0, react.useCallback)((...args) => callback(...args), [useEqualSignal(dependencies)]);
|
|
@@ -786,6 +932,91 @@ function useEqualCallback(callback, dependencies) {
|
|
|
786
932
|
//#endregion
|
|
787
933
|
//#region src/FaasDataWrapper.tsx
|
|
788
934
|
const fixedForwardRef = react.forwardRef;
|
|
935
|
+
/**
|
|
936
|
+
* Fetch FaasJS data and inject the result into a render prop or child element.
|
|
937
|
+
*
|
|
938
|
+
* The wrapper defers rendering `children` or `render` until the first request
|
|
939
|
+
* completes, then keeps passing the latest request state to the rendered output.
|
|
940
|
+
*
|
|
941
|
+
* @param props - Wrapper props controlling the request and rendered fallback.
|
|
942
|
+
* @param props.render - Render prop that receives the resolved Faas request state.
|
|
943
|
+
* @param props.children - Child element cloned with injected Faas request state.
|
|
944
|
+
* @param props.fallback - Element rendered before the first successful load.
|
|
945
|
+
* @param props.action - Action path to request.
|
|
946
|
+
* @param props.params - Params sent to the action.
|
|
947
|
+
* @param props.onDataChange - Callback invoked when the resolved data value changes.
|
|
948
|
+
* @param props.data - Controlled data value used instead of internal state.
|
|
949
|
+
* @param props.setData - Controlled setter used instead of internal state.
|
|
950
|
+
* @param props.baseUrl - Base URL override used for this wrapper instance.
|
|
951
|
+
*
|
|
952
|
+
* @example
|
|
953
|
+
* ```tsx
|
|
954
|
+
* import { FaasDataWrapper } from '@faasjs/react'
|
|
955
|
+
*
|
|
956
|
+
* type User = {
|
|
957
|
+
* name: string
|
|
958
|
+
* }
|
|
959
|
+
*
|
|
960
|
+
* function UserView(props: {
|
|
961
|
+
* data?: User
|
|
962
|
+
* error?: Error
|
|
963
|
+
* reload?: () => void
|
|
964
|
+
* }) {
|
|
965
|
+
* if (props.error) {
|
|
966
|
+
* return (
|
|
967
|
+
* <div>
|
|
968
|
+
* <p>Failed to load user: {props.error.message}</p>
|
|
969
|
+
* <button type="button" onClick={() => props.reload?.()}>
|
|
970
|
+
* Retry
|
|
971
|
+
* </button>
|
|
972
|
+
* </div>
|
|
973
|
+
* )
|
|
974
|
+
* }
|
|
975
|
+
*
|
|
976
|
+
* return <div>Hello, {props.data?.name}</div>
|
|
977
|
+
* }
|
|
978
|
+
*
|
|
979
|
+
* // Render-prop mode
|
|
980
|
+
* export function UserProfile(props: { id: number }) {
|
|
981
|
+
* return (
|
|
982
|
+
* <FaasDataWrapper<User>
|
|
983
|
+
* action="/pages/users/get"
|
|
984
|
+
* params={{ id: props.id }}
|
|
985
|
+
* fallback={<div>Loading user...</div>}
|
|
986
|
+
* render={({ data, error, reload }) => {
|
|
987
|
+
* if (error) {
|
|
988
|
+
* return (
|
|
989
|
+
* <div>
|
|
990
|
+
* <p>Failed to load user: {error.message}</p>
|
|
991
|
+
* <button type="button" onClick={() => reload()}>
|
|
992
|
+
* Retry
|
|
993
|
+
* </button>
|
|
994
|
+
* </div>
|
|
995
|
+
* )
|
|
996
|
+
* }
|
|
997
|
+
*
|
|
998
|
+
* return <div>Hello, {data.name}</div>
|
|
999
|
+
* }}
|
|
1000
|
+
* />
|
|
1001
|
+
* )
|
|
1002
|
+
* }
|
|
1003
|
+
*
|
|
1004
|
+
* // Children injection mode
|
|
1005
|
+
* export function UserProfileWithChildren(props: { id: number }) {
|
|
1006
|
+
* return (
|
|
1007
|
+
* <FaasDataWrapper<User>
|
|
1008
|
+
* action="/pages/users/get"
|
|
1009
|
+
* params={{ id: props.id }}
|
|
1010
|
+
* fallback={<div>Loading user...</div>}
|
|
1011
|
+
* >
|
|
1012
|
+
* <UserView />
|
|
1013
|
+
* </FaasDataWrapper>
|
|
1014
|
+
* )
|
|
1015
|
+
* }
|
|
1016
|
+
* ```
|
|
1017
|
+
*
|
|
1018
|
+
* When a ref is provided, it exposes the current Faas request state imperatively.
|
|
1019
|
+
*/
|
|
789
1020
|
const FaasDataWrapper = fixedForwardRef((props, ref) => {
|
|
790
1021
|
const requestOptions = {
|
|
791
1022
|
...props.data !== void 0 ? { data: props.data } : {},
|
|
@@ -817,11 +1048,36 @@ const FaasDataWrapper = fixedForwardRef((props, ref) => {
|
|
|
817
1048
|
});
|
|
818
1049
|
Object.assign(FaasDataWrapper, { displayName: "FaasDataWrapper" });
|
|
819
1050
|
/**
|
|
820
|
-
*
|
|
1051
|
+
* Wrap a component with {@link FaasDataWrapper} and inject Faas request state as props.
|
|
1052
|
+
*
|
|
1053
|
+
* `withFaasData` is most useful for wrapper-style exports or compatibility with
|
|
1054
|
+
* an existing component boundary. For new code, prefer `useFaas` or
|
|
1055
|
+
* `FaasDataWrapper` when they express the request ownership more directly.
|
|
1056
|
+
*
|
|
1057
|
+
* @template PathOrData - Action path or response data type used for inference.
|
|
1058
|
+
* @template TComponentProps - Component props including injected Faas data fields.
|
|
1059
|
+
* @param Component - Component that consumes injected Faas data props.
|
|
1060
|
+
* @param faasProps - Request configuration forwarded to `FaasDataWrapper`.
|
|
1061
|
+
* @returns Component that accepts the original props minus the injected Faas data fields.
|
|
821
1062
|
*
|
|
822
1063
|
* @example
|
|
823
1064
|
* ```tsx
|
|
824
|
-
*
|
|
1065
|
+
* import { withFaasData } from '@faasjs/react'
|
|
1066
|
+
*
|
|
1067
|
+
* const MyComponent = withFaasData(
|
|
1068
|
+
* ({ data, error, reload }) => {
|
|
1069
|
+
* if (error) {
|
|
1070
|
+
* return (
|
|
1071
|
+
* <button type="button" onClick={() => reload()}>
|
|
1072
|
+
* Retry
|
|
1073
|
+
* </button>
|
|
1074
|
+
* )
|
|
1075
|
+
* }
|
|
1076
|
+
*
|
|
1077
|
+
* return <div>{data.name}</div>
|
|
1078
|
+
* },
|
|
1079
|
+
* { action: '/pages/users/get', params: { id: 1 } },
|
|
1080
|
+
* )
|
|
825
1081
|
* ```
|
|
826
1082
|
*/
|
|
827
1083
|
function withFaasData(Component, faasProps) {
|
|
@@ -835,22 +1091,51 @@ function withFaasData(Component, faasProps) {
|
|
|
835
1091
|
/**
|
|
836
1092
|
* Request FaasJS data and keep request state in React state.
|
|
837
1093
|
*
|
|
838
|
-
* `useFaas`
|
|
839
|
-
*
|
|
1094
|
+
* `useFaas` is the default hook for standard FaasJS request-response flows in React.
|
|
1095
|
+
* It sends an initial request unless `skip` is enabled, and returns request state
|
|
1096
|
+
* plus helpers for reloading, updating data, and handling errors.
|
|
1097
|
+
*
|
|
1098
|
+
* @template PathOrData - Action path or response data type used for inference.
|
|
840
1099
|
*
|
|
841
1100
|
* @param action - Action path to invoke.
|
|
842
1101
|
* @param defaultParams - Params used for the initial request and future reloads.
|
|
843
1102
|
* @param options - Optional hook configuration such as controlled data, debounce, and skip logic.
|
|
844
|
-
* @
|
|
1103
|
+
* @param options.params - Request params override used without mutating the hook's stored params state.
|
|
1104
|
+
* @param options.data - Controlled data value used instead of the hook's internal state.
|
|
1105
|
+
* @param options.setData - Controlled setter used instead of the hook's internal `setData`.
|
|
1106
|
+
* @param options.skip - Boolean or predicate that suppresses the automatic request until `reload()` runs.
|
|
1107
|
+
* @param options.debounce - Milliseconds to wait before sending the latest request.
|
|
1108
|
+
* @param options.baseUrl - Base URL override used for this hook instance.
|
|
1109
|
+
* @returns Request state and helper methods described by {@link FaasDataInjection}.
|
|
845
1110
|
*
|
|
846
1111
|
* @example
|
|
847
1112
|
* ```tsx
|
|
848
1113
|
* import { useFaas } from '@faasjs/react'
|
|
849
1114
|
*
|
|
850
|
-
* function
|
|
851
|
-
* const { data } = useFaas
|
|
1115
|
+
* function Profile({ id }: { id: number }) {
|
|
1116
|
+
* const { data, error, loading, reload } = useFaas('/pages/users/get', { id })
|
|
1117
|
+
*
|
|
1118
|
+
* if (loading) return <div>Loading...</div>
|
|
1119
|
+
*
|
|
1120
|
+
* if (error) {
|
|
1121
|
+
* return (
|
|
1122
|
+
* <div>
|
|
1123
|
+
* <div>Load failed: {error.message}</div>
|
|
1124
|
+
* <button type="button" onClick={() => reload()}>
|
|
1125
|
+
* Retry
|
|
1126
|
+
* </button>
|
|
1127
|
+
* </div>
|
|
1128
|
+
* )
|
|
1129
|
+
* }
|
|
852
1130
|
*
|
|
853
|
-
* return
|
|
1131
|
+
* return (
|
|
1132
|
+
* <div>
|
|
1133
|
+
* <span>{data.name}</span>
|
|
1134
|
+
* <button type="button" onClick={() => reload()}>
|
|
1135
|
+
* Refresh
|
|
1136
|
+
* </button>
|
|
1137
|
+
* </div>
|
|
1138
|
+
* )
|
|
854
1139
|
* }
|
|
855
1140
|
* ```
|
|
856
1141
|
*/
|
|
@@ -937,7 +1222,6 @@ function useFaas(action, defaultParams, options = {}) {
|
|
|
937
1222
|
if (skip) setSkip(false);
|
|
938
1223
|
if (params) setParams(params);
|
|
939
1224
|
const reloadCounter = ++reloadCounterRef.current;
|
|
940
|
-
setReloadTimes((prev) => prev + 1);
|
|
941
1225
|
return new Promise((resolve, reject) => {
|
|
942
1226
|
pendingReloadsRef.current.set(reloadCounter, {
|
|
943
1227
|
resolve,
|
|
@@ -975,18 +1259,32 @@ const clients = {};
|
|
|
975
1259
|
* used by helpers such as {@link faas} and {@link useFaas}.
|
|
976
1260
|
*
|
|
977
1261
|
* @param options - Client configuration including base URL, default request options, and error hooks.
|
|
1262
|
+
* @param options.baseUrl - Base URL used to register and route the client instance.
|
|
1263
|
+
* @param options.options - Default browser-client request options forwarded to `FaasBrowserClient`.
|
|
1264
|
+
* @param options.onError - Hook factory used to handle failed `faas` and `useFaas` requests.
|
|
1265
|
+
* See {@link Options} for supported browser-client request fields such as `headers`,
|
|
1266
|
+
* `beforeRequest`, `request`, `baseUrl`, and `stream`.
|
|
978
1267
|
* @returns Registered FaasReactClient instance.
|
|
979
1268
|
*
|
|
980
1269
|
* @example
|
|
981
1270
|
* ```ts
|
|
982
|
-
* import { FaasReactClient } from '@faasjs/react'
|
|
1271
|
+
* import { FaasReactClient, ResponseError } from '@faasjs/react'
|
|
983
1272
|
*
|
|
984
1273
|
* const client = FaasReactClient({
|
|
985
1274
|
* baseUrl: 'http://localhost:8080/api/',
|
|
1275
|
+
* onError: (action, params) => async (res) => {
|
|
1276
|
+
* if (res instanceof ResponseError) {
|
|
1277
|
+
* reportErrorToSentry(res, {
|
|
1278
|
+
* tags: { action },
|
|
1279
|
+
* extra: { params },
|
|
1280
|
+
* })
|
|
1281
|
+
* }
|
|
1282
|
+
* },
|
|
986
1283
|
* })
|
|
987
1284
|
* ```
|
|
988
1285
|
*/
|
|
989
|
-
function FaasReactClient(
|
|
1286
|
+
function FaasReactClient(options = { baseUrl: "/" }) {
|
|
1287
|
+
const { baseUrl, options: clientOptions, onError } = options;
|
|
990
1288
|
const resolvedBaseUrl = baseUrl ?? "/";
|
|
991
1289
|
const client = new FaasBrowserClient(resolvedBaseUrl, clientOptions);
|
|
992
1290
|
function withBaseUrl(options) {
|
|
@@ -1015,16 +1313,28 @@ function FaasReactClient({ baseUrl, options: clientOptions, onError } = { baseUr
|
|
|
1015
1313
|
*
|
|
1016
1314
|
* When `host` is omitted, the first registered client is returned. If no client
|
|
1017
1315
|
* has been created yet, a default client is initialized automatically.
|
|
1316
|
+
* Use `getClient` only for special cases such as multiple Faas clients with
|
|
1317
|
+
* different base URLs. In normal single-client app code, prefer the default
|
|
1318
|
+
* `faas`, `useFaas`, or `FaasReactClient` setup directly.
|
|
1018
1319
|
*
|
|
1019
1320
|
* @param host - Registered base URL to look up. Omit it to use the default client.
|
|
1020
1321
|
* @returns Registered or newly created FaasReactClient instance.
|
|
1021
1322
|
*
|
|
1022
1323
|
* @example
|
|
1023
1324
|
* ```ts
|
|
1024
|
-
* import { getClient } from '@faasjs/react'
|
|
1325
|
+
* import { FaasReactClient, getClient } from '@faasjs/react'
|
|
1326
|
+
*
|
|
1327
|
+
* FaasReactClient({
|
|
1328
|
+
* baseUrl: 'https://service-a.example.com/api/',
|
|
1329
|
+
* })
|
|
1330
|
+
*
|
|
1331
|
+
* FaasReactClient({
|
|
1332
|
+
* baseUrl: 'https://service-b.example.com/api/',
|
|
1333
|
+
* })
|
|
1334
|
+
*
|
|
1335
|
+
* const client = getClient('https://service-b.example.com/api/')
|
|
1025
1336
|
*
|
|
1026
|
-
*
|
|
1027
|
-
* getClient('http://localhost:8080/api/')
|
|
1337
|
+
* await client.faas('/pages/posts/get', { id: 1 })
|
|
1028
1338
|
* ```
|
|
1029
1339
|
*/
|
|
1030
1340
|
function getClient(host) {
|
|
@@ -1039,6 +1349,20 @@ function getClient(host) {
|
|
|
1039
1349
|
//#region src/constant.ts
|
|
1040
1350
|
/**
|
|
1041
1351
|
* Returns a constant value that is created by the given function.
|
|
1352
|
+
*
|
|
1353
|
+
* @template T - Constant value type returned by the initializer.
|
|
1354
|
+
* @param fn - Initializer that runs only once for the current component instance.
|
|
1355
|
+
*
|
|
1356
|
+
* @example
|
|
1357
|
+
* ```tsx
|
|
1358
|
+
* import { useConstant } from '@faasjs/react'
|
|
1359
|
+
*
|
|
1360
|
+
* function Page() {
|
|
1361
|
+
* const requestId = useConstant(() => crypto.randomUUID())
|
|
1362
|
+
*
|
|
1363
|
+
* return <span>{requestId}</span>
|
|
1364
|
+
* }
|
|
1365
|
+
* ```
|
|
1042
1366
|
*/
|
|
1043
1367
|
function useConstant(fn) {
|
|
1044
1368
|
const ref = (0, react.useRef)(null);
|
|
@@ -1047,8 +1371,39 @@ function useConstant(fn) {
|
|
|
1047
1371
|
}
|
|
1048
1372
|
//#endregion
|
|
1049
1373
|
//#region src/ErrorBoundary.tsx
|
|
1374
|
+
/**
|
|
1375
|
+
* React error boundary with an optional custom fallback element.
|
|
1376
|
+
*
|
|
1377
|
+
* The boundary renders its children until a descendant throws. After that it
|
|
1378
|
+
* either clones `errorChildren` with injected error details or renders a simple
|
|
1379
|
+
* built-in fallback.
|
|
1380
|
+
*
|
|
1381
|
+
* @example
|
|
1382
|
+
* ```tsx
|
|
1383
|
+
* import { ErrorBoundary } from '@faasjs/react'
|
|
1384
|
+
*
|
|
1385
|
+
* function Fallback({ errorMessage }: { errorMessage?: string }) {
|
|
1386
|
+
* return <div>{errorMessage}</div>
|
|
1387
|
+
* }
|
|
1388
|
+
*
|
|
1389
|
+
* <ErrorBoundary errorChildren={<Fallback />}>
|
|
1390
|
+
* <DangerousWidget />
|
|
1391
|
+
* </ErrorBoundary>
|
|
1392
|
+
* ```
|
|
1393
|
+
*/
|
|
1050
1394
|
var ErrorBoundary = class extends react.Component {
|
|
1395
|
+
/**
|
|
1396
|
+
* Stable display name used by React DevTools.
|
|
1397
|
+
*/
|
|
1051
1398
|
static displayName = "ErrorBoundary";
|
|
1399
|
+
/**
|
|
1400
|
+
* Create an error boundary with empty error state.
|
|
1401
|
+
*
|
|
1402
|
+
* @param props - Boundary props.
|
|
1403
|
+
* @param props.children - Descendant elements protected by the boundary.
|
|
1404
|
+
* @param props.onError - Callback invoked after a render error is captured.
|
|
1405
|
+
* @param props.errorChildren - Custom fallback element that receives error details.
|
|
1406
|
+
*/
|
|
1052
1407
|
constructor(props) {
|
|
1053
1408
|
super(props);
|
|
1054
1409
|
this.state = {
|
|
@@ -1056,12 +1411,21 @@ var ErrorBoundary = class extends react.Component {
|
|
|
1056
1411
|
info: { componentStack: "" }
|
|
1057
1412
|
};
|
|
1058
1413
|
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Capture rendering errors from descendant components.
|
|
1416
|
+
*
|
|
1417
|
+
* @param error - Caught render error.
|
|
1418
|
+
* @param info - React component stack metadata.
|
|
1419
|
+
*/
|
|
1059
1420
|
componentDidCatch(error, info) {
|
|
1060
1421
|
this.setState({
|
|
1061
1422
|
error,
|
|
1062
1423
|
info
|
|
1063
1424
|
});
|
|
1064
1425
|
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Render children or the configured fallback for the captured error.
|
|
1428
|
+
*/
|
|
1065
1429
|
render() {
|
|
1066
1430
|
const { error, info } = this.state;
|
|
1067
1431
|
const errorMessage = String(error ?? "");
|
|
@@ -1082,7 +1446,14 @@ var ErrorBoundary = class extends react.Component {
|
|
|
1082
1446
|
//#endregion
|
|
1083
1447
|
//#region src/OptionalWrapper.tsx
|
|
1084
1448
|
/**
|
|
1085
|
-
*
|
|
1449
|
+
* Conditionally wrap children with another component.
|
|
1450
|
+
*
|
|
1451
|
+
* @param props - Wrapper condition, wrapper component, and child content.
|
|
1452
|
+
* @param props.condition - When `true`, wrap children with `Wrapper`.
|
|
1453
|
+
* @param props.Wrapper - Component used as the wrapper when the condition passes.
|
|
1454
|
+
* @param props.wrapperProps - Props forwarded to the wrapper component.
|
|
1455
|
+
* @param props.children - Content rendered directly or inside the wrapper.
|
|
1456
|
+
* @returns Wrapped children or the original children when `condition` is false.
|
|
1086
1457
|
*
|
|
1087
1458
|
* @example
|
|
1088
1459
|
* ```tsx
|
|
@@ -1099,7 +1470,8 @@ var ErrorBoundary = class extends react.Component {
|
|
|
1099
1470
|
* )
|
|
1100
1471
|
* ```
|
|
1101
1472
|
*/
|
|
1102
|
-
function OptionalWrapper(
|
|
1473
|
+
function OptionalWrapper(props) {
|
|
1474
|
+
const { condition, Wrapper, wrapperProps, children } = props;
|
|
1103
1475
|
if (condition) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wrapper, {
|
|
1104
1476
|
...wrapperProps,
|
|
1105
1477
|
children
|
|
@@ -1139,9 +1511,15 @@ function useSplittingState(initialStates) {
|
|
|
1139
1511
|
//#endregion
|
|
1140
1512
|
//#region src/splittingContext.tsx
|
|
1141
1513
|
/**
|
|
1142
|
-
*
|
|
1514
|
+
* Create a context whose keys can be consumed independently.
|
|
1515
|
+
*
|
|
1516
|
+
* `createSplittingContext` returns a `Provider` and a `use` hook. Each key in
|
|
1517
|
+
* the provided shape is backed by a separate React context so readers only
|
|
1518
|
+
* subscribe to the values they access.
|
|
1143
1519
|
*
|
|
1144
|
-
* @
|
|
1520
|
+
* @template T - Context value shape exposed by the provider and hook.
|
|
1521
|
+
* @param defaultValue - Default value map or key list used to create split contexts.
|
|
1522
|
+
* @returns Provider and hook helpers for the split context.
|
|
1145
1523
|
*
|
|
1146
1524
|
* @example
|
|
1147
1525
|
* ```tsx
|
|
@@ -1223,30 +1601,42 @@ function createSplittingContext(defaultValue) {
|
|
|
1223
1601
|
/**
|
|
1224
1602
|
* Stream a FaasJS response into React state.
|
|
1225
1603
|
*
|
|
1226
|
-
*
|
|
1227
|
-
*
|
|
1604
|
+
* `useFaasStream` is the default hook for streaming FaasJS responses in React.
|
|
1605
|
+
* It sends a streaming request, appends decoded text chunks to `data`, and
|
|
1606
|
+
* exposes reload helpers for retrying the same action.
|
|
1228
1607
|
*
|
|
1229
1608
|
* @param action - Action path to invoke.
|
|
1230
1609
|
* @param defaultParams - Params used for the initial request and future reloads.
|
|
1231
1610
|
* @param options - Optional hook configuration such as controlled data, debounce, and skip logic.
|
|
1232
|
-
* @
|
|
1611
|
+
* @param options.params - Request params override used without mutating the hook's stored params state.
|
|
1612
|
+
* @param options.data - Controlled stream text used instead of the hook's internal state.
|
|
1613
|
+
* @param options.setData - Controlled setter used instead of the hook's internal `setData`.
|
|
1614
|
+
* @param options.skip - Boolean or predicate that suppresses the automatic request until `reload()` runs.
|
|
1615
|
+
* @param options.debounce - Milliseconds to wait before sending the latest request.
|
|
1616
|
+
* @param options.baseUrl - Base URL override used for this hook instance.
|
|
1617
|
+
* @returns Streaming request state and helper methods described by {@link UseFaasStreamResult}.
|
|
1233
1618
|
*
|
|
1234
1619
|
* @example
|
|
1235
1620
|
* ```tsx
|
|
1236
|
-
* import { useState } from 'react'
|
|
1237
1621
|
* import { useFaasStream } from '@faasjs/react'
|
|
1238
1622
|
*
|
|
1239
|
-
* function Chat() {
|
|
1240
|
-
* const
|
|
1241
|
-
* const { data, loading, reload } = useFaasStream('chat', { prompt })
|
|
1623
|
+
* function Chat({ prompt }: { prompt: string }) {
|
|
1624
|
+
* const { data, error, loading, reload } = useFaasStream('/pages/chat/stream', { prompt })
|
|
1242
1625
|
*
|
|
1243
|
-
* return
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
* <div>
|
|
1248
|
-
*
|
|
1249
|
-
*
|
|
1626
|
+
* if (loading) return <div>Streaming...</div>
|
|
1627
|
+
*
|
|
1628
|
+
* if (error) {
|
|
1629
|
+
* return (
|
|
1630
|
+
* <div>
|
|
1631
|
+
* <div>Stream failed: {error.message}</div>
|
|
1632
|
+
* <button type="button" onClick={() => reload()}>
|
|
1633
|
+
* Retry
|
|
1634
|
+
* </button>
|
|
1635
|
+
* </div>
|
|
1636
|
+
* )
|
|
1637
|
+
* }
|
|
1638
|
+
*
|
|
1639
|
+
* return <pre>{data}</pre>
|
|
1250
1640
|
* }
|
|
1251
1641
|
* ```
|
|
1252
1642
|
*/
|
|
@@ -1378,6 +1768,17 @@ function useFaasStream(action, defaultParams, options = {}) {
|
|
|
1378
1768
|
* @template T - The type of the value.
|
|
1379
1769
|
* @param value - The current value to track.
|
|
1380
1770
|
* @returns Previous value from the prior render, or `undefined` on the first render.
|
|
1771
|
+
*
|
|
1772
|
+
* @example
|
|
1773
|
+
* ```tsx
|
|
1774
|
+
* import { usePrevious } from '@faasjs/react'
|
|
1775
|
+
*
|
|
1776
|
+
* function Counter({ count }: { count: number }) {
|
|
1777
|
+
* const previous = usePrevious(count)
|
|
1778
|
+
*
|
|
1779
|
+
* return <span>{previous} -> {count}</span>
|
|
1780
|
+
* }
|
|
1781
|
+
* ```
|
|
1381
1782
|
*/
|
|
1382
1783
|
function usePrevious(value) {
|
|
1383
1784
|
const ref = (0, react.useRef)(void 0);
|