@alepha/react 0.8.0 → 0.9.0

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.js CHANGED
@@ -1,31 +1,35 @@
1
- import { $hook, $inject, $logger, Alepha, KIND, NotImplementedError, OPTIONS, __bind, __descriptor, t } from "@alepha/core";
1
+ import { $env, $hook, $inject, $logger, $module, Alepha, Descriptor, KIND, NotImplementedError, createDescriptor, t } from "@alepha/core";
2
2
  import { AlephaServer, ServerRouterProvider, ServerTimingProvider, apiLinksResponseSchema } from "@alepha/server";
3
3
  import { AlephaServerCache } from "@alepha/server-cache";
4
4
  import { AlephaServerLinks, LinkProvider, ServerLinksProvider } from "@alepha/server-links";
5
5
  import React, { StrictMode, createContext, createElement, useContext, useEffect, useMemo, useState } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { RouterProvider } from "@alepha/router";
7
8
  import { existsSync } from "node:fs";
8
9
  import { join } from "node:path";
9
10
  import { ServerStaticProvider } from "@alepha/server-static";
10
11
  import { renderToString } from "react-dom/server";
11
- import { RouterProvider } from "@alepha/router";
12
12
 
13
13
  //#region src/descriptors/$page.ts
14
- const KEY = "PAGE";
15
14
  /**
16
15
  * Main descriptor for defining a React route in the application.
17
16
  */
18
17
  const $page = (options) => {
19
- __descriptor(KEY);
20
- return {
21
- [KIND]: KEY,
22
- [OPTIONS]: options,
23
- render: () => {
24
- throw new NotImplementedError(KEY);
25
- }
26
- };
18
+ return createDescriptor(PageDescriptor, options);
27
19
  };
28
- $page[KIND] = KEY;
20
+ var PageDescriptor = class extends Descriptor {
21
+ get name() {
22
+ return this.options.name ?? this.config.propertyKey;
23
+ }
24
+ /**
25
+ * For testing or build purposes, this will render the page (with or without the HTML layout) and return the HTML and context.
26
+ * Only valid for server-side rendering, it will throw an error if called on the client-side.
27
+ */
28
+ async render(options) {
29
+ throw new NotImplementedError("");
30
+ }
31
+ };
32
+ $page[KIND] = PageDescriptor;
29
33
 
30
34
  //#endregion
31
35
  //#region src/components/ClientOnly.tsx
