@faasjs/react 8.0.0-beta.6 → 8.0.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,741 +1,1006 @@
1
- import { FaasBrowserClient } from '@faasjs/browser';
2
- import { useState, useImperativeHandle, cloneElement, forwardRef, useEffect, useMemo, createContext, useRef, useCallback, Component, useContext } from 'react';
3
- import { jsx, jsxs } from 'react/jsx-runtime';
1
+ import { FaasBrowserClient } from "@faasjs/browser";
2
+ import { Component, cloneElement, createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
4
 
5
- // src/client.tsx
6
- var AsyncFunction = (async () => {
7
- }).constructor;
5
+ //#region src/equal.ts
6
+ const AsyncFunction = (async () => {}).constructor;
7
+ /**
8
+ * Compares two values for deep equality.
9
+ *
10
+ * This function checks if two values are deeply equal by comparing their types and contents.
11
+ * It handles various data types including primitives, arrays, dates, regular expressions, functions,
12
+ * maps, sets, and promises.
13
+ *
14
+ * @param a - The first value to compare.
15
+ * @param b - The second value to compare.
16
+ * @returns `true` if the values are deeply equal, `false` otherwise.
17
+ */
8
18
  function equal(a, b) {
9
- if (a === b) return true;
10
- if ((a === null || a === void 0) && (b === null || b === void 0))
11
- return true;
12
- if (typeof a !== typeof b) return false;
13
- if (a === null || a === void 0 || b === null || b === void 0)
14
- return false;
15
- const ctor = a.constructor;
16
- if (ctor !== b.constructor) return false;
17
- switch (ctor) {
18
- case String:
19
- case Boolean:
20
- return a === b;
21
- case Number:
22
- return Number.isNaN(a) && Number.isNaN(b) || a === b;
23
- case Array: {
24
- if (a.length !== b.length) return false;
25
- for (let i = 0; i < a.length; i++) {
26
- if (!equal(a[i], b[i])) return false;
27
- }
28
- return true;
29
- }
30
- case Date:
31
- return a.getTime() === b.getTime();
32
- case RegExp:
33
- case Function:
34
- case AsyncFunction:
35
- return a.toString() === b.toString();
36
- case Map:
37
- case Set:
38
- return equal(Array.from(a), Array.from(b));
39
- case Promise:
40
- return a === b;
41
- case Object: {
42
- for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]))
43
- if (!equal(a[key], b[key])) return false;
44
- return true;
45
- }
46
- default:
47
- throw Error(`Unsupported type: ${ctor}`);
48
- }
19
+ if (a === b) return true;
20
+ if ((a === null || a === void 0) && (b === null || b === void 0)) return true;
21
+ if (typeof a !== typeof b) return false;
22
+ if (a === null || a === void 0 || b === null || b === void 0) return false;
23
+ const ctor = a.constructor;
24
+ if (ctor !== b.constructor) return false;
25
+ switch (ctor) {
26
+ case String:
27
+ case Boolean: return a === b;
28
+ case Number: return Number.isNaN(a) && Number.isNaN(b) || a === b;
29
+ case Array:
30
+ if (a.length !== b.length) return false;
31
+ for (let i = 0; i < a.length; i++) if (!equal(a[i], b[i])) return false;
32
+ return true;
33
+ case Date: return a.getTime() === b.getTime();
34
+ case RegExp:
35
+ case Function:
36
+ case AsyncFunction: return a.toString() === b.toString();
37
+ case Map:
38
+ case Set: return equal(Array.from(a), Array.from(b));
39
+ case Promise: return a === b;
40
+ case Object:
41
+ for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) if (!equal(a[key], b[key])) return false;
42
+ return true;
43
+ default: throw Error(`Unsupported type: ${ctor}`);
44
+ }
49
45
  }
46
+ /**
47
+ * Custom hook that memoizes a value using deep equality comparison.
48
+ *
49
+ * @param value - The value to be memoized.
50
+ * @returns The memoized value.
51
+ */
50
52
  function useEqualMemoize(value) {
51
- const ref = useRef(value);
52
- const signalRef = useRef(0);
53
- if (!equal(value, ref.current)) {
54
- ref.current = value;
55
- signalRef.current += 1;
56
- }
57
- return useMemo(() => ref.current, [signalRef.current]);
53
+ const ref = useRef(value);
54
+ const signalRef = useRef(0);
55
+ if (!equal(value, ref.current)) {
56
+ ref.current = value;
57
+ signalRef.current += 1;
58
+ }
59
+ return useMemo(() => ref.current, [signalRef.current]);
58
60
  }
61
+ /**
62
+ * Custom hook that works like `useEffect` but uses deep comparison on dependencies.
63
+ *
64
+ * @param callback - The effect callback function to run.
65
+ * @param dependencies - The list of dependencies for the effect.
66
+ * @returns The result of the `useEffect` hook with memoized dependencies.
67
+ */
59
68
  function useEqualEffect(callback, dependencies) {
60
- return useEffect(callback, useEqualMemoize(dependencies));
69
+ return useEffect(callback, useEqualMemoize(dependencies));
61
70
  }
71
+ /**
72
+ * Custom hook that works like `useMemo` but uses deep comparison on dependencies.
73
+ *
74
+ * @param callback - The callback function to run.
75
+ * @param dependencies - The list of dependencies.
76
+ * @returns The result of the `useMemo` hook with memoized dependencies.
77
+ */
62
78
  function useEqualMemo(callback, dependencies) {
63
- return useMemo(callback, useEqualMemoize(dependencies));
79
+ return useMemo(callback, useEqualMemoize(dependencies));
64
80
  }
81
+ /**
82
+ * Custom hook that works like `useCallback` but uses deep comparison on dependencies.
83
+ *
84
+ * @param callback - The callback function to run.
85
+ * @param dependencies - The list of dependencies.
86
+ * @returns The result of the `useCallback` hook with memoized dependencies.
87
+ */
65
88
  function useEqualCallback(callback, dependencies) {
66
- return useCallback(
67
- (...args) => callback(...args),
68
- useEqualMemoize(dependencies)
69
- );
89
+ return useCallback((...args) => callback(...args), useEqualMemoize(dependencies));
70
90
  }
