@alepha/react 0.9.5 → 0.10.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
@@ -34,14 +34,96 @@ run(alepha);
34
34
 
35
35
  ### Descriptors
36
36
 
37
- Descriptors are functions that define and configure various aspects of your application. They follow the convention of starting with `$` and return configured descriptor instances.
37
+ Descriptors are functions that define and configure various aspects of your application. They follow the convention of starting with ` $ ` and return configured descriptor instances.
38
38
 
39
- For more details, see the [Descriptors documentation](https://feunard.github.io/alepha/docs/descriptors).
39
+ For more details, see the [Descriptors documentation](/docs/descriptors).
40
40
 
41
41
  #### $page()
42
42
 
43
43
  Main descriptor for defining a React route in the application.
44
44
 
45
+ The $page descriptor is the core building block for creating type-safe, SSR-enabled React routes.
46
+ It provides a declarative way to define pages with powerful features:
47
+
48
+ **Routing & Navigation**
49
+ - URL pattern matching with parameters (e.g., `/users/:id`)
50
+ - Nested routing with parent-child relationships
51
+ - Type-safe URL parameter and query string validation
52
+
53
+ **Data Loading**
54
+ - Server-side data fetching with the `resolve` function
55
+ - Automatic serialization and hydration for SSR
56
+ - Access to request context, URL params, and parent data
57
+
58
+ **Component Loading**
59
+ - Direct component rendering or lazy loading for code splitting
60
+ - Client-only rendering when browser APIs are needed
61
+ - Automatic fallback handling during hydration
62
+
63
+ **Performance Optimization**
64
+ - Static generation for pre-rendered pages at build time
65
+ - Server-side caching with configurable TTL and providers
66
+ - Code splitting through lazy component loading
67
+
68
+ **Error Handling**
69
+ - Custom error handlers with support for redirects
70
+ - Hierarchical error handling (child → parent)
71
+ - HTTP status code handling (404, 401, etc.)
72
+
73
+ **Page Animations**
74
+ - CSS-based enter/exit animations
75
+ - Dynamic animations based on page state
76
+ - Custom timing and easing functions
77
+
78
+ **Lifecycle Management**
79
+ - Server response hooks for headers and status codes
80
+ - Page leave handlers for cleanup (browser only)
81
+ - Permission-based access control
82
+
83
+ ```typescript
84
+ const userProfile = $page({
85
+ path: "/users/:id",
86
+ schema: {
87
+ params: t.object({ id: t.int() }),
88
+ query: t.object({ tab: t.optional(t.string()) })
89
+ },
90
+ resolve: async ({ params }) => {
91
+ const user = await userApi.getUser(params.id);
92
+ return { user };
93
+ },
94
+ lazy: () => import("./UserProfile.tsx")
95
+ });
96
+ ```
97
+
98
+ ```typescript
99
+ const projectSection = $page({
100
+ path: "/projects/:id",
101
+ children: () => [projectBoard, projectSettings],
102
+ resolve: async ({ params }) => {
103
+ const project = await projectApi.get(params.id);
104
+ return { project };
105
+ },
106
+ errorHandler: (error) => {
107
+ if (HttpError.is(error, 404)) {
108
+ return <ProjectNotFound />;
109
+ }
110
+ }
111
+ });
112
+ ```
113
+
114
+ ```typescript
115
+ const blogPost = $page({
116
+ path: "/blog/:slug",
117
+ static: {
118
+ entries: posts.map(p => ({ params: { slug: p.slug } }))
119
+ },
120
+ resolve: async ({ params }) => {
121
+ const post = await loadPost(params.slug);
122
+ return { post };
123
+ }
124
+ });
125
+ ```
126
+
45
127
  ### Hooks
46
128
 
47
129
  Hooks provide a way to tap into various lifecycle events and extend functionality. They follow the convention of starting with `use` and return configured hook instances.
@@ -56,7 +138,7 @@ With Alepha, you can access the core functionalities of the framework:
56
138
 
57
139
  - alepha.state() for state management
58
140
  - alepha.inject() for dependency injection
59
- - alepha.emit() for event handling
141
+ - alepha.events.emit() for event handling
60
142
  etc...
61
143
 
62
144
  #### useClient()
@@ -1,4 +1,4 @@
1
- import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, TypeGuard, createDescriptor, t } from "@alepha/core";
1
+ import { $env, $hook, $inject, $module, Alepha, AlephaError, Descriptor, KIND, createDescriptor, t } from "@alepha/core";
2
2
  import { AlephaServer, HttpClient } from "@alepha/server";
