@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.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
@@ -47,23 +51,11 @@ const ClientOnly = (props) => {
47
51
  };
48
52
  var ClientOnly_default = ClientOnly;
49
53
 
50
- //#endregion
51
- //#region src/contexts/RouterContext.ts
52
- const RouterContext = createContext(void 0);
53
-
54
- //#endregion
55
- //#region src/hooks/useAlepha.ts
56
- const useAlepha = () => {
57
- const routerContext = useContext(RouterContext);
58
- if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
59
- return routerContext.alepha;
60
- };
61
-
62
54
  //#endregion
63
55
  //#region src/components/ErrorViewer.tsx
64
- const ErrorViewer = ({ error }) => {
56
+ const ErrorViewer = ({ error, alepha }) => {
65
57
  const [expanded, setExpanded] = useState(false);
66
- const isProduction = useAlepha().isProduction();
58
+ const isProduction = alepha.isProduction();
67
59
  if (isProduction) return /* @__PURE__ */ jsx(ErrorViewerProduction, {});
68
60
  const stackLines = error.stack?.split("\n") ?? [];
69
61
  const previewLines = stackLines.slice(0, 5);
@@ -207,6 +199,10 @@ const ErrorViewerProduction = () => {
207
199
  });
208
200
  };
209
201
 
202
+ //#endregion
203
+ //#region src/contexts/RouterContext.ts
204
+ const RouterContext = createContext(void 0);
205
+
210
206
  //#endregion
211
207
  //#region src/contexts/RouterLayerContext.ts
212
208
  const RouterLayerContext = createContext(void 0);
