@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/README.md CHANGED
@@ -23,127 +23,6 @@ The React module enables building modern React applications using the `$page` de
23
23
  It delivers seamless server-side rendering, automatic code splitting, and client-side navigation with full
24
24
  type safety and schema validation for route parameters and data.
25
25
 
26
- **Key Features:**
27
- - Declarative page definition with `$page` descriptor
28
- - Server-side rendering (SSR) with automatic hydration
29
- - Type-safe routing with parameter validation
30
- - Schema-based data resolution and validation
31
- - SEO-friendly meta tag management
32
- - Automatic code splitting and lazy loading
33
- - Client-side navigation with browser history
34
-
35
- **Basic Usage:**
36
- ```ts
37
- import { Alepha, run, t } from "alepha";
38
- import { AlephaReact, $page } from "alepha/react";
39
-
40
- class AppRoutes {
41
- // Home page
42
- home = $page({
43
- path: "/",
44
- component: () => (
45
- <div>
46
- <h1>Welcome to Alepha</h1>
47
- <p>Build amazing React applications!</p>
48
- </div>
49
- ),
50
- });
51
-
52
- // About page with meta tags
53
- about = $page({
54
- path: "/about",
55
- head: {
56
- title: "About Us",
57
- description: "Learn more about our mission",
58
- },
59
- component: () => (
60
- <div>
61
- <h1>About Us</h1>
62
- <p>Learn more about our mission.</p>
63
- </div>
64
- ),
65
- });
66
- }
67
-
68
- const alepha = Alepha.create()
69
- .with(AlephaReact)
70
- .with(AppRoutes);
71
-
72
- run(alepha);
73
- ```
74
-
75
- **Dynamic Routes with Parameters:**
76
- ```tsx
77
- class UserRoutes {
78
- userProfile = $page({
79
- path: "/users/:id",
80
- schema: {
81
- params: t.object({
82
- id: t.string(),
83
- }),
84
- },
85
- resolve: async ({ params }) => {
86
- // Fetch user data server-side
87
- const user = await getUserById(params.id);
88
- return { user };
89
- },
90
- head: ({ user }) => ({
91
- title: `${user.name} - Profile`,
92
- description: `View ${user.name}'s profile`,
93
- }),
94
- component: ({ user }) => (
95
- <div>
96
- <h1>{user.name}</h1>
97
- <p>Email: {user.email}</p>
98
- </div>
99
- ),
100
- });
101
-
102
- userSettings = $page({
103
- path: "/users/:id/settings",
104
- schema: {
105
- params: t.object({
106
- id: t.string(),
107
- }),
108
- },
109
- component: ({ params }) => (
110
- <UserSettings userId={params.id} />
111
- ),
112
- });
113
- }
114
- ```
115
-
116
- **Static Generation:**
117
- ```tsx
118
- class BlogRoutes {
119
- blogPost = $page({
120
- path: "/blog/:slug",
121
- schema: {
122
- params: t.object({
123
- slug: t.string(),
124
- }),
125
- },
126
- static: {
127
- entries: [
128
- { params: { slug: "getting-started" } },
129
- { params: { slug: "advanced-features" } },
130
- { params: { slug: "deployment" } },
131
- ],
132
- },
133
- resolve: ({ params }) => {
134
- const post = getBlogPost(params.slug);
135
- return { post };
136
- },
137
- component: ({ post }) => (
138
- <article>
139
- <h1>{post.title}</h1>
140
- <div>{post.content}</div>
141
- </article>
142
- ),
143
- });
144
- }
145
- ```
146
-
147
26
  ## API Reference
148
27
 
149
28
  ### Descriptors
@@ -1,4 +1,4 @@
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 } from "@alepha/server";
3
3
  import { AlephaServerLinks, LinkProvider } from "@alepha/server-links";
4
4
  import { RouterProvider } from "@alepha/router";
@@ -7,21 +7,25 @@ import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { createRoot, hydrateRoot } from "react-dom/client";
8
8
 
9
9
  //#region src/descriptors/$page.ts
10
- const KEY = "PAGE";
11
10
  /**
12
11
  * Main descriptor for defining a React route in the application.
13
12
  */
14
13
  const $page = (options) => {
15
- __descriptor(KEY);
16
- return {
17
- [KIND]: KEY,
18
- [OPTIONS]: options,
19
- render: () => {
20
- throw new NotImplementedError(KEY);
21
- }
22
- };
14
+ return createDescriptor(PageDescriptor, options);
23
15
  };
24
- $page[KIND] = KEY;
16
+ var PageDescriptor = class extends Descriptor {
17
+ get name() {
18
+ return this.options.name ?? this.config.propertyKey;
19
+ }
20
+ /**
21
+ * For testing or build purposes, this will render the page (with or without the HTML layout) and return the HTML and context.
22
+ * Only valid for server-side rendering, it will throw an error if called on the client-side.
23
+ */
24
+ async render(options) {
25
+ throw new NotImplementedError("");
26
+ }
27
+ };
28
+ $page[KIND] = PageDescriptor;
25
29
 
26
30
  //#endregion
27
31
  //#region src/components/NotFound.tsx
@@ -67,23 +71,11 @@ const ClientOnly = (props) => {
67
71
  };
68
72
  var ClientOnly_default = ClientOnly;
69
73
 
70
- //#endregion
71
- //#region src/contexts/RouterContext.ts
72
- const RouterContext = createContext(void 0);
73
-
74
- //#endregion
75
- //#region src/hooks/useAlepha.ts
76
- const useAlepha = () => {
77
- const routerContext = useContext(RouterContext);
78
- if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
79
- return routerContext.alepha;
80
- };
81
-
82
74
  //#endregion
83
75
  //#region src/components/ErrorViewer.tsx
84
- const ErrorViewer = ({ error }) => {
76
+ const ErrorViewer = ({ error, alepha }) => {
85
77
  const [expanded, setExpanded] = useState(false);
86
- const isProduction = useAlepha().isProduction();
78
+ const isProduction = alepha.isProduction();
87
79
  if (isProduction) return /* @__PURE__ */ jsx(ErrorViewerProduction, {});
88
80
  const stackLines = error.stack?.split("\n") ?? [];
89
81
  const previewLines = stackLines.slice(0, 5);
@@ -227,6 +219,10 @@ const ErrorViewerProduction = () => {
227
219
  });
228
220
  };
229
221
 
222
+ //#endregion
223
+ //#region src/contexts/RouterContext.ts
224
+ const RouterContext = createContext(void 0);
225
+
230
226
  //#endregion
231
227
  //#region src/contexts/RouterLayerContext.ts
232
228
  const RouterLayerContext = createContext(void 0);
@@ -337,7 +333,7 @@ var RedirectionError = class extends Error {
337
333
  const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
338
334
  var PageDescriptorProvider = class {
339
335
  log = $logger();
340
- env = $inject(envSchema$1);
336
+ env = $env(envSchema$1);
341
337
  alepha = $inject(Alepha);
342
338
  pages = [];
343
339
  getPages() {
@@ -510,7 +506,10 @@ var PageDescriptorProvider = class {
510
506
  return void 0;
511
507
  }
512
508
  renderError(error) {
513
- return createElement(ErrorViewer_default, { error });
509
+ return createElement(ErrorViewer_default, {
510
+ error,
511
+ alepha: this.alepha
512
+ });
514
513
  }
515
514
  renderEmptyView() {
516
515
  return createElement(NestedView_default, {});
@@ -543,18 +542,17 @@ var PageDescriptorProvider = class {
543
542
  on: "configure",
544
543
  handler: () => {
545
544
  let hasNotFoundHandler = false;
546
- const pages = this.alepha.getDescriptorValues($page);
545
+ const pages = this.alepha.descriptors($page);
547
546
  const hasParent = (it) => {
548
547
  for (const page of pages) {
549
- const children = page.value[OPTIONS].children ? Array.isArray(page.value[OPTIONS].children) ? page.value[OPTIONS].children : page.value[OPTIONS].children() : [];
548
+ const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
550
549
  if (children.includes(it)) return true;
551
550
  }
552
551
  };
553
- for (const { value, key } of pages) value[OPTIONS].name ??= key;
554
- for (const { value } of pages) {
555
- if (value[OPTIONS].path === "/*") hasNotFoundHandler = true;
556
- if (hasParent(value)) continue;
557
- this.add(this.map(pages, value));
552
+ for (const page of pages) {
553
+ if (page.options.path === "/*") hasNotFoundHandler = true;
554
+ if (hasParent(page)) continue;
555
+ this.add(this.map(pages, page));
558
556
  }
559
557
  if (!hasNotFoundHandler && pages.length > 0) this.add({
560
558
  path: "/*",
@@ -568,9 +566,10 @@ var PageDescriptorProvider = class {
568
566
  }
569
567
  });
570
568
  map(pages, target) {
571
- const children = target[OPTIONS].children ? Array.isArray(target[OPTIONS].children) ? target[OPTIONS].children : target[OPTIONS].children() : [];
569
+ const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
572
570
  return {
573
- ...target[OPTIONS],
571
+ ...target.options,
572
+ name: target.name,
574
573
  parent: void 0,
575
574
  children: children.map((it) => this.map(pages, it))
576
575
  };
@@ -808,7 +807,7 @@ var ReactBrowserProvider = class {
808
807
  hydration
809
808
  });
810
809
  window.addEventListener("popstate", () => {
811
- if (this.state.pathname === location.pathname) return;
810
+ if (this.state.pathname === this.url) return;
812
811
  this.render();
813
812
  });
814
813
  }
@@ -821,7 +820,7 @@ const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
821
820
  var ReactBrowserRenderer = class {
822
821
  browserProvider = $inject(ReactBrowserProvider);
823
822
  browserRouterProvider = $inject(BrowserRouterProvider);
824
- env = $inject(envSchema);
823
+ env = $env(envSchema);
825
824
  log = $logger();
826
825
  root;
827
826
  options = { scrollRestoration: "top" };
@@ -948,9 +947,9 @@ const useRouter = () => {
948
947
  const layer = useContext(RouterLayerContext);
949
948
  if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
950
949
  const pages = useMemo(() => {
951
- return ctx.alepha.get(PageDescriptorProvider).getPages();
950
+ return ctx.alepha.inject(PageDescriptorProvider).getPages();
952
951
  }, []);
953
- return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
952
+ return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
954
953
  };
955
954
 
956
955
  //#endregion
@@ -958,11 +957,11 @@ const useRouter = () => {
958
957
  const Link = (props) => {
959
958
  React.useContext(RouterContext);
960
959
  const router = useRouter();
961
- const to = typeof props.to === "string" ? props.to : props.to[OPTIONS].path;
960
+ const to = typeof props.to === "string" ? props.to : props.to.options.path;
962
961
  if (!to) return null;
963
- const can = typeof props.to === "string" ? void 0 : props.to[OPTIONS].can;
962
+ const can = typeof props.to === "string" ? void 0 : props.to.options.can;
964
963
  if (can && !can()) return null;
965
- const name = typeof props.to === "string" ? void 0 : props.to[OPTIONS].name;
964
+ const name = typeof props.to === "string" ? void 0 : props.to.options.name;
966
965
  const anchorProps = {
967
966
  ...props,
968
967
  to: void 0
@@ -1009,12 +1008,20 @@ const useActive = (path) => {
1009
1008
  };
1010
1009
  };
1011
1010
 
1011
+ //#endregion
1012
+ //#region src/hooks/useAlepha.ts
1013
+ const useAlepha = () => {
1014
+ const routerContext = useContext(RouterContext);
1015
+ if (!routerContext) throw new Error("useAlepha must be used within a RouterProvider");
1016
+ return routerContext.alepha;
1017
+ };
1018
+
1012
1019
  //#endregion
1013
1020
  //#region src/hooks/useInject.ts
1014
1021
  const useInject = (clazz) => {
1015
1022
  const ctx = useContext(RouterContext);
1016
1023
  if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
1017
- return useMemo(() => ctx.alepha.get(clazz), []);
1024
+ return useMemo(() => ctx.alepha.inject(clazz), []);
1018
1025
  };
1019
1026
 
1020
1027
  //#endregion
@@ -1069,12 +1076,18 @@ const useRouterState = () => {
1069
1076
 
1070
1077
  //#endregion
1071
1078
  //#region src/index.browser.ts
1072
- var AlephaReact = class {
1073
- name = "alepha.react";
1074
- $services = (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(PageDescriptorProvider).with(ReactBrowserProvider).with(BrowserRouterProvider).with(ReactBrowserRenderer);
1075
- };
1076
- __bind($page, AlephaReact);
1079
+ const AlephaReact = $module({
1080
+ name: "alepha.react",
1081
+ descriptors: [$page],
1082
+ services: [
1083
+ PageDescriptorProvider,
1084
+ ReactBrowserRenderer,
1085
+ BrowserRouterProvider,
1086
+ ReactBrowserProvider
1087
+ ],
1088
+ register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(PageDescriptorProvider).with(ReactBrowserProvider).with(BrowserRouterProvider).with(ReactBrowserRenderer)
1089
+ });
1077
1090
 
1078
1091
  //#endregion
1079
- export { $page, AlephaReact, BrowserRouterProvider, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptorProvider, ReactBrowserProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
1092
+ export { $page, AlephaReact, BrowserRouterProvider, ClientOnly_default as ClientOnly, ErrorBoundary_default as ErrorBoundary, Link_default as Link, NestedView_default as NestedView, NotFoundPage as NotFound, PageDescriptor, PageDescriptorProvider, ReactBrowserProvider, RedirectionError, RouterContext, RouterHookApi, RouterLayerContext, isPageRoute, useActive, useAlepha, useClient, useInject, useQueryParams, useRouter, useRouterEvents, useRouterState };
1080
1093
  //# sourceMappingURL=index.browser.js.map