@alepha/react 0.9.1 → 0.9.3

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.cjs CHANGED
@@ -41,6 +41,12 @@ const $page = (options) => {
41
41
  return (0, __alepha_core.createDescriptor)(PageDescriptor, options);
42
42
  };
43
43
  var PageDescriptor = class extends __alepha_core.Descriptor {
44
+ onInit() {
45
+ if (this.options.static) this.options.cache ??= {
46
+ provider: "memory",
47
+ ttl: [1, "week"]
48
+ };
49
+ }
44
50
  get name() {
45
51
  return this.options.name ?? this.config.propertyKey;
46
52
  }
@@ -49,7 +55,7 @@ var PageDescriptor = class extends __alepha_core.Descriptor {
49
55
  * Only valid for server-side rendering, it will throw an error if called on the client-side.
50
56
  */
51
57
  async render(options) {
52
- throw new __alepha_core.NotImplementedError("");
58
+ throw new Error("render method is not implemented in this environment");
53
59
  }
54
60
  };
55
61
  $page[__alepha_core.KIND] = PageDescriptor;
@@ -103,7 +109,7 @@ const ErrorViewer = ({ error, alepha }) => {
103
109
  heading: {
104
110
  fontSize: "20px",
105
111
  fontWeight: "bold",
106
- marginBottom: "4px"
112
+ marginBottom: "10px"
107
113
  },
108
114
  name: {
109
115
  fontSize: "16px",
@@ -230,20 +236,31 @@ const RouterContext = (0, react.createContext)(void 0);
230
236
  //#region src/contexts/RouterLayerContext.ts
231
237
  const RouterLayerContext = (0, react.createContext)(void 0);
232
238
 
239
+ //#endregion
240
+ //#region src/contexts/AlephaContext.ts
241
+ const AlephaContext = (0, react.createContext)(void 0);
242
+
243
+ //#endregion
244
+ //#region src/hooks/useAlepha.ts
245
+ const useAlepha = () => {
246
+ const alepha = (0, react.useContext)(AlephaContext);
247
+ if (!alepha) throw new Error("useAlepha must be used within an AlephaContext.Provider");
248
+ return alepha;
249
+ };
250
+
233
251
  //#endregion
234
252
  //#region src/hooks/useRouterEvents.ts
235
253
  const useRouterEvents = (opts = {}, deps = []) => {
236
- const ctx = (0, react.useContext)(RouterContext);
237
- if (!ctx) throw new Error("useRouter must be used within a RouterProvider");
254
+ const alepha = useAlepha();
238
255
  (0, react.useEffect)(() => {
239
- if (!ctx.alepha.isBrowser()) return;
256
+ if (!alepha.isBrowser()) return;
240
257
  const subs = [];
241
258
  const onBegin = opts.onBegin;
242
259
  const onEnd = opts.onEnd;
243
260
  const onError = opts.onError;
244
- if (onBegin) subs.push(ctx.alepha.on("react:transition:begin", { callback: onBegin }));
245
- if (onEnd) subs.push(ctx.alepha.on("react:transition:end", { callback: onEnd }));
246
- if (onError) subs.push(ctx.alepha.on("react:transition:error", { callback: onError }));
261
+ if (onBegin) subs.push(alepha.on("react:transition:begin", { callback: onBegin }));
262
+ if (onEnd) subs.push(alepha.on("react:transition:end", { callback: onEnd }));
263
+ if (onError) subs.push(alepha.on("react:transition:error", { callback: onError }));
247
264
  return () => {
248
265
  for (const sub of subs) sub();
249
266
  };
@@ -309,13 +326,16 @@ const NestedView = (props) => {
309
326
  const layer = (0, react.useContext)(RouterLayerContext);
310
327
  const index = layer?.index ?? 0;
311
328
  const [view, setView] = (0, react.useState)(app?.state.layers[index]?.element);
312
- useRouterEvents({ onEnd: ({ state }) => {
329
+ useRouterEvents({ onEnd: ({ state, context }) => {
330
+ if (app) app.context = context;
313
331
  if (!state.layers[index]?.cache) setView(state.layers[index]?.element);
314
332
  } }, [app]);
315
333
  if (!app) throw new Error("NestedView must be used within a RouterContext.");
316
334
  const element = view ?? props.children ?? null;
317
335
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorBoundary_default, {
318
- fallback: app.context.onError,
336
+ fallback: (error) => {
337
+ return app.context.onError?.(error, app.context);
338
+ },
319
339
  children: element
320
340
  });
321
341
  };
@@ -323,7 +343,7 @@ var NestedView_default = NestedView;
323
343
 
324
344
  //#endregion
325
345
  //#region src/components/NotFound.tsx
326
- function NotFoundPage() {
346
+ function NotFoundPage(props) {
327
347
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
328
348
  style: {
329
349
  height: "100vh",
@@ -333,7 +353,8 @@ function NotFoundPage() {
333
353
  alignItems: "center",
334
354
  textAlign: "center",
335
355
  fontFamily: "sans-serif",
336
- padding: "1rem"
356
+ padding: "1rem",
357
+ ...props.style
337
358
  },
338
359
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
339
360
  style: {
@@ -346,8 +367,8 @@ function NotFoundPage() {
346
367
  }
347
368
 
348
369
  //#endregion
349
- //#region src/errors/RedirectionError.ts
350
- var RedirectionError = class extends Error {
370
+ //#region src/errors/Redirection.ts
371
+ var Redirection = class extends Error {
351
372
  page;
352
373
  constructor(page) {
353
374
  super("Redirection");
@@ -370,7 +391,7 @@ var PageDescriptorProvider = class {
370
391
  for (const page of this.pages) if (page.name === name) return page;
371
392
  throw new Error(`Page ${name} not found`);
372
393
  }
373
- url(name, options = {}) {
394
+ pathname(name, options = {}) {
374
395
  const page = this.page(name);
375
396
  if (!page) throw new Error(`Page ${name} not found`);
376
397
  let url = page.path ?? "";
@@ -380,14 +401,20 @@ var PageDescriptorProvider = class {
380
401
  parent = parent.parent;
381
402
  }
382
403
  url = this.compile(url, options.params ?? {});
383
- return new URL(url.replace(/\/\/+/g, "/") || "/", options.base ?? `http://localhost`);
404
+ if (options.query) {
405
+ const query = new URLSearchParams(options.query);
406
+ if (query.toString()) url += `?${query.toString()}`;
407
+ }
408
+ return url.replace(/\/\/+/g, "/") || "/";
409
+ }
410
+ url(name, options = {}) {
411
+ return new URL(this.pathname(name, options), options.base ?? `http://localhost`);
384
412
  }
385
413
  root(state, context) {
386
- const root = (0, react.createElement)(RouterContext.Provider, { value: {
387
- alepha: this.alepha,
414
+ const root = (0, react.createElement)(AlephaContext.Provider, { value: this.alepha }, (0, react.createElement)(RouterContext.Provider, { value: {
388
415
  state,
389
416
  context
390
- } }, (0, react.createElement)(NestedView_default, {}, state.layers[0]?.element));
417
+ } }, (0, react.createElement)(NestedView_default, {}, state.layers[0]?.element)));
391
418
  if (this.env.REACT_STRICT_MODE) return (0, react.createElement)(react.StrictMode, {}, root);
392
419
  return root;
393
420
  }
@@ -456,12 +483,10 @@ var PageDescriptorProvider = class {
456
483
  ...props
457
484
  };
458
485
  } catch (e) {
459
- if (e instanceof RedirectionError) return {
460
- layers: [],
461
- redirect: typeof e.page === "string" ? e.page : this.href(e.page),
486
+ if (e instanceof Redirection) return this.createRedirectionLayer(e.page, {
462
487
  pathname,
463
488
  search
464
- };
489
+ });
465
490
  this.log.error(e);
466
491
  it.error = e;
467
492
  break;
@@ -477,9 +502,21 @@ var PageDescriptorProvider = class {
477
502
  acc += it.route.path ? this.compile(it.route.path, params) : "";
478
503
  const path = acc.replace(/\/+/, "/");
479
504
  const localErrorHandler = this.getErrorHandler(it.route);
480
- if (localErrorHandler) request.onError = localErrorHandler;
481
- if (it.error) {
482
- let element$1 = await request.onError(it.error);
505
+ if (localErrorHandler) {
506
+ const onErrorParent = request.onError;
507
+ request.onError = (error, context$1) => {
508
+ const result = localErrorHandler(error, context$1);
509
+ if (result === void 0) return onErrorParent(error, context$1);
510
+ return result;
511
+ };
512
+ }
513
+ if (it.error) try {
514
+ let element$1 = await request.onError(it.error, request);
515
+ if (element$1 === void 0) throw it.error;
516
+ if (element$1 instanceof Redirection) return this.createRedirectionLayer(element$1.page, {
517
+ pathname,
518
+ search
519
+ });
483
520
  if (element$1 === null) element$1 = this.renderError(it.error);
484
521
  layers.push({
485
522
  props,
@@ -493,6 +530,12 @@ var PageDescriptorProvider = class {
493
530
  route: it.route
494
531
  });
495
532
  break;
533
+ } catch (e) {
534
+ if (e instanceof Redirection) return this.createRedirectionLayer(e.page, {
535
+ pathname,
536
+ search
537
+ });
538
+ throw e;
496
539
  }
497
540
  const element = await this.createElement(it.route, {
498
541
  ...props,
@@ -516,6 +559,14 @@ var PageDescriptorProvider = class {
516
559
  search
517
560
  };
518
561
  }
562
+ createRedirectionLayer(href, context) {
563
+ return {
564
+ layers: [],
565
+ redirect: typeof href === "string" ? href : this.href(href),
566
+ pathname: context.pathname,
567
+ search: context.search
568
+ };
569
+ }
519
570
  getErrorHandler(route) {
520
571
  if (route.errorHandler) return route.errorHandler;
521
572
  let parent = route.parent;
@@ -571,6 +622,7 @@ var PageDescriptorProvider = class {
571
622
  let hasNotFoundHandler = false;
572
623
  const pages = this.alepha.descriptors($page);
573
624
  const hasParent = (it) => {
625
+ if (it.options.parent) return true;
574
626
  for (const page of pages) {
575
627
  const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
576
628
  if (children.includes(it)) return true;
@@ -586,7 +638,7 @@ var PageDescriptorProvider = class {
586
638
  name: "notFound",
587
639
  cache: true,
588
640
  component: NotFoundPage,
589
- afterHandler: ({ reply }) => {
641
+ onServerResponse: ({ reply }) => {
590
642
  reply.status = 404;
591
643
  }
592
644
  });
@@ -594,6 +646,12 @@ var PageDescriptorProvider = class {
594
646
  });
595
647
  map(pages, target) {
596
648
  const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
649
+ const getChildrenFromParent = (it) => {
650
+ const children$1 = [];
651
+ for (const page of pages) if (page.options.parent === it) children$1.push(page);
652
+ return children$1;
653
+ };
654
+ children.push(...getChildrenFromParent(target));
597
655
  return {
598
656
  ...target.options,
599
657
  name: target.name,
@@ -715,6 +773,10 @@ var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
715
773
  options.state.pathname = state.pathname;
716
774
  options.state.search = state.search;
717
775
  }
776
+ if (options.previous) for (let i = 0; i < options.previous.length; i++) {
777
+ const layer = options.previous[i];
778
+ if (state.layers[i]?.name !== layer.name) this.pageDescriptorProvider.page(layer.name)?.onLeave?.();
779
+ }
718
780
  await this.alepha.emit("react:transition:end", {
719
781
  state: options.state,
720
782
  context
@@ -784,15 +846,11 @@ var ReactBrowserProvider = class {
784
846
  }
785
847
  async go(url, options = {}) {
786
848
  const result = await this.render({ url });
787
- if (result.context.url.pathname !== url) {
788
- this.pushState(result.context.url.pathname);
789
- return;
790
- }
791
- if (options.replace) {
792
- this.pushState(url);
849
+ if (result.context.url.pathname + result.context.url.search !== url) {
850
+ this.pushState(result.context.url.pathname + result.context.url.search);
793
851
  return;
794
852
  }
795
- this.pushState(url);
853
+ this.pushState(url, options.replace);
796
854
  }
797
855
  async render(options = {}) {
798
856
  const previous = options.previous ?? this.state.layers;
@@ -821,7 +879,13 @@ var ReactBrowserProvider = class {
821
879
  handler: async () => {
822
880
  const hydration = this.getHydrationState();
823
881
  const previous = hydration?.layers ?? [];
824
- if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
882
+ if (hydration) {
883
+ for (const [key, value] of Object.entries(hydration)) if (key !== "layers" && key !== "links") this.alepha.state(key, value);
884
+ }
885
+ if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink({
886
+ ...link,
887
+ prefix: hydration.links.prefix
888
+ });
825
889
  const { context } = await this.render({ previous });
826
890
  await this.alepha.emit("react:browser:render", {
827
891
  state: this.state,
@@ -949,6 +1013,7 @@ var ReactServerProvider = class {
949
1013
  html: (0, react_dom_server.renderToString)(this.pageDescriptorProvider.root(state, context))
950
1014
  };
951
1015
  const html = this.renderToHtml(this.template ?? "", state, context, options.hydration);
1016
+ if (html instanceof Redirection) throw new Error("Redirection is not supported in this context");
952
1017
  const result = {
953
1018
  context,
954
1019
  state,
@@ -963,6 +1028,7 @@ var ReactServerProvider = class {
963
1028
  const { url, reply, query, params } = serverRequest;
964
1029
  const template = await templateLoader();
965
1030
  if (!template) throw new Error("Template not found");
1031
+ this.log.trace("Rendering page", { name: page.name });
966
1032
  const context = {
967
1033
  url,
968
1034
  params,
@@ -988,6 +1054,10 @@ var ReactServerProvider = class {
988
1054
  }
989
1055
  target = target.parent;
990
1056
  }
1057
+ await this.alepha.emit("react:transition:begin", {
1058
+ request: serverRequest,
1059
+ context
1060
+ });
991
1061
  await this.alepha.emit("react:server:render:begin", {
992
1062
  request: serverRequest,
993
1063
  context
@@ -1002,14 +1072,20 @@ var ReactServerProvider = class {
1002
1072
  reply.headers.expires = "0";
1003
1073
  if (page.cache && serverRequest.user) delete context.links;
1004
1074
  const html = this.renderToHtml(template, state, context);
1005
- await this.alepha.emit("react:server:render:end", {
1075
+ if (html instanceof Redirection) {
1076
+ reply.redirect(typeof html.page === "string" ? html.page : this.pageDescriptorProvider.href(html.page));
1077
+ return;
1078
+ }
1079
+ const event = {
1006
1080
  request: serverRequest,
1007
1081
  context,
1008
1082
  state,
1009
1083
  html
1010
- });
1011
- page.afterHandler?.(serverRequest);
1012
- return html;
1084
+ };
1085
+ await this.alepha.emit("react:server:render:end", event);
1086
+ page.onServerResponse?.(serverRequest);
1087
+ this.log.trace("Page rendered", { name: page.name });
1088
+ return event.html;
1013
1089
  };
1014
1090
  }
1015
1091
  renderToHtml(template, state, context, hydration = true) {
@@ -1020,20 +1096,23 @@ var ReactServerProvider = class {
1020
1096
  app = (0, react_dom_server.renderToString)(element);
1021
1097
  } catch (error) {
1022
1098
  this.log.error("Error during SSR", error);
1023
- app = (0, react_dom_server.renderToString)(context.onError(error));
1099
+ const element$1 = context.onError(error, context);
1100
+ if (element$1 instanceof Redirection) return element$1;
1101
+ app = (0, react_dom_server.renderToString)(element$1);
1024
1102
  }
1025
1103
  this.serverTimingProvider.endTiming("renderToString");
1026
1104
  const response = { html: template };
1027
1105
  if (hydration) {
1106
+ const { request, context: context$1,...rest } = this.alepha.context.als?.getStore() ?? {};
1028
1107
  const hydrationData = {
1029
- links: context.links,
1108
+ ...rest,
1030
1109
  layers: state.layers.map((it) => ({
1031
1110
  ...it,
1032
1111
  error: it.error ? {
1033
1112
  ...it.error,
1034
1113
  name: it.error.name,
1035
1114
  message: it.error.message,
1036
- stack: it.error.stack
1115
+ stack: !this.alepha.isProduction() ? it.error.stack : void 0
1037
1116
  } : void 0,
1038
1117
  index: void 0,
1039
1118
  path: void 0,
@@ -1053,24 +1132,34 @@ var ReactServerProvider = class {
1053
1132
  else {
1054
1133
  const bodyOpenTag = /<body([^>]*)>/i;
1055
1134
  if (bodyOpenTag.test(response.html)) response.html = response.html.replace(bodyOpenTag, (match) => {
1056
- return `${match}\n<div id="${this.env.REACT_ROOT_ID}">${app}</div>`;
1135
+ return `${match}<div id="${this.env.REACT_ROOT_ID}">${app}</div>`;
1057
1136
  });
1058
1137
  }
1059
1138
  const bodyCloseTagRegex = /<\/body>/i;
1060
- if (bodyCloseTagRegex.test(response.html)) response.html = response.html.replace(bodyCloseTagRegex, `${script}\n</body>`);
1139
+ if (bodyCloseTagRegex.test(response.html)) response.html = response.html.replace(bodyCloseTagRegex, `${script}</body>`);
1061
1140
  }
1062
1141
  };
1063
1142
 
1064
1143
  //#endregion
1065
1144
  //#region src/hooks/RouterHookApi.ts
1066
1145
  var RouterHookApi = class {
1067
- constructor(pages, context, state, layer, browser) {
1146
+ constructor(pages, context, state, layer, pageApi, browser) {
1068
1147
  this.pages = pages;
1069
1148
  this.context = context;
1070
1149
  this.state = state;
1071
1150
  this.layer = layer;
1151
+ this.pageApi = pageApi;
1072
1152
  this.browser = browser;
1073
1153
  }
1154
+ path(name, config = {}) {
1155
+ return this.pageApi.pathname(name, {
1156
+ params: {
1157
+ ...this.context.params,
1158
+ ...config.params
1159
+ },
1160
+ query: config.query
1161
+ });
1162
+ }
1074
1163
  getURL() {
1075
1164
  if (!this.browser) return this.context.url;
1076
1165
  return new URL(this.location.href);
@@ -1112,23 +1201,23 @@ var RouterHookApi = class {
1112
1201
  }
1113
1202
  async go(path, options) {
1114
1203
  for (const page of this.pages) if (page.name === path) {
1115
- path = page.path ?? "";
1116
- break;
1204
+ await this.browser?.go(this.path(path, options), options);
1205
+ return;
1117
1206
  }
1118
- await this.browser?.go(this.createHref(path, this.layer, options), options);
1207
+ await this.browser?.go(path, options);
1119
1208
  }
1120
1209
  anchor(path, options = {}) {
1210
+ let href = path;
1121
1211
  for (const page of this.pages) if (page.name === path) {
1122
- path = page.path ?? "";
1212
+ href = this.path(path, options);
1123
1213
  break;
1124
1214
  }
1125
- const href = this.createHref(path, this.layer, options);
1126
1215
  return {
1127
1216
  href,
1128
1217
  onClick: (ev) => {
1129
1218
  ev.stopPropagation();
1130
1219
  ev.preventDefault();
1131
- this.go(path, options).catch(console.error);
1220
+ this.go(href, options).catch(console.error);
1132
1221
  }
1133
1222
  };
1134
1223
  }
@@ -1150,33 +1239,25 @@ var RouterHookApi = class {
1150
1239
  //#endregion
1151
1240
  //#region src/hooks/useRouter.ts
1152
1241
  const useRouter = () => {
1242
+ const alepha = useAlepha();
1153
1243
  const ctx = (0, react.useContext)(RouterContext);
1154
1244
  const layer = (0, react.useContext)(RouterLayerContext);
1155
1245
  if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1156
1246
  const pages = (0, react.useMemo)(() => {
1157
- return ctx.alepha.inject(PageDescriptorProvider).getPages();
1247
+ return alepha.inject(PageDescriptorProvider).getPages();
1158
1248
  }, []);
1159
- return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
1249
+ return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, alepha.inject(PageDescriptorProvider), alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : void 0), [layer]);
1160
1250
  };
1161
1251
 
1162
1252
  //#endregion
1163
1253
  //#region src/components/Link.tsx
1164
1254
  const Link = (props) => {
1165
- react.default.useContext(RouterContext);
1166
1255
  const router = useRouter();
1167
- const to = typeof props.to === "string" ? props.to : props.to.options.path;
1168
- if (!to) return null;
1169
- const can = typeof props.to === "string" ? void 0 : props.to.options.can;
1170
- if (can && !can()) return null;
1171
- const name = typeof props.to === "string" ? void 0 : props.to.options.name;
1172
- const anchorProps = {
1173
- ...props,
1174
- to: void 0
1175
- };
1256
+ const { to,...anchorProps } = props;
1176
1257
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1177
1258
  ...router.anchor(to),
1178
1259
  ...anchorProps,
1179
- children: props.children ?? name
1260
+ children: props.children
1180
1261
  });
1181
1262
  };
1182
1263
  var Link_default = Link;
@@ -1188,22 +1269,21 @@ const useActive = (path) => {
1188
1269
  const ctx = (0, react.useContext)(RouterContext);
1189
1270
  const layer = (0, react.useContext)(RouterLayerContext);
1190
1271
  if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1191
- let name;
1192
- if (typeof path === "object" && path.options.name) name = path.options.name;
1193
1272
  const [current, setCurrent] = (0, react.useState)(ctx.state.pathname);
1194
- const href = (0, react.useMemo)(() => router.createHref(path, layer), [path, layer]);
1273
+ const href = (0, react.useMemo)(() => router.createHref(path ?? "", layer), [path, layer]);
1195
1274
  const [isPending, setPending] = (0, react.useState)(false);
1196
- const isActive = current === href;
1197
- useRouterEvents({ onEnd: ({ state }) => setCurrent(state.pathname) });
1275
+ const isActive = current === href || current === `${href}/` || `${current}/` === href;
1276
+ useRouterEvents({ onEnd: ({ state }) => {
1277
+ path && setCurrent(state.pathname);
1278
+ } }, [path]);
1198
1279
  return {
1199
- name,
1200
1280
  isPending,
1201
1281
  isActive,
1202
1282
  anchorProps: {
1203
1283
  href,
1204
1284
  onClick: (ev) => {
1205
- ev.stopPropagation();
1206
- ev.preventDefault();
1285
+ ev?.stopPropagation();
1286
+ ev?.preventDefault();
1207
1287
  if (isActive) return;
1208
1288
  if (isPending) return;
1209
1289
  setPending(true);
@@ -1216,45 +1296,62 @@ const useActive = (path) => {
1216
1296
  };
1217
1297
 
1218
1298
  //#endregion
1219
- //#region src/hooks/useAlepha.ts
1220
- const useAlepha = () => {
1221
- const routerContext = (0, react.useContext)(RouterContext);
1222
- if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
1223
- return routerContext.alepha;
1299
+ //#region src/hooks/useInject.ts
1300
+ const useInject = (service) => {
1301
+ const alepha = useAlepha();
1302
+ return (0, react.useMemo)(() => alepha.inject(service), []);
1224
1303
  };
1225
1304
 
1226
1305
  //#endregion
1227
- //#region src/hooks/useInject.ts
1228
- const useInject = (clazz) => {
1229
- const ctx = (0, react.useContext)(RouterContext);
1230
- if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
1231
- return (0, react.useMemo)(() => ctx.alepha.inject(clazz), []);
1306
+ //#region src/hooks/useStore.ts
1307
+ /**
1308
+ * Hook to access and mutate the Alepha state.
1309
+ */
1310
+ const useStore = (key, defaultValue) => {
1311
+ const alepha = useAlepha();
1312
+ (0, react.useMemo)(() => {
1313
+ if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
1314
+ }, [defaultValue]);
1315
+ const [state, setState] = (0, react.useState)(alepha.state(key));
1316
+ (0, react.useEffect)(() => {
1317
+ if (!alepha.isBrowser()) return;
1318
+ return alepha.on("state:mutate", (ev) => {
1319
+ if (ev.key === key) setState(ev.value);
1320
+ });
1321
+ }, []);
1322
+ if (!alepha.isBrowser()) {
1323
+ const value = alepha.context.get(key);
1324
+ if (value !== null) return [value, (_) => {}];
1325
+ }
1326
+ return [state, (value) => {
1327
+ alepha.state(key, value);
1328
+ }];
1232
1329
  };
1233
1330
 
1234
1331
  //#endregion
1235
1332
  //#region src/hooks/useClient.ts
1236
1333
  const useClient = (_scope) => {
1334
+ useStore("user");
1237
1335
  return useInject(__alepha_server_links.LinkProvider).client();
1238
1336
  };
1239
1337
 
1240
1338
  //#endregion
1241
1339
  //#region src/hooks/useQueryParams.ts
1242
1340
  const useQueryParams = (schema, options = {}) => {
1243
- const ctx = (0, react.useContext)(RouterContext);
1244
- if (!ctx) throw new Error("useQueryParams must be used within a RouterProvider");
1341
+ const alepha = useAlepha();
1245
1342
  const key = options.key ?? "q";
1246
1343
  const router = useRouter();
1247
1344
  const querystring = router.query[key];
1248
- const [queryParams, setQueryParams] = (0, react.useState)(decode(ctx.alepha, schema, router.query[key]));
1345
+ const [queryParams, setQueryParams] = (0, react.useState)(decode(alepha, schema, router.query[key]));
1249
1346
  (0, react.useEffect)(() => {
1250
- setQueryParams(decode(ctx.alepha, schema, querystring));
1347
+ setQueryParams(decode(alepha, schema, querystring));
1251
1348
  }, [querystring]);
1252
1349
  return [queryParams, (queryParams$1) => {
1253
1350
  setQueryParams(queryParams$1);
1254
1351
  router.setQueryParams((data) => {
1255
1352
  return {
1256
1353
  ...data,
1257
- [key]: encode(ctx.alepha, schema, queryParams$1)
1354
+ [key]: encode(alepha, schema, queryParams$1)
1258
1355
  };
1259
1356
  });
1260
1357
  }];
@@ -1273,14 +1370,50 @@ const decode = (alepha, schema, data) => {
1273
1370
  //#endregion
1274
1371
  //#region src/hooks/useRouterState.ts
1275
1372
  const useRouterState = () => {
1276
- const ctx = (0, react.useContext)(RouterContext);
1373
+ const router = (0, react.useContext)(RouterContext);
1277
1374
  const layer = (0, react.useContext)(RouterLayerContext);
1278
- if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1279
- const [state, setState] = (0, react.useState)(ctx.state);
1375
+ if (!router || !layer) throw new Error("useRouterState must be used within a RouterContext.Provider");
1376
+ const [state, setState] = (0, react.useState)(router.state);
1280
1377
  useRouterEvents({ onEnd: ({ state: state$1 }) => setState({ ...state$1 }) });
1281
1378
  return state;
1282
1379
  };
1283
1380
 
1381
+ //#endregion
1382
+ //#region src/hooks/useSchema.ts
1383
+ const useSchema = (action) => {
1384
+ const name = action.name;
1385
+ const alepha = useAlepha();
1386
+ const httpClient = useInject(__alepha_server.HttpClient);
1387
+ const linkProvider = useInject(__alepha_server_links.LinkProvider);
1388
+ const [schema, setSchema] = (0, react.useState)(ssrSchemaLoading(alepha, name));
1389
+ (0, react.useEffect)(() => {
1390
+ if (!schema.loading) return;
1391
+ const opts = { cache: true };
1392
+ httpClient.fetch(`${linkProvider.URL_LINKS}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
1393
+ }, [name]);
1394
+ return schema;
1395
+ };
1396
+ /**
1397
+ * Get an action schema during server-side rendering (SSR) or client-side rendering (CSR).
1398
+ */
1399
+ const ssrSchemaLoading = (alepha, name) => {
1400
+ if (!alepha.isBrowser()) {
1401
+ const links = alepha.context.get("links")?.links ?? [];
1402
+ const can = links.find((it) => it.name === name);
1403
+ if (can) {
1404
+ const schema$1 = alepha.inject(__alepha_server_links.LinkProvider).links?.find((it) => it.name === name)?.schema;
1405
+ if (schema$1) {
1406
+ can.schema = schema$1;
1407
+ return schema$1;
1408
+ }
1409
+ }
1410
+ return { loading: true };
1411
+ }
1412
+ const schema = alepha.inject(__alepha_server_links.LinkProvider).links?.find((it) => it.name === name)?.schema;
1413
+ if (schema) return schema;
1414
+ return { loading: true };
1415
+ };
1416
+
1284
1417
  //#endregion
1285
1418
  //#region src/index.ts
1286
1419
  /**
@@ -1306,6 +1439,7 @@ const AlephaReact = (0, __alepha_core.$module)({
1306
1439
 
1307
1440
  //#endregion
1308
1441
  exports.$page = $page;
1442
+ exports.AlephaContext = AlephaContext;
1309
1443
  exports.AlephaReact = AlephaReact;
1310
1444
  exports.ClientOnly = ClientOnly_default;
1311
1445
  exports.ErrorBoundary = ErrorBoundary_default;
@@ -1316,11 +1450,12 @@ exports.PageDescriptor = PageDescriptor;
1316
1450
  exports.PageDescriptorProvider = PageDescriptorProvider;
1317
1451
  exports.ReactBrowserProvider = ReactBrowserProvider;
1318
1452
  exports.ReactServerProvider = ReactServerProvider;
1319
- exports.RedirectionError = RedirectionError;
1453
+ exports.Redirection = Redirection;
1320
1454
  exports.RouterContext = RouterContext;
1321
1455
  exports.RouterHookApi = RouterHookApi;
1322
1456
  exports.RouterLayerContext = RouterLayerContext;
1323
1457
  exports.isPageRoute = isPageRoute;
1458
+ exports.ssrSchemaLoading = ssrSchemaLoading;
1324
1459
  exports.useActive = useActive;
1325
1460
  exports.useAlepha = useAlepha;
1326
1461
  exports.useClient = useClient;
@@ -1329,4 +1464,6 @@ exports.useQueryParams = useQueryParams;
1329
1464
  exports.useRouter = useRouter;
1330
1465
  exports.useRouterEvents = useRouterEvents;
1331
1466
  exports.useRouterState = useRouterState;
1467
+ exports.useSchema = useSchema;
1468
+ exports.useStore = useStore;
1332
1469
  //# sourceMappingURL=index.cjs.map