@@ -341,7 +337,7 @@ var RedirectionError = class extends Error {
341
337
  const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
342
338
  var PageDescriptorProvider = class {
343
339
  log = $logger();
344
- env = $inject(envSchema$1);
340
+ env = $env(envSchema$1);
345
341
  alepha = $inject(Alepha);
346
342
  pages = [];
347
343
  getPages() {
@@ -514,7 +510,10 @@ var PageDescriptorProvider = class {
514
510
  return void 0;
515
511
  }
516
512
  renderError(error) {
517
- return createElement(ErrorViewer_default, { error });
513
+ return createElement(ErrorViewer_default, {
514
+ error,
515
+ alepha: this.alepha
516
+ });
518
517
  }
519
518
  renderEmptyView() {
520
519
  return createElement(NestedView_default, {});
@@ -547,18 +546,17 @@ var PageDescriptorProvider = class {
547
546
  on: "configure",
548
547
  handler: () => {
549
548
  let hasNotFoundHandler = false;
550
- const pages = this.alepha.getDescriptorValues($page);
549
+ const pages = this.alepha.descriptors($page);
551
550
  const hasParent = (it) => {
552
551
  for (const page of pages) {
553
- const children = page.value[OPTIONS].children ? Array.isArray(page.value[OPTIONS].children) ? page.value[OPTIONS].children : page.value[OPTIONS].children() : [];
552
+ const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
554
553
  if (children.includes(it)) return true;
555
554
  }
556
555
  };
557
- for (const { value, key } of pages) value[OPTIONS].name ??= key;
558
- for (const { value } of pages) {
559
- if (value[OPTIONS].path === "/*") hasNotFoundHandler = true;
560
- if (hasParent(value)) continue;
561
- this.add(this.map(pages, value));
556
+ for (const page of pages) {
557
+ if (page.options.path === "/*") hasNotFoundHandler = true;
558
+ if (hasParent(page)) continue;
559
+ this.add(this.map(pages, page));
562
560
  }
563
561
  if (!hasNotFoundHandler && pages.length > 0) this.add({
564
562
  path: "/*",
@@ -572,9 +570,10 @@ var PageDescriptorProvider = class {
572
570
  }
573
571
  });
574
572
  map(pages, target) {
575
- const children = target[OPTIONS].children ? Array.isArray(target[OPTIONS].children) ? target[OPTIONS].children : target[OPTIONS].children() : [];
573
+ const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
576
574
  return {
577
- ...target[OPTIONS],
575
+ ...target.options,
576
+ name: target.name,
578
577
  parent: void 0,
579
578
  children: children.map((it) => this.map(pages, it))
580
579
  };
@@ -611,13 +610,222 @@ const isPageRoute = (it) => {
611
610
  return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
612
611
  };
613
612
 
613
+ //#endregion
614
+ //#region src/providers/BrowserRouterProvider.ts
615
+ var BrowserRouterProvider = class extends RouterProvider {
616
+ log = $logger();
617
+ alepha = $inject(Alepha);
618
+ pageDescriptorProvider = $inject(PageDescriptorProvider);
619
+ add(entry) {
620
+ this.pageDescriptorProvider.add(entry);
621
+ }
622
+ configure = $hook({
623
+ on: "configure",
624
+ handler: async () => {
625
+ for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
626
+ path: page.match,
627
+ page
628
+ });
629
+ }
630
+ });
631
+ async transition(url, options = {}) {
632
+ const { pathname, search } = url;
633
+ const state = {
634
+ pathname,
635
+ search,
636
+ layers: []
637
+ };
638
+ const context = {
639
+ url,
640
+ query: {},
641
+ params: {},
642
+ onError: () => null,
643
+ ...options.context ?? {}
644
+ };
645
+ await this.alepha.emit("react:transition:begin", {
646
+ state,
647
+ context
648
+ });
649
+ try {
650
+ const previous = options.previous;
651
+ const { route, params } = this.match(pathname);
652
+ const query = {};
653
+ if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
654
+ context.query = query;
655
+ context.params = params ?? {};
656
+ context.previous = previous;
657
+ if (isPageRoute(route)) {
658
+ const result = await this.pageDescriptorProvider.createLayers(route.page, context);
659
+ if (result.redirect) return {
660
+ redirect: result.redirect,
661
+ state,
662
+ context
663
+ };
664
+ state.layers = result.layers;
665
+ }
666
+ if (state.layers.length === 0) state.layers.push({
667
+ name: "not-found",
668
+ element: createElement(NotFoundPage),
669
+ index: 0,
670
+ path: "/"
671
+ });
672
+ await this.alepha.emit("react:transition:success", {
673
+ state,
674
+ context
675
+ });
676
+ } catch (e) {
677
+ this.log.error(e);
678
+ state.layers = [{
679
+ name: "error",
680
+ element: this.pageDescriptorProvider.renderError(e),
681
+ index: 0,
682
+ path: "/"
683
+ }];
684
+ await this.alepha.emit("react:transition:error", {
685
+ error: e,
686
+ state,
687
+ context
688
+ });
689
+ }
690
+ if (options.state) {
691
+ options.state.layers = state.layers;
692
+ options.state.pathname = state.pathname;
693
+ options.state.search = state.search;
694
+ }
695
+ await this.alepha.emit("react:transition:end", {
696
+ state: options.state,
697
+ context
698
+ });
699
+ return {
700
+ context,
701
+ state
702
+ };
703
+ }
704
+ root(state, context) {
705
+ return this.pageDescriptorProvider.root(state, context);
706
+ }
707
+ };
708
+
709
+ //#endregion
710
+ //#region src/providers/ReactBrowserProvider.ts
711
+ var ReactBrowserProvider = class {
712
+ log = $logger();
713
+ client = $inject(LinkProvider);
714
+ alepha = $inject(Alepha);
715
+ router = $inject(BrowserRouterProvider);
716
+ root;
717
+ transitioning;
718
+ state = {
719
+ layers: [],
720
+ pathname: "",
721
+ search: ""
722
+ };
723
+ get document() {
724
+ return window.document;
725
+ }
726
+ get history() {
727
+ return window.history;
728
+ }
729
+ get location() {
730
+ return window.location;
731
+ }
732
+ get url() {
733
+ let url = this.location.pathname + this.location.search;
734
+ if (import.meta?.env?.BASE_URL) {
735
+ url = url.replace(import.meta.env?.BASE_URL, "");
736
+ if (!url.startsWith("/")) url = `/${url}`;
737
+ }
738
+ return url;
739
+ }
740
+ pushState(url, replace) {
741
+ let path = url;
742
+ if (import.meta?.env?.BASE_URL) path = (import.meta.env?.BASE_URL + path).replaceAll("//", "/");
743
+ if (replace) this.history.replaceState({}, "", path);
744
+ else this.history.pushState({}, "", path);
745
+ }
746
+ async invalidate(props) {
747
+ const previous = [];
748
+ if (props) {
749
+ const [key] = Object.keys(props);
750
+ const value = props[key];
751
+ for (const layer of this.state.layers) {
752
+ if (layer.props?.[key]) {
753
+ previous.push({
754
+ ...layer,
755
+ props: {
756
+ ...layer.props,
757
+ [key]: value
758
+ }
759
+ });
760
+ break;
761
+ }
762
+ previous.push(layer);
763
+ }
764
+ }
765
+ await this.render({ previous });
766
+ }
767
+ async go(url, options = {}) {
768
+ const result = await this.render({ url });
769
+ if (result.context.url.pathname !== url) {
770
+ this.pushState(result.context.url.pathname);
771
+ return;
772
+ }
773
+ if (options.replace) {
774
+ this.pushState(url);
775
+ return;
776
+ }
777
+ this.pushState(url);
778
+ }
779
+ async render(options = {}) {
780
+ const previous = options.previous ?? this.state.layers;
781
+ const url = options.url ?? this.url;
782
+ this.transitioning = { to: url };
783
+ const result = await this.router.transition(new URL(`http://localhost${url}`), {
784
+ previous,
785
+ state: this.state
786
+ });
787
+ if (result.redirect) return await this.render({ url: result.redirect });
788
+ this.transitioning = void 0;
789
+ return result;
790
+ }
791
+ /**
792
+ * Get embedded layers from the server.
793
+ */
794
+ getHydrationState() {
795
+ try {
796
+ if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
797
+ } catch (error) {
798
+ console.error(error);
799
+ }
800
+ }
801
+ ready = $hook({
802
+ on: "ready",
803
+ handler: async () => {
804
+ const hydration = this.getHydrationState();
805
+ const previous = hydration?.layers ?? [];
806
+ if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
807
+ const { context } = await this.render({ previous });
808
+ await this.alepha.emit("react:browser:render", {
809
+ state: this.state,
810
+ context,
811
+ hydration
812
+ });
813
+ window.addEventListener("popstate", () => {
814
+ if (this.state.pathname === this.url) return;
815
+ this.render();
816
+ });
817
+ }
818
+ });
819
+ };
820
+
614
821
  //#endregion
615
822
  //#region src/providers/ReactServerProvider.ts
616
823
  const envSchema = t.object({
617
824
  REACT_SERVER_DIST: t.string({ default: "public" }),
618
825
  REACT_SERVER_PREFIX: t.string({ default: "" }),
619
826
  REACT_SSR_ENABLED: t.optional(t.boolean()),
620
- REACT_ROOT_ID: t.string({ default: "root" })
827
+ REACT_ROOT_ID: t.string({ default: "root" }),
828
+ REACT_SERVER_TEMPLATE: t.optional(t.string({ size: "rich" }))
621
829
  });
622
830
  var ReactServerProvider = class {
623
831
  log = $logger();
@@ -626,18 +834,15 @@ var ReactServerProvider = class {
626
834
  serverStaticProvider = $inject(ServerStaticProvider);
627
835
  serverRouterProvider = $inject(ServerRouterProvider);
628
836
  serverTimingProvider = $inject(ServerTimingProvider);
629
- env = $inject(envSchema);
837
+ env = $env(envSchema);
630
838
  ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
631
839
  onConfigure = $hook({
632
840
  on: "configure",
633
841
  handler: async () => {
634
- const pages = this.alepha.getDescriptorValues($page);
842
+ const pages = this.alepha.descriptors($page);
635
843
  const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
636
844
  this.alepha.state("react.server.ssr", ssrEnabled);
637
- for (const { key, instance, value } of pages) {
638
- const name = value[OPTIONS].name ?? key;
639
- instance[key].render = this.createRenderFunction(name);
640
- }
845
+ for (const page of pages) page.render = this.createRenderFunction(page.name);
641
846
  if (this.alepha.isServerless() === "vite") {
642
847
  await this.configureVite(ssrEnabled);
643
848
  return;
@@ -657,7 +862,7 @@ var ReactServerProvider = class {
657
862
  return;
658
863
  }
659
864
  this.log.info("SSR is disabled, use History API fallback");
660
- await this.serverRouterProvider.route({
865
+ this.serverRouterProvider.createRoute({
661
866
  path: "*",
662
867
  handler: async ({ url, reply }) => {
663
868
  if (url.pathname.includes(".")) {
@@ -673,13 +878,13 @@ var ReactServerProvider = class {
673
878
  }
674
879
  });
675
880
  get template() {
676
- return this.alepha.state("react.server.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
881
+ return this.alepha.env.REACT_SERVER_TEMPLATE ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
677
882
  }
678
883
  async registerPages(templateLoader) {
679
884
  for (const page of this.pageDescriptorProvider.getPages()) {
680
885
  if (page.children?.length) continue;
681
886
  this.log.debug(`+ ${page.match} -> ${page.name}`);
682
- await this.serverRouterProvider.route({
887
+ this.serverRouterProvider.createRoute({
683
888
  ...page,
684
889
  schema: void 0,
685
890
  method: "GET",
@@ -694,7 +899,7 @@ var ReactServerProvider = class {
694
899
  return "";
695
900
  }
696
901
  async configureStaticServer(root) {
697
- await this.serverStaticProvider.serve({
902
+ await this.serverStaticProvider.createStaticServer({
698
903
  root,
699
904
  path: this.env.REACT_SERVER_PREFIX
700
905
  });
@@ -748,7 +953,7 @@ var ReactServerProvider = class {
748
953
  onError: () => null
749
954
  };
750
955
  if (this.alepha.has(ServerLinksProvider)) {
751
- const srv = this.alepha.get(ServerLinksProvider);
956
+ const srv = this.alepha.inject(ServerLinksProvider);
752
957
  const schema = apiLinksResponseSchema;
753
958
  context.links = this.alepha.parse(schema, await srv.getLinks({
754
959
  user: serverRequest.user,
@@ -838,214 +1043,6 @@ var ReactServerProvider = class {
838
1043
  }
839
1044
  };
840
1045
 
841
- //#endregion
842
- //#region src/providers/BrowserRouterProvider.ts
843
- var BrowserRouterProvider = class extends RouterProvider {
844
- log = $logger();
845
- alepha = $inject(Alepha);
846
- pageDescriptorProvider = $inject(PageDescriptorProvider);
847
- add(entry) {
848
- this.pageDescriptorProvider.add(entry);
849
- }
850
- configure = $hook({
851
- on: "configure",
852
- handler: async () => {
853
- for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
854
- path: page.match,
855
- page
856
- });
857
- }
858
- });
859
- async transition(url, options = {}) {
860
- const { pathname, search } = url;
861
- const state = {
862
- pathname,
863
- search,
864
- layers: []
865
- };
866
- const context = {
867
- url,
868
- query: {},
869
- params: {},
870
- onError: () => null,
871
- ...options.context ?? {}
872
- };
873
- await this.alepha.emit("react:transition:begin", {
874
- state,
875
- context
876
- });
877
- try {
878
- const previous = options.previous;
879
- const { route, params } = this.match(pathname);
880
- const query = {};
881
- if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
882
- context.query = query;
883
- context.params = params ?? {};
884
- context.previous = previous;
885
- if (isPageRoute(route)) {
886
- const result = await this.pageDescriptorProvider.createLayers(route.page, context);
887
- if (result.redirect) return {
888
- redirect: result.redirect,
889
- state,
890
- context
891
- };
892
- state.layers = result.layers;
893
- }
894
- if (state.layers.length === 0) state.layers.push({
895
- name: "not-found",
896
- element: createElement(NotFoundPage),
897
- index: 0,
898
- path: "/"
899
- });
900
- await this.alepha.emit("react:transition:success", {
901
- state,
902
- context
903
- });
904
- } catch (e) {
905
- this.log.error(e);
906
- state.layers = [{
907
- name: "error",
908
- element: this.pageDescriptorProvider.renderError(e),
909
- index: 0,
910
- path: "/"
911
- }];
912
- await this.alepha.emit("react:transition:error", {
913
- error: e,
914
- state,
915
- context
916
- });
917
- }
918
- if (options.state) {
919
- options.state.layers = state.layers;
920
- options.state.pathname = state.pathname;
921
- options.state.search = state.search;
922
- }
923
- await this.alepha.emit("react:transition:end", {
924
- state: options.state,
925
- context
926
- });
927
- return {
928
- context,
929
- state
930
- };
931
- }
932
- root(state, context) {
933
- return this.pageDescriptorProvider.root(state, context);
934
- }
935
- };
936
-
937
- //#endregion
938
- //#region src/providers/ReactBrowserProvider.ts
939
- var ReactBrowserProvider = class {
940
- log = $logger();
941
- client = $inject(LinkProvider);
942
- alepha = $inject(Alepha);
943
- router = $inject(BrowserRouterProvider);
944
- root;
945
- transitioning;
946
- state = {
947
- layers: [],
948
- pathname: "",
949
- search: ""
950
- };
951
- get document() {
952
- return window.document;
953
- }
954
- get history() {
955
- return window.history;
956
- }
957
- get location() {
958
- return window.location;
959
- }
960
- get url() {
961
- let url = this.location.pathname + this.location.search;
962
- if (import.meta?.env?.BASE_URL) {
963
- url = url.replace(import.meta.env?.BASE_URL, "");
964
- if (!url.startsWith("/")) url = `/${url}`;
965
- }
966
- return url;
967
- }
968
- pushState(url, replace) {
969
- let path = url;
970
- if (import.meta?.env?.BASE_URL) path = (import.meta.env?.BASE_URL + path).replaceAll("//", "/");
971
- if (replace) this.history.replaceState({}, "", path);
972
- else this.history.pushState({}, "", path);
973
- }
974
- async invalidate(props) {
975
- const previous = [];
976
- if (props) {
977
- const [key] = Object.keys(props);
978
- const value = props[key];
979
- for (const layer of this.state.layers) {
980
- if (layer.props?.[key]) {
981
- previous.push({
982
- ...layer,
983
- props: {
984
- ...layer.props,
985
- [key]: value
986
- }
987
- });
988
- break;
989
- }
990
- previous.push(layer);
991
- }
992
- }
993
- await this.render({ previous });
994
- }
995
- async go(url, options = {}) {
996
- const result = await this.render({ url });
997
- if (result.context.url.pathname !== url) {
998
- this.pushState(result.context.url.pathname);
999
- return;
1000
- }
1001
- if (options.replace) {
1002
- this.pushState(url);
1003
- return;
1004
- }
1005
- this.pushState(url);
1006
- }
1007
- async render(options = {}) {
1008
- const previous = options.previous ?? this.state.layers;
1009
- const url = options.url ?? this.url;
1010
- this.transitioning = { to: url };
1011
- const result = await this.router.transition(new URL(`http://localhost${url}`), {
1012
- previous,
1013
- state: this.state
1014
- });
1015
- if (result.redirect) return await this.render({ url: result.redirect });
1016
- this.transitioning = void 0;
1017
- return result;
1018
- }
1019
- /**
1020
- * Get embedded layers from the server.
1021
- */
1022
- getHydrationState() {
1023
- try {
1024
- if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
1025
- } catch (error) {
1026
- console.error(error);
1027
- }
1028
- }
1029
- ready = $hook({
1030
- on: "ready",
1031
- handler: async () => {
1032
- const hydration = this.getHydrationState();
1033
- const previous = hydration?.layers ?? [];
1034
- if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
1035
- const { context } = await this.render({ previous });
1036
- await this.alepha.emit("react:browser:render", {
1037
- state: this.state,
1038
- context,
1039
- hydration
1040
- });
1041
- window.addEventListener("popstate", () => {
1042
- if (this.state.pathname === location.pathname) return;
1043
- this.render();
1044
- });
1045
- }
1046
- });
1047
- };
1048
-
1049
1046
  //#endregion
1050
1047
  //#region src/hooks/RouterHookApi.ts
1051
1048
  var RouterHookApi = class {
@@ -1139,9 +1136,9 @@ const useRouter = () => {
1139
1136
  const layer = useContext(RouterLayerContext);
1140
1137
  if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1141
1138
  const pages = useMemo(() => {
1142
- return ctx.alepha.get(PageDescriptorProvider).getPages();
1139
+ return ctx.alepha.inject(PageDescriptorProvider).getPages();
1143
1140
  }, []);
1144
- return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
1141
+ return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
1145
1142
  };
1146
1143
 
1147
1144
  //#endregion
@@ -1149,11 +1146,11 @@ const useRouter = () => {
1149
1146
  const Link = (props) => {
1150
1147
  React.useContext(RouterContext);
1151
1148
  const router = useRouter();
1152
- const to = typeof props.to === "string" ? props.to : props.to[OPTIONS].path;
1149
+ const to = typeof props.to === "string" ? props.to : props.to.options.path;
1153
1150
  if (!to) return null;
1154
- const can = typeof props.to === "string" ? void 0 : props.to[OPTIONS].can;
1151
+ const can = typeof props.to === "string" ? void 0 : props.to.options.can;
1155
1152
  if (can && !can()) return null;
1156
- const name = typeof props.to === "string" ? void 0 : props.to[OPTIONS].name;
1153
+ const name = typeof props.to === "string" ? void 0 : props.to.options.name;
1157
1154
  const anchorProps = {
1158
1155
  ...props,
1159
1156
  to: void 0
@@ -1200,12 +1197,20 @@ const useActive = (path) => {
1200
1197
  };
1201
1198
  };
1202
1199
 
1200
+ //#endregion
1201
+ //#region src/hooks/useAlepha.ts
1202
+ const useAlepha = () => {
1203
+ const routerContext = useContext(RouterContext);
1204
+ if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
1205
+ return routerContext.alepha;
1206
+ };
1207
+
1203
1208
  //#endregion
1204
1209
  //#region src/hooks/useInject.ts
1205
1210
  const useInject = (clazz) => {
1206
1211
  const ctx = useContext(RouterContext);
1207
1212
  if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
1208
- return useMemo(() => ctx.alepha.get(clazz), []);
1213
+ return useMemo(() => ctx.alepha.inject(clazz), []);
1209
1214
  };
1210
1215
 
1211
1216
  //#endregion
@@ -1267,136 +1272,20 @@ const useRouterState = () => {
1267
1272
  * It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
1268
1273
  * type safety and schema validation for route parameters and data.
1269
1274
  *
1270
- * **Key Features:**
1271
- * - Declarative page definition with `$page` descriptor
1272
- * - Server-side rendering (SSR) with automatic hydration
1273
- * - Type-safe routing with parameter validation
1274
- * - Schema-based data resolution and validation
1275
- * - SEO-friendly meta tag management
1276
- * - Automatic code splitting and lazy loading
1277
- * - Client-side navigation with browser history
1278
- *
1279
- * **Basic Usage:**
1280
- * ```ts
1281
- * import { Alepha, run, t } from "alepha";
1282
- * import { AlephaReact, $page } from "alepha/react";
1283
- *
1284
- * class AppRoutes {
1285
- * // Home page
1286
- * home = $page({
1287
- * path: "/",
1288
- * component: () => (
1289
- * <div>
1290
- * <h1>Welcome to Alepha</h1>
1291
- * <p>Build amazing React applications!</p>
1292
- * </div>
1293
- * ),
1294
- * });
1295
- *
1296
- * // About page with meta tags
1297
- * about = $page({
1298
- * path: "/about",
1299
- * head: {
1300
- * title: "About Us",
1301
- * description: "Learn more about our mission",
1302
- * },
1303
- * component: () => (
1304
- * <div>
1305
- * <h1>About Us</h1>
1306
- * <p>Learn more about our mission.</p>
1307
- * </div>
1308
- * ),
1309
- * });
1310
- * }
1311
- *
1312
- * const alepha = Alepha.create()
1313
- * .with(AlephaReact)
1314
- * .with(AppRoutes);
1315
- *
1316
- * run(alepha);
1317
- * ```
1318
- *
1319
- * **Dynamic Routes with Parameters:**
1320
- * ```tsx
1321
- * class UserRoutes {
1322
- * userProfile = $page({
1323
- * path: "/users/:id",
1324
- * schema: {
1325
- * params: t.object({
1326
- * id: t.string(),
1327
- * }),
1328
- * },
1329
- * resolve: async ({ params }) => {
1330
- * // Fetch user data server-side
1331
- * const user = await getUserById(params.id);
1332
- * return { user };
1333
- * },
1334
- * head: ({ user }) => ({
1335
- * title: `${user.name} - Profile`,
1336
- * description: `View ${user.name}'s profile`,
1337
- * }),
1338
- * component: ({ user }) => (
1339
- * <div>
1340
- * <h1>{user.name}</h1>
1341
- * <p>Email: {user.email}</p>
1342
- * </div>
1343
- * ),
1344
- * });
1345
- *
1346
- * userSettings = $page({
1347
- * path: "/users/:id/settings",
1348
- * schema: {
1349
- * params: t.object({
1350
- * id: t.string(),
1351
- * }),
1352
- * },
1353
- * component: ({ params }) => (
1354
- * <UserSettings userId={params.id} />
1355
- * ),
1356
- * });
1357
- * }
1358
- * ```
1359
- *
1360
- * **Static Generation:**
1361
- * ```tsx
1362
- * class BlogRoutes {
1363
- * blogPost = $page({
1364
- * path: "/blog/:slug",
1365
- * schema: {
1366
- * params: t.object({
1367
- * slug: t.string(),
1368
- * }),
1369
- * },
1370
- * static: {
1371
- * entries: [
1372
- * { params: { slug: "getting-started" } },
1373
- * { params: { slug: "advanced-features" } },
1374
- * { params: { slug: "deployment" } },
1375
- * ],
1376
- * },
1377
- * resolve: ({ params }) => {
1378
- * const post = getBlogPost(params.slug);
1379
- * return { post };
1380
- * },
1381
- * component: ({ post }) => (
1382
- * <article>
1383
- * <h1>{post.title}</h1>
1384
- * <div>{post.content}</div>
1385
- * </article>
1386
- * ),
1387
- * });
1388
- * }
1389
- * ```
1390
- *
1391
1275
  * @see {@link $page}
1392
1276
  * @module alepha.react
1393
1277
  */
1394
- var AlephaReact = class {
1395
- name = "alepha.react";
1396
- $services = (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider);
1397
- };
1398
- __bind($page, AlephaReact);
1278
+ const AlephaReact = $module({
1279
+ name: "alepha.react",
1280
+ descriptors: [$page],
1281
+ services: [
1282
+ ReactServerProvider,
1283
+ PageDescriptorProvider,
1284
+ ReactBrowserProvider
1285
+ ],
1286
+ register: (alepha) => alepha.with(AlephaServer).with(AlephaServerCache).with(AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider)
1287
+ });
1399
1288
 
1400
1289
  //#endregion
1401
- export { $page, AlephaReact, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptorProvider, ReactBrowserProvider, ReactServerProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
1290
+ 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 };
1402
1291
  //# sourceMappingURL=index.js.map