@alepha/react 0.8.1 → 0.9.1

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
@@ -27,28 +27,32 @@ const __alepha_server_cache = __toESM(require("@alepha/server-cache"));
27
27
  const __alepha_server_links = __toESM(require("@alepha/server-links"));
28
28
  const react = __toESM(require("react"));
29
29
  const react_jsx_runtime = __toESM(require("react/jsx-runtime"));
30
+ const __alepha_router = __toESM(require("@alepha/router"));
30
31
  const node_fs = __toESM(require("node:fs"));
31
32
  const node_path = __toESM(require("node:path"));
32
33
  const __alepha_server_static = __toESM(require("@alepha/server-static"));
33
34
  const react_dom_server = __toESM(require("react-dom/server"));
34
- const __alepha_router = __toESM(require("@alepha/router"));
35
35
 
36
36
  //#region src/descriptors/$page.ts
37
- const KEY = "PAGE";
38
37
  /**
39
38
  * Main descriptor for defining a React route in the application.
40
39
  */
41
40
  const $page = (options) => {
42
- (0, __alepha_core.__descriptor)(KEY);
43
- return {
44
- [__alepha_core.KIND]: KEY,
45
- [__alepha_core.OPTIONS]: options,
46
- render: () => {
47
- throw new __alepha_core.NotImplementedError(KEY);
48
- }
49
- };
41
+ return (0, __alepha_core.createDescriptor)(PageDescriptor, options);
50
42
  };
51
- $page[__alepha_core.KIND] = KEY;
43
+ var PageDescriptor = class extends __alepha_core.Descriptor {
44
+ get name() {
45
+ return this.options.name ?? this.config.propertyKey;
46
+ }
47
+ /**
48
+ * For testing or build purposes, this will render the page (with or without the HTML layout) and return the HTML and context.
49
+ * Only valid for server-side rendering, it will throw an error if called on the client-side.
50
+ */
51
+ async render(options) {
52
+ throw new __alepha_core.NotImplementedError("");
53
+ }
54
+ };
55
+ $page[__alepha_core.KIND] = PageDescriptor;
52
56
 
53
57
  //#endregion
54
58
  //#region src/components/ClientOnly.tsx
@@ -70,23 +74,11 @@ const ClientOnly = (props) => {
70
74
  };
71
75
  var ClientOnly_default = ClientOnly;
72
76
 
73
- //#endregion
74
- //#region src/contexts/RouterContext.ts
75
- const RouterContext = (0, react.createContext)(void 0);
76
-
77
- //#endregion
78
- //#region src/hooks/useAlepha.ts
79
- const useAlepha = () => {
80
- const routerContext = (0, react.useContext)(RouterContext);
81
- if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
82
- return routerContext.alepha;
83
- };
84
-
85
77
  //#endregion
86
78
  //#region src/components/ErrorViewer.tsx
87
- const ErrorViewer = ({ error }) => {
79
+ const ErrorViewer = ({ error, alepha }) => {
88
80
  const [expanded, setExpanded] = (0, react.useState)(false);
89
- const isProduction = useAlepha().isProduction();
81
+ const isProduction = alepha.isProduction();
90
82
  if (isProduction) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ErrorViewerProduction, {});
91
83
  const stackLines = error.stack?.split("\n") ?? [];
92
84
  const previewLines = stackLines.slice(0, 5);
@@ -230,6 +222,10 @@ const ErrorViewerProduction = () => {
230
222
  });
231
223
  };
232
224
 
225
+ //#endregion
226
+ //#region src/contexts/RouterContext.ts
227
+ const RouterContext = (0, react.createContext)(void 0);
228
+
233
229
  //#endregion
234
230
  //#region src/contexts/RouterLayerContext.ts
235
231
  const RouterLayerContext = (0, react.createContext)(void 0);
