@alepha/react 0.9.0 → 0.9.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.
package/README.md CHANGED
@@ -15,6 +15,7 @@ Alternatively, you can install it individually:
15
15
  ```bash
16
16
  npm install @alepha/core @alepha/react
17
17
  ```
18
+
18
19
  ## Module
19
20
 
20
21
  Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
@@ -30,3 +31,9 @@ type safety and schema validation for route parameters and data.
30
31
  #### $page()
31
32
 
32
33
  Main descriptor for defining a React route in the application.
34
+
35
+ ### Hooks
36
+
37
+ #### useStore()
38
+
39
+ Hook to access and mutate the Alepha state.
@@ -1,5 +1,5 @@
1
1
  import { $env, $hook, $inject, $logger, $module, Alepha, Descriptor, KIND, NotImplementedError, createDescriptor, t } from "@alepha/core";
2
- import { AlephaServer } from "@alepha/server";
2
+ import { AlephaServer, HttpClient } from "@alepha/server";
3
3
  import { AlephaServerLinks, LinkProvider } from "@alepha/server-links";
4
4
  import { RouterProvider } from "@alepha/router";
5
5
  import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
@@ -71,23 +71,11 @@ const ClientOnly = (props) => {
71
71
  };
72
72
  var ClientOnly_default = ClientOnly;
73
73
 
74
- //#endregion
75
- //#region src/contexts/RouterContext.ts
76
- const RouterContext = createContext(void 0);
77
-
78
- //#endregion
79
- //#region src/hooks/useAlepha.ts
80
- const useAlepha = () => {
81
- const routerContext = useContext(RouterContext);
82
- if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
83
- return routerContext.alepha;
84
- };
85
-
86
74
  //#endregion
87
75
  //#region src/components/ErrorViewer.tsx
88
- const ErrorViewer = ({ error }) => {
76
+ const ErrorViewer = ({ error, alepha }) => {
89
77
  const [expanded, setExpanded] = useState(false);
90
- const isProduction = useAlepha().isProduction();
78
+ const isProduction = alepha.isProduction();
91
79
  if (isProduction) return /* @__PURE__ */ jsx(ErrorViewerProduction, {});
92
80
  const stackLines = error.stack?.split("\n") ?? [];
93
81
  const previewLines = stackLines.slice(0, 5);
@@ -231,24 +219,39 @@ const ErrorViewerProduction = () => {
231
219
  });
232
220
  };
233
221
 
222
+ //#endregion
223
+ //#region src/contexts/RouterContext.ts
224
+ const RouterContext = createContext(void 0);
225
+
234
226
  //#endregion
235
227
  //#region src/contexts/RouterLayerContext.ts
236
228
  const RouterLayerContext = createContext(void 0);
237
229
 
230
+ //#endregion
231
+ //#region src/contexts/AlephaContext.ts
232
+ const AlephaContext = createContext(void 0);
233
+
234
+ //#endregion
235
+ //#region src/hooks/useAlepha.ts
236
+ const useAlepha = () => {
237
+ const alepha = useContext(AlephaContext);
238
+ if (!alepha) throw new Error("useAlepha must be used within an AlephaContext.Provider");
239
+ return alepha;
240
+ };
241
+
238
242
  //#endregion
239
243
  //#region src/hooks/useRouterEvents.ts
