diff --git a/.changeset/empty-roses-obey.md b/.changeset/empty-roses-obey.md new file mode 100644 index 000000000000..ec51eb768e68 --- /dev/null +++ b/.changeset/empty-roses-obey.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Enable multipart formdata parsing with node-fetch diff --git a/packages/kit/package.json b/packages/kit/package.json index cc72d674af55..aae4207b55f3 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -33,6 +33,7 @@ "locate-character": "^2.0.5", "marked": "^4.0.16", "mime": "^3.0.0", + "node-fetch": "^3.2.4", "port-authority": "^1.2.0", "rollup": "^2.75.3", "selfsigned": "^2.0.1", diff --git a/packages/kit/src/node/index.js b/packages/kit/src/node/index.js index 1b30bd113389..33a18065f956 100644 --- a/packages/kit/src/node/index.js +++ b/packages/kit/src/node/index.js @@ -1,4 +1,6 @@ import * as set_cookie_parser from 'set-cookie-parser'; +import { Request as NodeFetchRequest } from 'node-fetch'; +import { Readable } from 'stream'; /** @param {import('http').IncomingMessage} req */ function get_raw_body(req) { @@ -64,11 +66,22 @@ export async function getRequest(base, req) { delete headers[':scheme']; } - return new Request(base + req.url, { + const request = new Request(base + req.url, { method: req.method, headers, body: await get_raw_body(req) // TODO stream rather than buffer }); + + request.formData = async () => { + return new NodeFetchRequest(request.url, { + method: request.method, + headers: request.headers, + // @ts-expect-error TypeScript doesn't understand that ReadableStream implements Symbol.asyncIterator + body: request.body && Readable.from(request.body) + }).formData(); + }; + + return request; } /** @type {import('@sveltejs/kit/node').setResponse} */ diff --git a/packages/kit/src/node/polyfills.js b/packages/kit/src/node/polyfills.js index 66ae54b155d8..ae1c8ff6c502 100644 --- a/packages/kit/src/node/polyfills.js +++ b/packages/kit/src/node/polyfills.js @@ -17,8 +17,6 @@ const globals = { // exported for dev/preview and node environments export function installPolyfills() { for (const name in globals) { - if (name in globalThis) continue; - Object.defineProperty(globalThis, name, { enumerable: true, configurable: true, diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 6fddaa9009ae..69831be459c1 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -38,17 +38,13 @@ export function is_pojo(body) { if (body) { if (body instanceof Uint8Array) return false; + if (body instanceof ReadableStream) return false; // if body is a node Readable, throw an error // TODO remove this for 1.0 if (body._readableState && typeof body.pipe === 'function') { throw new Error('Node streams are no longer supported — use a ReadableStream instead'); } - - if (body instanceof ReadableStream) return false; - - // similarly, it could be a web ReadableStream - if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) return false; } return true; diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/error-post.js b/packages/kit/test/apps/basics/src/routes/shadowed/error-post.js index a4d616a831a4..fdf8aebba386 100644 --- a/packages/kit/test/apps/basics/src/routes/shadowed/error-post.js +++ b/packages/kit/test/apps/basics/src/routes/shadowed/error-post.js @@ -6,11 +6,13 @@ export function get() { }; } -export function post() { +export async function post({ request }) { + const data = await request.formData(); + return { status: 400, body: { - post_message: 'hello from post' + post_message: `echo: ${data.get('message')}` } }; } diff --git a/packages/kit/test/apps/basics/src/routes/shadowed/index.svelte b/packages/kit/test/apps/basics/src/routes/shadowed/index.svelte index 8f5c46d2c5c4..3d37d71a7df4 100644 --- a/packages/kit/test/apps/basics/src/routes/shadowed/index.svelte +++ b/packages/kit/test/apps/basics/src/routes/shadowed/index.svelte @@ -14,6 +14,7 @@ -
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js index a726480ac740..e619f4a03b63 100644 --- a/packages/kit/test/apps/basics/test/test.js +++ b/packages/kit/test/apps/basics/test/test.js @@ -543,7 +543,7 @@ test.describe.parallel('Shadowed pages', () => { test('Merges bodies for 4xx and 5xx responses from non-GET', async ({ page }) => { await page.goto('/shadowed'); await Promise.all([page.waitForNavigation(), page.click('#error-post')]); - expect(await page.textContent('h1')).toBe('hello from get / hello from post'); + expect(await page.textContent('h1')).toBe('hello from get / echo: posted data'); }); test('Responds from endpoint if Accept includes application/json but not text/html', async ({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f5a074f9c81..2cbe5597d0f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,6 +271,7 @@ importers: locate-character: ^2.0.5 marked: ^4.0.16 mime: ^3.0.0 + node-fetch: ^3.2.4 port-authority: ^1.2.0 rollup: ^2.75.3 sade: ^1.8.1 @@ -309,6 +310,7 @@ importers: locate-character: 2.0.5 marked: 4.0.16 mime: 3.0.0 + node-fetch: 3.2.4 port-authority: 1.2.0 rollup: 2.75.3 selfsigned: 2.0.1