@alepha/react 0.9.3 → 0.9.5

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 (39) hide show
  1. package/README.md +64 -6
  2. package/dist/index.browser.js +442 -328
  3. package/dist/index.browser.js.map +1 -1
  4. package/dist/index.cjs +644 -482
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +402 -339
  7. package/dist/index.d.cts.map +1 -1
  8. package/dist/index.d.ts +412 -349
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +641 -484
  11. package/dist/index.js.map +1 -1
  12. package/package.json +16 -11
  13. package/src/components/Link.tsx +2 -5
  14. package/src/components/NestedView.tsx +164 -19
  15. package/src/components/NotFound.tsx +1 -1
  16. package/src/descriptors/$page.ts +100 -5
  17. package/src/errors/Redirection.ts +8 -5
  18. package/src/hooks/useActive.ts +25 -35
  19. package/src/hooks/useAlepha.ts +16 -2
  20. package/src/hooks/useClient.ts +7 -4
  21. package/src/hooks/useInject.ts +4 -1
  22. package/src/hooks/useQueryParams.ts +9 -6
  23. package/src/hooks/useRouter.ts +18 -31
  24. package/src/hooks/useRouterEvents.ts +30 -22
  25. package/src/hooks/useRouterState.ts +8 -20
  26. package/src/hooks/useSchema.ts +10 -15
  27. package/src/hooks/useStore.ts +0 -7
  28. package/src/index.browser.ts +14 -11
  29. package/src/index.shared.ts +2 -3
  30. package/src/index.ts +27 -31
  31. package/src/providers/ReactBrowserProvider.ts +151 -62
  32. package/src/providers/ReactBrowserRendererProvider.ts +22 -0
  33. package/src/providers/ReactBrowserRouterProvider.ts +137 -0
  34. package/src/providers/{PageDescriptorProvider.ts → ReactPageProvider.ts} +121 -104
  35. package/src/providers/ReactServerProvider.ts +90 -76
  36. package/src/{hooks/RouterHookApi.ts → services/ReactRouter.ts} +49 -62
  37. package/src/contexts/RouterContext.ts +0 -14
  38. package/src/providers/BrowserRouterProvider.ts +0 -155
  39. package/src/providers/ReactBrowserRenderer.ts +0 -93
@@ -1,9 +1,11 @@
1
- import { $env, $hook, $inject, $logger, $module, Alepha, Descriptor, KIND, createDescriptor, t } from "@alepha/core";
1
+ import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, TypeGuard, createDescriptor, t } from "@alepha/core";
2
2
  import { AlephaServer, HttpClient } from "@alepha/server";
3
3
  import { AlephaServerLinks, LinkProvider } from "@alepha/server-links";
4
+ import { DateTimeProvider } from "@alepha/datetime";
5
+ import { $logger } from "@alepha/logger";
4
6
  import { RouterProvider } from "@alepha/router";
5
- import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
+ import React, { StrictMode, createContext, createElement, memo, use, useContext, useEffect, useMemo, useRef, useState } from "react";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
9
  import { createRoot, hydrateRoot } from "react-dom/client";
8
10
 
9
11
  //#region src/descriptors/$page.ts
@@ -28,7 +30,16 @@ var PageDescriptor = class extends Descriptor {
28
30
  * Only valid for server-side rendering, it will throw an error if called on the client-side.
29
31
  */
30
32
  async render(options) {
31
- throw new Error("render method is not implemented in this environment");
33
+ throw new AlephaError("render() method is not implemented in this environment");
34
+ }
35
+ async fetch(options) {
36
+ throw new AlephaError("fetch() method is not implemented in this environment");
37
+ }
38
+ match(url) {
39
+ return false;
40
+ }
41
+ pathname(config) {
42
+ return this.options.path || "";
32
43
  }
33
44
  };
34
45
  $page[KIND] = PageDescriptor;
@@ -53,7 +64,7 @@ function NotFoundPage(props) {
53
64
  fontSize: "1rem",
54
65
  marginBottom: "0.5rem"
55
66
  },
56
- children: "This page does not exist"
67
+ children: "404 - This page does not exist"
57
68
  })
58
69
  });
59
70
  }
@@ -76,7 +87,6 @@ const ClientOnly = (props) => {
76
87
  if (props.disabled) return props.children;
77
88
  return mounted ? props.children : props.fallback;
78
89
  };
79
- var ClientOnly_default = ClientOnly;
80
90
 
81
91
  //#endregion
82
92
  //#region src/components/ErrorViewer.tsx
@@ -184,7 +194,6 @@ const ErrorViewer = ({ error, alepha }) => {
184
194
  })] })]
185
195
  });
186
196
  };
