@alepha/react 0.8.1 → 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/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
@@ -337,7 +341,7 @@ var RedirectionError = class extends Error {
337
341
  const envSchema$1 = t.object({ REACT_STRICT_MODE: t.boolean({ default: true }) });
338
342
  var PageDescriptorProvider = class {
339
343
  log = $logger();
340
- env = $inject(envSchema$1);
344
+ env = $env(envSchema$1);
341
345
  alepha = $inject(Alepha);
342
346
  pages = [];
343
347
  getPages() {
@@ -543,18 +547,17 @@ var PageDescriptorProvider = class {
543
547
  on: "configure",
544
548
  handler: () => {
545
549
  let hasNotFoundHandler = false;
546
- const pages = this.alepha.getDescriptorValues($page);
550
+ const pages = this.alepha.descriptors($page);
547
551
  const hasParent = (it) => {
548
552
  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() : [];
553
+ const children = page.options.children ? Array.isArray(page.options.children) ? page.options.children : page.options.children() : [];
550
554
  if (children.includes(it)) return true;
551
555
  }
552
556
  };
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));
557
+ for (const page of pages) {
558
+ if (page.options.path === "/*") hasNotFoundHandler = true;
559
+ if (hasParent(page)) continue;
560
+ this.add(this.map(pages, page));
558
561
  }
559
562
  if (!hasNotFoundHandler && pages.length > 0) this.add({
560
563
  path: "/*",
@@ -568,9 +571,10 @@ var PageDescriptorProvider = class {
568
571
  }
569
572
  });
570
573
  map(pages, target) {
571
- const children = target[OPTIONS].children ? Array.isArray(target[OPTIONS].children) ? target[OPTIONS].children : target[OPTIONS].children() : [];
574
+ const children = target.options.children ? Array.isArray(target.options.children) ? target.options.children : target.options.children() : [];
572
575
  return {
573
- ...target[OPTIONS],
576
+ ...target.options,
577
+ name: target.name,
574
578
  parent: void 0,
575
579
  children: children.map((it) => this.map(pages, it))
576
580
  };
@@ -821,7 +825,7 @@ const envSchema = t.object({ REACT_ROOT_ID: t.string({ default: "root" }) });
821
825
  var ReactBrowserRenderer = class {
822
826
  browserProvider = $inject(ReactBrowserProvider);
823
827
  browserRouterProvider = $inject(BrowserRouterProvider);
824
- env = $inject(envSchema);
828
+ env = $env(envSchema);
825
829
  log = $logger();
826
830
  root;
827
831
  options = { scrollRestoration: "top" };
@@ -948,9 +952,9 @@ const useRouter = () => {
948
952
  const layer = useContext(RouterLayerContext);
949
953
  if (!ctx || !layer) throw new Error("useRouter must be used within a RouterProvider");
950
954
  const pages = useMemo(() => {
951
- return ctx.alepha.get(PageDescriptorProvider).getPages();
955
+ return ctx.alepha.inject(PageDescriptorProvider).getPages();
952
956
  }, []);
953
- return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.get(ReactBrowserProvider) : void 0), [layer]);
957
+ return useMemo(() => new RouterHookApi(pages, ctx.context, ctx.state, layer, ctx.alepha.isBrowser() ? ctx.alepha.inject(ReactBrowserProvider) : void 0), [layer]);
954
958
  };
955
959
 
956
960
  //#endregion
@@ -958,11 +962,11 @@ const useRouter = () => {
958
962
  const Link = (props) => {
959
963
  React.useContext(RouterContext);
960
964
  const router = useRouter();
961
- const to = typeof props.to === "string" ? props.to : props.to[OPTIONS].path;
965
+ const to = typeof props.to === "string" ? props.to : props.to.options.path;
962
966
  if (!to) return null;
963
- const can = typeof props.to === "string" ? void 0 : props.to[OPTIONS].can;
967
+ const can = typeof props.to === "string" ? void 0 : props.to.options.can;
964
968
  if (can && !can()) return null;
965
- const name = typeof props.to === "string" ? void 0 : props.to[OPTIONS].name;
969
+ const name = typeof props.to === "string" ? void 0 : props.to.options.name;
966
970
  const anchorProps = {
967
971
  ...props,
968
972
  to: void 0
@@ -1014,7 +1018,7 @@ const useActive = (path) => {
1014
1018
  const useInject = (clazz) => {
1015
1019
  const ctx = useContext(RouterContext);
1016
1020
  if (!ctx) throw new Error("useRouter must be used within a <RouterProvider>");
1017
- return useMemo(() => ctx.alepha.get(clazz), []);
1021
+ return useMemo(() => ctx.alepha.inject(clazz), []);
1018
1022
  };
1019
1023
 
1020
1024
  //#endregion
@@ -1069,12 +1073,18 @@ const useRouterState = () => {
1069
1073
 
1070
1074
  //#endregion
1071
1075
  //#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);
1076
+ const AlephaReact = $module({
1077
+ name: "alepha.react",
1078
+ descriptors: [$page],
1079
+ services: [
1080
+ PageDescriptorProvider,
1081
+ ReactBrowserRenderer,
1082
+ BrowserRouterProvider,
1083
+ ReactBrowserProvider
1084
+ ],
1085
+ register: (alepha) => alepha.with(AlephaServer).with(AlephaServerLinks).with(PageDescriptorProvider).with(ReactBrowserProvider).with(BrowserRouterProvider).with(ReactBrowserRenderer)
1086
+ });
1077
1087
 
1078
1088
  //#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 };
1089
+ 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
1090
  //# sourceMappingURL=index.browser.js.map