71
- var fixedForwardRef = forwardRef;
72
- var FaasDataWrapper = fixedForwardRef(
73
- (props, ref) => {
74
- const request = getClient(props.baseUrl).useFaas(
75
- props.action,
76
- props.params,
77
- {
78
- data: props.data,
79
- setData: props.setData
80
- }
81
- );
82
- const [loaded, setLoaded] = useState(false);
83
- useImperativeHandle(ref, () => request, [request]);
84
- useEqualEffect(() => {
85
- if (!request.loading) setLoaded((prev) => prev === false ? true : prev);
86
- }, [request.loading]);
87
- useEqualEffect(() => {
88
- if (props.onDataChange) props.onDataChange(request);
89
- }, [request.data]);
90
- const child = useEqualMemo(() => {
91
- if (loaded) {
92
- if (props.children) return cloneElement(props.children, request);
93
- if (props.render) return props.render(request);
94
- }
95
- return props.fallback || null;
96
- }, [
97
- loaded,
98
- request.action,
99
- request.params,
100
- request.data,
101
- request.error,
102
- request.loading
103
- ]);
104
- return child;
105
- }
106
- );
107
- Object.assign(FaasDataWrapper, {
108
- displayName: "FaasDataWrapper"
91
+
92
+ //#endregion
93
+ //#region src/FaasDataWrapper.tsx
94
+ const fixedForwardRef = forwardRef;
95
+ const FaasDataWrapper = fixedForwardRef((props, ref) => {
96
+ const requestOptions = {
97
+ ...props.data !== void 0 ? { data: props.data } : {},
98
+ ...props.setData ? { setData: props.setData } : {}
99
+ };
100
+ const request = getClient(props.baseUrl).useFaas(props.action, props.params ?? {}, requestOptions);
101
+ const [loaded, setLoaded] = useState(false);
102
+ useImperativeHandle(ref, () => request, [request]);
103
+ useEqualEffect(() => {
104
+ if (!request.loading) setLoaded((prev) => prev === false ? true : prev);
105
+ }, [request.loading]);
106
+ useEqualEffect(() => {
107
+ if (props.onDataChange) props.onDataChange(request);
108
+ }, [request.data]);
109
+ return useEqualMemo(() => {
110
+ if (loaded) {
111
+ if (props.children) return cloneElement(props.children, request);
112
+ if (props.render) return props.render(request);
113
+ }
114
+ return props.fallback || null;
115
+ }, [
116
+ loaded,
117
+ request.action,
118
+ request.params,
119
+ request.data,
120
+ request.error,
121
+ request.loading
122
+ ]);
109
123
  });
110
- function withFaasData(Component2, faasProps) {
111
- return (props) => /* @__PURE__ */ jsx(FaasDataWrapper, { ...faasProps, children: /* @__PURE__ */ jsx(Component2, { ...props }) });
124
+ Object.assign(FaasDataWrapper, { displayName: "FaasDataWrapper" });
125
+ /**
126
+ * HOC to wrap a component with FaasDataWrapper
127
+ *
128
+ * @example
129
+ * ```tsx
130
+ * const MyComponent = withFaasData(({ data }) => <div>{data.name}</div>, { action: 'test', params: { a: 1 } })
131
+ * ```
132
+ */
133
+ function withFaasData(Component, faasProps) {
134
+ return (props) => /* @__PURE__ */ jsx(FaasDataWrapper, {
135
+ ...faasProps,
136
+ children: /* @__PURE__ */ jsx(Component, { ...props })
137
+ });
112
138
  }
113
139
 
114
- // src/faas.ts
140
+ //#endregion
141
+ //#region src/faas.ts
142
+ /**
143
+ * Request faas server
144
+ *
145
+ * @param action {string} action name
146
+ * @param params {object} action params
147
+ * @returns {Promise<Response<any>>}
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * faas<{ title: string }>('post/get', { id: 1 }).then(res => {
152
+ * console.log(res.data.title)
153
+ * })
154
+ * ```
155
+ */
115
156
  async function faas(action, params, options) {
116
- const client = getClient(options?.baseUrl);
117
- if (client.onError)
118
- return client.browserClient.action(action, params, options).catch(async (res) => {
119
- await client.onError(action, params)(res);
120
- return Promise.reject(res);
121
- });
122
- return client.browserClient.action(action, params, options);
157
+ const client = getClient(options?.baseUrl);
158
+ const onError = client.onError;
159
+ if (onError) return client.browserClient.action(action, params, options).catch(async (res) => {
160
+ await onError(action, params)(res);
161
+ return Promise.reject(res);
162
+ });
163
+ return client.browserClient.action(action, params, options);
123
164
  }
165
+
166
+ //#endregion
167
+ //#region src/useFaas.tsx
168
+ /**
169
+ * Request faas server with React hook
170
+ *
171
+ * @param action {string} action name
172
+ * @param defaultParams {object} initial action params
173
+ * @returns {FaasDataInjection<any>}
174
+ *
175
+ * @example
176
+ * ```tsx
177
+ * function Post ({ id }) {
178
+ * const { data } = useFaas<{ title: string }>('post/get', { id })
179
+ * return <h1>{data.title}</h1>
180
+ * }
181
+ * ```
182
+ */
124
183
  function useFaas(action, defaultParams, options = {}) {
125
- const [loading, setLoading] = useState(true);
126
- const [data, setData] = useState();
127
- const [error, setError] = useState();
128
- const [params, setParams] = useState(defaultParams);
129
- const [reloadTimes, setReloadTimes] = useState(0);
130
- const [fails, setFails] = useState(0);
131
- const [skip, setSkip] = useState(
132
- typeof options.skip === "function" ? options.skip(defaultParams) : options.skip
133
- );
134
- const promiseRef = useRef(null);
135
- const controllerRef = useRef(null);
136
- const pendingReloadsRef = useRef(/* @__PURE__ */ new Map());
137
- const reloadCounterRef = useRef(0);
138
- useEqualEffect(() => {
139
- setSkip(
140
- typeof options.skip === "function" ? options.skip(params) : options.skip
141
- );
142
- }, [typeof options.skip === "function" ? params : options.skip]);
143
- useEqualEffect(() => {
144
- if (!equal(defaultParams, params)) {
145
- setParams(defaultParams);
146
- }
147
- }, [defaultParams]);
148
- useEqualEffect(() => {
149
- if (!action || skip) {
150
- setLoading(false);
151
- return;
152
- }
153
- setLoading(true);
154
- controllerRef.current = new AbortController();
155
- const client = getClient(options.baseUrl);
156
- function send() {
157
- const request = client.faas(
158
- action,
159
- options.params || params,
160
- { signal: controllerRef.current.signal }
161
- );
162
- promiseRef.current = request;
163
- request.then((r) => {
164
- setFails(0);
165
- setError(null);
166
- options.setData ? options.setData(r.data) : setData(r.data);
167
- setLoading(false);
168
- for (const { resolve } of pendingReloadsRef.current.values())
169
- resolve(r.data);
170
- pendingReloadsRef.current.clear();
171
- }).catch(async (e) => {
172
- if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0)
173
- return;
174
- if (!fails && typeof e?.message === "string" && e.message.indexOf("Failed to fetch") >= 0) {
175
- console.warn(`FaasReactClient: ${e.message} retry...`);
176
- setFails(1);
177
- return send();
178
- }
179
- let error2 = e;
180
- if (client.onError)
181
- try {
182
- await client.onError(action, params)(e);
183
- } catch (newError) {
184
- error2 = newError;
185
- }
186
- setError(error2);
187
- setLoading(false);
188
- for (const { reject } of pendingReloadsRef.current.values())
189
- reject(error2);
190
- pendingReloadsRef.current.clear();
191
- return;
192
- });
193
- }
194
- if (options.debounce) {
195
- const timeout = setTimeout(send, options.debounce);
196
- return () => {
197
- clearTimeout(timeout);
198
- controllerRef.current?.abort();
199
- setLoading(false);
200
- };
201
- }
202
- send();
203
- return () => {
204
- controllerRef.current?.abort();
205
- setLoading(false);
206
- };
207
- }, [action, options.params || params, reloadTimes, skip]);
208
- const reload = useEqualCallback(
209
- (params2) => {
210
- if (skip) setSkip(false);
211
- if (params2) setParams(params2);
212
- const reloadCounter = ++reloadCounterRef.current;
213
- setReloadTimes((prev) => prev + 1);
214
- return new Promise((resolve, reject) => {
215
- pendingReloadsRef.current.set(reloadCounter, { resolve, reject });
216
- setReloadTimes((prev) => prev + 1);
217
- });
218
- },
219
- [params, skip]
220
- );
221
- return {
222
- action,
223
- params,
224
- loading,
225
- data: options.data || data,
226
- reloadTimes,
227
- error,
228
- promise: promiseRef.current,
229
- reload,
230
- setData: options.setData || setData,
231
- setLoading,
232
- setPromise: (newPromise) => typeof newPromise === "function" ? newPromise(promiseRef.current) : promiseRef.current = newPromise,
233
- setError
234
- };
184
+ const [loading, setLoading] = useState(true);
185
+ const [data, setData] = useState();
186
+ const [error, setError] = useState();
187
+ const [params, setParams] = useState(defaultParams);
188
+ const [reloadTimes, setReloadTimes] = useState(0);
189
+ const [fails, setFails] = useState(0);
190
+ const [skip, setSkip] = useState(typeof options.skip === "function" ? options.skip(defaultParams) : options.skip);
191
+ const promiseRef = useRef(null);
192
+ const controllerRef = useRef(null);
193
+ const localSetData = setData;
194
+ const pendingReloadsRef = useRef(/* @__PURE__ */ new Map());
195
+ const reloadCounterRef = useRef(0);
196
+ useEqualEffect(() => {
197
+ setSkip(typeof options.skip === "function" ? options.skip(params) : options.skip);
198
+ }, [typeof options.skip === "function" ? params : options.skip]);
199
+ useEqualEffect(() => {
200
+ if (!equal(defaultParams, params)) setParams(defaultParams);
201
+ }, [defaultParams]);
202
+ useEqualEffect(() => {
203
+ if (!action || skip) {
204
+ setLoading(false);
205
+ return;
206
+ }
207
+ setLoading(true);
208
+ const controller = new AbortController();
209
+ controllerRef.current = controller;
210
+ const client = getClient(options.baseUrl);
211
+ const requestParams = options.params ?? params;
212
+ function send() {
213
+ const request = client.faas(action, requestParams, { signal: controller.signal });
214
+ promiseRef.current = request;
215
+ request.then((r) => {
216
+ const nextData = r.data;
217
+ setFails(0);
218
+ setError(null);
219
+ options.setData ? options.setData(nextData) : localSetData(nextData);
220
+ setLoading(false);
221
+ for (const { resolve } of pendingReloadsRef.current.values()) resolve(nextData);
222
+ pendingReloadsRef.current.clear();
223
+ }).catch(async (e) => {
224
+ if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0) return;
225
+ if (!fails && typeof e?.message === "string" && e.message.indexOf("Failed to fetch") >= 0) {
226
+ console.warn(`FaasReactClient: ${e.message} retry...`);
227
+ setFails(1);
228
+ return send();
229
+ }
230
+ let error = e;
231
+ if (client.onError) try {
232
+ await client.onError(action, requestParams)(e);
233
+ } catch (newError) {
234
+ error = newError;
235
+ }
236
+ setError(error);
237
+ setLoading(false);
238
+ for (const { reject } of pendingReloadsRef.current.values()) reject(error);
239
+ pendingReloadsRef.current.clear();
240
+ });
241
+ }
242
+ if (options.debounce) {
243
+ const timeout = setTimeout(send, options.debounce);
244
+ return () => {
245
+ clearTimeout(timeout);
246
+ controllerRef.current?.abort();
247
+ setLoading(false);
248
+ };
249
+ }
250
+ send();
251
+ return () => {
252
+ controllerRef.current?.abort();
253
+ setLoading(false);
254
+ };
255
+ }, [
256
+ action,
257
+ options.params || params,
258
+ reloadTimes,
259
+ skip
260
+ ]);
261
+ const reload = useEqualCallback((params) => {
262
+ if (skip) setSkip(false);
263
+ if (params) setParams(params);
264
+ const reloadCounter = ++reloadCounterRef.current;
265
+ setReloadTimes((prev) => prev + 1);
266
+ return new Promise((resolve, reject) => {
267
+ pendingReloadsRef.current.set(reloadCounter, {
268
+ resolve,
269
+ reject
270
+ });
271
+ setReloadTimes((prev) => prev + 1);
272
+ });
273
+ }, [params, skip]);
274
+ const currentData = options.data ?? data;
275
+ const currentPromise = promiseRef.current ?? Promise.resolve({});
276
+ return {
277
+ action,
278
+ params,
279
+ loading,
280
+ data: currentData,
281
+ reloadTimes,
282
+ error,
283
+ promise: currentPromise,
284
+ reload,
285
+ setData: options.setData ?? localSetData,
286
+ setLoading,
287
+ setPromise: (newPromise) => {
288
+ promiseRef.current = typeof newPromise === "function" ? newPromise(currentPromise) : newPromise;
289
+ },
290
+ setError
291
+ };
235
292
  }
236
- var clients = {};
237
- function FaasReactClient({ baseUrl, options, onError } = {
238
- baseUrl: "/"
239
- }) {
240
- const client = new FaasBrowserClient(baseUrl, options);
241
- const reactClient = {
242
- id: client.id,
243
- faas: async (action, params, options2) => faas(action, params, { baseUrl, ...options2 }),
244
- useFaas: (action, defaultParams, options2) => useFaas(action, defaultParams, { baseUrl, ...options2 }),
245
- FaasDataWrapper: (props) => /* @__PURE__ */ jsx(FaasDataWrapper, { baseUrl, ...props }),
246
- onError,
247
- browserClient: client
248
- };
249
- clients[baseUrl] = reactClient;
250
- return reactClient;
293
+
294
+ //#endregion
295
+ //#region src/client.tsx
296
+ const clients = {};
297
+ /**
298
+ * Before use faas, you should initialize a FaasReactClient.
299
+ *
300
+ * @returns FaasReactClient instance.
301
+ *
302
+ * @example
303
+ * ```ts
304
+ * const client = FaasReactClient({
305
+ * baseUrl: 'localhost:8080/api/'
306
+ * })
307
+ * ```
308
+ */
309
+ function FaasReactClient({ baseUrl, options: clientOptions, onError } = { baseUrl: "/" }) {
310
+ const resolvedBaseUrl = baseUrl ?? "/";
311
+ const client = new FaasBrowserClient(resolvedBaseUrl, clientOptions);
312
+ function withBaseUrl(options) {
313
+ if (options?.baseUrl) return options;
314
+ return {
315
+ ...options ?? {},
316
+ baseUrl: resolvedBaseUrl
317
+ };
318
+ }
319
+ const reactClient = {
320
+ id: client.id,
321
+ faas: async (action, params, requestOptions) => faas(action, params, withBaseUrl(requestOptions)),
322
+ useFaas: (action, defaultParams, requestOptions) => useFaas(action, defaultParams, withBaseUrl(requestOptions)),
323
+ FaasDataWrapper: (props) => /* @__PURE__ */ jsx(FaasDataWrapper, {
324
+ ...props,
325
+ baseUrl: resolvedBaseUrl
326
+ }),
327
+ ...onError ? { onError } : {},
328
+ browserClient: client
329
+ };
330
+ clients[resolvedBaseUrl] = reactClient;
331
+ return reactClient;
251
332
  }
333
+ /**
334
+ * Get FaasReactClient instance
335
+ *
336
+ * @param host {string} empty string for default host
337
+ * @returns {FaasReactClientInstance}
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * getClient()
342
+ * // or
343
+ * getClient('another-host')
344
+ * ```
345
+ */
252
346
  function getClient(host) {
253
- const client = clients[host || Object.keys(clients)[0]];
254
- if (!client) {
255
- console.warn("FaasReactClient is not initialized manually, use default.");
256
- return FaasReactClient();
257
- }
258
- return client;
347
+ const client = clients[host || Object.keys(clients)[0]];
348
+ if (!client) {
349
+ console.warn("FaasReactClient is not initialized manually, use default.");
350
+ return FaasReactClient();
351
+ }
352
+ return client;
259
353
  }
354
+
355
+ //#endregion
356
+ //#region src/constant.ts
357
+ /**
358
+ * Returns a constant value that is created by the given function.
359
+ */
260
360
  function useConstant(fn) {
261
- const ref = useRef(null);
262
- if (!ref.current) {
263
- ref.current = { v: fn() };
264
- }
265
- return ref.current.v;
361
+ const ref = useRef(null);
362
+ if (!ref.current) ref.current = { v: fn() };
363
+ return ref.current.v;
266
364
  }
365
+
366
+ //#endregion
367
+ //#region src/ErrorBoundary.tsx
267
368
  var ErrorBoundary = class extends Component {
268
- static displayName = "ErrorBoundary";
269
- constructor(props) {
270
- super(props);
271
- this.state = {
272
- error: void 0,
273
- info: { componentStack: "" }
274
- };
275
- }
276
- componentDidCatch(error, info) {
277
- this.setState({
278
- error,
279
- info
280
- });
281
- }
282
- render() {
283
- const errorMessage = (this.state.error || "").toString();
284
- const errorDescription = this.state.info?.componentStack ? this.state.info.componentStack : null;
285
- if (this.state.error) {
286
- if (this.props.onError)
287
- this.props.onError(this.state.error, this.state.info);
288
- if (this.props.errorChildren)
289
- return cloneElement(this.props.errorChildren, {
290
- error: this.state.error,
291
- info: this.state.info,
292
- errorMessage,
293
- errorDescription
294
- });
295
- return /* @__PURE__ */ jsxs("div", { children: [
296
- /* @__PURE__ */ jsx("p", { children: errorMessage }),
297
- /* @__PURE__ */ jsx("pre", { children: errorDescription })
298
- ] });
299
- }
300
- return this.props.children;
301
- }
369
+ static displayName = "ErrorBoundary";
370
+ constructor(props) {
371
+ super(props);
372
+ this.state = {
373
+ error: null,
374
+ info: { componentStack: "" }
375
+ };
376
+ }
377
+ componentDidCatch(error, info) {
378
+ this.setState({
379
+ error,
380
+ info
381
+ });
382
+ }
383
+ render() {
384
+ const { error, info } = this.state;
385
+ const errorMessage = String(error ?? "");
386
+ const errorDescription = info.componentStack || void 0;
387
+ if (error) {
388
+ if (this.props.onError) this.props.onError(error, info);
389
+ if (this.props.errorChildren) return cloneElement(this.props.errorChildren, {
390
+ error,
391
+ info,
392
+ errorMessage,
393
+ ...errorDescription ? { errorDescription } : {}
394
+ });
395
+ return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("p", { children: errorMessage }), /* @__PURE__ */ jsx("pre", { children: errorDescription })] });
396
+ }
397
+ return this.props.children ?? null;
398
+ }
302
399
  };
