alepha 0.15.2 → 0.15.4

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.
Files changed (180) hide show
  1. package/README.md +68 -80
  2. package/dist/api/audits/index.d.ts.map +1 -1
  3. package/dist/api/audits/index.js +8 -0
  4. package/dist/api/audits/index.js.map +1 -1
  5. package/dist/api/files/index.d.ts +170 -170
  6. package/dist/api/files/index.d.ts.map +1 -1
  7. package/dist/api/files/index.js +1 -0
  8. package/dist/api/files/index.js.map +1 -1
  9. package/dist/api/jobs/index.d.ts.map +1 -1
  10. package/dist/api/jobs/index.js +3 -0
  11. package/dist/api/jobs/index.js.map +1 -1
  12. package/dist/api/notifications/index.browser.js +1 -0
  13. package/dist/api/notifications/index.browser.js.map +1 -1
  14. package/dist/api/notifications/index.js +1 -0
  15. package/dist/api/notifications/index.js.map +1 -1
  16. package/dist/api/parameters/index.d.ts +260 -260
  17. package/dist/api/parameters/index.d.ts.map +1 -1
  18. package/dist/api/parameters/index.js +10 -0
  19. package/dist/api/parameters/index.js.map +1 -1
  20. package/dist/api/users/index.d.ts +12 -1
  21. package/dist/api/users/index.d.ts.map +1 -1
  22. package/dist/api/users/index.js +18 -2
  23. package/dist/api/users/index.js.map +1 -1
  24. package/dist/batch/index.d.ts +4 -4
  25. package/dist/bucket/index.d.ts +8 -0
  26. package/dist/bucket/index.d.ts.map +1 -1
  27. package/dist/bucket/index.js +7 -2
  28. package/dist/bucket/index.js.map +1 -1
  29. package/dist/cli/index.d.ts +196 -74
  30. package/dist/cli/index.d.ts.map +1 -1
  31. package/dist/cli/index.js +234 -50
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/command/index.d.ts +10 -0
  34. package/dist/command/index.d.ts.map +1 -1
  35. package/dist/command/index.js +67 -13
  36. package/dist/command/index.js.map +1 -1
  37. package/dist/core/index.browser.js +28 -21
  38. package/dist/core/index.browser.js.map +1 -1
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +28 -21
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/core/index.native.js +28 -21
  43. package/dist/core/index.native.js.map +1 -1
  44. package/dist/email/index.d.ts +21 -13
  45. package/dist/email/index.d.ts.map +1 -1
  46. package/dist/email/index.js +10561 -4
  47. package/dist/email/index.js.map +1 -1
  48. package/dist/lock/core/index.d.ts +6 -1
  49. package/dist/lock/core/index.d.ts.map +1 -1
  50. package/dist/lock/core/index.js +9 -1
  51. package/dist/lock/core/index.js.map +1 -1
  52. package/dist/mcp/index.d.ts +5 -5
  53. package/dist/orm/index.bun.js +32 -16
  54. package/dist/orm/index.bun.js.map +1 -1
  55. package/dist/orm/index.d.ts +4 -1
  56. package/dist/orm/index.d.ts.map +1 -1
  57. package/dist/orm/index.js +34 -22
  58. package/dist/orm/index.js.map +1 -1
  59. package/dist/react/auth/index.browser.js +2 -1
  60. package/dist/react/auth/index.browser.js.map +1 -1
  61. package/dist/react/auth/index.js +2 -1
  62. package/dist/react/auth/index.js.map +1 -1
  63. package/dist/react/core/index.d.ts +3 -3
  64. package/dist/react/router/index.browser.js +9 -15
  65. package/dist/react/router/index.browser.js.map +1 -1
  66. package/dist/react/router/index.d.ts +305 -407
  67. package/dist/react/router/index.d.ts.map +1 -1
  68. package/dist/react/router/index.js +581 -781
  69. package/dist/react/router/index.js.map +1 -1
  70. package/dist/scheduler/index.d.ts +13 -1
  71. package/dist/scheduler/index.d.ts.map +1 -1
  72. package/dist/scheduler/index.js +42 -4
  73. package/dist/scheduler/index.js.map +1 -1
  74. package/dist/security/index.d.ts +42 -42
  75. package/dist/security/index.d.ts.map +1 -1
  76. package/dist/security/index.js +8 -7
  77. package/dist/security/index.js.map +1 -1
  78. package/dist/server/auth/index.d.ts +167 -167
  79. package/dist/server/compress/index.d.ts.map +1 -1
  80. package/dist/server/compress/index.js +1 -0
  81. package/dist/server/compress/index.js.map +1 -1
  82. package/dist/server/health/index.d.ts +17 -17
  83. package/dist/server/links/index.d.ts +39 -39
  84. package/dist/server/links/index.js +1 -1
  85. package/dist/server/links/index.js.map +1 -1
  86. package/dist/server/static/index.js +7 -2
  87. package/dist/server/static/index.js.map +1 -1
  88. package/dist/server/swagger/index.d.ts +8 -0
  89. package/dist/server/swagger/index.d.ts.map +1 -1
  90. package/dist/server/swagger/index.js +7 -2
  91. package/dist/server/swagger/index.js.map +1 -1
  92. package/dist/sms/index.d.ts +8 -0
  93. package/dist/sms/index.d.ts.map +1 -1
  94. package/dist/sms/index.js +7 -2
  95. package/dist/sms/index.js.map +1 -1
  96. package/dist/system/index.browser.js +734 -12
  97. package/dist/system/index.browser.js.map +1 -1
  98. package/dist/system/index.d.ts +8 -0
  99. package/dist/system/index.d.ts.map +1 -1
  100. package/dist/system/index.js +7 -2
  101. package/dist/system/index.js.map +1 -1
  102. package/dist/vite/index.d.ts +3 -2
  103. package/dist/vite/index.d.ts.map +1 -1
  104. package/dist/vite/index.js +42 -8
  105. package/dist/vite/index.js.map +1 -1
  106. package/dist/websocket/index.d.ts +34 -34
  107. package/dist/websocket/index.d.ts.map +1 -1
  108. package/package.json +9 -4
  109. package/src/api/audits/controllers/AdminAuditController.ts +8 -0
  110. package/src/api/files/controllers/AdminFileStatsController.ts +1 -0
  111. package/src/api/jobs/controllers/AdminJobController.ts +3 -0
  112. package/src/api/logs/TODO.md +13 -10
  113. package/src/api/notifications/controllers/AdminNotificationController.ts +1 -0
  114. package/src/api/parameters/controllers/AdminConfigController.ts +10 -0
  115. package/src/api/users/controllers/AdminIdentityController.ts +3 -0
  116. package/src/api/users/controllers/AdminSessionController.ts +3 -0
  117. package/src/api/users/controllers/AdminUserController.ts +5 -0
  118. package/src/cli/apps/AlephaPackageBuilderCli.ts +9 -0
  119. package/src/cli/atoms/buildOptions.ts +99 -9
  120. package/src/cli/commands/build.ts +150 -32
  121. package/src/cli/commands/db.ts +5 -7
  122. package/src/cli/commands/init.spec.ts +50 -6
  123. package/src/cli/commands/init.ts +28 -5
  124. package/src/cli/providers/ViteDevServerProvider.ts +31 -9
  125. package/src/cli/services/AlephaCliUtils.ts +16 -0
  126. package/src/cli/services/PackageManagerUtils.ts +2 -0
  127. package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
  128. package/src/cli/services/ProjectScaffolder.ts +28 -6
  129. package/src/cli/templates/agentMd.ts +6 -1
  130. package/src/cli/templates/apiAppSecurityTs.ts +11 -0
  131. package/src/cli/templates/apiIndexTs.ts +18 -4
  132. package/src/cli/templates/webAppRouterTs.ts +25 -1
  133. package/src/cli/templates/webHelloComponentTsx.ts +15 -5
  134. package/src/command/helpers/Runner.spec.ts +135 -0
  135. package/src/command/helpers/Runner.ts +4 -1
  136. package/src/command/providers/CliProvider.spec.ts +325 -0
  137. package/src/command/providers/CliProvider.ts +117 -7
  138. package/src/core/Alepha.ts +32 -25
  139. package/src/email/index.workerd.ts +36 -0
  140. package/src/email/providers/WorkermailerEmailProvider.ts +221 -0
  141. package/src/lock/core/primitives/$lock.ts +13 -1
  142. package/src/orm/index.bun.ts +1 -1
  143. package/src/orm/index.ts +2 -6
  144. package/src/orm/providers/drivers/BunSqliteProvider.ts +4 -1
  145. package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
  146. package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
  147. package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
  148. package/src/react/auth/services/ReactAuth.ts +3 -1
  149. package/src/react/router/atoms/ssrManifestAtom.ts +7 -0
  150. package/src/react/router/hooks/useActive.ts +1 -1
  151. package/src/react/router/hooks/useRouter.ts +1 -1
  152. package/src/react/router/index.ts +4 -0
  153. package/src/react/router/primitives/$page.browser.spec.tsx +24 -24
  154. package/src/react/router/primitives/$page.spec.tsx +0 -32
  155. package/src/react/router/primitives/$page.ts +6 -14
  156. package/src/react/router/providers/ReactBrowserProvider.ts +6 -3
  157. package/src/react/router/providers/ReactPageProvider.ts +1 -1
  158. package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
  159. package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
  160. package/src/react/router/providers/ReactServerProvider.ts +21 -82
  161. package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
  162. package/src/react/router/providers/ReactServerTemplateProvider.ts +228 -665
  163. package/src/react/router/providers/SSRManifestProvider.ts +7 -0
  164. package/src/react/router/services/ReactRouter.ts +13 -13
  165. package/src/scheduler/index.workerd.ts +43 -0
  166. package/src/scheduler/providers/CronProvider.ts +53 -6
  167. package/src/scheduler/providers/WorkerdCronProvider.ts +102 -0
  168. package/src/security/__tests__/ServerSecurityProvider.spec.ts +77 -0
  169. package/src/security/providers/ServerSecurityProvider.ts +30 -22
  170. package/src/server/compress/providers/ServerCompressProvider.ts +6 -0
  171. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
  172. package/src/server/links/providers/ServerLinksProvider.spec.ts +332 -0
  173. package/src/server/links/providers/ServerLinksProvider.ts +1 -1
  174. package/src/system/index.browser.ts +25 -0
  175. package/src/system/index.workerd.ts +1 -0
  176. package/src/system/providers/FileSystemProvider.ts +8 -0
  177. package/src/system/providers/NodeFileSystemProvider.ts +11 -2
  178. package/src/vite/tasks/buildServer.ts +2 -12
  179. package/src/vite/tasks/generateCloudflare.ts +47 -8
  180. package/src/vite/tasks/generateDocker.ts +4 -0
