mirror of
https://github.com/discordjs/discord.js.git
synced 2024-08-21 10:34:41 +12:00
refactor(brokers): re-design API to make groups a constructor option (#10297)
* fix(BaseRedis): remove listeners on destroy and stop pooling when no subscription * refactor(BaseRedis): group as constructor param and cleanup subscribers * fix(BaseRedis): remove listeners on destroy and stop pooling when no subscription * refactor(BaseRedis): group as constructor param and cleanup subscribers * chore(RPCRedis): group * Update packages/brokers/src/brokers/Broker.ts * Update packages/brokers/src/brokers/Broker.ts * Update packages/brokers/src/brokers/redis/BaseRedis.ts Removed `removeAllListeners` from destroy * chore(BaseRedis): destroy unsubscribe spread array --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
29a50bb476
commit
38a37b5caf
3 changed files with 28 additions and 18 deletions
|
@ -42,13 +42,13 @@ export type ToEventMap<
|
||||||
|
|
||||||
export interface IBaseBroker<TEvents extends Record<string, any>> {
|
export interface IBaseBroker<TEvents extends Record<string, any>> {
|
||||||
/**
|
/**
|
||||||
* Subscribes to the given events, grouping them by the given group name
|
* Subscribes to the given events
|
||||||
*/
|
*/
|
||||||
subscribe(group: string, events: (keyof TEvents)[]): Promise<void>;
|
subscribe(events: (keyof TEvents)[]): Promise<void>;
|
||||||
/**
|
/**
|
||||||
* Unsubscribes from the given events - it's required to pass the same group name as when subscribing for proper cleanup
|
* Unsubscribes from the given events
|
||||||
*/
|
*/
|
||||||
unsubscribe(group: string, events: (keyof TEvents)[]): Promise<void>;
|
unsubscribe(events: (keyof TEvents)[]): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPubSubBroker<TEvents extends Record<string, any>>
|
export interface IPubSubBroker<TEvents extends Record<string, any>>
|
||||||
|
|
|
@ -23,10 +23,19 @@ export interface RedisBrokerOptions extends BaseBrokerOptions {
|
||||||
* How long to block for messages when polling
|
* How long to block for messages when polling
|
||||||
*/
|
*/
|
||||||
blockTimeout?: number;
|
blockTimeout?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumer group name to use for this broker
|
||||||
|
*
|
||||||
|
* @see {@link https://redis.io/commands/xreadgroup/}
|
||||||
|
*/
|
||||||
|
group: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Max number of messages to poll at once
|
* Max number of messages to poll at once
|
||||||
*/
|
*/
|
||||||
maxChunk?: number;
|
maxChunk?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique consumer name.
|
* Unique consumer name.
|
||||||
*
|
*
|
||||||
|
@ -43,7 +52,7 @@ export const DefaultRedisBrokerOptions = {
|
||||||
name: randomBytes(20).toString('hex'),
|
name: randomBytes(20).toString('hex'),
|
||||||
maxChunk: 10,
|
maxChunk: 10,
|
||||||
blockTimeout: 5_000,
|
blockTimeout: 5_000,
|
||||||
} as const satisfies Required<RedisBrokerOptions>;
|
} as const satisfies Required<Omit<RedisBrokerOptions, 'group'>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class with shared Redis logic
|
* Helper class with shared Redis logic
|
||||||
|
@ -93,13 +102,13 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc IBaseBroker.subscribe}
|
* {@inheritDoc IBaseBroker.subscribe}
|
||||||
*/
|
*/
|
||||||
public async subscribe(group: string, events: (keyof TEvents)[]): Promise<void> {
|
public async subscribe(events: (keyof TEvents)[]): Promise<void> {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
// @ts-expect-error: Intended
|
// @ts-expect-error: Intended
|
||||||
events.map(async (event) => {
|
events.map(async (event) => {
|
||||||
this.subscribedEvents.add(event as string);
|
this.subscribedEvents.add(event as string);
|
||||||
try {
|
try {
|
||||||
return await this.redisClient.xgroup('CREATE', event as string, group, 0, 'MKSTREAM');
|
return await this.redisClient.xgroup('CREATE', event as string, this.options.group, 0, 'MKSTREAM');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!(error instanceof ReplyError)) {
|
if (!(error instanceof ReplyError)) {
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -107,18 +116,18 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
void this.listen(group);
|
void this.listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc IBaseBroker.unsubscribe}
|
* {@inheritDoc IBaseBroker.unsubscribe}
|
||||||
*/
|
*/
|
||||||
public async unsubscribe(group: string, events: (keyof TEvents)[]): Promise<void> {
|
public async unsubscribe(events: (keyof TEvents)[]): Promise<void> {
|
||||||
const commands: unknown[][] = Array.from({ length: events.length * 2 });
|
const commands: unknown[][] = Array.from({ length: events.length * 2 });
|
||||||
for (let idx = 0; idx < commands.length; idx += 2) {
|
for (let idx = 0; idx < commands.length; idx += 2) {
|
||||||
const event = events[idx / 2];
|
const event = events[idx / 2];
|
||||||
commands[idx] = ['xgroup', 'delconsumer', event as string, group, this.options.name];
|
commands[idx] = ['xgroup', 'delconsumer', event as string, this.options.group, this.options.name];
|
||||||
commands[idx + 1] = ['xcleangroup', event as string, group];
|
commands[idx + 1] = ['xcleangroup', event as string, this.options.group];
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.redisClient.pipeline(commands).exec();
|
await this.redisClient.pipeline(commands).exec();
|
||||||
|
@ -131,18 +140,18 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||||
/**
|
/**
|
||||||
* Begins polling for events, firing them to {@link BaseRedisBroker.listen}
|
* Begins polling for events, firing them to {@link BaseRedisBroker.listen}
|
||||||
*/
|
*/
|
||||||
protected async listen(group: string): Promise<void> {
|
protected async listen(): Promise<void> {
|
||||||
if (this.listening) {
|
if (this.listening) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listening = true;
|
this.listening = true;
|
||||||
|
|
||||||
while (true) {
|
while (this.subscribedEvents.size > 0) {
|
||||||
try {
|
try {
|
||||||
const data = await this.streamReadClient.xreadgroupBuffer(
|
const data = await this.streamReadClient.xreadgroupBuffer(
|
||||||
'GROUP',
|
'GROUP',
|
||||||
group,
|
this.options.group,
|
||||||
this.options.name,
|
this.options.name,
|
||||||
'COUNT',
|
'COUNT',
|
||||||
String(this.options.maxChunk),
|
String(this.options.maxChunk),
|
||||||
|
@ -169,7 +178,7 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emitEvent(id, group, event.toString('utf8'), this.options.decode(data));
|
this.emitEvent(id, this.options.group, event.toString('utf8'), this.options.decode(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -185,6 +194,7 @@ export abstract class BaseRedisBroker<TEvents extends Record<string, any>>
|
||||||
* Destroys the broker, closing all connections
|
* Destroys the broker, closing all connections
|
||||||
*/
|
*/
|
||||||
public async destroy() {
|
public async destroy() {
|
||||||
|
await this.unsubscribe([...this.subscribedEvents]);
|
||||||
this.streamReadClient.disconnect();
|
this.streamReadClient.disconnect();
|
||||||
this.redisClient.disconnect();
|
this.redisClient.disconnect();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ export interface RPCRedisBrokerOptions extends RedisBrokerOptions {
|
||||||
export const DefaultRPCRedisBrokerOptions = {
|
export const DefaultRPCRedisBrokerOptions = {
|
||||||
...DefaultRedisBrokerOptions,
|
...DefaultRedisBrokerOptions,
|
||||||
timeout: 5_000,
|
timeout: 5_000,
|
||||||
} as const satisfies Required<RPCRedisBrokerOptions>;
|
} as const satisfies Required<Omit<RPCRedisBrokerOptions, 'group'>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RPC broker powered by Redis
|
* RPC broker powered by Redis
|
||||||
|
@ -114,11 +114,11 @@ export class RPCRedisBroker<TEvents extends Record<string, any>, TResponses exte
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected emitEvent(id: Buffer, group: string, event: string, data: unknown) {
|
protected emitEvent(id: Buffer, event: string, data: unknown) {
|
||||||
const payload: { ack(): Promise<void>; data: unknown; reply(data: unknown): Promise<void> } = {
|
const payload: { ack(): Promise<void>; data: unknown; reply(data: unknown): Promise<void> } = {
|
||||||
data,
|
data,
|
||||||
ack: async () => {
|
ack: async () => {
|
||||||
await this.redisClient.xack(event, group, id);
|
await this.redisClient.xack(event, this.options.group, id);
|
||||||
},
|
},
|
||||||
reply: async (data) => {
|
reply: async (data) => {
|
||||||
await this.redisClient.publish(`${event}:${id.toString()}`, this.options.encode(data));
|
await this.redisClient.publish(`${event}:${id.toString()}`, this.options.encode(data));
|
||||||
|
|
Loading…
Reference in a new issue