400
+
401
+ //#endregion
402
+ //#region src/useStateRef.ts
403
+ /**
404
+ * Custom hook that returns a stateful value and a ref to that value.
405
+ *
406
+ * @template T - The type of the value.
407
+ * @param {T} initialValue - The initial value of the state.
408
+ * @returns {[T, (value: T) => void, RefObject<T>]} - The stateful value, a function to set the value, and a ref to the value.
409
+ *
410
+ * @example
411
+ * ```tsx
412
+ * import { useStateRef } from '@faasjs/react'
413
+ *
414
+ * function MyComponent() {
415
+ * const [value, setValue, ref] = useStateRef(0)
416
+ *
417
+ * return (
418
+ * <div>
419
+ * <p>Value: {value}</p>
420
+ * <button onClick={() => setValue(value + 1)}>Increment</button>
421
+ * <button onClick={() => console.log(ref.current)}>Submit</button>
422
+ * </div>
423
+ * )
424
+ */
303
425
  function useStateRef(initialValue) {
304
- const [state, setState] = useState(initialValue ?? null);
305
- const ref = useRef(state);
306
- useEffect(() => {
307
- ref.current = state;
308
- }, [state]);
309
- return [state, setState, ref];
426
+ const [state, setState] = useState(initialValue ?? null);
427
+ const ref = useRef(state);
428
+ useEffect(() => {
429
+ ref.current = state;
430
+ }, [state]);
431
+ return [
432
+ state,
433
+ setState,
434
+ ref
435
+ ];
310
436
  }
