@alepha/react 0.10.7 → 0.11.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/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.10.7",
4
+ "version": "0.11.0",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=22.0.0"
@@ -17,22 +17,22 @@
17
17
  "src"
18
18
  ],
19
19
  "dependencies": {
20
- "@alepha/core": "0.10.7",
21
- "@alepha/datetime": "0.10.7",
22
- "@alepha/logger": "0.10.7",
23
- "@alepha/router": "0.10.7",
24
- "@alepha/server": "0.10.7",
25
- "@alepha/server-cache": "0.10.7",
26
- "@alepha/server-links": "0.10.7",
27
- "@alepha/server-static": "0.10.7"
20
+ "@alepha/core": "0.11.0",
21
+ "@alepha/datetime": "0.11.0",
22
+ "@alepha/logger": "0.11.0",
23
+ "@alepha/router": "0.11.0",
24
+ "@alepha/server": "0.11.0",
25
+ "@alepha/server-cache": "0.11.0",
26
+ "@alepha/server-links": "0.11.0",
27
+ "@alepha/server-static": "0.11.0"
28
28
  },
29
29
  "devDependencies": {
30
- "@biomejs/biome": "^2.2.6",
30
+ "@biomejs/biome": "^2.3.2",
31
31
  "@types/react": "^19.2.2",
32
32
  "@types/react-dom": "^19.2.2",
33
33
  "react": "^19.2.0",
34
34
  "react-dom": "^19.2.0",
35
- "tsdown": "^0.15.9",
35
+ "tsdown": "^0.15.11",
36
36
  "typescript": "^5.9.3",
37
37
  "vitest": "^3.2.4"
38
38
  },
@@ -44,7 +44,7 @@ export interface UseQueryParamsHookOptions {
44
44
  }
45
45
 
46
46
  const encode = (alepha: Alepha, schema: TObject, data: any) => {
47
- return btoa(JSON.stringify(alepha.parse(schema, data)));
47
+ return btoa(JSON.stringify(alepha.codec.decode(schema, data)));
48
48
  };
49
49
 
50
50
  const decode = <T extends TObject>(
@@ -53,7 +53,10 @@ const decode = <T extends TObject>(
53
53
  data: any,
54
54
  ): Static<T> | undefined => {
55
55
  try {
56
- return alepha.parse(schema, JSON.parse(atob(decodeURIComponent(data))));
56
+ return alepha.codec.decode(
57
+ schema,
58
+ JSON.parse(atob(decodeURIComponent(data))),
59
+ );
57
60
  } catch {
58
61
  return;
59
62
  }
@@ -25,11 +25,11 @@ export const useSchema = <TConfig extends RequestConfigSchema>(
25
25
  }
26
26
 
27
27
  const opts: FetchOptions = {
28
- cache: true,
28
+ localCache: true,
29
29
  };
30
30
 
31
31
  httpClient
32
- .fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, {}, opts)
32
+ .fetch(`${LinkProvider.path.apiLinks}/${name}/schema`, opts)
33
33
  .then((it) => setSchema(it.data as UseSchemaReturn<TConfig>));
34
34
  }, [name]);
35
35
 
