From 5b41258247f1dddf0f883b46c5e988084712cf93 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sun, 15 Feb 2026 02:10:22 -0800 Subject: [PATCH] fix(types): allow emitWithAck for events with void callbacks EventNamesWithAck previously excluded events whose callback had no non-error arguments (e.g. `(cb: () => void) => void` or `(cb: (err: Error) => void) => void`). This made it impossible to use emitWithAck as a simple acknowledgement mechanism without data. The fix removes the FirstNonErrorArg void check while keeping the guard against events with no parameters at all, so events like `() => void` (no callback) are still correctly excluded. Closes #5257 --- packages/socket.io/lib/typed-events.ts | 12 +++++------- packages/socket.io/test/socket.io.test-d.ts | 19 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/socket.io/lib/typed-events.ts b/packages/socket.io/lib/typed-events.ts index 0a04e67577..bbdf5a633c 100644 --- a/packages/socket.io/lib/typed-events.ts +++ b/packages/socket.io/lib/typed-events.ts @@ -22,8 +22,6 @@ export type EventNames = keyof Map & (string | symbol); /** * Returns a union type containing all the keys of an event map that have an acknowledgement callback. - * - * That also have *some* data coming in. */ export type EventNamesWithAck< Map extends EventsMap, @@ -32,11 +30,11 @@ export type EventNamesWithAck< Last> | Map[K], K, K extends ( - Last> extends (...args: any[]) => any - ? FirstNonErrorArg>> extends void - ? never - : K - : never + Parameters extends never[] + ? never + : Last> extends (...args: any[]) => any + ? K + : never ) ? K : never diff --git a/packages/socket.io/test/socket.io.test-d.ts b/packages/socket.io/test/socket.io.test-d.ts index 5c6ef3b187..625f42a3c4 100644 --- a/packages/socket.io/test/socket.io.test-d.ts +++ b/packages/socket.io/test/socket.io.test-d.ts @@ -265,15 +265,11 @@ describe("server", () => { interface ServerToClientEventsWithMultipleWithAck { ackFromServer: (a: boolean, b: string) => Promise; ackFromServerSingleArg: (a: boolean, b: string) => Promise; - // This should technically be `undefined[]`, but this doesn't work currently *only* with emitWithAck - // you can use an empty callback with emit, but not emitWithAck onlyCallback: () => Promise; } interface ServerToClientEventsWithAck { ackFromServer: (a: boolean, b: string) => Promise; ackFromServerSingleArg: (a: boolean, b: string) => Promise; - // This doesn't work currently *only* with emitWithAck - // you can use an empty callback with emit, but not emitWithAck onlyCallback: () => Promise; } describe("Emitting Types", () => { @@ -420,8 +416,9 @@ describe("server", () => { sio.timeout(0).emitWithAck("noArgs"); // @ts-expect-error - "helloFromServer" doesn't have a callback and is thus excluded sio.timeout(0).emitWithAck("helloFromServer"); - // @ts-expect-error - "onlyCallback" doesn't have a callback and is thus excluded - sio.timeout(0).emitWithAck("onlyCallback"); + expectType< + ToEmitWithAck + >(sio.timeout(0).emitWithAck<"onlyCallback">); expectType< ToEmitWithAck< ServerToClientEventsWithMultipleWithAck, @@ -496,10 +493,12 @@ describe("server", () => { s.emitWithAck("noArgs"); // @ts-expect-error - "helloFromServer" doesn't have a callback and is thus excluded s.emitWithAck("helloFromServer"); - // @ts-expect-error - "onlyCallback" doesn't have a callback and is thus excluded - s.emitWithAck("onlyCallback"); - // @ts-expect-error - "onlyCallback" doesn't have a callback and is thus excluded - s.timeout(0).emitWithAck("onlyCallback"); + expectType< + ToEmitWithAck + >(s.emitWithAck<"onlyCallback">); + expectType< + ToEmitWithAck + >(s.timeout(0).emitWithAck<"onlyCallback">); expectType< ToEmitWithAck >(s.emitWithAck<"ackFromServerSingleArg">);