@alepha/react 0.14.0 → 0.14.2

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.
Files changed (72) hide show
  1. package/README.md +1 -1
  2. package/dist/auth/index.browser.js +1488 -4
  3. package/dist/auth/index.browser.js.map +1 -1
  4. package/dist/auth/index.d.ts +2 -2
  5. package/dist/auth/index.js +1827 -4
  6. package/dist/auth/index.js.map +1 -1
  7. package/dist/core/index.d.ts +54 -937
  8. package/dist/core/index.d.ts.map +1 -1
  9. package/dist/core/index.js +132 -2010
  10. package/dist/core/index.js.map +1 -1
  11. package/dist/form/index.d.ts.map +1 -1
  12. package/dist/form/index.js +6 -1
  13. package/dist/form/index.js.map +1 -1
  14. package/dist/head/index.browser.js +191 -17
  15. package/dist/head/index.browser.js.map +1 -1
  16. package/dist/head/index.d.ts +652 -31
  17. package/dist/head/index.d.ts.map +1 -1
  18. package/dist/head/index.js +209 -18
  19. package/dist/head/index.js.map +1 -1
  20. package/dist/{core → router}/index.browser.js +126 -516
  21. package/dist/router/index.browser.js.map +1 -0
  22. package/dist/router/index.d.ts +1334 -0
  23. package/dist/router/index.d.ts.map +1 -0
  24. package/dist/router/index.js +1939 -0
  25. package/dist/router/index.js.map +1 -0
  26. package/package.json +12 -6
  27. package/src/auth/index.ts +1 -1
  28. package/src/auth/services/ReactAuth.ts +1 -1
  29. package/src/core/components/ClientOnly.tsx +14 -0
  30. package/src/core/components/ErrorBoundary.tsx +3 -2
  31. package/src/core/contexts/AlephaContext.ts +3 -0
  32. package/src/core/contexts/AlephaProvider.tsx +2 -1
  33. package/src/core/index.ts +13 -102
  34. package/src/form/services/FormModel.ts +5 -0
  35. package/src/head/helpers/SeoExpander.ts +141 -0
  36. package/src/head/index.browser.ts +1 -0
  37. package/src/head/index.ts +17 -7
  38. package/src/head/interfaces/Head.ts +69 -27
  39. package/src/head/providers/BrowserHeadProvider.ts +45 -12
  40. package/src/head/providers/HeadProvider.ts +32 -8
  41. package/src/head/providers/ServerHeadProvider.ts +34 -2
  42. package/src/{core → router}/components/ErrorViewer.tsx +2 -0
  43. package/src/router/components/Link.tsx +21 -0
  44. package/src/{core → router}/components/NestedView.tsx +3 -5
  45. package/src/router/components/NotFound.tsx +30 -0
  46. package/src/router/errors/Redirection.ts +28 -0
  47. package/src/{core → router}/hooks/useActive.ts +6 -2
  48. package/src/{core → router}/hooks/useQueryParams.ts +2 -2
  49. package/src/{core → router}/hooks/useRouter.ts +1 -1
  50. package/src/{core → router}/hooks/useRouterState.ts +1 -1
  51. package/src/{core → router}/index.browser.ts +14 -12
  52. package/src/{core/index.shared-router.ts → router/index.shared.ts} +6 -3
  53. package/src/router/index.ts +125 -0
  54. package/src/{core → router}/primitives/$page.ts +1 -1
  55. package/src/{core → router}/providers/ReactBrowserProvider.ts +3 -13
  56. package/src/{core → router}/providers/ReactBrowserRendererProvider.ts +3 -0
  57. package/src/{core → router}/providers/ReactBrowserRouterProvider.ts +3 -0
  58. package/src/{core → router}/providers/ReactPageProvider.ts +5 -3
  59. package/src/{core → router}/providers/ReactServerProvider.ts +9 -28
  60. package/src/{core → router}/services/ReactPageServerService.ts +3 -0
  61. package/src/{core → router}/services/ReactPageService.ts +5 -5
  62. package/src/{core → router}/services/ReactRouter.ts +26 -5
  63. package/dist/core/index.browser.js.map +0 -1
  64. package/dist/core/index.native.js +0 -403
  65. package/dist/core/index.native.js.map +0 -1
  66. package/src/core/components/Link.tsx +0 -18
  67. package/src/core/components/NotFound.tsx +0 -27
  68. package/src/core/errors/Redirection.ts +0 -13
  69. package/src/core/hooks/useSchema.ts +0 -88
  70. package/src/core/index.native.ts +0 -21
  71. package/src/core/index.shared.ts +0 -9
  72. /package/src/{core → router}/contexts/RouterLayerContext.ts +0 -0