240
244
  const useRouterEvents = (opts = {}, deps = []) => {
241
- const ctx = useContext(RouterContext);
242
- if (!ctx) throw new Error("useRouter must be used within a RouterProvider");
245
+ const alepha = useAlepha();
243
246
  useEffect(() => {
244
- if (!ctx.alepha.isBrowser()) return;
247
+ if (!alepha.isBrowser()) return;
245
248
  const subs = [];
246
249
  const onBegin = opts.onBegin;
247
250
  const onEnd = opts.onEnd;
248
251
  const onError = opts.onError;
249
- if (onBegin) subs.push(ctx.alepha.on("react:transition:begin", { callback: onBegin }));
250
- if (onEnd) subs.push(ctx.alepha.on("react:transition:end", { callback: onEnd }));
251
- if (onError) subs.push(ctx.alepha.on("react:transition:error", { callback: onError }));
252
+ if (onBegin) subs.push(alepha.on("react:transition:begin", { callback: onBegin }));
253
+ if (onEnd) subs.push(alepha.on("react:transition:end", { callback: onEnd }));
254
+ if (onError) subs.push(alepha.on("react:transition:error", { callback: onError }));
252
255
  return () => {
253
256
  for (const sub of subs) sub();
254
257
  };
@@ -364,11 +367,10 @@ var PageDescriptorProvider = class {
364
367
  return new URL(url.replace(/\/\/+/g, "/") || "/", options.base ?? `http://localhost`);
365
368
  }
366
369
  root(state, context) {
367
- const root = createElement(RouterContext.Provider, { value: {
368
- alepha: this.alepha,
370
+ const root = createElement(AlephaContext.Provider, { value: this.alepha }, createElement(RouterContext.Provider, { value: {
369
371
  state,
370
372
  context
371
- } }, createElement(NestedView_default, {}, state.layers[0]?.element));
373
+ } }, createElement(NestedView_default, {}, state.layers[0]?.element)));
372
374
  if (this.env.REACT_STRICT_MODE) return createElement(StrictMode, {}, root);
373
375
  return root;
374
376
  }
@@ -514,7 +516,10 @@ var PageDescriptorProvider = class {
514
516
  return void 0;
515
517
  }
516
518
  renderError(error) {
517
- return createElement(ErrorViewer_default, { error });
519
+ return createElement(ErrorViewer_default, {
520
+ error,
521
+ alepha: this.alepha
522
+ });
518
523
  }
519
524
  renderEmptyView() {
520
525
  return createElement(NestedView_default, {});
@@ -812,7 +817,7 @@ var ReactBrowserProvider = class {
812
817
  hydration
813
818
  });
814
819
  window.addEventListener("popstate", () => {
815
- if (this.state.pathname === location.pathname) return;
820
+ if (this.state.pathname === this.url) return;
816
821
  this.render();
817
822
  });
818
823
  }
@@ -948,13 +953,14 @@ var RouterHookApi = class {
948
953
  //#endregion
949
954
  //#region src/hooks/useRouter.ts
950
955
  const useRouter = () => {
956
+ const alepha = useAlepha();
951
957
  const ctx = useContext(RouterContext);
952
958
  const layer = useContext(RouterLayerContext);
953
959
  if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
954
960
  const pages = useMemo(() => {
955
- return ctx.alepha.inject(PageDescriptorProvider).getPages();
961
+ return alepha.inject(PageDescriptorProvider).getPages();
956
962
  }, []);
957
- return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
963
+ return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : void 0), [layer]);
958
964
  };
959
965
 
960
966
  //#endregion
@@ -1015,10 +1021,9 @@ const useActive = (path) => {
1015
1021
 
1016
1022
  //#endregion
1017
1023
  //#region src/hooks/useInject.ts
1018
- const useInject = (clazz) => {
1019
- const ctx = useContext(RouterContext);
1020
- if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
1021
- return useMemo(() => ctx.alepha.inject(clazz), []);
1024
+ const useInject = (service) => {
1025
+ const alepha = useAlepha();
1026
+ return useMemo(() => alepha.inject(service), []);
1022
1027
  };
1023
1028
 
1024
1029
  //#endregion
@@ -1030,21 +1035,20 @@ const useClient = (_scope) => {
1030
1035
  //#endregion
1031
1036
  //#region src/hooks/useQueryParams.ts
1032
1037
  const useQueryParams = (schema, options = {}) => {
1033
- const ctx = useContext(RouterContext);
1034
- if (!ctx) throw new Error("useQueryParams must be used within a RouterProvider");
1038
+ const alepha = useAlepha();
1035
1039
  const key = options.key ?? "q";
1036
1040
  const router = useRouter();
1037
1041
  const querystring = router.query[key];
1038
- const [queryParams, setQueryParams] = useState(decode(ctx.alepha, schema, router.query[key]));
1042
+ const [queryParams, setQueryParams] = useState(decode(alepha, schema, router.query[key]));
1039
1043
  useEffect(() => {
1040
- setQueryParams(decode(ctx.alepha, schema, querystring));
1044
+ setQueryParams(decode(alepha, schema, querystring));
1041
1045
  }, [querystring]);
1042
1046
  return [queryParams, (queryParams$1) => {
1043
1047
  setQueryParams(queryParams$1);
1044
1048
  router.setQueryParams((data) => {
1045
1049
  return {
1046
1050
  ...data,
1047
- [key]: encode(ctx.alepha, schema, queryParams$1)
1051
+ [key]: encode(alepha, schema, queryParams$1)
1048
1052
  };
1049
1053
  });
1050
1054
  }];
@@ -1063,14 +1067,73 @@ const decode = (alepha, schema, data) => {
1063
1067
  //#endregion
1064
1068
  //#region src/hooks/useRouterState.ts
1065
1069
  const useRouterState = () => {
1066
- const ctx = useContext(RouterContext);
1070
+ const router = useContext(RouterContext);
1067
1071
  const layer = useContext(RouterLayerContext);
1068
- if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1069
- const [state, setState] = useState(ctx.state);
1072
+ if (!router || !layer) throw new Error("useRouterState must be used within a RouterContext.Provider");
1073
+ const [state, setState] = useState(router.state);
1070
1074
  useRouterEvents({ onEnd: ({ state: state$1 }) => setState({ ...state$1 }) });
1071
1075
  return state;
1072
1076
  };
1073
1077
 
1078
+ //#endregion
1079
+ //#region src/hooks/useSchema.ts
1080
+ const useSchema = (action) => {
1081
+ const name = action.name;
1082
+ const alepha = useAlepha();
1083
+ const httpClient = useInject(HttpClient);
1084
+ const linkProvider = useInject(LinkProvider);
1085
+ const [schema, setSchema] = useState(ssrSchemaLoading(alepha, name));
1086
+ useEffect(() => {
1087
+ if (!schema.loading) return;
1088
+ const opts = { cache: true };
1089
+ httpClient.fetch(`${linkProvider.URL_LINKS}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
1090
+ }, [name]);
1091
+ return schema;
1092
+ };
1093
+ /**
1094
+ * Get an action schema during server-side rendering (SSR) or client-side rendering (CSR).
1095
+ */
1096
+ const ssrSchemaLoading = (alepha, name) => {
1097
+ if (!alepha.isBrowser()) {
1098
+ const links = alepha.context.get("links")?.links ?? [];
1099
+ const can = links.find((it) => it.name === name);
1100
+ if (can) {
1101
+ const schema$1 = alepha.inject(LinkProvider).links?.find((it) => it.name === name)?.schema;
1102
+ if (schema$1) {
1103
+ can.schema = schema$1;
1104
+ return schema$1;
1105
+ }
1106
+ }
1107
+ return { loading: true };
1108
+ }
1109
+ const schema = alepha.inject(LinkProvider).links?.find((it) => it.name === name)?.schema;
1110
+ if (schema) return schema;
1111
+ return { loading: true };
1112
+ };
1113
+
1114
+ //#endregion
1115
+ //#region src/hooks/useStore.ts
1116
+ /**
1117
+ * Hook to access and mutate the Alepha state.
1118
+ */
1119
+ const useStore = (key) => {
1120
+ const alepha = useAlepha();
1121
+ const [state, setState] = useState(alepha.state(key));
1122
+ useEffect(() => {
1123
+ if (!alepha.isBrowser()) return;
1124
+ return alepha.on("state:mutate", (ev) => {
1125
+ if (ev.key === key) setState(ev.value);
1126
+ });
1127
+ }, []);
1128
+ if (!alepha.isBrowser()) {
1129
+ const value = alepha.context.get(key);
1130
+ if (value !== null) return [value, (_) => {}];
1131
+ }
1132
+ return [state, (value) => {
1133
+ alepha.state(key, value);
1134
+ }];
1135
+ };
1136
+
1074
1137
  //#endregion
1075
1138
  //#region src/index.browser.ts
1076
1139
  const AlephaReact = $module({
@@ -1086,5 +1149,5 @@ const AlephaReact = $module({
1086
1149
  });
1087
1150
 
1088
1151
  //#endregion
1089
- export { $page, AlephaReact, BrowserRouterProvider, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
1152
+ export { $page, AlephaContext, AlephaReact, BrowserRouterProvider, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, ssrSchemaLoading, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState, useSchema, useStore };
1090
1153
  //# sourceMappingURL=index.browser.js.map