@faasjs/react 8.0.0-beta.27 → 8.0.0-beta.29
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 +1 -1
- package/dist/index.d.ts +72 -741
- package/dist/index.mjs +40 -551
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component, cloneElement, createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/generate-id/index.ts
|
|
4
4
|
/**
|
|
5
5
|
* Generate a random identifier with an optional prefix.
|
|
6
6
|
*
|
|
@@ -23,7 +23,7 @@ function generateId(prefix = "", length = 18) {
|
|
|
23
23
|
return `${prefix}${Date.now().toString(36).padStart(8, "0")}${Math.random().toString(36).substring(2, length - 6).padEnd(length - 8, "0")}`;
|
|
24
24
|
}
|
|
25
25
|
//#endregion
|
|
26
|
-
//#region src/browser/
|
|
26
|
+
//#region src/browser/response.ts
|
|
27
27
|
/**
|
|
28
28
|
* Wrapper class for HTTP responses from FaasJS functions.
|
|
29
29
|
*
|
|
@@ -31,113 +31,6 @@ function generateId(prefix = "", length = 18) {
|
|
|
31
31
|
* body, and parsed data. Automatically handles JSON serialization and status code defaults.
|
|
32
32
|
*
|
|
33
33
|
* @template T - The type of the data property for type-safe response handling
|
|
34
|
-
*
|
|
35
|
-
* @property {number} status - The HTTP status code of the response.
|
|
36
|
-
* Defaults to 200 if data or body is provided, 204 if neither is present.
|
|
37
|
-
* @property {ResponseHeaders} headers - The response headers as a key-value object.
|
|
38
|
-
* Empty object if no headers were provided.
|
|
39
|
-
* @property {any} body - The raw response body as a string or object.
|
|
40
|
-
* If data is provided without body, body is automatically set to JSON.stringify(data).
|
|
41
|
-
* @property {T} [data] - The parsed JSON data from the response.
|
|
42
|
-
* Optional property that contains the response payload when JSON is provided.
|
|
43
|
-
*
|
|
44
|
-
* Notes:
|
|
45
|
-
* - status defaults to 200 if data or body is present, 204 otherwise
|
|
46
|
-
* - body is automatically populated from data if not explicitly provided
|
|
47
|
-
* - headers defaults to an empty object if not provided
|
|
48
|
-
* - Use generic type parameter T for type-safe data access
|
|
49
|
-
* - Commonly used as the return type from client.action() method
|
|
50
|
-
* - Can be used in mock handlers to return structured responses
|
|
51
|
-
* - The data property is optional and may be undefined for responses without data
|
|
52
|
-
*
|
|
53
|
-
* @example Create successful response with data
|
|
54
|
-
* ```ts
|
|
55
|
-
* const response = new Response({
|
|
56
|
-
* status: 200,
|
|
57
|
-
* data: {
|
|
58
|
-
* id: 123,
|
|
59
|
-
* name: 'John Doe'
|
|
60
|
-
* }
|
|
61
|
-
* })
|
|
62
|
-
* console.log(response.status) // 200
|
|
63
|
-
* console.log(response.data.name) // 'John Doe'
|
|
64
|
-
* ```
|
|
65
|
-
*
|
|
66
|
-
* @example Create response with type safety
|
|
67
|
-
* ```ts
|
|
68
|
-
* interface User {
|
|
69
|
-
* id: number
|
|
70
|
-
* name: string
|
|
71
|
-
* email: string
|
|
72
|
-
* }
|
|
73
|
-
*
|
|
74
|
-
* const response = new Response<User>({
|
|
75
|
-
* data: {
|
|
76
|
-
* id: 123,
|
|
77
|
-
* name: 'John',
|
|
78
|
-
* email: 'john@example.com'
|
|
79
|
-
* }
|
|
80
|
-
* })
|
|
81
|
-
* // TypeScript knows response.data.name is a string
|
|
82
|
-
* ```
|
|
83
|
-
*
|
|
84
|
-
* @example Create response with headers
|
|
85
|
-
* ```ts
|
|
86
|
-
* const response = new Response({
|
|
87
|
-
* status: 201,
|
|
88
|
-
* data: { created: true },
|
|
89
|
-
* headers: {
|
|
90
|
-
* 'Content-Type': 'application/json',
|
|
91
|
-
* 'x-faasjs-request-id': 'req-123',
|
|
92
|
-
* 'X-Cache-Key': 'user-123'
|
|
93
|
-
* }
|
|
94
|
-
* })
|
|
95
|
-
* ```
|
|
96
|
-
*
|
|
97
|
-
* @example Create response with custom body
|
|
98
|
-
* ```ts
|
|
99
|
-
* const response = new Response({
|
|
100
|
-
* status: 200,
|
|
101
|
-
* body: JSON.stringify({ custom: 'format' }),
|
|
102
|
-
* headers: { 'Content-Type': 'application/json' }
|
|
103
|
-
* })
|
|
104
|
-
* ```
|
|
105
|
-
*
|
|
106
|
-
* @example Create empty response (204 No Content)
|
|
107
|
-
* ```ts
|
|
108
|
-
* const response = new Response()
|
|
109
|
-
* // status: 204, headers: {}, body: undefined, data: undefined
|
|
110
|
-
* ```
|
|
111
|
-
*
|
|
112
|
-
* @example Create error response
|
|
113
|
-
* ```ts
|
|
114
|
-
* const response = new Response({
|
|
115
|
-
* status: 404,
|
|
116
|
-
* data: {
|
|
117
|
-
* error: {
|
|
118
|
-
* message: 'User not found',
|
|
119
|
-
* code: 'USER_NOT_FOUND'
|
|
120
|
-
* }
|
|
121
|
-
* }
|
|
122
|
-
* })
|
|
123
|
-
* ```
|
|
124
|
-
*
|
|
125
|
-
* @example Use in mock handler
|
|
126
|
-
* ```ts
|
|
127
|
-
* setMock(async (action, params) => {
|
|
128
|
-
* if (action === 'user') {
|
|
129
|
-
* return new Response({
|
|
130
|
-
* status: 200,
|
|
131
|
-
* data: { id: params.id, name: 'Mock User' }
|
|
132
|
-
* })
|
|
133
|
-
* }
|
|
134
|
-
* return new Response({ status: 404, data: { error: 'Not found' } })
|
|
135
|
-
* })
|
|
136
|
-
* ```
|
|
137
|
-
*
|
|
138
|
-
* @see {@link ResponseProps} for response property type.
|
|
139
|
-
* @see {@link ResponseError} for error response handling.
|
|
140
|
-
* @see {@link FaasBrowserClient.action} for method returning Response.
|
|
141
34
|
*/
|
|
142
35
|
var Response = class {
|
|
143
36
|
/**
|
|
@@ -158,13 +51,6 @@ var Response = class {
|
|
|
158
51
|
data;
|
|
159
52
|
/**
|
|
160
53
|
* Create a wrapped response object.
|
|
161
|
-
*
|
|
162
|
-
* @param {ResponseProps<T>} [props] - Response properties including status, headers, body, and data.
|
|
163
|
-
* @param {number} [props.status] - HTTP status code. Defaults to `200` when `data` or `body` exists, otherwise `204`.
|
|
164
|
-
* @param {ResponseHeaders} [props.headers] - Response headers keyed by header name.
|
|
165
|
-
* @param {any} [props.body] - Raw response body to expose without additional parsing.
|
|
166
|
-
* @param {T} [props.data] - Parsed response payload to expose on `response.data`.
|
|
167
|
-
* @returns {Response<T>} Wrapped response instance.
|
|
168
54
|
*/
|
|
169
55
|
constructor(props = {}) {
|
|
170
56
|
this.status = props.status || (props.data || props.body ? 200 : 204);
|
|
@@ -179,96 +65,6 @@ var Response = class {
|
|
|
179
65
|
*
|
|
180
66
|
* Extends the built-in Error class to provide additional information about failed requests,
|
|
181
67
|
* including HTTP status code, response headers, response body, and the original error.
|
|
182
|
-
*
|
|
183
|
-
* @augments Error
|
|
184
|
-
*
|
|
185
|
-
* @property {number} status - The HTTP status code of the failed response. Defaults to 500 if not provided.
|
|
186
|
-
* @property {ResponseHeaders} headers - The response headers from the failed request.
|
|
187
|
-
* @property {any} body - The response body containing error details or the original error if available.
|
|
188
|
-
* @property {Error} [originalError] - The original Error object if this ResponseError was created from another Error.
|
|
189
|
-
*
|
|
190
|
-
* @example Basic error with message
|
|
191
|
-
* ```ts
|
|
192
|
-
* throw new ResponseError('User not found')
|
|
193
|
-
* // or inside action method:
|
|
194
|
-
* catch (error) {
|
|
195
|
-
* throw new ResponseError(error.message)
|
|
196
|
-
* }
|
|
197
|
-
* ```
|
|
198
|
-
*
|
|
199
|
-
* @example Error from existing Error
|
|
200
|
-
* ```ts
|
|
201
|
-
* try {
|
|
202
|
-
* await someOperation()
|
|
203
|
-
* } catch (error) {
|
|
204
|
-
* throw new ResponseError(error, {
|
|
205
|
-
* status: 500,
|
|
206
|
-
* headers: { 'X-Error-Type': 'internal' }
|
|
207
|
-
* })
|
|
208
|
-
* }
|
|
209
|
-
* ```
|
|
210
|
-
*
|
|
211
|
-
* @example Error with complete response details
|
|
212
|
-
* ```ts
|
|
213
|
-
* throw new ResponseError({
|
|
214
|
-
* message: 'Validation failed',
|
|
215
|
-
* status: 400,
|
|
216
|
-
* headers: { 'X-Error-Code': 'VALIDATION_ERROR' },
|
|
217
|
-
* body: {
|
|
218
|
-
* error: {
|
|
219
|
-
* message: 'Validation failed',
|
|
220
|
-
* fields: ['email', 'password']
|
|
221
|
-
* }
|
|
222
|
-
* }
|
|
223
|
-
* })
|
|
224
|
-
* ```
|
|
225
|
-
*
|
|
226
|
-
* @example Handling ResponseError in client
|
|
227
|
-
* ```ts
|
|
228
|
-
* try {
|
|
229
|
-
* const response = await client.action('user', { id: 123 })
|
|
230
|
-
* console.log(response.data)
|
|
231
|
-
* } catch (error) {
|
|
232
|
-
* if (error instanceof ResponseError) {
|
|
233
|
-
* console.error(`Request failed: ${error.message}`)
|
|
234
|
-
* console.error(`Status: ${error.status}`)
|
|
235
|
-
* if (error.body) {
|
|
236
|
-
* console.error('Error details:', error.body)
|
|
237
|
-
* }
|
|
238
|
-
* if (error.headers['x-faasjs-request-id']) {
|
|
239
|
-
* console.error('Request ID:', error.headers['x-faasjs-request-id'])
|
|
240
|
-
* }
|
|
241
|
-
* }
|
|
242
|
-
* }
|
|
243
|
-
* ```
|
|
244
|
-
*
|
|
245
|
-
* @example Throwing ResponseError from mock
|
|
246
|
-
* ```ts
|
|
247
|
-
* setMock(async (action, params) => {
|
|
248
|
-
* if (action === 'login') {
|
|
249
|
-
* if (!params.email || !params.password) {
|
|
250
|
-
* throw new ResponseError({
|
|
251
|
-
* message: 'Email and password are required',
|
|
252
|
-
* status: 400,
|
|
253
|
-
* body: { error: 'missing_fields' }
|
|
254
|
-
* })
|
|
255
|
-
* }
|
|
256
|
-
* return { data: { token: 'abc123' } }
|
|
257
|
-
* }
|
|
258
|
-
* })
|
|
259
|
-
* ```
|
|
260
|
-
*
|
|
261
|
-
* Notes:
|
|
262
|
-
* - ResponseError is automatically thrown by the action method when the server returns an error (status >= 400)
|
|
263
|
-
* - The error message from server responses is extracted from body.error.message if available
|
|
264
|
-
* - When created from an Error object, the original error is preserved in the originalError property
|
|
265
|
-
* - The status property defaults to 500 if not explicitly provided
|
|
266
|
-
* - Use instanceof ResponseError to distinguish FaasJS errors from other JavaScript errors
|
|
267
|
-
* - The body property can contain structured error information from the server response
|
|
268
|
-
*
|
|
269
|
-
* @see {@link FaasBrowserClient.action} for how ResponseError is thrown in requests.
|
|
270
|
-
* @see {@link ResponseProps} for the structure of response data.
|
|
271
|
-
* @see {@link setMock} for mocking errors in tests.
|
|
272
68
|
*/
|
|
273
69
|
var ResponseError = class extends Error {
|
|
274
70
|
/**
|
|
@@ -306,127 +102,15 @@ var ResponseError = class extends Error {
|
|
|
306
102
|
if (props.originalError) this.originalError = props.originalError;
|
|
307
103
|
}
|
|
308
104
|
};
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/browser/mock.ts
|
|
309
107
|
let mock = null;
|
|
310
108
|
/**
|
|
311
109
|
* Set the global mock handler used by all {@link FaasBrowserClient} instances.
|
|
312
|
-
*
|
|
313
|
-
* @param {MockHandler | ResponseProps | Response | null | undefined} handler - Mock handler, can be:
|
|
314
|
-
* - MockHandler function: receives (action, params, options) and returns response data
|
|
315
|
-
* - ResponseProps object: static response data
|
|
316
|
-
* - Response instance: pre-configured Response object
|
|
317
|
-
* - null or undefined: clear mock
|
|
318
|
-
*
|
|
319
|
-
* @example Reset in Vitest shared setup
|
|
320
|
-
* ```ts
|
|
321
|
-
* import { afterEach } from 'vitest'
|
|
322
|
-
*
|
|
323
|
-
* afterEach(() => {
|
|
324
|
-
* setMock(null)
|
|
325
|
-
* })
|
|
326
|
-
* ```
|
|
327
|
-
*
|
|
328
|
-
* @example Use ResponseProps object
|
|
329
|
-
* ```ts
|
|
330
|
-
* setMock({
|
|
331
|
-
* data: { name: 'FaasJS' },
|
|
332
|
-
* })
|
|
333
|
-
*
|
|
334
|
-
* setMock({
|
|
335
|
-
* status: 500,
|
|
336
|
-
* data: { message: 'Internal Server Error' },
|
|
337
|
-
* })
|
|
338
|
-
* ```
|
|
339
|
-
*
|
|
340
|
-
* @example Use MockHandler function
|
|
341
|
-
* ```ts
|
|
342
|
-
* setMock(async (action) => {
|
|
343
|
-
* if (action === '/pages/users/get') {
|
|
344
|
-
* return { data: { id: 1, name: 'FaasJS' } }
|
|
345
|
-
* }
|
|
346
|
-
*
|
|
347
|
-
* return { status: 404, data: { message: 'Not Found' } }
|
|
348
|
-
* })
|
|
349
|
-
*
|
|
350
|
-
* const response = await client.action('/pages/users/get')
|
|
351
|
-
* ```
|
|
352
|
-
*
|
|
353
|
-
* @example Branch by action and params
|
|
354
|
-
* ```ts
|
|
355
|
-
* setMock(async (action, params) => {
|
|
356
|
-
* if (action === '/pages/users/get' && params?.id === 1) {
|
|
357
|
-
* return { data: { id: 1, name: 'Admin' } }
|
|
358
|
-
* }
|
|
359
|
-
*
|
|
360
|
-
* if (action === '/pages/users/get' && params?.id === 2) {
|
|
361
|
-
* return { data: { id: 2, name: 'Editor' } }
|
|
362
|
-
* }
|
|
363
|
-
*
|
|
364
|
-
* return { status: 404, data: { message: 'User not found' } }
|
|
365
|
-
* })
|
|
366
|
-
* ```
|
|
367
|
-
*
|
|
368
|
-
* @example Use Response instance
|
|
369
|
-
* ```ts
|
|
370
|
-
* setMock(new Response({
|
|
371
|
-
* status: 200,
|
|
372
|
-
* data: { result: 'success' }
|
|
373
|
-
* }))
|
|
374
|
-
* ```
|
|
375
|
-
*
|
|
376
|
-
* @example Streaming response
|
|
377
|
-
* ```ts
|
|
378
|
-
* setMock({
|
|
379
|
-
* body: new ReadableStream({
|
|
380
|
-
* start(controller) {
|
|
381
|
-
* controller.enqueue(new TextEncoder().encode('hello'))
|
|
382
|
-
* controller.enqueue(new TextEncoder().encode(' world'))
|
|
383
|
-
* controller.close()
|
|
384
|
-
* },
|
|
385
|
-
* }),
|
|
386
|
-
* })
|
|
387
|
-
* ```
|
|
388
|
-
*
|
|
389
|
-
* @example Clear mock
|
|
390
|
-
* ```ts
|
|
391
|
-
* setMock(null)
|
|
392
|
-
* ```
|
|
393
|
-
*
|
|
394
|
-
* @example Handle errors
|
|
395
|
-
* ```ts
|
|
396
|
-
* setMock(async () => {
|
|
397
|
-
* throw new Error('Internal error')
|
|
398
|
-
* })
|
|
399
|
-
* // This will reject with ResponseError
|
|
400
|
-
* ```
|
|
401
110
|
*/
|
|
402
111
|
function setMock(handler) {
|
|
403
112
|
mock = handler;
|
|
404
113
|
}
|
|
405
|
-
function buildActionUrl(action, baseUrl, options, requestId) {
|
|
406
|
-
return `${(options?.baseUrl || baseUrl) + action.toLowerCase()}?_=${requestId}`;
|
|
407
|
-
}
|
|
408
|
-
function buildActionOptions(defaultOptions, options, params, requestId) {
|
|
409
|
-
const resolvedOptions = {
|
|
410
|
-
method: "POST",
|
|
411
|
-
headers: { "Content-Type": "application/json; charset=UTF-8" },
|
|
412
|
-
mode: "cors",
|
|
413
|
-
credentials: "include",
|
|
414
|
-
body: JSON.stringify(params),
|
|
415
|
-
...defaultOptions,
|
|
416
|
-
...options || Object.create(null)
|
|
417
|
-
};
|
|
418
|
-
if (!resolvedOptions.headers["X-FaasJS-Request-Id"] && !resolvedOptions.headers["x-faasjs-request-id"]) resolvedOptions.headers["X-FaasJS-Request-Id"] = requestId;
|
|
419
|
-
return resolvedOptions;
|
|
420
|
-
}
|
|
421
|
-
async function runBeforeRequest(action, params, options) {
|
|
422
|
-
if (!options.beforeRequest) return;
|
|
423
|
-
await options.beforeRequest({
|
|
424
|
-
action,
|
|
425
|
-
params,
|
|
426
|
-
options,
|
|
427
|
-
headers: options.headers
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
114
|
function normalizeMockResponse(response) {
|
|
431
115
|
if (response instanceof Error) throw new ResponseError(response);
|
|
432
116
|
if (response instanceof Response) {
|
|
@@ -454,10 +138,36 @@ function normalizeMockResponse(response) {
|
|
|
454
138
|
return new Response(response || {});
|
|
455
139
|
}
|
|
456
140
|
async function resolveMockResponse(action, params, options) {
|
|
457
|
-
console.debug(`[FaasJS] Mock request: ${action} %j`, params);
|
|
458
141
|
if (typeof mock === "function") return normalizeMockResponse(await mock(action, params, options));
|
|
459
142
|
return normalizeMockResponse(mock);
|
|
460
143
|
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/browser/helpers.ts
|
|
146
|
+
function buildActionUrl(action, baseUrl, options, requestId) {
|
|
147
|
+
return `${(options?.baseUrl || baseUrl) + action.toLowerCase()}?_=${requestId}`;
|
|
148
|
+
}
|
|
149
|
+
function buildActionOptions(defaultOptions, options, params, requestId) {
|
|
150
|
+
const resolvedOptions = {
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: { "Content-Type": "application/json; charset=UTF-8" },
|
|
153
|
+
mode: "cors",
|
|
154
|
+
credentials: "include",
|
|
155
|
+
body: JSON.stringify(params),
|
|
156
|
+
...defaultOptions,
|
|
157
|
+
...options || Object.create(null)
|
|
158
|
+
};
|
|
159
|
+
if (!resolvedOptions.headers["X-FaasJS-Request-Id"] && !resolvedOptions.headers["x-faasjs-request-id"]) resolvedOptions.headers["X-FaasJS-Request-Id"] = requestId;
|
|
160
|
+
return resolvedOptions;
|
|
161
|
+
}
|
|
162
|
+
async function runBeforeRequest(action, params, options) {
|
|
163
|
+
if (!options.beforeRequest) return;
|
|
164
|
+
await options.beforeRequest({
|
|
165
|
+
action,
|
|
166
|
+
params,
|
|
167
|
+
options,
|
|
168
|
+
headers: options.headers
|
|
169
|
+
});
|
|
170
|
+
}
|
|
461
171
|
function toResponseHeaders(headers) {
|
|
462
172
|
const responseHeaders = {};
|
|
463
173
|
for (const [key, value] of headers) responseHeaders[key] = value;
|
|
@@ -513,74 +223,10 @@ async function parseFetchResponse(response) {
|
|
|
513
223
|
if (response.status >= 200 && response.status < 300) return parseSuccessfulResponse(response.status, headers, text);
|
|
514
224
|
return parseFailedResponse(response.status, headers, text);
|
|
515
225
|
}
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/browser/client.ts
|
|
516
228
|
/**
|
|
517
229
|
* Browser client for FaasJS - provides HTTP client functionality for making API requests from web applications.
|
|
518
|
-
*
|
|
519
|
-
* @template PathOrData - Type parameter extending FaasActionUnionType for type-safe requests
|
|
520
|
-
*
|
|
521
|
-
* Features:
|
|
522
|
-
* - Type-safe API requests with TypeScript support
|
|
523
|
-
* - Built-in mock support for testing
|
|
524
|
-
* - Custom request function support
|
|
525
|
-
* - Request/response hooks (beforeRequest)
|
|
526
|
-
* - Automatic error handling with ResponseError
|
|
527
|
-
* - Streaming support for large responses
|
|
528
|
-
* - Multiple instance support with unique IDs
|
|
529
|
-
*
|
|
530
|
-
* Notes:
|
|
531
|
-
* - All requests are POST requests by default
|
|
532
|
-
* - Automatically adds X-FaasJS-Request-Id header for request tracking
|
|
533
|
-
* - baseUrl must end with '/' (will throw Error if not)
|
|
534
|
-
* - Supports global mock via setMock() for testing all instances
|
|
535
|
-
*
|
|
536
|
-
* @example Basic usage
|
|
537
|
-
* ```ts
|
|
538
|
-
* import { FaasBrowserClient } from '@faasjs/react'
|
|
539
|
-
*
|
|
540
|
-
* const client = new FaasBrowserClient('http://localhost:8080/')
|
|
541
|
-
* const response = await client.action('func', { key: 'value' })
|
|
542
|
-
* console.log(response.data)
|
|
543
|
-
* ```
|
|
544
|
-
*
|
|
545
|
-
* @example With custom headers and options
|
|
546
|
-
* ```ts
|
|
547
|
-
* const client = new FaasBrowserClient('https://api.example.com/', {
|
|
548
|
-
* headers: { 'X-API-Key': 'secret' },
|
|
549
|
-
* beforeRequest: async ({ action, params, headers }) => {
|
|
550
|
-
* console.log(`Calling ${action} with params:`, params)
|
|
551
|
-
* }
|
|
552
|
-
* })
|
|
553
|
-
* ```
|
|
554
|
-
*
|
|
555
|
-
* @example Multiple instances
|
|
556
|
-
* ```ts
|
|
557
|
-
* const apiClient = new FaasBrowserClient('https://api.example.com/')
|
|
558
|
-
* const localClient = new FaasBrowserClient('http://localhost:3000/')
|
|
559
|
-
*
|
|
560
|
-
* const apiData = await apiClient.action('users')
|
|
561
|
-
* const localData = await localClient.action('data')
|
|
562
|
-
* ```
|
|
563
|
-
*
|
|
564
|
-
* @example Error handling
|
|
565
|
-
* ```ts
|
|
566
|
-
* const client = new FaasBrowserClient('https://api.example.com/')
|
|
567
|
-
*
|
|
568
|
-
* try {
|
|
569
|
-
* const response = await client.action('user', { id: 123 })
|
|
570
|
-
* console.log(response.data)
|
|
571
|
-
* } catch (error) {
|
|
572
|
-
* if (error instanceof ResponseError) {
|
|
573
|
-
* console.error(`Request failed: ${error.message}`, error.status)
|
|
574
|
-
* } else {
|
|
575
|
-
* console.error('Unexpected error:', error)
|
|
576
|
-
* }
|
|
577
|
-
* }
|
|
578
|
-
* ```
|
|
579
|
-
*
|
|
580
|
-
* @throws {Error} When baseUrl does not end with '/'
|
|
581
|
-
*
|
|
582
|
-
* @see {@link setMock} for testing support.
|
|
583
|
-
* @see {@link ResponseError} for error handling.
|
|
584
230
|
*/
|
|
585
231
|
var FaasBrowserClient = class {
|
|
586
232
|
/**
|
|
@@ -597,172 +243,15 @@ var FaasBrowserClient = class {
|
|
|
597
243
|
defaultOptions;
|
|
598
244
|
/**
|
|
599
245
|
* Creates a new FaasBrowserClient instance.
|
|
600
|
-
*
|
|
601
|
-
* @param {BaseUrl} [baseUrl] - Base URL for all API requests. Must end with `/`. Defaults to `/` for relative requests.
|
|
602
|
-
* @param {Options} [options] - Default request options such as headers, hooks, request override, or stream mode.
|
|
603
|
-
* See {@link Options} for supported request fields such as `headers`, `beforeRequest`,
|
|
604
|
-
* `request`, `baseUrl`, and `stream`.
|
|
605
|
-
*
|
|
606
|
-
* @example Basic initialization
|
|
607
|
-
* ```ts
|
|
608
|
-
* const client = new FaasBrowserClient('/')
|
|
609
|
-
* ```
|
|
610
|
-
*
|
|
611
|
-
* @example With API endpoint
|
|
612
|
-
* ```ts
|
|
613
|
-
* const client = new FaasBrowserClient('https://api.example.com/')
|
|
614
|
-
* ```
|
|
615
|
-
*
|
|
616
|
-
* @example With custom headers
|
|
617
|
-
* ```ts
|
|
618
|
-
* const client = new FaasBrowserClient('https://api.example.com/', {
|
|
619
|
-
* headers: {
|
|
620
|
-
* 'Authorization': 'Bearer token123',
|
|
621
|
-
* 'X-Custom-Header': 'value'
|
|
622
|
-
* }
|
|
623
|
-
* })
|
|
624
|
-
* ```
|
|
625
|
-
*
|
|
626
|
-
* @example With beforeRequest hook
|
|
627
|
-
* ```ts
|
|
628
|
-
* const client = new FaasBrowserClient('https://api.example.com/', {
|
|
629
|
-
* beforeRequest: async ({ action, params, headers }) => {
|
|
630
|
-
* console.log(`Requesting ${action}`, params)
|
|
631
|
-
* // Modify headers before request
|
|
632
|
-
* headers['X-Timestamp'] = Date.now().toString()
|
|
633
|
-
* }
|
|
634
|
-
* })
|
|
635
|
-
* ```
|
|
636
|
-
*
|
|
637
|
-
* @example With custom request function
|
|
638
|
-
* ```ts
|
|
639
|
-
* import axios from 'axios'
|
|
640
|
-
*
|
|
641
|
-
* const client = new FaasBrowserClient('/', {
|
|
642
|
-
* request: async (url, options) => {
|
|
643
|
-
* const response = await axios.post(url, options.body, {
|
|
644
|
-
* headers: options.headers
|
|
645
|
-
* })
|
|
646
|
-
* return new Response({
|
|
647
|
-
* status: response.status,
|
|
648
|
-
* headers: response.headers,
|
|
649
|
-
* data: response.data
|
|
650
|
-
* })
|
|
651
|
-
* }
|
|
652
|
-
* })
|
|
653
|
-
* ```
|
|
654
|
-
*
|
|
655
|
-
* @throws {Error} When `baseUrl` does not end with `/`
|
|
656
246
|
*/
|
|
657
247
|
constructor(baseUrl = "/", options = Object.create(null)) {
|
|
658
248
|
if (baseUrl && !baseUrl.endsWith("/")) throw Error("[FaasJS] baseUrl should end with /");
|
|
659
249
|
this.id = `FBC-${generateId()}`;
|
|
660
250
|
this.baseUrl = baseUrl;
|
|
661
251
|
this.defaultOptions = options;
|
|
662
|
-
console.debug(`[FaasJS] Initialize with baseUrl: ${this.baseUrl}`);
|
|
663
252
|
}
|
|
664
253
|
/**
|
|
665
254
|
* Makes a request to a FaasJS function.
|
|
666
|
-
*
|
|
667
|
-
* @template PathOrData - The function path or data type for type safety
|
|
668
|
-
* @param {FaasAction<PathOrData>} action - The function path to call. Converted to lowercase when constructing the URL.
|
|
669
|
-
* Must be a non-empty string.
|
|
670
|
-
* @param {FaasParams<PathOrData>} [params] - The parameters to send to the function. Will be serialized as JSON.
|
|
671
|
-
* Optional if the function accepts no parameters.
|
|
672
|
-
* @param {Options} [options] - Optional request options that override client defaults.
|
|
673
|
-
* Supports headers, beforeRequest hook, custom request function, baseUrl override, and streaming mode.
|
|
674
|
-
* See {@link Options} for supported request fields such as `headers`, `beforeRequest`,
|
|
675
|
-
* `request`, `baseUrl`, and `stream`.
|
|
676
|
-
*
|
|
677
|
-
* @returns {Promise<Response<FaasData<PathOrData>>>} A promise resolving to the wrapped FaasJS response. When `options.stream`
|
|
678
|
-
* is `true`, the runtime returns the native fetch response so callers can read the stream.
|
|
679
|
-
*
|
|
680
|
-
* @throws {Error} When action is not provided or is empty
|
|
681
|
-
* @throws {ResponseError} When the server returns an error response (status >= 400 or body.error exists)
|
|
682
|
-
* @throws {Error} When the request fails before a response is received
|
|
683
|
-
*
|
|
684
|
-
* Notes:
|
|
685
|
-
* - All requests are POST requests by default
|
|
686
|
-
* - Action path is automatically converted to lowercase
|
|
687
|
-
* - A unique request ID is generated for each request and sent in X-FaasJS-Request-Id header
|
|
688
|
-
* - Headers are merged from client defaults and request options (request options take precedence)
|
|
689
|
-
* - If a global mock is set via setMock(), it will be used instead of making real requests
|
|
690
|
-
* - If a custom request function is provided in options, it will be used instead of fetch
|
|
691
|
-
* - When stream option is true, returns the native fetch Response instead of a wrapped Response
|
|
692
|
-
* - Response body is automatically parsed as JSON when possible
|
|
693
|
-
* - Server errors (body.error) are automatically converted to ResponseError
|
|
694
|
-
*
|
|
695
|
-
* @example Basic request
|
|
696
|
-
* ```ts
|
|
697
|
-
* const response = await client.action('user', { id: 123 })
|
|
698
|
-
* console.log(response.data)
|
|
699
|
-
* ```
|
|
700
|
-
*
|
|
701
|
-
* @example With no parameters
|
|
702
|
-
* ```ts
|
|
703
|
-
* const response = await client.action('status')
|
|
704
|
-
* console.log(response.data.status)
|
|
705
|
-
* ```
|
|
706
|
-
*
|
|
707
|
-
* @example With custom options
|
|
708
|
-
* ```ts
|
|
709
|
-
* const response = await client.action('data', {
|
|
710
|
-
* limit: 10,
|
|
711
|
-
* offset: 0
|
|
712
|
-
* }, {
|
|
713
|
-
* headers: { 'X-Custom-Header': 'value' }
|
|
714
|
-
* })
|
|
715
|
-
* ```
|
|
716
|
-
*
|
|
717
|
-
* @example Streaming large response
|
|
718
|
-
* ```ts
|
|
719
|
-
* const response = await client.action('stream', {
|
|
720
|
-
* format: 'json'
|
|
721
|
-
* }, {
|
|
722
|
-
* stream: true
|
|
723
|
-
* })
|
|
724
|
-
* // response is native fetch Response with streaming support
|
|
725
|
-
* const reader = response.body.getReader()
|
|
726
|
-
* ```
|
|
727
|
-
*
|
|
728
|
-
* @example With type safety
|
|
729
|
-
* ```ts
|
|
730
|
-
* interface UserData {
|
|
731
|
-
* id: number
|
|
732
|
-
* name: string
|
|
733
|
-
* email: string
|
|
734
|
-
* }
|
|
735
|
-
*
|
|
736
|
-
* const response = await client.action<UserData>('user', { id: 123 })
|
|
737
|
-
* console.log(response.data.name) // TypeScript knows it's a string
|
|
738
|
-
* ```
|
|
739
|
-
*
|
|
740
|
-
* @example Handling errors
|
|
741
|
-
* ```ts
|
|
742
|
-
* try {
|
|
743
|
-
* const response = await client.action('user', { id: 123 })
|
|
744
|
-
* console.log(response.data)
|
|
745
|
-
* } catch (error) {
|
|
746
|
-
* if (error instanceof ResponseError) {
|
|
747
|
-
* console.error(`Server error: ${error.message}`, error.status)
|
|
748
|
-
* if (error.body) console.error('Error details:', error.body)
|
|
749
|
-
* } else {
|
|
750
|
-
* console.error('Network error:', error)
|
|
751
|
-
* }
|
|
752
|
-
* }
|
|
753
|
-
* ```
|
|
754
|
-
*
|
|
755
|
-
* @example Chaining requests
|
|
756
|
-
* ```ts
|
|
757
|
-
* const userId = await client.action('createUser', {
|
|
758
|
-
* name: 'John',
|
|
759
|
-
* email: 'john@example.com'
|
|
760
|
-
* })
|
|
761
|
-
*
|
|
762
|
-
* const profile = await client.action('getProfile', {
|
|
763
|
-
* userId: userId.data.id
|
|
764
|
-
* })
|
|
765
|
-
* ```
|
|
766
255
|
*/
|
|
767
256
|
async action(action, params, options) {
|
|
768
257
|
if (!action) throw Error("[FaasJS] action required");
|
|
@@ -1262,7 +751,7 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
|
|
|
1262
751
|
if (typeof e?.message === "string" && e.message.toLowerCase().includes("aborted")) return;
|
|
1263
752
|
if (!failedOnceRef.current && typeof e?.message === "string" && e.message.includes("Failed to fetch")) {
|
|
1264
753
|
failedOnceRef.current = true;
|
|
1265
|
-
console.warn(`FaasReactClient: ${e.message} retry...`);
|
|
754
|
+
console.warn(`[FaasJS] FaasReactClient: ${e.message} retry...`);
|
|
1266
755
|
run();
|
|
1267
756
|
return;
|
|
1268
757
|
}
|
|
@@ -1348,8 +837,8 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
|
|
|
1348
837
|
*
|
|
1349
838
|
* @param {FaasAction<PathOrData>} action - Action path to invoke.
|
|
1350
839
|
* @param {FaasParams<PathOrData>} defaultParams - Params used for the initial request and future reloads.
|
|
1351
|
-
* @param {
|
|
1352
|
-
* See the `
|
|
840
|
+
* @param {UseFaasOptions<PathOrData>} [options] - Optional hook configuration such as controlled data, skip logic, debounce timing, polling, and base URL overrides.
|
|
841
|
+
* See the `UseFaasOptions` type for `params`, `data`, `setData`, `skip`, `debounce`, `polling`, and `baseUrl`.
|
|
1353
842
|
* @returns {FaasDataInjection<PathOrData>} Request state and helper methods described by {@link FaasDataInjection}.
|
|
1354
843
|
*
|
|
1355
844
|
* @example
|
|
@@ -1512,13 +1001,13 @@ function FaasReactClient(options = { baseUrl: "/" }) {
|
|
|
1512
1001
|
function getClient(host) {
|
|
1513
1002
|
const client = clients[host || Object.keys(clients)[0]];
|
|
1514
1003
|
if (!client) {
|
|
1515
|
-
console.warn("FaasReactClient is not initialized manually, use default.");
|
|
1004
|
+
console.warn("[FaasJS] FaasReactClient is not initialized manually, use default.");
|
|
1516
1005
|
return FaasReactClient();
|
|
1517
1006
|
}
|
|
1518
1007
|
return client;
|
|
1519
1008
|
}
|
|
1520
1009
|
//#endregion
|
|
1521
|
-
//#region src/
|
|
1010
|
+
//#region src/constants/index.ts
|
|
1522
1011
|
/**
|
|
1523
1012
|
* Returns a constant value that is created by the given function.
|
|
1524
1013
|
*
|
|
@@ -1653,7 +1142,7 @@ function OptionalWrapper(props) {
|
|
|
1653
1142
|
}
|
|
1654
1143
|
OptionalWrapper.displayName = "OptionalWrapper";
|
|
1655
1144
|
//#endregion
|
|
1656
|
-
//#region src/
|
|
1145
|
+
//#region src/splitting-state/index.tsx
|
|
1657
1146
|
/**
|
|
1658
1147
|
* Create local state entries and matching setters for each key in an object.
|
|
1659
1148
|
*
|
|
@@ -1682,7 +1171,7 @@ function useSplittingState(initialStates) {
|
|
|
1682
1171
|
return states;
|
|
1683
1172
|
}
|
|
1684
1173
|
//#endregion
|
|
1685
|
-
//#region src/
|
|
1174
|
+
//#region src/splitting-context/index.tsx
|
|
1686
1175
|
/**
|
|
1687
1176
|
* Create a context whose keys can be consumed independently.
|
|
1688
1177
|
*
|