@@ -1,14 +1,18 @@
1
- import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, Atom, KIND, Primitive, createPrimitive, t } from "alepha";
1
+ import { $atom, $env, $hook, $inject, $module, $use, Alepha, AlephaError, KIND, Primitive, createPrimitive, t } from "alepha";
2
2
  import { AlephaDateTime, DateTimeProvider } from "alepha/datetime";
3
- import { AlephaServer, HttpClient } from "alepha/server";
4
- import { AlephaServerLinks, LinkProvider } from "alepha/server/links";
5
3
  import { $logger } from "alepha/logger";
4
+ import { AlephaServerLinks, LinkProvider } from "alepha/server/links";
6
5
  import { RouterProvider } from "alepha/router";
7
- import React, { StrictMode, createContext, createElement, memo, use, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
6
+ import { StrictMode, createContext, createElement, memo, use, useEffect, useRef, useState } from "react";
8
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { AlephaContext, AlephaReact, ClientOnly, ErrorBoundary, useAlepha, useEvents, useInject, useStore } from "@alepha/react";
9
9
  import { createRoot, hydrateRoot } from "react-dom/client";
10
+ import { AlephaServer } from "alepha/server";
10
11
 
11
- //#region ../../src/core/services/ReactPageService.ts
12
+ //#region ../../src/router/services/ReactPageService.ts
13
+ /**
14
+ * $page methods interface.
15
+ */
12
16
  var ReactPageService = class {
13
17
  fetch(pathname, options = {}) {
14
18
  throw new AlephaError("Fetch is not available for this environment.");
@@ -19,7 +23,7 @@ var ReactPageService = class {
19
23
  };
20
24
 
21
25
  //#endregion
22
- //#region ../../src/core/primitives/$page.ts
26
+ //#region ../../src/router/primitives/$page.ts
23
27
  /**
24
28
  * Main primitive for defining a React route in the application.
25
29
  *
@@ -144,62 +148,44 @@ var PagePrimitive = class extends Primitive {
144
148
  $page[KIND] = PagePrimitive;
145
149
 
146
150
  //#endregion
147
- //#region ../../src/core/components/NotFound.tsx
148
- function NotFoundPage(props) {
149
- return /* @__PURE__ */ jsxs("div", {
150
- style: {
151
- width: "100%",
152
- minHeight: "90vh",
153
- boxSizing: "border-box",
154
- display: "flex",
155
- flexDirection: "column",
156
- justifyContent: "center",
157
- alignItems: "center",
158
- textAlign: "center",
159
- fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif",
160
- padding: "2rem",
161
- ...props.style
162
- },
163
- children: [/* @__PURE__ */ jsx("div", {
164
- style: {
165
- fontSize: "6rem",
166
- fontWeight: 200,
167
- lineHeight: 1
168
- },
169
- children: "404"
170
- }), /* @__PURE__ */ jsx("div", {
171
- style: {
172
- fontSize: "0.875rem",
173
- marginTop: "1rem",
174
- opacity: .6
175
- },
176
- children: "Page not found"
177
- })]
178
- });
179
- }
180
-
181
- //#endregion
182
- //#region ../../src/core/components/ClientOnly.tsx
151
+ //#region ../../src/router/components/NotFound.tsx
183
152
  /**
184
- * A small utility component that renders its children only on the client side.
185
- *
186
- * Optionally, you can provide a fallback React node that will be rendered.
187
- *
188
- * You should use this component when
189
- * - you have code that relies on browser-specific APIs
190
- * - you want to avoid server-side rendering for a specific part of your application
191
- * - you want to prevent pre-rendering of a component
153
+ * Default 404 Not Found page component.
192
154
  */
193
- const ClientOnly = (props) => {
194
- const [mounted, setMounted] = useState(false);
195
- useEffect(() => setMounted(true), []);
196
- if (props.disabled) return props.children;
197
- return mounted ? props.children : props.fallback;
198
- };
199
- var ClientOnly_default = ClientOnly;
155
+ const NotFound = (props) => /* @__PURE__ */ jsxs("div", {
156
+ style: {
157
+ width: "100%",
158
+ minHeight: "90vh",
159
+ boxSizing: "border-box",
160
+ display: "flex",
161
+ flexDirection: "column",
162
+ justifyContent: "center",
163
+ alignItems: "center",
164
+ textAlign: "center",
165
+ fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif",
166
+ padding: "2rem",
167
+ ...props.style
168
+ },
169
+ children: [/* @__PURE__ */ jsx("div", {
170
+ style: {
171
+ fontSize: "6rem",
172
+ fontWeight: 200,
173
+ lineHeight: 1
174
+ },
175
+ children: "404"
176
+ }), /* @__PURE__ */ jsx("div", {
177
+ style: {
178
+ fontSize: "0.875rem",
179
+ marginTop: "1rem",
180
+ opacity: .6
181
+ },
182
+ children: "Page not found"
183
+ })]
184
+ });
185
+ var NotFound_default = NotFound;
200
186
 
201
187
  //#endregion
202
- //#region ../../src/core/components/ErrorViewer.tsx
188
+ //#region ../../src/router/components/ErrorViewer.tsx
203
189
  /**
204
190
  * Error viewer component that displays error details in development mode
205
191
  */
@@ -585,17 +571,30 @@ const styles = {
585
571
  };
586
572
 
587
573
  //#endregion
588
- //#region ../../src/core/contexts/RouterLayerContext.ts
574
+ //#region ../../src/router/contexts/RouterLayerContext.ts
589
575
  const RouterLayerContext = createContext(void 0);
590
576
 
591
577
  //#endregion
592
- //#region ../../src/core/errors/Redirection.ts
578
+ //#region ../../src/router/errors/Redirection.ts
593
579
  /**
594
580
  * Used for Redirection during the page loading.
595
581
  *
596
582
  * Depends on the context, it can be thrown or just returned.
583
+ *
584
+ * @example
585
+ * ```ts
586
+ * import { Redirection } from "@alepha/react";
587
+ *
588
+ * const MyPage = $page({
589
+ * resolve: async () => {
590
+ * if (needRedirect) {
591
+ * throw new Redirection("/new-path");
592
+ * }
593
+ * },
594
+ * });
595
+ * ```
597
596
  */
598
- var Redirection = class extends Error {
597
+ var Redirection = class extends AlephaError {
599
598
  redirect;
600
599
  constructor(redirect) {
601
600
  super("Redirection");
@@ -604,88 +603,7 @@ var Redirection = class extends Error {
604
603
  };
605
604
 
606
605
  //#endregion
607
- //#region ../../src/core/contexts/AlephaContext.ts
608
- const AlephaContext = createContext(void 0);
609
-
610
- //#endregion
611
- //#region ../../src/core/hooks/useAlepha.ts
612
- /**
613
- * Main Alepha hook.
614
- *
615
- * It provides access to the Alepha instance within a React component.
616
- *
617
- * With Alepha, you can access the core functionalities of the framework:
618
- *
619
- * - alepha.state() for state management
620
- * - alepha.inject() for dependency injection
621
- * - alepha.events.emit() for event handling
622
- * etc...
623
- */
624
- const useAlepha = () => {
625
- const alepha = useContext(AlephaContext);
626
- if (!alepha) throw new AlephaError("Hook 'useAlepha()' must be used within an AlephaContext.Provider");
627
- return alepha;
628
- };
629
-
630
- //#endregion
631
- //#region ../../src/core/hooks/useEvents.ts
632
- /**
633
- * Allow subscribing to multiple Alepha events. See {@link Hooks} for available events.
634
- *
635
- * useEvents is fully typed to ensure correct event callback signatures.
636
- *
637
- * @example
638
- * ```tsx
639
- * useEvents(
640
- * {
641
- * "react:transition:begin": (ev) => {
642
- * console.log("Transition began to:", ev.to);
643
- * },
644
- * "react:transition:error": {
645
- * priority: "first",
646
- * callback: (ev) => {
647
- * console.error("Transition error:", ev.error);
648
- * },
649
- * },
650
- * },
651
- * [],
652
- * );
653
- * ```
654
- */
655
- const useEvents = (opts, deps) => {
656
- const alepha = useAlepha();
657
- useEffect(() => {
658
- if (!alepha.isBrowser()) return;
659
- const subs = [];
660
- for (const [name, hook] of Object.entries(opts)) subs.push(alepha.events.on(name, hook));
661
- return () => {
662
- for (const clear of subs) clear();
663
- };
664
- }, deps);
665
- };
666
-
667
- //#endregion
668
- //#region ../../src/core/hooks/useStore.ts
669
- function useStore(target, defaultValue) {
670
- const alepha = useAlepha();
671
- useMemo(() => {
672
- if (defaultValue != null && alepha.store.get(target) == null) alepha.store.set(target, defaultValue);
673
- }, [defaultValue]);
674
- const [state, setState] = useState(alepha.store.get(target));
675
- useEffect(() => {
676
- if (!alepha.isBrowser()) return;
677
- const key = target instanceof Atom ? target.key : target;
678
- return alepha.events.on("state:mutate", (ev) => {
679
- if (ev.key === key) setState(ev.value);
680
- });
681
- }, []);
682
- return [state, (value) => {
683
- alepha.store.set(target, value);
684
- }];
685
- }
686
-
687
- //#endregion
688
- //#region ../../src/core/hooks/useRouterState.ts
606
+ //#region ../../src/router/hooks/useRouterState.ts
689
607
  const useRouterState = () => {
690
608
  const [state] = useStore("alepha.react.router.state");
691
609
  if (!state) throw new AlephaError("Missing react router state");
@@ -693,38 +611,7 @@ const useRouterState = () => {
693
611
  };
694
612
 
695
613
  //#endregion
696
- //#region ../../src/core/components/ErrorBoundary.tsx
697
- /**
698
- * A reusable error boundary for catching rendering errors
699
- * in any part of the React component tree.
700
- */
701
- var ErrorBoundary = class extends React.Component {
702
- constructor(props) {
703
- super(props);
704
- this.state = {};
705
- }
706
- /**
707
- * Update state so the next render shows the fallback UI.
708
- */
709
- static getDerivedStateFromError(error) {
710
- return { error };
711
- }
712
- /**
713
- * Lifecycle method called when an error is caught.
714
- * You can log the error or perform side effects here.
715
- */
716
- componentDidCatch(error, info) {
717
- if (this.props.onError) this.props.onError(error, info);
718
- }
719
- render() {
720
- if (this.state.error) return this.props.fallback(this.state.error);
721
- return this.props.children;
722
- }
723
- };
724
- var ErrorBoundary_default = ErrorBoundary;
725
-
726
- //#endregion
727
- //#region ../../src/core/components/NestedView.tsx
614
+ //#region ../../src/router/components/NestedView.tsx
728
615
  /**
729
616
  * A component that renders the current view of the nested router layer.
730
617
  *
@@ -809,7 +696,7 @@ const NestedView = (props) => {
809
696
  })
810
697
  });
811
698
  if (props.errorBoundary === false) return /* @__PURE__ */ jsx(Fragment, { children: element });
812
- if (props.errorBoundary) return /* @__PURE__ */ jsx(ErrorBoundary_default, {
699
+ if (props.errorBoundary) return /* @__PURE__ */ jsx(ErrorBoundary, {
813
700
  fallback: props.errorBoundary,
814
701
  children: element
815
702
  });
@@ -821,7 +708,7 @@ const NestedView = (props) => {
821
708
  if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
822
709
  return result;
823
710
  };
824
- return /* @__PURE__ */ jsx(ErrorBoundary_default, {
711
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
825
712
  fallback,
826
713
  children: element
827
714
  });
@@ -854,8 +741,11 @@ function parseAnimation(animationLike, state, type = "enter") {
854
741
  }
855
742
 
856
743
  //#endregion
857
- //#region ../../src/core/providers/ReactPageProvider.ts
744
+ //#region ../../src/router/providers/ReactPageProvider.ts
858
745
  const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
746
+ /**
747
+ * Handle page routes for React applications. (Browser and Server)
748
+ */
859
749
  var ReactPageProvider = class {
860
750
  log = $logger();
861
751
  env = $env(envSchema$1);
@@ -1097,7 +987,7 @@ var ReactPageProvider = class {
1097
987
  }
1098
988
  renderView(index, path, view, page) {
1099
989
  view ??= this.renderEmptyView();
1100
- const element = page.client ? createElement(ClientOnly_default, typeof page.client === "object" ? page.client : {}, view) : view;
990
+ const element = page.client ? createElement(ClientOnly, typeof page.client === "object" ? page.client : {}, view) : view;
1101
991
  return createElement(RouterLayerContext.Provider, { value: {
1102
992
  index,
1103
993
  path,
@@ -1122,7 +1012,7 @@ var ReactPageProvider = class {
1122
1012
  path: "/*",
1123
1013
  name: "notFound",
1124
1014
  cache: true,
1125
- component: NotFoundPage,
1015
+ component: NotFound_default,
1126
1016
  onServerResponse: ({ reply }) => {
1127
1017
  reply.status = 404;
1128
1018
  }
@@ -1177,7 +1067,10 @@ const isPageRoute = (it) => {
1177
1067
  };
1178
1068
 
1179
1069
  //#endregion
1180
- //#region ../../src/core/providers/ReactBrowserRouterProvider.ts
1070
+ //#region ../../src/router/providers/ReactBrowserRouterProvider.ts
1071
+ /**
1072
+ * Implementation of AlephaRouter for React in browser environment.
1073
+ */
1181
1074
  var ReactBrowserRouterProvider = class extends RouterProvider {
1182
1075
  log = $logger();
1183
1076
  alepha = $inject(Alepha);
@@ -1222,7 +1115,7 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
1222
1115
  }
1223
1116
  if (state.layers.length === 0) state.layers.push({
1224
1117
  name: "not-found",
1225
- element: createElement(NotFoundPage),
1118
+ element: createElement(NotFound_default),
1226
1119
  index: 0,
1227
1120
  path: "/"
1228
1121
  });
@@ -1259,7 +1152,7 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
1259
1152
  };
1260
1153
 
1261
1154
  //#endregion
1262
- //#region ../../src/core/providers/ReactBrowserProvider.ts
1155
+ //#region ../../src/router/providers/ReactBrowserProvider.ts
1263
1156
  const envSchema = t.object({ REACT_ROOT_ID: t.text({ default: "root" }) });
1264
1157
  /**
1265
1158
  * React browser renderer configuration atom
@@ -1424,27 +1317,12 @@ var ReactBrowserProvider = class {
1424
1317
  };
1425
1318
 
1426
1319
  //#endregion
1427
- //#region ../../src/core/providers/ReactBrowserRendererProvider.ts
1428
- var ReactBrowserRendererProvider = class {
1429
- log = $logger();
1430
- root;
1431
- onBrowserRender = $hook({
1432
- on: "react:browser:render",
1433
- handler: async ({ hydration, root, element }) => {
1434
- if (hydration?.layers) {
1435
- this.root = hydrateRoot(root, element);
1436
- this.log.info("Hydrated root element");
1437
- } else {
1438
- this.root ??= createRoot(root);
1439
- this.root.render(element);
1440
- this.log.info("Created root element");
1441
- }
1442
- }
1443
- });
1444
- };
1445
-
1446
- //#endregion
1447
- //#region ../../src/core/services/ReactRouter.ts
1320
+ //#region ../../src/router/services/ReactRouter.ts
1321
+ /**
1322
+ * Friendly browser router API.
1323
+ *
1324
+ * Can be safely used server-side, but most methods will be no-op.
1325
+ */
1448
1326
  var ReactRouter = class {
1449
1327
  alepha = $inject(Alepha);
1450
1328
  pageApi = $inject(ReactPageProvider);
@@ -1468,6 +1346,11 @@ var ReactRouter = class {
1468
1346
  }
1469
1347
  node(name, config = {}) {
1470
1348
  const page = this.pageApi.page(name);
1349
+ if (!page.lazy && !page.component) return {
1350
+ ...page,
1351
+ label: page.label ?? page.name,
1352
+ children: void 0
1353
+ };
1471
1354
  return {
1472
1355
  ...page,
1473
1356
  label: page.label ?? page.name,
@@ -1566,311 +1449,30 @@ var ReactRouter = class {
1566
1449
  };
1567
1450
 
1568
1451
  //#endregion
1569
- //#region ../../src/core/contexts/AlephaProvider.tsx
1570
- /**
1571
- * AlephaProvider component to initialize and provide Alepha instance to the app.
1572
- * This isn't recommended for apps using alepha/react/router, as Router will handle this for you.
1573
- */
1574
- const AlephaProvider = (props) => {
1575
- const alepha = useMemo(() => Alepha.create(), []);
1576
- const [started, setStarted] = useState(false);
1577
- const [error, setError] = useState();
1578
- useEffect(() => {
1579
- alepha.start().then(() => setStarted(true)).catch((err) => setError(err));
1580
- }, [alepha]);
1581
- if (error) return props.onError(error);
1582
- if (!started) return props.onLoading();
1583
- return /* @__PURE__ */ jsx(AlephaContext.Provider, {
1584
- value: alepha,
1585
- children: props.children
1586
- });
1587
- };
1588
-
1589
- //#endregion
1590
- //#region ../../src/core/hooks/useInject.ts
1591
- /**
1592
- * Hook to inject a service instance.
1593
- * It's a wrapper of `useAlepha().inject(service)` with a memoization.
1594
- */
1595
- const useInject = (service) => {
1596
- const alepha = useAlepha();
1597
- return useMemo(() => alepha.inject(service), []);
1598
- };
1599
-
1600
- //#endregion
1601
- //#region ../../src/core/hooks/useAction.ts
1602
- /**
1603
- * Hook for handling async actions with automatic error handling and event emission.
1604
- *
1605
- * By default, prevents concurrent executions - if an action is running and you call it again,
1606
- * the second call will be ignored. Use `debounce` option to delay execution instead.
1607
- *
1608
- * Emits lifecycle events:
1609
- * - `react:action:begin` - When action starts
1610
- * - `react:action:success` - When action completes successfully
1611
- * - `react:action:error` - When action throws an error
1612
- * - `react:action:end` - Always emitted at the end
1613
- *
1614
- * @example Basic usage
1615
- * ```tsx
1616
- * const action = useAction({
1617
- * handler: async (data) => {
1618
- * await api.save(data);
1619
- * }
1620
- * }, []);
1621
- *
1622
- * <button onClick={() => action.run(data)} disabled={action.loading}>
1623
- * Save
1624
- * </button>
1625
- * ```
1626
- *
1627
- * @example With debounce (search input)
1628
- * ```tsx
1629
- * const search = useAction({
1630
- * handler: async (query: string) => {
1631
- * await api.search(query);
1632
- * },
1633
- * debounce: 300 // Wait 300ms after last call
1634
- * }, []);
1635
- *
1636
- * <input onChange={(e) => search.run(e.target.value)} />
1637
- * ```
1638
- *
1639
- * @example Run on component mount
1640
- * ```tsx
1641
- * const fetchData = useAction({
1642
- * handler: async () => {
1643
- * const data = await api.getData();
1644
- * return data;
1645
- * },
1646
- * runOnInit: true // Runs once when component mounts
1647
- * }, []);
1648
- * ```
1649
- *
1650
- * @example Run periodically (polling)
1651
- * ```tsx
1652
- * const pollStatus = useAction({
1653
- * handler: async () => {
1654
- * const status = await api.getStatus();
1655
- * return status;
1656
- * },
1657
- * runEvery: 5000 // Run every 5 seconds
1658
- * }, []);
1659
- *
1660
- * // Or with duration tuple
1661
- * const pollStatus = useAction({
1662
- * handler: async () => {
1663
- * const status = await api.getStatus();
1664
- * return status;
1665
- * },
1666
- * runEvery: [30, 'seconds'] // Run every 30 seconds
1667
- * }, []);
1668
- * ```
1669
- *
1670
- * @example With AbortController
1671
- * ```tsx
1672
- * const fetch = useAction({
1673
- * handler: async (url, { signal }) => {
1674
- * const response = await fetch(url, { signal });
1675
- * return response.json();
1676
- * }
1677
- * }, []);
1678
- * // Automatically cancelled on unmount or when new request starts
1679
- * ```
1680
- *
1681
- * @example With error handling
1682
- * ```tsx
1683
- * const deleteAction = useAction({
1684
- * handler: async (id: string) => {
1685
- * await api.delete(id);
1686
- * },
1687
- * onError: (error) => {
1688
- * if (error.code === 'NOT_FOUND') {
1689
- * // Custom error handling
1690
- * }
1691
- * }
1692
- * }, []);
1693
- *
1694
- * {deleteAction.error && <div>Error: {deleteAction.error.message}</div>}
1695
- * ```
1696
- *
1697
- * @example Global error handling
1698
- * ```tsx
1699
- * // In your root app setup
1700
- * alepha.events.on("react:action:error", ({ error }) => {
1701
- * toast.danger(error.message);
1702
- * Sentry.captureException(error);
1703
- * });
1704
- * ```
1705
- */
1706
- function useAction(options, deps) {
1707
- const alepha = useAlepha();
1708
- const dateTimeProvider = useInject(DateTimeProvider);
1709
- const [loading, setLoading] = useState(false);
1710
- const [error, setError] = useState();
1711
- const isExecutingRef = useRef(false);
1712
- const debounceTimerRef = useRef(void 0);
1713
- const abortControllerRef = useRef(void 0);
1714
- const isMountedRef = useRef(true);
1715
- const intervalRef = useRef(void 0);
1716
- useEffect(() => {
1717
- return () => {
1718
- isMountedRef.current = false;
1719
- if (debounceTimerRef.current) {
1720
- dateTimeProvider.clearTimeout(debounceTimerRef.current);
1721
- debounceTimerRef.current = void 0;
1722
- }
1723
- if (intervalRef.current) {
1724
- dateTimeProvider.clearInterval(intervalRef.current);
1725
- intervalRef.current = void 0;
1726
- }
1727
- if (abortControllerRef.current) {
1728
- abortControllerRef.current.abort();
1729
- abortControllerRef.current = void 0;
1730
- }
1731
- };
1732
- }, []);
1733
- const executeAction = useCallback(async (...args) => {
1734
- if (isExecutingRef.current) return;
1735
- if (abortControllerRef.current) abortControllerRef.current.abort();
1736
- const abortController = new AbortController();
1737
- abortControllerRef.current = abortController;
1738
- isExecutingRef.current = true;
1739
- setLoading(true);
1740
- setError(void 0);
1741
- await alepha.events.emit("react:action:begin", {
1742
- type: "custom",
1743
- id: options.id
1744
- });
1745
- try {
1746
- const result = await options.handler(...args, { signal: abortController.signal });
1747
- if (!isMountedRef.current || abortController.signal.aborted) return;
1748
- await alepha.events.emit("react:action:success", {
1749
- type: "custom",
1750
- id: options.id
1751
- });
1752
- if (options.onSuccess) await options.onSuccess(result);
1753
- return result;
1754
- } catch (err) {
1755
- if (err instanceof Error && err.name === "AbortError") return;
1756
- if (!isMountedRef.current) return;
1757
- const error$1 = err;
1758
- setError(error$1);
1759
- await alepha.events.emit("react:action:error", {
1760
- type: "custom",
1761
- id: options.id,
1762
- error: error$1
1763
- });
1764
- if (options.onError) await options.onError(error$1);
1765
- else throw error$1;
1766
- } finally {
1767
- isExecutingRef.current = false;
1768
- setLoading(false);
1769
- await alepha.events.emit("react:action:end", {
1770
- type: "custom",
1771
- id: options.id
1772
- });
1773
- if (abortControllerRef.current === abortController) abortControllerRef.current = void 0;
1774
- }
1775
- }, [
1776
- ...deps,
1777
- options.id,
1778
- options.onError,
1779
- options.onSuccess
1780
- ]);
1781
- const handler = useCallback(async (...args) => {
1782
- if (options.debounce) {
1783
- if (debounceTimerRef.current) dateTimeProvider.clearTimeout(debounceTimerRef.current);
1784
- return new Promise((resolve) => {
1785
- debounceTimerRef.current = dateTimeProvider.createTimeout(async () => {
1786
- resolve(await executeAction(...args));
1787
- }, options.debounce ?? 0);
1788
- });
1789
- }
1790
- return executeAction(...args);
1791
- }, [executeAction, options.debounce]);
1792
- const cancel = useCallback(() => {
1793
- if (debounceTimerRef.current) {
1794
- dateTimeProvider.clearTimeout(debounceTimerRef.current);
1795
- debounceTimerRef.current = void 0;
1796
- }
1797
- if (abortControllerRef.current) {
1798
- abortControllerRef.current.abort();
1799
- abortControllerRef.current = void 0;
1800
- }
1801
- if (isMountedRef.current) {
1802
- isExecutingRef.current = false;
1803
- setLoading(false);
1804
- }
1805
- }, []);
1806
- useEffect(() => {
1807
- if (options.runOnInit) handler(...[]);
1808
- }, deps);
1809
- useEffect(() => {
1810
- if (!options.runEvery) return;
1811
- intervalRef.current = dateTimeProvider.createInterval(() => handler(...[]), options.runEvery, true);
1812
- return () => {
1813
- if (intervalRef.current) {
1814
- dateTimeProvider.clearInterval(intervalRef.current);
1815
- intervalRef.current = void 0;
1816
- }
1817
- };
1818
- }, [handler, options.runEvery]);
1819
- return {
1820
- run: handler,
1821
- loading,
1822
- error,
1823
- cancel
1824
- };
1825
- }
1826
-
1827
- //#endregion
1828
- //#region ../../src/core/hooks/useClient.ts
1829
- /**
1830
- * Hook to get a virtual client for the specified scope.
1831
- *
1832
- * It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
1833
- */
1834
- const useClient = (scope) => {
1835
- return useInject(LinkProvider).client(scope);
1836
- };
1837
-
1838
- //#endregion
1839
- //#region ../../src/core/hooks/useSchema.ts
1840
- const useSchema = (action) => {
1841
- const name = action.name;
1842
- const alepha = useAlepha();
1843
- const httpClient = useInject(HttpClient);
1844
- const [schema, setSchema] = useState(ssrSchemaLoading(alepha, name));
1845
- useEffect(() => {
1846
- if (!schema.loading) return;
1847
- httpClient.fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, { localCache: true }).then((it) => setSchema(it.data));
1848
- }, [name]);
1849
- return schema;
1850
- };
1452
+ //#region ../../src/router/providers/ReactBrowserRendererProvider.ts
1851
1453
  /**
1852
- * Get an action schema during server-side rendering (SSR) or client-side rendering (CSR).
1454
+ * Browser specific React renderer (react-dom/client interface)
1853
1455
  */
1854
- const ssrSchemaLoading = (alepha, name) => {
1855
- if (!alepha.isBrowser()) {
1856
- const linkProvider = alepha.inject(LinkProvider);
1857
- const can = linkProvider.getServerLinks().find((link) => link.name === name);
1858
- if (can) {
1859
- const schema$1 = linkProvider.links.find((it) => it.name === name)?.schema;
1860
- if (schema$1) {
1861
- can.schema = schema$1;
1862
- return schema$1;
1456
+ var ReactBrowserRendererProvider = class {
1457
+ log = $logger();
1458
+ root;
1459
+ onBrowserRender = $hook({
1460
+ on: "react:browser:render",
1461
+ handler: async ({ hydration, root, element }) => {
1462
+ if (hydration?.layers) {
1463
+ this.root = hydrateRoot(root, element);
1464
+ this.log.info("Hydrated root element");
1465
+ } else {
1466
+ this.root ??= createRoot(root);
1467
+ this.root.render(element);
1468
+ this.log.info("Created root element");
1863
1469
  }
1864
1470
  }
1865
- return { loading: true };
1866
- }
1867
- const schema = alepha.inject(LinkProvider).links.find((it) => it.name === name)?.schema;
1868
- if (schema) return schema;
1869
- return { loading: true };
1471
+ });
1870
1472
  };
1871
1473
 
1872
1474
  //#endregion
1873
- //#region ../../src/core/hooks/useRouter.ts
1475
+ //#region ../../src/router/hooks/useRouter.ts
1874
1476
  /**
1875
1477
  * Use this hook to access the React Router instance.
1876
1478
  *
@@ -1890,23 +1492,31 @@ const useRouter = () => {
1890
1492
  };
1891
1493
 
1892
1494
  //#endregion
1893
- //#region ../../src/core/components/Link.tsx
1495
+ //#region ../../src/router/components/Link.tsx
1496
+ /**
1497
+ * Link component for client-side navigation.
1498
+ *
1499
+ * It's a simple wrapper around an anchor (`<a>`) element using the `useRouter` hook.
1500
+ */
1894
1501
  const Link = (props) => {
1895
1502
  const router = useRouter();
1896
- return /* @__PURE__ */ jsx("a", {
1503
+ return createElement("a", {
1897
1504
  ...props,
1898
- ...router.anchor(props.href),
1899
- children: props.children
1900
- });
1505
+ ...router.anchor(props.href)
1506
+ }, props.children);
1901
1507
  };
1902
1508
  var Link_default = Link;
1903
1509
 
1904
1510
  //#endregion
1905
- //#region ../../src/core/hooks/useActive.ts
1511
+ //#region ../../src/router/hooks/useActive.ts
1512
+ /**
1513
+ * Hook to determine if a given route is active and to provide anchor props for navigation.
1514
+ * This hook refreshes on router state changes.
1515
+ */
1906
1516
  const useActive = (args) => {
1517
+ useRouterState();
1907
1518
  const router = useRouter();
1908
1519
  const [isPending, setPending] = useState(false);
1909
- useRouterState().url.pathname;
1910
1520
  const options = typeof args === "string" ? { href: args } : {
1911
1521
  ...args,
1912
1522
  href: args.href
@@ -1935,9 +1545,9 @@ const useActive = (args) => {
1935
1545
  };
1936
1546
 
1937
1547
  //#endregion
1938
- //#region ../../src/core/hooks/useQueryParams.ts
1548
+ //#region ../../src/router/hooks/useQueryParams.ts
1939
1549
  /**
1940
- * Not well tested. Use with caution.
1550
+ * Hook to manage query parameters in the URL using a defined schema.
1941
1551
  */
1942
1552
  const useQueryParams = (schema, options = {}) => {
1943
1553
  const alepha = useAlepha();
@@ -1970,9 +1580,9 @@ const decode = (alepha, schema, data) => {
1970
1580
  };
1971
1581
 
1972
1582
  //#endregion
1973
- //#region ../../src/core/index.browser.ts
1974
- const AlephaReact = $module({
1975
- name: "alepha.react",
1583
+ //#region ../../src/router/index.browser.ts
1584
+ const AlephaReactRouter = $module({
1585
+ name: "alepha.react.router",
1976
1586
  primitives: [$page],
1977
1587
  services: [
1978
1588
  ReactPageProvider,
@@ -1982,9 +1592,9 @@ const AlephaReact = $module({
1982
1592
  ReactBrowserRendererProvider,
1983
1593
  ReactPageService
1984
1594
  ],
1985
- register: (alepha) => alepha.with(AlephaDateTime).with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
1595
+ register: (alepha) => alepha.with(AlephaReact).with(AlephaDateTime).with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
1986
1596
  });
1987
1597
 
1988
1598
  //#endregion
1989
- export { $page, AlephaContext, AlephaProvider, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, ErrorViewer_default as ErrorViewer, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PagePrimitive, ReactBrowserProvider, ReactBrowserRouterProvider, ReactPageProvider, ReactRouter, Redirection, RouterLayerContext, isPageRoute, reactBrowserOptions, ssrSchemaLoading, useAction, useActive, useAlepha, useClient, useEvents, useInject, useQueryParams, useRouter, useRouterState, useSchema, useStore };
1599
+ export { $page, AlephaReactRouter, ErrorViewer_default as ErrorViewer, Link_default as Link, NestedView_default as NestedView, NotFound_default as NotFound, PagePrimitive, ReactBrowserProvider, ReactBrowserRendererProvider, ReactBrowserRouterProvider, ReactPageProvider, ReactPageService, ReactRouter, Redirection, RouterLayerContext, isPageRoute, reactBrowserOptions, useActive, useQueryParams, useRouter, useRouterState };
1990
1600
  //# sourceMappingURL=index.browser.js.map