@@ -364,7 +360,7 @@ var RedirectionError = class extends Error {
364
360
  const envSchema$1 = __alepha_core.t.object({ REACT_STRICT_MODE: __alepha_core.t.boolean({ default: true }) });
365
361
  var PageDescriptorProvider = class {
366
362
  log = (0, __alepha_core.$logger)();
367
- env = (0, __alepha_core.$inject)(envSchema$1);
363
+ env = (0, __alepha_core.$env)(envSchema$1);
368
364
  alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
369
365
  pages = [];
370
366
  getPages() {
@@ -537,7 +533,10 @@ var PageDescriptorProvider = class {
537
533
  return void 0;
538
534
  }
539
535
  renderError(error) {
540
- return (0, react.createElement)(ErrorViewer_default, { error });
536
+ return (0, react.createElement)(ErrorViewer_default, {
537
+ error,
538
+ alepha: this.alepha
539
+ });
541
540
  }
542
541
  renderEmptyView() {
543
542
  return (0, react.createElement)(NestedView_default, {});
@@ -570,18 +569,17 @@ var PageDescriptorProvider = class {
570
569
  on: "configure",
571
570
  handler: () => {
572
571
  let hasNotFoundHandler = false;
573
- const pages = this.alepha.getDescriptorValues($page);
572
+ const pages = this.alepha.descriptors($page);
574
573
  const hasParent = (it) => {
575
574
  for (const page of pages) {
576
- const children = page.value[__alepha_core.OPTIONS].children ? Array.isArray(page.value[__alepha_core.OPTIONS].children) ? page.value[__alepha_core.OPTIONS].children : page.value[__alepha_core.OPTIONS].children() : [];
575
+ const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
577
576
  if (children.includes(it)) return true;
578
577
  }
579
578
  };
580
- for (const { value, key } of pages) value[__alepha_core.OPTIONS].name ??= key;
581
- for (const { value } of pages) {
582
- if (value[__alepha_core.OPTIONS].path === "/*") hasNotFoundHandler = true;
583
- if (hasParent(value)) continue;
584
- this.add(this.map(pages, value));
579
+ for (const page of pages) {
580
+ if (page.options.path === "/*") hasNotFoundHandler = true;
581
+ if (hasParent(page)) continue;
582
+ this.add(this.map(pages, page));
585
583
  }
586
584
  if (!hasNotFoundHandler && pages.length > 0) this.add({
587
585
  path: "/*",
@@ -595,9 +593,10 @@ var PageDescriptorProvider = class {
595
593
  }
596
594
  });
597
595
  map(pages, target) {
598
- const children = target[__alepha_core.OPTIONS].children ? Array.isArray(target[__alepha_core.OPTIONS].children) ? target[__alepha_core.OPTIONS].children : target[__alepha_core.OPTIONS].children() : [];
596
+ const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
599
597
  return {
600
- ...target[__alepha_core.OPTIONS],
598
+ ...target.options,
599
+ name: target.name,
601
600
  parent: void 0,
602
601
  children: children.map((it) => this.map(pages, it))
603
602
  };
@@ -634,13 +633,217 @@ const isPageRoute = (it) => {
634
633
  return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
635
634
  };
636
635
 
636
+ //#endregion
637
+ //#region src/providers/BrowserRouterProvider.ts
638
+ var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
639
+ log = (0, __alepha_core.$logger)();
640
+ alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
641
+ pageDescriptorProvider = (0, __alepha_core.$inject)(PageDescriptorProvider);
642
+ add(entry) {
643
+ this.pageDescriptorProvider.add(entry);
644
+ }
645
+ configure = (0, __alepha_core.$hook)({
646
+ on: "configure",
647
+ handler: async () => {
648
+ for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
649
+ path: page.match,
650
+ page
651
+ });
652
+ }
653
+ });
654
+ async transition(url, options = {}) {
655
+ const { pathname, search } = url;
656
+ const state = {
657
+ pathname,
658
+ search,
659
+ layers: []
660
+ };
661
+ const context = {
662
+ url,
663
+ query: {},
664
+ params: {},
665
+ onError: () => null,
666
+ ...options.context ?? {}
667
+ };
668
+ await this.alepha.emit("react:transition:begin", {
669
+ state,
670
+ context
671
+ });
672
+ try {
673
+ const previous = options.previous;
674
+ const { route, params } = this.match(pathname);
675
+ const query = {};
676
+ if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
677
+ context.query = query;
678
+ context.params = params ?? {};
679
+ context.previous = previous;
680
+ if (isPageRoute(route)) {
681
+ const result = await this.pageDescriptorProvider.createLayers(route.page, context);
682
+ if (result.redirect) return {
683
+ redirect: result.redirect,
684
+ state,
685
+ context
686
+ };
687
+ state.layers = result.layers;
688
+ }
689
+ if (state.layers.length === 0) state.layers.push({
690
+ name: "not-found",
691
+ element: (0, react.createElement)(NotFoundPage),
692
+ index: 0,
693
+ path: "/"
694
+ });
695
+ await this.alepha.emit("react:transition:success", {
696
+ state,
697
+ context
698
+ });
699
+ } catch (e) {
700
+ this.log.error(e);
701
+ state.layers = [{
702
+ name: "error",
703
+ element: this.pageDescriptorProvider.renderError(e),
704
+ index: 0,
705
+ path: "/"
706
+ }];
707
+ await this.alepha.emit("react:transition:error", {
708
+ error: e,
709
+ state,
710
+ context
711
+ });
712
+ }
713
+ if (options.state) {
714
+ options.state.layers = state.layers;
715
+ options.state.pathname = state.pathname;
716
+ options.state.search = state.search;
717
+ }
718
+ await this.alepha.emit("react:transition:end", {
719
+ state: options.state,
720
+ context
721
+ });
722
+ return {
723
+ context,
724
+ state
725
+ };
726
+ }
727
+ root(state, context) {
728
+ return this.pageDescriptorProvider.root(state, context);
729
+ }
730
+ };
731
+
732
+ //#endregion
733
+ //#region src/providers/ReactBrowserProvider.ts
734
+ var ReactBrowserProvider = class {
735
+ log = (0, __alepha_core.$logger)();
736
+ client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
737
+ alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
738
+ router = (0, __alepha_core.$inject)(BrowserRouterProvider);
739
+ root;
740
+ transitioning;
741
+ state = {
742
+ layers: [],
743
+ pathname: "",
744
+ search: ""
745
+ };
746
+ get document() {
747
+ return window.document;
748
+ }
749
+ get history() {
750
+ return window.history;
751
+ }
752
+ get location() {
753
+ return window.location;
754
+ }
755
+ get url() {
756
+ let url = this.location.pathname + this.location.search;
757
+ return url;
758
+ }
759
+ pushState(url, replace) {
760
+ let path = url;
761
+ if (replace) this.history.replaceState({}, "", path);
762
+ else this.history.pushState({}, "", path);
763
+ }
764
+ async invalidate(props) {
765
+ const previous = [];
766
+ if (props) {
767
+ const [key] = Object.keys(props);
768
+ const value = props[key];
769
+ for (const layer of this.state.layers) {
770
+ if (layer.props?.[key]) {
771
+ previous.push({
772
+ ...layer,
773
+ props: {
774
+ ...layer.props,
775
+ [key]: value
776
+ }
777
+ });
778
+ break;
779
+ }
780
+ previous.push(layer);
781
+ }
782
+ }
783
+ await this.render({ previous });
784
+ }
785
+ async go(url, options = {}) {
786
+ 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);
793
+ return;
794
+ }
795
+ this.pushState(url);
796
+ }
797
+ async render(options = {}) {
798
+ const previous = options.previous ?? this.state.layers;
799
+ const url = options.url ?? this.url;
800
+ this.transitioning = { to: url };
801
+ const result = await this.router.transition(new URL(`http://localhost${url}`), {
802
+ previous,
803
+ state: this.state
804
+ });
805
+ if (result.redirect) return await this.render({ url: result.redirect });
806
+ this.transitioning = void 0;
807
+ return result;
808
+ }
809
+ /**
810
+ * Get embedded layers from the server.
811
+ */
812
+ getHydrationState() {
813
+ try {
814
+ if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
815
+ } catch (error) {
816
+ console.error(error);
817
+ }
818
+ }
819
+ ready = (0, __alepha_core.$hook)({
820
+ on: "ready",
821
+ handler: async () => {
822
+ const hydration = this.getHydrationState();
823
+ const previous = hydration?.layers ?? [];
824
+ if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
825
+ const { context } = await this.render({ previous });
826
+ await this.alepha.emit("react:browser:render", {
827
+ state: this.state,
828
+ context,
829
+ hydration
830
+ });
831
+ window.addEventListener("popstate", () => {
832
+ if (this.state.pathname === this.url) return;
833
+ this.render();
834
+ });
835
+ }
836
+ });
837
+ };
838
+
637
839
  //#endregion
638
840
  //#region src/providers/ReactServerProvider.ts
639
841
  const envSchema = __alepha_core.t.object({
640
842
  REACT_SERVER_DIST: __alepha_core.t.string({ default: "public" }),
641
843
  REACT_SERVER_PREFIX: __alepha_core.t.string({ default: "" }),
642
844
  REACT_SSR_ENABLED: __alepha_core.t.optional(__alepha_core.t.boolean()),
643
- REACT_ROOT_ID: __alepha_core.t.string({ default: "root" })
845
+ REACT_ROOT_ID: __alepha_core.t.string({ default: "root" }),
846
+ REACT_SERVER_TEMPLATE: __alepha_core.t.optional(__alepha_core.t.string({ size: "rich" }))
644
847
  });
645
848
  var ReactServerProvider = class {
646
849
  log = (0, __alepha_core.$logger)();
@@ -649,18 +852,15 @@ var ReactServerProvider = class {
649
852
  serverStaticProvider = (0, __alepha_core.$inject)(__alepha_server_static.ServerStaticProvider);
650
853
  serverRouterProvider = (0, __alepha_core.$inject)(__alepha_server.ServerRouterProvider);
651
854
  serverTimingProvider = (0, __alepha_core.$inject)(__alepha_server.ServerTimingProvider);
652
- env = (0, __alepha_core.$inject)(envSchema);
855
+ env = (0, __alepha_core.$env)(envSchema);
653
856
  ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
654
857
  onConfigure = (0, __alepha_core.$hook)({
655
858
  on: "configure",
656
859
  handler: async () => {
657
- const pages = this.alepha.getDescriptorValues($page);
860
+ const pages = this.alepha.descriptors($page);
658
861
  const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
659
862
  this.alepha.state("react.server.ssr", ssrEnabled);
660
- for (const { key, instance, value } of pages) {
661
- const name = value[__alepha_core.OPTIONS].name ?? key;
662
- instance[key].render = this.createRenderFunction(name);
663
- }
863
+ for (const page of pages) page.render = this.createRenderFunction(page.name);
664
864
  if (this.alepha.isServerless() === "vite") {
665
865
  await this.configureVite(ssrEnabled);
666
866
  return;
@@ -680,7 +880,7 @@ var ReactServerProvider = class {
680
880
  return;
681
881
  }
682
882
  this.log.info("SSR is disabled, use History API fallback");
683
- await this.serverRouterProvider.route({
883
+ this.serverRouterProvider.createRoute({
684
884
  path: "*",
685
885
  handler: async ({ url, reply }) => {
686
886
  if (url.pathname.includes(".")) {
@@ -696,13 +896,13 @@ var ReactServerProvider = class {
696
896
  }
697
897
  });
698
898
  get template() {
699
- return this.alepha.state("react.server.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
899
+ return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
700
900
  }
701
901
  async registerPages(templateLoader) {
702
902
  for (const page of this.pageDescriptorProvider.getPages()) {
703
903
  if (page.children?.length) continue;
704
904
  this.log.debug(`+ ${page.match} -> ${page.name}`);
705
- await this.serverRouterProvider.route({
905
+ this.serverRouterProvider.createRoute({
706
906
  ...page,
707
907
  schema: void 0,
708
908
  method: "GET",
@@ -717,7 +917,7 @@ var ReactServerProvider = class {
717
917
  return "";
718
918
  }
719
919
  async configureStaticServer(root) {
720
- await this.serverStaticProvider.serve({
920
+ await this.serverStaticProvider.createStaticServer({
721
921
  root,
722
922
  path: this.env.REACT_SERVER_PREFIX
723
923
  });
@@ -771,7 +971,7 @@ var ReactServerProvider = class {
771
971
  onError: () => null
772
972
  };
773
973
  if (this.alepha.has(__alepha_server_links.ServerLinksProvider)) {
774
- const srv = this.alepha.get(__alepha_server_links.ServerLinksProvider);
974
+ const srv = this.alepha.inject(__alepha_server_links.ServerLinksProvider);
775
975
  const schema = __alepha_server.apiLinksResponseSchema;
776
976
  context.links = this.alepha.parse(schema, await srv.getLinks({
777
977
  user: serverRequest.user,
@@ -861,209 +1061,6 @@ var ReactServerProvider = class {
861
1061
  }
862
1062
  };
863
1063
 
864
- //#endregion
865
- //#region src/providers/BrowserRouterProvider.ts
866
- var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
867
- log = (0, __alepha_core.$logger)();
868
- alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
869
- pageDescriptorProvider = (0, __alepha_core.$inject)(PageDescriptorProvider);
870
- add(entry) {
871
- this.pageDescriptorProvider.add(entry);
872
- }
873
- configure = (0, __alepha_core.$hook)({
874
- on: "configure",
875
- handler: async () => {
876
- for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
877
- path: page.match,
878
- page
879
- });
880
- }
881
- });
882
- async transition(url, options = {}) {
883
- const { pathname, search } = url;
884
- const state = {
885
- pathname,
886
- search,
887
- layers: []
888
- };
889
- const context = {
890
- url,
891
- query: {},
892
- params: {},
893
- onError: () => null,
894
- ...options.context ?? {}
895
- };
896
- await this.alepha.emit("react:transition:begin", {
897
- state,
898
- context
899
- });
900
- try {
901
- const previous = options.previous;
902
- const { route, params } = this.match(pathname);
903
- const query = {};
904
- if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
905
- context.query = query;
906
- context.params = params ?? {};
907
- context.previous = previous;
908
- if (isPageRoute(route)) {
909
- const result = await this.pageDescriptorProvider.createLayers(route.page, context);
910
- if (result.redirect) return {
911
- redirect: result.redirect,
912
- state,
913
- context
914
- };
915
- state.layers = result.layers;
916
- }
917
- if (state.layers.length === 0) state.layers.push({
918
- name: "not-found",
919
- element: (0, react.createElement)(NotFoundPage),
920
- index: 0,
921
- path: "/"
922
- });
923
- await this.alepha.emit("react:transition:success", {
924
- state,
925
- context
926
- });
927
- } catch (e) {
928
- this.log.error(e);
929
- state.layers = [{
930
- name: "error",
931
- element: this.pageDescriptorProvider.renderError(e),
932
- index: 0,
933
- path: "/"
934
- }];
935
- await this.alepha.emit("react:transition:error", {
936
- error: e,
937
- state,
938
- context
939
- });
940
- }
941
- if (options.state) {
942
- options.state.layers = state.layers;
943
- options.state.pathname = state.pathname;
944
- options.state.search = state.search;
945
- }
946
- await this.alepha.emit("react:transition:end", {
947
- state: options.state,
948
- context
949
- });
950
- return {
951
- context,
952
- state
953
- };
954
- }
955
- root(state, context) {
956
- return this.pageDescriptorProvider.root(state, context);
957
- }
958
- };
959
-
960
- //#endregion
961
- //#region src/providers/ReactBrowserProvider.ts
962
- var ReactBrowserProvider = class {
963
- log = (0, __alepha_core.$logger)();
964
- client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
965
- alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
966
- router = (0, __alepha_core.$inject)(BrowserRouterProvider);
967
- root;
968
- transitioning;
969
- state = {
970
- layers: [],
971
- pathname: "",
972
- search: ""
973
- };
974
- get document() {
975
- return window.document;
976
- }
977
- get history() {
978
- return window.history;
979
- }
980
- get location() {
981
- return window.location;
982
- }
983
- get url() {
984
- let url = this.location.pathname + this.location.search;
985
- return url;
986
- }
987
- pushState(url, replace) {
988
- let path = url;
989
- if (replace) this.history.replaceState({}, "", path);
990
- else this.history.pushState({}, "", path);
991
- }
992
- async invalidate(props) {
993
- const previous = [];
994
- if (props) {
995
- const [key] = Object.keys(props);
996
- const value = props[key];
997
- for (const layer of this.state.layers) {
998
- if (layer.props?.[key]) {
999
- previous.push({
1000
- ...layer,
1001
- props: {
1002
- ...layer.props,
1003
- [key]: value
1004
- }
1005
- });
1006
- break;
1007
- }
1008
- previous.push(layer);
1009
- }
1010
- }
1011
- await this.render({ previous });
1012
- }
1013
- async go(url, options = {}) {
1014
- const result = await this.render({ url });
1015
- if (result.context.url.pathname !== url) {
1016
- this.pushState(result.context.url.pathname);
1017
- return;
1018
- }
1019
- if (options.replace) {
1020
- this.pushState(url);
1021
- return;
1022
- }
1023
- this.pushState(url);
1024
- }
1025
- async render(options = {}) {
1026
- const previous = options.previous ?? this.state.layers;
1027
- const url = options.url ?? this.url;
1028
- this.transitioning = { to: url };
1029
- const result = await this.router.transition(new URL(`http://localhost${url}`), {
1030
- previous,
1031
- state: this.state
1032
- });
1033
- if (result.redirect) return await this.render({ url: result.redirect });
1034
- this.transitioning = void 0;
1035
- return result;
1036
- }
1037
- /**
1038
- * Get embedded layers from the server.
1039
- */
1040
- getHydrationState() {
1041
- try {
1042
- if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
1043
- } catch (error) {
1044
- console.error(error);
1045
- }
1046
- }
1047
- ready = (0, __alepha_core.$hook)({
1048
- on: "ready",
1049
- handler: async () => {
1050
- const hydration = this.getHydrationState();
1051
- const previous = hydration?.layers ?? [];
1052
- if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
1053
- const { context } = await this.render({ previous });
1054
- await this.alepha.emit("react:browser:render", {
1055
- state: this.state,
1056
- context,
1057
- hydration
1058
- });
1059
- window.addEventListener("popstate", () => {
1060
- if (this.state.pathname === location.pathname) return;
1061
- this.render();
1062
- });
1063
- }
1064
- });
1065
- };
1066
-
1067
1064
  //#endregion
1068
1065
  //#region src/hooks/RouterHookApi.ts
1069
1066
  var RouterHookApi = class {
@@ -1157,9 +1154,9 @@ const useRouter = () => {
1157
1154
  const layer = (0, react.useContext)(RouterLayerContext);
1158
1155
  if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1159
1156
  const pages = (0, react.useMemo)(() => {
1160
- return ctx.alepha.get(PageDescriptorProvider).getPages();
1157
+ return ctx.alepha.inject(PageDescriptorProvider).getPages();
1161
1158
  }, []);
1162
- return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
1159
+ return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
1163
1160
  };
1164
1161
 
1165
1162
  //#endregion
@@ -1167,11 +1164,11 @@ const useRouter = () => {
1167
1164
  const Link = (props) => {
1168
1165
  react.default.useContext(RouterContext);
1169
1166
  const router = useRouter();
1170
- const to = typeof props.to === "string" ? props.to : props.to[__alepha_core.OPTIONS].path;
1167
+ const to = typeof props.to === "string" ? props.to : props.to.options.path;
1171
1168
  if (!to) return null;
1172
- const can = typeof props.to === "string" ? void 0 : props.to[__alepha_core.OPTIONS].can;
1169
+ const can = typeof props.to === "string" ? void 0 : props.to.options.can;
1173
1170
  if (can && !can()) return null;
1174
- const name = typeof props.to === "string" ? void 0 : props.to[__alepha_core.OPTIONS].name;
1171
+ const name = typeof props.to === "string" ? void 0 : props.to.options.name;
1175
1172
  const anchorProps = {
1176
1173
  ...props,
1177
1174
  to: void 0
@@ -1218,12 +1215,20 @@ const useActive = (path) => {
1218
1215
  };
1219
1216
  };
1220
1217
 
1218
+ //#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;
1224
+ };
1225
+
1221
1226
  //#endregion
1222
1227
  //#region src/hooks/useInject.ts
1223
1228
  const useInject = (clazz) => {
1224
1229
  const ctx = (0, react.useContext)(RouterContext);
1225
1230
  if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
1226
- return (0, react.useMemo)(() => ctx.alepha.get(clazz), []);
1231
+ return (0, react.useMemo)(() => ctx.alepha.inject(clazz), []);
1227
1232
  };
1228
1233
 
1229
1234
  //#endregion
@@ -1285,135 +1290,19 @@ const useRouterState = () => {
1285
1290
  * It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
1286
1291
  * type safety and schema validation for route parameters and data.
1287
1292
  *
1288
- * **Key Features:**
1289
- * - Declarative page definition with `$page` descriptor
1290
- * - Server-side rendering (SSR) with automatic hydration
1291
- * - Type-safe routing with parameter validation
1292
- * - Schema-based data resolution and validation
1293
- * - SEO-friendly meta tag management
1294
- * - Automatic code splitting and lazy loading
1295
- * - Client-side navigation with browser history
1296
- *
1297
- * **Basic Usage:**
1298
- * ```ts
1299
- * import { Alepha, run, t } from "alepha";
1300
- * import { AlephaReact, $page } from "alepha/react";
1301
- *
1302
- * class AppRoutes {
1303
- * // Home page
1304
- * home = $page({
1305
- * path: "/",
1306
- * component: () => (
1307
- * <div>
1308
- * <h1>Welcome to Alepha</h1>
1309
- * <p>Build amazing React applications!</p>
1310
- * </div>
1311
- * ),
1312
- * });
1313
- *
1314
- * // About page with meta tags
1315
- * about = $page({
1316
- * path: "/about",
1317
- * head: {
1318
- * title: "About Us",
1319
- * description: "Learn more about our mission",
1320
- * },
1321
- * component: () => (
1322
- * <div>
1323
- * <h1>About Us</h1>
1324
- * <p>Learn more about our mission.</p>
1325
- * </div>
1326
- * ),
1327
- * });
1328
- * }
1329
- *
1330
- * const alepha = Alepha.create()
1331
- * .with(AlephaReact)
1332
- * .with(AppRoutes);
1333
- *
1334
- * run(alepha);
1335
- * ```
1336
- *
1337
- * **Dynamic Routes with Parameters:**
1338
- * ```tsx
1339
- * class UserRoutes {
1340
- * userProfile = $page({
1341
- * path: "/users/:id",
1342
- * schema: {
1343
- * params: t.object({
1344
- * id: t.string(),
1345
- * }),
1346
- * },
1347
- * resolve: async ({ params }) => {
1348
- * // Fetch user data server-side
1349
- * const user = await getUserById(params.id);
1350
- * return { user };
1351
- * },
1352
- * head: ({ user }) => ({
1353
- * title: `${user.name} - Profile`,
1354
- * description: `View ${user.name}'s profile`,
1355
- * }),
1356
- * component: ({ user }) => (
1357
- * <div>
1358
- * <h1>{user.name}</h1>
1359
- * <p>Email: {user.email}</p>
1360
- * </div>
1361
- * ),
1362
- * });
1363
- *
1364
- * userSettings = $page({
1365
- * path: "/users/:id/settings",
1366
- * schema: {
1367
- * params: t.object({
1368
- * id: t.string(),
1369
- * }),
1370
- * },
1371
- * component: ({ params }) => (
1372
- * <UserSettings userId={params.id} />
1373
- * ),
1374
- * });
1375
- * }
1376
- * ```
1377
- *
1378
- * **Static Generation:**
1379
- * ```tsx
1380
- * class BlogRoutes {
1381
- * blogPost = $page({
1382
- * path: "/blog/:slug",
1383
- * schema: {
1384
- * params: t.object({
1385
- * slug: t.string(),
1386
- * }),
1387
- * },
1388
- * static: {
1389
- * entries: [
1390
- * { params: { slug: "getting-started" } },
1391
- * { params: { slug: "advanced-features" } },
1392
- * { params: { slug: "deployment" } },
1393
- * ],
1394
- * },
1395
- * resolve: ({ params }) => {
1396
- * const post = getBlogPost(params.slug);
1397
- * return { post };
1398
- * },
1399
- * component: ({ post }) => (
1400
- * <article>
1401
- * <h1>{post.title}</h1>
1402
- * <div>{post.content}</div>
1403
- * </article>
1404
- * ),
1405
- * });
1406
- * }
1407
- * ```
1408
- *
1409
1293
  * @see {@link $page}
1410
1294
  * @module alepha.react
1411
1295
  */
1412
- var AlephaReact = class {
1413
- name = "alepha.react";
1414
- $services = (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(__alepha_server_links.AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider);
1415
- };
1416
- (0, __alepha_core.__bind)($page, AlephaReact);
1296
+ const AlephaReact = (0, __alepha_core.$module)({
1297
+ name: "alepha.react",
1298
+ descriptors: [$page],
1299
+ services: [
1300
+ ReactServerProvider,
1301
+ PageDescriptorProvider,
1302
+ ReactBrowserProvider
1303
+ ],
1304
+ register: (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(__alepha_server_links.AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider)
1305
+ });
1417
1306
 
1418
1307
  //#endregion
1419
1308
  exports.$page = $page;
@@ -1423,6 +1312,7 @@ exports.ErrorBoundary = ErrorBoundary_default;
1423
1312
  exports.Link = Link_default;
1424
1313
  exports.NestedView = NestedView_default;
1425
1314
  exports.NotFound = NotFoundPage;
1315
+ exports.PageDescriptor = PageDescriptor;
1426
1316
  exports.PageDescriptorProvider = PageDescriptorProvider;
1427
1317
  exports.ReactBrowserProvider = ReactBrowserProvider;
1428
1318
  exports.ReactServerProvider = ReactServerProvider;