@faasjs/react 8.0.0-beta.23 → 8.0.0-beta.25

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.d.ts CHANGED
@@ -1318,7 +1318,7 @@ declare class ErrorBoundary extends Component<ErrorBoundaryProps, {
1318
1318
  /**
1319
1319
  * Render children or the configured fallback for the captured error.
1320
1320
  */
1321
- render(): string | number | bigint | boolean | _$react_jsx_runtime0.JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | _$react.ReactPortal | ReactElement<unknown, string | _$react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null;
1321
+ render(): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | _$react.ReactPortal | ReactElement<unknown, string | _$react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | _$react_jsx_runtime0.JSX.Element | null;
1322
1322
  }
1323
1323
  //#endregion
1324
1324
  //#region src/equal.d.ts
@@ -1465,7 +1465,7 @@ type OptionalWrapperProps<TWrapper extends ComponentType<{
1465
1465
  * )
1466
1466
  * ```
1467
1467
  */
1468
- declare function OptionalWrapper(props: OptionalWrapperProps): string | number | bigint | boolean | _$react_jsx_runtime0.JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | _$react.ReactPortal | _$react.ReactElement<unknown, string | _$react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
1468
+ declare function OptionalWrapper(props: OptionalWrapperProps): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | _$react.ReactPortal | _$react.ReactElement<unknown, string | _$react.JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | _$react_jsx_runtime0.JSX.Element | null | undefined;
1469
1469
  declare namespace OptionalWrapper {
1470
1470
  var displayName: string;
1471
1471
  }
package/dist/index.mjs CHANGED
@@ -402,6 +402,117 @@ let mock = null;
402
402
  function setMock(handler) {
403
403
  mock = handler;
404
404
  }
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
+ function normalizeMockResponse(response) {
431
+ if (response instanceof Error) throw new ResponseError(response);
432
+ if (response instanceof Response) {
433
+ if (typeof ReadableStream !== "undefined" && response.body instanceof ReadableStream && response.body.locked === false) {
434
+ const [nextBody, currentBody] = response.body.tee();
435
+ response.body = nextBody;
436
+ const clonedResponse = {
437
+ status: response.status,
438
+ headers: response.headers,
439
+ body: currentBody
440
+ };
441
+ if (response.data !== void 0) clonedResponse.data = response.data;
442
+ return new Response({ ...clonedResponse });
443
+ }
444
+ return response;
445
+ }
446
+ if (response && typeof ReadableStream !== "undefined" && response.body instanceof ReadableStream && response.body.locked === false) {
447
+ const [nextBody, currentBody] = response.body.tee();
448
+ response.body = nextBody;
449
+ return new Response({
450
+ ...response,
451
+ body: currentBody
452
+ });
453
+ }
454
+ return new Response(response || {});
455
+ }
456
+ async function resolveMockResponse(action, params, options) {
457
+ console.debug(`[FaasJS] Mock request: ${action} %j`, params);
458
+ if (typeof mock === "function") return normalizeMockResponse(await mock(action, params, options));
459
+ return normalizeMockResponse(mock);
460
+ }
461
+ function toResponseHeaders(headers) {
462
+ const responseHeaders = {};
463
+ for (const [key, value] of headers) responseHeaders[key] = value;
464
+ return responseHeaders;
465
+ }
466
+ function parseSuccessfulResponse(status, headers, text) {
467
+ if (!text) return new Response({
468
+ status,
469
+ headers
470
+ });
471
+ const body = JSON.parse(text);
472
+ if (body.error?.message) throw new ResponseError({
473
+ message: body.error.message,
474
+ status,
475
+ headers,
476
+ body
477
+ });
478
+ return new Response({
479
+ status,
480
+ headers,
481
+ body,
482
+ data: body.data
483
+ });
484
+ }
485
+ function parseFailedResponse(status, headers, text) {
486
+ try {
487
+ const body = JSON.parse(text);
488
+ if (body.error?.message) throw new ResponseError({
489
+ message: body.error.message,
490
+ status,
491
+ headers,
492
+ body
493
+ });
494
+ throw new ResponseError({
495
+ message: text,
496
+ status,
497
+ headers,
498
+ body
499
+ });
500
+ } catch (error) {
501
+ if (error instanceof ResponseError) throw error;
502
+ throw new ResponseError({
503
+ message: text,
504
+ status,
505
+ headers,
506
+ body: text
507
+ });
508
+ }
509
+ }
510
+ async function parseFetchResponse(response) {
511
+ const headers = toResponseHeaders(response.headers);
512
+ const text = await response.text();
513
+ if (response.status >= 200 && response.status < 300) return parseSuccessfulResponse(response.status, headers, text);
514
+ return parseFailedResponse(response.status, headers, text);
515
+ }
405
516
  /**
406
517
  * Browser client for FaasJS - provides HTTP client functionality for making API requests from web applications.
407
518
  *
@@ -655,86 +766,15 @@ var FaasBrowserClient = class {
655
766
  */
656
767
  async action(action, params, options) {
657
768
  if (!action) throw Error("[FaasJS] action required");
658
- const id = `F-${generateId()}`;
659
- const url = `${(options?.baseUrl || this.baseUrl) + action.toLowerCase()}?_=${id}`;
660
769
  if (!params) params = Object.create(null);
661
- if (!options) options = Object.create(null);
662
- const parsedOptions = {
663
- method: "POST",
664
- headers: { "Content-Type": "application/json; charset=UTF-8" },
665
- mode: "cors",
666
- credentials: "include",
667
- body: JSON.stringify(params),
668
- ...this.defaultOptions,
669
- ...options
670
- };
671
- if (!parsedOptions.headers["X-FaasJS-Request-Id"] && !parsedOptions.headers["x-faasjs-request-id"]) parsedOptions.headers["X-FaasJS-Request-Id"] = id;
672
- if (parsedOptions.beforeRequest) await parsedOptions.beforeRequest({
673
- action,
674
- params,
675
- options: parsedOptions,
676
- headers: parsedOptions.headers
677
- });
678
- if (mock) {
679
- console.debug(`[FaasJS] Mock request: ${action} %j`, params);
680
- if (typeof mock === "function") {
681
- const response = await mock(action, params, parsedOptions);
682
- if (response instanceof Error) return Promise.reject(new ResponseError(response));
683
- if (response instanceof Response) return response;
684
- return new Response(response || {});
685
- }
686
- if (mock instanceof Response) return mock;
687
- return new Response(mock || {});
688
- }
689
- if (parsedOptions.request) return parsedOptions.request(url, parsedOptions);
690
- if (parsedOptions.stream) return fetch(url, parsedOptions);
691
- return fetch(url, parsedOptions).then(async (response) => {
692
- const headers = {};
693
- for (const values of response.headers) headers[values[0]] = values[1];
694
- return response.text().then((res) => {
695
- if (response.status >= 200 && response.status < 300) {
696
- if (!res) return new Response({
697
- status: response.status,
698
- headers
699
- });
700
- const body = JSON.parse(res);
701
- if (body.error?.message) return Promise.reject(new ResponseError({
702
- message: body.error.message,
703
- status: response.status,
704
- headers,
705
- body
706
- }));
707
- return new Response({
708
- status: response.status,
709
- headers,
710
- body,
711
- data: body.data
712
- });
713
- }
714
- try {
715
- const body = JSON.parse(res);
716
- if (body.error?.message) return Promise.reject(new ResponseError({
717
- message: body.error.message,
718
- status: response.status,
719
- headers,
720
- body
721
- }));
722
- return Promise.reject(new ResponseError({
723
- message: res,
724
- status: response.status,
725
- headers,
726
- body
727
- }));
728
- } catch {
729
- return Promise.reject(new ResponseError({
730
- message: res,
731
- status: response.status,
732
- headers,
733
- body: res
734
- }));
735
- }
736
- });
737
- });
770
+ const requestId = `F-${generateId()}`;
771
+ const url = buildActionUrl(action, this.baseUrl, options, requestId);
772
+ const resolvedOptions = buildActionOptions(this.defaultOptions, options, params, requestId);
773
+ await runBeforeRequest(action, params, resolvedOptions);
774
+ if (mock) return resolveMockResponse(action, params, resolvedOptions);
775
+ if (resolvedOptions.request) return resolvedOptions.request(url, resolvedOptions);
776
+ if (resolvedOptions.stream) return fetch(url, resolvedOptions);
777
+ return parseFetchResponse(await fetch(url, resolvedOptions));
738
778
  }
739
779
  };
740
780
  //#endregion
@@ -1132,6 +1172,7 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
1132
1172
  const failedOnceRef = useRef(false);
1133
1173
  const pendingReloadsRef = useRef(/* @__PURE__ */ new Map());
1134
1174
  const reloadCounterRef = useRef(0);
1175
+ const requestVersionRef = useRef(0);
1135
1176
  const beforeSendRef = useRef(beforeSend);
1136
1177
  const onSuccessRef = useRef(onSuccess);
1137
1178
  const sendRef = useRef(send);
@@ -1153,6 +1194,7 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
1153
1194
  beforeSendRef.current?.();
1154
1195
  failedOnceRef.current = false;
1155
1196
  const controller = new AbortController();
1197
+ const requestVersion = ++requestVersionRef.current;
1156
1198
  controllerRef.current = controller;
1157
1199
  const client = getClient(options.baseUrl);
1158
1200
  const requestParams = options.params || params;
@@ -1164,6 +1206,7 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
1164
1206
  for (const { resolve } of pendingReloadsRef.current.values()) resolve(value);
1165
1207
  pendingReloadsRef.current.clear();
1166
1208
  };
1209
+ const isCurrentRequest = () => requestVersion === requestVersionRef.current && controllerRef.current === controller;
1167
1210
  const run = () => {
1168
1211
  sendRef.current({
1169
1212
  action,
@@ -1174,12 +1217,14 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
1174
1217
  promiseRef.current = promise;
1175
1218
  }
1176
1219
  }).then((result) => {
1220
+ if (!isCurrentRequest()) return;
1177
1221
  failedOnceRef.current = false;
1178
1222
  setError(null);
1179
1223
  onSuccessRef.current?.(result);
1180
1224
  setLoading(false);
1181
1225
  resolvePending(result);
1182
1226
  }).catch(async (e) => {
1227
+ if (!isCurrentRequest()) return;
1183
1228
  if (typeof e?.message === "string" && e.message.toLowerCase().includes("aborted")) return;
1184
1229
  if (!failedOnceRef.current && typeof e?.message === "string" && e.message.includes("Failed to fetch")) {
1185
1230
  failedOnceRef.current = true;
@@ -1193,6 +1238,7 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
1193
1238
  } catch (newError) {
1194
1239
  nextError = newError;
1195
1240
  }
1241
+ if (!isCurrentRequest()) return;
1196
1242
  setError(nextError);
1197
1243
  setLoading(false);
1198
1244
  rejectPending(nextError);
@@ -1202,13 +1248,15 @@ function useFaasRequest({ action, defaultParams, options, beforeSend, onSuccess,
1202
1248
  const timeout = setTimeout(run, options.debounce);
1203
1249
  return () => {
1204
1250
  clearTimeout(timeout);
1205
- controllerRef.current?.abort();
1251
+ if (controllerRef.current === controller) controllerRef.current = null;
1252
+ controller.abort();
1206
1253
  setLoading(false);
1207
1254
  };
1208
1255
  }
1209
1256
  run();
1210
1257
  return () => {
1211
- controllerRef.current?.abort();
1258
+ if (controllerRef.current === controller) controllerRef.current = null;
1259
+ controller.abort();
1212
1260
  setLoading(false);
1213
1261
  };
1214
1262
  }, [
@@ -1712,12 +1760,13 @@ function createSplittingContext(defaultValue) {
1712
1760
  * ```