@@ -6,7 +6,6 @@ import {
6
6
  $inject,
7
7
  $use,
8
8
  Alepha,
9
- AlephaError,
10
9
  type Static,
11
10
  t,
12
11
  } from "alepha";
@@ -101,54 +100,21 @@ export class ReactServerProvider {
101
100
  }
102
101
 
103
102
  if (ssrEnabled) {
104
- await this.registerPages(async () => this.template);
103
+ this.registerPages();
105
104
  this.log.info("SSR OK");
106
105
  return;
107
106
  }
108
107
 
109
- // no SSR enabled, serve index.html for all unmatched routes
110
- this.log.info("SSR is disabled, use History API fallback");
111
- this.serverRouterProvider.createRoute({
112
- path: "*",
113
- handler: async ({ url, reply }) => {
114
- if (url.pathname.includes(".")) {
115
- // If the request is for a file (e.g., /style.css), do not fallback
116
- reply.headers["content-type"] = "text/plain";
117
- reply.body = "Not Found";
118
- reply.status = 404;
119
- return;
120
- }
121
-
122
- reply.headers["content-type"] = "text/html";
123
-
124
- // serve index.html for all unmatched routes
125
- return this.template;
126
- },
127
- });
108
+ // no SSR enabled, serve a minimal fallback
109
+ this.log.info("SSR is disabled");
128
110
  },
