alepha 0.15.2 → 0.15.3

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 (132) hide show
  1. package/README.md +68 -80
  2. package/dist/api/audits/index.d.ts +332 -332
  3. package/dist/api/audits/index.d.ts.map +1 -1
  4. package/dist/api/files/index.d.ts +170 -170
  5. package/dist/api/files/index.d.ts.map +1 -1
  6. package/dist/api/jobs/index.d.ts +151 -151
  7. package/dist/api/keys/index.d.ts +195 -195
  8. package/dist/api/keys/index.d.ts.map +1 -1
  9. package/dist/api/parameters/index.d.ts +260 -260
  10. package/dist/api/users/index.d.ts +22 -11
  11. package/dist/api/users/index.d.ts.map +1 -1
  12. package/dist/api/users/index.js +7 -2
  13. package/dist/api/users/index.js.map +1 -1
  14. package/dist/api/verifications/index.d.ts +128 -128
  15. package/dist/api/verifications/index.d.ts.map +1 -1
  16. package/dist/bucket/index.d.ts +8 -0
  17. package/dist/bucket/index.d.ts.map +1 -1
  18. package/dist/bucket/index.js +7 -2
  19. package/dist/bucket/index.js.map +1 -1
  20. package/dist/cli/index.d.ts +191 -74
  21. package/dist/cli/index.d.ts.map +1 -1
  22. package/dist/cli/index.js +215 -48
  23. package/dist/cli/index.js.map +1 -1
  24. package/dist/command/index.d.ts +10 -0
  25. package/dist/command/index.d.ts.map +1 -1
  26. package/dist/command/index.js +67 -13
  27. package/dist/command/index.js.map +1 -1
  28. package/dist/core/index.browser.js +28 -21
  29. package/dist/core/index.browser.js.map +1 -1
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/core/index.js +28 -21
  32. package/dist/core/index.js.map +1 -1
  33. package/dist/core/index.native.js +28 -21
  34. package/dist/core/index.native.js.map +1 -1
  35. package/dist/email/index.d.ts +8 -0
  36. package/dist/email/index.d.ts.map +1 -1
  37. package/dist/email/index.js +7 -2
  38. package/dist/email/index.js.map +1 -1
  39. package/dist/mcp/index.d.ts +5 -5
  40. package/dist/orm/index.bun.js +32 -16
  41. package/dist/orm/index.bun.js.map +1 -1
  42. package/dist/orm/index.d.ts +4 -1
  43. package/dist/orm/index.d.ts.map +1 -1
  44. package/dist/orm/index.js +34 -22
  45. package/dist/orm/index.js.map +1 -1
  46. package/dist/react/router/index.browser.js +9 -15
  47. package/dist/react/router/index.browser.js.map +1 -1
  48. package/dist/react/router/index.d.ts +295 -407
  49. package/dist/react/router/index.d.ts.map +1 -1
  50. package/dist/react/router/index.js +566 -776
  51. package/dist/react/router/index.js.map +1 -1
  52. package/dist/redis/index.d.ts +19 -19
  53. package/dist/security/index.d.ts +42 -42
  54. package/dist/security/index.d.ts.map +1 -1
  55. package/dist/security/index.js +8 -7
  56. package/dist/security/index.js.map +1 -1
  57. package/dist/server/auth/index.d.ts +167 -167
  58. package/dist/server/core/index.d.ts +9 -9
  59. package/dist/server/health/index.d.ts +17 -17
  60. package/dist/server/links/index.d.ts +39 -39
  61. package/dist/server/static/index.js +7 -2
  62. package/dist/server/static/index.js.map +1 -1
  63. package/dist/server/swagger/index.d.ts +8 -0
  64. package/dist/server/swagger/index.d.ts.map +1 -1
  65. package/dist/server/swagger/index.js +7 -2
  66. package/dist/server/swagger/index.js.map +1 -1
  67. package/dist/sms/index.d.ts +8 -0
  68. package/dist/sms/index.d.ts.map +1 -1
  69. package/dist/sms/index.js +7 -2
  70. package/dist/sms/index.js.map +1 -1
  71. package/dist/system/index.browser.js +734 -12
  72. package/dist/system/index.browser.js.map +1 -1
  73. package/dist/system/index.d.ts +8 -0
  74. package/dist/system/index.d.ts.map +1 -1
  75. package/dist/system/index.js +7 -2
  76. package/dist/system/index.js.map +1 -1
  77. package/dist/vite/index.d.ts +1 -1
  78. package/dist/vite/index.js +15 -7
  79. package/dist/vite/index.js.map +1 -1
  80. package/package.json +4 -2
  81. package/src/api/logs/TODO.md +13 -10
  82. package/src/cli/apps/AlephaPackageBuilderCli.ts +9 -0
  83. package/src/cli/atoms/buildOptions.ts +99 -9
  84. package/src/cli/commands/build.ts +149 -32
  85. package/src/cli/commands/db.ts +5 -7
  86. package/src/cli/commands/init.spec.ts +50 -6
  87. package/src/cli/commands/init.ts +28 -5
  88. package/src/cli/providers/ViteDevServerProvider.ts +1 -10
  89. package/src/cli/services/AlephaCliUtils.ts +16 -0
  90. package/src/cli/services/PackageManagerUtils.ts +2 -0
  91. package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
  92. package/src/cli/services/ProjectScaffolder.ts +28 -6
  93. package/src/cli/templates/agentMd.ts +6 -1
  94. package/src/cli/templates/apiAppSecurityTs.ts +11 -0
  95. package/src/cli/templates/apiIndexTs.ts +18 -4
  96. package/src/cli/templates/webAppRouterTs.ts +25 -1
  97. package/src/cli/templates/webHelloComponentTsx.ts +15 -5
  98. package/src/command/helpers/Runner.spec.ts +135 -0
  99. package/src/command/helpers/Runner.ts +4 -1
  100. package/src/command/providers/CliProvider.spec.ts +325 -0
  101. package/src/command/providers/CliProvider.ts +117 -7
  102. package/src/core/Alepha.ts +32 -25
  103. package/src/orm/index.bun.ts +1 -1
  104. package/src/orm/index.ts +2 -6
  105. package/src/orm/providers/drivers/BunSqliteProvider.ts +4 -1
  106. package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
  107. package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
  108. package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
  109. package/src/react/router/hooks/useActive.ts +1 -1
  110. package/src/react/router/hooks/useRouter.ts +1 -1
  111. package/src/react/router/index.ts +4 -0
  112. package/src/react/router/primitives/$page.browser.spec.tsx +24 -24
  113. package/src/react/router/primitives/$page.spec.tsx +0 -32
  114. package/src/react/router/primitives/$page.ts +6 -14
  115. package/src/react/router/providers/ReactBrowserProvider.ts +6 -3
  116. package/src/react/router/providers/ReactPageProvider.ts +1 -1
  117. package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
  118. package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
  119. package/src/react/router/providers/ReactServerProvider.ts +7 -78
  120. package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
  121. package/src/react/router/providers/ReactServerTemplateProvider.ts +228 -665
  122. package/src/react/router/services/ReactRouter.ts +13 -13
  123. package/src/security/__tests__/ServerSecurityProvider.spec.ts +77 -0
  124. package/src/security/providers/ServerSecurityProvider.ts +30 -22
  125. package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
  126. package/src/system/index.browser.ts +25 -0
  127. package/src/system/index.workerd.ts +1 -0
  128. package/src/system/providers/FileSystemProvider.ts +8 -0
  129. package/src/system/providers/NodeFileSystemProvider.ts +11 -2
  130. package/src/vite/tasks/buildServer.ts +2 -12
  131. package/src/vite/tasks/generateCloudflare.ts +10 -7
  132. package/src/vite/tasks/generateDocker.ts +4 -0
