@alepha/react 0.11.5 → 0.11.7

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@alepha/react",
3
3
  "description": "Build server-side rendered (SSR) or single-page React applications.",
4
- "version": "0.11.5",
4
+ "version": "0.11.7",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=22.0.0"
@@ -17,25 +17,25 @@
17
17
  "src"
18
18
  ],
19
19
  "dependencies": {
20
- "@alepha/core": "0.11.5",
21
- "@alepha/datetime": "0.11.5",
22
- "@alepha/logger": "0.11.5",
23
- "@alepha/router": "0.11.5",
24
- "@alepha/server": "0.11.5",
25
- "@alepha/server-cache": "0.11.5",
26
- "@alepha/server-links": "0.11.5",
27
- "@alepha/server-static": "0.11.5"
20
+ "@alepha/core": "0.11.7",
21
+ "@alepha/datetime": "0.11.7",
22
+ "@alepha/logger": "0.11.7",
23
+ "@alepha/router": "0.11.7",
24
+ "@alepha/server": "0.11.7",
25
+ "@alepha/server-cache": "0.11.7",
26
+ "@alepha/server-links": "0.11.7",
27
+ "@alepha/server-static": "0.11.7"
28
28
  },
29
29
  "devDependencies": {
30
- "@alepha/testing": "0.11.5",
31
- "@biomejs/biome": "^2.3.3",
32
- "@types/react": "^19.2.2",
33
- "@types/react-dom": "^19.2.2",
30
+ "@alepha/testing": "0.11.7",
31
+ "@biomejs/biome": "^2.3.5",
32
+ "@types/react": "^19.2.4",
33
+ "@types/react-dom": "^19.2.3",
34
34
  "react": "^19.2.0",
35
35
  "react-dom": "^19.2.0",
36
- "tsdown": "^0.16.0",
36
+ "tsdown": "^0.16.4",
37
37
  "typescript": "^5.9.3",
38
- "vitest": "^4.0.6"
38
+ "vitest": "^4.0.8"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": "*",
@@ -297,7 +297,7 @@ export function useAction<Args extends any[], Result = void>(
297
297
  if (options.runOnInit) {
298
298
  handler(...([] as any));
299
299
  }
300
- }, []);
300
+ }, deps);
301
301
 
302
302
  // Run action periodically if runEvery is specified