@@ -119,7 +119,7 @@ export class ReactPageProvider {
119
119
  typeof value[key] === "string"
120
120
  ) {
121
121
  try {
122
- value[key] = this.alepha.parse(
122
+ value[key] = this.alepha.codec.decode(
123
123
  schema.properties[key],
124
124
  decodeURIComponent(value[key]),
125
125
  );
@@ -161,7 +161,7 @@ export class ReactPageProvider {
161
161
  try {
162
162
  this.convertStringObjectToObject(route.schema?.query, state.query);
163
163
  config.query = route.schema?.query
164
- ? this.alepha.parse(route.schema.query, state.query)
164
+ ? this.alepha.codec.decode(route.schema.query, state.query)
165
165
  : {};
166
166
  } catch (e) {
167
167
  it.error = e as Error;
@@ -170,7 +170,7 @@ export class ReactPageProvider {
170
170
 
171
171
  try {
172
172
  config.params = route.schema?.params
173
- ? this.alepha.parse(route.schema.params, state.params)
173
+ ? this.alepha.codec.decode(route.schema.params, state.params)
174
174
  : {};
175
175
  } catch (e) {
176
176
  it.error = e as Error;
@@ -6,6 +6,7 @@ import {
6
6
  $inject,
7
7
  Alepha,
8
8
  AlephaError,
9
+ type Configurable,
9
10
  type Static,
10
11
  t,
11
12
  } from "@alepha/core";
@@ -17,7 +18,10 @@ import {
17
18
  ServerTimingProvider,
18
19
  } from "@alepha/server";
19
20
  import { ServerLinksProvider } from "@alepha/server-links";
20
- import { ServerStaticProvider } from "@alepha/server-static";
21
+ import {
22
+ type ServeDescriptorOptions,
23
+ ServerStaticProvider,
24
+ } from "@alepha/server-static";
21
25
  import { renderToString } from "react-dom/server";
22
26
  import {
23
27
  $page,
@@ -36,7 +40,7 @@ const envSchema = t.object({
36
40
  REACT_SERVER_DIST: t.text({ default: "public" }),
37
41
  REACT_SERVER_PREFIX: t.text({ default: "" }),
38
42
  REACT_SSR_ENABLED: t.optional(t.boolean()),
39
- REACT_ROOT_ID: t.text({ default: "root" }),
43
+ REACT_ROOT_ID: t.text({ default: "root" }), // TODO: move to ReactPageProvider.options?
40
44
  REACT_SERVER_TEMPLATE: t.optional(
41
45
  t.text({
42
46
  size: "rich",
@@ -51,21 +55,35 @@ declare module "@alepha/core" {
51
55
  }
52
56
  }
53
57
 
54
- export class ReactServerProvider {
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">>;
64
+ }
65
+
66
+ export class ReactServerProvider implements Configurable {
55
67
  protected readonly log = $logger();
56
68
  protected readonly alepha = $inject(Alepha);
69
+ protected readonly env = $env(envSchema);
57
70
  protected readonly pageApi = $inject(ReactPageProvider);
58
71
  protected readonly serverProvider = $inject(ServerProvider);
59
72
  protected readonly serverStaticProvider = $inject(ServerStaticProvider);
60
73
  protected readonly serverRouterProvider = $inject(ServerRouterProvider);
61
74
  protected readonly serverTimingProvider = $inject(ServerTimingProvider);
62
- protected readonly env = $env(envSchema);
75
+
63
76
  protected readonly ROOT_DIV_REGEX = new RegExp(
64
77
  `<div([^>]*)\\s+id=["']${this.env.REACT_ROOT_ID}["']([^>]*)>(.*?)<\\/div>`,
65
78
  "is",
66
79
  );
67
80
  protected preprocessedTemplate: PreprocessedTemplate | null = null;
68
81
 
82
+ public options: ReactServerProviderOptions = {};
83
+
84
+ /**
85
+ * Configure the React server provider.
86
+ */
69
87
  public readonly onConfigure = $hook({
70
88
  on: "configure",
71
89
  handler: async () => {
@@ -174,6 +192,9 @@ export class ReactServerProvider {
174
192
  }
175
193
  }
176
194
 
195
+ /**
196
+ * Get the public directory path where static files are located.
197
+ */
177
198
  protected getPublicDirectory(): string {
178
199
  const maybe = [
179
200
  join(process.cwd(), `dist/${this.env.REACT_SERVER_DIST}`),
@@ -189,13 +210,24 @@ export class ReactServerProvider {
189
210
  return "";
190
211
  }
191
212
 
213
+ /**
214
+ * Configure the static file server to serve files from the given root directory.
215
+ */
192
216
  protected async configureStaticServer(root: string) {
193
217
  await this.serverStaticProvider.createStaticServer({
194
218
  root,
195
219
  path: this.env.REACT_SERVER_PREFIX,
220
+ cacheControl: {
221
+ maxAge: 3600,
222
+ immutable: true,
223
+ },
224
+ ...this.options.static,
196
225
  });
197
226
  }
198
227
 
228
+ /**
229
+ * Configure Vite for SSR.
230
+ */
199
231
  protected async configureVite(ssrEnabled: boolean) {
200
232
  if (!ssrEnabled) {
201
233
  // do nothing, vite will handle everything for us
@@ -286,7 +318,7 @@ export class ReactServerProvider {
286
318
  const { url, reply, query, params } = serverRequest;
287
319
  const template = await templateLoader();
288
320
  if (!template) {
289
- throw new Error("Template not found");
321
+ throw new AlephaError("Template not found");
290
322
  }
291
323
 
292
324
  this.log.trace("Rendering page", {