3
3
  import { AlephaServerLinks, LinkProvider } from "@alepha/server-links";
4
4
  import { DateTimeProvider } from "@alepha/datetime";
@@ -11,6 +11,91 @@ import { createRoot, hydrateRoot } from "react-dom/client";
11
11
  //#region src/descriptors/$page.ts
12
12
  /**
13
13
  * Main descriptor for defining a React route in the application.
14
+ *
15
+ * The $page descriptor is the core building block for creating type-safe, SSR-enabled React routes.
16
+ * It provides a declarative way to define pages with powerful features:
17
+ *
18
+ * **Routing & Navigation**
19
+ * - URL pattern matching with parameters (e.g., `/users/:id`)
20
+ * - Nested routing with parent-child relationships
21
+ * - Type-safe URL parameter and query string validation
22
+ *
23
+ * **Data Loading**
24
+ * - Server-side data fetching with the `resolve` function
25
+ * - Automatic serialization and hydration for SSR
26
+ * - Access to request context, URL params, and parent data
27
+ *
28
+ * **Component Loading**
29
+ * - Direct component rendering or lazy loading for code splitting
30
+ * - Client-only rendering when browser APIs are needed
31
+ * - Automatic fallback handling during hydration
32
+ *
33
+ * **Performance Optimization**
34
+ * - Static generation for pre-rendered pages at build time
35
+ * - Server-side caching with configurable TTL and providers
36
+ * - Code splitting through lazy component loading
37
+ *
38
+ * **Error Handling**
39
+ * - Custom error handlers with support for redirects
40
+ * - Hierarchical error handling (child → parent)
41
+ * - HTTP status code handling (404, 401, etc.)
42
+ *
43
+ * **Page Animations**
44
+ * - CSS-based enter/exit animations
45
+ * - Dynamic animations based on page state
46
+ * - Custom timing and easing functions
47
+ *
48
+ * **Lifecycle Management**
49
+ * - Server response hooks for headers and status codes
50
+ * - Page leave handlers for cleanup (browser only)
51
+ * - Permission-based access control
52
+ *
53
+ * @example Simple page with data fetching
54
+ * ```typescript
55
+ * const userProfile = $page({
56
+ * path: "/users/:id",
57
+ * schema: {
58
+ * params: t.object({ id: t.int() }),
59
+ * query: t.object({ tab: t.optional(t.string()) })
60
+ * },
61
+ * resolve: async ({ params }) => {
62
+ * const user = await userApi.getUser(params.id);
63
+ * return { user };
64
+ * },
65
+ * lazy: () => import("./UserProfile.tsx")
66
+ * });
67
+ * ```
68
+ *
69
+ * @example Nested routing with error handling
70
+ * ```typescript
71
+ * const projectSection = $page({
72
+ * path: "/projects/:id",
73
+ * children: () => [projectBoard, projectSettings],
74
+ * resolve: async ({ params }) => {
75
+ * const project = await projectApi.get(params.id);
76
+ * return { project };
77
+ * },
78
+ * errorHandler: (error) => {
79
+ * if (HttpError.is(error, 404)) {
80
+ * return <ProjectNotFound />;
81
+ * }
82
+ * }
83
+ * });
84
+ * ```
85
+ *
86
+ * @example Static generation with caching
87
+ * ```typescript
88
+ * const blogPost = $page({
89
+ * path: "/blog/:slug",
90
+ * static: {
91
+ * entries: posts.map(p => ({ params: { slug: p.slug } }))
92
+ * },
93
+ * resolve: async ({ params }) => {
94
+ * const post = await loadPost(params.slug);
95
+ * return { post };
96
+ * }
97
+ * });
98
+ * ```
14
99
  */
