Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 4 additions & 26 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@azure/functions",
"version": "4.7.2",
"version": "5.0.0",
"description": "Microsoft Azure Functions NodeJS Framework",
"keywords": [
"azure",
Expand Down Expand Up @@ -28,7 +28,7 @@
"README.md"
],
"engines": {
"node": ">=18.0"
"node": ">=20.0"
},
"scripts": {
"build": "webpack --mode development",
Expand All @@ -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",
Expand Down
52 changes: 28 additions & 24 deletions src/http/HttpRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,22 +16,22 @@ import { isDefined, nonNullProp } from '../utils/nonNull';
import { extractHttpUserFromHeaders } from './extractHttpUserFromHeaders';

interface InternalHttpRequestInit extends RpcHttpData {
undiciRequest?: uRequest;
request?: Request;
}

export class HttpRequest implements types.HttpRequest {
readonly query: URLSearchParams;
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;
Expand All @@ -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 {
Expand All @@ -79,37 +78,40 @@ export class HttpRequest implements types.HttpRequest {
return this.#cachedUser;
}

get body(): ReadableStream<any> | null {
return this.#uReq.body;
get body(): ReadableStream<Uint8Array> | null {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this.#req.body as any; // Type compatibility between global and Node.js ReadableStream
Copy link
Preview

Copilot AI Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using any here bypasses type checks; consider creating a proper type guard or wrapper for the request body stream to keep type safety.

Copilot uses AI. Check for mistakes.

}

get bodyUsed(): boolean {
return this.#uReq.bodyUsed;
return this.#req.bodyUsed;
}

async arrayBuffer(): Promise<ArrayBuffer> {
return this.#uReq.arrayBuffer();
return this.#req.arrayBuffer();
}

// eslint-disable-next-line @typescript-eslint/require-await
async blob(): Promise<Blob> {
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<FormData> {
return this.#uReq.formData();
return this.#req.formData();
}

async json(): Promise<unknown> {
return this.#uReq.json();
return this.#req.json();
}

async text(): Promise<string> {
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);
}
}
Expand Down Expand Up @@ -144,12 +146,14 @@ export function createStreamRequest(
headers = <HeadersInit>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
Comment on lines +150 to +156
Copy link
Preview

Copilot AI Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Casting the init object to any can hide real type errors. You could define or extend a RequestInit type to properly handle the duplex and stream compatibility instead of using any.

Copilot uses AI. Check for mistakes.


const params: Record<string, string> = {};
for (const [key, rpcValue] of Object.entries(rpcParams)) {
Expand All @@ -159,7 +163,7 @@ export function createStreamRequest(
}

return new HttpRequest({
undiciRequest: uReq,
request: req,
params,
});
}
48 changes: 29 additions & 19 deletions src/http/HttpResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand All @@ -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<any> | null {
return this.#uRes.body;
get body(): ReadableStream<Uint8Array> | null {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this.#res.body as any; // Type compatibility between global and Node.js ReadableStream
}
Comment on lines +56 to 59
Copy link
Preview

Copilot AI Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Avoid using any and disabling lint rules. Consider defining a proper type alias or wrapper for the stream to maintain type safety.

Suggested change
get body(): ReadableStream<Uint8Array> | 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 body(): ReadableStream<Uint8Array> | null {
return this.#res.body as ReadableStream<Uint8Array> | null; // Type compatibility between global and Node.js ReadableStream
}

Copilot uses AI. Check for mistakes.


get bodyUsed(): boolean {
return this.#uRes.bodyUsed;
return this.#res.bodyUsed;
}

async arrayBuffer(): Promise<ArrayBuffer> {
return this.#uRes.arrayBuffer();
return this.#res.arrayBuffer();
}

// eslint-disable-next-line @typescript-eslint/require-await
async blob(): Promise<Blob> {
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<FormData> {
return this.#uRes.formData();
return this.#res.formData();
}

async json(): Promise<unknown> {
return this.#uRes.json();
return this.#res.json();
}

async text(): Promise<string> {
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);
}
}
1 change: 0 additions & 1 deletion src/http/extractHttpUserFromHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
2 changes: 1 addition & 1 deletion src/trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
HttpTrigger,
HttpTriggerOptions,
MySqlTrigger,
MySqlTriggerOptions,
MySqlTriggerOptions,
ServiceBusQueueTrigger,
ServiceBusQueueTriggerOptions,
ServiceBusTopicTrigger,
Expand Down
1 change: 0 additions & 1 deletion test/converters/toRpcHttp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Loading
Loading