@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 +0 -1
- package/dist/index.d.ts +82 -33
- package/dist/index.mjs +180 -31
- package/package.json +5 -3
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
|
|
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="/
|
|
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="/
|
|
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> &
|
|
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: '/
|
|
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('/
|
|
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('/
|
|
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 |
|
|
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 |
|
|
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('/
|
|
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,
|
|
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="/
|
|
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="/
|
|
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: '/
|
|
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: '/
|
|
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('/
|
|
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('/
|
|
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('/
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|