@vertz/cloudflare 0.2.9 → 0.2.11
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/.turbo/turbo-build.log +1 -0
- package/CHANGELOG.md +8 -0
- package/dist/handler.d.ts +73 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +196 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js.map +1 -0
- package/package.json +5 -6
- package/tests/handler.test.ts +31 -35
- package/vitest.config.ts +0 -21
|
@@ -0,0 +1 @@
|
|
|
1
|
+
$ tsc
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @vertz/cloudflare
|
|
2
2
|
|
|
3
|
+
## 0.2.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`b2878cf`](https://github.com/vertz-dev/vertz/commit/b2878cfe2acb3d1155ca5e0da13b2ee91c9aea9a), [`5ed4c1a`](https://github.com/vertz-dev/vertz/commit/5ed4c1a4c5c9ea946e97b1636011251c6287eaf4), [`1fc9e33`](https://github.com/vertz-dev/vertz/commit/1fc9e33a9aa5283898c8974084f519a3caacbabb)]:
|
|
8
|
+
- @vertz/ui-server@0.2.11
|
|
9
|
+
- @vertz/core@0.2.11
|
|
10
|
+
|
|
3
11
|
## 0.2.8
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { AppBuilder } from '@vertz/core';
|
|
2
|
+
import type { SSRModule } from '@vertz/ui-server/ssr';
|
|
3
|
+
export interface CloudflareHandlerOptions {
|
|
4
|
+
basePath?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* SSR module configuration for zero-boilerplate server-side rendering.
|
|
8
|
+
*
|
|
9
|
+
* Pass the app module directly and the handler generates the HTML template,
|
|
10
|
+
* wires up createSSRHandler, and handles the full SSR pipeline.
|
|
11
|
+
*/
|
|
12
|
+
export interface SSRModuleConfig {
|
|
13
|
+
/** App module exporting App, theme?, styles?, getInjectedCSS? */
|
|
14
|
+
module: SSRModule;
|
|
15
|
+
/** Client-side entry script path. Default: '/assets/entry-client.js' */
|
|
16
|
+
clientScript?: string;
|
|
17
|
+
/** HTML document title. Default: 'Vertz App' */
|
|
18
|
+
title?: string;
|
|
19
|
+
/** SSR query timeout in ms. Default: 5000 (generous for D1 cold starts). */
|
|
20
|
+
ssrTimeout?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Full-stack configuration for createHandler.
|
|
24
|
+
*
|
|
25
|
+
* Supports lazy app initialization (for D1 bindings), SSR fallback,
|
|
26
|
+
* and automatic security headers.
|
|
27
|
+
*/
|
|
28
|
+
export interface CloudflareHandlerConfig {
|
|
29
|
+
/**
|
|
30
|
+
* Factory that creates the AppBuilder. Receives the Worker env bindings.
|
|
31
|
+
* Called once on first request, then cached.
|
|
32
|
+
*/
|
|
33
|
+
app: (env: unknown) => AppBuilder;
|
|
34
|
+
/** API path prefix. Requests matching this prefix go to the app handler. */
|
|
35
|
+
basePath: string;
|
|
36
|
+
/**
|
|
37
|
+
* SSR configuration for non-API routes.
|
|
38
|
+
*
|
|
39
|
+
* - `SSRModuleConfig` — zero-boilerplate: pass the app module directly
|
|
40
|
+
* - `(request: Request) => Promise<Response>` — custom SSR callback
|
|
41
|
+
* - `undefined` — non-API requests return 404
|
|
42
|
+
*/
|
|
43
|
+
ssr?: SSRModuleConfig | ((request: Request) => Promise<Response>);
|
|
44
|
+
/** When true, adds standard security headers to all responses. */
|
|
45
|
+
securityHeaders?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** Generate a cryptographically random nonce for CSP. */
|
|
48
|
+
export declare function generateNonce(): string;
|
|
49
|
+
/**
|
|
50
|
+
* Create a Cloudflare Worker handler from a Vertz app.
|
|
51
|
+
*
|
|
52
|
+
* Simple form — wraps an AppBuilder directly:
|
|
53
|
+
* ```ts
|
|
54
|
+
* export default createHandler(app, { basePath: '/api' });
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* Config form — full-stack with lazy init, SSR, and security headers:
|
|
58
|
+
* ```ts
|
|
59
|
+
* export default createHandler({
|
|
60
|
+
* app: (env) => createServer({ entities, db: createDb({ d1: env.DB }) }),
|
|
61
|
+
* basePath: '/api',
|
|
62
|
+
* ssr: (req) => renderToString(new URL(req.url).pathname),
|
|
63
|
+
* securityHeaders: true,
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
/** Worker module shape returned by createHandler. */
|
|
68
|
+
export interface CloudflareWorkerModule {
|
|
69
|
+
fetch(request: Request, env: unknown, ctx: ExecutionContext): Promise<Response>;
|
|
70
|
+
}
|
|
71
|
+
export declare function createHandler(appOrConfig: AppBuilder | CloudflareHandlerConfig, options?: CloudflareHandlerOptions): CloudflareWorkerModule;
|
|
72
|
+
export declare function generateHTMLTemplate(clientScript: string, title: string, nonce?: string): string;
|
|
73
|
+
//# sourceMappingURL=handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAMtD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,MAAM,EAAE,SAAS,CAAC;IAClB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,UAAU,CAAC;IAElC,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,eAAe,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElE,kEAAkE;IAClE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAMD,yDAAyD;AACzD,wBAAgB,aAAa,IAAI,MAAM,CAStC;AA4CD;;;;;;;;;;;;;;;;;GAiBG;AACH,qDAAqD;AACrD,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACjF;AAED,wBAAgB,aAAa,CAC3B,WAAW,EAAE,UAAU,GAAG,uBAAuB,EACjD,OAAO,CAAC,EAAE,wBAAwB,GACjC,sBAAsB,CAQxB;AA+BD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAchG"}
|
package/dist/handler.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Security headers
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
/** Generate a cryptographically random nonce for CSP. */
|
|
5
|
+
export function generateNonce() {
|
|
6
|
+
const bytes = new Uint8Array(16);
|
|
7
|
+
crypto.getRandomValues(bytes);
|
|
8
|
+
// Base64-encode without padding for a compact, URL-safe nonce
|
|
9
|
+
let binary = '';
|
|
10
|
+
for (const byte of bytes) {
|
|
11
|
+
binary += String.fromCharCode(byte);
|
|
12
|
+
}
|
|
13
|
+
return btoa(binary);
|
|
14
|
+
}
|
|
15
|
+
const STATIC_SECURITY_HEADERS = {
|
|
16
|
+
'X-Content-Type-Options': 'nosniff',
|
|
17
|
+
'X-Frame-Options': 'DENY',
|
|
18
|
+
'X-XSS-Protection': '1; mode=block',
|
|
19
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
20
|
+
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
|
21
|
+
};
|
|
22
|
+
function buildCSPHeader(nonce) {
|
|
23
|
+
return `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;`;
|
|
24
|
+
}
|
|
25
|
+
function withSecurityHeaders(response, nonce) {
|
|
26
|
+
const headers = new Headers(response.headers);
|
|
27
|
+
for (const [key, value] of Object.entries(STATIC_SECURITY_HEADERS)) {
|
|
28
|
+
headers.set(key, value);
|
|
29
|
+
}
|
|
30
|
+
headers.set('Content-Security-Policy', buildCSPHeader(nonce));
|
|
31
|
+
return new Response(response.body, {
|
|
32
|
+
status: response.status,
|
|
33
|
+
statusText: response.statusText,
|
|
34
|
+
headers,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
function stripBasePath(request, basePath) {
|
|
41
|
+
const url = new URL(request.url);
|
|
42
|
+
if (url.pathname.startsWith(basePath)) {
|
|
43
|
+
url.pathname = url.pathname.slice(basePath.length) || '/';
|
|
44
|
+
return new Request(url.toString(), request);
|
|
45
|
+
}
|
|
46
|
+
return request;
|
|
47
|
+
}
|
|
48
|
+
export function createHandler(appOrConfig, options) {
|
|
49
|
+
// Config object form
|
|
50
|
+
if ('app' in appOrConfig && typeof appOrConfig.app === 'function') {
|
|
51
|
+
return createFullStackHandler(appOrConfig);
|
|
52
|
+
}
|
|
53
|
+
// Simple AppBuilder form (backward compat)
|
|
54
|
+
return createSimpleHandler(appOrConfig, options);
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Simple handler (backward compat)
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
function createSimpleHandler(app, options) {
|
|
60
|
+
const handler = app.handler;
|
|
61
|
+
return {
|
|
62
|
+
async fetch(request, _env, _ctx) {
|
|
63
|
+
if (options?.basePath) {
|
|
64
|
+
request = stripBasePath(request, options.basePath);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
return await handler(request);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
console.error('Unhandled error in worker:', error);
|
|
71
|
+
return new Response('Internal Server Error', { status: 500 });
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// HTML template generation
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
export function generateHTMLTemplate(clientScript, title, nonce) {
|
|
80
|
+
const nonceAttr = nonce != null ? ` nonce="${nonce}"` : '';
|
|
81
|
+
return `<!doctype html>
|
|
82
|
+
<html lang="en">
|
|
83
|
+
<head>
|
|
84
|
+
<meta charset="UTF-8">
|
|
85
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
86
|
+
<title>${title}</title>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<div id="app"><!--ssr-outlet--></div>
|
|
90
|
+
<script type="module" src="${clientScript}"${nonceAttr}></script>
|
|
91
|
+
</body>
|
|
92
|
+
</html>`;
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Full-stack handler
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
function isSSRModuleConfig(ssr) {
|
|
98
|
+
return typeof ssr === 'object' && 'module' in ssr;
|
|
99
|
+
}
|
|
100
|
+
function createFullStackHandler(config) {
|
|
101
|
+
const { basePath, ssr, securityHeaders } = config;
|
|
102
|
+
let cachedApp = null;
|
|
103
|
+
// SSR handler factory: when using SSRModuleConfig with nonce support, this
|
|
104
|
+
// is called per-request with the current nonce. For custom callbacks it
|
|
105
|
+
// is set once and always returns the same handler.
|
|
106
|
+
let ssrHandlerFactory = null;
|
|
107
|
+
let ssrResolved = false;
|
|
108
|
+
function getApp(env) {
|
|
109
|
+
if (!cachedApp) {
|
|
110
|
+
cachedApp = config.app(env);
|
|
111
|
+
}
|
|
112
|
+
return cachedApp;
|
|
113
|
+
}
|
|
114
|
+
async function resolveSSR() {
|
|
115
|
+
if (ssrResolved)
|
|
116
|
+
return;
|
|
117
|
+
ssrResolved = true;
|
|
118
|
+
if (!ssr)
|
|
119
|
+
return;
|
|
120
|
+
if (isSSRModuleConfig(ssr)) {
|
|
121
|
+
const { createSSRHandler } = await import('@vertz/ui-server/ssr');
|
|
122
|
+
const { module, clientScript = '/assets/entry-client.js', title = 'Vertz App', ssrTimeout = 5000, } = ssr;
|
|
123
|
+
// Return a factory that creates an SSR handler with the per-request nonce
|
|
124
|
+
ssrHandlerFactory = (nonce) => createSSRHandler({
|
|
125
|
+
module,
|
|
126
|
+
template: generateHTMLTemplate(clientScript, title, nonce),
|
|
127
|
+
ssrTimeout,
|
|
128
|
+
nonce,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Custom callback — wrap it in a factory that ignores the nonce
|
|
133
|
+
ssrHandlerFactory = () => ssr;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function applyHeaders(response, nonce) {
|
|
137
|
+
return securityHeaders ? withSecurityHeaders(response, nonce) : response;
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
async fetch(request, env, _ctx) {
|
|
141
|
+
await resolveSSR();
|
|
142
|
+
const url = new URL(request.url);
|
|
143
|
+
const nonce = generateNonce();
|
|
144
|
+
// Route splitting: basePath/* → API handler (no URL rewriting — the
|
|
145
|
+
// app's own basePath/apiPrefix handles prefix matching internally)
|
|
146
|
+
if (url.pathname.startsWith(basePath)) {
|
|
147
|
+
try {
|
|
148
|
+
const app = getApp(env);
|
|
149
|
+
const response = await app.handler(request);
|
|
150
|
+
return applyHeaders(response, nonce);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error('Unhandled error in worker:', error);
|
|
154
|
+
return applyHeaders(new Response('Internal Server Error', { status: 500 }), nonce);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Non-API routes → SSR or 404
|
|
158
|
+
if (ssrHandlerFactory) {
|
|
159
|
+
const app = getApp(env);
|
|
160
|
+
const ssrHandler = ssrHandlerFactory(nonce);
|
|
161
|
+
const origin = url.origin;
|
|
162
|
+
// Patch fetch during SSR so API requests (e.g. query() calling
|
|
163
|
+
// fetch('/api/todos')) are routed through the in-memory app handler
|
|
164
|
+
// instead of attempting a network self-fetch (which fails on Workers).
|
|
165
|
+
const originalFetch = globalThis.fetch;
|
|
166
|
+
globalThis.fetch = (input, init) => {
|
|
167
|
+
// Determine the pathname from the input (string, URL, or Request)
|
|
168
|
+
const rawUrl = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
169
|
+
const isRelative = rawUrl.startsWith('/');
|
|
170
|
+
const pathname = isRelative ? rawUrl.split('?')[0] : new URL(rawUrl).pathname;
|
|
171
|
+
const isLocal = isRelative || new URL(rawUrl).origin === origin;
|
|
172
|
+
if (isLocal && pathname.startsWith(basePath)) {
|
|
173
|
+
// Build an absolute URL so Request() doesn't reject relative URLs
|
|
174
|
+
const absoluteUrl = isRelative ? `${origin}${rawUrl}` : rawUrl;
|
|
175
|
+
const req = new Request(absoluteUrl, init);
|
|
176
|
+
return app.handler(req);
|
|
177
|
+
}
|
|
178
|
+
return originalFetch(input, init);
|
|
179
|
+
};
|
|
180
|
+
try {
|
|
181
|
+
const response = await ssrHandler(request);
|
|
182
|
+
return applyHeaders(response, nonce);
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
console.error('Unhandled error in worker:', error);
|
|
186
|
+
return applyHeaders(new Response('Internal Server Error', { status: 500 }), nonce);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
globalThis.fetch = originalFetch;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return applyHeaders(new Response('Not Found', { status: 404 }), nonce);
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handler.js","sourceRoot":"","sources":["../src/handler.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,eAAe,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vertz/cloudflare",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Cloudflare Workers adapter for vertz",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,19 +19,18 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc",
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
|
-
"test": "
|
|
22
|
+
"test": "bun test"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@vertz/core": "
|
|
25
|
+
"@vertz/core": "^0.2.11"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@cloudflare/workers-types": "^4.20260305.0",
|
|
29
|
-
"@vertz/ui-server": "
|
|
30
|
-
"@vitest/coverage-v8": "^4.0.18",
|
|
29
|
+
"@vertz/ui-server": "^0.2.11",
|
|
31
30
|
"typescript": "^5.7.3"
|
|
32
31
|
},
|
|
33
32
|
"peerDependencies": {
|
|
34
|
-
"@vertz/ui-server": "
|
|
33
|
+
"@vertz/ui-server": "^0.2.11"
|
|
35
34
|
},
|
|
36
35
|
"peerDependenciesMeta": {
|
|
37
36
|
"@vertz/ui-server": {
|
package/tests/handler.test.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import type { AppBuilder } from '@vertz/core';
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it,
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
|
|
3
3
|
import { createHandler, generateHTMLTemplate, generateNonce } from '../src/handler.js';
|
|
4
4
|
|
|
5
5
|
function mockApp(handler?: (...args: unknown[]) => Promise<Response>): AppBuilder {
|
|
6
6
|
return {
|
|
7
|
-
handler: handler ??
|
|
7
|
+
handler: handler ?? mock().mockResolvedValue(new Response('OK')),
|
|
8
8
|
} as unknown as AppBuilder;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
describe('createHandler', () => {
|
|
12
12
|
it('returns proper Worker export with fetch method', () => {
|
|
13
|
-
const mockHandler =
|
|
13
|
+
const mockHandler = mock().mockResolvedValue(new Response('OK'));
|
|
14
14
|
const mockApp = {
|
|
15
15
|
handler: mockHandler,
|
|
16
16
|
} as unknown as AppBuilder;
|
|
@@ -23,7 +23,7 @@ describe('createHandler', () => {
|
|
|
23
23
|
|
|
24
24
|
it('forwards requests to the vertz handler', async () => {
|
|
25
25
|
const mockResponse = new Response('Hello from handler');
|
|
26
|
-
const mockHandler =
|
|
26
|
+
const mockHandler = mock().mockResolvedValue(mockResponse);
|
|
27
27
|
const mockApp = {
|
|
28
28
|
handler: mockHandler,
|
|
29
29
|
} as unknown as AppBuilder;
|
|
@@ -40,7 +40,7 @@ describe('createHandler', () => {
|
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
it('strips basePath prefix from pathname', async () => {
|
|
43
|
-
const mockHandler =
|
|
43
|
+
const mockHandler = mock().mockResolvedValue(new Response('OK'));
|
|
44
44
|
const mockApp = {
|
|
45
45
|
handler: mockHandler,
|
|
46
46
|
} as unknown as AppBuilder;
|
|
@@ -59,7 +59,7 @@ describe('createHandler', () => {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it('strips basePath with trailing slash correctly', async () => {
|
|
62
|
-
const mockHandler =
|
|
62
|
+
const mockHandler = mock().mockResolvedValue(new Response('OK'));
|
|
63
63
|
const mockApp = {
|
|
64
64
|
handler: mockHandler,
|
|
65
65
|
} as unknown as AppBuilder;
|
|
@@ -77,7 +77,7 @@ describe('createHandler', () => {
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('handles basePath when pathname does not start with basePath', async () => {
|
|
80
|
-
const mockHandler =
|
|
80
|
+
const mockHandler = mock().mockResolvedValue(new Response('OK'));
|
|
81
81
|
const mockApp = {
|
|
82
82
|
handler: mockHandler,
|
|
83
83
|
} as unknown as AppBuilder;
|
|
@@ -95,7 +95,7 @@ describe('createHandler', () => {
|
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
it('preserves query parameters when stripping basePath', async () => {
|
|
98
|
-
const mockHandler =
|
|
98
|
+
const mockHandler = mock().mockResolvedValue(new Response('OK'));
|
|
99
99
|
const mockApp = {
|
|
100
100
|
handler: mockHandler,
|
|
101
101
|
} as unknown as AppBuilder;
|
|
@@ -115,7 +115,7 @@ describe('createHandler', () => {
|
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
it('preserves request headers and method', async () => {
|
|
118
|
-
const mockHandler =
|
|
118
|
+
const mockHandler = mock().mockResolvedValue(new Response('OK'));
|
|
119
119
|
const mockApp = {
|
|
120
120
|
handler: mockHandler,
|
|
121
121
|
} as unknown as AppBuilder;
|
|
@@ -141,7 +141,7 @@ describe('createHandler', () => {
|
|
|
141
141
|
|
|
142
142
|
it('works without basePath option', async () => {
|
|
143
143
|
const mockResponse = new Response('No basePath');
|
|
144
|
-
const mockHandler =
|
|
144
|
+
const mockHandler = mock().mockResolvedValue(mockResponse);
|
|
145
145
|
const mockApp = {
|
|
146
146
|
handler: mockHandler,
|
|
147
147
|
} as unknown as AppBuilder;
|
|
@@ -160,9 +160,9 @@ describe('createHandler', () => {
|
|
|
160
160
|
});
|
|
161
161
|
|
|
162
162
|
it('returns 500 response when handler throws an error', async () => {
|
|
163
|
-
const consoleErrorSpy =
|
|
163
|
+
const consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {});
|
|
164
164
|
const testError = new Error('Test error');
|
|
165
|
-
const mockHandler =
|
|
165
|
+
const mockHandler = mock().mockRejectedValue(testError);
|
|
166
166
|
const mockApp = {
|
|
167
167
|
handler: mockHandler,
|
|
168
168
|
} as unknown as AppBuilder;
|
|
@@ -192,12 +192,12 @@ describe('createHandler (config object)', () => {
|
|
|
192
192
|
const mockCtx = {} as ExecutionContext;
|
|
193
193
|
|
|
194
194
|
it('routes API requests to app handler and SSR to ssr handler', async () => {
|
|
195
|
-
const apiHandler =
|
|
195
|
+
const apiHandler = mock().mockResolvedValue(
|
|
196
196
|
new Response('{"items":[]}', {
|
|
197
197
|
headers: { 'Content-Type': 'application/json' },
|
|
198
198
|
}),
|
|
199
199
|
);
|
|
200
|
-
const ssrHandler =
|
|
200
|
+
const ssrHandler = mock().mockResolvedValue(
|
|
201
201
|
new Response('<html>SSR</html>', {
|
|
202
202
|
headers: { 'Content-Type': 'text/html' },
|
|
203
203
|
}),
|
|
@@ -225,8 +225,8 @@ describe('createHandler (config object)', () => {
|
|
|
225
225
|
});
|
|
226
226
|
|
|
227
227
|
it('passes env to the app factory and caches the result', async () => {
|
|
228
|
-
const apiHandler =
|
|
229
|
-
const appFactory =
|
|
228
|
+
const apiHandler = mock().mockResolvedValue(new Response('OK'));
|
|
229
|
+
const appFactory = mock().mockReturnValue(mockApp(apiHandler));
|
|
230
230
|
|
|
231
231
|
const worker = createHandler({
|
|
232
232
|
app: appFactory,
|
|
@@ -245,7 +245,7 @@ describe('createHandler (config object)', () => {
|
|
|
245
245
|
|
|
246
246
|
it('adds security headers when securityHeaders is true', async () => {
|
|
247
247
|
const worker = createHandler({
|
|
248
|
-
app: () => mockApp(
|
|
248
|
+
app: () => mockApp(mock().mockResolvedValue(new Response('OK'))),
|
|
249
249
|
basePath: '/api',
|
|
250
250
|
securityHeaders: true,
|
|
251
251
|
});
|
|
@@ -276,7 +276,7 @@ describe('createHandler (config object)', () => {
|
|
|
276
276
|
});
|
|
277
277
|
|
|
278
278
|
it('passes full URL to app handler (no basePath stripping)', async () => {
|
|
279
|
-
const apiHandler =
|
|
279
|
+
const apiHandler = mock().mockResolvedValue(new Response('OK'));
|
|
280
280
|
|
|
281
281
|
const worker = createHandler({
|
|
282
282
|
app: () => mockApp(apiHandler),
|
|
@@ -291,10 +291,10 @@ describe('createHandler (config object)', () => {
|
|
|
291
291
|
});
|
|
292
292
|
|
|
293
293
|
it('returns 500 with error message when app handler throws', async () => {
|
|
294
|
-
const consoleErrorSpy =
|
|
294
|
+
const consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {});
|
|
295
295
|
|
|
296
296
|
const worker = createHandler({
|
|
297
|
-
app: () => mockApp(
|
|
297
|
+
app: () => mockApp(mock().mockRejectedValue(new Error('DB connection failed'))),
|
|
298
298
|
basePath: '/api',
|
|
299
299
|
});
|
|
300
300
|
|
|
@@ -310,7 +310,7 @@ describe('createHandler (config object)', () => {
|
|
|
310
310
|
});
|
|
311
311
|
|
|
312
312
|
it('returns 500 with error message when SSR handler throws', async () => {
|
|
313
|
-
const consoleErrorSpy =
|
|
313
|
+
const consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {});
|
|
314
314
|
|
|
315
315
|
const worker = createHandler({
|
|
316
316
|
app: () => mockApp(),
|
|
@@ -368,25 +368,21 @@ describe('createHandler (SSR module config)', () => {
|
|
|
368
368
|
|
|
369
369
|
// We mock @vertz/ui-server's createSSRHandler to isolate wiring logic.
|
|
370
370
|
// The mock returns a handler that echoes 'SSR Module' so we can verify routing.
|
|
371
|
-
const mockSSRRequestHandler =
|
|
371
|
+
const mockSSRRequestHandler = mock().mockResolvedValue(
|
|
372
372
|
new Response('<html>SSR Module</html>', {
|
|
373
373
|
headers: { 'Content-Type': 'text/html' },
|
|
374
374
|
}),
|
|
375
375
|
);
|
|
376
|
-
const mockCreateSSRHandler =
|
|
376
|
+
const mockCreateSSRHandler = mock().mockReturnValue(mockSSRRequestHandler);
|
|
377
377
|
|
|
378
378
|
beforeEach(() => {
|
|
379
|
-
|
|
379
|
+
mock.module('@vertz/ui-server/ssr', () => ({
|
|
380
380
|
createSSRHandler: mockCreateSSRHandler,
|
|
381
381
|
}));
|
|
382
382
|
mockSSRRequestHandler.mockClear();
|
|
383
383
|
mockCreateSSRHandler.mockClear();
|
|
384
384
|
});
|
|
385
385
|
|
|
386
|
-
afterEach(() => {
|
|
387
|
-
vi.doUnmock('@vertz/ui-server/ssr');
|
|
388
|
-
});
|
|
389
|
-
|
|
390
386
|
it('routes non-API requests through the SSR handler created from module config', async () => {
|
|
391
387
|
const { createHandler: freshCreateHandler } = await import('../src/handler.js');
|
|
392
388
|
|
|
@@ -463,7 +459,7 @@ describe('createHandler (SSR module config)', () => {
|
|
|
463
459
|
it('still works with ssr callback (backward compat)', async () => {
|
|
464
460
|
const { createHandler: freshCreateHandler } = await import('../src/handler.js');
|
|
465
461
|
|
|
466
|
-
const ssrCallback =
|
|
462
|
+
const ssrCallback = mock().mockResolvedValue(
|
|
467
463
|
new Response('<html>Callback SSR</html>', {
|
|
468
464
|
headers: { 'Content-Type': 'text/html' },
|
|
469
465
|
}),
|
|
@@ -486,7 +482,7 @@ describe('createHandler (SSR module config)', () => {
|
|
|
486
482
|
it('routes API requests to app handler even with SSR module config', async () => {
|
|
487
483
|
const { createHandler: freshCreateHandler } = await import('../src/handler.js');
|
|
488
484
|
|
|
489
|
-
const apiHandler =
|
|
485
|
+
const apiHandler = mock().mockResolvedValue(
|
|
490
486
|
new Response('{"items":[]}', {
|
|
491
487
|
headers: { 'Content-Type': 'application/json' },
|
|
492
488
|
}),
|
|
@@ -540,7 +536,7 @@ describe('nonce-based CSP headers', () => {
|
|
|
540
536
|
|
|
541
537
|
it('CSP header contains nonce (not unsafe-inline) for script-src', async () => {
|
|
542
538
|
const worker = createHandler({
|
|
543
|
-
app: () => mockApp(
|
|
539
|
+
app: () => mockApp(mock().mockResolvedValue(new Response('OK'))),
|
|
544
540
|
basePath: '/api',
|
|
545
541
|
securityHeaders: true,
|
|
546
542
|
});
|
|
@@ -563,7 +559,7 @@ describe('nonce-based CSP headers', () => {
|
|
|
563
559
|
|
|
564
560
|
it('CSP header keeps unsafe-inline for style-src', async () => {
|
|
565
561
|
const worker = createHandler({
|
|
566
|
-
app: () => mockApp(
|
|
562
|
+
app: () => mockApp(mock().mockResolvedValue(new Response('OK'))),
|
|
567
563
|
basePath: '/api',
|
|
568
564
|
securityHeaders: true,
|
|
569
565
|
});
|
|
@@ -580,7 +576,7 @@ describe('nonce-based CSP headers', () => {
|
|
|
580
576
|
|
|
581
577
|
it('each request gets a different nonce in the CSP header', async () => {
|
|
582
578
|
const worker = createHandler({
|
|
583
|
-
app: () => mockApp(
|
|
579
|
+
app: () => mockApp(mock().mockResolvedValue(new Response('OK'))),
|
|
584
580
|
basePath: '/api',
|
|
585
581
|
securityHeaders: true,
|
|
586
582
|
});
|
|
@@ -637,10 +633,10 @@ describe('nonce-based CSP headers', () => {
|
|
|
637
633
|
});
|
|
638
634
|
|
|
639
635
|
it('applies nonce-based CSP to 500 error responses', async () => {
|
|
640
|
-
const consoleErrorSpy =
|
|
636
|
+
const consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {});
|
|
641
637
|
|
|
642
638
|
const worker = createHandler({
|
|
643
|
-
app: () => mockApp(
|
|
639
|
+
app: () => mockApp(mock().mockRejectedValue(new Error('fail'))),
|
|
644
640
|
basePath: '/api',
|
|
645
641
|
securityHeaders: true,
|
|
646
642
|
});
|
package/vitest.config.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { dirname, resolve } from 'node:path';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { defineConfig } from 'vitest/config';
|
|
4
|
-
|
|
5
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
|
|
7
|
-
export default defineConfig({
|
|
8
|
-
test: {
|
|
9
|
-
include: ['src/**/*.test.ts', 'tests/**/*.test.ts'],
|
|
10
|
-
environment: 'node',
|
|
11
|
-
alias: {
|
|
12
|
-
'@': resolve(__dirname, './src'),
|
|
13
|
-
},
|
|
14
|
-
coverage: {
|
|
15
|
-
reporter: ['text', 'json-summary', 'json'],
|
|
16
|
-
provider: 'v8',
|
|
17
|
-
include: ['src/**/*.ts'],
|
|
18
|
-
exclude: ['src/**/*.test.ts', 'src/**/*.test-d.ts', 'src/index.ts'],
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
});
|