437
+
438
+ //#endregion
439
+ //#region src/splittingState.tsx
440
+ /**
441
+ * A hook that initializes and splits state variables and their corresponding setters.
442
+ *
443
+ * @template T - A generic type that extends a record with string keys and any values.
444
+ * @param {T} initialStates - An object containing the initial states.
445
+ *
446
+ * @example
447
+ * ```tsx
448
+ * function Counter() {
449
+ * const { count, setCount, name, setName } = useSplittingState({ count: 0, name: 'John' });
450
+ *
451
+ * return <>{name}: {count}</>
452
+ * }
453
+ * ```
454
+ */
311
455
  function useSplittingState(initialStates) {
312
- const states = {};
313
- for (const key of Object.keys(initialStates)) {
314
- const state = useState(initialStates[key]);
315
- Object.assign(states, {
316
- [key]: state[0],
317
- [`set${String(key).charAt(0).toUpperCase()}${String(key).slice(1)}`]: state[1]
318
- });
319
- }
320
- return states;
456
+ const states = {};
457
+ for (const key of Object.keys(initialStates)) {
458
+ const state = useState(initialStates[key]);
459
+ Object.assign(states, {
460
+ [key]: state[0],
461
+ [`set${String(key).charAt(0).toUpperCase()}${String(key).slice(1)}`]: state[1]
462
+ });
463
+ }
464
+ return states;
321
465
  }