@@ -0,0 +1,85 @@
1
+ import { $hook, $inject, Alepha } from "alepha";
2
+ import { SSRManifestProvider } from "./SSRManifestProvider.ts";
3
+
4
+ /**
5
+ * Adds HTTP Link headers for preloading entry assets.
6
+ *
7
+ * Benefits:
8
+ * - Early Hints (103): Servers can send preload hints before the full response
9
+ * - CDN optimization: Many CDNs use Link headers to optimize asset delivery
10
+ * - Browser prefetching: Browsers can start fetching resources earlier
11
+ *
12
+ * The Link header is computed once at first request and cached for reuse.
13
+ */
14
+ export class ReactPreloadProvider {
15
+ protected readonly alepha = $inject(Alepha);
16
+ protected readonly ssrManifest = $inject(SSRManifestProvider);
17
+
18
+ /**
19
+ * Cached Link header value - computed once, reused for all requests.
20
+ */
21
+ protected cachedLinkHeader: string | null | undefined;
22
+
23
+ /**
24
+ * Build the Link header string from entry assets.
25
+ *
26
+ * Format: <url>; rel=preload; as=type, <url>; rel=modulepreload
27
+ *
28
+ * @returns Link header string or null if no assets
29
+ */
30
+ protected buildLinkHeader(): string | null {
31
+ const assets = this.ssrManifest.getEntryAssets();
32
+ if (!assets) return null;
33
+
34
+ const links: string[] = [];
35
+
36
+ // CSS - preload as style
37
+ for (const css of assets.css) {
38
+ links.push(`<${css}>; rel=preload; as=style`);
39
+ }
40
+
41
+ // JS - modulepreload for ES modules
42
+ if (assets.js) {
43
+ links.push(`<${assets.js}>; rel=modulepreload`);
44
+ }
45
+
46
+ return links.length > 0 ? links.join(", ") : null;
47
+ }
48
+
49
+ /**
50
+ * Get the cached Link header, computing it on first access.
51
+ */
52
+ protected getLinkHeader(): string | null {
53
+ if (this.cachedLinkHeader === undefined) {
54
+ this.cachedLinkHeader = this.buildLinkHeader();
55
+ }
56
+ return this.cachedLinkHeader;
57
+ }
58
+
59
+ /**
60
+ * Add Link header to HTML responses for asset preloading.
61
+ */
62
+ protected readonly onResponse = $hook({
63
+ on: "server:onResponse",
64
+ priority: "first",
65
+ handler: ({ response }) => {
66
+ // Only add to HTML responses (SSR pages)
67
+ const contentType = response.headers["content-type"];
68
+ if (!contentType || !contentType.includes("text/html")) {
69
+ return;
70
+ }
71
+
72
+ const linkHeader = this.getLinkHeader();
73
+ if (!linkHeader) {
74
+ return;
75
+ }
76
+
77
+ // Append to existing Link header if present
78
+ if (response.headers.link) {
79
+ response.headers.link = `${response.headers.link}, ${linkHeader}`;
80
+ } else {
81
+ response.headers.link = linkHeader;
82
+ }
83
+ },
84
+ });
85
+ }
@@ -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,13 +140,6 @@ 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
145
  const assets = this.ssrManifestProvider.getEntryAssets();