@@ -291,7 +295,7 @@ const NestedView = (props) => {
291
295
  const index = layer?.index ?? 0;
292
296
  const [view, setView] = useState(app?.state.layers[index]?.element);
293
297
  useRouterEvents({ onEnd: ({ state }) => {
294
- setView(state.layers[index]?.element);
298
+ if (!state.layers[index]?.cache) setView(state.layers[index]?.element);
295
299
  } }, [app]);
296
300
  if (!app) throw new Error("NestedView must be used within a RouterContext.");
297
301
  const element = view ?? props.children ?? null;
@@ -341,7 +345,7 @@ var RedirectionError = class extends Error {
341
345
  const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
342
346
  var PageDescriptorProvider = class {
343
347
  log = $logger();
344
- env = $inject(envSchema$1);
348
+ env = $env(envSchema$1);
345
349
  alepha = $inject(Alepha);
346
350
  pages = [];
347
351
  getPages() {
@@ -389,19 +393,18 @@ var PageDescriptorProvider = class {
389
393
  const route$1 = it.route;
390
394
  const config = {};
391
395
  try {
392
- config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) : request.query;
396
+ config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) : {};
393
397
  } catch (e) {
394
398
  it.error = e;
395
399
  break;
396
400
  }
397
401
  try {
398
- config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) : request.params;
402
+ config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) : {};
399
403
  } catch (e) {
400
404
  it.error = e;
401
405
  break;
402
406
  }
403
407
  it.config = { ...config };
404
- if (!route$1.resolve) continue;
405
408
  const previous = request.previous;
406
409
  if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
407
410
  const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
@@ -416,6 +419,7 @@ var PageDescriptorProvider = class {
416
419
  if (prev === curr) {
417
420
  it.props = previous[i].props;
418
421
  it.error = previous[i].error;
422
+ it.cache = true;
419
423
  context = {
420
424
  ...context,
421
425
  ...it.props
@@ -424,6 +428,7 @@ var PageDescriptorProvider = class {
424
428
  }
425
429
  forceRefresh = true;
426
430
  }
431
+ if (!route$1.resolve) continue;
427
432
  try {
428
433
  const props = await route$1.resolve?.({
429
434
  ...request,
@@ -470,7 +475,7 @@ var PageDescriptorProvider = class {
470
475
  element: this.renderView(i + 1, path, element$1, it.route),
471
476
  index: i + 1,
472
477
  path,
473
- route
478
+ route: it.route
474
479
  });
475
480
  break;
476
481
  }
@@ -486,7 +491,8 @@ var PageDescriptorProvider = class {
486
491
  element: this.renderView(i + 1, path, element, it.route),
487
492
  index: i + 1,
488
493
  path,
489
- route
494
+ route: it.route,
495
+ cache: it.cache
490
496
  });
491
497
  }
492
498
  return {
@@ -545,18 +551,17 @@ var PageDescriptorProvider = class {
545
551
  on: "configure",
546
552
  handler: () => {
547
553
  let hasNotFoundHandler = false;
548
- const pages = this.alepha.getDescriptorValues($page);
554
+ const pages = this.alepha.descriptors($page);
549
555
  const hasParent = (it) => {
550
556
  for (const page of pages) {
551
- const children = page.value[OPTIONS].children ? Array.isArray(page.value[OPTIONS].children) ? page.value[OPTIONS].children : page.value[OPTIONS].children() : [];
557
+ const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
552
558
  if (children.includes(it)) return true;
553
559
  }
554
560
  };
555
- for (const { value, key } of pages) value[OPTIONS].name ??= key;
556
- for (const { value } of pages) {
557
- if (hasParent(value)) continue;
558
- if (value[OPTIONS].path === "/*") hasNotFoundHandler = true;
559
- this.add(this.map(pages, value));
561
+ for (const page of pages) {
562
+ if (page.options.path === "/*") hasNotFoundHandler = true;
563
+ if (hasParent(page)) continue;
564
+ this.add(this.map(pages, page));
560
565
  }
561
566
  if (!hasNotFoundHandler && pages.length > 0) this.add({
562
567
  path: "/*",
@@ -570,9 +575,10 @@ var PageDescriptorProvider = class {
570
575
  }
571
576
  });
572
577
  map(pages, target) {
573
- const children = target[OPTIONS].children ? Array.isArray(target[OPTIONS].children) ? target[OPTIONS].children : target[OPTIONS].children() : [];
578
+ const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
574
579
  return {
575
- ...target[OPTIONS],
580
+ ...target.options,
581
+ name: target.name,
576
582
  parent: void 0,
577
583
  children: children.map((it) => this.map(pages, it))
578
584
  };
@@ -609,6 +615,214 @@ const isPageRoute = (it) => {
609
615
  return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
610
616
  };
611
617
 
618
+ //#endregion
619
+ //#region src/providers/BrowserRouterProvider.ts
620
+ var BrowserRouterProvider = class extends RouterProvider {
621
+ log = $logger();
622
+ alepha = $inject(Alepha);
623
+ pageDescriptorProvider = $inject(PageDescriptorProvider);
624
+ add(entry) {
625
+ this.pageDescriptorProvider.add(entry);
626
+ }
627
+ configure = $hook({
628
+ on: "configure",
629
+ handler: async () => {
630
+ for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
631
+ path: page.match,
632
+ page
633
+ });
634
+ }
635
+ });
636
+ async transition(url, options = {}) {
637
+ const { pathname, search } = url;
638
+ const state = {
639
+ pathname,
640
+ search,
641
+ layers: []
642
+ };
643
+ const context = {
644
+ url,
645
+ query: {},
646
+ params: {},
647
+ onError: () => null,
648
+ ...options.context ?? {}
649
+ };
650
+ await this.alepha.emit("react:transition:begin", {
651
+ state,
652
+ context
653
+ });
654
+ try {
655
+ const previous = options.previous;
656
+ const { route, params } = this.match(pathname);
657
+ const query = {};
658
+ if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
659
+ context.query = query;
660
+ context.params = params ?? {};
661
+ context.previous = previous;
662
+ if (isPageRoute(route)) {
663
+ const result = await this.pageDescriptorProvider.createLayers(route.page, context);
664
+ if (result.redirect) return {
665
+ redirect: result.redirect,
666
+ state,
667
+ context
668
+ };
669
+ state.layers = result.layers;
670
+ }
671
+ if (state.layers.length === 0) state.layers.push({
672
+ name: "not-found",
673
+ element: createElement(NotFoundPage),
674
+ index: 0,
675
+ path: "/"
676
+ });
677
+ await this.alepha.emit("react:transition:success", {
678
+ state,
679
+ context
680
+ });
681
+ } catch (e) {
682
+ this.log.error(e);
683
+ state.layers = [{
684
+ name: "error",
685
+ element: this.pageDescriptorProvider.renderError(e),
686
+ index: 0,
687
+ path: "/"
688
+ }];
689
+ await this.alepha.emit("react:transition:error", {
690
+ error: e,
691
+ state,
692
+ context
693
+ });
694
+ }
695
+ if (options.state) {
696
+ options.state.layers = state.layers;
697
+ options.state.pathname = state.pathname;
698
+ options.state.search = state.search;
699
+ }
700
+ await this.alepha.emit("react:transition:end", {
701
+ state: options.state,
702
+ context
703
+ });
704
+ return {
705
+ context,
706
+ state
707
+ };
708
+ }
709
+ root(state, context) {
710
+ return this.pageDescriptorProvider.root(state, context);
711
+ }
712
+ };
713
+
714
+ //#endregion
715
+ //#region src/providers/ReactBrowserProvider.ts
716
+ var ReactBrowserProvider = class {
717
+ log = $logger();
718
+ client = $inject(LinkProvider);
719
+ alepha = $inject(Alepha);
720
+ router = $inject(BrowserRouterProvider);
721
+ root;
722
+ transitioning;
723
+ state = {
724
+ layers: [],
725
+ pathname: "",
726
+ search: ""
727
+ };
728
+ get document() {
729
+ return window.document;
730
+ }
731
+ get history() {
732
+ return window.history;
733
+ }
734
+ get location() {
735
+ return window.location;
736
+ }
737
+ get url() {
738
+ let url = this.location.pathname + this.location.search;
739
+ if (import.meta?.env?.BASE_URL) {
740
+ url = url.replace(import.meta.env?.BASE_URL, "");
741
+ if (!url.startsWith("/")) url = `/${url}`;
742
+ }
743
+ return url;
744
+ }
745
+ pushState(url, replace) {
746
+ let path = url;
747
+ if (import.meta?.env?.BASE_URL) path = (import.meta.env?.BASE_URL + path).replaceAll("//", "/");
748
+ if (replace) this.history.replaceState({}, "", path);
749
+ else this.history.pushState({}, "", path);
750
+ }
751
+ async invalidate(props) {
752
+ const previous = [];
753
+ if (props) {
754
+ const [key] = Object.keys(props);
755
+ const value = props[key];
756
+ for (const layer of this.state.layers) {
757
+ if (layer.props?.[key]) {
758
+ previous.push({
759
+ ...layer,
760
+ props: {
761
+ ...layer.props,
762
+ [key]: value
763
+ }
764
+ });
765
+ break;
766
+ }
767
+ previous.push(layer);
768
+ }
769
+ }
770
+ await this.render({ previous });
771
+ }
772
+ async go(url, options = {}) {
773
+ const result = await this.render({ url });
774
+ if (result.context.url.pathname !== url) {
775
+ this.pushState(result.context.url.pathname);
776
+ return;
777
+ }
778
+ if (options.replace) {
779
+ this.pushState(url);
780
+ return;
781
+ }
782
+ this.pushState(url);
783
+ }
784
+ async render(options = {}) {
785
+ const previous = options.previous ?? this.state.layers;
786
+ const url = options.url ?? this.url;
787
+ this.transitioning = { to: url };
788
+ const result = await this.router.transition(new URL(`http://localhost${url}`), {
789
+ previous,
790
+ state: this.state
791
+ });
792
+ if (result.redirect) return await this.render({ url: result.redirect });
793
+ this.transitioning = void 0;
794
+ return result;
795
+ }
796
+ /**
797
+ * Get embedded layers from the server.
798
+ */
799
+ getHydrationState() {
800
+ try {
801
+ if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
802
+ } catch (error) {
803
+ console.error(error);
804
+ }
805
+ }
806
+ ready = $hook({
807
+ on: "ready",
808
+ handler: async () => {
809
+ const hydration = this.getHydrationState();
810
+ const previous = hydration?.layers ?? [];
811
+ if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
812
+ const { context } = await this.render({ previous });
813
+ await this.alepha.emit("react:browser:render", {
814
+ state: this.state,
815
+ context,
816
+ hydration
817
+ });
818
+ window.addEventListener("popstate", () => {
819
+ if (this.state.pathname === location.pathname) return;
820
+ this.render();
821
+ });
822
+ }
823
+ });
824
+ };
825
+
612
826
  //#endregion
613
827
  //#region src/providers/ReactServerProvider.ts
614
828
  const envSchema = t.object({
@@ -624,18 +838,15 @@ var ReactServerProvider = class {
624
838
  serverStaticProvider = $inject(ServerStaticProvider);
625
839
  serverRouterProvider = $inject(ServerRouterProvider);
626
840
  serverTimingProvider = $inject(ServerTimingProvider);
627
- env = $inject(envSchema);
841
+ env = $env(envSchema);
628
842
  ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
629
843
  onConfigure = $hook({
630
844
  on: "configure",
631
845
  handler: async () => {
632
- const pages = this.alepha.getDescriptorValues($page);
846
+ const pages = this.alepha.descriptors($page);
633
847
  const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
634
- this.alepha.state("ReactServerProvider.ssr", ssrEnabled);
635
- for (const { key, instance, value } of pages) {
636
- const name = value[OPTIONS].name ?? key;
637
- instance[key].render = this.createRenderFunction(name);
638
- }
848
+ this.alepha.state("react.server.ssr", ssrEnabled);
849
+ for (const page of pages) page.render = this.createRenderFunction(page.name);
639
850
  if (this.alepha.isServerless() === "vite") {
640
851
  await this.configureVite(ssrEnabled);
641
852
  return;
@@ -655,7 +866,7 @@ var ReactServerProvider = class {
655
866
  return;
656
867
  }
657
868
  this.log.info("SSR is disabled, use History API fallback");
658
- await this.serverRouterProvider.route({
869
+ this.serverRouterProvider.createRoute({
659
870
  path: "*",
660
871
  handler: async ({ url, reply }) => {
661
872
  if (url.pathname.includes(".")) {
@@ -671,13 +882,13 @@ var ReactServerProvider = class {
671
882
  }
672
883
  });
673
884
  get template() {
674
- return this.alepha.state("ReactServerProvider.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
885
+ return this.alepha.state("react.server.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
675
886
  }
676
887
  async registerPages(templateLoader) {
677
888
  for (const page of this.pageDescriptorProvider.getPages()) {
678
889
  if (page.children?.length) continue;
679
890
  this.log.debug(`+ ${page.match} -> ${page.name}`);
680
- await this.serverRouterProvider.route({
891
+ this.serverRouterProvider.createRoute({
681
892
  ...page,
682
893
  schema: void 0,
683
894
  method: "GET",
@@ -692,7 +903,7 @@ var ReactServerProvider = class {
692
903
  return "";
693
904
  }
694
905
  async configureStaticServer(root) {
695
- await this.serverStaticProvider.serve({
906
+ await this.serverStaticProvider.createStaticServer({
696
907
  root,
697
908
  path: this.env.REACT_SERVER_PREFIX
698
909
  });
@@ -746,7 +957,7 @@ var ReactServerProvider = class {
746
957
  onError: () => null
747
958
  };
748
959
  if (this.alepha.has(ServerLinksProvider)) {
749
- const srv = this.alepha.get(ServerLinksProvider);
960
+ const srv = this.alepha.inject(ServerLinksProvider);
750
961
  const schema = apiLinksResponseSchema;
751
962
  context.links = this.alepha.parse(schema, await srv.getLinks({
752
963
  user: serverRequest.user,
@@ -836,208 +1047,24 @@ var ReactServerProvider = class {
836
1047
  }
837
1048
  };
838
1049
 
839
- //#endregion
840
- //#region src/providers/BrowserRouterProvider.ts
841
- var BrowserRouterProvider = class extends RouterProvider {
842
- log = $logger();
843
- alepha = $inject(Alepha);
844
- pageDescriptorProvider = $inject(PageDescriptorProvider);
845
- add(entry) {
846
- this.pageDescriptorProvider.add(entry);
847
- }
848
- configure = $hook({
849
- on: "configure",
850
- handler: async () => {
851
- for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
852
- path: page.match,
853
- page
854
- });
855
- }
856
- });
857
- async transition(url, options = {}) {
858
- const { pathname, search } = url;
859
- const state = {
860
- pathname,
861
- search,
862
- layers: []
863
- };
864
- const context = {
865
- url,
866
- query: {},
867
- params: {},
868
- onError: () => null,
869
- ...options.context ?? {}
870
- };
871
- await this.alepha.emit("react:transition:begin", {
872
- state,
873
- context
874
- });
875
- try {
876
- const previous = options.previous;
877
- const { route, params } = this.match(pathname);
878
- const query = {};
879
- if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
880
- context.query = query;
881
- context.params = params ?? {};
882
- context.previous = previous;
883
- if (isPageRoute(route)) {
884
- const result = await this.pageDescriptorProvider.createLayers(route.page, context);
885
- if (result.redirect) return {
886
- redirect: result.redirect,
887
- state,
888
- context
889
- };
890
- state.layers = result.layers;
891
- }
892
- if (state.layers.length === 0) state.layers.push({
893
- name: "not-found",
894
- element: createElement(NotFoundPage),
895
- index: 0,
896
- path: "/"
897
- });
898
- await this.alepha.emit("react:transition:success", {
899
- state,
900
- context
901
- });
902
- } catch (e) {
903
- this.log.error(e);
904
- state.layers = [{
905
- name: "error",
906
- element: this.pageDescriptorProvider.renderError(e),
907
- index: 0,
908
- path: "/"
909
- }];
910
- await this.alepha.emit("react:transition:error", {
911
- error: e,
912
- state,
913
- context
914
- });
915
- }
916
- if (options.state) {
917
- options.state.layers = state.layers;
918
- options.state.pathname = state.pathname;
919
- options.state.search = state.search;
920
- }
921
- await this.alepha.emit("react:transition:end", {
922
- state: options.state,
923
- context
924
- });
925
- return {
926
- context,
927
- state
928
- };
929
- }
930
- root(state, context) {
931
- return this.pageDescriptorProvider.root(state, context);
932
- }
933
- };
934
-
935
- //#endregion
936
- //#region src/providers/ReactBrowserProvider.ts
937
- var ReactBrowserProvider = class {
938
- log = $logger();
939
- client = $inject(LinkProvider);
940
- alepha = $inject(Alepha);
941
- router = $inject(BrowserRouterProvider);
942
- root;
943
- transitioning;
944
- state = {
945
- layers: [],
946
- pathname: "",
947
- search: ""
948
- };
949
- get document() {
950
- return window.document;
951
- }
952
- get history() {
953
- return window.history;
954
- }
955
- get url() {
956
- return window.location.pathname + window.location.search;
957
- }
958
- async invalidate(props) {
959
- const previous = [];
960
- if (props) {
961
- const [key] = Object.keys(props);
962
- const value = props[key];
963
- for (const layer of this.state.layers) {
964
- if (layer.props?.[key]) {
965
- previous.push({
966
- ...layer,
967
- props: {
968
- ...layer.props,
969
- [key]: value
970
- }
971
- });
972
- break;
973
- }
974
- previous.push(layer);
975
- }
976
- }
977
- await this.render({ previous });
978
- }
979
- async go(url, options = {}) {
980
- const result = await this.render({ url });
981
- if (result.context.url.pathname !== url) {
982
- this.history.replaceState({}, "", result.context.url.pathname);
983
- return;
984
- }
985
- if (options.replace) {
986
- this.history.replaceState({}, "", url);
987
- return;
988
- }
989
- this.history.pushState({}, "", url);
990
- }
991
- async render(options = {}) {
992
- const previous = options.previous ?? this.state.layers;
993
- const url = options.url ?? this.url;
994
- this.transitioning = { to: url };
995
- const result = await this.router.transition(new URL(`http://localhost${url}`), {
996
- previous,
997
- state: this.state
998
- });
999
- if (result.redirect) return await this.render({ url: result.redirect });
1000
- this.transitioning = void 0;
1001
- return result;
1002
- }
1003
- /**
1004
- * Get embedded layers from the server.
1005
- */
1006
- getHydrationState() {
1007
- try {
1008
- if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
1009
- } catch (error) {
1010
- console.error(error);
1011
- }
1012
- }
1013
- ready = $hook({
1014
- on: "ready",
1015
- handler: async () => {
1016
- const hydration = this.getHydrationState();
1017
- const previous = hydration?.layers ?? [];
1018
- if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
1019
- const { context } = await this.render({ previous });
1020
- await this.alepha.emit("react:browser:render", {
1021
- state: this.state,
1022
- context,
1023
- hydration
1024
- });
1025
- window.addEventListener("popstate", () => {
1026
- this.render();
1027
- });
1028
- }
1029
- });
1030
- };
1031
-
1032
1050
  //#endregion
1033
1051
  //#region src/hooks/RouterHookApi.ts
1034
1052
  var RouterHookApi = class {
1035
- constructor(pages, state, layer, browser) {
1053
+ constructor(pages, context, state, layer, browser) {
1036
1054
  this.pages = pages;
1055
+ this.context = context;
1037
1056
  this.state = state;
1038
1057
  this.layer = layer;
1039
1058
  this.browser = browser;
1040
1059
  }
1060
+ getURL() {
1061
+ if (!this.browser) return this.context.url;
1062
+ return new URL(this.location.href);
1063
+ }
1064
+ get location() {
1065
+ if (!this.browser) throw new Error("Browser is required");
1066
+ return this.browser.location;
1067
+ }
1041
1068
  get current() {
1042
1069
  return this.state;
1043
1070
  }
@@ -1113,9 +1140,9 @@ const useRouter = () => {
1113
1140
  const layer = useContext(RouterLayerContext);
1114
1141
  if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1115
1142
  const pages = useMemo(() => {
1116
- return ctx.alepha.get(PageDescriptorProvider).getPages();
1143
+ return ctx.alepha.inject(PageDescriptorProvider).getPages();
1117
1144
  }, []);
1118
- return useMemo(() => new RouterHookApi(pages, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
1145
+ return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
1119
1146
  };
1120
1147
 
1121
1148
  //#endregion
@@ -1123,11 +1150,11 @@ const useRouter = () => {
1123
1150
  const Link = (props) => {
1124
1151
  React.useContext(RouterContext);
1125
1152
  const router = useRouter();
1126
- const to = typeof props.to === "string" ? props.to : props.to[OPTIONS].path;
1153
+ const to = typeof props.to === "string" ? props.to : props.to.options.path;
1127
1154
  if (!to) return null;
1128
- const can = typeof props.to === "string" ? void 0 : props.to[OPTIONS].can;
1155
+ const can = typeof props.to === "string" ? void 0 : props.to.options.can;
1129
1156
  if (can && !can()) return null;
1130
- const name = typeof props.to === "string" ? void 0 : props.to[OPTIONS].name;
1157
+ const name = typeof props.to === "string" ? void 0 : props.to.options.name;
1131
1158
  const anchorProps = {
1132
1159
  ...props,
1133
1160
  to: void 0
@@ -1179,7 +1206,7 @@ const useActive = (path) => {
1179
1206
  const useInject = (clazz) => {
1180
1207
  const ctx = useContext(RouterContext);
1181
1208
  if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
1182
- return useMemo(() => ctx.alepha.get(clazz), []);
1209
+ return useMemo(() => ctx.alepha.inject(clazz), []);
1183
1210
  };
1184
1211
 
1185
1212
  //#endregion
@@ -1235,20 +1262,26 @@ const useRouterState = () => {
1235
1262
  //#endregion
1236
1263
  //#region src/index.ts
1237
1264
  /**
1238
- * Alepha React Module
1265
+ * Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
1239
1266
  *
1240
- * Alepha React Module contains a router for client-side navigation and server-side rendering.
1241
- * Routes can be defined using the `$page` descriptor.
1267
+ * The React module enables building modern React applications using the `$page` descriptor on class properties.
1268
+ * It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
1269
+ * type safety and schema validation for route parameters and data.
1242
1270
  *
1243
1271
  * @see {@link $page}
1244
1272
  * @module alepha.react
1245
1273
  */
1246
- var AlephaReact = class {
1247
- name = "alepha.react";
1248
- $services = (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider);
1249
- };
1250
- __bind($page, AlephaReact);
1274
+ const AlephaReact = $module({
1275
+ name: "alepha.react",
1276
+ descriptors: [$page],
1277
+ services: [
1278
+ ReactServerProvider,
1279
+ PageDescriptorProvider,
1280
+ ReactBrowserProvider
1281
+ ],
1282
+ register: (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider)
1283
+ });
1251
1284
 
1252
1285
  //#endregion
1253
- export { $page, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, PageDescriptorProvider, ReactBrowserProvider, ReactServerProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
1286
+ export { $page, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider, ReactServerProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
1254
1287
  //# sourceMappingURL=index.js.map