129
111
  });
130
112
 
131
- /**
132
- * Get the current HTML template.
133
- */
134
- public get template() {
135
- return (
136
- this.alepha.store.get("alepha.react.server.template") ??
137
- "<!DOCTYPE html><html lang='en'><head></head><body><div id='root'></div></body></html>"
138
- );
139
- }
140
-
141
113
  /**
142
114
  * Register all pages as server routes.
143
115
  */
144
- protected async registerPages(templateLoader: TemplateLoader) {
145
- // Parse template once at startup
146
- const template = await templateLoader();
147
- if (template) {
148
- this.templateProvider.parseTemplate(template);
149
- }
150
-
151
- // Set up early head content (entry assets preloads)
116
+ protected registerPages(): void {
117
+ // Set up early head content (entry assets)
152
118
  this.setupEarlyHeadContent();
153
119
 
154
120
  // Cache ServerLinksProvider check at startup
@@ -163,7 +129,7 @@ export class ReactServerProvider {
163
129
  schema: undefined, // schema is handled by the page primitive provider
164
130
  method: "GET",
165
131
  path: page.match,
166
- handler: this.createHandler(page, templateLoader),
132
+ handler: this.createHandler(page),
167
133
  });
168
134
  }
169
135
  }
@@ -174,28 +140,28 @@ export class ReactServerProvider {
174
140
  *
175
141
  * This content is sent immediately when streaming starts, before page loaders run,
176
142
  * allowing the browser to start downloading entry.js and CSS files early.
177
- *
178
- * Uses <script type="module"> instead of <link rel="modulepreload"> for JS
179
- * because the script needs to execute anyway - this way the browser starts
180
- * downloading, parsing, AND will execute as soon as ready.
181
- *
182
- * Also injects critical meta tags (charset, viewport) if not specified in $head,
183
- * and strips these assets from the original template head to avoid duplicates.
184
143
  */
185
144
  protected setupEarlyHeadContent(): void {
186
- const assets = this.ssrManifestProvider.getEntryAssets();
187
145
  const globalHead = this.serverHeadProvider.resolveGlobalHead();
146
+ const manifest = this.ssrManifestProvider.getManifest();
188
147
 
189
- const parts: string[] = [];
148
+ // Dev mode: use pre-transformed head content from Vite
149
+ if (manifest.devHead) {
150
+ this.templateProvider.setEarlyHeadContent(
151
+ `${manifest.devHead}\n`,
152
+ globalHead,
153
+ );
154
+ this.log.debug("Early head content set (dev mode)");
155
+ return;
156
+ }
190
157
 
158
+ // Production: build from SSR manifest entry assets
159
+ const parts: string[] = [];
160
+ const assets = this.ssrManifestProvider.getEntryAssets();
191
161
  if (assets) {
192
- // Add CSS stylesheets (critical for rendering)
193
162
  for (const css of assets.css) {
194
163
  parts.push(`<link rel="stylesheet" href="${css}" crossorigin="">`);
195
164
  }
196
-
197
- // Add entry JS as script module (not just modulepreload)
198
- // This starts download, parse, AND execution immediately
199
165
  if (assets.js) {
200
166
  parts.push(
201
167
  `<script type="module" crossorigin="" src="${assets.js}"></script>`,
@@ -203,16 +169,13 @@ export class ReactServerProvider {
203
169
  }
204
170
  }
205
171
 
206
- // Pass global head so critical meta tags can be injected if missing
207
172
  this.templateProvider.setEarlyHeadContent(
208
173
  parts.length > 0 ? `${parts.join("\n")}\n` : "",
209
174
  globalHead,
210
- assets ?? undefined,
211
175
  );
212
176
 
213
177
  this.log.debug("Early head content set", {
214
- css: assets?.css.length ?? 0,
215
- js: assets?.js ? 1 : 0,
178
+ parts: parts.length,
216
179
  });
217
180
  }
218
181
 
@@ -251,23 +214,10 @@ export class ReactServerProvider {
251
214
  /**
252
215
  * Create the request handler for a page route.
253
216
  */
254
- protected createHandler(
255
- route: PageRoute,
256
- templateLoader: TemplateLoader,
257
- ): ServerHandler {
217
+ protected createHandler(route: PageRoute): ServerHandler {
258
218
  return async (serverRequest) => {
259
219
  const { url, reply, query, params } = serverRequest;
260
220
 
261
- // Ensure template is parsed (handles dev mode where template may change)
262
- if (!this.templateProvider.isReady()) {
263
- const template = await templateLoader();
264
- if (!template) {
265
- throw new AlephaError("Missing template for SSR rendering");
266
- }
267
- this.templateProvider.parseTemplate(template);
268
- this.setupEarlyHeadContent();
269
- }
270
-
271
221
  this.log.trace("Rendering page", { name: route.name });
272
222
 
273
223
  // Initialize router state
@@ -446,12 +396,6 @@ export class ReactServerProvider {
446
396
 
447
397
  await this.alepha.events.emit("react:server:render:begin", { state });
448
398
 
449
- // Ensure template is parsed with early head content (entry.js, CSS)
450
- if (!this.templateProvider.isReady()) {
451
- this.templateProvider.parseTemplate(this.template);
452
- this.setupEarlyHeadContent();
453
- }
454
-
455
399
  // Use shared rendering logic
456
400
  const result = await this.renderPage(page, state);
457
401
 
@@ -508,10 +452,6 @@ export class ReactServerProvider {
508
452
 
509
453
  // ---------------------------------------------------------------------------------------------------------------------
510
454
 
511
- type TemplateLoader = () => Promise<string | undefined>;
512
-
513
- // ---------------------------------------------------------------------------------------------------------------------
514
-
515
455
  const envSchema = t.object({
516
456
  REACT_SSR_ENABLED: t.optional(t.boolean()),
517
457
  });
@@ -520,7 +460,6 @@ declare module "alepha" {
520
460
  interface Env extends Partial<Static<typeof envSchema>> {}
521
461
  interface State {
522
462
  "alepha.react.server.ssr"?: boolean;
523
- "alepha.react.server.template"?: string;
524
463
  }
525
464
  }
526
465
 
@@ -0,0 +1,210 @@
1
+ import { Alepha } from "alepha";
2
+ import { $head } from "alepha/react/head";
3
+ import { HttpClient, ServerProvider } from "alepha/server";
4
+ import { describe, it } from "vitest";
5
+ import { ssrManifestAtom } from "../atoms/ssrManifestAtom.ts";
6
+ import { $page } from "../index.ts";
7
+
8
+ describe("ReactServerTemplateProvider", () => {
9
+ describe("streaming", () => {
10
+ class App {
11
+ head = $head({
12
+ htmlAttributes: { lang: "en" },
13
+ });
14
+
15
+ home = $page({
16
+ path: "/",
17
+ head: {
18
+ title: "Test Page",
19
+ meta: [{ name: "description", content: "Test description" }],
20
+ },
21
+ component: () => "Hello World",
22
+ });
23
+
24
+ withLoader = $page({
25
+ path: "/with-loader",
26
+ head: { title: "Loader Page" },
27
+ loader: async () => ({ data: "loaded" }),
28
+ component: ({ data }) => `Data: ${data}`,
29
+ });
30
+ }
31
+
32
+ it("should stream complete HTML document with correct structure", async ({
33
+ expect,
34
+ }) => {
35
+ const alepha = Alepha.create({
36
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
37
+ }).with(App);
38
+
39
+ await alepha.start();
40
+
41
+ const server = alepha.inject(ServerProvider);
42
+ const http = alepha.inject(HttpClient);
43
+
44
+ const response = await http.fetch(`${server.hostname}/`);
45
+
46
+ // Verify HTML structure
47
+ expect(response.data).toContain("<!DOCTYPE html>");
48
+ expect(response.data).toContain('<html lang="en">');
49
+ expect(response.data).toContain("<head>");
50
+ expect(response.data).toContain('<meta charset="UTF-8">');
51
+ expect(response.data).toContain('<meta name="viewport"');
52
+ expect(response.data).toContain("<title>Test Page</title>");
53
+ expect(response.data).toContain(
54
+ '<meta name="description" content="Test description">',
55
+ );
56
+ expect(response.data).toContain("</head>");
57
+ expect(response.data).toContain("<body>");
58
+ expect(response.data).toContain('<div id="root">');
59
+ expect(response.data).toContain("Hello World");
60
+ expect(response.data).toContain("</div>");
61
+ expect(response.data).toContain("</body>");
62
+ expect(response.data).toContain("</html>");
63
+
64
+ await alepha.stop();
65
+ });
66
+
67
+ it("should include hydration data when enabled", async ({ expect }) => {
68
+ const alepha = Alepha.create({
69
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
70
+ }).with(App);
71
+
72
+ await alepha.start();
73
+
74
+ const server = alepha.inject(ServerProvider);
75
+ const http = alepha.inject(HttpClient);
76
+
77
+ const response = await http.fetch(`${server.hostname}/with-loader`);
78
+
79
+ // Verify hydration script is present
80
+ expect(response.data).toContain("<script>window.__ssr=");
81
+ expect(response.data).toContain("</script>");
82
+
83
+ // Verify hydration data structure
84
+ expect(response.data).toMatch(/window\.__ssr=\{.*"layers".*\}/);
85
+
86
+ await alepha.stop();
87
+ });
88
+
89
+ it("should include entry assets in head when manifest is available", async ({
90
+ expect,
91
+ }) => {
92
+ const alepha = Alepha.create({
93
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
94
+ }).with(App);
95
+
96
+ // Set up mock SSR manifest
97
+ alepha.store.set(ssrManifestAtom, {
98
+ client: {
99
+ "src/entry.tsx": {
100
+ file: "assets/entry.abc123.js",
101
+ isEntry: true,
102
+ css: ["assets/style.def456.css"],
103
+ },
104
+ },
105
+ });
106
+
107
+ await alepha.start();
108
+
109
+ const server = alepha.inject(ServerProvider);
110
+ const http = alepha.inject(HttpClient);
111
+
112
+ const response = await http.fetch(`${server.hostname}/`);
113
+
114
+ // Verify entry assets are in the head
115
+ expect(response.data).toContain(
116
+ '<link rel="stylesheet" href="/assets/style.def456.css" crossorigin="">',
117
+ );
118
+ expect(response.data).toContain(
119
+ '<script type="module" crossorigin="" src="/assets/entry.abc123.js"></script>',
120
+ );
121
+
122
+ await alepha.stop();
123
+ });
124
+
125
+ it("should handle pages with loaders correctly", async ({ expect }) => {
126
+ const alepha = Alepha.create({
127
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
128
+ }).with(App);
129
+
130
+ await alepha.start();
131
+
132
+ const server = alepha.inject(ServerProvider);
133
+ const http = alepha.inject(HttpClient);
134
+
135
+ const response = await http.fetch(`${server.hostname}/with-loader`);
136
+
137
+ // Verify loader data is rendered
138
+ expect(response.data).toContain("Data: loaded");
139
+
140
+ await alepha.stop();
141
+ });
142
+
143
+ it("should set correct content-type header", async ({ expect }) => {
144
+ const alepha = Alepha.create({
145
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
146
+ }).with(App);
147
+
148
+ await alepha.start();
149
+
150
+ const server = alepha.inject(ServerProvider);
151
+ const http = alepha.inject(HttpClient);
152
+
153
+ const response = await http.fetch(`${server.hostname}/`);
154
+
155
+ expect(response.headers.get("content-type")).toBe("text/html");
156
+
157
+ await alepha.stop();
158
+ });
159
+
160
+ it("should set cache-control headers for SSR responses", async ({
161
+ expect,
162
+ }) => {
163
+ const alepha = Alepha.create({
164
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
165
+ }).with(App);
166
+
167
+ await alepha.start();
168
+
169
+ const server = alepha.inject(ServerProvider);
170
+ const http = alepha.inject(HttpClient);
171
+
172
+ const response = await http.fetch(`${server.hostname}/`);
173
+
174
+ expect(response.headers.get("cache-control")).toContain("no-store");
175
+
176
+ await alepha.stop();
177
+ });
178
+ });
179
+
180
+ describe("error handling", () => {
181
+ class ErrorApp {
182
+ errorPage = $page({
183
+ path: "/error",
184
+ loader: async () => {
185
+ throw new Error("Loader error");
186
+ },
187
+ component: () => "Should not render",
188
+ });
189
+ }
190
+
191
+ it("should render error when loader throws", async ({ expect }) => {
192
+ const alepha = Alepha.create({
193
+ env: { LOG_LEVEL: "error", SERVER_PORT: 0 },
194
+ }).with(ErrorApp);
195
+
196
+ await alepha.start();
197
+
198
+ const server = alepha.inject(ServerProvider);
199
+ const http = alepha.inject(HttpClient);
200
+
201
+ const response = await http.fetch(`${server.hostname}/error`);
202
+
203
+ // Should still return a valid HTML response with error
204
+ expect(response.data).toContain("<!DOCTYPE html>");
205
+ expect(response.data).toContain("Loader error");
206
+
207
+ await alepha.stop();
208
+ });
209
+ });
210
+ });