diff --git a/package-lock.json b/package-lock.json index b620ce1c..e6ae45ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,16 @@ { "name": "@azure/functions", - "version": "4.7.2", + "version": "5.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@azure/functions", - "version": "4.7.2", + "version": "5.0.0", "license": "MIT", "dependencies": { "cookie": "^0.7.0", - "long": "^4.0.0", - "undici": "^5.29.0" + "long": "^4.0.0" }, "devDependencies": { "@types/chai": "^4.2.22", @@ -52,7 +51,7 @@ "webpack-cli": "^4.10.0" }, "engines": { - "node": ">=18.0" + "node": ">=20.0" } }, "node_modules/@babel/code-frame": { @@ -239,15 +238,6 @@ "node": ">= 4" } }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", @@ -6447,18 +6437,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/undici": { - "version": "5.29.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", - "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", - "license": "MIT", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index dabaf9c4..36aa6819 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@azure/functions", - "version": "4.7.2", + "version": "5.0.0", "description": "Microsoft Azure Functions NodeJS Framework", "keywords": [ "azure", @@ -28,7 +28,7 @@ "README.md" ], "engines": { - "node": ">=18.0" + "node": ">=20.0" }, "scripts": { "build": "webpack --mode development", @@ -42,8 +42,7 @@ }, "dependencies": { "cookie": "^0.7.0", - "long": "^4.0.0", - "undici": "^5.29.0" + "long": "^4.0.0" }, "devDependencies": { "@types/chai": "^4.2.22", diff --git a/src/http/HttpRequest.ts b/src/http/HttpRequest.ts index 7e0ab8f6..2865868b 100644 --- a/src/http/HttpRequest.ts +++ b/src/http/HttpRequest.ts @@ -8,7 +8,6 @@ import { Blob } from 'buffer'; import { IncomingMessage } from 'http'; import * as stream from 'stream'; import { ReadableStream } from 'stream/web'; -import { FormData, Headers, HeadersInit, Request as uRequest } from 'undici'; import { URLSearchParams } from 'url'; import { fromNullableMapping } from '../converters/fromRpcNullable'; import { fromRpcTypedData } from '../converters/fromRpcTypedData'; @@ -17,7 +16,7 @@ import { isDefined, nonNullProp } from '../utils/nonNull'; import { extractHttpUserFromHeaders } from './extractHttpUserFromHeaders'; interface InternalHttpRequestInit extends RpcHttpData { - undiciRequest?: uRequest; + request?: Request; } export class HttpRequest implements types.HttpRequest { @@ -25,14 +24,14 @@ export class HttpRequest implements types.HttpRequest { readonly params: HttpRequestParams; #cachedUser?: HttpRequestUser | null; - #uReq: uRequest; + #req: Request; #init: InternalHttpRequestInit; constructor(init: InternalHttpRequestInit) { this.#init = init; - let uReq = init.undiciRequest; - if (!uReq) { + let req = init.request; + if (!req) { const url = nonNullProp(init, 'url'); let body: Buffer | string | undefined; @@ -42,33 +41,33 @@ export class HttpRequest implements types.HttpRequest { body = init.body.string; } - uReq = new uRequest(url, { + req = new Request(url, { body, method: nonNullProp(init, 'method'), headers: fromNullableMapping(init.nullableHeaders, init.headers), }); } - this.#uReq = uReq; + this.#req = req; if (init.nullableQuery || init.query) { this.query = new URLSearchParams(fromNullableMapping(init.nullableQuery, init.query)); } else { - this.query = new URL(this.#uReq.url).searchParams; + this.query = new URL(this.#req.url).searchParams; } this.params = fromNullableMapping(init.nullableParams, init.params); } get url(): string { - return this.#uReq.url; + return this.#req.url; } get method(): string { - return this.#uReq.method; + return this.#req.method; } get headers(): Headers { - return this.#uReq.headers; + return this.#req.headers; } get user(): HttpRequestUser | null { @@ -79,37 +78,40 @@ export class HttpRequest implements types.HttpRequest { return this.#cachedUser; } - get body(): ReadableStream | null { - return this.#uReq.body; + get body(): ReadableStream | null { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return this.#req.body as any; // Type compatibility between global and Node.js ReadableStream } get bodyUsed(): boolean { - return this.#uReq.bodyUsed; + return this.#req.bodyUsed; } async arrayBuffer(): Promise { - return this.#uReq.arrayBuffer(); + return this.#req.arrayBuffer(); } + // eslint-disable-next-line @typescript-eslint/require-await async blob(): Promise { - return this.#uReq.blob(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return this.#req.blob() as any; // Type compatibility with Node.js Blob } async formData(): Promise { - return this.#uReq.formData(); + return this.#req.formData(); } async json(): Promise { - return this.#uReq.json(); + return this.#req.json(); } async text(): Promise { - return this.#uReq.text(); + return this.#req.text(); } clone(): HttpRequest { const newInit = structuredClone(this.#init); - newInit.undiciRequest = this.#uReq.clone(); + newInit.request = this.#req.clone(); return new HttpRequest(newInit); } } @@ -144,12 +146,14 @@ export function createStreamRequest( headers = headersData; } - const uReq = new uRequest(url, { - body, + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + const req = new Request(url, { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + body: body as any, // Node.js Readable stream compatibility duplex: 'half', method: nonNullProp(proxyReq, 'method'), headers, - }); + } as any); // Global Request constructor compatibility const params: Record = {}; for (const [key, rpcValue] of Object.entries(rpcParams)) { @@ -159,7 +163,7 @@ export function createStreamRequest( } return new HttpRequest({ - undiciRequest: uReq, + request: req, params, }); } diff --git a/src/http/HttpResponse.ts b/src/http/HttpResponse.ts index 120dc1a4..eeadabae 100644 --- a/src/http/HttpResponse.ts +++ b/src/http/HttpResponse.ts @@ -5,32 +5,39 @@ import * as types from '@azure/functions'; import { HttpResponseInit } from '@azure/functions'; import { Blob } from 'buffer'; import { ReadableStream } from 'stream/web'; -import { FormData, Headers, Response as uResponse, ResponseInit as uResponseInit } from 'undici'; import { isDefined } from '../utils/nonNull'; interface InternalHttpResponseInit extends HttpResponseInit { - undiciResponse?: uResponse; + response?: Response; } export class HttpResponse implements types.HttpResponse { readonly cookies: types.Cookie[]; readonly enableContentNegotiation: boolean; - #uRes: uResponse; + #res: Response; #init: InternalHttpResponseInit; constructor(init?: InternalHttpResponseInit) { init ??= {}; this.#init = init; - if (init.undiciResponse) { - this.#uRes = init.undiciResponse; + if (init.response) { + this.#res = init.response; } else { - const uResInit: uResponseInit = { status: init.status, headers: init.headers }; + const resInit: ResponseInit = { status: init.status, headers: init.headers }; if (isDefined(init.jsonBody)) { - this.#uRes = uResponse.json(init.jsonBody, uResInit); + // Create JSON response manually for compatibility + const jsonHeaders = new Headers(resInit.headers); + if (!jsonHeaders.has('content-type')) { + jsonHeaders.set('content-type', 'application/json'); + } + this.#res = new Response(JSON.stringify(init.jsonBody), { + ...resInit, + headers: jsonHeaders, + }); } else { - this.#uRes = new uResponse(init.body, uResInit); + this.#res = new Response(init.body, resInit); } } @@ -39,44 +46,47 @@ export class HttpResponse implements types.HttpResponse { } get status(): number { - return this.#uRes.status; + return this.#res.status; } get headers(): Headers { - return this.#uRes.headers; + return this.#res.headers; } - get body(): ReadableStream | null { - return this.#uRes.body; + get body(): ReadableStream | null { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return this.#res.body as any; // Type compatibility between global and Node.js ReadableStream } get bodyUsed(): boolean { - return this.#uRes.bodyUsed; + return this.#res.bodyUsed; } async arrayBuffer(): Promise { - return this.#uRes.arrayBuffer(); + return this.#res.arrayBuffer(); } + // eslint-disable-next-line @typescript-eslint/require-await async blob(): Promise { - return this.#uRes.blob(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return this.#res.blob() as any; // Type compatibility with Node.js Blob } async formData(): Promise { - return this.#uRes.formData(); + return this.#res.formData(); } async json(): Promise { - return this.#uRes.json(); + return this.#res.json(); } async text(): Promise { - return this.#uRes.text(); + return this.#res.text(); } clone(): HttpResponse { const newInit = structuredClone(this.#init); - newInit.undiciResponse = this.#uRes.clone(); + newInit.response = this.#res.clone(); return new HttpResponse(newInit); } } diff --git a/src/http/extractHttpUserFromHeaders.ts b/src/http/extractHttpUserFromHeaders.ts index a2b24a22..616cdac5 100644 --- a/src/http/extractHttpUserFromHeaders.ts +++ b/src/http/extractHttpUserFromHeaders.ts @@ -2,7 +2,6 @@ // Licensed under the MIT License. import { HttpRequestUser } from '@azure/functions'; -import { Headers } from 'undici'; import { nonNullValue } from '../utils/nonNull'; /* grandfathered in. Should fix when possible */ diff --git a/src/trigger.ts b/src/trigger.ts index f709abb0..ed2f7f6d 100644 --- a/src/trigger.ts +++ b/src/trigger.ts @@ -13,7 +13,7 @@ import { HttpTrigger, HttpTriggerOptions, MySqlTrigger, - MySqlTriggerOptions, + MySqlTriggerOptions, ServiceBusQueueTrigger, ServiceBusQueueTriggerOptions, ServiceBusTopicTrigger, diff --git a/test/converters/toRpcHttp.test.ts b/test/converters/toRpcHttp.test.ts index bbc056fe..2de39ce5 100644 --- a/test/converters/toRpcHttp.test.ts +++ b/test/converters/toRpcHttp.test.ts @@ -5,7 +5,6 @@ import 'mocha'; import * as chai from 'chai'; import { expect } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { Headers } from 'undici'; import { toRpcHttp } from '../../src/converters/toRpcHttp'; import { HttpResponse } from '../../src/http/HttpResponse'; diff --git a/test/http/HttpRequest.test.ts b/test/http/HttpRequest.test.ts index 23cddba2..f2c07963 100644 --- a/test/http/HttpRequest.test.ts +++ b/test/http/HttpRequest.test.ts @@ -5,7 +5,6 @@ import 'mocha'; import * as chai from 'chai'; import { expect } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; -import { File } from 'undici'; import { HttpRequest } from '../../src/http/HttpRequest'; chai.use(chaiAsPromised); @@ -94,7 +93,7 @@ world const parsedForm = await req.formData(); expect(parsedForm.has('myfile')).to.equal(true); - const file = parsedForm.get('myfile'); + const file = parsedForm.get('myfile') as File; expect(file.name).to.equal('test.txt'); expect(file.type).to.equal('text/plain'); expect(await file.text()).to.equal(`hello\r\nworld`); @@ -135,7 +134,7 @@ value2 const contentTypes = ['application/octet-stream', 'application/json', 'text/plain', 'invalid']; for (const contentType of contentTypes) { const req = createFormRequest('', contentType); - await expect(req.formData()).to.eventually.be.rejectedWith(/Could not parse content as FormData/i); + await expect(req.formData()).to.eventually.be.rejectedWith(); } }); }); diff --git a/test/http/extractHttpUserFromHeaders.test.ts b/test/http/extractHttpUserFromHeaders.test.ts index 3c5452b4..4eff6e6d 100644 --- a/test/http/extractHttpUserFromHeaders.test.ts +++ b/test/http/extractHttpUserFromHeaders.test.ts @@ -4,7 +4,6 @@ import 'mocha'; import { HttpRequestUser } from '@azure/functions'; import { expect } from 'chai'; -import { Headers } from 'undici'; import { extractHttpUserFromHeaders } from '../../src/http/extractHttpUserFromHeaders'; describe('Extract Http User Claims Principal from Headers', () => { diff --git a/types/http.d.ts b/types/http.d.ts index e918ef1f..83693a9a 100644 --- a/types/http.d.ts +++ b/types/http.d.ts @@ -3,7 +3,6 @@ import { Blob } from 'buffer'; import { ReadableStream } from 'stream/web'; -import { BodyInit, FormData, Headers, HeadersInit } from 'undici'; import { URLSearchParams } from 'url'; import { FunctionOptions, FunctionOutput, FunctionResult, FunctionTrigger } from './index'; import { InvocationContext } from './InvocationContext';