diff --git a/packages/start-server-core/package.json b/packages/start-server-core/package.json index 513aac7ae79..d06830f089b 100644 --- a/packages/start-server-core/package.json +++ b/packages/start-server-core/package.json @@ -79,7 +79,7 @@ "@tanstack/router-core": "workspace:*", "@tanstack/start-client-core": "workspace:*", "@tanstack/start-storage-context": "workspace:*", - "h3-v2": "npm:h3@2.0.1-rc.2", + "h3-v2": "npm:h3@2.0.1-rc.6", "seroval": "^1.4.1", "tiny-invariant": "^1.3.3" }, diff --git a/packages/start-server-core/src/request-response.ts b/packages/start-server-core/src/request-response.ts index e02b2ce5c38..f8de03aa566 100644 --- a/packages/start-server-core/src/request-response.ts +++ b/packages/start-server-core/src/request-response.ts @@ -59,6 +59,65 @@ const eventStorage = globalObj[GLOBAL_EVENT_STORAGE_KEY] export type { ResponseHeaderName, RequestHeaderName } +type HeadersWithGetSetCookie = Headers & { + getSetCookie?: () => Array +} + +type MaybePromise = T | Promise + +function isPromiseLike(value: MaybePromise): value is Promise { + return typeof (value as Promise).then === 'function' +} + +function getSetCookieValues(headers: Headers): Array { + const headersWithSetCookie = headers as HeadersWithGetSetCookie + if (typeof headersWithSetCookie.getSetCookie === 'function') { + return headersWithSetCookie.getSetCookie() + } + const value = headers.get('set-cookie') + return value ? [value] : [] +} + +function mergeEventResponseHeaders(response: Response, event: H3Event): void { + if (response.ok) { + return + } + + const eventSetCookies = getSetCookieValues(event.res.headers) + if (eventSetCookies.length === 0) { + return + } + + const responseSetCookies = getSetCookieValues(response.headers) + response.headers.delete('set-cookie') + for (const cookie of responseSetCookies) { + response.headers.append('set-cookie', cookie) + } + for (const cookie of eventSetCookies) { + response.headers.append('set-cookie', cookie) + } +} + +function attachResponseHeaders( + value: MaybePromise, + event: H3Event, +): MaybePromise { + if (isPromiseLike(value)) { + return value.then((resolved) => { + if (resolved instanceof Response) { + mergeEventResponseHeaders(resolved, event) + } + return resolved + }) + } + + if (value instanceof Response) { + mergeEventResponseHeaders(value, event) + } + + return value +} + export function requestHandler( handler: RequestHandler, ) { @@ -68,7 +127,7 @@ export function requestHandler( const response = eventStorage.run({ h3Event }, () => handler(request, requestOpts), ) - return h3_toResponse(response, h3Event) + return h3_toResponse(attachResponseHeaders(response, h3Event), h3Event) } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f215bdf2b6..e220cb90005 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10589,7 +10589,7 @@ importers: devDependencies: '@netlify/vite-plugin-tanstack-start': specifier: ^1.1.4 - version: 1.1.4(@tanstack/solid-start@packages+solid-start)(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + version: 1.1.4(@tanstack/solid-start@packages+solid-start)(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) '@tailwindcss/postcss': specifier: ^4.1.15 version: 4.1.15 @@ -12342,8 +12342,8 @@ importers: specifier: workspace:* version: link:../start-storage-context h3-v2: - specifier: npm:h3@2.0.1-rc.2 - version: h3@2.0.1-rc.2(crossws@0.4.1(srvx@0.9.8)) + specifier: npm:h3@2.0.1-rc.6 + version: h3@2.0.1-rc.6(crossws@0.4.1(srvx@0.9.8)) seroval: specifier: ^1.4.1 version: 1.4.1 @@ -20305,6 +20305,15 @@ packages: crossws: optional: true + h3@2.0.1-rc.6: + resolution: {integrity: sha512-kKLFVFNJlDVTbQjakz1ZTFSHB9+oi9+Khf0v7xQsUKU3iOqu2qmrFzTD56YsDvvj2nBgqVDphGRXB2VRursw4w==} + engines: {node: '>=20.11.1'} + peerDependencies: + crossws: ^0.4.1 + peerDependenciesMeta: + crossws: + optional: true + handle-thing@2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} @@ -26823,13 +26832,13 @@ snapshots: uuid: 11.1.0 write-file-atomic: 5.0.1 - '@netlify/dev@4.6.3(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)': + '@netlify/dev@4.6.3(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)': dependencies: '@netlify/blobs': 10.1.0 '@netlify/config': 23.2.0 '@netlify/dev-utils': 4.3.0 '@netlify/edge-functions-dev': 1.0.0 - '@netlify/functions-dev': 1.0.0(encoding@0.1.13)(rollup@4.52.5) + '@netlify/functions-dev': 1.0.0(rollup@4.52.5) '@netlify/headers': 2.1.0 '@netlify/images': 1.3.0(@netlify/blobs@10.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0) '@netlify/redirects': 3.1.0 @@ -26897,12 +26906,12 @@ snapshots: dependencies: '@netlify/types': 2.1.0 - '@netlify/functions-dev@1.0.0(encoding@0.1.13)(rollup@4.52.5)': + '@netlify/functions-dev@1.0.0(rollup@4.52.5)': dependencies: '@netlify/blobs': 10.1.0 '@netlify/dev-utils': 4.3.0 '@netlify/functions': 5.0.0 - '@netlify/zip-it-and-ship-it': 14.1.11(encoding@0.1.13)(rollup@4.52.5) + '@netlify/zip-it-and-ship-it': 14.1.11(rollup@4.52.5) cron-parser: 4.9.0 decache: 4.6.2 extract-zip: 2.0.1 @@ -26992,9 +27001,9 @@ snapshots: '@netlify/types@2.1.0': {} - '@netlify/vite-plugin-tanstack-start@1.1.4(@tanstack/solid-start@packages+solid-start)(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))': + '@netlify/vite-plugin-tanstack-start@1.1.4(@tanstack/solid-start@packages+solid-start)(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))': dependencies: - '@netlify/vite-plugin': 2.7.4(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) + '@netlify/vite-plugin': 2.7.4(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1)) vite: 7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) optionalDependencies: '@tanstack/solid-start': link:packages/solid-start @@ -27022,9 +27031,9 @@ snapshots: - supports-color - uploadthing - '@netlify/vite-plugin@2.7.4(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))': + '@netlify/vite-plugin@2.7.4(babel-plugin-macros@3.1.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1))': dependencies: - '@netlify/dev': 4.6.3(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(encoding@0.1.13)(ioredis@5.8.0)(rollup@4.52.5) + '@netlify/dev': 4.6.3(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.8.0)(rollup@4.52.5) '@netlify/dev-utils': 4.3.0 dedent: 1.7.0(babel-plugin-macros@3.1.0) vite: 7.1.7(@types/node@22.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.8.1) @@ -27052,13 +27061,13 @@ snapshots: - supports-color - uploadthing - '@netlify/zip-it-and-ship-it@14.1.11(encoding@0.1.13)(rollup@4.52.5)': + '@netlify/zip-it-and-ship-it@14.1.11(rollup@4.52.5)': dependencies: '@babel/parser': 7.28.5 '@babel/types': 7.28.4 '@netlify/binary-info': 1.0.0 '@netlify/serverless-functions-api': 2.7.1 - '@vercel/nft': 0.29.4(encoding@0.1.13)(rollup@4.52.5) + '@vercel/nft': 0.29.4(rollup@4.52.5) archiver: 7.0.1 common-path-prefix: 3.0.0 copy-file: 11.1.0 @@ -30126,7 +30135,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vercel/nft@0.29.4(encoding@0.1.13)(rollup@4.52.5)': + '@vercel/nft@0.29.4(rollup@4.52.5)': dependencies: '@mapbox/node-pre-gyp': 2.0.0(encoding@0.1.13) '@rollup/pluginutils': 5.1.4(rollup@4.52.5) @@ -33216,12 +33225,10 @@ snapshots: optionalDependencies: crossws: 0.4.1(srvx@0.8.15) - h3@2.0.1-rc.2(crossws@0.4.1(srvx@0.9.8)): + h3@2.0.1-rc.6(crossws@0.4.1(srvx@0.9.8)): dependencies: - cookie-es: 2.0.0 - fetchdts: 0.1.7 rou3: 0.7.12 - srvx: 0.8.15 + srvx: 0.9.8 optionalDependencies: crossws: 0.4.1(srvx@0.9.8)