@@ -189,13 +148,9 @@ export class ReactServerProvider {
189
148
  const parts: string[] = [];
190
149
 
191
150
  if (assets) {
192
- // Add CSS stylesheets (critical for rendering)
193
151
  for (const css of assets.css) {
194
152
  parts.push(`<link rel="stylesheet" href="${css}" crossorigin="">`);
195
153
  }
196
-
197
- // Add entry JS as script module (not just modulepreload)
198
- // This starts download, parse, AND execution immediately
199
154
  if (assets.js) {
200
155
  parts.push(
201
156
  `<script type="module" crossorigin="" src="${assets.js}"></script>`,
@@ -203,11 +158,9 @@ export class ReactServerProvider {
203
158
  }
204
159
  }
205
160
 
206
- // Pass global head so critical meta tags can be injected if missing
207
161
  this.templateProvider.setEarlyHeadContent(
208
162
  parts.length > 0 ? `${parts.join("\n")}\n` : "",
209
163
  globalHead,
210
- assets ?? undefined,
211
164
  );
212
165
 
213
166
  this.log.debug("Early head content set", {
@@ -251,23 +204,10 @@ export class ReactServerProvider {
251
204
  /**
252
205
  * Create the request handler for a page route.
253
206
  */
254
- protected createHandler(
255
- route: PageRoute,
256
- templateLoader: TemplateLoader,
257
- ): ServerHandler {
207
+ protected createHandler(route: PageRoute): ServerHandler {
258
208
  return async (serverRequest) => {
259
209
  const { url, reply, query, params } = serverRequest;
260
210
 
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
211
  this.log.trace("Rendering page", { name: route.name });
272
212
 
273
213
  // Initialize router state
@@ -446,12 +386,6 @@ export class ReactServerProvider {
446
386
 
447
387
  await this.alepha.events.emit("react:server:render:begin", { state });
448
388
 
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
389
  // Use shared rendering logic
456
390
  const result = await this.renderPage(page, state);
457
391
 
@@ -508,10 +442,6 @@ export class ReactServerProvider {
508
442
 
509
443
  // ---------------------------------------------------------------------------------------------------------------------
510
444
 
511
- type TemplateLoader = () => Promise<string | undefined>;
512
-
513
- // ---------------------------------------------------------------------------------------------------------------------
514
-
515
445
  const envSchema = t.object({
516
446
  REACT_SSR_ENABLED: t.optional(t.boolean()),
517
447
  });
@@ -520,7 +450,6 @@ declare module "alepha" {
520
450
  interface Env extends Partial<Static<typeof envSchema>> {}
521
451
  interface State {
522
452
  "alepha.react.server.ssr"?: boolean;
523
- "alepha.react.server.template"?: string;
524
453
  }
525
454
  }
526
455
 
@@ -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
+ });