303
303
  useEffect(() => {
@@ -3,7 +3,7 @@ import type { ReactRouterState } from "../providers/ReactPageProvider.ts";
3
3
  import { useStore } from "./useStore.ts";
4
4
 
5
5
  export const useRouterState = (): ReactRouterState => {
6
- const [state] = useStore("react.router.state");
6
+ const [state] = useStore("alepha.react.router.state");
7
7
  if (!state) {
8
8
  throw new AlephaError("Missing react router state");
9
9
  }
@@ -1,29 +1,37 @@
1
- import type { State } from "@alepha/core";
1
+ import type { State, Static, TAtomObject } from "@alepha/core";
2
+ import { Atom } from "@alepha/core";
2
3
  import { useEffect, useMemo, useState } from "react";
3
4
  import { useAlepha } from "./useAlepha.ts";
4
5
 
5
6
  /**
6
7
  * Hook to access and mutate the Alepha state.
7
8
  */
8
- export const useStore = <Key extends keyof State>(
9
- key: Key,
9
+ function useStore<T extends TAtomObject>(
10
+ target: Atom<T>,
11
+ defaultValue?: Static<T>,
12
+ ): UseStoreReturn<Static<T>>;
13
+ function useStore<Key extends keyof State>(
14
+ target: Key,
10
15
  defaultValue?: State[Key],
11
- ): [State[Key], (value: State[Key]) => void] => {
16
+ ): UseStoreReturn<State[Key]>;
17
+ function useStore(target: any, defaultValue?: any): any {
12
18
  const alepha = useAlepha();
13
19
 
14
20
  useMemo(() => {
15
- if (defaultValue != null && alepha.state.get(key) == null) {
16
- alepha.state.set(key, defaultValue);
21
+ if (defaultValue != null && alepha.state.get(target) == null) {
22
+ alepha.state.set(target, defaultValue);
17
23
  }
18
24
  }, [defaultValue]);
19
25
 
20
- const [state, setState] = useState(alepha.state.get(key));
26
+ const [state, setState] = useState(alepha.state.get(target));
21
27
 
22
28
  useEffect(() => {
23
29
  if (!alepha.isBrowser()) {
24
30
  return;
25
31
  }
26
32
 
33
+ const key = target instanceof Atom ? target.key : target;
34
+
27
35
  return alepha.events.on("state:mutate", (ev) => {
28
36
  if (ev.key === key) {
29
37
  setState(ev.value);
@@ -33,8 +41,12 @@ export const useStore = <Key extends keyof State>(
33
41
 
34
42
  return [
35
43
  state,
36
- (value: State[Key]) => {
37
- alepha.state.set(key, value);
44
+ (value: any) => {
45
+ alepha.state.set(target, value);
38
46
  },
39
47
  ] as const;
40
- };
48
+ }
49
+
50
+ export type UseStoreReturn<T> = [T, (value: T) => void];
51
+
52
+ export { useStore };
package/src/index.ts CHANGED
@@ -26,7 +26,7 @@ export * from "./providers/ReactServerProvider.ts";
26
26
 
27
27
  declare module "@alepha/core" {
28
28
  interface State {
29
- "react.router.state"?: ReactRouterState;
29
+ "alepha.react.router.state"?: ReactRouterState;
30
30
  }
31
31
 
32
32
  interface Hooks {
@@ -1,7 +1,9 @@
1
1
  import {
2
+ $atom,
2
3
  $env,
3
4
  $hook,
4
5
  $inject,
6
+ $use,
5
7
  Alepha,
6
8
  type State,
7
9
  type Static,
@@ -17,6 +19,8 @@ import type {
17
19
  TransitionOptions,
18
20
  } from "./ReactPageProvider.ts";
19
21
 
22
+ // ---------------------------------------------------------------------------------------------------------------------
23
+
20
24
  const envSchema = t.object({
21
25
  REACT_ROOT_ID: t.text({ default: "root" }),
22
26
  });
@@ -25,10 +29,31 @@ declare module "@alepha/core" {
25
29
  interface Env extends Partial<Static<typeof envSchema>> {}
26
30
  }
27
31
 
28
- export interface ReactBrowserRendererOptions {
29
- scrollRestoration?: "top" | "manual";
32
+ /**
33
+ * React browser renderer configuration atom
34
+ */
35
+ export const reactBrowserOptions = $atom({
36
+ name: "alepha.react.browser.options",
37
+ schema: t.object({
38
+ scrollRestoration: t.enum(["top", "manual"]),
39
+ }),
40
+ default: {
41
+ scrollRestoration: "top" as const,
42
+ },
43
+ });
44
+
45
+ export type ReactBrowserRendererOptions = Static<
46
+ typeof reactBrowserOptions.schema
47
+ >;
48
+
49
+ declare module "@alepha/core" {
50
+ interface State {
51
+ [reactBrowserOptions.key]: ReactBrowserRendererOptions;
52
+ }
30
53
  }
31
54
 
55
+ // ---------------------------------------------------------------------------------------------------------------------
56
+
32
57
  export class ReactBrowserProvider {
33
58
  protected readonly env = $env(envSchema);
34
59
  protected readonly log = $logger();
@@ -37,9 +62,7 @@ export class ReactBrowserProvider {
37
62
  protected readonly router = $inject(ReactBrowserRouterProvider);
38
63
  protected readonly dateTimeProvider = $inject(DateTimeProvider);
39
64
 
40
- public options: ReactBrowserRendererOptions = {
41
- scrollRestoration: "top",
42
- };
65
+ protected readonly options = $use(reactBrowserOptions);
43
66
 
44
67
  protected getRootElement() {
45
68
  const root = this.document.getElementById(this.env.REACT_ROOT_ID);
@@ -61,7 +84,7 @@ export class ReactBrowserProvider {
61
84
  };
62
85
 
63
86
  public get state(): ReactRouterState {
64
- return this.alepha.state.get("react.router.state")!;
87
+ return this.alepha.state.get("alepha.react.router.state")!;
65
88
  }
66
89
 
67
90
  /**
@@ -63,7 +63,7 @@ export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
63
63
  type: "transition",
64
64
  });
65
65
  await this.alepha.events.emit("react:transition:begin", {
66
- previous: this.alepha.state.get("react.router.state")!,
66
+ previous: this.alepha.state.get("alepha.react.router.state")!,
67
67
  state,
68
68
  });
69
69
 
@@ -135,7 +135,7 @@ export class ReactBrowserRouterProvider extends RouterProvider<BrowserRoute> {
135
135
  }
136
136
  }
137
137
 
138
- this.alepha.state.set("react.router.state", state);
138
+ this.alepha.state.set("alepha.react.router.state", state);
139
139
 
140
140
  await this.alepha.events.emit("react:action:end", {
141
141
  type: "transition",
@@ -1,12 +1,13 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import {
4
+ $atom,
4
5
  $env,
5
6
  $hook,
6
7
  $inject,
8
+ $use,
7
9
  Alepha,
8
10
  AlephaError,
9
- type Configurable,
10
11
  type Static,
11
12
  t,
12
13
  } from "@alepha/core";
@@ -18,10 +19,7 @@ import {
18
19
  ServerTimingProvider,
19
20
  } from "@alepha/server";
20
21
  import { ServerLinksProvider } from "@alepha/server-links";
21
- import {
22
- type ServeDescriptorOptions,
23
- ServerStaticProvider,
24
- } from "@alepha/server-static";
22
+ import { ServerStaticProvider } from "@alepha/server-static";
25
23
  import { renderToString } from "react-dom/server";
26
24
  import {
27
25
  $page,
@@ -36,9 +34,9 @@ import {
36
34
  type ReactRouterState,
37
35
  } from "./ReactPageProvider.ts";
38
36
 
37
+ // ---------------------------------------------------------------------------------------------------------------------
38
+
39
39
  const envSchema = t.object({
40
- REACT_SERVER_DIST: t.text({ default: "public" }),
41
- REACT_SERVER_PREFIX: t.text({ default: "" }),
42
40
  REACT_SSR_ENABLED: t.optional(t.boolean()),
43
41
  REACT_ROOT_ID: t.text({ default: "root" }), // TODO: move to ReactPageProvider.options?
44
42
  REACT_SERVER_TEMPLATE: t.optional(
@@ -51,19 +49,46 @@ const envSchema = t.object({
51
49
  declare module "@alepha/core" {
52
50
  interface Env extends Partial<Static<typeof envSchema>> {}
53
51
  interface State {
54
- "react.server.ssr"?: boolean;
52
+ "alepha.react.server.ssr"?: boolean;
55
53
  }
56
54
  }
57
55
 
58
- export interface ReactServerProviderOptions {
59
- /**
60
- * Override default options for the static file server.
61
- * > Static file server is only created in non-serverless production mode.
62
- */
63
- static?: Partial<Omit<ServeDescriptorOptions, "root">>;
56
+ /**
57
+ * React server provider configuration atom
58
+ */
59
+ export const reactServerOptions = $atom({
60
+ name: "alepha.react.server.options",
61
+ schema: t.object({
62
+ publicDir: t.string(),
63
+ staticServer: t.object({
64
+ disabled: t.boolean(),
65
+ path: t.string({
66
+ description: "URL path where static files will be served.",
67
+ }),
68
+ }),
69
+ }),
70
+ default: {
71
+ publicDir: "public",
72
+ staticServer: {
73
+ disabled: false,
74
+ path: "/",
75
+ },
76
+ },
77
+ });
78
+
79
+ export type ReactServerProviderOptions = Static<
80
+ typeof reactServerOptions.schema
81
+ >;
82
+
83
+ declare module "@alepha/core" {
84
+ interface State {
85
+ [reactServerOptions.key]: ReactServerProviderOptions;
86
+ }
64
87
  }
65
88
 
66
- export class ReactServerProvider implements Configurable {
89
+ // ---------------------------------------------------------------------------------------------------------------------
90
+
91
+ export class ReactServerProvider {
67
92
  protected readonly log = $logger();
68
93
  protected readonly alepha = $inject(Alepha);
69
94
  protected readonly env = $env(envSchema);
@@ -79,7 +104,7 @@ export class ReactServerProvider implements Configurable {
79
104
  );
80
105
  protected preprocessedTemplate: PreprocessedTemplate | null = null;
81
106
 
82
- public options: ReactServerProviderOptions = {};
107
+ protected readonly options = $use(reactServerOptions);
83
108
 
84
109
  /**
85
110
  * Configure the React server provider.
@@ -92,7 +117,7 @@ export class ReactServerProvider implements Configurable {
92
117
  const ssrEnabled =
93
118
  pages.length > 0 && this.env.REACT_SSR_ENABLED !== false;
94
119
 
95
- this.alepha.state.set("react.server.ssr", ssrEnabled);
120
+ this.alepha.state.set("alepha.react.server.ssr", ssrEnabled);
96
121
 
97
122
  // development mode
98
123
  if (this.alepha.isViteDev()) {
@@ -180,8 +205,8 @@ export class ReactServerProvider implements Configurable {
180
205
  */
181
206
  protected getPublicDirectory(): string {
182
207
  const maybe = [
183
- join(process.cwd(), `dist/${this.env.REACT_SERVER_DIST}`),
184
- join(process.cwd(), this.env.REACT_SERVER_DIST),
208
+ join(process.cwd(), `dist/${this.options.publicDir}`),
209
+ join(process.cwd(), this.options.publicDir),
185
210
  ];
186
211
 
187
212
  for (const it of maybe) {
@@ -199,12 +224,11 @@ export class ReactServerProvider implements Configurable {
199
224
  protected async configureStaticServer(root: string) {
200
225
  await this.serverStaticProvider.createStaticServer({
201
226
  root,
202
- path: this.env.REACT_SERVER_PREFIX,
203
227
  cacheControl: {
204
228
  maxAge: 3600,
205
229
  immutable: true,
206
230
  },
207
- ...this.options.static,
231
+ ...this.options.staticServer,
208
232
  });
209
233
  }
210
234
 
@@ -217,7 +241,7 @@ export class ReactServerProvider implements Configurable {
217
241
  return;
218
242
  }
219
243
 
220
- this.log.info("SSR (vite) OK");
244
+ this.log.info("SSR (dev) OK");
221
245
 
222
246
  const url = `http://${process.env.SERVER_HOST}:${process.env.SERVER_PORT}`;
223
247
 
@@ -265,7 +289,7 @@ export class ReactServerProvider implements Configurable {
265
289
  }
266
290
 
267
291
  if (!options.html) {
268
- this.alepha.state.set("react.router.state", state);
292
+ this.alepha.state.set("alepha.react.router.state", state);
269
293
 
270
294
  return {
271
295
  state,
@@ -317,7 +341,7 @@ export class ReactServerProvider implements Configurable {
317
341
 
318
342
  if (this.alepha.has(ServerLinksProvider)) {
319
343
  this.alepha.state.set(
320
- "api",
344
+ "alepha.server.request.apiLinks",
321
345
  await this.alepha.inject(ServerLinksProvider).getUserApiLinks({
322
346
  user: (serverRequest as any).user, // TODO: fix type
323
347
  authorization: serverRequest.headers.authorization,
@@ -407,7 +431,7 @@ export class ReactServerProvider implements Configurable {
407
431
  const element = this.pageApi.root(state);
408
432
 
409
433
  // attach react router state to the http request context
410
- this.alepha.state.set("react.router.state", state);
434
+ this.alepha.state.set("alepha.react.router.state", state);
411
435
 
412
436
  this.serverTimingProvider.beginTiming("renderToString");
413
437
  let app = "";
@@ -440,7 +464,7 @@ export class ReactServerProvider implements Configurable {
440
464
  const hydrationData: ReactHydrationState = {
441
465
  ...store,
442
466
  // map react.router.state to the hydration state
443
- "react.router.state": undefined,
467
+ "alepha.react.router.state": undefined,
444
468
  layers: state.layers.map((it) => ({
445
469
  ...it,
446
470
  error: it.error
@@ -15,7 +15,7 @@ export class ReactRouter<T extends object> {
15
15
  protected readonly pageApi = $inject(ReactPageProvider);
16
16
 
17
17
  public get state(): ReactRouterState {
18
- return this.alepha.state.get("react.router.state")!;
18
+ return this.alepha.state.get("alepha.react.router.state")!;
19
19
  }
20
20
 
21
21
  public get pages() {