15
100
  const $page = (options) => {
16
101
  return createDescriptor(PageDescriptor, options);
@@ -269,7 +354,7 @@ const AlephaContext = createContext(void 0);
269
354
  *
270
355
  * - alepha.state() for state management
271
356
  * - alepha.inject() for dependency injection
272
- * - alepha.emit() for event handling
357
+ * - alepha.events.emit() for event handling
273
358
  * etc...
274
359
  */
275
360
  const useAlepha = () => {
@@ -296,10 +381,10 @@ const useRouterEvents = (opts = {}, deps = []) => {
296
381
  const onEnd = opts.onEnd;
297
382
  const onError = opts.onError;
298
383
  const onSuccess = opts.onSuccess;
299
- if (onBegin) subs.push(alepha.on("react:transition:begin", cb(onBegin)));
300
- if (onEnd) subs.push(alepha.on("react:transition:end", cb(onEnd)));
301
- if (onError) subs.push(alepha.on("react:transition:error", cb(onError)));
302
- if (onSuccess) subs.push(alepha.on("react:transition:success", cb(onSuccess)));
384
+ if (onBegin) subs.push(alepha.events.on("react:transition:begin", cb(onBegin)));
385
+ if (onEnd) subs.push(alepha.events.on("react:transition:end", cb(onEnd)));
386
+ if (onError) subs.push(alepha.events.on("react:transition:error", cb(onError)));
387
+ if (onSuccess) subs.push(alepha.events.on("react:transition:success", cb(onSuccess)));
303
388
  return () => {
304
389
  for (const sub of subs) sub();
305
390
  };
@@ -314,17 +399,17 @@ const useRouterEvents = (opts = {}, deps = []) => {
314
399
  const useStore = (key, defaultValue) => {
315
400
  const alepha = useAlepha();
316
401
  useMemo(() => {
317
- if (defaultValue != null && alepha.state(key) == null) alepha.state(key, defaultValue);
402
+ if (defaultValue != null && alepha.state.get(key) == null) alepha.state.set(key, defaultValue);
318
403
  }, [defaultValue]);
319
- const [state, setState] = useState(alepha.state(key));
404
+ const [state, setState] = useState(alepha.state.get(key));
320
405
  useEffect(() => {
321
406
  if (!alepha.isBrowser()) return;
322
- return alepha.on("state:mutate", (ev) => {
407
+ return alepha.events.on("state:mutate", (ev) => {
323
408
  if (ev.key === key) setState(ev.value);
324
409
  });
325
410
  }, []);
326
411
  return [state, (value) => {
327
- alepha.state(key, value);
412
+ alepha.state.set(key, value);
328
413
  }];
329
414
  };
330
415
 
@@ -533,8 +618,8 @@ var ReactPageProvider = class {
533
618
  return root;
534
619
  }
535
620
  convertStringObjectToObject = (schema, value) => {
536
- if (TypeGuard.IsObject(schema) && typeof value === "object") {
537
- for (const key in schema.properties) if (TypeGuard.IsObject(schema.properties[key]) && typeof value[key] === "string") try {
621
+ if (t.schema.isObject(schema) && typeof value === "object") {
622
+ for (const key in schema.properties) if (t.schema.isObject(schema.properties[key]) && typeof value[key] === "string") try {
538
623
  value[key] = this.alepha.parse(schema.properties[key], decodeURIComponent(value[key]));
539
624
  } catch (e) {}
540
625
  }
@@ -831,8 +916,8 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
831
916
  meta
832
917
  };
833
918
  const state = entry;
834
- await this.alepha.emit("react:transition:begin", {
835
- previous: this.alepha.state("react.router.state"),
919
+ await this.alepha.events.emit("react:transition:begin", {
920
+ previous: this.alepha.state.get("react.router.state"),
836
921
  state
837
922
  });
838
923
  try {
@@ -851,7 +936,7 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
851
936
  index: 0,
852
937
  path: "/"
853
938
  });
854
- await this.alepha.emit("react:transition:success", { state });
939
+ await this.alepha.events.emit("react:transition:success", { state });
855
940
  } catch (e) {
856
941
  this.log.error("Transition has failed", e);
857
942
  state.layers = [{
@@ -860,7 +945,7 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
860
945
  index: 0,
861
946
  path: "/"
862
947
  }];
863
- await this.alepha.emit("react:transition:error", {
948
+ await this.alepha.events.emit("react:transition:error", {
864
949
  error: e,
865
950
  state
866
951
  });
@@ -869,8 +954,8 @@ var ReactBrowserRouterProvider = class extends RouterProvider {
869
954
  const layer = previous[i];
870
955
  if (state.layers[i]?.name !== layer.name) this.pageApi.page(layer.name)?.onLeave?.();
871
956
  }
872
- this.alepha.state("react.router.state", state);
873
- await this.alepha.emit("react:transition:end", { state });
957
+ this.alepha.state.set("react.router.state", state);
958
+ await this.alepha.events.emit("react:transition:end", { state });
874
959
  }
875
960
  root(state) {
876
961
  return this.pageApi.root(state);
@@ -898,7 +983,7 @@ var ReactBrowserProvider = class {
898
983
  }
899
984
  transitioning;
900
985
  get state() {
901
- return this.alepha.state("react.router.state");
986
+ return this.alepha.state.get("react.router.state");
902
987
  }
903
988
  /**
904
989
  * Accessor for Document DOM API.
@@ -1014,11 +1099,11 @@ var ReactBrowserProvider = class {
1014
1099
  const hydration = this.getHydrationState();
1015
1100
  const previous = hydration?.layers ?? [];
1016
1101
  if (hydration) {
1017
- for (const [key, value] of Object.entries(hydration)) if (key !== "layers") this.alepha.state(key, value);
1102
+ for (const [key, value] of Object.entries(hydration)) if (key !== "layers") this.alepha.state.set(key, value);
1018
1103
  }
1019
1104
  await this.render({ previous });
1020
1105
  const element = this.router.root(this.state);
1021
- await this.alepha.emit("react:browser:render", {
1106
+ await this.alepha.events.emit("react:browser:render", {
1022
1107
  element,
1023
1108
  root: this.getRootElement(),
1024
1109
  hydration,
@@ -1059,7 +1144,7 @@ var ReactRouter = class {
1059
1144
  alepha = $inject(Alepha);
1060
1145
  pageApi = $inject(ReactPageProvider);
1061
1146
  get state() {
1062
- return this.alepha.state("react.router.state");
1147
+ return this.alepha.state.get("react.router.state");
1063
1148
  }
1064
1149
  get pages() {
1065
1150
  return this.pageApi.getPages();
@@ -1245,7 +1330,7 @@ const useQueryParams = (schema, options = {}) => {
1245
1330
  const key = options.key ?? "q";
1246
1331
  const router = useRouter();
1247
1332
  const querystring = router.query[key];
1248
- const [queryParams, setQueryParams] = useState(decode(alepha, schema, router.query[key]));
1333
+ const [queryParams = {}, setQueryParams] = useState(decode(alepha, schema, router.query[key]));
1249
1334
  useEffect(() => {
1250
1335
  setQueryParams(decode(alepha, schema, querystring));
1251
1336
  }, [querystring]);
@@ -1265,8 +1350,8 @@ const encode = (alepha, schema, data) => {
1265
1350
  const decode = (alepha, schema, data) => {
1266
1351
  try {
1267
1352
  return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
1268
- } catch (_error) {
1269
- return {};
1353
+ } catch {
1354
+ return;
1270
1355
  }
1271
1356
  };
1272
1357