Skip to content

Commit

Permalink
refactor(typings): improve emit types (#4817)
Browse files Browse the repository at this point in the history
This commit fixes several issues with emit types:

- calling `emit()` without calling `timeout()` first is now only available for events without acknowledgement
- calling `emit()` after calling `timeout()` is now only available for events with an acknowledgement
- calling `emitWithAck()` is now only available for events with an acknowledgement
- `timeout()` must be called before calling `emitWithAck()`
  • Loading branch information
ZachHaber committed Oct 11, 2023
1 parent 1cdf36b commit f6ef267
Show file tree
Hide file tree
Showing 11 changed files with 631 additions and 321 deletions.
19 changes: 9 additions & 10 deletions lib/broadcast-operator.ts
Expand Up @@ -8,10 +8,10 @@ import type {
EventsMap,
TypedEventBroadcaster,
DecorateAcknowledgements,
DecorateAcknowledgementsWithTimeoutAndMultipleResponses,
AllButLast,
Last,
SecondArg,
FirstNonErrorArg,
EventNamesWithError,
} from "./typed-events";

export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
Expand Down Expand Up @@ -177,7 +177,7 @@ export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
public timeout(timeout: number) {
const flags = Object.assign({}, this.flags, { timeout });
return new BroadcastOperator<
DecorateAcknowledgementsWithTimeoutAndMultipleResponses<EmitEvents>,
DecorateAcknowledgements<EmitEvents>,
SocketData
>(this.adapter, this.rooms, this.exceptRooms, flags);
}
Expand Down Expand Up @@ -300,10 +300,10 @@ export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
*
* @return a Promise that will be fulfilled when all clients have acknowledged the event
*/
public emitWithAck<Ev extends EventNames<EmitEvents>>(
public emitWithAck<Ev extends EventNamesWithError<EmitEvents>>(
ev: Ev,
...args: AllButLast<EventParams<EmitEvents, Ev>>
): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>> {
): Promise<FirstNonErrorArg<Last<EventParams<EmitEvents, Ev>>>> {
return new Promise((resolve, reject) => {
args.push((err, responses) => {
if (err) {
Expand Down Expand Up @@ -516,11 +516,10 @@ export class RemoteSocket<EmitEvents extends EventsMap, SocketData>
*
* @param timeout
*/
public timeout(timeout: number) {
return this.operator.timeout(timeout) as BroadcastOperator<
DecorateAcknowledgements<EmitEvents>,
SocketData
>;
public timeout(
timeout: number
): BroadcastOperator<DecorateAcknowledgements<EmitEvents>, SocketData> {
return this.operator.timeout(timeout);
}

public emit<Ev extends EventNames<EmitEvents>>(
Expand Down
39 changes: 12 additions & 27 deletions lib/index.ts
Expand Up @@ -36,8 +36,9 @@ import {
DecorateAcknowledgementsWithTimeoutAndMultipleResponses,
AllButLast,
Last,
FirstArg,
SecondArg,
RemoveAcknowledgements,
EventNamesWithAck,
FirstNonErrorArg,
} from "./typed-events";
import { patchAdapter, restoreAdapter, serveFile } from "./uws";
import corsMiddleware from "cors";
Expand Down Expand Up @@ -140,7 +141,7 @@ export class Server<
SocketData = any
> extends StrictEventEmitter<
ServerSideEvents,
EmitEvents,
RemoveAcknowledgements<EmitEvents>,
ServerReservedEventsMap<
ListenEvents,
EmitEvents,
Expand Down Expand Up @@ -846,26 +847,6 @@ export class Server<
return this.sockets.except(room);
}

/**
* Emits an event and waits for an acknowledgement from all clients.
*
* @example
* try {
* const responses = await io.timeout(1000).emitWithAck("some-event");
* console.log(responses); // one response per client
* } catch (e) {
* // some clients did not acknowledge the event in the given delay
* }
*
* @return a Promise that will be fulfilled when all clients have acknowledged the event
*/
public emitWithAck<Ev extends EventNames<EmitEvents>>(
ev: Ev,
...args: AllButLast<EventParams<EmitEvents, Ev>>
): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>> {
return this.sockets.emitWithAck(ev, ...args);
}

/**
* Sends a `message` event to all clients.
*
Expand All @@ -882,7 +863,9 @@ export class Server<
* @return self
*/
public send(...args: EventParams<EmitEvents, "message">): this {
this.sockets.emit("message", ...args);
// This type-cast is needed because EmitEvents likely doesn't have `message` as a key.
// if you specify the EmitEvents, the type of args will be never.
this.sockets.emit("message" as any, ...args);
return this;
}

Expand All @@ -892,7 +875,9 @@ export class Server<
* @return self
*/
public write(...args: EventParams<EmitEvents, "message">): this {
this.sockets.emit("message", ...args);
// This type-cast is needed because EmitEvents likely doesn't have `message` as a key.
// if you specify the EmitEvents, the type of args will be never.
this.sockets.emit("message" as any, ...args);
return this;
}

Expand Down Expand Up @@ -948,10 +933,10 @@ export class Server<
*
* @return a Promise that will be fulfilled when all servers have acknowledged the event
*/
public serverSideEmitWithAck<Ev extends EventNames<ServerSideEvents>>(
public serverSideEmitWithAck<Ev extends EventNamesWithAck<ServerSideEvents>>(
ev: Ev,
...args: AllButLast<EventParams<ServerSideEvents, Ev>>
): Promise<FirstArg<Last<EventParams<ServerSideEvents, Ev>>>[]> {
): Promise<FirstNonErrorArg<Last<EventParams<ServerSideEvents, Ev>>>[]> {
return this.sockets.serverSideEmitWithAck(ev, ...args);
}

Expand Down
98 changes: 52 additions & 46 deletions lib/namespace.ts
Expand Up @@ -9,8 +9,12 @@ import {
DecorateAcknowledgementsWithTimeoutAndMultipleResponses,
AllButLast,
Last,
FirstArg,
SecondArg,
DecorateAcknowledgementsWithMultipleResponses,
DecorateAcknowledgements,
RemoveAcknowledgements,
EventNamesWithAck,
FirstNonErrorArg,
EventNamesWithoutAck,
} from "./typed-events";
import type { Client } from "./client";
import debugModule from "debug";
Expand Down Expand Up @@ -117,7 +121,7 @@ export class Namespace<
SocketData = any
> extends StrictEventEmitter<
ServerSideEvents,
EmitEvents,
RemoveAcknowledgements<EmitEvents>,
NamespaceReservedEventsMap<
ListenEvents,
EmitEvents,
Expand Down Expand Up @@ -252,7 +256,10 @@ export class Namespace<
* @return a new {@link BroadcastOperator} instance for chaining
*/
public to(room: Room | Room[]) {
return new BroadcastOperator<EmitEvents, SocketData>(this.adapter).to(room);
return new BroadcastOperator<
DecorateAcknowledgementsWithMultipleResponses<EmitEvents>,
SocketData
>(this.adapter).to(room);
}

/**
Expand All @@ -268,7 +275,10 @@ export class Namespace<
* @return a new {@link BroadcastOperator} instance for chaining
*/
public in(room: Room | Room[]) {
return new BroadcastOperator<EmitEvents, SocketData>(this.adapter).in(room);
return new BroadcastOperator<
DecorateAcknowledgementsWithMultipleResponses<EmitEvents>,
SocketData
>(this.adapter).in(room);
}

/**
Expand All @@ -290,9 +300,10 @@ export class Namespace<
* @return a new {@link BroadcastOperator} instance for chaining
*/
public except(room: Room | Room[]) {
return new BroadcastOperator<EmitEvents, SocketData>(this.adapter).except(
room
);
return new BroadcastOperator<
DecorateAcknowledgementsWithMultipleResponses<EmitEvents>,
SocketData
>(this.adapter).except(room);
}

/**
Expand Down Expand Up @@ -430,7 +441,7 @@ export class Namespace<
*
* @return Always true
*/
public emit<Ev extends EventNames<EmitEvents>>(
public emit<Ev extends EventNamesWithoutAck<EmitEvents>>(
ev: Ev,
...args: EventParams<EmitEvents, Ev>
): boolean {
Expand All @@ -440,30 +451,6 @@ export class Namespace<
);
}

/**
* Emits an event and waits for an acknowledgement from all clients.
*
* @example
* const myNamespace = io.of("/my-namespace");
*
* try {
* const responses = await myNamespace.timeout(1000).emitWithAck("some-event");
* console.log(responses); // one response per client
* } catch (e) {
* // some clients did not acknowledge the event in the given delay
* }
*
* @return a Promise that will be fulfilled when all clients have acknowledged the event
*/
public emitWithAck<Ev extends EventNames<EmitEvents>>(
ev: Ev,
...args: AllButLast<EventParams<EmitEvents, Ev>>
): Promise<SecondArg<Last<EventParams<EmitEvents, Ev>>>> {
return new BroadcastOperator<EmitEvents, SocketData>(
this.adapter
).emitWithAck(ev, ...args);
}

/**
* Sends a `message` event to all clients.
*
Expand All @@ -482,7 +469,9 @@ export class Namespace<
* @return self
*/
public send(...args: EventParams<EmitEvents, "message">): this {
this.emit("message", ...args);
// This type-cast is needed because EmitEvents likely doesn't have `message` as a key.
// if you specify the EmitEvents, the type of args will be never.
this.emit("message" as any, ...args);
return this;
}

Expand All @@ -492,7 +481,9 @@ export class Namespace<
* @return self
*/
public write(...args: EventParams<EmitEvents, "message">): this {
this.emit("message", ...args);
// This type-cast is needed because EmitEvents likely doesn't have `message` as a key.
// if you specify the EmitEvents, the type of args will be never.
this.emit("message" as any, ...args);
return this;
}

Expand Down Expand Up @@ -557,10 +548,10 @@ export class Namespace<
*
* @return a Promise that will be fulfilled when all servers have acknowledged the event
*/
public serverSideEmitWithAck<Ev extends EventNames<ServerSideEvents>>(
public serverSideEmitWithAck<Ev extends EventNamesWithAck<ServerSideEvents>>(
ev: Ev,
...args: AllButLast<EventParams<ServerSideEvents, Ev>>
): Promise<FirstArg<Last<EventParams<ServerSideEvents, Ev>>>[]> {
): Promise<FirstNonErrorArg<Last<EventParams<ServerSideEvents, Ev>>>[]> {
return new Promise((resolve, reject) => {
args.push((err, responses) => {
if (err) {
Expand Down Expand Up @@ -612,9 +603,10 @@ export class Namespace<
* @return self
*/
public compress(compress: boolean) {
return new BroadcastOperator<EmitEvents, SocketData>(this.adapter).compress(
compress
);
return new BroadcastOperator<
DecorateAcknowledgementsWithMultipleResponses<EmitEvents>,
SocketData
>(this.adapter).compress(compress);
}

/**
Expand All @@ -630,7 +622,10 @@ export class Namespace<
* @return self
*/
public get volatile() {
return new BroadcastOperator<EmitEvents, SocketData>(this.adapter).volatile;
return new BroadcastOperator<
DecorateAcknowledgementsWithMultipleResponses<EmitEvents>,
SocketData
>(this.adapter).volatile;
}

/**
Expand All @@ -645,7 +640,10 @@ export class Namespace<
* @return a new {@link BroadcastOperator} instance for chaining
*/
public get local() {
return new BroadcastOperator<EmitEvents, SocketData>(this.adapter).local;
return new BroadcastOperator<
DecorateAcknowledgementsWithMultipleResponses<EmitEvents>,
SocketData
>(this.adapter).local;
}

/**
Expand All @@ -664,10 +662,18 @@ export class Namespace<
*
* @param timeout
*/
public timeout(timeout: number) {
return new BroadcastOperator<EmitEvents, SocketData>(this.adapter).timeout(
timeout
);
public timeout(
timeout: number
): BroadcastOperator<
DecorateAcknowledgements<
DecorateAcknowledgementsWithMultipleResponses<EmitEvents>
>,
SocketData
> {
return new BroadcastOperator<
DecorateAcknowledgementsWithMultipleResponses<EmitEvents>,
SocketData
>(this.adapter).timeout(timeout);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions lib/parent-namespace.ts
Expand Up @@ -2,9 +2,9 @@ import { Namespace } from "./namespace";
import type { Server, RemoteSocket } from "./index";
import type {
EventParams,
EventNames,
EventsMap,
DefaultEventsMap,
EventNamesWithoutAck,
} from "./typed-events";
import type { BroadcastOptions } from "socket.io-adapter";
import debugModule from "debug";
Expand Down Expand Up @@ -56,7 +56,7 @@ export class ParentNamespace<
this.adapter = { broadcast };
}

public emit<Ev extends EventNames<EmitEvents>>(
public emit<Ev extends EventNamesWithoutAck<EmitEvents>>(
ev: Ev,
...args: EventParams<EmitEvents, Ev>
): boolean {
Expand Down
7 changes: 4 additions & 3 deletions lib/socket.ts
Expand Up @@ -7,9 +7,10 @@ import {
DecorateAcknowledgementsWithMultipleResponses,
DefaultEventsMap,
EventNames,
EventNamesWithAck,
EventParams,
EventsMap,
FirstArg,
FirstNonErrorArg,
Last,
StrictEventEmitter,
} from "./typed-events";
Expand Down Expand Up @@ -383,10 +384,10 @@ export class Socket<
*
* @return a Promise that will be fulfilled when the client acknowledges the event
*/
public emitWithAck<Ev extends EventNames<EmitEvents>>(
public emitWithAck<Ev extends EventNamesWithAck<EmitEvents>>(
ev: Ev,
...args: AllButLast<EventParams<EmitEvents, Ev>>
): Promise<FirstArg<Last<EventParams<EmitEvents, Ev>>>> {
): Promise<FirstNonErrorArg<Last<EventParams<EmitEvents, Ev>>>> {
// the timeout flag is optional
const withErr = this.flags.timeout !== undefined;
return new Promise((resolve, reject) => {
Expand Down

0 comments on commit f6ef267

Please sign in to comment.