@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.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
@@ -314,7 +318,7 @@ const NestedView = (props) => {
314
318
  const index = layer?.index ?? 0;
315
319
  const [view, setView] = (0, react.useState)(app?.state.layers[index]?.element);
316
320
  useRouterEvents({ onEnd: ({ state }) => {
317
- setView(state.layers[index]?.element);
321
+ if (!state.layers[index]?.cache) setView(state.layers[index]?.element);
318
322
  } }, [app]);
319
323
  if (!app) throw new Error("NestedView must be used within a RouterContext.");
320
324
  const element = view ?? props.children ?? null;
@@ -364,7 +368,7 @@ var RedirectionError = class extends Error {
364
368
  const envSchema$1 = __alepha_core.t.object({ REACT_STRICT_MODE: __alepha_core.t.boolean({ default: true }) });
365
369
  var PageDescriptorProvider = class {
366
370
  log = (0, __alepha_core.$logger)();
367
- env = (0, __alepha_core.$inject)(envSchema$1);
371
+ env = (0, __alepha_core.$env)(envSchema$1);
368
372
  alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
369
373
  pages = [];
370
374
  getPages() {
@@ -412,19 +416,18 @@ var PageDescriptorProvider = class {
412
416
  const route$1 = it.route;
413
417
  const config = {};
414
418
  try {
415
- config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) : request.query;
419
+ config.query = route$1.schema?.query ? this.alepha.parse(route$1.schema.query, request.query) : {};
416
420
  } catch (e) {
417
421
  it.error = e;
418
422
  break;
419
423
  }
420
424
  try {
421
- config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) : request.params;
425
+ config.params = route$1.schema?.params ? this.alepha.parse(route$1.schema.params, request.params) : {};
422
426
  } catch (e) {
423
427
  it.error = e;
424
428
  break;
425
429
  }
426
430
  it.config = { ...config };
427
- if (!route$1.resolve) continue;
428
431
  const previous = request.previous;
429
432
  if (previous?.[i] && !forceRefresh && previous[i].name === route$1.name) {
430
433
  const url = (str) => str ? str.replace(/\/\/+/g, "/") : "/";
@@ -439,6 +442,7 @@ var PageDescriptorProvider = class {
439
442
  if (prev === curr) {
440
443
  it.props = previous[i].props;
441
444
  it.error = previous[i].error;
445
+ it.cache = true;
442
446
  context = {
443
447
  ...context,
444
448
  ...it.props
@@ -447,6 +451,7 @@ var PageDescriptorProvider = class {
447
451
  }
448
452
  forceRefresh = true;
449
453
  }
454
+ if (!route$1.resolve) continue;
450
455
  try {
451
456
  const props = await route$1.resolve?.({
452
457
  ...request,
@@ -493,7 +498,7 @@ var PageDescriptorProvider = class {
493
498
  element: this.renderView(i + 1, path, element$1, it.route),
494
499
  index: i + 1,
495
500
  path,
496
- route
501
+ route: it.route
497
502
  });
498
503
  break;
499
504
  }
@@ -509,7 +514,8 @@ var PageDescriptorProvider = class {
509
514
  element: this.renderView(i + 1, path, element, it.route),
510
515
  index: i + 1,
511
516
  path,
512
- route
517
+ route: it.route,
518
+ cache: it.cache
513
519
  });
514
520
  }
515
521
  return {
@@ -568,18 +574,17 @@ var PageDescriptorProvider = class {
568
574
  on: "configure",
569
575
  handler: () => {
570
576
  let hasNotFoundHandler = false;
571
- const pages = this.alepha.getDescriptorValues($page);
577
+ const pages = this.alepha.descriptors($page);
572
578
  const hasParent = (it) => {
573
579
  for (const page of pages) {
574
- 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() : [];
580
+ const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
575
581
  if (children.includes(it)) return true;
576
582
  }
577
583
  };
578
- for (const { value, key } of pages) value[__alepha_core.OPTIONS].name ??= key;
579
- for (const { value } of pages) {
580
- if (hasParent(value)) continue;
581
- if (value[__alepha_core.OPTIONS].path === "/*") hasNotFoundHandler = true;
582
- this.add(this.map(pages, value));
584
+ for (const page of pages) {
585
+ if (page.options.path === "/*") hasNotFoundHandler = true;
586
+ if (hasParent(page)) continue;
587
+ this.add(this.map(pages, page));
583
588
  }
584
589
  if (!hasNotFoundHandler && pages.length > 0) this.add({
585
590
  path: "/*",
@@ -593,9 +598,10 @@ var PageDescriptorProvider = class {
593
598
  }
594
599
  });
595
600
  map(pages, target) {
596
- const children = target[__alepha_core.OPTIONS].children ? Array.isArray(target[__alepha_core.OPTIONS].children) ? target[__alepha_core.OPTIONS].children : target[__alepha_core.OPTIONS].children() : [];
601
+ const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
597
602
  return {
598
- ...target[__alepha_core.OPTIONS],
603
+ ...target.options,
604
+ name: target.name,
599
605
  parent: void 0,
600
606
  children: children.map((it) => this.map(pages, it))
601
607
  };
@@ -632,6 +638,209 @@ const isPageRoute = (it) => {
632
638
  return it && typeof it === "object" && typeof it.path === "string" && typeof it.page === "object";
633
639
  };
634
640
 
641
+ //#endregion
642
+ //#region src/providers/BrowserRouterProvider.ts
643
+ var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
644
+ log = (0, __alepha_core.$logger)();
645
+ alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
646
+ pageDescriptorProvider = (0, __alepha_core.$inject)(PageDescriptorProvider);
647
+ add(entry) {
648
+ this.pageDescriptorProvider.add(entry);
649
+ }
650
+ configure = (0, __alepha_core.$hook)({
651
+ on: "configure",
652
+ handler: async () => {
653
+ for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
654
+ path: page.match,
655
+ page
656
+ });
657
+ }
658
+ });
659
+ async transition(url, options = {}) {
660
+ const { pathname, search } = url;
661
+ const state = {
662
+ pathname,
663
+ search,
664
+ layers: []
665
+ };
666
+ const context = {
667
+ url,
668
+ query: {},
669
+ params: {},
670
+ onError: () => null,
671
+ ...options.context ?? {}
672
+ };
673
+ await this.alepha.emit("react:transition:begin", {
674
+ state,
675
+ context
676
+ });
677
+ try {
678
+ const previous = options.previous;
679
+ const { route, params } = this.match(pathname);
680
+ const query = {};
681
+ if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
682
+ context.query = query;
683
+ context.params = params ?? {};
684
+ context.previous = previous;
685
+ if (isPageRoute(route)) {
686
+ const result = await this.pageDescriptorProvider.createLayers(route.page, context);
687
+ if (result.redirect) return {
688
+ redirect: result.redirect,
689
+ state,
690
+ context
691
+ };
692
+ state.layers = result.layers;
693
+ }
694
+ if (state.layers.length === 0) state.layers.push({
695
+ name: "not-found",
696
+ element: (0, react.createElement)(NotFoundPage),
697
+ index: 0,
698
+ path: "/"
699
+ });
700
+ await this.alepha.emit("react:transition:success", {
701
+ state,
702
+ context
703
+ });
704
+ } catch (e) {
705
+ this.log.error(e);
706
+ state.layers = [{
707
+ name: "error",
708
+ element: this.pageDescriptorProvider.renderError(e),
709
+ index: 0,
710
+ path: "/"
711
+ }];
712
+ await this.alepha.emit("react:transition:error", {
713
+ error: e,
714
+ state,
715
+ context
716
+ });
717
+ }
718
+ if (options.state) {
719
+ options.state.layers = state.layers;
720
+ options.state.pathname = state.pathname;
721
+ options.state.search = state.search;
722
+ }
723
+ await this.alepha.emit("react:transition:end", {
724
+ state: options.state,
725
+ context
726
+ });
727
+ return {
728
+ context,
729
+ state
730
+ };
731
+ }
732
+ root(state, context) {
733
+ return this.pageDescriptorProvider.root(state, context);
734
+ }
735
+ };
736
+
737
+ //#endregion
738
+ //#region src/providers/ReactBrowserProvider.ts
739
+ var ReactBrowserProvider = class {
740
+ log = (0, __alepha_core.$logger)();
741
+ client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
742
+ alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
743
+ router = (0, __alepha_core.$inject)(BrowserRouterProvider);
744
+ root;
745
+ transitioning;
746
+ state = {
747
+ layers: [],
748
+ pathname: "",
749
+ search: ""
750
+ };
751
+ get document() {
752
+ return window.document;
753
+ }
754
+ get history() {
755
+ return window.history;
756
+ }
757
+ get location() {
758
+ return window.location;
759
+ }
760
+ get url() {
761
+ let url = this.location.pathname + this.location.search;
762
+ return url;
763
+ }
764
+ pushState(url, replace) {
765
+ let path = url;
766
+ if (replace) this.history.replaceState({}, "", path);
767
+ else this.history.pushState({}, "", path);
768
+ }
769
+ async invalidate(props) {
770
+ const previous = [];
771
+ if (props) {
772
+ const [key] = Object.keys(props);
773
+ const value = props[key];
774
+ for (const layer of this.state.layers) {
775
+ if (layer.props?.[key]) {
776
+ previous.push({
777
+ ...layer,
778
+ props: {
779
+ ...layer.props,
780
+ [key]: value
781
+ }
782
+ });
783
+ break;
784
+ }
785
+ previous.push(layer);
786
+ }
787
+ }
788
+ await this.render({ previous });
789
+ }
790
+ async go(url, options = {}) {
791
+ const result = await this.render({ url });
792
+ if (result.context.url.pathname !== url) {
793
+ this.pushState(result.context.url.pathname);
794
+ return;
795
+ }
796
+ if (options.replace) {
797
+ this.pushState(url);
798
+ return;
799
+ }
800
+ this.pushState(url);
801
+ }
802
+ async render(options = {}) {
803
+ const previous = options.previous ?? this.state.layers;
804
+ const url = options.url ?? this.url;
805
+ this.transitioning = { to: url };
806
+ const result = await this.router.transition(new URL(`http://localhost${url}`), {
807
+ previous,
808
+ state: this.state
809
+ });
810
+ if (result.redirect) return await this.render({ url: result.redirect });
811
+ this.transitioning = void 0;
812
+ return result;
813
+ }
814
+ /**
815
+ * Get embedded layers from the server.
816
+ */
817
+ getHydrationState() {
818
+ try {
819
+ if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
820
+ } catch (error) {
821
+ console.error(error);
822
+ }
823
+ }
824
+ ready = (0, __alepha_core.$hook)({
825
+ on: "ready",
826
+ handler: async () => {
827
+ const hydration = this.getHydrationState();
828
+ const previous = hydration?.layers ?? [];
829
+ if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
830
+ const { context } = await this.render({ previous });
831
+ await this.alepha.emit("react:browser:render", {
832
+ state: this.state,
833
+ context,
834
+ hydration
835
+ });
836
+ window.addEventListener("popstate", () => {
837
+ if (this.state.pathname === location.pathname) return;
838
+ this.render();
839
+ });
840
+ }
841
+ });
842
+ };
843
+
635
844
  //#endregion
636
845
  //#region src/providers/ReactServerProvider.ts
637
846
  const envSchema = __alepha_core.t.object({
@@ -647,18 +856,15 @@ var ReactServerProvider = class {
647
856
  serverStaticProvider = (0, __alepha_core.$inject)(__alepha_server_static.ServerStaticProvider);
648
857
  serverRouterProvider = (0, __alepha_core.$inject)(__alepha_server.ServerRouterProvider);
649
858
  serverTimingProvider = (0, __alepha_core.$inject)(__alepha_server.ServerTimingProvider);
650
- env = (0, __alepha_core.$inject)(envSchema);
859
+ env = (0, __alepha_core.$env)(envSchema);
651
860
  ROOT_DIV_REGEX = new RegExp(`<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`, "is");
652
861
  onConfigure = (0, __alepha_core.$hook)({
653
862
  on: "configure",
654
863
  handler: async () => {
655
- const pages = this.alepha.getDescriptorValues($page);
864
+ const pages = this.alepha.descriptors($page);
656
865
  const ssrEnabled = pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
657
- this.alepha.state("ReactServerProvider.ssr", ssrEnabled);
658
- for (const { key, instance, value } of pages) {
659
- const name = value[__alepha_core.OPTIONS].name ?? key;
660
- instance[key].render = this.createRenderFunction(name);
661
- }
866
+ this.alepha.state("react.server.ssr", ssrEnabled);
867
+ for (const page of pages) page.render = this.createRenderFunction(page.name);
662
868
  if (this.alepha.isServerless() === "vite") {
663
869
  await this.configureVite(ssrEnabled);
664
870
  return;
@@ -678,7 +884,7 @@ var ReactServerProvider = class {
678
884
  return;
679
885
  }
680
886
  this.log.info("SSR is disabled, use History API fallback");
681
- await this.serverRouterProvider.route({
887
+ this.serverRouterProvider.createRoute({
682
888
  path: "*",
683
889
  handler: async ({ url, reply }) => {
684
890
  if (url.pathname.includes(".")) {
@@ -694,13 +900,13 @@ var ReactServerProvider = class {
694
900
  }
695
901
  });
696
902
  get template() {
697
- return this.alepha.state("ReactServerProvider.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
903
+ return this.alepha.state("react.server.template") ?? "<!DOCTYPE html><html lang='en'><head></head><body></body></html>";
698
904
  }
699
905
  async registerPages(templateLoader) {
700
906
  for (const page of this.pageDescriptorProvider.getPages()) {
701
907
  if (page.children?.length) continue;
702
908
  this.log.debug(`+ ${page.match} -> ${page.name}`);
703
- await this.serverRouterProvider.route({
909
+ this.serverRouterProvider.createRoute({
704
910
  ...page,
705
911
  schema: void 0,
706
912
  method: "GET",
@@ -715,7 +921,7 @@ var ReactServerProvider = class {
715
921
  return "";
716
922
  }
717
923
  async configureStaticServer(root) {
718
- await this.serverStaticProvider.serve({
924
+ await this.serverStaticProvider.createStaticServer({
719
925
  root,
720
926
  path: this.env.REACT_SERVER_PREFIX
721
927
  });
@@ -769,7 +975,7 @@ var ReactServerProvider = class {
769
975
  onError: () => null
770
976
  };
771
977
  if (this.alepha.has(__alepha_server_links.ServerLinksProvider)) {
772
- const srv = this.alepha.get(__alepha_server_links.ServerLinksProvider);
978
+ const srv = this.alepha.inject(__alepha_server_links.ServerLinksProvider);
773
979
  const schema = __alepha_server.apiLinksResponseSchema;
774
980
  context.links = this.alepha.parse(schema, await srv.getLinks({
775
981
  user: serverRequest.user,
@@ -859,208 +1065,24 @@ var ReactServerProvider = class {
859
1065
  }
860
1066
  };
861
1067
 
862
- //#endregion
863
- //#region src/providers/BrowserRouterProvider.ts
864
- var BrowserRouterProvider = class extends __alepha_router.RouterProvider {
865
- log = (0, __alepha_core.$logger)();
866
- alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
867
- pageDescriptorProvider = (0, __alepha_core.$inject)(PageDescriptorProvider);
868
- add(entry) {
869
- this.pageDescriptorProvider.add(entry);
870
- }
871
- configure = (0, __alepha_core.$hook)({
872
- on: "configure",
873
- handler: async () => {
874
- for (const page of this.pageDescriptorProvider.getPages()) if (page.component || page.lazy) this.push({
875
- path: page.match,
876
- page
877
- });
878
- }
879
- });
880
- async transition(url, options = {}) {
881
- const { pathname, search } = url;
882
- const state = {
883
- pathname,
884
- search,
885
- layers: []
886
- };
887
- const context = {
888
- url,
889
- query: {},
890
- params: {},
891
- onError: () => null,
892
- ...options.context ?? {}
893
- };
894
- await this.alepha.emit("react:transition:begin", {
895
- state,
896
- context
897
- });
898
- try {
899
- const previous = options.previous;
900
- const { route, params } = this.match(pathname);
901
- const query = {};
902
- if (search) for (const [key, value] of new URLSearchParams(search).entries()) query[key] = String(value);
903
- context.query = query;
904
- context.params = params ?? {};
905
- context.previous = previous;
906
- if (isPageRoute(route)) {
907
- const result = await this.pageDescriptorProvider.createLayers(route.page, context);
908
- if (result.redirect) return {
909
- redirect: result.redirect,
910
- state,
911
- context
912
- };
913
- state.layers = result.layers;
914
- }
915
- if (state.layers.length === 0) state.layers.push({
916
- name: "not-found",
917
- element: (0, react.createElement)(NotFoundPage),
918
- index: 0,
919
- path: "/"
920
- });
921
- await this.alepha.emit("react:transition:success", {
922
- state,
923
- context
924
- });
925
- } catch (e) {
926
- this.log.error(e);
927
- state.layers = [{
928
- name: "error",
929
- element: this.pageDescriptorProvider.renderError(e),
930
- index: 0,
931
- path: "/"
932
- }];
933
- await this.alepha.emit("react:transition:error", {
934
- error: e,
935
- state,
936
- context
937
- });
938
- }
939
- if (options.state) {
940
- options.state.layers = state.layers;
941
- options.state.pathname = state.pathname;
942
- options.state.search = state.search;
943
- }
944
- await this.alepha.emit("react:transition:end", {
945
- state: options.state,
946
- context
947
- });
948
- return {
949
- context,
950
- state
951
- };
952
- }
953
- root(state, context) {
954
- return this.pageDescriptorProvider.root(state, context);
955
- }
956
- };
957
-
958
- //#endregion
959
- //#region src/providers/ReactBrowserProvider.ts
960
- var ReactBrowserProvider = class {
961
- log = (0, __alepha_core.$logger)();
962
- client = (0, __alepha_core.$inject)(__alepha_server_links.LinkProvider);
963
- alepha = (0, __alepha_core.$inject)(__alepha_core.Alepha);
964
- router = (0, __alepha_core.$inject)(BrowserRouterProvider);
965
- root;
966
- transitioning;
967
- state = {
968
- layers: [],
969
- pathname: "",
970
- search: ""
971
- };
972
- get document() {
973
- return window.document;
974
- }
975
- get history() {
976
- return window.history;
977
- }
978
- get url() {
979
- return window.location.pathname + window.location.search;
980
- }
981
- async invalidate(props) {
982
- const previous = [];
983
- if (props) {
984
- const [key] = Object.keys(props);
985
- const value = props[key];
986
- for (const layer of this.state.layers) {
987
- if (layer.props?.[key]) {
988
- previous.push({
989
- ...layer,
990
- props: {
991
- ...layer.props,
992
- [key]: value
993
- }
994
- });
995
- break;
996
- }
997
- previous.push(layer);
998
- }
999
- }
1000
- await this.render({ previous });
1001
- }
1002
- async go(url, options = {}) {
1003
- const result = await this.render({ url });
1004
- if (result.context.url.pathname !== url) {
1005
- this.history.replaceState({}, "", result.context.url.pathname);
1006
- return;
1007
- }
1008
- if (options.replace) {
1009
- this.history.replaceState({}, "", url);
1010
- return;
1011
- }
1012
- this.history.pushState({}, "", url);
1013
- }
1014
- async render(options = {}) {
1015
- const previous = options.previous ?? this.state.layers;
1016
- const url = options.url ?? this.url;
1017
- this.transitioning = { to: url };
1018
- const result = await this.router.transition(new URL(`http://localhost${url}`), {
1019
- previous,
1020
- state: this.state
1021
- });
1022
- if (result.redirect) return await this.render({ url: result.redirect });
1023
- this.transitioning = void 0;
1024
- return result;
1025
- }
1026
- /**
1027
- * Get embedded layers from the server.
1028
- */
1029
- getHydrationState() {
1030
- try {
1031
- if ("__ssr" in window && typeof window.__ssr === "object") return window.__ssr;
1032
- } catch (error) {
1033
- console.error(error);
1034
- }
1035
- }
1036
- ready = (0, __alepha_core.$hook)({
1037
- on: "ready",
1038
- handler: async () => {
1039
- const hydration = this.getHydrationState();
1040
- const previous = hydration?.layers ?? [];
1041
- if (hydration?.links) for (const link of hydration.links.links) this.client.pushLink(link);
1042
- const { context } = await this.render({ previous });
1043
- await this.alepha.emit("react:browser:render", {
1044
- state: this.state,
1045
- context,
1046
- hydration
1047
- });
1048
- window.addEventListener("popstate", () => {
1049
- this.render();
1050
- });
1051
- }
1052
- });
1053
- };
1054
-
1055
1068
  //#endregion
1056
1069
  //#region src/hooks/RouterHookApi.ts
1057
1070
  var RouterHookApi = class {
1058
- constructor(pages, state, layer, browser) {
1071
+ constructor(pages, context, state, layer, browser) {
1059
1072
  this.pages = pages;
1073
+ this.context = context;
1060
1074
  this.state = state;
1061
1075
  this.layer = layer;
1062
1076
  this.browser = browser;
1063
1077
  }
1078
+ getURL() {
1079
+ if (!this.browser) return this.context.url;
1080
+ return new URL(this.location.href);
1081
+ }
1082
+ get location() {
1083
+ if (!this.browser) throw new Error("Browser is required");
1084
+ return this.browser.location;
1085
+ }
1064
1086
  get current() {
1065
1087
  return this.state;
1066
1088
  }
@@ -1136,9 +1158,9 @@ const useRouter = () => {
1136
1158
  const layer = (0, react.useContext)(RouterLayerContext);
1137
1159
  if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
1138
1160
  const pages = (0, react.useMemo)(() => {
1139
- return ctx.alepha.get(PageDescriptorProvider).getPages();
1161
+ return ctx.alepha.inject(PageDescriptorProvider).getPages();
1140
1162
  }, []);
1141
- return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
1163
+ return (0, react.useMemo)(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
1142
1164
  };
1143
1165
 
1144
1166
  //#endregion
@@ -1146,11 +1168,11 @@ const useRouter = () => {
1146
1168
  const Link = (props) => {
1147
1169
  react.default.useContext(RouterContext);
1148
1170
  const router = useRouter();
1149
- const to = typeof props.to === "string" ? props.to : props.to[__alepha_core.OPTIONS].path;
1171
+ const to = typeof props.to === "string" ? props.to : props.to.options.path;
1150
1172
  if (!to) return null;
1151
- const can = typeof props.to === "string" ? void 0 : props.to[__alepha_core.OPTIONS].can;
1173
+ const can = typeof props.to === "string" ? void 0 : props.to.options.can;
1152
1174
  if (can && !can()) return null;
1153
- const name = typeof props.to === "string" ? void 0 : props.to[__alepha_core.OPTIONS].name;
1175
+ const name = typeof props.to === "string" ? void 0 : props.to.options.name;
1154
1176
  const anchorProps = {
1155
1177
  ...props,
1156
1178
  to: void 0
@@ -1202,7 +1224,7 @@ const useActive = (path) => {
1202
1224
  const useInject = (clazz) => {
1203
1225
  const ctx = (0, react.useContext)(RouterContext);
1204
1226
  if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
1205
- return (0, react.useMemo)(() => ctx.alepha.get(clazz), []);
1227
+ return (0, react.useMemo)(() => ctx.alepha.inject(clazz), []);
1206
1228
  };
1207
1229
 
1208
1230
  //#endregion
@@ -1258,19 +1280,25 @@ const useRouterState = () => {
1258
1280
  //#endregion
1259
1281
  //#region src/index.ts
1260
1282
  /**
1261
- * Alepha React Module
1283
+ * Provides full-stack React development with declarative routing, server-side rendering, and client-side hydration.
1262
1284
  *
1263
- * Alepha React Module contains a router for client-side navigation and server-side rendering.
1264
- * Routes can be defined using the `$page` descriptor.
1285
+ * The React module enables building modern React applications using the `$page` descriptor on class properties.
1286
+ * It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
1287
+ * type safety and schema validation for route parameters and data.
1265
1288
  *
1266
1289
  * @see {@link $page}
1267
1290
  * @module alepha.react
1268
1291
  */
1269
- var AlephaReact = class {
1270
- name = "alepha.react";
1271
- $services = (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(__alepha_server_links.AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider);
1272
- };
1273
- (0, __alepha_core.__bind)($page, AlephaReact);
1292
+ const AlephaReact = (0, __alepha_core.$module)({
1293
+ name: "alepha.react",
1294
+ descriptors: [$page],
1295
+ services: [
1296
+ ReactServerProvider,
1297
+ PageDescriptorProvider,
1298
+ ReactBrowserProvider
1299
+ ],
1300
+ register: (alepha) => alepha.with(__alepha_server.AlephaServer).with(__alepha_server_cache.AlephaServerCache).with(__alepha_server_links.AlephaServerLinks).with(ReactServerProvider).with(PageDescriptorProvider)
1301
+ });
1274
1302
 
1275
1303
  //#endregion
1276
1304
  exports.$page = $page;
@@ -1279,6 +1307,8 @@ exports.ClientOnly = ClientOnly_default;
1279
1307
  exports.ErrorBoundary = ErrorBoundary_default;
1280
1308
  exports.Link = Link_default;
1281
1309
  exports.NestedView = NestedView_default;
1310
+ exports.NotFound = NotFoundPage;
1311
+ exports.PageDescriptor = PageDescriptor;
1282
1312
  exports.PageDescriptorProvider = PageDescriptorProvider;
1283
1313
  exports.ReactBrowserProvider = ReactBrowserProvider;
1284
1314
  exports.ReactServerProvider = ReactServerProvider;