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.
- package/README.md +68 -80
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js +8 -0
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +170 -170
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js +1 -0
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +3 -0
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.browser.js +1 -0
- package/dist/api/notifications/index.browser.js.map +1 -1
- package/dist/api/notifications/index.js +1 -0
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/parameters/index.d.ts +260 -260
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js +10 -0
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +12 -1
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +18 -2
- package/dist/api/users/index.js.map +1 -1
- package/dist/batch/index.d.ts +4 -4
- package/dist/bucket/index.d.ts +8 -0
- package/dist/bucket/index.d.ts.map +1 -1
- package/dist/bucket/index.js +7 -2
- package/dist/bucket/index.js.map +1 -1
- package/dist/cli/index.d.ts +196 -74
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +234 -50
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +10 -0
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +67 -13
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js +28 -21
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +28 -21
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +28 -21
- package/dist/core/index.native.js.map +1 -1
- package/dist/email/index.d.ts +21 -13
- package/dist/email/index.d.ts.map +1 -1
- package/dist/email/index.js +10561 -4
- package/dist/email/index.js.map +1 -1
- package/dist/lock/core/index.d.ts +6 -1
- package/dist/lock/core/index.d.ts.map +1 -1
- package/dist/lock/core/index.js +9 -1
- package/dist/lock/core/index.js.map +1 -1
- package/dist/mcp/index.d.ts +5 -5
- package/dist/orm/index.bun.js +32 -16
- package/dist/orm/index.bun.js.map +1 -1
- package/dist/orm/index.d.ts +4 -1
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +34 -22
- package/dist/orm/index.js.map +1 -1
- package/dist/react/auth/index.browser.js +2 -1
- package/dist/react/auth/index.browser.js.map +1 -1
- package/dist/react/auth/index.js +2 -1
- package/dist/react/auth/index.js.map +1 -1
- package/dist/react/core/index.d.ts +3 -3
- package/dist/react/router/index.browser.js +9 -15
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +305 -407
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +581 -781
- package/dist/react/router/index.js.map +1 -1
- package/dist/scheduler/index.d.ts +13 -1
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +42 -4
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts +42 -42
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +8 -7
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +167 -167
- package/dist/server/compress/index.d.ts.map +1 -1
- package/dist/server/compress/index.js +1 -0
- package/dist/server/compress/index.js.map +1 -1
- package/dist/server/health/index.d.ts +17 -17
- package/dist/server/links/index.d.ts +39 -39
- package/dist/server/links/index.js +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/static/index.js +7 -2
- package/dist/server/static/index.js.map +1 -1
- package/dist/server/swagger/index.d.ts +8 -0
- package/dist/server/swagger/index.d.ts.map +1 -1
- package/dist/server/swagger/index.js +7 -2
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/sms/index.d.ts +8 -0
- package/dist/sms/index.d.ts.map +1 -1
- package/dist/sms/index.js +7 -2
- package/dist/sms/index.js.map +1 -1
- package/dist/system/index.browser.js +734 -12
- package/dist/system/index.browser.js.map +1 -1
- package/dist/system/index.d.ts +8 -0
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +7 -2
- package/dist/system/index.js.map +1 -1
- package/dist/vite/index.d.ts +3 -2
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +42 -8
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.d.ts +34 -34
- package/dist/websocket/index.d.ts.map +1 -1
- package/package.json +9 -4
- package/src/api/audits/controllers/AdminAuditController.ts +8 -0
- package/src/api/files/controllers/AdminFileStatsController.ts +1 -0
- package/src/api/jobs/controllers/AdminJobController.ts +3 -0
- package/src/api/logs/TODO.md +13 -10
- package/src/api/notifications/controllers/AdminNotificationController.ts +1 -0
- package/src/api/parameters/controllers/AdminConfigController.ts +10 -0
- package/src/api/users/controllers/AdminIdentityController.ts +3 -0
- package/src/api/users/controllers/AdminSessionController.ts +3 -0
- package/src/api/users/controllers/AdminUserController.ts +5 -0
- package/src/cli/apps/AlephaPackageBuilderCli.ts +9 -0
- package/src/cli/atoms/buildOptions.ts +99 -9
- package/src/cli/commands/build.ts +150 -32
- package/src/cli/commands/db.ts +5 -7
- package/src/cli/commands/init.spec.ts +50 -6
- package/src/cli/commands/init.ts +28 -5
- package/src/cli/providers/ViteDevServerProvider.ts +31 -9
- package/src/cli/services/AlephaCliUtils.ts +16 -0
- package/src/cli/services/PackageManagerUtils.ts +2 -0
- package/src/cli/services/ProjectScaffolder.spec.ts +97 -0
- package/src/cli/services/ProjectScaffolder.ts +28 -6
- package/src/cli/templates/agentMd.ts +6 -1
- package/src/cli/templates/apiAppSecurityTs.ts +11 -0
- package/src/cli/templates/apiIndexTs.ts +18 -4
- package/src/cli/templates/webAppRouterTs.ts +25 -1
- package/src/cli/templates/webHelloComponentTsx.ts +15 -5
- package/src/command/helpers/Runner.spec.ts +135 -0
- package/src/command/helpers/Runner.ts +4 -1
- package/src/command/providers/CliProvider.spec.ts +325 -0
- package/src/command/providers/CliProvider.ts +117 -7
- package/src/core/Alepha.ts +32 -25
- package/src/email/index.workerd.ts +36 -0
- package/src/email/providers/WorkermailerEmailProvider.ts +221 -0
- package/src/lock/core/primitives/$lock.ts +13 -1
- package/src/orm/index.bun.ts +1 -1
- package/src/orm/index.ts +2 -6
- package/src/orm/providers/drivers/BunSqliteProvider.ts +4 -1
- package/src/orm/providers/drivers/CloudflareD1Provider.ts +57 -30
- package/src/orm/providers/drivers/DatabaseProvider.ts +9 -1
- package/src/orm/providers/drivers/NodeSqliteProvider.ts +4 -1
- package/src/react/auth/services/ReactAuth.ts +3 -1
- package/src/react/router/atoms/ssrManifestAtom.ts +7 -0
- package/src/react/router/hooks/useActive.ts +1 -1
- package/src/react/router/hooks/useRouter.ts +1 -1
- package/src/react/router/index.ts +4 -0
- package/src/react/router/primitives/$page.browser.spec.tsx +24 -24
- package/src/react/router/primitives/$page.spec.tsx +0 -32
- package/src/react/router/primitives/$page.ts +6 -14
- package/src/react/router/providers/ReactBrowserProvider.ts +6 -3
- package/src/react/router/providers/ReactPageProvider.ts +1 -1
- package/src/react/router/providers/ReactPreloadProvider.spec.ts +142 -0
- package/src/react/router/providers/ReactPreloadProvider.ts +85 -0
- package/src/react/router/providers/ReactServerProvider.ts +21 -82
- package/src/react/router/providers/ReactServerTemplateProvider.spec.ts +210 -0
- package/src/react/router/providers/ReactServerTemplateProvider.ts +228 -665
- package/src/react/router/providers/SSRManifestProvider.ts +7 -0
- package/src/react/router/services/ReactRouter.ts +13 -13
- package/src/scheduler/index.workerd.ts +43 -0
- package/src/scheduler/providers/CronProvider.ts +53 -6
- package/src/scheduler/providers/WorkerdCronProvider.ts +102 -0
- package/src/security/__tests__/ServerSecurityProvider.spec.ts +77 -0
- package/src/security/providers/ServerSecurityProvider.ts +30 -22
- package/src/server/compress/providers/ServerCompressProvider.ts +6 -0
- package/src/server/core/providers/NodeHttpServerProvider.spec.ts +9 -3
- package/src/server/links/providers/ServerLinksProvider.spec.ts +332 -0
- package/src/server/links/providers/ServerLinksProvider.ts +1 -1
- package/src/system/index.browser.ts +25 -0
- package/src/system/index.workerd.ts +1 -0
- package/src/system/providers/FileSystemProvider.ts +8 -0
- package/src/system/providers/NodeFileSystemProvider.ts +11 -2
- package/src/vite/tasks/buildServer.ts +2 -12
- package/src/vite/tasks/generateCloudflare.ts +47 -8
- 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
|
-
|
|
103
|
+
this.registerPages();
|
|
105
104
|
this.log.info("SSR OK");
|
|
106
105
|
return;
|
|
107
106
|
}
|
|
108
107
|
|
|
109
|
-
// no SSR enabled, serve
|
|
110
|
-
this.log.info("SSR is disabled
|
|
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
|
|
145
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
});
|