@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.
@@ -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"}
@@ -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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"}
@@ -0,0 +1,3 @@
1
+ export type { CloudflareHandlerConfig, CloudflareHandlerOptions, CloudflareWorkerModule, SSRModuleConfig, } from './handler.js';
2
+ export { createHandler, generateHTMLTemplate, generateNonce } from './handler.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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.9",
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": "vitest run"
22
+ "test": "bun test"
23
23
  },
24
24
  "dependencies": {
25
- "@vertz/core": "workspace:^"
25
+ "@vertz/core": "^0.2.11"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@cloudflare/workers-types": "^4.20260305.0",
29
- "@vertz/ui-server": "workspace:^",
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": "workspace:^"
33
+ "@vertz/ui-server": "^0.2.11"
35
34
  },
36
35
  "peerDependenciesMeta": {
37
36
  "@vertz/ui-server": {
@@ -1,16 +1,16 @@
1
1
  import type { AppBuilder } from '@vertz/core';
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
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 ?? vi.fn().mockResolvedValue(new Response('OK')),
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 = vi.fn().mockResolvedValue(new Response('OK'));
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 = vi.fn().mockResolvedValue(mockResponse);
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 = vi.fn().mockResolvedValue(new Response('OK'));
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 = vi.fn().mockResolvedValue(new Response('OK'));
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 = vi.fn().mockResolvedValue(new Response('OK'));
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 = vi.fn().mockResolvedValue(new Response('OK'));
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 = vi.fn().mockResolvedValue(new Response('OK'));
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 = vi.fn().mockResolvedValue(mockResponse);
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 = vi.spyOn(console, 'error').mockImplementation(() => {});
163
+ const consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {});
164
164
  const testError = new Error('Test error');
165
- const mockHandler = vi.fn().mockRejectedValue(testError);
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 = vi.fn().mockResolvedValue(
195
+ const apiHandler = mock().mockResolvedValue(
196
196
  new Response('{"items":[]}', {
197
197
  headers: { 'Content-Type': 'application/json' },
198
198
  }),
199
199
  );
200
- const ssrHandler = vi.fn().mockResolvedValue(
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 = vi.fn().mockResolvedValue(new Response('OK'));
229
- const appFactory = vi.fn().mockReturnValue(mockApp(apiHandler));
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(vi.fn().mockResolvedValue(new Response('OK'))),
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 = vi.fn().mockResolvedValue(new Response('OK'));
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 = vi.spyOn(console, 'error').mockImplementation(() => {});
294
+ const consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {});
295
295
 
296
296
  const worker = createHandler({
297
- app: () => mockApp(vi.fn().mockRejectedValue(new Error('DB connection failed'))),
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 = vi.spyOn(console, 'error').mockImplementation(() => {});
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 = vi.fn().mockResolvedValue(
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 = vi.fn().mockReturnValue(mockSSRRequestHandler);
376
+ const mockCreateSSRHandler = mock().mockReturnValue(mockSSRRequestHandler);
377
377
 
378
378
  beforeEach(() => {
379
- vi.doMock('@vertz/ui-server/ssr', () => ({
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 = vi.fn().mockResolvedValue(
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 = vi.fn().mockResolvedValue(
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(vi.fn().mockResolvedValue(new Response('OK'))),
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(vi.fn().mockResolvedValue(new Response('OK'))),
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(vi.fn().mockResolvedValue(new Response('OK'))),
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 = vi.spyOn(console, 'error').mockImplementation(() => {});
636
+ const consoleErrorSpy = spyOn(console, 'error').mockImplementation(() => {});
641
637
 
642
638
  const worker = createHandler({
643
- app: () => mockApp(vi.fn().mockRejectedValue(new Error('fail'))),
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
- });