466
+
467
+ //#endregion
468
+ //#region src/splittingContext.tsx
469
+ /**
470
+ * Creates a splitting context with the given default value.
471
+ *
472
+ * @param defaultValue The default value of the splitting context.
473
+ *
474
+ * @example
475
+ * ```tsx
476
+ * const { Provider, use } = createSplittingContext<{
477
+ * value: number
478
+ * setValue: React.Dispatch<React.SetStateAction<number>>
479
+ * }>({
480
+ * value: 0,
481
+ * setValue: null,
482
+ * })
483
+ *
484
+ * function ReaderComponent() {
485
+ * const { value } = use()
486
+ *
487
+ * return <div>{value}</div>
488
+ * }
489
+ *
490
+ * function WriterComponent() {
491
+ * const { setValue } = use()
492
+ *
493
+ * return (
494
+ * <button type='button' onClick={() => setValue((p: number) => p + 1)}>
495
+ * Change
496
+ * </button>
497
+ * )
498
+ * }
499
+ *
500
+ * function App() {
501
+ * const [value, setValue] = useState(0)
502
+ *
503
+ * return (
504
+ * <Provider value={{ value, setValue }}>
505
+ * <ReaderComponent />
506
+ * <WriterComponent />
507
+ * </Provider>
508
+ * )
509
+ * }
510
+ * ```
511
+ */
322
512
  function createSplittingContext(defaultValue) {
323
- const keys = Array.isArray(defaultValue) ? defaultValue : Object.keys(defaultValue);
324
- const defaultValues = Array.isArray(defaultValue) ? keys.reduce(
325
- (prev, cur) => {
326
- prev[cur] = null;
327
- return prev;
328
- },
329
- {}
330
- ) : defaultValue;
331
- const contexts = {};
332
- for (const key of keys) contexts[key] = createContext(defaultValues[key]);
333
- function Provider(props) {
334
- const states = props.initializeStates ? useSplittingState(props.initializeStates) : {};
335
- let children = props.memo ? useEqualMemo(
336
- () => props.children,
337
- props.memo === true ? [] : props.memo
338
- ) : props.children;
339
- for (const key of keys) {
340
- const Context = contexts[key];
341
- const value = props.value?.[key] ?? states[key] ?? defaultValues[key];
342
- children = /* @__PURE__ */ jsx(Context.Provider, { value, children });
343
- }
344
- return children;
345
- }
346
- Provider.displayName = "SplittingContextProvider";
347
- function use() {
348
- return useConstant(() => {
349
- const obj = /* @__PURE__ */ Object.create(null);
350
- for (const key of Object.keys(contexts)) {
351
- Object.defineProperty(obj, key, {
352
- get: () => {
353
- if (!contexts[key]) {
354
- throw new Error(`Context for key "${key}" is undefined`);
355
- }
356
- return useContext(contexts[key]);
357
- }
358
- });
359
- }
360
- return Object.freeze(obj);
361
- });
362
- }
363
- return {
364
- Provider,
365
- use
366
- };
513
+ const keys = Array.isArray(defaultValue) ? defaultValue : Object.keys(defaultValue);
514
+ const defaultValues = Array.isArray(defaultValue) ? keys.reduce((prev, cur) => {
515
+ prev[cur] = null;
516
+ return prev;
517
+ }, {}) : defaultValue;
518
+ const contexts = {};
519
+ for (const key of keys) contexts[key] = createContext(defaultValues[key]);
520
+ function Provider(props) {
521
+ const states = props.initializeStates ? useSplittingState(props.initializeStates) : {};
522
+ let children = props.memo ? useEqualMemo(() => props.children, props.memo === true ? [] : props.memo) : props.children;
523
+ for (const key of keys) {
524
+ const Context = contexts[key];
525
+ const value = props.value?.[key] ?? states[key] ?? defaultValues[key];
526
+ children = /* @__PURE__ */ jsx(Context.Provider, {
527
+ value,
528
+ children
529
+ });
530
+ }
531
+ return children;
532
+ }
533
+ Provider.displayName = "SplittingContextProvider";
534
+ function use() {
535
+ return useConstant(() => {
536
+ const obj = Object.create(null);
537
+ for (const key of Object.keys(contexts)) Object.defineProperty(obj, key, { get: () => {
538
+ if (!contexts[key]) throw new Error(`Context for key "${key}" is undefined`);
539
+ return useContext(contexts[key]);
540
+ } });
541
+ return Object.freeze(obj);
542
+ });
543
+ }
544
+ return {
545
+ Provider,
546
+ use
547
+ };
367
548
  }
368
549
 
