@faasjs/react 8.0.0-beta.7 → 8.0.0-beta.9
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 +11 -3
- package/dist/index.cjs +667 -2
- package/dist/index.d.ts +797 -76
- package/dist/index.mjs +662 -2
- package/package.json +18 -18
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,668 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
-
let _faasjs_browser = require("@faasjs/browser");
|
|
3
2
|
let react = require("react");
|
|
4
3
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
5
4
|
|
|
5
|
+
//#region src/generateId.ts
|
|
6
|
+
/**
|
|
7
|
+
* Generate random id with prefix
|
|
8
|
+
*
|
|
9
|
+
* @param prefix prefix of id
|
|
10
|
+
* @param length length of id without prefix, range is 8 ~ 18, default is 18
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* generateId('prefix-') // prefix-1z3b4c5d6e
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
function generateId(prefix = "", length = 18) {
|
|
18
|
+
if (length < 8 || length > 18) throw new Error("Length must be 8 ~ 18");
|
|
19
|
+
return `${prefix}${Date.now().toString(36).padStart(8, "0")}${Math.random().toString(36).substring(2, length - 6).padEnd(length - 8, "0")}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/browser.ts
|
|
24
|
+
/**
|
|
25
|
+
* Wrapper class for HTTP responses from FaasJS functions.
|
|
26
|
+
*
|
|
27
|
+
* Provides a consistent interface for handling server responses with status code, headers,
|
|
28
|
+
* body, and parsed data. Automatically handles JSON serialization and status code defaults.
|
|
29
|
+
*
|
|
30
|
+
* @template T - The type of the data property for type-safe response handling
|
|
31
|
+
*
|
|
32
|
+
* @property {number} status - The HTTP status code of the response.
|
|
33
|
+
* Defaults to 200 if data or body is provided, 204 if neither is present.
|
|
34
|
+
* @property {ResponseHeaders} headers - The response headers as a key-value object.
|
|
35
|
+
* Empty object if no headers were provided.
|
|
36
|
+
* @property {any} body - The raw response body as a string or object.
|
|
37
|
+
* If data is provided without body, body is automatically set to JSON.stringify(data).
|
|
38
|
+
* @property {T} [data] - The parsed JSON data from the response.
|
|
39
|
+
* Optional property that contains the response payload when JSON is provided.
|
|
40
|
+
*
|
|
41
|
+
* @param {ResponseProps<T>} [props] - Response properties including status, headers, body, and data.
|
|
42
|
+
* All properties are optional with sensible defaults.
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
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-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 ResponseProps for response property type
|
|
139
|
+
* @see ResponseError for error response handling
|
|
140
|
+
* @see FaasBrowserClient.action for method returning Response
|
|
141
|
+
*/
|
|
142
|
+
var Response = class {
|
|
143
|
+
status;
|
|
144
|
+
headers;
|
|
145
|
+
body;
|
|
146
|
+
data;
|
|
147
|
+
constructor(props = {}) {
|
|
148
|
+
this.status = props.status || (props.data || props.body ? 200 : 204);
|
|
149
|
+
this.headers = props.headers || {};
|
|
150
|
+
this.body = props.body;
|
|
151
|
+
if (props.data !== void 0) this.data = props.data;
|
|
152
|
+
if (props.data && !props.body) this.body = JSON.stringify(props.data);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Custom error class for handling HTTP response errors from FaasJS requests.
|
|
157
|
+
*
|
|
158
|
+
* Extends the built-in Error class to provide additional information about failed requests,
|
|
159
|
+
* including HTTP status code, response headers, response body, and the original error.
|
|
160
|
+
*
|
|
161
|
+
* @class ResponseError
|
|
162
|
+
* @extends {Error}
|
|
163
|
+
*
|
|
164
|
+
* @property {number} status - The HTTP status code of the failed response. Defaults to 500 if not provided.
|
|
165
|
+
* @property {ResponseHeaders} headers - The response headers from the failed request.
|
|
166
|
+
* @property {any} body - The response body containing error details or the original error if available.
|
|
167
|
+
* @property {Error} [originalError] - The original Error object if this ResponseError was created from another Error.
|
|
168
|
+
*
|
|
169
|
+
* @param {string | Error | ResponseErrorProps} data - The error message, an Error object, or a ResponseErrorProps object.
|
|
170
|
+
* @param {Omit<ResponseErrorProps, 'message' | 'originalError'>} [options] - Additional options for the error (status, headers, body).
|
|
171
|
+
*
|
|
172
|
+
* @example Basic error with message
|
|
173
|
+
* ```ts
|
|
174
|
+
* throw new ResponseError('User not found')
|
|
175
|
+
* // or inside action method:
|
|
176
|
+
* catch (error) {
|
|
177
|
+
* throw new ResponseError(error.message)
|
|
178
|
+
* }
|
|
179
|
+
* ```
|
|
180
|
+
*
|
|
181
|
+
* @example Error from existing Error
|
|
182
|
+
* ```ts
|
|
183
|
+
* try {
|
|
184
|
+
* await someOperation()
|
|
185
|
+
* } catch (error) {
|
|
186
|
+
* throw new ResponseError(error, {
|
|
187
|
+
* status: 500,
|
|
188
|
+
* headers: { 'X-Error-Type': 'internal' }
|
|
189
|
+
* })
|
|
190
|
+
* }
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @example Error with complete response details
|
|
194
|
+
* ```ts
|
|
195
|
+
* throw new ResponseError({
|
|
196
|
+
* message: 'Validation failed',
|
|
197
|
+
* status: 400,
|
|
198
|
+
* headers: { 'X-Error-Code': 'VALIDATION_ERROR' },
|
|
199
|
+
* body: {
|
|
200
|
+
* error: {
|
|
201
|
+
* message: 'Validation failed',
|
|
202
|
+
* fields: ['email', 'password']
|
|
203
|
+
* }
|
|
204
|
+
* }
|
|
205
|
+
* })
|
|
206
|
+
* ```
|
|
207
|
+
*
|
|
208
|
+
* @example Handling ResponseError in client
|
|
209
|
+
* ```ts
|
|
210
|
+
* try {
|
|
211
|
+
* const response = await client.action('user', { id: 123 })
|
|
212
|
+
* console.log(response.data)
|
|
213
|
+
* } catch (error) {
|
|
214
|
+
* if (error instanceof ResponseError) {
|
|
215
|
+
* console.error(`Request failed: ${error.message}`)
|
|
216
|
+
* console.error(`Status: ${error.status}`)
|
|
217
|
+
* if (error.body) {
|
|
218
|
+
* console.error('Error details:', error.body)
|
|
219
|
+
* }
|
|
220
|
+
* if (error.headers['X-Request-Id']) {
|
|
221
|
+
* console.error('Request ID:', error.headers['X-Request-Id'])
|
|
222
|
+
* }
|
|
223
|
+
* }
|
|
224
|
+
* }
|
|
225
|
+
* ```
|
|
226
|
+
*
|
|
227
|
+
* @example Throwing ResponseError from mock
|
|
228
|
+
* ```ts
|
|
229
|
+
* setMock(async (action, params) => {
|
|
230
|
+
* if (action === 'login') {
|
|
231
|
+
* if (!params.email || !params.password) {
|
|
232
|
+
* throw new ResponseError({
|
|
233
|
+
* message: 'Email and password are required',
|
|
234
|
+
* status: 400,
|
|
235
|
+
* body: { error: 'missing_fields' }
|
|
236
|
+
* })
|
|
237
|
+
* }
|
|
238
|
+
* return { data: { token: 'abc123' } }
|
|
239
|
+
* }
|
|
240
|
+
* })
|
|
241
|
+
* ```
|
|
242
|
+
*
|
|
243
|
+
* @remarks
|
|
244
|
+
* - ResponseError is automatically thrown by the action method when the server returns an error (status >= 400)
|
|
245
|
+
* - The error message from server responses is extracted from body.error.message if available
|
|
246
|
+
* - When created from an Error object, the original error is preserved in the originalError property
|
|
247
|
+
* - The status property defaults to 500 if not explicitly provided
|
|
248
|
+
* - Use instanceof ResponseError to distinguish FaasJS errors from other JavaScript errors
|
|
249
|
+
* - The body property can contain structured error information from the server response
|
|
250
|
+
*
|
|
251
|
+
* @see FaasBrowserClient.action for how ResponseError is thrown in requests
|
|
252
|
+
* @see ResponseProps for the structure of response data
|
|
253
|
+
* @see setMock for mocking errors in tests
|
|
254
|
+
*/
|
|
255
|
+
var ResponseError = class extends Error {
|
|
256
|
+
status;
|
|
257
|
+
headers;
|
|
258
|
+
body;
|
|
259
|
+
originalError;
|
|
260
|
+
constructor(data, options) {
|
|
261
|
+
let props;
|
|
262
|
+
if (typeof data === "string") props = {
|
|
263
|
+
message: data,
|
|
264
|
+
...options
|
|
265
|
+
};
|
|
266
|
+
else if (data instanceof Error || data?.constructor?.name?.includes("Error")) props = {
|
|
267
|
+
message: data.message,
|
|
268
|
+
originalError: data,
|
|
269
|
+
...options
|
|
270
|
+
};
|
|
271
|
+
else props = data;
|
|
272
|
+
super(props.message);
|
|
273
|
+
this.status = props.status || 500;
|
|
274
|
+
this.headers = props.headers || {};
|
|
275
|
+
this.body = props.body || props.originalError || { error: { message: props.message } };
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
let mock = null;
|
|
279
|
+
/**
|
|
280
|
+
* Set global mock handler for testing. Mock affects all FaasBrowserClient instances.
|
|
281
|
+
*
|
|
282
|
+
* @param handler - Mock handler, can be:
|
|
283
|
+
* - MockHandler function: receives (action, params, options) and returns response data
|
|
284
|
+
* - ResponseProps object: static response data
|
|
285
|
+
* - Response instance: pre-configured Response object
|
|
286
|
+
* - null or undefined: clear mock
|
|
287
|
+
*
|
|
288
|
+
* @example Use MockHandler function
|
|
289
|
+
* ```ts
|
|
290
|
+
* setMock(async (action, params, options) => {
|
|
291
|
+
* if (action === 'user') {
|
|
292
|
+
* return { data: { name: 'John' } }
|
|
293
|
+
* }
|
|
294
|
+
* return { status: 404, data: { error: 'Not found' } }
|
|
295
|
+
* })
|
|
296
|
+
*
|
|
297
|
+
* const response = await client.action('user')
|
|
298
|
+
* ```
|
|
299
|
+
*
|
|
300
|
+
* @example Use ResponseProps object
|
|
301
|
+
* ```ts
|
|
302
|
+
* setMock({
|
|
303
|
+
* status: 200,
|
|
304
|
+
* data: { result: 'success' },
|
|
305
|
+
* headers: { 'X-Custom': 'value' }
|
|
306
|
+
* })
|
|
307
|
+
* ```
|
|
308
|
+
*
|
|
309
|
+
* @example Use Response instance
|
|
310
|
+
* ```ts
|
|
311
|
+
* setMock(new Response({
|
|
312
|
+
* status: 200,
|
|
313
|
+
* data: { result: 'success' }
|
|
314
|
+
* }))
|
|
315
|
+
* ```
|
|
316
|
+
*
|
|
317
|
+
* @example Clear mock
|
|
318
|
+
* ```ts
|
|
319
|
+
* setMock(null)
|
|
320
|
+
* // or
|
|
321
|
+
* setMock(undefined)
|
|
322
|
+
* ```
|
|
323
|
+
*
|
|
324
|
+
* @example Handle errors
|
|
325
|
+
* ```ts
|
|
326
|
+
* setMock(async () => {
|
|
327
|
+
* throw new Error('Internal error')
|
|
328
|
+
* })
|
|
329
|
+
* // This will reject with ResponseError
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
function setMock(handler) {
|
|
333
|
+
mock = handler;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Browser client for FaasJS - provides HTTP client functionality for making API requests from web applications.
|
|
337
|
+
*
|
|
338
|
+
* @template PathOrData - Type parameter extending FaasActionUnionType for type-safe requests
|
|
339
|
+
*
|
|
340
|
+
* Features:
|
|
341
|
+
* - Type-safe API requests with TypeScript support
|
|
342
|
+
* - Built-in mock support for testing
|
|
343
|
+
* - Custom request function support
|
|
344
|
+
* - Request/response hooks (beforeRequest)
|
|
345
|
+
* - Automatic error handling with ResponseError
|
|
346
|
+
* - Streaming support for large responses
|
|
347
|
+
* - Multiple instance support with unique IDs
|
|
348
|
+
*
|
|
349
|
+
* @remarks
|
|
350
|
+
* - All requests are POST requests by default
|
|
351
|
+
* - Automatically adds X-FaasJS-Request-Id header for request tracking
|
|
352
|
+
* - baseUrl must end with '/' (will throw Error if not)
|
|
353
|
+
* - Supports global mock via setMock() for testing all instances
|
|
354
|
+
*
|
|
355
|
+
* @example Basic usage
|
|
356
|
+
* ```ts
|
|
357
|
+
* import { FaasBrowserClient } from '@faasjs/react'
|
|
358
|
+
*
|
|
359
|
+
* const client = new FaasBrowserClient('http://localhost:8080/')
|
|
360
|
+
* const response = await client.action('func', { key: 'value' })
|
|
361
|
+
* console.log(response.data)
|
|
362
|
+
* ```
|
|
363
|
+
*
|
|
364
|
+
* @example With custom headers and options
|
|
365
|
+
* ```ts
|
|
366
|
+
* const client = new FaasBrowserClient('https://api.example.com/', {
|
|
367
|
+
* headers: { 'X-API-Key': 'secret' },
|
|
368
|
+
* beforeRequest: async ({ action, params, headers }) => {
|
|
369
|
+
* console.log(`Calling ${action} with params:`, params)
|
|
370
|
+
* }
|
|
371
|
+
* })
|
|
372
|
+
* ```
|
|
373
|
+
*
|
|
374
|
+
* @example Multiple instances
|
|
375
|
+
* ```ts
|
|
376
|
+
* const apiClient = new FaasBrowserClient('https://api.example.com/')
|
|
377
|
+
* const localClient = new FaasBrowserClient('http://localhost:3000/')
|
|
378
|
+
*
|
|
379
|
+
* const apiData = await apiClient.action('users')
|
|
380
|
+
* const localData = await localClient.action('data')
|
|
381
|
+
* ```
|
|
382
|
+
*
|
|
383
|
+
* @example Error handling
|
|
384
|
+
* ```ts
|
|
385
|
+
* const client = new FaasBrowserClient('https://api.example.com/')
|
|
386
|
+
*
|
|
387
|
+
* try {
|
|
388
|
+
* const response = await client.action('user', { id: 123 })
|
|
389
|
+
* console.log(response.data)
|
|
390
|
+
* } catch (error) {
|
|
391
|
+
* if (error instanceof ResponseError) {
|
|
392
|
+
* console.error(`Request failed: ${error.message}`, error.status)
|
|
393
|
+
* } else {
|
|
394
|
+
* console.error('Unexpected error:', error)
|
|
395
|
+
* }
|
|
396
|
+
* }
|
|
397
|
+
* ```
|
|
398
|
+
*
|
|
399
|
+
* @throws {Error} When baseUrl does not end with '/'
|
|
400
|
+
*
|
|
401
|
+
* @see setMock for testing support
|
|
402
|
+
* @see ResponseError for error handling
|
|
403
|
+
*/
|
|
404
|
+
var FaasBrowserClient = class {
|
|
405
|
+
id;
|
|
406
|
+
baseUrl;
|
|
407
|
+
defaultOptions;
|
|
408
|
+
/**
|
|
409
|
+
* Creates a new FaasBrowserClient instance.
|
|
410
|
+
*
|
|
411
|
+
* @param baseUrl - Base URL for all API requests. Must end with '/'. Defaults to '/' for relative requests.
|
|
412
|
+
* @throws {Error} If baseUrl does not end with '/'
|
|
413
|
+
* @param options - Configuration options for the client.
|
|
414
|
+
* Supports default headers, beforeRequest hook, custom request function,
|
|
415
|
+
* baseUrl override, and streaming mode.
|
|
416
|
+
*
|
|
417
|
+
* @example Basic initialization
|
|
418
|
+
* ```ts
|
|
419
|
+
* const client = new FaasBrowserClient('/')
|
|
420
|
+
* ```
|
|
421
|
+
*
|
|
422
|
+
* @example With API endpoint
|
|
423
|
+
* ```ts
|
|
424
|
+
* const client = new FaasBrowserClient('https://api.example.com/')
|
|
425
|
+
* ```
|
|
426
|
+
*
|
|
427
|
+
* @example With custom headers
|
|
428
|
+
* ```ts
|
|
429
|
+
* const client = new FaasBrowserClient('https://api.example.com/', {
|
|
430
|
+
* headers: {
|
|
431
|
+
* 'Authorization': 'Bearer token123',
|
|
432
|
+
* 'X-Custom-Header': 'value'
|
|
433
|
+
* }
|
|
434
|
+
* })
|
|
435
|
+
* ```
|
|
436
|
+
*
|
|
437
|
+
* @example With beforeRequest hook
|
|
438
|
+
* ```ts
|
|
439
|
+
* const client = new FaasBrowserClient('https://api.example.com/', {
|
|
440
|
+
* beforeRequest: async ({ action, params, headers }) => {
|
|
441
|
+
* console.log(`Requesting ${action}`, params)
|
|
442
|
+
* // Modify headers before request
|
|
443
|
+
* headers['X-Timestamp'] = Date.now().toString()
|
|
444
|
+
* }
|
|
445
|
+
* })
|
|
446
|
+
* ```
|
|
447
|
+
*
|
|
448
|
+
* @example With custom request function
|
|
449
|
+
* ```ts
|
|
450
|
+
* import axios from 'axios'
|
|
451
|
+
*
|
|
452
|
+
* const client = new FaasBrowserClient('/', {
|
|
453
|
+
* request: async (url, options) => {
|
|
454
|
+
* const response = await axios.post(url, options.body, {
|
|
455
|
+
* headers: options.headers
|
|
456
|
+
* })
|
|
457
|
+
* return new Response({
|
|
458
|
+
* status: response.status,
|
|
459
|
+
* headers: response.headers,
|
|
460
|
+
* data: response.data
|
|
461
|
+
* })
|
|
462
|
+
* }
|
|
463
|
+
* })
|
|
464
|
+
* ```
|
|
465
|
+
*
|
|
466
|
+
* @throws {Error} When baseUrl does not end with '/'
|
|
467
|
+
*/
|
|
468
|
+
constructor(baseUrl = "/", options = Object.create(null)) {
|
|
469
|
+
if (baseUrl && !baseUrl.endsWith("/")) throw Error("[FaasJS] baseUrl should end with /");
|
|
470
|
+
this.id = `FBC-${generateId()}`;
|
|
471
|
+
this.baseUrl = baseUrl;
|
|
472
|
+
this.defaultOptions = options;
|
|
473
|
+
console.debug(`[FaasJS] Initialize with baseUrl: ${this.baseUrl}`);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Makes a request to a FaasJS function.
|
|
477
|
+
*
|
|
478
|
+
* @template PathOrData - The function path or data type for type safety
|
|
479
|
+
* @param action - The function path to call. Converted to lowercase when constructing the URL.
|
|
480
|
+
* Must be a non-empty string.
|
|
481
|
+
* @param params - The parameters to send to the function. Will be serialized as JSON.
|
|
482
|
+
* Optional if the function accepts no parameters.
|
|
483
|
+
* @param options - Optional request options that override client defaults.
|
|
484
|
+
* Supports headers, beforeRequest hook, custom request function, baseUrl override, and streaming mode.
|
|
485
|
+
*
|
|
486
|
+
* @returns A Promise that resolves to a Response object containing status, headers, body, and data.
|
|
487
|
+
* The data property is typed based on the PathOrData generic parameter.
|
|
488
|
+
*
|
|
489
|
+
* @throws {Error} When action is not provided or is empty
|
|
490
|
+
* @throws {ResponseError} When the server returns an error response (status >= 400 or body.error exists)
|
|
491
|
+
* @throws {NetworkError} When network request fails
|
|
492
|
+
*
|
|
493
|
+
* @remarks
|
|
494
|
+
* - All requests are POST requests by default
|
|
495
|
+
* - Action path is automatically converted to lowercase
|
|
496
|
+
* - A unique request ID is generated for each request and sent in X-FaasJS-Request-Id header
|
|
497
|
+
* - Headers are merged from client defaults and request options (request options take precedence)
|
|
498
|
+
* - If a global mock is set via setMock(), it will be used instead of making real requests
|
|
499
|
+
* - If a custom request function is provided in options, it will be used instead of fetch
|
|
500
|
+
* - When stream option is true, returns the native fetch Response instead of a wrapped Response
|
|
501
|
+
* - Response body is automatically parsed as JSON when possible
|
|
502
|
+
* - Server errors (body.error) are automatically converted to ResponseError
|
|
503
|
+
*
|
|
504
|
+
* @example Basic request
|
|
505
|
+
* ```ts
|
|
506
|
+
* const response = await client.action('user', { id: 123 })
|
|
507
|
+
* console.log(response.data)
|
|
508
|
+
* ```
|
|
509
|
+
*
|
|
510
|
+
* @example With no parameters
|
|
511
|
+
* ```ts
|
|
512
|
+
* const response = await client.action('status')
|
|
513
|
+
* console.log(response.data.status)
|
|
514
|
+
* ```
|
|
515
|
+
*
|
|
516
|
+
* @example With custom options
|
|
517
|
+
* ```ts
|
|
518
|
+
* const response = await client.action('data', {
|
|
519
|
+
* limit: 10,
|
|
520
|
+
* offset: 0
|
|
521
|
+
* }, {
|
|
522
|
+
* headers: { 'X-Custom-Header': 'value' }
|
|
523
|
+
* })
|
|
524
|
+
* ```
|
|
525
|
+
*
|
|
526
|
+
* @example Streaming large response
|
|
527
|
+
* ```ts
|
|
528
|
+
* const response = await client.action('stream', {
|
|
529
|
+
* format: 'json'
|
|
530
|
+
* }, {
|
|
531
|
+
* stream: true
|
|
532
|
+
* })
|
|
533
|
+
* // response is native fetch Response with streaming support
|
|
534
|
+
* const reader = response.body.getReader()
|
|
535
|
+
* ```
|
|
536
|
+
*
|
|
537
|
+
* @example With type safety
|
|
538
|
+
* ```ts
|
|
539
|
+
* interface UserData {
|
|
540
|
+
* id: number
|
|
541
|
+
* name: string
|
|
542
|
+
* email: string
|
|
543
|
+
* }
|
|
544
|
+
*
|
|
545
|
+
* const response = await client.action<{
|
|
546
|
+
* action: 'user'
|
|
547
|
+
* params: { id: number }
|
|
548
|
+
* data: UserData
|
|
549
|
+
* }>('user', { id: 123 })
|
|
550
|
+
* console.log(response.data.name) // TypeScript knows it's a string
|
|
551
|
+
* ```
|
|
552
|
+
*
|
|
553
|
+
* @example Handling errors
|
|
554
|
+
* ```ts
|
|
555
|
+
* try {
|
|
556
|
+
* const response = await client.action('user', { id: 123 })
|
|
557
|
+
* console.log(response.data)
|
|
558
|
+
* } catch (error) {
|
|
559
|
+
* if (error instanceof ResponseError) {
|
|
560
|
+
* console.error(`Server error: ${error.message}`, error.status)
|
|
561
|
+
* if (error.data) console.error('Error details:', error.data)
|
|
562
|
+
* } else {
|
|
563
|
+
* console.error('Network error:', error)
|
|
564
|
+
* }
|
|
565
|
+
* }
|
|
566
|
+
* ```
|
|
567
|
+
*
|
|
568
|
+
* @example Chaining requests
|
|
569
|
+
* ```ts
|
|
570
|
+
* const userId = await client.action('createUser', {
|
|
571
|
+
* name: 'John',
|
|
572
|
+
* email: 'john@example.com'
|
|
573
|
+
* })
|
|
574
|
+
*
|
|
575
|
+
* const profile = await client.action('getProfile', {
|
|
576
|
+
* userId: userId.data.id
|
|
577
|
+
* })
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
async action(action, params, options) {
|
|
581
|
+
if (!action) throw Error("[FaasJS] action required");
|
|
582
|
+
const id = `F-${generateId()}`;
|
|
583
|
+
const url = `${(options?.baseUrl || this.baseUrl) + action.toLowerCase()}?_=${id}`;
|
|
584
|
+
if (!params) params = Object.create(null);
|
|
585
|
+
if (!options) options = Object.create(null);
|
|
586
|
+
const parsedOptions = {
|
|
587
|
+
method: "POST",
|
|
588
|
+
headers: { "Content-Type": "application/json; charset=UTF-8" },
|
|
589
|
+
mode: "cors",
|
|
590
|
+
credentials: "include",
|
|
591
|
+
body: JSON.stringify(params),
|
|
592
|
+
...this.defaultOptions,
|
|
593
|
+
...options
|
|
594
|
+
};
|
|
595
|
+
if (!parsedOptions.headers["X-FaasJS-Request-Id"] && !parsedOptions.headers["x-faasjs-request-id"]) parsedOptions.headers["X-FaasJS-Request-Id"] = id;
|
|
596
|
+
if (parsedOptions.beforeRequest) await parsedOptions.beforeRequest({
|
|
597
|
+
action,
|
|
598
|
+
params,
|
|
599
|
+
options: parsedOptions,
|
|
600
|
+
headers: parsedOptions.headers
|
|
601
|
+
});
|
|
602
|
+
if (mock) {
|
|
603
|
+
console.debug(`[FaasJS] Mock request: ${action} %j`, params);
|
|
604
|
+
if (typeof mock === "function") {
|
|
605
|
+
const response = await mock(action, params, parsedOptions);
|
|
606
|
+
if (response instanceof Error) return Promise.reject(new ResponseError(response));
|
|
607
|
+
if (response instanceof Response) return response;
|
|
608
|
+
return new Response(response || {});
|
|
609
|
+
}
|
|
610
|
+
if (mock instanceof Response) return mock;
|
|
611
|
+
return new Response(mock || {});
|
|
612
|
+
}
|
|
613
|
+
if (parsedOptions.request) return parsedOptions.request(url, parsedOptions);
|
|
614
|
+
if (parsedOptions.stream) return fetch(url, parsedOptions);
|
|
615
|
+
return fetch(url, parsedOptions).then(async (response) => {
|
|
616
|
+
const headers = {};
|
|
617
|
+
for (const values of response.headers) headers[values[0]] = values[1];
|
|
618
|
+
return response.text().then((res) => {
|
|
619
|
+
if (response.status >= 200 && response.status < 300) {
|
|
620
|
+
if (!res) return new Response({
|
|
621
|
+
status: response.status,
|
|
622
|
+
headers
|
|
623
|
+
});
|
|
624
|
+
const body = JSON.parse(res);
|
|
625
|
+
if (body.error?.message) return Promise.reject(new ResponseError({
|
|
626
|
+
message: body.error.message,
|
|
627
|
+
status: response.status,
|
|
628
|
+
headers,
|
|
629
|
+
body
|
|
630
|
+
}));
|
|
631
|
+
return new Response({
|
|
632
|
+
status: response.status,
|
|
633
|
+
headers,
|
|
634
|
+
body,
|
|
635
|
+
data: body.data
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
try {
|
|
639
|
+
const body = JSON.parse(res);
|
|
640
|
+
if (body.error?.message) return Promise.reject(new ResponseError({
|
|
641
|
+
message: body.error.message,
|
|
642
|
+
status: response.status,
|
|
643
|
+
headers,
|
|
644
|
+
body
|
|
645
|
+
}));
|
|
646
|
+
return Promise.reject(new ResponseError({
|
|
647
|
+
message: res,
|
|
648
|
+
status: response.status,
|
|
649
|
+
headers,
|
|
650
|
+
body
|
|
651
|
+
}));
|
|
652
|
+
} catch (_) {
|
|
653
|
+
return Promise.reject(new ResponseError({
|
|
654
|
+
message: res,
|
|
655
|
+
status: response.status,
|
|
656
|
+
headers,
|
|
657
|
+
body: res
|
|
658
|
+
}));
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
//#endregion
|
|
6
666
|
//#region src/equal.ts
|
|
7
667
|
const AsyncFunction = (async () => {}).constructor;
|
|
8
668
|
/**
|
|
@@ -309,7 +969,7 @@ const clients = {};
|
|
|
309
969
|
*/
|
|
310
970
|
function FaasReactClient({ baseUrl, options: clientOptions, onError } = { baseUrl: "/" }) {
|
|
311
971
|
const resolvedBaseUrl = baseUrl ?? "/";
|
|
312
|
-
const client = new
|
|
972
|
+
const client = new FaasBrowserClient(resolvedBaseUrl, clientOptions);
|
|
313
973
|
function withBaseUrl(options) {
|
|
314
974
|
if (options?.baseUrl) return options;
|
|
315
975
|
return {
|
|
@@ -1005,6 +1665,7 @@ function usePrevious(value) {
|
|
|
1005
1665
|
|
|
1006
1666
|
//#endregion
|
|
1007
1667
|
exports.ErrorBoundary = ErrorBoundary;
|
|
1668
|
+
exports.FaasBrowserClient = FaasBrowserClient;
|
|
1008
1669
|
exports.FaasDataWrapper = FaasDataWrapper;
|
|
1009
1670
|
exports.FaasReactClient = FaasReactClient;
|
|
1010
1671
|
exports.Form = FormContainer;
|
|
@@ -1015,10 +1676,14 @@ exports.FormDefaultRules = FormDefaultRules;
|
|
|
1015
1676
|
exports.FormInput = FormInput;
|
|
1016
1677
|
exports.FormItem = FormItem;
|
|
1017
1678
|
exports.OptionalWrapper = OptionalWrapper;
|
|
1679
|
+
exports.Response = Response;
|
|
1680
|
+
exports.ResponseError = ResponseError;
|
|
1018
1681
|
exports.createSplittingContext = createSplittingContext;
|
|
1019
1682
|
exports.equal = equal;
|
|
1020
1683
|
exports.faas = faas;
|
|
1684
|
+
exports.generateId = generateId;
|
|
1021
1685
|
exports.getClient = getClient;
|
|
1686
|
+
exports.setMock = setMock;
|
|
1022
1687
|
exports.useConstant = useConstant;
|
|
1023
1688
|
exports.useEqualCallback = useEqualCallback;
|
|
1024
1689
|
exports.useEqualEffect = useEqualEffect;
|