187
- var ErrorViewer_default = ErrorViewer;
188
197
  const ErrorViewerProduction = () => {
189
198
  const styles = {
190
199
  container: {
@@ -226,45 +235,107 @@ const ErrorViewerProduction = () => {
226
235
  });
227
236
  };
228
237
 
229
- //#endregion
230
- //#region src/contexts/RouterContext.ts
231
- const RouterContext = createContext(void 0);
232
-
233
238
  //#endregion
234
239
  //#region src/contexts/RouterLayerContext.ts
235
240
  const RouterLayerContext = createContext(void 0);
236
241
 
242
+ //#endregion
243
+ //#region src/errors/Redirection.ts
244
+ /**
245
+ * Used for Redirection during the page loading.
246
+ *
247
+ * Depends on the context, it can be thrown or just returned.
248
+ */
249
+ var Redirection = class extends Error {
250
+ redirect;
251
+ constructor(redirect) {
252
+ super("Redirection");
253
+ this.redirect = redirect;
254
+ }
255
+ };
256
+
237
257
  //#endregion
238
258
  //#region src/contexts/AlephaContext.ts
239
259
  const AlephaContext = createContext(void 0);
240
260
 
241
261
  //#endregion
242
262
  //#region src/hooks/useAlepha.ts
263
+ /**
264
+ * Main Alepha hook.
265
+ *
266
+ * It provides access to the Alepha instance within a React component.
267
+ *
268
+ * With Alepha, you can access the core functionalities of the framework:
269
+ *
270
+ * - alepha.state() for state management
271
+ * - alepha.inject() for dependency injection
272
+ * - alepha.emit() for event handling
273
+ * etc...
274
+ */
243
275
  const useAlepha = () => {
244
276
  const alepha = useContext(AlephaContext);
245
- if (!alepha) throw new Error("useAlepha must be used within an AlephaContext.Provider");
277
+ if (!alepha) throw new AlephaError("Hook 'useAlepha()' must be used within an AlephaContext.Provider");
246
278
  return alepha;
247
279
  };
248
280
 
249
281
  //#endregion
250
282
  //#region src/hooks/useRouterEvents.ts
283
+ /**
284
+ * Subscribe to various router events.
285
+ */
251
286
  const useRouterEvents = (opts = {}, deps = []) => {
252
287
  const alepha = useAlepha();
253
288
  useEffect(() => {
254
289
  if (!alepha.isBrowser()) return;
290
+ const cb = (callback) => {
291
+ if (typeof callback === "function") return { callback };
292
+ return callback;
293
+ };
255
294
  const subs = [];
256
295
  const onBegin = opts.onBegin;
257
296
  const onEnd = opts.onEnd;
258
297
  const onError = opts.onError;
259
- if (onBegin) subs.push(alepha.on("react:transition:begin", { callback: onBegin }));
260
- if (onEnd) subs.push(alepha.on("react:transition:end", { callback: onEnd }));
261
- if (onError) subs.push(alepha.on("react:transition:error", { callback: onError }));
298
+ const onSuccess = opts.onSuccess;
299
+ if (onBegin) subs.push(alepha.on("react:transition:begin", cb(onBegin)));
300
+ if (onEnd) subs.push(alepha.on("react:transition:end", cb(onEnd)));
301
+ if (onError) subs.push(alepha.on("react:transition:error", cb(onError)));
302
+ if (onSuccess) subs.push(alepha.on("react:transition:success", cb(onSuccess)));
262
303
  return () => {
263
304
  for (const sub of subs) sub();
264
305
  };
265
306
  }, deps);
266
307
  };
267
308
 
309
+ //#endregion
310
+ //#region src/hooks/useStore.ts
311
+ /**
312
+ * Hook to access and mutate the Alepha state.
313
+ */
314
+ const useStore = (key, defaultValue) => {
315
+ const alepha = useAlepha();
316
+ useMemo(() => {
317
+ if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
318
+ }, [defaultValue]);
319
+ const [state, setState] = useState(alepha.state(key));
320
+ useEffect(() => {
321
+ if (!alepha.isBrowser()) return;
322
+ return alepha.on("state:mutate", (ev) => {
323
+ if (ev.key === key) setState(ev.value);
324
+ });
325
+ }, []);
326
+ return [state, (value) => {
327
+ alepha.state(key, value);
328
+ }];
329
+ };
330
+
331
+ //#endregion
332
+ //#region src/hooks/useRouterState.ts
333
+ const useRouterState = () => {
334
+ const [state] = useStore("react.router.state");
335
+ if (!state) throw new AlephaError("Missing react router state");
336
+ return state;
337
+ };
338
+
268
339
  //#endregion
269
340
  //#region src/components/ErrorBoundary.tsx
270
341
  /**
@@ -294,7 +365,6 @@ var ErrorBoundary = class extends React.Component {
294
365
  return this.props.children;
295
366
  }
296
367
  };
297
- var ErrorBoundary_default = ErrorBoundary;
298
368
 
299
369
  //#endregion
300
370
  //#region src/components/NestedView.tsx
@@ -305,7 +375,7 @@ var ErrorBoundary_default = ErrorBoundary;
305
375
  *
306
376
  * @example
307
377
  * ```tsx
308
- * import { NestedView } from "@alepha/react";
378
+ * import { NestedView } from "alepha/react";
309
379
  *
310
380
  * class App {
311
381
  * parent = $page({
@@ -320,39 +390,113 @@ var ErrorBoundary_default = ErrorBoundary;
320
390
  * ```
321
391
  */
322
392
  const NestedView = (props) => {
323
- const app = useContext(RouterContext);
324
- const layer = useContext(RouterLayerContext);
325
- const index = layer?.index ?? 0;
326
- const [view, setView] = useState(app?.state.layers[index]?.element);
327
- useRouterEvents({ onEnd: ({ state, context }) => {
328
- if (app) app.context = context;
329
- if (!state.layers[index]?.cache) setView(state.layers[index]?.element);
330
- } }, [app]);
331
- if (!app) throw new Error("NestedView must be used within a RouterContext.");
332
- const element = view ?? props.children ?? null;
333
- return /* @__PURE__ */ jsx(ErrorBoundary_default, {
393
+ const index = use(RouterLayerContext)?.index ?? 0;
394
+ const state = useRouterState();
395
+ const [view, setView] = useState(state.layers[index]?.element);
396
+ const [animation, setAnimation] = useState("");
397
+ const animationExitDuration = useRef(0);
398
+ const animationExitNow = useRef(0);
399
+ useRouterEvents({
400
+ onBegin: async ({ previous, state: state$1 }) => {
401
+ const layer = previous.layers[index];
402
+ if (`${state$1.url.pathname}/`.startsWith(`${layer?.path}/`)) return;
403
+ const animationExit = parseAnimation(layer.route?.animation, state$1, "exit");
404
+ if (animationExit) {
405
+ const duration = animationExit.duration || 200;
406
+ animationExitNow.current = Date.now();
407
+ animationExitDuration.current = duration;
408
+ setAnimation(animationExit.animation);
409
+ } else {
410
+ animationExitNow.current = 0;
411
+ animationExitDuration.current = 0;
412
+ setAnimation("");
413
+ }
414
+ },
415
+ onEnd: async ({ state: state$1 }) => {
416
+ const layer = state$1.layers[index];
417
+ if (animationExitNow.current) {
418
+ const duration = animationExitDuration.current;
419
+ const diff = Date.now() - animationExitNow.current;
420
+ if (diff < duration) await new Promise((resolve) => setTimeout(resolve, duration - diff));
421
+ }
422
+ if (!layer?.cache) {
423
+ setView(layer?.element);
424
+ const animationEnter = parseAnimation(layer?.route?.animation, state$1, "enter");
425
+ if (animationEnter) setAnimation(animationEnter.animation);
426
+ else setAnimation("");
427
+ }
428
+ }
429
+ }, []);
430
+ let element = view ?? props.children ?? null;
431
+ if (animation) element = /* @__PURE__ */ jsx("div", {
432
+ style: {
433
+ display: "flex",
434
+ flex: 1,
435
+ height: "100%",
436
+ width: "100%",
437
+ position: "relative",
438
+ overflow: "hidden"
439
+ },
440
+ children: /* @__PURE__ */ jsx("div", {
441
+ style: {
442
+ height: "100%",
443
+ width: "100%",
444
+ display: "flex",
445
+ animation
446
+ },
447
+ children: element
448
+ })
449
+ });
450
+ if (props.errorBoundary === false) return /* @__PURE__ */ jsx(Fragment, { children: element });
451
+ if (props.errorBoundary) return /* @__PURE__ */ jsx(ErrorBoundary, {
452
+ fallback: props.errorBoundary,
453
+ children: element
454
+ });
455
+ return /* @__PURE__ */ jsx(ErrorBoundary, {
334
456
  fallback: (error) => {
335
- return app.context.onError?.(error, app.context);
457
+ const result = state.onError(error, state);
458
+ if (result instanceof Redirection) return "Redirection inside ErrorBoundary is not allowed.";
459
+ return result;
336
460
  },
337
461
  children: element
338
462
  });
339
463
  };
340
- var NestedView_default = NestedView;
341
-
342
- //#endregion
343
- //#region src/errors/Redirection.ts
344
- var Redirection = class extends Error {
345
- page;
346
- constructor(page) {
347
- super("Redirection");
348
- this.page = page;
464
+ var NestedView_default = memo(NestedView);
465
+ function parseAnimation(animationLike, state, type = "enter") {
466
+ if (!animationLike) return void 0;
467
+ const DEFAULT_DURATION = 300;
468
+ const animation = typeof animationLike === "function" ? animationLike(state) : animationLike;
469
+ if (typeof animation === "string") {
470
+ if (type === "exit") return;
471
+ return {
472
+ duration: DEFAULT_DURATION,
473
+ animation: `${DEFAULT_DURATION}ms ${animation}`
474
+ };
349
475
  }
350
- };
476
+ if (typeof animation === "object") {
477
+ const anim = animation[type];
478
+ const duration = typeof anim === "object" ? anim.duration ?? DEFAULT_DURATION : DEFAULT_DURATION;
479
+ const name = typeof anim === "object" ? anim.name : anim;
480
+ if (type === "exit") {
481
+ const timing$1 = typeof anim === "object" ? anim.timing ?? "" : "";
482
+ return {
483
+ duration,
484
+ animation: `${duration}ms ${timing$1} ${name}`
485
+ };
486
+ }
487
+ const timing = typeof anim === "object" ? anim.timing ?? "" : "";
488
+ return {
489
+ duration,
490
+ animation: `${duration}ms ${timing} ${name}`
491
+ };
492
+ }
493
+ return void 0;
494
+ }
351
495
 
352
496
  //#endregion
353
- //#region src/providers/PageDescriptorProvider.ts
497
+ //#region src/providers/ReactPageProvider.ts
354
498
  const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
355
- var PageDescriptorProvider = class {
499
+ var ReactPageProvider = class {
356
500
  log = $logger();
357
501
  env = $env(envSchema$1);
358
502
  alepha = $inject(Alepha);
@@ -381,22 +525,29 @@ var PageDescriptorProvider = class {
381
525
  return url.replace(/\/\/+/g, "/") || "/";
382
526
  }
383
527
  url(name, options = {}) {
384
- return new URL(this.pathname(name, options), options.base ?? `http://localhost`);
528
+ return new URL(this.pathname(name, options), options.host ?? `http://localhost`);
385
529
  }
386
- root(state, context) {
387
- const root = createElement(AlephaContext.Provider, { value: this.alepha }, createElement(RouterContext.Provider, { value: {
388
- state,
389
- context
390
- } }, createElement(NestedView_default, {}, state.layers[0]?.element)));
530
+ root(state) {
531
+ const root = createElement(AlephaContext.Provider, { value: this.alepha }, createElement(NestedView_default, {}, state.layers[0]?.element));
391
532
  if (this.env.REACT_STRICT_MODE) return createElement(StrictMode, {}, root);
392
533
  return root;
393
534
  }
394
- async createLayers(route, request) {
395
- const { pathname, search } = request.url;
396
- const layers = [];
535
+ convertStringObjectToObject = (schema, value) => {
536
+ if (TypeGuard.IsObject(schema) && typeof value === "object") {
537
+ for (const key in schema.properties) if (TypeGuard.IsObject(schema.properties[key]) && typeof value[key] === "string") try {
538
+ value[key] = this.alepha.parse(schema.properties[key], decodeURIComponent(value[key]));
539
+ } catch (e) {}
540
+ }
541
+ return value;
542
+ };
543
+ /**
544
+ * Create a new RouterState based on a given route and request.
545
+ * This method resolves the layers for the route, applying any query and params schemas defined in the route.
546
+ * It also handles errors and redirects.
547
+ */
548
+ async createLayers(route, state, previous = []) {
397
549
  let context = {};
398
550
  const stack = [{ route }];
399
- request.onError = (error) => this.renderError(error);
400
551
  let parent = route.parent;
401
552
  while (parent) {
402
553
  stack.unshift({ route: parent });
@@ -408,19 +559,19 @@ var PageDescriptorProvider = class {
408
559
  const route$1 = it.route;
409
560
  const config = {};
410
561
  try {
411
- config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) : {};
562
+ this.convertStringObjectToObject(route$1.schema?.query, state.query);
563
+ config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, state.query) : {};
412
564
  } catch (e) {
413
565
  it.error = e;
414
566
  break;
415
567
  }
416
568
  try {
417
- config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) : {};
569
+ config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, state.params) : {};
418
570
  } catch (e) {
419
571
  it.error = e;
420
572
  break;
421
573
  }
422
574
  it.config = { ...config };
423
- const previous = request.previous;
424
575
  if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
425
576
  const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
426
577
  const prev = JSON.stringify({
@@ -446,7 +597,7 @@ var PageDescriptorProvider = class {
446
597
  if (!route$1.resolve) continue;
447
598
  try {
448
599
  const props = await route$1.resolve?.({
449
- ...request,
600
+ ...state,
450
601
  ...config,
451
602
  ...context
452
603
  }) ?? {};
@@ -456,11 +607,8 @@ var PageDescriptorProvider = class {
456
607
  ...props
457
608
  };
458
609
  } catch (e) {
459
- if (e instanceof Redirection) return this.createRedirectionLayer(e.page, {
460
- pathname,
461
- search
462
- });
463
- this.log.error(e);
610
+ if (e instanceof Redirection) return { redirect: e.redirect };
611
+ this.log.error("Page resolver has failed", e);
464
612
  it.error = e;
465
613
  break;
466
614
  }
@@ -476,69 +624,58 @@ var PageDescriptorProvider = class {
476
624
  const path = acc.replace(/\/+/, "/");
477
625
  const localErrorHandler = this.getErrorHandler(it.route);
478
626
  if (localErrorHandler) {
479
- const onErrorParent = request.onError;
480
- request.onError = (error, context$1) => {
627
+ const onErrorParent = state.onError;
628
+ state.onError = (error, context$1) => {
481
629
  const result = localErrorHandler(error, context$1);
482
630
  if (result === void 0) return onErrorParent(error, context$1);
483
631
  return result;
484
632
  };
485
633
  }
486
- if (it.error) try {
487
- let element$1 = await request.onError(it.error, request);
488
- if (element$1 === void 0) throw it.error;
489
- if (element$1 instanceof Redirection) return this.createRedirectionLayer(element$1.page, {
490
- pathname,
491
- search
634
+ if (!it.error) try {
635
+ const element = await this.createElement(it.route, {
636
+ ...props,
637
+ ...context
492
638
  });
493
- if (element$1 === null) element$1 = this.renderError(it.error);
494
- layers.push({
639
+ state.layers.push({
640
+ name: it.route.name,
641
+ props,
642
+ part: it.route.path,
643
+ config: it.config,
644
+ element: this.renderView(i + 1, path, element, it.route),
645
+ index: i + 1,
646
+ path,
647
+ route: it.route,
648
+ cache: it.cache
649
+ });
650
+ } catch (e) {
651
+ it.error = e;
652
+ }
653
+ if (it.error) try {
654
+ let element = await state.onError(it.error, state);
655
+ if (element === void 0) throw it.error;
656
+ if (element instanceof Redirection) return { redirect: element.redirect };
657
+ if (element === null) element = this.renderError(it.error);
658
+ state.layers.push({
495
659
  props,
496
660
  error: it.error,
497
661
  name: it.route.name,
498
662
  part: it.route.path,
499
663
  config: it.config,
500
- element: this.renderView(i + 1, path, element$1, it.route),
664
+ element: this.renderView(i + 1, path, element, it.route),
501
665
  index: i + 1,
502
666
  path,
503
667
  route: it.route
504
668
  });
505
669
  break;
506
670
  } catch (e) {
507
- if (e instanceof Redirection) return this.createRedirectionLayer(e.page, {
508
- pathname,
509
- search
510
- });
671
+ if (e instanceof Redirection) return { redirect: e.redirect };
511
672
  throw e;
512
673
  }
513
- const element = await this.createElement(it.route, {
514
- ...props,
515
- ...context
516
- });
517
- layers.push({
518
- name: it.route.name,
519
- props,
520
- part: it.route.path,
521
- config: it.config,
522
- element: this.renderView(i + 1, path, element, it.route),
523
- index: i + 1,
524
- path,
525
- route: it.route,
526
- cache: it.cache
527
- });
528
674
  }
529
- return {
530
- layers,
531
- pathname,
532
- search
533
- };
675
+ return { state };
534
676
  }
535
- createRedirectionLayer(href, context) {
536
- return {
537
- layers: [],
538
- redirect: typeof href === "string" ? href : this.href(href),
539
- pathname: context.pathname,
540
- search: context.search
541
- };
677
+ createRedirectionLayer(redirect) {
678
+ return { redirect };
542
679
  }
543
680
  getErrorHandler(route) {
544
681
  if (route.errorHandler) return route.errorHandler;
@@ -549,6 +686,7 @@ var PageDescriptorProvider = class {
549
686
  }
550
687
  }
551
688
  async createElement(page, props) {
689
+ if (page.lazy && page.component) this.log.warn(`Page ${page.name} has both lazy and component options, lazy will be used`);
552
690
  if (page.lazy) {
553
691
  const component = await page.lazy();
554
692
  return createElement(component.default, props);
@@ -557,7 +695,7 @@ var PageDescriptorProvider = class {
557
695
  return void 0;
558
696
  }
559
697
  renderError(error) {
560
- return createElement(ErrorViewer_default, {
698
+ return createElement(ErrorViewer, {
561
699
  error,
562
700
  alepha: this.alepha
563
701
  });
@@ -583,7 +721,7 @@ var PageDescriptorProvider = class {
583
721
  }
584
722
  renderView(index, path, view, page) {
585
723
  view ??= this.renderEmptyView();
586
- const element = page.client ? createElement(ClientOnly_default, typeof page.client === "object" ? page.client : {}, view) : view;
724
+ const element = page.client ? createElement(ClientOnly, typeof page.client === "object" ? page.client : {}, view) : view;
587
725
  return createElement(RouterLayerContext.Provider, { value: {
588
726
  index,
589
727
  path
@@ -665,57 +803,47 @@ const isPageRoute = (it) => {
665
803
  };
666
804
 
667
805
  //#endregion
668
- //#region src/providers/BrowserRouterProvider.ts
669
- var BrowserRouterProvider = class extends RouterProvider {
806
+ //#region src/providers/ReactBrowserRouterProvider.ts
807
+ var ReactBrowserRouterProvider = class extends RouterProvider {
670
808
  log = $logger();
671
809
  alepha = $inject(Alepha);
672
- pageDescriptorProvider = $inject(PageDescriptorProvider);
810
+ pageApi = $inject(ReactPageProvider);
673
811
  add(entry) {
674
- this.pageDescriptorProvider.add(entry);
812
+ this.pageApi.add(entry);
675
813
  }
676
814
  configure = $hook({
677
815
  on: "configure",
678
816
  handler: async () => {
679
- for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
817
+ for (const page of this.pageApi.getPages()) if (page.component || page.lazy) this.push({
680
818
  path: page.match,
681
819
  page
682
820
  });
683
821
  }
684
822
  });
685
- async transition(url, options = {}) {
823
+ async transition(url, previous = [], meta = {}) {
686
824
  const { pathname, search } = url;
687
- const state = {
688
- pathname,
689
- search,
690
- layers: []
691
- };
692
- const context = {
825
+ const entry = {
693
826
  url,
694
827
  query: {},
695
828
  params: {},
829
+ layers: [],
696
830
  onError: () => null,
697
- ...options.context ?? {}
831
+ meta
698
832
  };
833
+ const state = entry;
699
834
  await this.alepha.emit("react:transition:begin", {
700
- state,
701
- context
835
+ previous: this.alepha.state("react.router.state"),
836
+ state
702
837
  });
703
838
  try {
704
- const previous = options.previous;
705
839
  const { route, params } = this.match(pathname);
706
840
  const query = {};
707
841
  if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
708
- context.query = query;
709
- context.params = params ?? {};
710
- context.previous = previous;
842
+ state.query = query;
843
+ state.params = params ?? {};
711
844
  if (isPageRoute(route)) {
712
- const result = await this.pageDescriptorProvider.createLayers(route.page, context);
713
- if (result.redirect) return {
714
- redirect: result.redirect,
715
- state,
716
- context
717
- };
718
- state.layers = result.layers;
845
+ const { redirect } = await this.pageApi.createLayers(route.page, state, previous);
846
+ if (redirect) return redirect;
719
847
  }
720
848
  if (state.layers.length === 0) state.layers.push({
721
849
  name: "not-found",
@@ -723,86 +851,91 @@ var BrowserRouterProvider = class extends RouterProvider {
723
851
  index: 0,
724
852
  path: "/"
725
853
  });
726
- await this.alepha.emit("react:transition:success", {
727
- state,
728
- context
729
- });
854
+ await this.alepha.emit("react:transition:success", { state });
730
855
  } catch (e) {
731
- this.log.error(e);
856
+ this.log.error("Transition has failed", e);
732
857
  state.layers = [{
733
858
  name: "error",
734
- element: this.pageDescriptorProvider.renderError(e),
859
+ element: this.pageApi.renderError(e),
735
860
  index: 0,
736
861
  path: "/"
737
862
  }];
738
863
  await this.alepha.emit("react:transition:error", {
739
864
  error: e,
740
- state,
741
- context
865
+ state
742
866
  });
743
867
  }
744
- if (options.state) {
745
- options.state.layers = state.layers;
746
- options.state.pathname = state.pathname;
747
- options.state.search = state.search;
748
- }
749
- if (options.previous) for (let i = 0; i < options.previous.length; i++) {
750
- const layer = options.previous[i];
751
- if (state.layers[i]?.name !== layer.name) this.pageDescriptorProvider.page(layer.name)?.onLeave?.();
868
+ if (previous) for (let i = 0; i < previous.length; i++) {
869
+ const layer = previous[i];
870
+ if (state.layers[i]?.name !== layer.name) this.pageApi.page(layer.name)?.onLeave?.();
752
871
  }
753
- await this.alepha.emit("react:transition:end", {
754
- state: options.state,
755
- context
756
- });
757
- return {
758
- context,
759
- state
760
- };
872
+ this.alepha.state("react.router.state", state);
873
+ await this.alepha.emit("react:transition:end", { state });
761
874
  }
762
- root(state, context) {
763
- return this.pageDescriptorProvider.root(state, context);
875
+ root(state) {
876
+ return this.pageApi.root(state);
764
877
  }
765
878
  };
766
879
 
767
880
  //#endregion
768
881
  //#region src/providers/ReactBrowserProvider.ts
882
+ const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
769
883
  var ReactBrowserProvider = class {
884
+ env = $env(envSchema);
770
885
  log = $logger();
771
886
  client = $inject(LinkProvider);
772
887
  alepha = $inject(Alepha);
773
- router = $inject(BrowserRouterProvider);
774
- root;
888
+ router = $inject(ReactBrowserRouterProvider);
889
+ dateTimeProvider = $inject(DateTimeProvider);
890
+ options = { scrollRestoration: "top" };
891
+ getRootElement() {
892
+ const root = this.document.getElementById(this.env.REACT_ROOT_ID);
893
+ if (root) return root;
894
+ const div = this.document.createElement("div");
895
+ div.id = this.env.REACT_ROOT_ID;
896
+ this.document.body.prepend(div);
897
+ return div;
898
+ }
775
899
  transitioning;
776
- state = {
777
- layers: [],
778
- pathname: "",
779
- search: ""
780
- };
900
+ get state() {
901
+ return this.alepha.state("react.router.state");
902
+ }
903
+ /**
904
+ * Accessor for Document DOM API.
905
+ */
781
906
  get document() {
782
907
  return window.document;
783
908
  }
909
+ /**
910
+ * Accessor for History DOM API.
911
+ */
784
912
  get history() {
785
913
  return window.history;
786
914
  }
915
+ /**
916
+ * Accessor for Location DOM API.
917
+ */
787
918
  get location() {
788
919
  return window.location;
789
920
  }
921
+ get base() {
922
+ const base = import.meta.env?.BASE_URL;
923
+ if (!base || base === "/") return "";
924
+ return base;
925
+ }
790
926
  get url() {
791
- let url = this.location.pathname + this.location.search;
792
- if (import.meta?.env?.BASE_URL) {
793
- url = url.replace(import.meta.env?.BASE_URL, "");
794
- if (!url.startsWith("/")) url = `/${url}`;
795
- }
927
+ const url = this.location.pathname + this.location.search;
928
+ if (this.base) return url.replace(this.base, "");
796
929
  return url;
797
930
  }
798
- pushState(url, replace) {
799
- let path = url;
800
- if (import.meta?.env?.BASE_URL) path = (import.meta.env?.BASE_URL + path).replaceAll("//", "/");
801
- if (replace) this.history.replaceState({}, "", path);
802
- else this.history.pushState({}, "", path);
931
+ pushState(path, replace) {
932
+ const url = this.base + path;
933
+ if (replace) this.history.replaceState({}, "", url);
934
+ else this.history.pushState({}, "", url);
803
935
  }
804
936
  async invalidate(props) {
805
937
  const previous = [];
938
+ this.log.trace("Invalidating layers");
806
939
  if (props) {
807
940
  const [key] = Object.keys(props);
808
941
  const value = props[key];
@@ -823,9 +956,17 @@ var ReactBrowserProvider = class {
823
956
  await this.render({ previous });
824
957
  }
825
958
  async go(url, options = {}) {
826
- const result = await this.render({ url });
827
- if (result.context.url.pathname + result.context.url.search !== url) {
828
- this.pushState(result.context.url.pathname + result.context.url.search);
959
+ this.log.trace(`Going to ${url}`, {
960
+ url,
961
+ options
962
+ });
963
+ await this.render({
964
+ url,
965
+ previous: options.force ? [] : this.state.layers,
966
+ meta: options.meta
967
+ });
968
+ if (this.state.url.pathname + this.state.url.search !== url) {
969
+ this.pushState(this.state.url.pathname + this.state.url.search);
829
970
  return;
830
971
  }
831
972
  this.pushState(url, options.replace);
@@ -833,14 +974,20 @@ var ReactBrowserProvider = class {
833
974
  async render(options = {}) {
834
975
  const previous = options.previous ?? this.state.layers;
835
976
  const url = options.url ?? this.url;
836
- this.transitioning = { to: url };
837
- const result = await this.router.transition(new URL(`http://localhost${url}`), {
838
- previous,
839
- state: this.state
840
- });
841
- if (result.redirect) return await this.render({ url: result.redirect });
977
+ const start = this.dateTimeProvider.now();
978
+ this.transitioning = {
979
+ to: url,
980
+ from: this.state?.url.pathname
981
+ };
982
+ this.log.debug("Transitioning...", { to: url });
983
+ const redirect = await this.router.transition(new URL(`http://localhost${url}`), previous, options.meta);
984
+ if (redirect) {
985
+ this.log.info("Redirecting to", { redirect });
986
+ return await this.render({ url: redirect });
987
+ }
988
+ const ms = this.dateTimeProvider.now().diff(start);
989
+ this.log.info(`Transition OK [${ms}ms]`, this.transitioning);
842
990
  this.transitioning = void 0;
843
- return result;
844
991
  }
845
992
  /**
846
993
  * Get embedded layers from the server.
@@ -852,26 +999,34 @@ var ReactBrowserProvider = class {
852
999
  console.error(error);
853
1000
  }
854
1001
  }
1002
+ onTransitionEnd = $hook({
1003
+ on: "react:transition:end",
1004
+ handler: () => {
1005
+ if (this.options.scrollRestoration === "top" && typeof window !== "undefined" && !this.alepha.isTest()) {
1006
+ this.log.trace("Restoring scroll position to top");
1007
+ window.scrollTo(0, 0);
1008
+ }
1009
+ }
1010
+ });
855
1011
  ready = $hook({
856
1012
  on: "ready",
857
1013
  handler: async () => {
858
1014
  const hydration = this.getHydrationState();
859
1015
  const previous = hydration?.layers ?? [];
860
1016
  if (hydration) {
861
- for (const [key, value] of Object.entries(hydration)) if (key !== "layers" && key !== "links") this.alepha.state(key, value);
1017
+ for (const [key, value] of Object.entries(hydration)) if (key !== "layers") this.alepha.state(key, value);
862
1018
  }
863
- if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink({
864
- ...link,
865
- prefix: hydration.links.prefix
866
- });
867
- const { context } = await this.render({ previous });
1019
+ await this.render({ previous });
1020
+ const element = this.router.root(this.state);
868
1021
  await this.alepha.emit("react:browser:render", {
869
- state: this.state,
870
- context,
871
- hydration
1022
+ element,
1023
+ root: this.getRootElement(),
1024
+ hydration,
1025
+ state: this.state
872
1026
  });
873
1027
  window.addEventListener("popstate", () => {
874
- if (this.state.pathname === this.url) return;
1028
+ if (this.base + this.state.url.pathname === this.location.pathname) return;
1029
+ this.log.debug("Popstate event triggered - rendering new state", { url: this.location.pathname + this.location.search });
875
1030
  this.render();
876
1031
  });
877
1032
  }
@@ -879,67 +1034,51 @@ var ReactBrowserProvider = class {
879
1034
  };
880
1035
 
881
1036
  //#endregion
882
- //#region src/providers/ReactBrowserRenderer.ts
883
- const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
884
- var ReactBrowserRenderer = class {
885
- browserProvider = $inject(ReactBrowserProvider);
886
- browserRouterProvider = $inject(BrowserRouterProvider);
887
- env = $env(envSchema);
1037
+ //#region src/providers/ReactBrowserRendererProvider.ts
1038
+ var ReactBrowserRendererProvider = class {
888
1039
  log = $logger();
889
1040
  root;
890
- options = { scrollRestoration: "top" };
891
- getRootElement() {
892
- const root = this.browserProvider.document.getElementById(this.env.REACT_ROOT_ID);
893
- if (root) return root;
894
- const div = this.browserProvider.document.createElement("div");
895
- div.id = this.env.REACT_ROOT_ID;
896
- this.browserProvider.document.body.prepend(div);
897
- return div;
898
- }
899
- ready = $hook({
1041
+ onBrowserRender = $hook({
900
1042
  on: "react:browser:render",
901
- handler: async ({ state, context, hydration }) => {
902
- const element = this.browserRouterProvider.root(state, context);
1043
+ handler: async ({ hydration, root, element }) => {
903
1044
  if (hydration?.layers) {
904
- this.root = hydrateRoot(this.getRootElement(), element);
1045
+ this.root = hydrateRoot(root, element);
905
1046
  this.log.info("Hydrated root element");
906
1047
  } else {
907
- this.root ??= createRoot(this.getRootElement());
1048
+ this.root ??= createRoot(root);
908
1049
  this.root.render(element);
909
1050
  this.log.info("Created root element");
910
1051
  }
911
1052
  }
912
1053
  });
913
- onTransitionEnd = $hook({
914
- on: "react:transition:end",
915
- handler: () => {
916
- if (this.options.scrollRestoration === "top" && typeof window !== "undefined") window.scrollTo(0, 0);
917
- }
918
- });
919
1054
  };
920
1055
 
921
1056
  //#endregion
922
- //#region src/hooks/RouterHookApi.ts
923
- var RouterHookApi = class {
924
- constructor(pages, context, state, layer, pageApi, browser) {
925
- this.pages = pages;
926
- this.context = context;
927
- this.state = state;
928
- this.layer = layer;
929
- this.pageApi = pageApi;
930
- this.browser = browser;
1057
+ //#region src/services/ReactRouter.ts
1058
+ var ReactRouter = class {
1059
+ alepha = $inject(Alepha);
1060
+ pageApi = $inject(ReactPageProvider);
1061
+ get state() {
1062
+ return this.alepha.state("react.router.state");
1063
+ }
1064
+ get pages() {
1065
+ return this.pageApi.getPages();
1066
+ }
1067
+ get browser() {
1068
+ if (this.alepha.isBrowser()) return this.alepha.inject(ReactBrowserProvider);
1069
+ return void 0;
931
1070
  }
932
1071
  path(name, config = {}) {
933
1072
  return this.pageApi.pathname(name, {
934
1073
  params: {
935
- ...this.context.params,
1074
+ ...this.state.params,
936
1075
  ...config.params
937
1076
  },
938
1077
  query: config.query
939
1078
  });
940
1079
  }
941
1080
  getURL() {
942
- if (!this.browser) return this.context.url;
1081
+ if (!this.browser) return this.state.url;
943
1082
  return new URL(this.location.href);
944
1083
  }
945
1084
  get location() {
@@ -950,11 +1089,11 @@ var RouterHookApi = class {
950
1089
  return this.state;
951
1090
  }
952
1091
  get pathname() {
953
- return this.state.pathname;
1092
+ return this.state.url.pathname;
954
1093
  }
955
1094
  get query() {
956
1095
  const query = {};
957
- for (const [key, value] of new URLSearchParams(this.state.search).entries()) query[key] = String(value);
1096
+ for (const [key, value] of new URLSearchParams(this.state.url.search).entries()) query[key] = String(value);
958
1097
  return query;
959
1098
  }
960
1099
  async back() {
@@ -966,17 +1105,6 @@ var RouterHookApi = class {
966
1105
  async invalidate(props) {
967
1106
  await this.browser?.invalidate(props);
968
1107
  }
969
- /**
970
- * Create a valid href for the given pathname.
971
- *
972
- * @param pathname
973
- * @param layer
974
- */
975
- createHref(pathname, layer = this.layer, options = {}) {
976
- if (typeof pathname === "object") pathname = pathname.options.path ?? "";
977
- if (options.params) for (const [key, value] of Object.entries(options.params)) pathname = pathname.replace(`:${key}`, String(value));
978
- return pathname.startsWith("/") ? pathname : `${layer.path}/${pathname}`.replace(/\/\/+/g, "/");
979
- }
980
1108
  async go(path, options) {
981
1109
  for (const page of this.pages) if (page.name === path) {
982
1110
  await this.browser?.go(this.path(path, options), options);
@@ -991,7 +1119,7 @@ var RouterHookApi = class {
991
1119
  break;
992
1120
  }
993
1121
  return {
994
- href,
1122
+ href: this.base(href),
995
1123
  onClick: (ev) => {
996
1124
  ev.stopPropagation();
997
1125
  ev.preventDefault();
@@ -999,6 +1127,11 @@ var RouterHookApi = class {
999
1127
  }
1000
1128
  };
1001
1129
  }
1130
+ base(path) {
1131
+ const base = import.meta.env?.BASE_URL;
1132
+ if (!base || base === "/") return path;
1133
+ return base + path;
1134
+ }
1002
1135
  /**
1003
1136
  * Set query params.
1004
1137
  *
@@ -1014,107 +1147,99 @@ var RouterHookApi = class {
1014
1147
  }
1015
1148
  };
1016
1149
 
1150
+ //#endregion
1151
+ //#region src/hooks/useInject.ts
1152
+ /**
1153
+ * Hook to inject a service instance.
1154
+ * It's a wrapper of `useAlepha().inject(service)` with a memoization.
1155
+ */
1156
+ const useInject = (service) => {
1157
+ const alepha = useAlepha();
1158
+ return useMemo(() => alepha.inject(service), []);
1159
+ };
1160
+
1017
1161
  //#endregion
1018
1162
  //#region src/hooks/useRouter.ts
1163
+ /**
1164
+ * Use this hook to access the React Router instance.
1165
+ *
1166
+ * You can add a type parameter to specify the type of your application.
1167
+ * This will allow you to use the router in a typesafe way.
1168
+ *
1169
+ * @example
1170
+ * class App {
1171
+ * home = $page();
1172
+ * }
1173
+ *
1174
+ * const router = useRouter<App>();
1175
+ * router.go("home"); // typesafe
1176
+ */
1019
1177
  const useRouter = () => {
1020
- const alepha = useAlepha();
1021
- const ctx = useContext(RouterContext);
1022
- const layer = useContext(RouterLayerContext);
1023
- if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1024
- const pages = useMemo(() => {
1025
- return alepha.inject(PageDescriptorProvider).getPages();
1026
- }, []);
1027
- return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, alepha.inject(PageDescriptorProvider), alepha.isBrowser() ? alepha.inject(ReactBrowserProvider) : void 0), [layer]);
1178
+ return useInject(ReactRouter);
1028
1179
  };
1029
1180
 
1030
1181
  //#endregion
1031
1182
  //#region src/components/Link.tsx
1032
1183
  const Link = (props) => {
1033
1184
  const router = useRouter();
1034
- const { to,...anchorProps } = props;
1035
1185
  return /* @__PURE__ */ jsx("a", {
1036
- ...router.anchor(to),
1037
- ...anchorProps,
1186
+ ...props,
1187
+ ...router.anchor(props.href),
1038
1188
  children: props.children
1039
1189
  });
1040
1190
  };
1041
- var Link_default = Link;
1042
1191
 
1043
1192
  //#endregion
1044
1193
  //#region src/hooks/useActive.ts
1045
- const useActive = (path) => {
1194
+ const useActive = (args) => {
1046
1195
  const router = useRouter();
1047
- const ctx = useContext(RouterContext);
1048
- const layer = useContext(RouterLayerContext);
1049
- if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1050
- const [current, setCurrent] = useState(ctx.state.pathname);
1051
- const href = useMemo(() => router.createHref(path ?? "", layer), [path, layer]);
1052
1196
  const [isPending, setPending] = useState(false);
1053
- const isActive = current === href || current === `${href}/` || `${current}/` === href;
1054
- useRouterEvents({ onEnd: ({ state }) => {
1055
- path && setCurrent(state.pathname);
1056
- } }, [path]);
1197
+ const state = useRouterState();
1198
+ const current = state.url.pathname;
1199
+ const options = typeof args === "string" ? { href: args } : {
1200
+ ...args,
1201
+ href: args.href
1202
+ };
1203
+ const href = options.href;
1204
+ let isActive = current === href || current === `${href}/` || `${current}/` === href;
1205
+ if (options.startWith && !isActive) isActive = current.startsWith(href);
1057
1206
  return {
1058
1207
  isPending,
1059
1208
  isActive,
1060
1209
  anchorProps: {
1061
- href,
1062
- onClick: (ev) => {
1210
+ href: router.base(href),
1211
+ onClick: async (ev) => {
1063
1212
  ev?.stopPropagation();
1064
1213
  ev?.preventDefault();
1065
1214
  if (isActive) return;
1066
1215
  if (isPending) return;
1067
1216
  setPending(true);
1068
- router.go(href).then(() => {
1217
+ try {
1218
+ await router.go(href);
1219
+ } finally {
1069
1220
  setPending(false);
1070
- });
1221
+ }
1071
1222
  }
1072
1223
  }
1073
1224
  };
1074
1225
  };
1075
1226
 
1076
1227
  //#endregion
1077
- //#region src/hooks/useInject.ts
1078
- const useInject = (service) => {
1079
- const alepha = useAlepha();
1080
- return useMemo(() => alepha.inject(service), []);
1081
- };
1082
-
1083
- //#endregion
1084
- //#region src/hooks/useStore.ts
1228
+ //#region src/hooks/useClient.ts
1085
1229
  /**
1086
- * Hook to access and mutate the Alepha state.
1230
+ * Hook to get a virtual client for the specified scope.
1231
+ *
1232
+ * It's the React-hook version of `$client()`, from `AlephaServerLinks` module.
1087
1233
  */
1088
- const useStore = (key, defaultValue) => {
1089
- const alepha = useAlepha();
1090
- useMemo(() => {
1091
- if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
1092
- }, [defaultValue]);
1093
- const [state, setState] = useState(alepha.state(key));
1094
- useEffect(() => {
1095
- if (!alepha.isBrowser()) return;
1096
- return alepha.on("state:mutate", (ev) => {
1097
- if (ev.key === key) setState(ev.value);
1098
- });
1099
- }, []);
1100
- if (!alepha.isBrowser()) {
1101
- const value = alepha.context.get(key);
1102
- if (value !== null) return [value, (_) => {}];
1103
- }
1104
- return [state, (value) => {
1105
- alepha.state(key, value);
1106
- }];
1107
- };
1108
-
1109
- //#endregion
1110
- //#region src/hooks/useClient.ts
1111
- const useClient = (_scope) => {
1112
- useStore("user");
1113
- return useInject(LinkProvider).client();
1234
+ const useClient = (scope) => {
1235
+ return useInject(LinkProvider).client(scope);
1114
1236
  };
1115
1237
 
1116
1238
  //#endregion
1117
1239
  //#region src/hooks/useQueryParams.ts
1240
+ /**
1241
+ * Not well tested. Use with caution.
1242
+ */
1118
1243
  const useQueryParams = (schema, options = {}) => {
1119
1244
  const alepha = useAlepha();
1120
1245
  const key = options.key ?? "q";
@@ -1145,29 +1270,17 @@ const decode = (alepha, schema, data) => {
1145
1270
  }
1146
1271
  };
1147
1272
 
1148
- //#endregion
1149
- //#region src/hooks/useRouterState.ts
1150
- const useRouterState = () => {
1151
- const router = useContext(RouterContext);
1152
- const layer = useContext(RouterLayerContext);
1153
- if (!router || !layer) throw new Error("useRouterState must be used within a RouterContext.Provider");
1154
- const [state, setState] = useState(router.state);
1155
- useRouterEvents({ onEnd: ({ state: state$1 }) => setState({ ...state$1 }) });
1156
- return state;
1157
- };
1158
-
1159
1273
  //#endregion
1160
1274
  //#region src/hooks/useSchema.ts
1161
1275
  const useSchema = (action) => {
1162
1276
  const name = action.name;
1163
1277
  const alepha = useAlepha();
1164
1278
  const httpClient = useInject(HttpClient);
1165
- const linkProvider = useInject(LinkProvider);
1166
1279
  const [schema, setSchema] = useState(ssrSchemaLoading(alepha, name));
1167
1280
  useEffect(() => {
1168
1281
  if (!schema.loading) return;
1169
1282
  const opts = { cache: true };
1170
- httpClient.fetch(`${linkProvider.URL_LINKS}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
1283
+ httpClient.fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, {}, opts).then((it) => setSchema(it.data));
1171
1284
  }, [name]);
1172
1285
  return schema;
1173
1286
  };
@@ -1176,10 +1289,10 @@ const useSchema = (action) => {
1176
1289
  */
1177
1290
  const ssrSchemaLoading = (alepha, name) => {
1178
1291
  if (!alepha.isBrowser()) {
1179
- const links = alepha.context.get("links")?.links ?? [];
1180
- const can = links.find((it) => it.name === name);
1292
+ const linkProvider = alepha.inject(LinkProvider);
1293
+ const can = linkProvider.getServerLinks().find((link) => link.name === name);
1181
1294
  if (can) {
1182
- const schema$1 = alepha.inject(LinkProvider).links?.find((it) => it.name === name)?.schema;
1295
+ const schema$1 = linkProvider.links.find((it) => it.name === name)?.schema;
1183
1296
  if (schema$1) {
1184
1297
  can.schema = schema$1;
1185
1298
  return schema$1;
@@ -1187,7 +1300,7 @@ const ssrSchemaLoading = (alepha, name) => {
1187
1300
  }
1188
1301
  return { loading: true };
1189
1302
  }
1190
- const schema = alepha.inject(LinkProvider).links?.find((it) => it.name === name)?.schema;
1303
+ const schema = alepha.inject(LinkProvider).links.find((it) => it.name === name)?.schema;
1191
1304
  if (schema) return schema;
1192
1305
  return { loading: true };
1193
1306
  };
@@ -1198,14 +1311,15 @@ const AlephaReact = $module({
1198
1311
  name: "alepha.react",
1199
1312
  descriptors: [$page],
1200
1313
  services: [
1201
- PageDescriptorProvider,
1202
- ReactBrowserRenderer,
1203
- BrowserRouterProvider,
1204
- ReactBrowserProvider
1314
+ ReactPageProvider,
1315
+ ReactBrowserRouterProvider,
1316
+ ReactBrowserProvider,
1317
+ ReactRouter,
1318
+ ReactBrowserRendererProvider
1205
1319
  ],
1206
- register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(PageDescriptorProvider).with(ReactBrowserProvider).with(BrowserRouterProvider).with(ReactBrowserRenderer)
1320
+ register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(ReactPageProvider).with(ReactBrowserProvider).with(ReactBrowserRouterProvider).with(ReactBrowserRendererProvider).with(ReactRouter)
1207
1321
  });
1208
1322
 
1209
1323
  //#endregion
1210
- 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, Redirection, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, ssrSchemaLoading, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState, useSchema, useStore };
1324
+ export { $page, AlephaContext, AlephaReact, ClientOnly, ErrorBoundary, ErrorViewer, Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, ReactBrowserProvider, ReactBrowserRouterProvider, ReactPageProvider, ReactRouter, Redirection, RouterLayerContext, isPageRoute, ssrSchemaLoading, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState, useSchema, useStore };
1211
1325
  //# sourceMappingURL=index.browser.js.map