369
- // src/Form/context.tsx
370
- var FormContext = createSplittingContext([
371
- "items",
372
- "onSubmit",
373
- "Elements",
374
- "lang",
375
- "rules",
376
- "submitting",
377
- "setSubmitting",
378
- "values",
379
- "setValues",
380
- "errors",
381
- "setErrors",
382
- "valuesRef"
550
+ //#endregion
551
+ //#region src/Form/context.tsx
552
+ const FormContext = createSplittingContext([
553
+ "items",
554
+ "onSubmit",
555
+ "Elements",
556
+ "lang",
557
+ "rules",
558
+ "submitting",
559
+ "setSubmitting",
560
+ "values",
561
+ "setValues",
562
+ "errors",
563
+ "setErrors",
564
+ "valuesRef"
383
565
  ]);
384
- var FormContextProvider = FormContext.Provider;
385
- var useFormContext = FormContext.use;
566
+ const FormContextProvider = FormContext.Provider;
567
+ const useFormContext = FormContext.use;
568
+
569
+ //#endregion
570
+ //#region src/Form/Input.tsx
386
571
  function processValue(input, rules) {
387
- switch (rules?.type) {
388
- case "number":
389
- return Number(input);
390
- case "string":
391
- return String(input);
392
- default:
393
- return input;
394
- }
572
+ switch (rules?.type) {
573
+ case "number": return Number(input);
574
+ case "string": return String(input);
575
+ default: return input;
576
+ }
395
577
  }
396
- function FormInput({
397
- name,
398
- rules,
399
- ...rest
400
- }) {
401
- const { Elements, values, setValues } = useFormContext();
402
- const value = values?.[name];
403
- if (rest.Input) {
404
- return /* @__PURE__ */ jsx(
405
- rest.Input,
406
- {
407
- name,
408
- value,
409
- onChange: (v) => setValues((prev) => ({
410
- ...prev,
411
- [name]: processValue(v, rules)
412
- })),
413
- ...rest.props
414
- }
415
- );
416
- }
417
- return /* @__PURE__ */ jsx(
418
- Elements.Input,
419
- {
420
- name,
421
- value,
422
- onChange: (v) => setValues((prev) => ({
423
- ...prev,
424
- [name]: processValue(v, rules)
425
- })),
426
- ...rest.props
427
- }
428
- );
578
+ function FormInput({ name, rules, ...rest }) {
579
+ const { Elements, values, setValues } = useFormContext();
580
+ const value = values?.[name];
581
+ if (rest.Input) return /* @__PURE__ */ jsx(rest.Input, {
582
+ name,
583
+ value,
584
+ onChange: (v) => setValues((prev) => ({
585
+ ...prev,
586
+ [name]: processValue(v, rules)
587
+ })),
588
+ ...rest.props
589
+ });
590
+ return /* @__PURE__ */ jsx(Elements.Input, {
591
+ name,
592
+ value,
593
+ onChange: (v) => setValues((prev) => ({
594
+ ...prev,
595
+ [name]: processValue(v, rules)
596
+ })),
597
+ ...rest.props
598
+ });
429
599
  }
430
600
  FormInput.displayName = "FormInput";
601
+
602
+ //#endregion
603
+ //#region src/Form/Item.tsx
431
604
  function FormItem(props) {
432
- const { Elements, errors } = useFormContext();
433
- const Label = props.label?.Label ?? Elements.Label;
434
- return /* @__PURE__ */ jsx(Label, { name: props.name, ...props.label, error: errors[props.name], children: /* @__PURE__ */ jsx(FormInput, { name: props.name, rules: props.rules, ...props.input }) });
605
+ const { Elements, errors } = useFormContext();
606
+ return /* @__PURE__ */ jsx(props.label?.Label ?? Elements.Label, {
607
+ name: props.name,
608
+ ...props.label,
609
+ error: errors[props.name],
610
+ children: /* @__PURE__ */ jsx(FormInput, {
611
+ name: props.name,
612
+ ...props.input,
613
+ ...props.rules ? { rules: props.rules } : {}
614
+ })
615
+ });
435
616
  }
436
617
  FormItem.displayName = "FormItem";
618
+
619
+ //#endregion
620
+ //#region src/Form/Body.tsx
437
621
  function FormBody() {
438
- const { items } = useFormContext();
439
- return items.map((item) => /* @__PURE__ */ jsx(FormItem, { ...item }, item.name));
622
+ const { items } = useFormContext();
623
+ return items.map((item) => /* @__PURE__ */ jsx(FormItem, { ...item }, item.name));
440
624
  }
441
625
  FormBody.displayName = "FormBody";
442
- var FormButtonElement = forwardRef(({ children, submit, submitting, ...props }, ref) => /* @__PURE__ */ jsx(
443
- "button",
444
- {
445
- type: "button",
446
- disabled: submitting,
447
- onClick: submit,
448
- ...props,
449
- ref,
450
- children
451
- }
452
- ));
626
+
627
+ //#endregion
628
+ //#region src/Form/elements/Button.tsx
629
+ const FormButtonElement = forwardRef(({ children, submit, submitting, ...props }, ref) => /* @__PURE__ */ jsx("button", {
630
+ type: "button",
631
+ disabled: submitting,
632
+ onClick: submit,
633
+ ...props,
634
+ ref,
635
+ children
636
+ }));
453
637
  FormButtonElement.displayName = "FormButtonElement";
454
- var FormInputElement = forwardRef(({ onChange, ...props }, ref) => /* @__PURE__ */ jsx("input", { ...props, onChange: (e) => onChange(e.target.value), ref }));
638
+
639
+ //#endregion
640
+ //#region src/Form/elements/Input.tsx
641
+ const FormInputElement = forwardRef(({ onChange, ...props }, ref) => /* @__PURE__ */ jsx("input", {
642
+ ...props,
643
+ onChange: (e) => onChange(e.target.value),
644
+ ref
645
+ }));
455
646
  FormInputElement.displayName = "FormInputElement";
456
- var FormLabelElement = ({
457
- name,
458
- title,
459
- description,
460
- error,
461
- children
462
- }) => {
463
- return /* @__PURE__ */ jsxs("label", { children: [
464
- title ?? name,
465
- children,
466
- description,
467
- error && /* @__PURE__ */ jsx("div", { style: { color: "red" }, children: error.message })
468
- ] });
647
+
648
+ //#endregion
649
+ //#region src/Form/elements/Label.tsx
650
+ const FormLabelElement = ({ name, title, description, error, children }) => {
651
+ return /* @__PURE__ */ jsxs("label", { children: [
652
+ title ?? name,
653
+ children,
654
+ description,
655
+ error && /* @__PURE__ */ jsx("div", {
656
+ style: { color: "red" },
657
+ children: error.message
658
+ })
659
+ ] });
469
660
  };
470
661
  FormLabelElement.displayName = "FormLabelElement";
471
662
 
472
- // src/Form/elements/index.ts
473
- var FormDefaultElements = {
474
- Label: FormLabelElement,
475
- Input: FormInputElement,
476
- Button: FormButtonElement
663
+ //#endregion
664
+ //#region src/Form/elements/index.ts
665
+ const FormDefaultElements = {
666
+ Label: FormLabelElement,
667
+ Input: FormInputElement,
668
+ Button: FormButtonElement
477
669
  };
478
670
 
479
- // src/Form/rules.ts
480
- var FormDefaultRules = {
481
- required: async (value, _, lang) => {
482
- if (value === null || value === void 0 || value === "" || Number.isNaN(value)) {
483
- throw Error(lang?.required);
484
- }
485
- },
486
- type: async (value, options, lang) => {
487
- switch (options) {
488
- case "string":
489
- if (typeof value !== "string") throw Error(lang?.string);
490
- break;
491
- case "number":
492
- if (Number.isNaN(Number(value))) throw Error(lang?.number);
493
- break;
494
- }
495
- },
496
- custom: async (value, options) => {
497
- return options(value);
498
- }
671
+ //#endregion
672
+ //#region src/Form/rules.ts
673
+ /**
674
+ * Default validation rules for a form.
675
+ */
676
+ const FormDefaultRules = {
677
+ required: async (value, _, lang) => {
678
+ if (value === null || value === void 0 || value === "" || Number.isNaN(value)) throw Error(lang?.required);
679
+ },
680
+ type: async (value, options, lang) => {
681
+ switch (options) {
682
+ case "string":
683
+ if (typeof value !== "string") throw Error(lang?.string);
684
+ break;
685
+ case "number":
686
+ if (Number.isNaN(Number(value))) throw Error(lang?.number);
687
+ break;
688
+ }
689
+ },
690
+ custom: async (value, options) => {
691
+ return options(value);
692
+ }
499
693
  };
500
694
  async function validValues(rules, items, values, lang) {
501
- const errors = {};
502
- for (const item of items) {
503
- const value = values[item.name];
504
- const rulesOptions = item.rules;
505
- if (rulesOptions) {
506
- for (const [name, options] of Object.entries(rulesOptions)) {
507
- try {
508
- await rules[name](value, options, lang);
509
- } catch (error) {
510
- errors[item.name] = error;
511
- break;
512
- }
513
- }
514
- }
515
- }
516
- return errors;
695
+ const errors = {};
696
+ for (const item of items) {
697
+ const value = values[item.name];
698
+ const rulesOptions = item.rules;
699
+ if (rulesOptions) for (const [name, options] of Object.entries(rulesOptions)) try {
700
+ await rules[name](value, options, lang);
701
+ } catch (error) {
702
+ errors[item.name] = error;
703
+ break;
704
+ }
705
+ }
706
+ return errors;
517
707
  }
708
+
709
+ //#endregion
710
+ //#region src/Form/Footer.tsx
518
711
  function FormFooter() {
519
- const {
520
- submitting,
521
- setSubmitting,
522
- onSubmit,
523
- valuesRef,
524
- Elements,
525
- items,
526
- setErrors,
527
- lang,
528
- rules
529
- } = useFormContext();
530
- const handleSubmit = useCallback(async () => {
531
- setSubmitting(true);
532
- setErrors({});
533
- const errors = await validValues(rules, items, valuesRef.current, lang);
534
- if (Object.keys(errors).length) {
535
- setErrors(errors);
536
- setSubmitting(false);
537
- return;
538
- }
539
- onSubmit(valuesRef.current).finally(() => setSubmitting(false));
540
- }, [setSubmitting, setErrors, rules, items, lang, onSubmit]);
541
- const MemoizedButton = useMemo(
542
- () => /* @__PURE__ */ jsx(Elements.Button, { submitting, submit: handleSubmit, children: lang.submit }),
543
- [submitting, handleSubmit, lang.submit, Elements.Button]
544
- );
545
- return MemoizedButton;
712
+ const { submitting, setSubmitting, onSubmit, valuesRef, Elements, items, setErrors, lang, rules } = useFormContext();
713
+ const handleSubmit = useCallback(async () => {
714
+ setSubmitting(true);
715
+ setErrors({});
716
+ const errors = await validValues(rules, items, valuesRef.current, lang);
717
+ if (Object.keys(errors).length) {
718
+ setErrors(errors);
719
+ setSubmitting(false);
720
+ return;
721
+ }
722
+ onSubmit(valuesRef.current).finally(() => setSubmitting(false));
723
+ }, [
724
+ setSubmitting,
725
+ setErrors,
726
+ rules,
727
+ items,
728
+ lang,
729
+ onSubmit
730
+ ]);
731
+ return useMemo(() => /* @__PURE__ */ jsx(Elements.Button, {
732
+ submitting,
733
+ submit: handleSubmit,
734
+ children: lang.submit
735
+ }), [
736
+ submitting,
737
+ handleSubmit,
738
+ lang.submit,
739
+ Elements.Button
740
+ ]);
546
741
  }
547
742
  FormFooter.displayName = "FormFooter";
548
743
 
549
- // src/Form/lang.ts
550
- var FormDefaultLang = {
551
- submit: "Submit",
552
- required: "This field is required",
553
- string: "This field must be a string",
554
- number: "This field must be a number"
744
+ //#endregion
745
+ //#region src/Form/lang.ts
746
+ const FormDefaultLang = {
747
+ submit: "Submit",
748
+ required: "This field is required",
749
+ string: "This field must be a string",
750
+ number: "This field must be a number"
555
751
  };
752
+
753
+ //#endregion
754
+ //#region src/Form/Container.tsx
556
755
  function mergeValues(items, defaultValues = {}) {
557
- const values = {};
558
- for (const item of items)
559
- values[item.name] = defaultValues[item.name] ?? "";
560
- return values;
756
+ const values = {};
757
+ for (const item of items) values[item.name] = defaultValues[item.name] ?? "";
758
+ return values;
561
759
  }
562
- function FormContainer({
563
- defaultValues,
564
- Elements,
565
- rules,
566
- lang,
567
- items,
568
- ...props
569
- }) {
570
- const [values, setValues, valuesRef] = useStateRef(
571
- mergeValues(items, defaultValues)
572
- );
573
- return /* @__PURE__ */ jsxs(
574
- FormContextProvider,
575
- {
576
- initializeStates: {
577
- errors: {},
578
- submitting: false
579
- },
580
- value: {
581
- Elements: Object.assign(FormDefaultElements, Elements),
582
- lang: Object.assign(FormDefaultLang, lang),
583
- rules: Object.assign(FormDefaultRules, rules),
584
- items,
585
- values,
586
- setValues,
587
- valuesRef,
588
- ...props
589
- },
590
- memo: true,
591
- children: [
592
- /* @__PURE__ */ jsx(FormBody, {}),
593
- /* @__PURE__ */ jsx(FormFooter, {})
594
- ]
595
- }
596
- );
760
+ /**
761
+ * FormContainer component is a wrapper that provides context and state management for form elements.
762
+ * It initializes form states such as values, errors, submitting status, elements, language, and rules.
763
+ *
764
+ * @template Values - The type of form values, defaults to Record<string, any>.
765
+ * @template FormElements - The type of form elements, defaults to FormElementTypes.
766
+ * @template Rules - The type of form rules, defaults to FormDefaultRules.
767
+ *
768
+ * @param {FormProps<Values, FormElements, Rules>} props - The properties for the FormContainer component.
769
+ * @param {Values} props.defaultValues - The default values for the form fields.
770
+ * @param {FormElements} props.Elements - The form elements to be used in the form.
771
+ * @param {Rules} props.rules - The validation rules for the form fields.
772
+ * @param {FormLang} props.lang - The language settings for the form.
773
+ * @param {Partial<FormContextProps>} props - Additional properties for the form context.
774
+ *
775
+ * @returns {JSX.Element} The FormContainer component.
776
+ *
777
+ * @example
778
+ * ```tsx
779
+ * import { Form } from '@faasjs/react'
780
+ *
781
+ * function MyForm() {
782
+ * return <Form
783
+ * items={[
784
+ * { name: 'name' },
785
+ * ]}
786
+ * />
787
+ * }
788
+ * ```
789
+ */
790
+ function FormContainer({ defaultValues, Elements, rules, lang, items, ...props }) {
791
+ const [values, setValues, valuesRef] = useStateRef(mergeValues(items, defaultValues));
792
+ return /* @__PURE__ */ jsxs(FormContextProvider, {
793
+ initializeStates: {
794
+ errors: {},
795
+ submitting: false
796
+ },
797
+ value: {
798
+ Elements: Object.assign(FormDefaultElements, Elements),
799
+ lang: Object.assign(FormDefaultLang, lang),
800
+ rules: Object.assign(FormDefaultRules, rules),
801
+ items,
802
+ values,
803
+ setValues,
804
+ valuesRef,
805
+ ...props
806
+ },
807
+ memo: true,
808
+ children: [/* @__PURE__ */ jsx(FormBody, {}), /* @__PURE__ */ jsx(FormFooter, {})]
809
+ });
597
810
  }
598
811
  FormContainer.displayName = "FormContainer";
599
- function OptionalWrapper({
600
- condition,
601
- Wrapper,
602
- wrapperProps,
603
- children
604
- }) {
605
- if (condition) return /* @__PURE__ */ jsx(Wrapper, { ...wrapperProps, children });
606
- return children;
812
+
813
+ //#endregion
814
+ //#region src/OptionalWrapper.tsx
815
+ /**
816
+ * A wrapper component that conditionally wraps its children with a provided wrapper component.
817
+ *
818
+ * @example
819
+ * ```tsx
820
+ * import { OptionalWrapper } from '@faasjs/react'
821
+ *
822
+ * const Wrapper = ({ children }: { children: React.ReactNode }) => (
823
+ * <div className='wrapper'>{children}</div>
824
+ * )
825
+ *
826
+ * const App = () => (
827
+ * <OptionalWrapper condition={true} Wrapper={Wrapper}>
828
+ * <span>Test</span>
829
+ * </OptionalWrapper>
830
+ * )
831
+ * ```
832
+ */
833
+ function OptionalWrapper({ condition, Wrapper, wrapperProps, children }) {
834
+ if (condition) return /* @__PURE__ */ jsx(Wrapper, {
835
+ ...wrapperProps,
836
+ children
837
+ });
838
+ return children;
607
839
  }
608
840
  OptionalWrapper.displayName = "OptionalWrapper";
841
+
842
+ //#endregion
843
+ //#region src/useFaasStream.tsx
844
+ /**
845
+ * Stream faas server response with React hook
846
+ *
847
+ * @param action {string} action name
848
+ * @param defaultParams {object} initial action params
849
+ * @returns {UseFaasStreamResult}
850
+ *
851
+ * @example
852
+ * ```tsx
853
+ * function Chat() {
854
+ * const [prompt, setPrompt] = useState('')
855
+ * const { data, loading, reload } = useFaasStream('chat', { prompt })
856
+ *
857
+ * return (
858
+ * <div>
859
+ * <textarea value={prompt} onChange={e => setPrompt(e.target.value)} />
860
+ * <button onClick={reload} disabled={loading}>Send</button>
861
+ * <div>{data}</div>
862
+ * </div>
863
+ * )
864
+ * }
865
+ * ```
866
+ */
609
867
  function useFaasStream(action, defaultParams, options = {}) {
610
- const [loading, setLoading] = useState(true);
611
- const [data, setData] = useState(options.data || "");
612
- const [error, setError] = useState();
613
- const [params, setParams] = useState(defaultParams);
614
- const [reloadTimes, setReloadTimes] = useState(0);
615
- const [fails, setFails] = useState(0);
616
- const [skip, setSkip] = useState(
617
- typeof options.skip === "function" ? options.skip(defaultParams) : options.skip
618
- );
619
- const controllerRef = useRef(null);
620
- const pendingReloadsRef = useRef(/* @__PURE__ */ new Map());
621
- const reloadCounterRef = useRef(0);
622
- useEqualEffect(() => {
623
- setSkip(
624
- typeof options.skip === "function" ? options.skip(params) : options.skip
625
- );
626
- }, [typeof options.skip === "function" ? params : options.skip]);
627
- useEqualEffect(() => {
628
- if (!equal(defaultParams, params)) {
629
- setParams(defaultParams);
630
- }
631
- }, [defaultParams]);
632
- useEqualEffect(() => {
633
- if (!action || skip) {
634
- setLoading(false);
635
- return;
636
- }
637
- setLoading(true);
638
- setData("");
639
- controllerRef.current = new AbortController();
640
- const client = getClient(options.baseUrl);
641
- function send() {
642
- client.browserClient.action(action, options.params || params, {
643
- signal: controllerRef.current.signal,
644
- stream: true
645
- }).then(async (response) => {
646
- if (!response.body) {
647
- setError(new Error("Response body is null"));
648
- setLoading(false);
649
- return;
650
- }
651
- const reader = response.body.getReader();
652
- const decoder = new TextDecoder();
653
- let accumulatedText = "";
654
- try {
655
- while (true) {
656
- const { done, value } = await reader.read();
657
- if (done) break;
658
- accumulatedText += decoder.decode(value, { stream: true });
659
- setData(accumulatedText);
660
- }
661
- setFails(0);
662
- setError(null);
663
- setLoading(false);
664
- for (const { resolve } of pendingReloadsRef.current.values())
665
- resolve(accumulatedText);
666
- pendingReloadsRef.current.clear();
667
- } catch (readError) {
668
- reader.releaseLock();
669
- throw readError;
670
- }
671
- }).catch(async (e) => {
672
- if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0)
673
- return;
674
- if (!fails && typeof e?.message === "string" && e.message.indexOf("Failed to fetch") >= 0) {
675
- console.warn(`FaasReactClient: ${e.message} retry...`);
676
- setFails(1);
677
- return send();
678
- }
679
- let error2 = e;
680
- if (client.onError)
681
- try {
682
- await client.onError(action, params)(e);
683
- } catch (newError) {
684
- error2 = newError;
685
- }
686
- setError(error2);
687
- setLoading(false);
688
- for (const { reject } of pendingReloadsRef.current.values())
689
- reject(error2);
690
- pendingReloadsRef.current.clear();
691
- return;
692
- });
693
- }
694
- if (options.debounce) {
695
- const timeout = setTimeout(send, options.debounce);
696
- return () => {
697
- clearTimeout(timeout);
698
- controllerRef.current?.abort();
699
- setLoading(false);
700
- };
701
- }
702
- send();
703
- return () => {
704
- controllerRef.current?.abort();
705
- setLoading(false);
706
- };
707
- }, [action, options.params || params, reloadTimes, skip]);
708
- const reload = useEqualCallback(
709
- (params2) => {
710
- if (skip) setSkip(false);
711
- if (params2) setParams(params2);
712
- const reloadCounter = ++reloadCounterRef.current;
713
- return new Promise((resolve, reject) => {
714
- pendingReloadsRef.current.set(reloadCounter, { resolve, reject });
715
- setReloadTimes((prev) => prev + 1);
716
- });
717
- },
718
- [params, skip]
719
- );
720
- return {
721
- action,
722
- params,
723
- loading,
724
- data: options.data || data,
725
- reloadTimes,
726
- error,
727
- reload,
728
- setData: options.setData || setData,
729
- setLoading,
730
- setError
731
- };
868
+ const [loading, setLoading] = useState(true);
869
+ const [data, setData] = useState(options.data || "");
870
+ const [error, setError] = useState();
871
+ const [params, setParams] = useState(defaultParams);
872
+ const [reloadTimes, setReloadTimes] = useState(0);
873
+ const [fails, setFails] = useState(0);
874
+ const [skip, setSkip] = useState(typeof options.skip === "function" ? options.skip(defaultParams) : options.skip);
875
+ const controllerRef = useRef(null);
876
+ const pendingReloadsRef = useRef(/* @__PURE__ */ new Map());
877
+ const reloadCounterRef = useRef(0);
878
+ useEqualEffect(() => {
879
+ setSkip(typeof options.skip === "function" ? options.skip(params) : options.skip);
880
+ }, [typeof options.skip === "function" ? params : options.skip]);
881
+ useEqualEffect(() => {
882
+ if (!equal(defaultParams, params)) setParams(defaultParams);
883
+ }, [defaultParams]);
884
+ useEqualEffect(() => {
885
+ if (!action || skip) {
886
+ setLoading(false);
887
+ return;
888
+ }
889
+ setLoading(true);
890
+ setData("");
891
+ const controller = new AbortController();
892
+ controllerRef.current = controller;
893
+ const client = getClient(options.baseUrl);
894
+ const requestParams = options.params ?? params;
895
+ function send() {
896
+ client.browserClient.action(action, requestParams, {
897
+ signal: controller.signal,
898
+ stream: true
899
+ }).then(async (response) => {
900
+ if (!response.body) {
901
+ setError(/* @__PURE__ */ new Error("Response body is null"));
902
+ setLoading(false);
903
+ return;
904
+ }
905
+ const reader = response.body.getReader();
906
+ const decoder = new TextDecoder();
907
+ let accumulatedText = "";
908
+ try {
909
+ while (true) {
910
+ const { done, value } = await reader.read();
911
+ if (done) break;
912
+ accumulatedText += decoder.decode(value, { stream: true });
913
+ setData(accumulatedText);
914
+ }
915
+ setFails(0);
916
+ setError(null);
917
+ setLoading(false);
918
+ for (const { resolve } of pendingReloadsRef.current.values()) resolve(accumulatedText);
919
+ pendingReloadsRef.current.clear();
920
+ } catch (readError) {
921
+ reader.releaseLock();
922
+ throw readError;
923
+ }
924
+ }).catch(async (e) => {
925
+ if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0) return;
926
+ if (!fails && typeof e?.message === "string" && e.message.indexOf("Failed to fetch") >= 0) {
927
+ console.warn(`FaasReactClient: ${e.message} retry...`);
928
+ setFails(1);
929
+ return send();
930
+ }
931
+ let error = e;
932
+ if (client.onError) try {
933
+ await client.onError(action, requestParams)(e);
934
+ } catch (newError) {
935
+ error = newError;
936
+ }
937
+ setError(error);
938
+ setLoading(false);
939
+ for (const { reject } of pendingReloadsRef.current.values()) reject(error);
940
+ pendingReloadsRef.current.clear();
941
+ });
942
+ }
943
+ if (options.debounce) {
944
+ const timeout = setTimeout(send, options.debounce);
945
+ return () => {
946
+ clearTimeout(timeout);
947
+ controllerRef.current?.abort();
948
+ setLoading(false);
949
+ };
950
+ }
951
+ send();
952
+ return () => {
953
+ controllerRef.current?.abort();
954
+ setLoading(false);
955
+ };
956
+ }, [
957
+ action,
958
+ options.params || params,
959
+ reloadTimes,
960
+ skip
961
+ ]);
962
+ const reload = useEqualCallback((params) => {
963
+ if (skip) setSkip(false);
964
+ if (params) setParams(params);
965
+ const reloadCounter = ++reloadCounterRef.current;
966
+ return new Promise((resolve, reject) => {
967
+ pendingReloadsRef.current.set(reloadCounter, {
968
+ resolve,
969
+ reject
970
+ });
971
+ setReloadTimes((prev) => prev + 1);
972
+ });
973
+ }, [params, skip]);
974
+ return {
975
+ action,
976
+ params,
977
+ loading,
978
+ data: options.data || data,
979
+ reloadTimes,
980
+ error,
981
+ reload,
982
+ setData: options.setData || setData,
983
+ setLoading,
984
+ setError
985
+ };
732
986
  }
987
+
988
+ //#endregion
989
+ //#region src/usePrevious.ts
990
+ /**
991
+ * Hook to store the previous value of a state or prop.
992
+ *
993
+ * @template T - The type of the value.
994
+ * @param {T} value - The current value to be stored.
995
+ * @returns {T | undefined} - The previous value, or undefined if there is no previous value.
996
+ */
733
997
  function usePrevious(value) {
734
- const ref = useRef(void 0);
735
- useEffect(() => {
736
- ref.current = value;
737
- });
738
- return ref.current;
998
+ const ref = useRef(void 0);
999
+ useEffect(() => {
1000
+ ref.current = value;
1001
+ });
1002
+ return ref.current;
739
1003
  }
740
1004
 
741
- export { ErrorBoundary, FaasDataWrapper, FaasReactClient, FormContainer as Form, FormContextProvider, FormDefaultElements, FormDefaultLang, FormDefaultRules, FormInput, FormItem, OptionalWrapper, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, useFormContext, usePrevious, useSplittingState, useStateRef, validValues, withFaasData };
1005
+ //#endregion
1006
+ export { ErrorBoundary, FaasDataWrapper, FaasReactClient, FormContainer as Form, FormContextProvider, FormDefaultElements, FormDefaultLang, FormDefaultRules, FormInput, FormItem, OptionalWrapper, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, useFormContext, usePrevious, useSplittingState, useStateRef, validValues, withFaasData };