1713
1761
  */
1714
1762
  function useFaasStream(action, defaultParams, options = {}) {
1715
- const [data, setData] = useState(options.data || "");
1763
+ const [data, setData] = useState(options.data ?? "");
1764
+ const updateData = options.setData ?? setData;
1716
1765
  const request = useFaasRequest({
1717
1766
  action,
1718
1767
  defaultParams,
1719
1768
  options,
1720
- beforeSend: () => setData(""),
1769
+ beforeSend: () => updateData(""),
1721
1770
  send: async ({ action, params, signal, client }) => {
1722
1771
  const response = await client.browserClient.action(action, params, {
1723
1772
  signal,
@@ -1727,17 +1776,32 @@ function useFaasStream(action, defaultParams, options = {}) {
1727
1776
  const reader = response.body.getReader();
1728
1777
  const decoder = new TextDecoder();
1729
1778
  let accumulatedText = "";
1779
+ const onAbort = () => {
1780
+ reader.cancel().catch(() => void 0);
1781
+ };
1782
+ if (signal.aborted) {
1783
+ onAbort();
1784
+ throw new Error("Request aborted");
1785
+ }
1786
+ signal.addEventListener("abort", onAbort, { once: true });
1730
1787
  try {
1731
1788
  while (true) {
1789
+ if (signal.aborted) throw new Error("Request aborted");
1732
1790
  const { done, value } = await reader.read();
1733
1791
  if (done) break;
1734
1792
  accumulatedText += decoder.decode(value, { stream: true });
1735
- setData(accumulatedText);
1793
+ updateData(accumulatedText);
1736
1794
  }
1795
+ accumulatedText += decoder.decode();
1737
1796
  return accumulatedText;
1738
1797
  } catch (error) {
1739
- reader.releaseLock();
1798
+ if (signal.aborted) throw new Error("Request aborted");
1740
1799
  throw error;
1800
+ } finally {
1801
+ signal.removeEventListener("abort", onAbort);
1802
+ try {
1803
+ reader.releaseLock();
1804
+ } catch {}
1741
1805
  }
1742
1806
  }
1743
1807
  });
@@ -1746,10 +1810,10 @@ function useFaasStream(action, defaultParams, options = {}) {
1746
1810
  params: request.params,
1747
1811
  loading: request.loading,
1748
1812
  reloadTimes: request.reloadTimes,
1749
- data: options.data || data,
1813
+ data: options.data ?? data,
1750
1814
  error: request.error,
1751
1815
  reload: request.reload,
1752
- setData: options.setData || setData,
1816
+ setData: updateData,
1753
1817
  setLoading: request.setLoading,
1754
1818
  setError: request.setError
1755
1819
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/react",
3
- "version": "8.0.0-beta.23",
3
+ "version": "8.0.0-beta.25",
4
4
  "homepage": "https://faasjs.com/doc/react/",
5
5
  "bugs": {
6
6
  "url": "https://github.com/faasjs/faasjs/issues"
@@ -26,12 +26,12 @@
26
26
  }
27
27
  },
28
28
  "devDependencies": {
29
- "@faasjs/types": ">=8.0.0-beta.23",
29
+ "@faasjs/types": ">=8.0.0-beta.25",
30
30
  "@types/react": "^19.0.0",
31
31
  "react": "^19.0.0"
32
32
  },
33
33
  "peerDependencies": {
34
- "@faasjs/types": ">=8.0.0-beta.23"
34
+ "@faasjs/types": ">=8.0.0-beta.25"
35
35
  },
36
36
  "engines": {
37
37
  "node": ">=24.0.0",