@discordjs/brokers 2.0.0-pr-11006.1765450794-e636950b2 → 2.0.0-pr-11005.1765454364-f3f6d34e7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
3
3
  import { Redis } from 'ioredis';
4
+ import { GatewayDispatchEvents, GatewayDispatchPayload, Gateway, GatewaySendPayload } from '@discordjs/core';
5
+ import { ManagerShardEventsMap, WebSocketShardEvents } from '@discordjs/ws';
4
6
  import Redis$1 from 'ioredis/built/Redis.js';
5
7
 
6
8
  /**
@@ -207,6 +209,99 @@ declare class PubSubRedisBroker<TEvents extends Record<string, any>> extends Bas
207
209
  protected emitEvent(id: Buffer, group: string, event: string, data: unknown): void;
208
210
  }
209
211
 
212
+ type _DiscordEvents = {
213
+ [K in GatewayDispatchEvents]: GatewayDispatchPayload & {
214
+ t: K;
215
+ };
216
+ };
217
+ type DiscordEvents = {
218
+ [K in keyof _DiscordEvents]: _DiscordEvents[K]['d'];
219
+ };
220
+ interface BrokerIntrinsicProps {
221
+ shardId: number;
222
+ }
223
+ interface Events extends DiscordEvents {
224
+ [RedisGateway.GatewaySendEvent]: GatewaySendPayload;
225
+ }
226
+ type RedisBrokerDiscordEvents = {
227
+ [K in keyof Events]: BrokerIntrinsicProps & {
228
+ payload: Events[K];
229
+ };
230
+ };
231
+ /**
232
+ * RedisGateway is an implementation for core's Gateway interface built on top of our Redis brokers.
233
+ *
234
+ * Some important notes:
235
+ * - Instances for this class are for your consumers/services that need the gateway. naturally, the events passed into
236
+ * `init` are the only ones the core client will be able to emit
237
+ * - You can also opt to use the class as-is without `@discordjs/core`, if you so desire. Events are properly typed
238
+ * - You need to implement your own gateway service. Refer to the example below for how that would look like. This class
239
+ * offers some static methods and properties that help in this errand. It is extremely important that you `publish`
240
+ * events as the receiving service expects, and also that you handle GatewaySend events.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * // gateway-service/index.ts
245
+ * import { RedisGateway, PubSubRedisBroker, kUseRandomGroupName } from '@discordjs/brokers';
246
+ * import Redis from 'ioredis';
247
+ *
248
+ * // the `name` here probably should be env-determined if you need to scale this. see the main README for more information.
249
+ * // also, we use a random group because we do NOT want work-balancing on gateway_send events.
250
+ * const broker = new PubSubRedisBroker(new Redis(), { group: kUseRandomGroupName, name: 'send-consumer-1' });
251
+ * const gateway = new WebSocketManager(gatewayOptionsHere); // see @discordjs/ws for examples.
252
+ *
253
+ * // emit events over the broker
254
+ * gateway.on(WebSocketShardEvents.Dispatch, (...data) => void broker.publish(RedisGateway.toPublishArgs(data));
255
+ *
256
+ * // listen to payloads we should send to Discord
257
+ * broker.on(RedisGateway.GatewaySendEvent, async ({ data: { payload, shardId }, ack }) => {
258
+ * await gateway.send(shardId, payload);
259
+ * await ack();
260
+ * });
261
+ * await broker.subscribe([RedisGateway.GatewaySendEvent]);
262
+ * await gateway.connect();
263
+ * ```
264
+ *
265
+ * ```ts
266
+ * // other-service/index.ts
267
+ * import { RedisGateway, PubSubRedisBroker, kUseRandomGroupName } from '@discordjs/brokers';
268
+ * import Redis from 'ioredis';
269
+ *
270
+ * // the name here should absolutely be env-determined, see the main README for more information.
271
+ * const broker = new PubSubRedisBroker(new Redis(), { group: 'my-service-name', name: 'service-name-instance-1' });
272
+ * // unfortunately, we have to know the shard count. ideally this should be an env var
273
+ * const gateway = new RedisGateway(broker, Number.parseInt(process.env.SHARD_COUNT, 10));
274
+ *
275
+ * const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
276
+ * const client = new Client({ rest, gateway });
277
+ *
278
+ * // set up your client as you normally would with core
279
+ *
280
+ * // subscribe to the events that you want
281
+ * await gateway.init([GatewayDispatchEvents.GuildCreate, GatewayDispatchEvents.MessageCreate]);
282
+ * ```
283
+ */
284
+ declare class RedisGateway extends AsyncEventEmitter<{
285
+ dispatch: ManagerShardEventsMap[WebSocketShardEvents.Dispatch];
286
+ }> implements Gateway {
287
+ private readonly broker;
288
+ private readonly shardCount;
289
+ /**
290
+ * Event used over the broker used to tell shards to send a payload to Discord.
291
+ */
292
+ static readonly GatewaySendEvent: "gateway_send";
293
+ /**
294
+ * Converts a dispatch event from `@discordjs/ws` to arguments for a `broker.publish` call.
295
+ */
296
+ static toPublishArgs(data: [payload: GatewayDispatchPayload, shardId: number]): [GatewayDispatchEvents, BrokerIntrinsicProps & {
297
+ payload: GatewayDispatchPayload['d'];
298
+ }];
299
+ constructor(broker: PubSubRedisBroker<RedisBrokerDiscordEvents>, shardCount: number);
300
+ getShardCount(): number;
301
+ send(shardId: number, payload: GatewaySendPayload): Promise<void>;
302
+ init(events: GatewayDispatchEvents[]): Promise<void>;
303
+ }
304
+
210
305
  interface InternalPromise {
211
306
  reject(error: any): void;
212
307
  resolve(data: any): void;
@@ -283,4 +378,4 @@ declare class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses e
283
378
  */
284
379
  declare const version: string;
285
380
 
286
- export { type BaseBrokerOptions, BaseRedisBroker, DefaultBrokerOptions, DefaultRPCRedisBrokerOptions, DefaultRedisBrokerOptions, type IBaseBroker, type IPubSubBroker, type IRPCBroker, PubSubRedisBroker, RPCRedisBroker, type RPCRedisBrokerOptions, type RedisBrokerOptions, type ToEventMap, kUseRandomGroupName, version };
381
+ export { type BaseBrokerOptions, BaseRedisBroker, DefaultBrokerOptions, DefaultRPCRedisBrokerOptions, DefaultRedisBrokerOptions, type DiscordEvents, type IBaseBroker, type IPubSubBroker, type IRPCBroker, PubSubRedisBroker, RPCRedisBroker, type RPCRedisBrokerOptions, type RedisBrokerDiscordEvents, type RedisBrokerOptions, RedisGateway, type ToEventMap, kUseRandomGroupName, version };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
3
3
  import { Redis } from 'ioredis';
4
+ import { GatewayDispatchEvents, GatewayDispatchPayload, Gateway, GatewaySendPayload } from '@discordjs/core';
5
+ import { ManagerShardEventsMap, WebSocketShardEvents } from '@discordjs/ws';
4
6
  import Redis$1 from 'ioredis/built/Redis.js';
5
7
 
6
8
  /**
@@ -207,6 +209,99 @@ declare class PubSubRedisBroker<TEvents extends Record<string, any>> extends Bas
207
209
  protected emitEvent(id: Buffer, group: string, event: string, data: unknown): void;
208
210
  }
209
211
 
212
+ type _DiscordEvents = {
213
+ [K in GatewayDispatchEvents]: GatewayDispatchPayload & {
214
+ t: K;
215
+ };
216
+ };
217
+ type DiscordEvents = {
218
+ [K in keyof _DiscordEvents]: _DiscordEvents[K]['d'];
219
+ };
220
+ interface BrokerIntrinsicProps {
221
+ shardId: number;
222
+ }
223
+ interface Events extends DiscordEvents {
224
+ [RedisGateway.GatewaySendEvent]: GatewaySendPayload;
225
+ }
226
+ type RedisBrokerDiscordEvents = {
227
+ [K in keyof Events]: BrokerIntrinsicProps & {
228
+ payload: Events[K];
229
+ };
230
+ };
231
+ /**
232
+ * RedisGateway is an implementation for core's Gateway interface built on top of our Redis brokers.
233
+ *
234
+ * Some important notes:
235
+ * - Instances for this class are for your consumers/services that need the gateway. naturally, the events passed into
236
+ * `init` are the only ones the core client will be able to emit
237
+ * - You can also opt to use the class as-is without `@discordjs/core`, if you so desire. Events are properly typed
238
+ * - You need to implement your own gateway service. Refer to the example below for how that would look like. This class
239
+ * offers some static methods and properties that help in this errand. It is extremely important that you `publish`
240
+ * events as the receiving service expects, and also that you handle GatewaySend events.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * // gateway-service/index.ts
245
+ * import { RedisGateway, PubSubRedisBroker, kUseRandomGroupName } from '@discordjs/brokers';
246
+ * import Redis from 'ioredis';
247
+ *
248
+ * // the `name` here probably should be env-determined if you need to scale this. see the main README for more information.
249
+ * // also, we use a random group because we do NOT want work-balancing on gateway_send events.
250
+ * const broker = new PubSubRedisBroker(new Redis(), { group: kUseRandomGroupName, name: 'send-consumer-1' });
251
+ * const gateway = new WebSocketManager(gatewayOptionsHere); // see @discordjs/ws for examples.
252
+ *
253
+ * // emit events over the broker
254
+ * gateway.on(WebSocketShardEvents.Dispatch, (...data) => void broker.publish(RedisGateway.toPublishArgs(data));
255
+ *
256
+ * // listen to payloads we should send to Discord
257
+ * broker.on(RedisGateway.GatewaySendEvent, async ({ data: { payload, shardId }, ack }) => {
258
+ * await gateway.send(shardId, payload);
259
+ * await ack();
260
+ * });
261
+ * await broker.subscribe([RedisGateway.GatewaySendEvent]);
262
+ * await gateway.connect();
263
+ * ```
264
+ *
265
+ * ```ts
266
+ * // other-service/index.ts
267
+ * import { RedisGateway, PubSubRedisBroker, kUseRandomGroupName } from '@discordjs/brokers';
268
+ * import Redis from 'ioredis';
269
+ *
270
+ * // the name here should absolutely be env-determined, see the main README for more information.
271
+ * const broker = new PubSubRedisBroker(new Redis(), { group: 'my-service-name', name: 'service-name-instance-1' });
272
+ * // unfortunately, we have to know the shard count. ideally this should be an env var
273
+ * const gateway = new RedisGateway(broker, Number.parseInt(process.env.SHARD_COUNT, 10));
274
+ *
275
+ * const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
276
+ * const client = new Client({ rest, gateway });
277
+ *
278
+ * // set up your client as you normally would with core
279
+ *
280
+ * // subscribe to the events that you want
281
+ * await gateway.init([GatewayDispatchEvents.GuildCreate, GatewayDispatchEvents.MessageCreate]);
282
+ * ```
283
+ */
284
+ declare class RedisGateway extends AsyncEventEmitter<{
285
+ dispatch: ManagerShardEventsMap[WebSocketShardEvents.Dispatch];
286
+ }> implements Gateway {
287
+ private readonly broker;
288
+ private readonly shardCount;
289
+ /**
290
+ * Event used over the broker used to tell shards to send a payload to Discord.
291
+ */
292
+ static readonly GatewaySendEvent: "gateway_send";
293
+ /**
294
+ * Converts a dispatch event from `@discordjs/ws` to arguments for a `broker.publish` call.
295
+ */
296
+ static toPublishArgs(data: [payload: GatewayDispatchPayload, shardId: number]): [GatewayDispatchEvents, BrokerIntrinsicProps & {
297
+ payload: GatewayDispatchPayload['d'];
298
+ }];
299
+ constructor(broker: PubSubRedisBroker<RedisBrokerDiscordEvents>, shardCount: number);
300
+ getShardCount(): number;
301
+ send(shardId: number, payload: GatewaySendPayload): Promise<void>;
302
+ init(events: GatewayDispatchEvents[]): Promise<void>;
303
+ }
304
+
210
305
  interface InternalPromise {
211
306
  reject(error: any): void;
212
307
  resolve(data: any): void;
@@ -283,4 +378,4 @@ declare class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses e
283
378
  */
284
379
  declare const version: string;
285
380
 
286
- export { type BaseBrokerOptions, BaseRedisBroker, DefaultBrokerOptions, DefaultRPCRedisBrokerOptions, DefaultRedisBrokerOptions, type IBaseBroker, type IPubSubBroker, type IRPCBroker, PubSubRedisBroker, RPCRedisBroker, type RPCRedisBrokerOptions, type RedisBrokerOptions, type ToEventMap, kUseRandomGroupName, version };
381
+ export { type BaseBrokerOptions, BaseRedisBroker, DefaultBrokerOptions, DefaultRPCRedisBrokerOptions, DefaultRedisBrokerOptions, type DiscordEvents, type IBaseBroker, type IPubSubBroker, type IRPCBroker, PubSubRedisBroker, RPCRedisBroker, type RPCRedisBrokerOptions, type RedisBrokerDiscordEvents, type RedisBrokerOptions, RedisGateway, type ToEventMap, kUseRandomGroupName, version };
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  DefaultRedisBrokerOptions: () => DefaultRedisBrokerOptions,
28
28
  PubSubRedisBroker: () => PubSubRedisBroker,
29
29
  RPCRedisBroker: () => RPCRedisBroker,
30
+ RedisGateway: () => RedisGateway,
30
31
  kUseRandomGroupName: () => kUseRandomGroupName,
31
32
  version: () => version
32
33
  });
@@ -51,7 +52,7 @@ var DefaultBrokerOptions = {
51
52
  };
52
53
 
53
54
  // src/brokers/redis/BaseRedis.ts
54
- var kUseRandomGroupName = /* @__PURE__ */ Symbol.for("djs.brokers.useRandomGroupName");
55
+ var kUseRandomGroupName = Symbol.for("djs.brokers.useRandomGroupName");
55
56
  var DefaultRedisBrokerOptions = {
56
57
  ...DefaultBrokerOptions,
57
58
  maxChunk: 10,
@@ -269,7 +270,53 @@ var PubSubRedisBroker = class extends BaseRedisBroker {
269
270
  }
270
271
  };
271
272
 
273
+ // src/brokers/redis/RedisGateway.ts
274
+ var import_async_event_emitter2 = require("@vladfrangu/async_event_emitter");
275
+ var RedisGateway = class extends import_async_event_emitter2.AsyncEventEmitter {
276
+ constructor(broker, shardCount) {
277
+ super();
278
+ this.broker = broker;
279
+ this.shardCount = shardCount;
280
+ }
281
+ static {
282
+ __name(this, "RedisGateway");
283
+ }
284
+ /**
285
+ * Event used over the broker used to tell shards to send a payload to Discord.
286
+ */
287
+ static GatewaySendEvent = "gateway_send";
288
+ /**
289
+ * Converts a dispatch event from `@discordjs/ws` to arguments for a `broker.publish` call.
290
+ */
291
+ static toPublishArgs(data) {
292
+ const [payload, shardId] = data;
293
+ return [payload.t, { shardId, payload: payload.d }];
294
+ }
295
+ getShardCount() {
296
+ return this.shardCount;
297
+ }
298
+ async send(shardId, payload) {
299
+ await this.broker.publish("gateway_send", { payload, shardId });
300
+ }
301
+ async init(events) {
302
+ for (const event of events) {
303
+ this.broker.on(
304
+ event,
305
+ ({
306
+ ack,
307
+ data: { payload, shardId }
308
+ }) => {
309
+ this.emit("dispatch", { shardId, data: payload });
310
+ void ack();
311
+ }
312
+ );
313
+ }
314
+ await this.broker.subscribe(events);
315
+ }
316
+ };
317
+
272
318
  // src/brokers/redis/RPCRedis.ts
319
+ var import_node_timers = require("timers");
273
320
  var DefaultRPCRedisBrokerOptions = {
274
321
  ...DefaultRedisBrokerOptions,
275
322
  timeout: 5e3
@@ -294,7 +341,7 @@ var RPCRedisBroker = class extends BaseRedisBroker {
294
341
  if (id && this.promises.has(id)) {
295
342
  const { resolve: resolve2, timeout } = this.promises.get(id);
296
343
  resolve2(this.options.decode(message));
297
- clearTimeout(timeout);
344
+ (0, import_node_timers.clearTimeout)(timeout);
298
345
  }
299
346
  });
300
347
  }
@@ -312,7 +359,7 @@ var RPCRedisBroker = class extends BaseRedisBroker {
312
359
  const timedOut = new Error(`timed out after ${timeoutDuration}ms`);
313
360
  await this.streamReadClient.subscribe(rpcChannel);
314
361
  return new Promise((resolve2, reject) => {
315
- const timeout = setTimeout(() => reject(timedOut), timeoutDuration).unref();
362
+ const timeout = (0, import_node_timers.setTimeout)(() => reject(timedOut), timeoutDuration).unref();
316
363
  this.promises.set(id, { resolve: resolve2, reject, timeout });
317
364
  }).finally(() => {
318
365
  void this.streamReadClient.unsubscribe(rpcChannel);
@@ -334,7 +381,7 @@ var RPCRedisBroker = class extends BaseRedisBroker {
334
381
  };
335
382
 
336
383
  // src/index.ts
337
- var version = "2.0.0-pr-11006.1765450794-e636950b2";
384
+ var version = "2.0.0-pr-11005.1765454364-f3f6d34e7";
338
385
  // Annotate the CommonJS export names for ESM import in node:
339
386
  0 && (module.exports = {
340
387
  BaseRedisBroker,
@@ -343,6 +390,7 @@ var version = "2.0.0-pr-11006.1765450794-e636950b2";
343
390
  DefaultRedisBrokerOptions,
344
391
  PubSubRedisBroker,
345
392
  RPCRedisBroker,
393
+ RedisGateway,
346
394
  kUseRandomGroupName,
347
395
  version
348
396
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/brokers/redis/BaseRedis.ts","../src/brokers/Broker.ts","../src/brokers/redis/PubSubRedis.ts","../src/brokers/redis/RPCRedis.ts"],"sourcesContent":["export * from './brokers/redis/BaseRedis.js';\nexport * from './brokers/redis/PubSubRedis.js';\nexport * from './brokers/redis/RPCRedis.js';\n\nexport * from './brokers/Broker.js';\n\n/**\n * The {@link https://github.com/discordjs/discord.js/blob/main/packages/brokers#readme | @discordjs/brokers} version\n * that you are currently using.\n *\n * @privateRemarks This needs to explicitly be `string` so it is not typed as a \"const string\" that gets injected by esbuild.\n */\nexport const version = '2.0.0-pr-11006.1765450794-e636950b2' as string;\n","import type { Buffer } from 'node:buffer';\nimport { randomBytes } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\nimport type { Redis } from 'ioredis';\nimport { ReplyError } from 'ioredis';\nimport type { BaseBrokerOptions, IBaseBroker, ToEventMap } from '../Broker.js';\nimport { DefaultBrokerOptions } from '../Broker.js';\n\ntype RedisReadGroupData = [Buffer, [Buffer, Buffer[]][]][];\n\n// For some reason ioredis doesn't have those typed, but they exist\ndeclare module 'ioredis' {\n\tinterface Redis {\n\t\txclaimBuffer(\n\t\t\tkey: Buffer | string,\n\t\t\tgroup: Buffer | string,\n\t\t\tconsumer: Buffer | string,\n\t\t\tminIdleTime: number,\n\t\t\tid: Buffer | string,\n\t\t\t...args: (Buffer | string)[]\n\t\t): Promise<string[]>;\n\t\txreadgroupBuffer(...args: (Buffer | string)[]): Promise<RedisReadGroupData | null>;\n\t}\n}\n\nexport const kUseRandomGroupName = Symbol.for('djs.brokers.useRandomGroupName');\n\n/**\n * Options specific for a Redis broker\n */\nexport interface RedisBrokerOptions extends BaseBrokerOptions {\n\t/**\n\t * How long to block for messages when polling\n\t */\n\tblockTimeout?: number;\n\t/**\n\t * Consumer group name to use for this broker. For fanning out events, use {@link kUseRandomGroupName}\n\t *\n\t * @see {@link https://redis.io/commands/xreadgroup/}\n\t */\n\tgroup: string | typeof kUseRandomGroupName;\n\t/**\n\t * Max number of messages to poll at once\n\t */\n\tmaxChunk?: number;\n\t/**\n\t * How many times a message can be delivered to a consumer before it is considered dead.\n\t * This is used to prevent messages from being stuck in the queue forever if a consumer is\n\t * unable to process them.\n\t */\n\tmaxDeliveredTimes?: number;\n\t/**\n\t * How long a message should be idle for before allowing it to be claimed by another consumer.\n\t * Note that too high of a value can lead to a high delay in processing messages during a service downscale,\n\t * while too low of a value can lead to messages being too eagerly claimed by other consumers during an instance\n\t * restart (which is most likely not actually that problematic)\n\t */\n\tmessageIdleTime?: number;\n\t/**\n\t * Unique consumer name.\n\t *\n\t * @see {@link https://redis.io/commands/xreadgroup/}\n\t */\n\tname: string;\n}\n\n/**\n * Default broker options for redis\n */\nexport const DefaultRedisBrokerOptions = {\n\t...DefaultBrokerOptions,\n\tmaxChunk: 10,\n\tmaxDeliveredTimes: 3,\n\tmessageIdleTime: 3_000,\n\tblockTimeout: 5_000,\n} as const satisfies Required<Omit<RedisBrokerOptions, 'group' | 'name'>>;\n\n/**\n * Helper class with shared Redis logic\n */\nexport abstract class BaseRedisBroker<\n\tTEvents extends Record<string, any[]>,\n\tTResponses extends Record<keyof TEvents, any> | undefined = undefined,\n>\n\textends AsyncEventEmitter<ToEventMap<TEvents, TResponses>>\n\timplements IBaseBroker<TEvents>\n{\n\t/**\n\t * Used for Redis queues, see the 3rd argument taken by {@link https://redis.io/commands/xadd | xadd}\n\t */\n\tpublic static readonly STREAM_DATA_KEY = 'data' as const;\n\n\t/**\n\t * Options this broker is using\n\t */\n\tprotected readonly options: Required<RedisBrokerOptions>;\n\n\t/**\n\t * Events this broker has subscribed to\n\t */\n\tprotected readonly subscribedEvents = new Set<string>();\n\n\t/**\n\t * Internal copy of the Redis client being used to read incoming payloads\n\t */\n\tprotected readonly streamReadClient: Redis;\n\n\t/**\n\t * The group being used by this broker.\n\t *\n\t * @privateRemarks\n\t * Stored as its own field to do the \"use random group\" resolution in the constructor.\n\t */\n\tprotected readonly group: string;\n\n\t/**\n\t * Whether this broker is currently polling events\n\t */\n\tprotected listening = false;\n\n\tpublic constructor(\n\t\tprotected readonly redisClient: Redis,\n\t\toptions: RedisBrokerOptions,\n\t) {\n\t\tsuper();\n\t\tthis.options = { ...DefaultRedisBrokerOptions, ...options };\n\t\tthis.group = this.options.group === kUseRandomGroupName ? randomBytes(16).toString('hex') : this.options.group;\n\t\tredisClient.defineCommand('xcleangroup', {\n\t\t\tnumberOfKeys: 1,\n\t\t\tlua: readFileSync(resolve(__dirname, '..', 'scripts', 'xcleangroup.lua'), 'utf8'),\n\t\t});\n\t\tthis.streamReadClient = redisClient.duplicate();\n\t}\n\n\t/**\n\t * {@inheritDoc IBaseBroker.subscribe}\n\t */\n\tpublic async subscribe(events: (keyof TEvents)[]): Promise<void> {\n\t\tawait Promise.all(\n\t\t\t// @ts-expect-error: Intended\n\t\t\tevents.map(async (event) => {\n\t\t\t\tthis.subscribedEvents.add(event as string);\n\t\t\t\ttry {\n\t\t\t\t\treturn await this.redisClient.xgroup('CREATE', event as string, this.group, 0, 'MKSTREAM');\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!(error instanceof ReplyError)) {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t\tvoid this.listen();\n\t}\n\n\t/**\n\t * {@inheritDoc IBaseBroker.unsubscribe}\n\t */\n\tpublic async unsubscribe(events: (keyof TEvents)[]): Promise<void> {\n\t\tconst commands: unknown[][] = Array.from({ length: events.length * 2 });\n\t\tfor (let idx = 0; idx < commands.length; idx += 2) {\n\t\t\tconst event = events[idx / 2];\n\t\t\tcommands[idx] = ['xgroup', 'delconsumer', event as string, this.options.group, this.options.name];\n\t\t\tcommands[idx + 1] = ['xcleangroup', event as string, this.options.group];\n\t\t}\n\n\t\tawait this.redisClient.pipeline(commands).exec();\n\n\t\tfor (const event of events) {\n\t\t\tthis.subscribedEvents.delete(event as string);\n\t\t}\n\t}\n\n\t/**\n\t * Begins polling for events, firing them to {@link BaseRedisBroker.emitEvent}\n\t */\n\tprotected async listen(): Promise<void> {\n\t\tif (this.listening) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.listening = true;\n\n\t\t// Enter regular polling\n\t\twhile (this.subscribedEvents.size > 0) {\n\t\t\ttry {\n\t\t\t\tawait this.claimAndEmitDeadEvents();\n\t\t\t} catch (error) {\n\t\t\t\t// @ts-expect-error: Intended\n\t\t\t\tthis.emit('error', error);\n\t\t\t\t// We don't break here to keep the loop running even if dead event processing fails\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// As per docs, '>' means \"give me a new message\"\n\t\t\t\tconst data = await this.readGroup('>', this.options.blockTimeout);\n\t\t\t\tif (!data) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait this.processMessages(data);\n\t\t\t} catch (error) {\n\t\t\t\t// @ts-expect-error: Intended\n\t\t\t\tthis.emit('error', error);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tthis.listening = false;\n\t}\n\n\tprivate async readGroup(fromId: string, block: number): Promise<RedisReadGroupData> {\n\t\tconst data = await this.streamReadClient.xreadgroupBuffer(\n\t\t\t'GROUP',\n\t\t\tthis.group,\n\t\t\tthis.options.name,\n\t\t\t'COUNT',\n\t\t\tString(this.options.maxChunk),\n\t\t\t'BLOCK',\n\t\t\tString(block),\n\t\t\t'STREAMS',\n\t\t\t...this.subscribedEvents,\n\t\t\t...Array.from({ length: this.subscribedEvents.size }, () => fromId),\n\t\t);\n\n\t\treturn data ?? [];\n\t}\n\n\tprivate async processMessages(data: RedisReadGroupData): Promise<void> {\n\t\tfor (const [event, messages] of data) {\n\t\t\tconst eventName = event.toString('utf8');\n\n\t\t\tfor (const [id, packet] of messages) {\n\t\t\t\tconst idx = packet.findIndex((value, idx) => value.toString('utf8') === 'data' && idx % 2 === 0);\n\t\t\t\tif (idx < 0) continue;\n\n\t\t\t\tconst payload = packet[idx + 1];\n\t\t\t\tif (!payload) continue;\n\n\t\t\t\tthis.emitEvent(id, this.group, eventName, this.options.decode(payload));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async claimAndEmitDeadEvents(): Promise<void> {\n\t\tfor (const stream of this.subscribedEvents) {\n\t\t\t// Get up to N oldest pending messages (note: a pending message is a message that has been read, but never ACKed)\n\t\t\tconst pending = (await this.streamReadClient.xpending(\n\t\t\t\tstream,\n\t\t\t\tthis.group,\n\t\t\t\t'-',\n\t\t\t\t'+',\n\t\t\t\tthis.options.maxChunk,\n\t\t\t\t// See: https://redis.io/docs/latest/commands/xpending/#extended-form-of-xpending\n\t\t\t)) as [id: string, consumer: string, idleMs: number, deliveredTimes: number][];\n\n\t\t\tfor (const [id, consumer, idleMs, deliveredTimes] of pending) {\n\t\t\t\t// Technically xclaim checks for us anyway, but why not avoid an extra call?\n\t\t\t\tif (idleMs < this.options.messageIdleTime) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (deliveredTimes > this.options.maxDeliveredTimes) {\n\t\t\t\t\t// This message is dead. It has repeatedly failed being processed by a consumer.\n\t\t\t\t\tawait this.streamReadClient.xdel(stream, this.group, id);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Try to claim the message if we don't already own it (this may fail if another consumer has already claimed it)\n\t\t\t\tif (consumer !== this.options.name) {\n\t\t\t\t\tconst claimed = await this.streamReadClient.xclaimBuffer(\n\t\t\t\t\t\tstream,\n\t\t\t\t\t\tthis.group,\n\t\t\t\t\t\tthis.options.name,\n\t\t\t\t\t\tMath.max(this.options.messageIdleTime, 1),\n\t\t\t\t\t\tid,\n\t\t\t\t\t\t'JUSTID',\n\t\t\t\t\t);\n\n\t\t\t\t\t// Another consumer got the message before us\n\t\t\t\t\tif (!claimed?.length) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Fetch message body\n\t\t\t\tconst entries = await this.streamReadClient.xrangeBuffer(stream, id, id);\n\t\t\t\t// No idea how this could happen, frankly!\n\t\t\t\tif (!entries?.length) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst [msgId, fields] = entries[0]!;\n\t\t\t\tconst idx = fields.findIndex((value, idx) => value.toString('utf8') === 'data' && idx % 2 === 0);\n\t\t\t\tif (idx < 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst payload = fields[idx + 1];\n\t\t\t\tif (!payload) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tthis.emitEvent(msgId, this.group, stream, this.options.decode(payload));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Destroys the broker, closing all connections\n\t */\n\tpublic async destroy() {\n\t\tawait this.unsubscribe([...this.subscribedEvents]);\n\t\tthis.streamReadClient.disconnect();\n\t\tthis.redisClient.disconnect();\n\t}\n\n\t/**\n\t * Handles an incoming Redis event\n\t */\n\tprotected abstract emitEvent(id: Buffer, group: string, event: string, data: unknown): unknown;\n}\n","import { Buffer } from 'node:buffer';\nimport { encode, decode } from '@msgpack/msgpack';\nimport type { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\n\n/**\n * Base options for a broker implementation\n */\nexport interface BaseBrokerOptions {\n\t/**\n\t * Function to use for decoding messages\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tdecode?: (data: Buffer) => unknown;\n\t/**\n\t * Function to use for encoding messages\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tencode?: (data: unknown) => Buffer;\n}\n\n/**\n * Default broker options\n */\nexport const DefaultBrokerOptions = {\n\tencode: (data): Buffer => {\n\t\tconst encoded = encode(data);\n\t\treturn Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);\n\t},\n\tdecode: (data): unknown => decode(data),\n} as const satisfies Required<BaseBrokerOptions>;\n\nexport type ToEventMap<\n\tTRecord extends Record<string, any[]>,\n\tTResponses extends Record<keyof TRecord, any> | undefined = undefined,\n> = {\n\t[TKey in keyof TRecord]: [\n\t\tevent: TResponses extends Record<keyof TRecord, any>\n\t\t\t? { ack(): Promise<void>; reply(data: TResponses[TKey]): Promise<void> }\n\t\t\t: { ack(): Promise<void>; data: TRecord[TKey] },\n\t];\n};\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IBaseBroker<TEvents extends {}> {\n\t/**\n\t * Subscribes to the given events\n\t */\n\tsubscribe(events: (keyof TEvents)[]): Promise<void>;\n\t/**\n\t * Unsubscribes from the given events\n\t */\n\tunsubscribe(events: (keyof TEvents)[]): Promise<void>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IPubSubBroker<TEvents extends {}>\n\textends IBaseBroker<TEvents>, AsyncEventEmitter<ToEventMap<TEvents>> {\n\t/**\n\t * Publishes an event\n\t */\n\tpublish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void>;\n}\n\nexport interface IRPCBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>\n\textends IBaseBroker<TEvents>, AsyncEventEmitter<ToEventMap<TEvents, TResponses>> {\n\t/**\n\t * Makes an RPC call\n\t */\n\tcall<Event extends keyof TEvents>(\n\t\tevent: Event,\n\t\tdata: TEvents[Event],\n\t\ttimeoutDuration?: number,\n\t): Promise<TResponses[Event]>;\n}\n","import type { Buffer } from 'node:buffer';\nimport type { IPubSubBroker } from '../Broker.js';\nimport { BaseRedisBroker } from './BaseRedis.js';\n\n/**\n * PubSub broker powered by Redis\n *\n * @example\n * ```ts\n * // publisher.js\n * import { PubSubRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new PubSubRedisBroker(new Redis());\n *\n * await broker.publish('test', 'Hello World!');\n * await broker.destroy();\n *\n * // subscriber.js\n * import { PubSubRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new PubSubRedisBroker(new Redis());\n * \tbroker.on('test', ({ data, ack }) => {\n * \tconsole.log(data);\n * \tvoid ack();\n * });\n *\n * await broker.subscribe('subscribers', ['test']);\n * ```\n */\nexport class PubSubRedisBroker<TEvents extends Record<string, any>>\n\textends BaseRedisBroker<TEvents>\n\timplements IPubSubBroker<TEvents>\n{\n\t/**\n\t * {@inheritDoc IPubSubBroker.publish}\n\t */\n\tpublic async publish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void> {\n\t\tawait this.redisClient.xadd(event as string, '*', BaseRedisBroker.STREAM_DATA_KEY, this.options.encode(data));\n\t}\n\n\tprotected emitEvent(id: Buffer, group: string, event: string, data: unknown) {\n\t\tconst payload: { ack(): Promise<void>; data: unknown } = {\n\t\t\tdata,\n\t\t\tack: async () => {\n\t\t\t\tawait this.redisClient.xack(event, group, id);\n\t\t\t},\n\t\t};\n\n\t\t// @ts-expect-error: Intended\n\t\tthis.emit(event, payload);\n\t}\n}\n","import type { Buffer } from 'node:buffer';\nimport type Redis from 'ioredis/built/Redis.js';\nimport type { IRPCBroker } from '../Broker.js';\nimport type { RedisBrokerOptions } from './BaseRedis.js';\nimport { BaseRedisBroker, DefaultRedisBrokerOptions } from './BaseRedis.js';\n\ninterface InternalPromise {\n\treject(error: any): void;\n\tresolve(data: any): void;\n\ttimeout: NodeJS.Timeout;\n}\n\n/**\n * Options specific for an RPC Redis broker\n */\nexport interface RPCRedisBrokerOptions extends RedisBrokerOptions {\n\ttimeout?: number;\n}\n\n/**\n * Default values used for the {@link RPCRedisBrokerOptions}\n */\nexport const DefaultRPCRedisBrokerOptions = {\n\t...DefaultRedisBrokerOptions,\n\ttimeout: 5_000,\n} as const satisfies Required<Omit<RPCRedisBrokerOptions, 'group' | 'name'>>;\n\n/**\n * RPC broker powered by Redis\n *\n * @example\n * ```ts\n * // caller.js\n * import { RPCRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new RPCRedisBroker(new Redis());\n *\n * console.log(await broker.call('testcall', 'Hello World!'));\n * await broker.destroy();\n *\n * // responder.js\n * import { RPCRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new RPCRedisBroker(new Redis());\n * broker.on('testcall', ({ data, ack, reply }) => {\n * \tconsole.log('responder', data);\n * \tvoid ack();\n * \tvoid reply(`Echo: ${data}`);\n * });\n *\n * await broker.subscribe('responders', ['testcall']);\n * ```\n */\nexport class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>\n\textends BaseRedisBroker<TEvents, TResponses>\n\timplements IRPCBroker<TEvents, TResponses>\n{\n\t/**\n\t * Options this broker is using\n\t */\n\tprotected override readonly options: Required<RPCRedisBrokerOptions>;\n\n\t/**\n\t * @internal\n\t */\n\tprotected readonly promises = new Map<string, InternalPromise>();\n\n\tpublic constructor(redisClient: Redis, options: RPCRedisBrokerOptions) {\n\t\tsuper(redisClient, options);\n\t\tthis.options = { ...DefaultRPCRedisBrokerOptions, ...options };\n\n\t\tthis.streamReadClient.on('messageBuffer', (channel: Buffer, message: Buffer) => {\n\t\t\tconst [, id] = channel.toString().split(':');\n\t\t\tif (id && this.promises.has(id)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/unbound-method\n\t\t\t\tconst { resolve, timeout } = this.promises.get(id)!;\n\t\t\t\tresolve(this.options.decode(message));\n\t\t\t\tclearTimeout(timeout);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * {@inheritDoc IRPCBroker.call}\n\t */\n\tpublic async call<Event extends keyof TEvents>(\n\t\tevent: Event,\n\t\tdata: TEvents[Event],\n\t\ttimeoutDuration: number = this.options.timeout,\n\t): Promise<TResponses[Event]> {\n\t\tconst id = await this.redisClient.xadd(\n\t\t\tevent as string,\n\t\t\t'*',\n\t\t\tBaseRedisBroker.STREAM_DATA_KEY,\n\t\t\tthis.options.encode(data),\n\t\t);\n\t\t// This id! assertion is valid. From redis docs:\n\t\t// \"The command returns a Null reply when used with the NOMKSTREAM option and the key doesn't exist.\"\n\t\t// See: https://redis.io/commands/xadd/\n\t\tconst rpcChannel = `${event as string}:${id!}`;\n\n\t\t// Construct the error here for better stack traces\n\t\tconst timedOut = new Error(`timed out after ${timeoutDuration}ms`);\n\n\t\tawait this.streamReadClient.subscribe(rpcChannel);\n\t\treturn new Promise<TResponses[Event]>((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => reject(timedOut), timeoutDuration).unref();\n\n\t\t\tthis.promises.set(id!, { resolve, reject, timeout });\n\t\t\t// eslint-disable-next-line promise/prefer-await-to-then\n\t\t}).finally(() => {\n\t\t\tvoid this.streamReadClient.unsubscribe(rpcChannel);\n\t\t\tthis.promises.delete(id!);\n\t\t});\n\t}\n\n\tprotected emitEvent(id: Buffer, event: string, data: unknown) {\n\t\tconst payload: { ack(): Promise<void>; data: unknown; reply(data: unknown): Promise<void> } = {\n\t\t\tdata,\n\t\t\tack: async () => {\n\t\t\t\tawait this.redisClient.xack(event, this.group, id);\n\t\t\t},\n\t\t\treply: async (data) => {\n\t\t\t\tawait this.redisClient.publish(`${event}:${id.toString()}`, this.options.encode(data));\n\t\t\t},\n\t\t};\n\n\t\t// @ts-expect-error: Intended\n\t\tthis.emit(event, payload);\n\t}\n}\n"],"mappings":"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","names":["idx","resolve","data"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/brokers/redis/BaseRedis.ts","../src/brokers/Broker.ts","../src/brokers/redis/PubSubRedis.ts","../src/brokers/redis/RedisGateway.ts","../src/brokers/redis/RPCRedis.ts"],"sourcesContent":["export * from './brokers/redis/BaseRedis.js';\nexport * from './brokers/redis/PubSubRedis.js';\nexport * from './brokers/redis/RedisGateway.js';\nexport * from './brokers/redis/RPCRedis.js';\n\nexport * from './brokers/Broker.js';\n\n/**\n * The {@link https://github.com/discordjs/discord.js/blob/main/packages/brokers#readme | @discordjs/brokers} version\n * that you are currently using.\n *\n * @privateRemarks This needs to explicitly be `string` so it is not typed as a \"const string\" that gets injected by esbuild.\n */\nexport const version = '2.0.0-pr-11005.1765454364-f3f6d34e7' as string;\n","import type { Buffer } from 'node:buffer';\nimport { randomBytes } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\nimport type { Redis } from 'ioredis';\nimport { ReplyError } from 'ioredis';\nimport type { BaseBrokerOptions, IBaseBroker, ToEventMap } from '../Broker.js';\nimport { DefaultBrokerOptions } from '../Broker.js';\n\ntype RedisReadGroupData = [Buffer, [Buffer, Buffer[]][]][];\n\n// For some reason ioredis doesn't have those typed, but they exist\ndeclare module 'ioredis' {\n\tinterface Redis {\n\t\txclaimBuffer(\n\t\t\tkey: Buffer | string,\n\t\t\tgroup: Buffer | string,\n\t\t\tconsumer: Buffer | string,\n\t\t\tminIdleTime: number,\n\t\t\tid: Buffer | string,\n\t\t\t...args: (Buffer | string)[]\n\t\t): Promise<string[]>;\n\t\txreadgroupBuffer(...args: (Buffer | string)[]): Promise<RedisReadGroupData | null>;\n\t}\n}\n\nexport const kUseRandomGroupName = Symbol.for('djs.brokers.useRandomGroupName');\n\n/**\n * Options specific for a Redis broker\n */\nexport interface RedisBrokerOptions extends BaseBrokerOptions {\n\t/**\n\t * How long to block for messages when polling\n\t */\n\tblockTimeout?: number;\n\t/**\n\t * Consumer group name to use for this broker. For fanning out events, use {@link kUseRandomGroupName}\n\t *\n\t * @see {@link https://redis.io/commands/xreadgroup/}\n\t */\n\tgroup: string | typeof kUseRandomGroupName;\n\t/**\n\t * Max number of messages to poll at once\n\t */\n\tmaxChunk?: number;\n\t/**\n\t * How many times a message can be delivered to a consumer before it is considered dead.\n\t * This is used to prevent messages from being stuck in the queue forever if a consumer is\n\t * unable to process them.\n\t */\n\tmaxDeliveredTimes?: number;\n\t/**\n\t * How long a message should be idle for before allowing it to be claimed by another consumer.\n\t * Note that too high of a value can lead to a high delay in processing messages during a service downscale,\n\t * while too low of a value can lead to messages being too eagerly claimed by other consumers during an instance\n\t * restart (which is most likely not actually that problematic)\n\t */\n\tmessageIdleTime?: number;\n\t/**\n\t * Unique consumer name.\n\t *\n\t * @see {@link https://redis.io/commands/xreadgroup/}\n\t */\n\tname: string;\n}\n\n/**\n * Default broker options for redis\n */\nexport const DefaultRedisBrokerOptions = {\n\t...DefaultBrokerOptions,\n\tmaxChunk: 10,\n\tmaxDeliveredTimes: 3,\n\tmessageIdleTime: 3_000,\n\tblockTimeout: 5_000,\n} as const satisfies Required<Omit<RedisBrokerOptions, 'group' | 'name'>>;\n\n/**\n * Helper class with shared Redis logic\n */\nexport abstract class BaseRedisBroker<\n\t\tTEvents extends Record<string, any[]>,\n\t\tTResponses extends Record<keyof TEvents, any> | undefined = undefined,\n\t>\n\textends AsyncEventEmitter<ToEventMap<TEvents, TResponses>>\n\timplements IBaseBroker<TEvents>\n{\n\t/**\n\t * Used for Redis queues, see the 3rd argument taken by {@link https://redis.io/commands/xadd | xadd}\n\t */\n\tpublic static readonly STREAM_DATA_KEY = 'data' as const;\n\n\t/**\n\t * Options this broker is using\n\t */\n\tprotected readonly options: Required<RedisBrokerOptions>;\n\n\t/**\n\t * Events this broker has subscribed to\n\t */\n\tprotected readonly subscribedEvents = new Set<string>();\n\n\t/**\n\t * Internal copy of the Redis client being used to read incoming payloads\n\t */\n\tprotected readonly streamReadClient: Redis;\n\n\t/**\n\t * The group being used by this broker.\n\t *\n\t * @privateRemarks\n\t * Stored as its own field to do the \"use random group\" resolution in the constructor.\n\t */\n\tprotected readonly group: string;\n\n\t/**\n\t * Whether this broker is currently polling events\n\t */\n\tprotected listening = false;\n\n\tpublic constructor(\n\t\tprotected readonly redisClient: Redis,\n\t\toptions: RedisBrokerOptions,\n\t) {\n\t\tsuper();\n\t\tthis.options = { ...DefaultRedisBrokerOptions, ...options };\n\t\tthis.group = this.options.group === kUseRandomGroupName ? randomBytes(16).toString('hex') : this.options.group;\n\t\tredisClient.defineCommand('xcleangroup', {\n\t\t\tnumberOfKeys: 1,\n\t\t\tlua: readFileSync(resolve(__dirname, '..', 'scripts', 'xcleangroup.lua'), 'utf8'),\n\t\t});\n\t\tthis.streamReadClient = redisClient.duplicate();\n\t}\n\n\t/**\n\t * {@inheritDoc IBaseBroker.subscribe}\n\t */\n\tpublic async subscribe(events: (keyof TEvents)[]): Promise<void> {\n\t\tawait Promise.all(\n\t\t\t// @ts-expect-error: Intended\n\t\t\tevents.map(async (event) => {\n\t\t\t\tthis.subscribedEvents.add(event as string);\n\t\t\t\ttry {\n\t\t\t\t\treturn await this.redisClient.xgroup('CREATE', event as string, this.group, 0, 'MKSTREAM');\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!(error instanceof ReplyError)) {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t\tvoid this.listen();\n\t}\n\n\t/**\n\t * {@inheritDoc IBaseBroker.unsubscribe}\n\t */\n\tpublic async unsubscribe(events: (keyof TEvents)[]): Promise<void> {\n\t\tconst commands: unknown[][] = Array.from({ length: events.length * 2 });\n\t\tfor (let idx = 0; idx < commands.length; idx += 2) {\n\t\t\tconst event = events[idx / 2];\n\t\t\tcommands[idx] = ['xgroup', 'delconsumer', event as string, this.options.group, this.options.name];\n\t\t\tcommands[idx + 1] = ['xcleangroup', event as string, this.options.group];\n\t\t}\n\n\t\tawait this.redisClient.pipeline(commands).exec();\n\n\t\tfor (const event of events) {\n\t\t\tthis.subscribedEvents.delete(event as string);\n\t\t}\n\t}\n\n\t/**\n\t * Begins polling for events, firing them to {@link BaseRedisBroker.emitEvent}\n\t */\n\tprotected async listen(): Promise<void> {\n\t\tif (this.listening) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.listening = true;\n\n\t\t// Enter regular polling\n\t\twhile (this.subscribedEvents.size > 0) {\n\t\t\ttry {\n\t\t\t\tawait this.claimAndEmitDeadEvents();\n\t\t\t} catch (error) {\n\t\t\t\t// @ts-expect-error: Intended\n\t\t\t\tthis.emit('error', error);\n\t\t\t\t// We don't break here to keep the loop running even if dead event processing fails\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// As per docs, '>' means \"give me a new message\"\n\t\t\t\tconst data = await this.readGroup('>', this.options.blockTimeout);\n\t\t\t\tif (!data) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait this.processMessages(data);\n\t\t\t} catch (error) {\n\t\t\t\t// @ts-expect-error: Intended\n\t\t\t\tthis.emit('error', error);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tthis.listening = false;\n\t}\n\n\tprivate async readGroup(fromId: string, block: number): Promise<RedisReadGroupData> {\n\t\tconst data = await this.streamReadClient.xreadgroupBuffer(\n\t\t\t'GROUP',\n\t\t\tthis.group,\n\t\t\tthis.options.name,\n\t\t\t'COUNT',\n\t\t\tString(this.options.maxChunk),\n\t\t\t'BLOCK',\n\t\t\tString(block),\n\t\t\t'STREAMS',\n\t\t\t...this.subscribedEvents,\n\t\t\t...Array.from({ length: this.subscribedEvents.size }, () => fromId),\n\t\t);\n\n\t\treturn data ?? [];\n\t}\n\n\tprivate async processMessages(data: RedisReadGroupData): Promise<void> {\n\t\tfor (const [event, messages] of data) {\n\t\t\tconst eventName = event.toString('utf8');\n\n\t\t\tfor (const [id, packet] of messages) {\n\t\t\t\tconst idx = packet.findIndex((value, idx) => value.toString('utf8') === 'data' && idx % 2 === 0);\n\t\t\t\tif (idx < 0) continue;\n\n\t\t\t\tconst payload = packet[idx + 1];\n\t\t\t\tif (!payload) continue;\n\n\t\t\t\tthis.emitEvent(id, this.group, eventName, this.options.decode(payload));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async claimAndEmitDeadEvents(): Promise<void> {\n\t\tfor (const stream of this.subscribedEvents) {\n\t\t\t// Get up to N oldest pending messages (note: a pending message is a message that has been read, but never ACKed)\n\t\t\tconst pending = (await this.streamReadClient.xpending(\n\t\t\t\tstream,\n\t\t\t\tthis.group,\n\t\t\t\t'-',\n\t\t\t\t'+',\n\t\t\t\tthis.options.maxChunk,\n\t\t\t\t// See: https://redis.io/docs/latest/commands/xpending/#extended-form-of-xpending\n\t\t\t)) as [id: string, consumer: string, idleMs: number, deliveredTimes: number][];\n\n\t\t\tfor (const [id, consumer, idleMs, deliveredTimes] of pending) {\n\t\t\t\t// Technically xclaim checks for us anyway, but why not avoid an extra call?\n\t\t\t\tif (idleMs < this.options.messageIdleTime) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (deliveredTimes > this.options.maxDeliveredTimes) {\n\t\t\t\t\t// This message is dead. It has repeatedly failed being processed by a consumer.\n\t\t\t\t\tawait this.streamReadClient.xdel(stream, this.group, id);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Try to claim the message if we don't already own it (this may fail if another consumer has already claimed it)\n\t\t\t\tif (consumer !== this.options.name) {\n\t\t\t\t\tconst claimed = await this.streamReadClient.xclaimBuffer(\n\t\t\t\t\t\tstream,\n\t\t\t\t\t\tthis.group,\n\t\t\t\t\t\tthis.options.name,\n\t\t\t\t\t\tMath.max(this.options.messageIdleTime, 1),\n\t\t\t\t\t\tid,\n\t\t\t\t\t\t'JUSTID',\n\t\t\t\t\t);\n\n\t\t\t\t\t// Another consumer got the message before us\n\t\t\t\t\tif (!claimed?.length) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Fetch message body\n\t\t\t\tconst entries = await this.streamReadClient.xrangeBuffer(stream, id, id);\n\t\t\t\t// No idea how this could happen, frankly!\n\t\t\t\tif (!entries?.length) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst [msgId, fields] = entries[0]!;\n\t\t\t\tconst idx = fields.findIndex((value, idx) => value.toString('utf8') === 'data' && idx % 2 === 0);\n\t\t\t\tif (idx < 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst payload = fields[idx + 1];\n\t\t\t\tif (!payload) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tthis.emitEvent(msgId, this.group, stream, this.options.decode(payload));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Destroys the broker, closing all connections\n\t */\n\tpublic async destroy() {\n\t\tawait this.unsubscribe([...this.subscribedEvents]);\n\t\tthis.streamReadClient.disconnect();\n\t\tthis.redisClient.disconnect();\n\t}\n\n\t/**\n\t * Handles an incoming Redis event\n\t */\n\tprotected abstract emitEvent(id: Buffer, group: string, event: string, data: unknown): unknown;\n}\n","import { Buffer } from 'node:buffer';\nimport { encode, decode } from '@msgpack/msgpack';\nimport type { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\n\n/**\n * Base options for a broker implementation\n */\nexport interface BaseBrokerOptions {\n\t/**\n\t * Function to use for decoding messages\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tdecode?: (data: Buffer) => unknown;\n\t/**\n\t * Function to use for encoding messages\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tencode?: (data: unknown) => Buffer;\n}\n\n/**\n * Default broker options\n */\nexport const DefaultBrokerOptions = {\n\tencode: (data): Buffer => {\n\t\tconst encoded = encode(data);\n\t\treturn Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);\n\t},\n\tdecode: (data): unknown => decode(data),\n} as const satisfies Required<BaseBrokerOptions>;\n\nexport type ToEventMap<\n\tTRecord extends Record<string, any[]>,\n\tTResponses extends Record<keyof TRecord, any> | undefined = undefined,\n> = {\n\t[TKey in keyof TRecord]: [\n\t\tevent: TResponses extends Record<keyof TRecord, any>\n\t\t\t? { ack(): Promise<void>; reply(data: TResponses[TKey]): Promise<void> }\n\t\t\t: { ack(): Promise<void>; data: TRecord[TKey] },\n\t];\n};\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IBaseBroker<TEvents extends {}> {\n\t/**\n\t * Subscribes to the given events\n\t */\n\tsubscribe(events: (keyof TEvents)[]): Promise<void>;\n\t/**\n\t * Unsubscribes from the given events\n\t */\n\tunsubscribe(events: (keyof TEvents)[]): Promise<void>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IPubSubBroker<TEvents extends {}>\n\textends IBaseBroker<TEvents>,\n\t\tAsyncEventEmitter<ToEventMap<TEvents>> {\n\t/**\n\t * Publishes an event\n\t */\n\tpublish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void>;\n}\n\nexport interface IRPCBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>\n\textends IBaseBroker<TEvents>,\n\t\tAsyncEventEmitter<ToEventMap<TEvents, TResponses>> {\n\t/**\n\t * Makes an RPC call\n\t */\n\tcall<Event extends keyof TEvents>(\n\t\tevent: Event,\n\t\tdata: TEvents[Event],\n\t\ttimeoutDuration?: number,\n\t): Promise<TResponses[Event]>;\n}\n","import type { Buffer } from 'node:buffer';\nimport type { IPubSubBroker } from '../Broker.js';\nimport { BaseRedisBroker } from './BaseRedis.js';\n\n/**\n * PubSub broker powered by Redis\n *\n * @example\n * ```ts\n * // publisher.js\n * import { PubSubRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new PubSubRedisBroker(new Redis());\n *\n * await broker.publish('test', 'Hello World!');\n * await broker.destroy();\n *\n * // subscriber.js\n * import { PubSubRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new PubSubRedisBroker(new Redis());\n * \tbroker.on('test', ({ data, ack }) => {\n * \tconsole.log(data);\n * \tvoid ack();\n * });\n *\n * await broker.subscribe('subscribers', ['test']);\n * ```\n */\nexport class PubSubRedisBroker<TEvents extends Record<string, any>>\n\textends BaseRedisBroker<TEvents>\n\timplements IPubSubBroker<TEvents>\n{\n\t/**\n\t * {@inheritDoc IPubSubBroker.publish}\n\t */\n\tpublic async publish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void> {\n\t\tawait this.redisClient.xadd(event as string, '*', BaseRedisBroker.STREAM_DATA_KEY, this.options.encode(data));\n\t}\n\n\tprotected emitEvent(id: Buffer, group: string, event: string, data: unknown) {\n\t\tconst payload: { ack(): Promise<void>; data: unknown } = {\n\t\t\tdata,\n\t\t\tack: async () => {\n\t\t\t\tawait this.redisClient.xack(event, group, id);\n\t\t\t},\n\t\t};\n\n\t\t// @ts-expect-error: Intended\n\t\tthis.emit(event, payload);\n\t}\n}\n","import type { Gateway, GatewayDispatchPayload, GatewayDispatchEvents, GatewaySendPayload } from '@discordjs/core';\nimport type { ManagerShardEventsMap, WebSocketShardEvents } from '@discordjs/ws';\nimport { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\nimport type { PubSubRedisBroker } from './PubSubRedis.js';\n\n// need this to be its own type for some reason, the compiler doesn't behave the same way if we in-line it\ntype _DiscordEvents = {\n\t[K in GatewayDispatchEvents]: GatewayDispatchPayload & {\n\t\tt: K;\n\t};\n};\n\nexport type DiscordEvents = {\n\t[K in keyof _DiscordEvents]: _DiscordEvents[K]['d'];\n};\n\ninterface BrokerIntrinsicProps {\n\tshardId: number;\n}\n\ninterface Events extends DiscordEvents {\n\t// eslint-disable-next-line @typescript-eslint/no-use-before-define\n\t[RedisGateway.GatewaySendEvent]: GatewaySendPayload;\n}\n\nexport type RedisBrokerDiscordEvents = {\n\t[K in keyof Events]: BrokerIntrinsicProps & { payload: Events[K] };\n};\n\n/**\n * RedisGateway is an implementation for core's Gateway interface built on top of our Redis brokers.\n *\n * Some important notes:\n * - Instances for this class are for your consumers/services that need the gateway. naturally, the events passed into\n * `init` are the only ones the core client will be able to emit\n * - You can also opt to use the class as-is without `@discordjs/core`, if you so desire. Events are properly typed\n * - You need to implement your own gateway service. Refer to the example below for how that would look like. This class\n * offers some static methods and properties that help in this errand. It is extremely important that you `publish`\n * events as the receiving service expects, and also that you handle GatewaySend events.\n *\n * @example\n * ```ts\n * // gateway-service/index.ts\n * import { RedisGateway, PubSubRedisBroker, kUseRandomGroupName } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * // the `name` here probably should be env-determined if you need to scale this. see the main README for more information.\n * // also, we use a random group because we do NOT want work-balancing on gateway_send events.\n * const broker = new PubSubRedisBroker(new Redis(), { group: kUseRandomGroupName, name: 'send-consumer-1' });\n * const gateway = new WebSocketManager(gatewayOptionsHere); // see @discordjs/ws for examples.\n *\n * // emit events over the broker\n * gateway.on(WebSocketShardEvents.Dispatch, (...data) => void broker.publish(RedisGateway.toPublishArgs(data));\n *\n * // listen to payloads we should send to Discord\n * broker.on(RedisGateway.GatewaySendEvent, async ({ data: { payload, shardId }, ack }) => {\n * \tawait gateway.send(shardId, payload);\n * \tawait ack();\n * });\n * await broker.subscribe([RedisGateway.GatewaySendEvent]);\n * await gateway.connect();\n * ```\n *\n * ```ts\n * // other-service/index.ts\n * import { RedisGateway, PubSubRedisBroker, kUseRandomGroupName } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * // the name here should absolutely be env-determined, see the main README for more information.\n * const broker = new PubSubRedisBroker(new Redis(), { group: 'my-service-name', name: 'service-name-instance-1' });\n * // unfortunately, we have to know the shard count. ideally this should be an env var\n * const gateway = new RedisGateway(broker, Number.parseInt(process.env.SHARD_COUNT, 10));\n *\n * const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);\n * const client = new Client({ rest, gateway });\n *\n * // set up your client as you normally would with core\n *\n * // subscribe to the events that you want\n * await gateway.init([GatewayDispatchEvents.GuildCreate, GatewayDispatchEvents.MessageCreate]);\n * ```\n */\nexport class RedisGateway\n\textends AsyncEventEmitter<{ dispatch: ManagerShardEventsMap[WebSocketShardEvents.Dispatch] }>\n\timplements Gateway\n{\n\t/**\n\t * Event used over the broker used to tell shards to send a payload to Discord.\n\t */\n\tpublic static readonly GatewaySendEvent = 'gateway_send' as const;\n\n\t/**\n\t * Converts a dispatch event from `@discordjs/ws` to arguments for a `broker.publish` call.\n\t */\n\tpublic static toPublishArgs(\n\t\tdata: [payload: GatewayDispatchPayload, shardId: number],\n\t): [GatewayDispatchEvents, BrokerIntrinsicProps & { payload: GatewayDispatchPayload['d'] }] {\n\t\tconst [payload, shardId] = data;\n\t\treturn [payload.t, { shardId, payload: payload.d }];\n\t}\n\n\tpublic constructor(\n\t\tprivate readonly broker: PubSubRedisBroker<RedisBrokerDiscordEvents>,\n\t\tprivate readonly shardCount: number,\n\t) {\n\t\tsuper();\n\t}\n\n\tpublic getShardCount(): number {\n\t\treturn this.shardCount;\n\t}\n\n\tpublic async send(shardId: number, payload: GatewaySendPayload): Promise<void> {\n\t\tawait this.broker.publish('gateway_send', { payload, shardId });\n\t}\n\n\tpublic async init(events: GatewayDispatchEvents[]) {\n\t\tfor (const event of events) {\n\t\t\t// async_event_emitter nukes our types on this one.\n\t\t\tthis.broker.on(\n\t\t\t\tevent,\n\t\t\t\t({\n\t\t\t\t\tack,\n\t\t\t\t\tdata: { payload, shardId },\n\t\t\t\t}: {\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\t\t\tack: () => Promise<void>;\n\t\t\t\t\tdata: BrokerIntrinsicProps & { payload: any };\n\t\t\t\t}) => {\n\t\t\t\t\t// @ts-expect-error - Union shenanigans\n\t\t\t\t\tthis.emit('dispatch', { shardId, data: payload });\n\t\t\t\t\tvoid ack();\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tawait this.broker.subscribe(events);\n\t}\n}\n","import type { Buffer } from 'node:buffer';\nimport { clearTimeout, setTimeout } from 'node:timers';\nimport type Redis from 'ioredis/built/Redis.js';\nimport type { IRPCBroker } from '../Broker.js';\nimport type { RedisBrokerOptions } from './BaseRedis.js';\nimport { BaseRedisBroker, DefaultRedisBrokerOptions } from './BaseRedis.js';\n\ninterface InternalPromise {\n\treject(error: any): void;\n\tresolve(data: any): void;\n\ttimeout: NodeJS.Timeout;\n}\n\n/**\n * Options specific for an RPC Redis broker\n */\nexport interface RPCRedisBrokerOptions extends RedisBrokerOptions {\n\ttimeout?: number;\n}\n\n/**\n * Default values used for the {@link RPCRedisBrokerOptions}\n */\nexport const DefaultRPCRedisBrokerOptions = {\n\t...DefaultRedisBrokerOptions,\n\ttimeout: 5_000,\n} as const satisfies Required<Omit<RPCRedisBrokerOptions, 'group' | 'name'>>;\n\n/**\n * RPC broker powered by Redis\n *\n * @example\n * ```ts\n * // caller.js\n * import { RPCRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new RPCRedisBroker(new Redis());\n *\n * console.log(await broker.call('testcall', 'Hello World!'));\n * await broker.destroy();\n *\n * // responder.js\n * import { RPCRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new RPCRedisBroker(new Redis());\n * broker.on('testcall', ({ data, ack, reply }) => {\n * \tconsole.log('responder', data);\n * \tvoid ack();\n * \tvoid reply(`Echo: ${data}`);\n * });\n *\n * await broker.subscribe('responders', ['testcall']);\n * ```\n */\nexport class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>\n\textends BaseRedisBroker<TEvents, TResponses>\n\timplements IRPCBroker<TEvents, TResponses>\n{\n\t/**\n\t * Options this broker is using\n\t */\n\tprotected override readonly options: Required<RPCRedisBrokerOptions>;\n\n\t/**\n\t * @internal\n\t */\n\tprotected readonly promises = new Map<string, InternalPromise>();\n\n\tpublic constructor(redisClient: Redis, options: RPCRedisBrokerOptions) {\n\t\tsuper(redisClient, options);\n\t\tthis.options = { ...DefaultRPCRedisBrokerOptions, ...options };\n\n\t\tthis.streamReadClient.on('messageBuffer', (channel: Buffer, message: Buffer) => {\n\t\t\tconst [, id] = channel.toString().split(':');\n\t\t\tif (id && this.promises.has(id)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/unbound-method\n\t\t\t\tconst { resolve, timeout } = this.promises.get(id)!;\n\t\t\t\tresolve(this.options.decode(message));\n\t\t\t\tclearTimeout(timeout);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * {@inheritDoc IRPCBroker.call}\n\t */\n\tpublic async call<Event extends keyof TEvents>(\n\t\tevent: Event,\n\t\tdata: TEvents[Event],\n\t\ttimeoutDuration: number = this.options.timeout,\n\t): Promise<TResponses[Event]> {\n\t\tconst id = await this.redisClient.xadd(\n\t\t\tevent as string,\n\t\t\t'*',\n\t\t\tBaseRedisBroker.STREAM_DATA_KEY,\n\t\t\tthis.options.encode(data),\n\t\t);\n\t\t// This id! assertion is valid. From redis docs:\n\t\t// \"The command returns a Null reply when used with the NOMKSTREAM option and the key doesn't exist.\"\n\t\t// See: https://redis.io/commands/xadd/\n\t\tconst rpcChannel = `${event as string}:${id!}`;\n\n\t\t// Construct the error here for better stack traces\n\t\tconst timedOut = new Error(`timed out after ${timeoutDuration}ms`);\n\n\t\tawait this.streamReadClient.subscribe(rpcChannel);\n\t\treturn new Promise<TResponses[Event]>((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => reject(timedOut), timeoutDuration).unref();\n\n\t\t\tthis.promises.set(id!, { resolve, reject, timeout });\n\t\t\t// eslint-disable-next-line promise/prefer-await-to-then\n\t\t}).finally(() => {\n\t\t\tvoid this.streamReadClient.unsubscribe(rpcChannel);\n\t\t\tthis.promises.delete(id!);\n\t\t});\n\t}\n\n\tprotected emitEvent(id: Buffer, event: string, data: unknown) {\n\t\tconst payload: { ack(): Promise<void>; data: unknown; reply(data: unknown): Promise<void> } = {\n\t\t\tdata,\n\t\t\tack: async () => {\n\t\t\t\tawait this.redisClient.xack(event, this.group, id);\n\t\t\t},\n\t\t\treply: async (data) => {\n\t\t\t\tawait this.redisClient.publish(`${event}:${id.toString()}`, this.options.encode(data));\n\t\t\t},\n\t\t};\n\n\t\t// @ts-expect-error: Intended\n\t\tthis.emit(event, payload);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,yBAA4B;AAC5B,qBAA6B;AAC7B,uBAAwB;AACxB,iCAAkC;AAElC,qBAA2B;;;ACN3B,yBAAuB;AACvB,qBAA+B;AAsBxB,IAAM,uBAAuB;AAAA,EACnC,QAAQ,wBAAC,SAAiB;AACzB,UAAM,cAAU,uBAAO,IAAI;AAC3B,WAAO,0BAAO,KAAK,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,UAAU;AAAA,EAC1E,GAHQ;AAAA,EAIR,QAAQ,wBAAC,aAAkB,uBAAO,IAAI,GAA9B;AACT;;;ADFO,IAAM,sBAAsB,OAAO,IAAI,gCAAgC;AA4CvE,IAAM,4BAA4B;AAAA,EACxC,GAAG;AAAA,EACH,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,cAAc;AACf;AAKO,IAAe,kBAAf,cAIE,6CAET;AAAA,EAkCQ,YACa,aACnB,SACC;AACD,UAAM;AAHa;AAInB,SAAK,UAAU,EAAE,GAAG,2BAA2B,GAAG,QAAQ;AAC1D,SAAK,QAAQ,KAAK,QAAQ,UAAU,0BAAsB,gCAAY,EAAE,EAAE,SAAS,KAAK,IAAI,KAAK,QAAQ;AACzG,gBAAY,cAAc,eAAe;AAAA,MACxC,cAAc;AAAA,MACd,SAAK,iCAAa,0BAAQ,WAAW,MAAM,WAAW,iBAAiB,GAAG,MAAM;AAAA,IACjF,CAAC;AACD,SAAK,mBAAmB,YAAY,UAAU;AAAA,EAC/C;AAAA,EAtID,OAwFA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAIC,OAAuB,kBAAkB;AAAA;AAAA;AAAA;AAAA,EAKtB;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,oBAAI,IAAY;AAAA;AAAA;AAAA;AAAA,EAKnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA;AAAA;AAAA;AAAA;AAAA,EAKT,YAAY;AAAA;AAAA;AAAA;AAAA,EAmBtB,MAAa,UAAU,QAA0C;AAChE,UAAM,QAAQ;AAAA;AAAA,MAEb,OAAO,IAAI,OAAO,UAAU;AAC3B,aAAK,iBAAiB,IAAI,KAAe;AACzC,YAAI;AACH,iBAAO,MAAM,KAAK,YAAY,OAAO,UAAU,OAAiB,KAAK,OAAO,GAAG,UAAU;AAAA,QAC1F,SAAS,OAAO;AACf,cAAI,EAAE,iBAAiB,4BAAa;AACnC,kBAAM;AAAA,UACP;AAAA,QACD;AAAA,MACD,CAAC;AAAA,IACF;AACA,SAAK,KAAK,OAAO;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,YAAY,QAA0C;AAClE,UAAM,WAAwB,MAAM,KAAK,EAAE,QAAQ,OAAO,SAAS,EAAE,CAAC;AACtE,aAAS,MAAM,GAAG,MAAM,SAAS,QAAQ,OAAO,GAAG;AAClD,YAAM,QAAQ,OAAO,MAAM,CAAC;AAC5B,eAAS,GAAG,IAAI,CAAC,UAAU,eAAe,OAAiB,KAAK,QAAQ,OAAO,KAAK,QAAQ,IAAI;AAChG,eAAS,MAAM,CAAC,IAAI,CAAC,eAAe,OAAiB,KAAK,QAAQ,KAAK;AAAA,IACxE;AAEA,UAAM,KAAK,YAAY,SAAS,QAAQ,EAAE,KAAK;AAE/C,eAAW,SAAS,QAAQ;AAC3B,WAAK,iBAAiB,OAAO,KAAe;AAAA,IAC7C;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAgB,SAAwB;AACvC,QAAI,KAAK,WAAW;AACnB;AAAA,IACD;AAEA,SAAK,YAAY;AAGjB,WAAO,KAAK,iBAAiB,OAAO,GAAG;AACtC,UAAI;AACH,cAAM,KAAK,uBAAuB;AAAA,MACnC,SAAS,OAAO;AAEf,aAAK,KAAK,SAAS,KAAK;AAAA,MAEzB;AAEA,UAAI;AAEH,cAAM,OAAO,MAAM,KAAK,UAAU,KAAK,KAAK,QAAQ,YAAY;AAChE,YAAI,CAAC,MAAM;AACV;AAAA,QACD;AAEA,cAAM,KAAK,gBAAgB,IAAI;AAAA,MAChC,SAAS,OAAO;AAEf,aAAK,KAAK,SAAS,KAAK;AACxB;AAAA,MACD;AAAA,IACD;AAEA,SAAK,YAAY;AAAA,EAClB;AAAA,EAEA,MAAc,UAAU,QAAgB,OAA4C;AACnF,UAAM,OAAO,MAAM,KAAK,iBAAiB;AAAA,MACxC;AAAA,MACA,KAAK;AAAA,MACL,KAAK,QAAQ;AAAA,MACb;AAAA,MACA,OAAO,KAAK,QAAQ,QAAQ;AAAA,MAC5B;AAAA,MACA,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,GAAG,KAAK;AAAA,MACR,GAAG,MAAM,KAAK,EAAE,QAAQ,KAAK,iBAAiB,KAAK,GAAG,MAAM,MAAM;AAAA,IACnE;AAEA,WAAO,QAAQ,CAAC;AAAA,EACjB;AAAA,EAEA,MAAc,gBAAgB,MAAyC;AACtE,eAAW,CAAC,OAAO,QAAQ,KAAK,MAAM;AACrC,YAAM,YAAY,MAAM,SAAS,MAAM;AAEvC,iBAAW,CAAC,IAAI,MAAM,KAAK,UAAU;AACpC,cAAM,MAAM,OAAO,UAAU,CAAC,OAAOA,SAAQ,MAAM,SAAS,MAAM,MAAM,UAAUA,OAAM,MAAM,CAAC;AAC/F,YAAI,MAAM,EAAG;AAEb,cAAM,UAAU,OAAO,MAAM,CAAC;AAC9B,YAAI,CAAC,QAAS;AAEd,aAAK,UAAU,IAAI,KAAK,OAAO,WAAW,KAAK,QAAQ,OAAO,OAAO,CAAC;AAAA,MACvE;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,yBAAwC;AACrD,eAAW,UAAU,KAAK,kBAAkB;AAE3C,YAAM,UAAW,MAAM,KAAK,iBAAiB;AAAA,QAC5C;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,KAAK,QAAQ;AAAA;AAAA,MAEd;AAEA,iBAAW,CAAC,IAAI,UAAU,QAAQ,cAAc,KAAK,SAAS;AAE7D,YAAI,SAAS,KAAK,QAAQ,iBAAiB;AAC1C;AAAA,QACD;AAEA,YAAI,iBAAiB,KAAK,QAAQ,mBAAmB;AAEpD,gBAAM,KAAK,iBAAiB,KAAK,QAAQ,KAAK,OAAO,EAAE;AACvD;AAAA,QACD;AAGA,YAAI,aAAa,KAAK,QAAQ,MAAM;AACnC,gBAAM,UAAU,MAAM,KAAK,iBAAiB;AAAA,YAC3C;AAAA,YACA,KAAK;AAAA,YACL,KAAK,QAAQ;AAAA,YACb,KAAK,IAAI,KAAK,QAAQ,iBAAiB,CAAC;AAAA,YACxC;AAAA,YACA;AAAA,UACD;AAGA,cAAI,CAAC,SAAS,QAAQ;AACrB;AAAA,UACD;AAAA,QACD;AAGA,cAAM,UAAU,MAAM,KAAK,iBAAiB,aAAa,QAAQ,IAAI,EAAE;AAEvE,YAAI,CAAC,SAAS,QAAQ;AACrB;AAAA,QACD;AAEA,cAAM,CAAC,OAAO,MAAM,IAAI,QAAQ,CAAC;AACjC,cAAM,MAAM,OAAO,UAAU,CAAC,OAAOA,SAAQ,MAAM,SAAS,MAAM,MAAM,UAAUA,OAAM,MAAM,CAAC;AAC/F,YAAI,MAAM,GAAG;AACZ;AAAA,QACD;AAEA,cAAM,UAAU,OAAO,MAAM,CAAC;AAC9B,YAAI,CAAC,SAAS;AACb;AAAA,QACD;AAEA,aAAK,UAAU,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,OAAO,OAAO,CAAC;AAAA,MACvE;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,UAAU;AACtB,UAAM,KAAK,YAAY,CAAC,GAAG,KAAK,gBAAgB,CAAC;AACjD,SAAK,iBAAiB,WAAW;AACjC,SAAK,YAAY,WAAW;AAAA,EAC7B;AAMD;;;AEnSO,IAAM,oBAAN,cACE,gBAET;AAAA,EAlCA,OAkCA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAIC,MAAa,QAAqC,OAAc,MAAqC;AACpG,UAAM,KAAK,YAAY,KAAK,OAAiB,KAAK,gBAAgB,iBAAiB,KAAK,QAAQ,OAAO,IAAI,CAAC;AAAA,EAC7G;AAAA,EAEU,UAAU,IAAY,OAAe,OAAe,MAAe;AAC5E,UAAM,UAAmD;AAAA,MACxD;AAAA,MACA,KAAK,mCAAY;AAChB,cAAM,KAAK,YAAY,KAAK,OAAO,OAAO,EAAE;AAAA,MAC7C,GAFK;AAAA,IAGN;AAGA,SAAK,KAAK,OAAO,OAAO;AAAA,EACzB;AACD;;;ACnDA,IAAAC,8BAAkC;AAgF3B,IAAM,eAAN,cACE,8CAET;AAAA,EAgBQ,YACW,QACA,YAChB;AACD,UAAM;AAHW;AACA;AAAA,EAGlB;AAAA,EA1GD,OAqFA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAIC,OAAuB,mBAAmB;AAAA;AAAA;AAAA;AAAA,EAK1C,OAAc,cACb,MAC2F;AAC3F,UAAM,CAAC,SAAS,OAAO,IAAI;AAC3B,WAAO,CAAC,QAAQ,GAAG,EAAE,SAAS,SAAS,QAAQ,EAAE,CAAC;AAAA,EACnD;AAAA,EASO,gBAAwB;AAC9B,WAAO,KAAK;AAAA,EACb;AAAA,EAEA,MAAa,KAAK,SAAiB,SAA4C;AAC9E,UAAM,KAAK,OAAO,QAAQ,gBAAgB,EAAE,SAAS,QAAQ,CAAC;AAAA,EAC/D;AAAA,EAEA,MAAa,KAAK,QAAiC;AAClD,eAAW,SAAS,QAAQ;AAE3B,WAAK,OAAO;AAAA,QACX;AAAA,QACA,CAAC;AAAA,UACA;AAAA,UACA,MAAM,EAAE,SAAS,QAAQ;AAAA,QAC1B,MAIM;AAEL,eAAK,KAAK,YAAY,EAAE,SAAS,MAAM,QAAQ,CAAC;AAChD,eAAK,IAAI;AAAA,QACV;AAAA,MACD;AAAA,IACD;AAEA,UAAM,KAAK,OAAO,UAAU,MAAM;AAAA,EACnC;AACD;;;ACzIA,yBAAyC;AAsBlC,IAAM,+BAA+B;AAAA,EAC3C,GAAG;AAAA,EACH,SAAS;AACV;AA8BO,IAAM,iBAAN,cACE,gBAET;AAAA,EA3DA,OA2DA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAI6B;AAAA;AAAA;AAAA;AAAA,EAKT,WAAW,oBAAI,IAA6B;AAAA,EAExD,YAAY,aAAoB,SAAgC;AACtE,UAAM,aAAa,OAAO;AAC1B,SAAK,UAAU,EAAE,GAAG,8BAA8B,GAAG,QAAQ;AAE7D,SAAK,iBAAiB,GAAG,iBAAiB,CAAC,SAAiB,YAAoB;AAC/E,YAAM,CAAC,EAAE,EAAE,IAAI,QAAQ,SAAS,EAAE,MAAM,GAAG;AAC3C,UAAI,MAAM,KAAK,SAAS,IAAI,EAAE,GAAG;AAEhC,cAAM,EAAE,SAAAC,UAAS,QAAQ,IAAI,KAAK,SAAS,IAAI,EAAE;AACjD,QAAAA,SAAQ,KAAK,QAAQ,OAAO,OAAO,CAAC;AACpC,6CAAa,OAAO;AAAA,MACrB;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,KACZ,OACA,MACA,kBAA0B,KAAK,QAAQ,SACV;AAC7B,UAAM,KAAK,MAAM,KAAK,YAAY;AAAA,MACjC;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,KAAK,QAAQ,OAAO,IAAI;AAAA,IACzB;AAIA,UAAM,aAAa,GAAG,KAAe,IAAI,EAAG;AAG5C,UAAM,WAAW,IAAI,MAAM,mBAAmB,eAAe,IAAI;AAEjE,UAAM,KAAK,iBAAiB,UAAU,UAAU;AAChD,WAAO,IAAI,QAA2B,CAACA,UAAS,WAAW;AAC1D,YAAM,cAAU,+BAAW,MAAM,OAAO,QAAQ,GAAG,eAAe,EAAE,MAAM;AAE1E,WAAK,SAAS,IAAI,IAAK,EAAE,SAAAA,UAAS,QAAQ,QAAQ,CAAC;AAAA,IAEpD,CAAC,EAAE,QAAQ,MAAM;AAChB,WAAK,KAAK,iBAAiB,YAAY,UAAU;AACjD,WAAK,SAAS,OAAO,EAAG;AAAA,IACzB,CAAC;AAAA,EACF;AAAA,EAEU,UAAU,IAAY,OAAe,MAAe;AAC7D,UAAM,UAAwF;AAAA,MAC7F;AAAA,MACA,KAAK,mCAAY;AAChB,cAAM,KAAK,YAAY,KAAK,OAAO,KAAK,OAAO,EAAE;AAAA,MAClD,GAFK;AAAA,MAGL,OAAO,8BAAOC,UAAS;AACtB,cAAM,KAAK,YAAY,QAAQ,GAAG,KAAK,IAAI,GAAG,SAAS,CAAC,IAAI,KAAK,QAAQ,OAAOA,KAAI,CAAC;AAAA,MACtF,GAFO;AAAA,IAGR;AAGA,SAAK,KAAK,OAAO,OAAO;AAAA,EACzB;AACD;;;ALxHO,IAAM,UAAU;","names":["idx","import_async_event_emitter","resolve","data"]}
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
- // ../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.55.2_@types+node@22.19.1__jiti@2.6.1_postcss@8.5._fc13ebfd5f92c667b15f2cc793b0626e/node_modules/tsup/assets/esm_shims.js
4
+ // ../../node_modules/.pnpm/tsup@8.5.0_@microsoft+api-extractor@7.53.3_@types+node@22.18.13__jiti@2.6.1_postcss@8.5_6739d53df36153c79f03610ffa9280c0/node_modules/tsup/assets/esm_shims.js
5
5
  import path from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  var getFilename = /* @__PURE__ */ __name(() => fileURLToPath(import.meta.url), "getFilename");
@@ -27,7 +27,7 @@ var DefaultBrokerOptions = {
27
27
  };
28
28
 
29
29
  // src/brokers/redis/BaseRedis.ts
30
- var kUseRandomGroupName = /* @__PURE__ */ Symbol.for("djs.brokers.useRandomGroupName");
30
+ var kUseRandomGroupName = Symbol.for("djs.brokers.useRandomGroupName");
31
31
  var DefaultRedisBrokerOptions = {
32
32
  ...DefaultBrokerOptions,
33
33
  maxChunk: 10,
@@ -245,7 +245,53 @@ var PubSubRedisBroker = class extends BaseRedisBroker {
245
245
  }
246
246
  };
247
247
 
248
+ // src/brokers/redis/RedisGateway.ts
249
+ import { AsyncEventEmitter as AsyncEventEmitter2 } from "@vladfrangu/async_event_emitter";
250
+ var RedisGateway = class extends AsyncEventEmitter2 {
251
+ constructor(broker, shardCount) {
252
+ super();
253
+ this.broker = broker;
254
+ this.shardCount = shardCount;
255
+ }
256
+ static {
257
+ __name(this, "RedisGateway");
258
+ }
259
+ /**
260
+ * Event used over the broker used to tell shards to send a payload to Discord.
261
+ */
262
+ static GatewaySendEvent = "gateway_send";
263
+ /**
264
+ * Converts a dispatch event from `@discordjs/ws` to arguments for a `broker.publish` call.
265
+ */
266
+ static toPublishArgs(data) {
267
+ const [payload, shardId] = data;
268
+ return [payload.t, { shardId, payload: payload.d }];
269
+ }
270
+ getShardCount() {
271
+ return this.shardCount;
272
+ }
273
+ async send(shardId, payload) {
274
+ await this.broker.publish("gateway_send", { payload, shardId });
275
+ }
276
+ async init(events) {
277
+ for (const event of events) {
278
+ this.broker.on(
279
+ event,
280
+ ({
281
+ ack,
282
+ data: { payload, shardId }
283
+ }) => {
284
+ this.emit("dispatch", { shardId, data: payload });
285
+ void ack();
286
+ }
287
+ );
288
+ }
289
+ await this.broker.subscribe(events);
290
+ }
291
+ };
292
+
248
293
  // src/brokers/redis/RPCRedis.ts
294
+ import { clearTimeout, setTimeout } from "timers";
249
295
  var DefaultRPCRedisBrokerOptions = {
250
296
  ...DefaultRedisBrokerOptions,
251
297
  timeout: 5e3
@@ -310,7 +356,7 @@ var RPCRedisBroker = class extends BaseRedisBroker {
310
356
  };
311
357
 
312
358
  // src/index.ts
313
- var version = "2.0.0-pr-11006.1765450794-e636950b2";
359
+ var version = "2.0.0-pr-11005.1765454364-f3f6d34e7";
314
360
  export {
315
361
  BaseRedisBroker,
316
362
  DefaultBrokerOptions,
@@ -318,6 +364,7 @@ export {
318
364
  DefaultRedisBrokerOptions,
319
365
  PubSubRedisBroker,
320
366
  RPCRedisBroker,
367
+ RedisGateway,
321
368
  kUseRandomGroupName,
322
369
  version
323
370
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.55.2_@types+node@22.19.1__jiti@2.6.1_postcss@8.5._fc13ebfd5f92c667b15f2cc793b0626e/node_modules/tsup/assets/esm_shims.js","../src/brokers/redis/BaseRedis.ts","../src/brokers/Broker.ts","../src/brokers/redis/PubSubRedis.ts","../src/brokers/redis/RPCRedis.ts","../src/index.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","import type { Buffer } from 'node:buffer';\nimport { randomBytes } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\nimport type { Redis } from 'ioredis';\nimport { ReplyError } from 'ioredis';\nimport type { BaseBrokerOptions, IBaseBroker, ToEventMap } from '../Broker.js';\nimport { DefaultBrokerOptions } from '../Broker.js';\n\ntype RedisReadGroupData = [Buffer, [Buffer, Buffer[]][]][];\n\n// For some reason ioredis doesn't have those typed, but they exist\ndeclare module 'ioredis' {\n\tinterface Redis {\n\t\txclaimBuffer(\n\t\t\tkey: Buffer | string,\n\t\t\tgroup: Buffer | string,\n\t\t\tconsumer: Buffer | string,\n\t\t\tminIdleTime: number,\n\t\t\tid: Buffer | string,\n\t\t\t...args: (Buffer | string)[]\n\t\t): Promise<string[]>;\n\t\txreadgroupBuffer(...args: (Buffer | string)[]): Promise<RedisReadGroupData | null>;\n\t}\n}\n\nexport const kUseRandomGroupName = Symbol.for('djs.brokers.useRandomGroupName');\n\n/**\n * Options specific for a Redis broker\n */\nexport interface RedisBrokerOptions extends BaseBrokerOptions {\n\t/**\n\t * How long to block for messages when polling\n\t */\n\tblockTimeout?: number;\n\t/**\n\t * Consumer group name to use for this broker. For fanning out events, use {@link kUseRandomGroupName}\n\t *\n\t * @see {@link https://redis.io/commands/xreadgroup/}\n\t */\n\tgroup: string | typeof kUseRandomGroupName;\n\t/**\n\t * Max number of messages to poll at once\n\t */\n\tmaxChunk?: number;\n\t/**\n\t * How many times a message can be delivered to a consumer before it is considered dead.\n\t * This is used to prevent messages from being stuck in the queue forever if a consumer is\n\t * unable to process them.\n\t */\n\tmaxDeliveredTimes?: number;\n\t/**\n\t * How long a message should be idle for before allowing it to be claimed by another consumer.\n\t * Note that too high of a value can lead to a high delay in processing messages during a service downscale,\n\t * while too low of a value can lead to messages being too eagerly claimed by other consumers during an instance\n\t * restart (which is most likely not actually that problematic)\n\t */\n\tmessageIdleTime?: number;\n\t/**\n\t * Unique consumer name.\n\t *\n\t * @see {@link https://redis.io/commands/xreadgroup/}\n\t */\n\tname: string;\n}\n\n/**\n * Default broker options for redis\n */\nexport const DefaultRedisBrokerOptions = {\n\t...DefaultBrokerOptions,\n\tmaxChunk: 10,\n\tmaxDeliveredTimes: 3,\n\tmessageIdleTime: 3_000,\n\tblockTimeout: 5_000,\n} as const satisfies Required<Omit<RedisBrokerOptions, 'group' | 'name'>>;\n\n/**\n * Helper class with shared Redis logic\n */\nexport abstract class BaseRedisBroker<\n\tTEvents extends Record<string, any[]>,\n\tTResponses extends Record<keyof TEvents, any> | undefined = undefined,\n>\n\textends AsyncEventEmitter<ToEventMap<TEvents, TResponses>>\n\timplements IBaseBroker<TEvents>\n{\n\t/**\n\t * Used for Redis queues, see the 3rd argument taken by {@link https://redis.io/commands/xadd | xadd}\n\t */\n\tpublic static readonly STREAM_DATA_KEY = 'data' as const;\n\n\t/**\n\t * Options this broker is using\n\t */\n\tprotected readonly options: Required<RedisBrokerOptions>;\n\n\t/**\n\t * Events this broker has subscribed to\n\t */\n\tprotected readonly subscribedEvents = new Set<string>();\n\n\t/**\n\t * Internal copy of the Redis client being used to read incoming payloads\n\t */\n\tprotected readonly streamReadClient: Redis;\n\n\t/**\n\t * The group being used by this broker.\n\t *\n\t * @privateRemarks\n\t * Stored as its own field to do the \"use random group\" resolution in the constructor.\n\t */\n\tprotected readonly group: string;\n\n\t/**\n\t * Whether this broker is currently polling events\n\t */\n\tprotected listening = false;\n\n\tpublic constructor(\n\t\tprotected readonly redisClient: Redis,\n\t\toptions: RedisBrokerOptions,\n\t) {\n\t\tsuper();\n\t\tthis.options = { ...DefaultRedisBrokerOptions, ...options };\n\t\tthis.group = this.options.group === kUseRandomGroupName ? randomBytes(16).toString('hex') : this.options.group;\n\t\tredisClient.defineCommand('xcleangroup', {\n\t\t\tnumberOfKeys: 1,\n\t\t\tlua: readFileSync(resolve(__dirname, '..', 'scripts', 'xcleangroup.lua'), 'utf8'),\n\t\t});\n\t\tthis.streamReadClient = redisClient.duplicate();\n\t}\n\n\t/**\n\t * {@inheritDoc IBaseBroker.subscribe}\n\t */\n\tpublic async subscribe(events: (keyof TEvents)[]): Promise<void> {\n\t\tawait Promise.all(\n\t\t\t// @ts-expect-error: Intended\n\t\t\tevents.map(async (event) => {\n\t\t\t\tthis.subscribedEvents.add(event as string);\n\t\t\t\ttry {\n\t\t\t\t\treturn await this.redisClient.xgroup('CREATE', event as string, this.group, 0, 'MKSTREAM');\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!(error instanceof ReplyError)) {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t\tvoid this.listen();\n\t}\n\n\t/**\n\t * {@inheritDoc IBaseBroker.unsubscribe}\n\t */\n\tpublic async unsubscribe(events: (keyof TEvents)[]): Promise<void> {\n\t\tconst commands: unknown[][] = Array.from({ length: events.length * 2 });\n\t\tfor (let idx = 0; idx < commands.length; idx += 2) {\n\t\t\tconst event = events[idx / 2];\n\t\t\tcommands[idx] = ['xgroup', 'delconsumer', event as string, this.options.group, this.options.name];\n\t\t\tcommands[idx + 1] = ['xcleangroup', event as string, this.options.group];\n\t\t}\n\n\t\tawait this.redisClient.pipeline(commands).exec();\n\n\t\tfor (const event of events) {\n\t\t\tthis.subscribedEvents.delete(event as string);\n\t\t}\n\t}\n\n\t/**\n\t * Begins polling for events, firing them to {@link BaseRedisBroker.emitEvent}\n\t */\n\tprotected async listen(): Promise<void> {\n\t\tif (this.listening) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.listening = true;\n\n\t\t// Enter regular polling\n\t\twhile (this.subscribedEvents.size > 0) {\n\t\t\ttry {\n\t\t\t\tawait this.claimAndEmitDeadEvents();\n\t\t\t} catch (error) {\n\t\t\t\t// @ts-expect-error: Intended\n\t\t\t\tthis.emit('error', error);\n\t\t\t\t// We don't break here to keep the loop running even if dead event processing fails\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// As per docs, '>' means \"give me a new message\"\n\t\t\t\tconst data = await this.readGroup('>', this.options.blockTimeout);\n\t\t\t\tif (!data) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait this.processMessages(data);\n\t\t\t} catch (error) {\n\t\t\t\t// @ts-expect-error: Intended\n\t\t\t\tthis.emit('error', error);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tthis.listening = false;\n\t}\n\n\tprivate async readGroup(fromId: string, block: number): Promise<RedisReadGroupData> {\n\t\tconst data = await this.streamReadClient.xreadgroupBuffer(\n\t\t\t'GROUP',\n\t\t\tthis.group,\n\t\t\tthis.options.name,\n\t\t\t'COUNT',\n\t\t\tString(this.options.maxChunk),\n\t\t\t'BLOCK',\n\t\t\tString(block),\n\t\t\t'STREAMS',\n\t\t\t...this.subscribedEvents,\n\t\t\t...Array.from({ length: this.subscribedEvents.size }, () => fromId),\n\t\t);\n\n\t\treturn data ?? [];\n\t}\n\n\tprivate async processMessages(data: RedisReadGroupData): Promise<void> {\n\t\tfor (const [event, messages] of data) {\n\t\t\tconst eventName = event.toString('utf8');\n\n\t\t\tfor (const [id, packet] of messages) {\n\t\t\t\tconst idx = packet.findIndex((value, idx) => value.toString('utf8') === 'data' && idx % 2 === 0);\n\t\t\t\tif (idx < 0) continue;\n\n\t\t\t\tconst payload = packet[idx + 1];\n\t\t\t\tif (!payload) continue;\n\n\t\t\t\tthis.emitEvent(id, this.group, eventName, this.options.decode(payload));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async claimAndEmitDeadEvents(): Promise<void> {\n\t\tfor (const stream of this.subscribedEvents) {\n\t\t\t// Get up to N oldest pending messages (note: a pending message is a message that has been read, but never ACKed)\n\t\t\tconst pending = (await this.streamReadClient.xpending(\n\t\t\t\tstream,\n\t\t\t\tthis.group,\n\t\t\t\t'-',\n\t\t\t\t'+',\n\t\t\t\tthis.options.maxChunk,\n\t\t\t\t// See: https://redis.io/docs/latest/commands/xpending/#extended-form-of-xpending\n\t\t\t)) as [id: string, consumer: string, idleMs: number, deliveredTimes: number][];\n\n\t\t\tfor (const [id, consumer, idleMs, deliveredTimes] of pending) {\n\t\t\t\t// Technically xclaim checks for us anyway, but why not avoid an extra call?\n\t\t\t\tif (idleMs < this.options.messageIdleTime) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (deliveredTimes > this.options.maxDeliveredTimes) {\n\t\t\t\t\t// This message is dead. It has repeatedly failed being processed by a consumer.\n\t\t\t\t\tawait this.streamReadClient.xdel(stream, this.group, id);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Try to claim the message if we don't already own it (this may fail if another consumer has already claimed it)\n\t\t\t\tif (consumer !== this.options.name) {\n\t\t\t\t\tconst claimed = await this.streamReadClient.xclaimBuffer(\n\t\t\t\t\t\tstream,\n\t\t\t\t\t\tthis.group,\n\t\t\t\t\t\tthis.options.name,\n\t\t\t\t\t\tMath.max(this.options.messageIdleTime, 1),\n\t\t\t\t\t\tid,\n\t\t\t\t\t\t'JUSTID',\n\t\t\t\t\t);\n\n\t\t\t\t\t// Another consumer got the message before us\n\t\t\t\t\tif (!claimed?.length) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Fetch message body\n\t\t\t\tconst entries = await this.streamReadClient.xrangeBuffer(stream, id, id);\n\t\t\t\t// No idea how this could happen, frankly!\n\t\t\t\tif (!entries?.length) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst [msgId, fields] = entries[0]!;\n\t\t\t\tconst idx = fields.findIndex((value, idx) => value.toString('utf8') === 'data' && idx % 2 === 0);\n\t\t\t\tif (idx < 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst payload = fields[idx + 1];\n\t\t\t\tif (!payload) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tthis.emitEvent(msgId, this.group, stream, this.options.decode(payload));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Destroys the broker, closing all connections\n\t */\n\tpublic async destroy() {\n\t\tawait this.unsubscribe([...this.subscribedEvents]);\n\t\tthis.streamReadClient.disconnect();\n\t\tthis.redisClient.disconnect();\n\t}\n\n\t/**\n\t * Handles an incoming Redis event\n\t */\n\tprotected abstract emitEvent(id: Buffer, group: string, event: string, data: unknown): unknown;\n}\n","import { Buffer } from 'node:buffer';\nimport { encode, decode } from '@msgpack/msgpack';\nimport type { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\n\n/**\n * Base options for a broker implementation\n */\nexport interface BaseBrokerOptions {\n\t/**\n\t * Function to use for decoding messages\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tdecode?: (data: Buffer) => unknown;\n\t/**\n\t * Function to use for encoding messages\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tencode?: (data: unknown) => Buffer;\n}\n\n/**\n * Default broker options\n */\nexport const DefaultBrokerOptions = {\n\tencode: (data): Buffer => {\n\t\tconst encoded = encode(data);\n\t\treturn Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);\n\t},\n\tdecode: (data): unknown => decode(data),\n} as const satisfies Required<BaseBrokerOptions>;\n\nexport type ToEventMap<\n\tTRecord extends Record<string, any[]>,\n\tTResponses extends Record<keyof TRecord, any> | undefined = undefined,\n> = {\n\t[TKey in keyof TRecord]: [\n\t\tevent: TResponses extends Record<keyof TRecord, any>\n\t\t\t? { ack(): Promise<void>; reply(data: TResponses[TKey]): Promise<void> }\n\t\t\t: { ack(): Promise<void>; data: TRecord[TKey] },\n\t];\n};\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IBaseBroker<TEvents extends {}> {\n\t/**\n\t * Subscribes to the given events\n\t */\n\tsubscribe(events: (keyof TEvents)[]): Promise<void>;\n\t/**\n\t * Unsubscribes from the given events\n\t */\n\tunsubscribe(events: (keyof TEvents)[]): Promise<void>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IPubSubBroker<TEvents extends {}>\n\textends IBaseBroker<TEvents>, AsyncEventEmitter<ToEventMap<TEvents>> {\n\t/**\n\t * Publishes an event\n\t */\n\tpublish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void>;\n}\n\nexport interface IRPCBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>\n\textends IBaseBroker<TEvents>, AsyncEventEmitter<ToEventMap<TEvents, TResponses>> {\n\t/**\n\t * Makes an RPC call\n\t */\n\tcall<Event extends keyof TEvents>(\n\t\tevent: Event,\n\t\tdata: TEvents[Event],\n\t\ttimeoutDuration?: number,\n\t): Promise<TResponses[Event]>;\n}\n","import type { Buffer } from 'node:buffer';\nimport type { IPubSubBroker } from '../Broker.js';\nimport { BaseRedisBroker } from './BaseRedis.js';\n\n/**\n * PubSub broker powered by Redis\n *\n * @example\n * ```ts\n * // publisher.js\n * import { PubSubRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new PubSubRedisBroker(new Redis());\n *\n * await broker.publish('test', 'Hello World!');\n * await broker.destroy();\n *\n * // subscriber.js\n * import { PubSubRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new PubSubRedisBroker(new Redis());\n * \tbroker.on('test', ({ data, ack }) => {\n * \tconsole.log(data);\n * \tvoid ack();\n * });\n *\n * await broker.subscribe('subscribers', ['test']);\n * ```\n */\nexport class PubSubRedisBroker<TEvents extends Record<string, any>>\n\textends BaseRedisBroker<TEvents>\n\timplements IPubSubBroker<TEvents>\n{\n\t/**\n\t * {@inheritDoc IPubSubBroker.publish}\n\t */\n\tpublic async publish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void> {\n\t\tawait this.redisClient.xadd(event as string, '*', BaseRedisBroker.STREAM_DATA_KEY, this.options.encode(data));\n\t}\n\n\tprotected emitEvent(id: Buffer, group: string, event: string, data: unknown) {\n\t\tconst payload: { ack(): Promise<void>; data: unknown } = {\n\t\t\tdata,\n\t\t\tack: async () => {\n\t\t\t\tawait this.redisClient.xack(event, group, id);\n\t\t\t},\n\t\t};\n\n\t\t// @ts-expect-error: Intended\n\t\tthis.emit(event, payload);\n\t}\n}\n","import type { Buffer } from 'node:buffer';\nimport type Redis from 'ioredis/built/Redis.js';\nimport type { IRPCBroker } from '../Broker.js';\nimport type { RedisBrokerOptions } from './BaseRedis.js';\nimport { BaseRedisBroker, DefaultRedisBrokerOptions } from './BaseRedis.js';\n\ninterface InternalPromise {\n\treject(error: any): void;\n\tresolve(data: any): void;\n\ttimeout: NodeJS.Timeout;\n}\n\n/**\n * Options specific for an RPC Redis broker\n */\nexport interface RPCRedisBrokerOptions extends RedisBrokerOptions {\n\ttimeout?: number;\n}\n\n/**\n * Default values used for the {@link RPCRedisBrokerOptions}\n */\nexport const DefaultRPCRedisBrokerOptions = {\n\t...DefaultRedisBrokerOptions,\n\ttimeout: 5_000,\n} as const satisfies Required<Omit<RPCRedisBrokerOptions, 'group' | 'name'>>;\n\n/**\n * RPC broker powered by Redis\n *\n * @example\n * ```ts\n * // caller.js\n * import { RPCRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new RPCRedisBroker(new Redis());\n *\n * console.log(await broker.call('testcall', 'Hello World!'));\n * await broker.destroy();\n *\n * // responder.js\n * import { RPCRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new RPCRedisBroker(new Redis());\n * broker.on('testcall', ({ data, ack, reply }) => {\n * \tconsole.log('responder', data);\n * \tvoid ack();\n * \tvoid reply(`Echo: ${data}`);\n * });\n *\n * await broker.subscribe('responders', ['testcall']);\n * ```\n */\nexport class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>\n\textends BaseRedisBroker<TEvents, TResponses>\n\timplements IRPCBroker<TEvents, TResponses>\n{\n\t/**\n\t * Options this broker is using\n\t */\n\tprotected override readonly options: Required<RPCRedisBrokerOptions>;\n\n\t/**\n\t * @internal\n\t */\n\tprotected readonly promises = new Map<string, InternalPromise>();\n\n\tpublic constructor(redisClient: Redis, options: RPCRedisBrokerOptions) {\n\t\tsuper(redisClient, options);\n\t\tthis.options = { ...DefaultRPCRedisBrokerOptions, ...options };\n\n\t\tthis.streamReadClient.on('messageBuffer', (channel: Buffer, message: Buffer) => {\n\t\t\tconst [, id] = channel.toString().split(':');\n\t\t\tif (id && this.promises.has(id)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/unbound-method\n\t\t\t\tconst { resolve, timeout } = this.promises.get(id)!;\n\t\t\t\tresolve(this.options.decode(message));\n\t\t\t\tclearTimeout(timeout);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * {@inheritDoc IRPCBroker.call}\n\t */\n\tpublic async call<Event extends keyof TEvents>(\n\t\tevent: Event,\n\t\tdata: TEvents[Event],\n\t\ttimeoutDuration: number = this.options.timeout,\n\t): Promise<TResponses[Event]> {\n\t\tconst id = await this.redisClient.xadd(\n\t\t\tevent as string,\n\t\t\t'*',\n\t\t\tBaseRedisBroker.STREAM_DATA_KEY,\n\t\t\tthis.options.encode(data),\n\t\t);\n\t\t// This id! assertion is valid. From redis docs:\n\t\t// \"The command returns a Null reply when used with the NOMKSTREAM option and the key doesn't exist.\"\n\t\t// See: https://redis.io/commands/xadd/\n\t\tconst rpcChannel = `${event as string}:${id!}`;\n\n\t\t// Construct the error here for better stack traces\n\t\tconst timedOut = new Error(`timed out after ${timeoutDuration}ms`);\n\n\t\tawait this.streamReadClient.subscribe(rpcChannel);\n\t\treturn new Promise<TResponses[Event]>((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => reject(timedOut), timeoutDuration).unref();\n\n\t\t\tthis.promises.set(id!, { resolve, reject, timeout });\n\t\t\t// eslint-disable-next-line promise/prefer-await-to-then\n\t\t}).finally(() => {\n\t\t\tvoid this.streamReadClient.unsubscribe(rpcChannel);\n\t\t\tthis.promises.delete(id!);\n\t\t});\n\t}\n\n\tprotected emitEvent(id: Buffer, event: string, data: unknown) {\n\t\tconst payload: { ack(): Promise<void>; data: unknown; reply(data: unknown): Promise<void> } = {\n\t\t\tdata,\n\t\t\tack: async () => {\n\t\t\t\tawait this.redisClient.xack(event, this.group, id);\n\t\t\t},\n\t\t\treply: async (data) => {\n\t\t\t\tawait this.redisClient.publish(`${event}:${id.toString()}`, this.options.encode(data));\n\t\t\t},\n\t\t};\n\n\t\t// @ts-expect-error: Intended\n\t\tthis.emit(event, payload);\n\t}\n}\n","export * from './brokers/redis/BaseRedis.js';\nexport * from './brokers/redis/PubSubRedis.js';\nexport * from './brokers/redis/RPCRedis.js';\n\nexport * from './brokers/Broker.js';\n\n/**\n * The {@link https://github.com/discordjs/discord.js/blob/main/packages/brokers#readme | @discordjs/brokers} version\n * that you are currently using.\n *\n * @privateRemarks This needs to explicitly be `string` so it is not typed as a \"const string\" that gets injected by esbuild.\n */\nexport const version = '2.0.0-pr-11006.1765450794-e636950b2' as string;\n"],"mappings":";;;;AACA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,IAAM,cAAc,6BAAM,cAAc,YAAY,GAAG,GAAnC;AACpB,IAAM,aAAa,6BAAM,KAAK,QAAQ,YAAY,CAAC,GAAhC;AAEZ,IAAM,YAA4B,2BAAW;;;ACNpD,SAAS,mBAAmB;AAC5B,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,SAAS,yBAAyB;AAElC,SAAS,kBAAkB;;;ACN3B,SAAS,UAAAA,eAAc;AACvB,SAAS,QAAQ,cAAc;AAsBxB,IAAM,uBAAuB;AAAA,EACnC,QAAQ,wBAAC,SAAiB;AACzB,UAAM,UAAU,OAAO,IAAI;AAC3B,WAAOC,QAAO,KAAK,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,UAAU;AAAA,EAC1E,GAHQ;AAAA,EAIR,QAAQ,wBAAC,SAAkB,OAAO,IAAI,GAA9B;AACT;;;ADFO,IAAM,sBAAsB,uBAAO,IAAI,gCAAgC;AA4CvE,IAAM,4BAA4B;AAAA,EACxC,GAAG;AAAA,EACH,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,cAAc;AACf;AAKO,IAAe,kBAAf,cAIE,kBAET;AAAA,EAkCQ,YACa,aACnB,SACC;AACD,UAAM;AAHa;AAInB,SAAK,UAAU,EAAE,GAAG,2BAA2B,GAAG,QAAQ;AAC1D,SAAK,QAAQ,KAAK,QAAQ,UAAU,sBAAsB,YAAY,EAAE,EAAE,SAAS,KAAK,IAAI,KAAK,QAAQ;AACzG,gBAAY,cAAc,eAAe;AAAA,MACxC,cAAc;AAAA,MACd,KAAK,aAAa,QAAQ,WAAW,MAAM,WAAW,iBAAiB,GAAG,MAAM;AAAA,IACjF,CAAC;AACD,SAAK,mBAAmB,YAAY,UAAU;AAAA,EAC/C;AAAA,EAtID,OAwFA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAIC,OAAuB,kBAAkB;AAAA;AAAA;AAAA;AAAA,EAKtB;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAmB,oBAAI,IAAY;AAAA;AAAA;AAAA;AAAA,EAKnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA;AAAA;AAAA;AAAA;AAAA,EAKT,YAAY;AAAA;AAAA;AAAA;AAAA,EAmBtB,MAAa,UAAU,QAA0C;AAChE,UAAM,QAAQ;AAAA;AAAA,MAEb,OAAO,IAAI,OAAO,UAAU;AAC3B,aAAK,iBAAiB,IAAI,KAAe;AACzC,YAAI;AACH,iBAAO,MAAM,KAAK,YAAY,OAAO,UAAU,OAAiB,KAAK,OAAO,GAAG,UAAU;AAAA,QAC1F,SAAS,OAAO;AACf,cAAI,EAAE,iBAAiB,aAAa;AACnC,kBAAM;AAAA,UACP;AAAA,QACD;AAAA,MACD,CAAC;AAAA,IACF;AACA,SAAK,KAAK,OAAO;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,YAAY,QAA0C;AAClE,UAAM,WAAwB,MAAM,KAAK,EAAE,QAAQ,OAAO,SAAS,EAAE,CAAC;AACtE,aAAS,MAAM,GAAG,MAAM,SAAS,QAAQ,OAAO,GAAG;AAClD,YAAM,QAAQ,OAAO,MAAM,CAAC;AAC5B,eAAS,GAAG,IAAI,CAAC,UAAU,eAAe,OAAiB,KAAK,QAAQ,OAAO,KAAK,QAAQ,IAAI;AAChG,eAAS,MAAM,CAAC,IAAI,CAAC,eAAe,OAAiB,KAAK,QAAQ,KAAK;AAAA,IACxE;AAEA,UAAM,KAAK,YAAY,SAAS,QAAQ,EAAE,KAAK;AAE/C,eAAW,SAAS,QAAQ;AAC3B,WAAK,iBAAiB,OAAO,KAAe;AAAA,IAC7C;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAgB,SAAwB;AACvC,QAAI,KAAK,WAAW;AACnB;AAAA,IACD;AAEA,SAAK,YAAY;AAGjB,WAAO,KAAK,iBAAiB,OAAO,GAAG;AACtC,UAAI;AACH,cAAM,KAAK,uBAAuB;AAAA,MACnC,SAAS,OAAO;AAEf,aAAK,KAAK,SAAS,KAAK;AAAA,MAEzB;AAEA,UAAI;AAEH,cAAM,OAAO,MAAM,KAAK,UAAU,KAAK,KAAK,QAAQ,YAAY;AAChE,YAAI,CAAC,MAAM;AACV;AAAA,QACD;AAEA,cAAM,KAAK,gBAAgB,IAAI;AAAA,MAChC,SAAS,OAAO;AAEf,aAAK,KAAK,SAAS,KAAK;AACxB;AAAA,MACD;AAAA,IACD;AAEA,SAAK,YAAY;AAAA,EAClB;AAAA,EAEA,MAAc,UAAU,QAAgB,OAA4C;AACnF,UAAM,OAAO,MAAM,KAAK,iBAAiB;AAAA,MACxC;AAAA,MACA,KAAK;AAAA,MACL,KAAK,QAAQ;AAAA,MACb;AAAA,MACA,OAAO,KAAK,QAAQ,QAAQ;AAAA,MAC5B;AAAA,MACA,OAAO,KAAK;AAAA,MACZ;AAAA,MACA,GAAG,KAAK;AAAA,MACR,GAAG,MAAM,KAAK,EAAE,QAAQ,KAAK,iBAAiB,KAAK,GAAG,MAAM,MAAM;AAAA,IACnE;AAEA,WAAO,QAAQ,CAAC;AAAA,EACjB;AAAA,EAEA,MAAc,gBAAgB,MAAyC;AACtE,eAAW,CAAC,OAAO,QAAQ,KAAK,MAAM;AACrC,YAAM,YAAY,MAAM,SAAS,MAAM;AAEvC,iBAAW,CAAC,IAAI,MAAM,KAAK,UAAU;AACpC,cAAM,MAAM,OAAO,UAAU,CAAC,OAAOC,SAAQ,MAAM,SAAS,MAAM,MAAM,UAAUA,OAAM,MAAM,CAAC;AAC/F,YAAI,MAAM,EAAG;AAEb,cAAM,UAAU,OAAO,MAAM,CAAC;AAC9B,YAAI,CAAC,QAAS;AAEd,aAAK,UAAU,IAAI,KAAK,OAAO,WAAW,KAAK,QAAQ,OAAO,OAAO,CAAC;AAAA,MACvE;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,yBAAwC;AACrD,eAAW,UAAU,KAAK,kBAAkB;AAE3C,YAAM,UAAW,MAAM,KAAK,iBAAiB;AAAA,QAC5C;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,KAAK,QAAQ;AAAA;AAAA,MAEd;AAEA,iBAAW,CAAC,IAAI,UAAU,QAAQ,cAAc,KAAK,SAAS;AAE7D,YAAI,SAAS,KAAK,QAAQ,iBAAiB;AAC1C;AAAA,QACD;AAEA,YAAI,iBAAiB,KAAK,QAAQ,mBAAmB;AAEpD,gBAAM,KAAK,iBAAiB,KAAK,QAAQ,KAAK,OAAO,EAAE;AACvD;AAAA,QACD;AAGA,YAAI,aAAa,KAAK,QAAQ,MAAM;AACnC,gBAAM,UAAU,MAAM,KAAK,iBAAiB;AAAA,YAC3C;AAAA,YACA,KAAK;AAAA,YACL,KAAK,QAAQ;AAAA,YACb,KAAK,IAAI,KAAK,QAAQ,iBAAiB,CAAC;AAAA,YACxC;AAAA,YACA;AAAA,UACD;AAGA,cAAI,CAAC,SAAS,QAAQ;AACrB;AAAA,UACD;AAAA,QACD;AAGA,cAAM,UAAU,MAAM,KAAK,iBAAiB,aAAa,QAAQ,IAAI,EAAE;AAEvE,YAAI,CAAC,SAAS,QAAQ;AACrB;AAAA,QACD;AAEA,cAAM,CAAC,OAAO,MAAM,IAAI,QAAQ,CAAC;AACjC,cAAM,MAAM,OAAO,UAAU,CAAC,OAAOA,SAAQ,MAAM,SAAS,MAAM,MAAM,UAAUA,OAAM,MAAM,CAAC;AAC/F,YAAI,MAAM,GAAG;AACZ;AAAA,QACD;AAEA,cAAM,UAAU,OAAO,MAAM,CAAC;AAC9B,YAAI,CAAC,SAAS;AACb;AAAA,QACD;AAEA,aAAK,UAAU,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,OAAO,OAAO,CAAC;AAAA,MACvE;AAAA,IACD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,UAAU;AACtB,UAAM,KAAK,YAAY,CAAC,GAAG,KAAK,gBAAgB,CAAC;AACjD,SAAK,iBAAiB,WAAW;AACjC,SAAK,YAAY,WAAW;AAAA,EAC7B;AAMD;;;AEnSO,IAAM,oBAAN,cACE,gBAET;AAAA,EAlCA,OAkCA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAIC,MAAa,QAAqC,OAAc,MAAqC;AACpG,UAAM,KAAK,YAAY,KAAK,OAAiB,KAAK,gBAAgB,iBAAiB,KAAK,QAAQ,OAAO,IAAI,CAAC;AAAA,EAC7G;AAAA,EAEU,UAAU,IAAY,OAAe,OAAe,MAAe;AAC5E,UAAM,UAAmD;AAAA,MACxD;AAAA,MACA,KAAK,mCAAY;AAChB,cAAM,KAAK,YAAY,KAAK,OAAO,OAAO,EAAE;AAAA,MAC7C,GAFK;AAAA,IAGN;AAGA,SAAK,KAAK,OAAO,OAAO;AAAA,EACzB;AACD;;;AC/BO,IAAM,+BAA+B;AAAA,EAC3C,GAAG;AAAA,EACH,SAAS;AACV;AA8BO,IAAM,iBAAN,cACE,gBAET;AAAA,EA1DA,OA0DA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAI6B;AAAA;AAAA;AAAA;AAAA,EAKT,WAAW,oBAAI,IAA6B;AAAA,EAExD,YAAY,aAAoB,SAAgC;AACtE,UAAM,aAAa,OAAO;AAC1B,SAAK,UAAU,EAAE,GAAG,8BAA8B,GAAG,QAAQ;AAE7D,SAAK,iBAAiB,GAAG,iBAAiB,CAAC,SAAiB,YAAoB;AAC/E,YAAM,CAAC,EAAE,EAAE,IAAI,QAAQ,SAAS,EAAE,MAAM,GAAG;AAC3C,UAAI,MAAM,KAAK,SAAS,IAAI,EAAE,GAAG;AAEhC,cAAM,EAAE,SAAAC,UAAS,QAAQ,IAAI,KAAK,SAAS,IAAI,EAAE;AACjD,QAAAA,SAAQ,KAAK,QAAQ,OAAO,OAAO,CAAC;AACpC,qBAAa,OAAO;AAAA,MACrB;AAAA,IACD,CAAC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAa,KACZ,OACA,MACA,kBAA0B,KAAK,QAAQ,SACV;AAC7B,UAAM,KAAK,MAAM,KAAK,YAAY;AAAA,MACjC;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,KAAK,QAAQ,OAAO,IAAI;AAAA,IACzB;AAIA,UAAM,aAAa,GAAG,KAAe,IAAI,EAAG;AAG5C,UAAM,WAAW,IAAI,MAAM,mBAAmB,eAAe,IAAI;AAEjE,UAAM,KAAK,iBAAiB,UAAU,UAAU;AAChD,WAAO,IAAI,QAA2B,CAACA,UAAS,WAAW;AAC1D,YAAM,UAAU,WAAW,MAAM,OAAO,QAAQ,GAAG,eAAe,EAAE,MAAM;AAE1E,WAAK,SAAS,IAAI,IAAK,EAAE,SAAAA,UAAS,QAAQ,QAAQ,CAAC;AAAA,IAEpD,CAAC,EAAE,QAAQ,MAAM;AAChB,WAAK,KAAK,iBAAiB,YAAY,UAAU;AACjD,WAAK,SAAS,OAAO,EAAG;AAAA,IACzB,CAAC;AAAA,EACF;AAAA,EAEU,UAAU,IAAY,OAAe,MAAe;AAC7D,UAAM,UAAwF;AAAA,MAC7F;AAAA,MACA,KAAK,mCAAY;AAChB,cAAM,KAAK,YAAY,KAAK,OAAO,KAAK,OAAO,EAAE;AAAA,MAClD,GAFK;AAAA,MAGL,OAAO,8BAAOC,UAAS;AACtB,cAAM,KAAK,YAAY,QAAQ,GAAG,KAAK,IAAI,GAAG,SAAS,CAAC,IAAI,KAAK,QAAQ,OAAOA,KAAI,CAAC;AAAA,MACtF,GAFO;AAAA,IAGR;AAGA,SAAK,KAAK,OAAO,OAAO;AAAA,EACzB;AACD;;;ACxHO,IAAM,UAAU;","names":["Buffer","Buffer","idx","resolve","data"]}
1
+ {"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.5.0_@microsoft+api-extractor@7.53.3_@types+node@22.18.13__jiti@2.6.1_postcss@8.5_6739d53df36153c79f03610ffa9280c0/node_modules/tsup/assets/esm_shims.js","../src/brokers/redis/BaseRedis.ts","../src/brokers/Broker.ts","../src/brokers/redis/PubSubRedis.ts","../src/brokers/redis/RedisGateway.ts","../src/brokers/redis/RPCRedis.ts","../src/index.ts"],"sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n","import type { Buffer } from 'node:buffer';\nimport { randomBytes } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\nimport type { Redis } from 'ioredis';\nimport { ReplyError } from 'ioredis';\nimport type { BaseBrokerOptions, IBaseBroker, ToEventMap } from '../Broker.js';\nimport { DefaultBrokerOptions } from '../Broker.js';\n\ntype RedisReadGroupData = [Buffer, [Buffer, Buffer[]][]][];\n\n// For some reason ioredis doesn't have those typed, but they exist\ndeclare module 'ioredis' {\n\tinterface Redis {\n\t\txclaimBuffer(\n\t\t\tkey: Buffer | string,\n\t\t\tgroup: Buffer | string,\n\t\t\tconsumer: Buffer | string,\n\t\t\tminIdleTime: number,\n\t\t\tid: Buffer | string,\n\t\t\t...args: (Buffer | string)[]\n\t\t): Promise<string[]>;\n\t\txreadgroupBuffer(...args: (Buffer | string)[]): Promise<RedisReadGroupData | null>;\n\t}\n}\n\nexport const kUseRandomGroupName = Symbol.for('djs.brokers.useRandomGroupName');\n\n/**\n * Options specific for a Redis broker\n */\nexport interface RedisBrokerOptions extends BaseBrokerOptions {\n\t/**\n\t * How long to block for messages when polling\n\t */\n\tblockTimeout?: number;\n\t/**\n\t * Consumer group name to use for this broker. For fanning out events, use {@link kUseRandomGroupName}\n\t *\n\t * @see {@link https://redis.io/commands/xreadgroup/}\n\t */\n\tgroup: string | typeof kUseRandomGroupName;\n\t/**\n\t * Max number of messages to poll at once\n\t */\n\tmaxChunk?: number;\n\t/**\n\t * How many times a message can be delivered to a consumer before it is considered dead.\n\t * This is used to prevent messages from being stuck in the queue forever if a consumer is\n\t * unable to process them.\n\t */\n\tmaxDeliveredTimes?: number;\n\t/**\n\t * How long a message should be idle for before allowing it to be claimed by another consumer.\n\t * Note that too high of a value can lead to a high delay in processing messages during a service downscale,\n\t * while too low of a value can lead to messages being too eagerly claimed by other consumers during an instance\n\t * restart (which is most likely not actually that problematic)\n\t */\n\tmessageIdleTime?: number;\n\t/**\n\t * Unique consumer name.\n\t *\n\t * @see {@link https://redis.io/commands/xreadgroup/}\n\t */\n\tname: string;\n}\n\n/**\n * Default broker options for redis\n */\nexport const DefaultRedisBrokerOptions = {\n\t...DefaultBrokerOptions,\n\tmaxChunk: 10,\n\tmaxDeliveredTimes: 3,\n\tmessageIdleTime: 3_000,\n\tblockTimeout: 5_000,\n} as const satisfies Required<Omit<RedisBrokerOptions, 'group' | 'name'>>;\n\n/**\n * Helper class with shared Redis logic\n */\nexport abstract class BaseRedisBroker<\n\t\tTEvents extends Record<string, any[]>,\n\t\tTResponses extends Record<keyof TEvents, any> | undefined = undefined,\n\t>\n\textends AsyncEventEmitter<ToEventMap<TEvents, TResponses>>\n\timplements IBaseBroker<TEvents>\n{\n\t/**\n\t * Used for Redis queues, see the 3rd argument taken by {@link https://redis.io/commands/xadd | xadd}\n\t */\n\tpublic static readonly STREAM_DATA_KEY = 'data' as const;\n\n\t/**\n\t * Options this broker is using\n\t */\n\tprotected readonly options: Required<RedisBrokerOptions>;\n\n\t/**\n\t * Events this broker has subscribed to\n\t */\n\tprotected readonly subscribedEvents = new Set<string>();\n\n\t/**\n\t * Internal copy of the Redis client being used to read incoming payloads\n\t */\n\tprotected readonly streamReadClient: Redis;\n\n\t/**\n\t * The group being used by this broker.\n\t *\n\t * @privateRemarks\n\t * Stored as its own field to do the \"use random group\" resolution in the constructor.\n\t */\n\tprotected readonly group: string;\n\n\t/**\n\t * Whether this broker is currently polling events\n\t */\n\tprotected listening = false;\n\n\tpublic constructor(\n\t\tprotected readonly redisClient: Redis,\n\t\toptions: RedisBrokerOptions,\n\t) {\n\t\tsuper();\n\t\tthis.options = { ...DefaultRedisBrokerOptions, ...options };\n\t\tthis.group = this.options.group === kUseRandomGroupName ? randomBytes(16).toString('hex') : this.options.group;\n\t\tredisClient.defineCommand('xcleangroup', {\n\t\t\tnumberOfKeys: 1,\n\t\t\tlua: readFileSync(resolve(__dirname, '..', 'scripts', 'xcleangroup.lua'), 'utf8'),\n\t\t});\n\t\tthis.streamReadClient = redisClient.duplicate();\n\t}\n\n\t/**\n\t * {@inheritDoc IBaseBroker.subscribe}\n\t */\n\tpublic async subscribe(events: (keyof TEvents)[]): Promise<void> {\n\t\tawait Promise.all(\n\t\t\t// @ts-expect-error: Intended\n\t\t\tevents.map(async (event) => {\n\t\t\t\tthis.subscribedEvents.add(event as string);\n\t\t\t\ttry {\n\t\t\t\t\treturn await this.redisClient.xgroup('CREATE', event as string, this.group, 0, 'MKSTREAM');\n\t\t\t\t} catch (error) {\n\t\t\t\t\tif (!(error instanceof ReplyError)) {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\t\tvoid this.listen();\n\t}\n\n\t/**\n\t * {@inheritDoc IBaseBroker.unsubscribe}\n\t */\n\tpublic async unsubscribe(events: (keyof TEvents)[]): Promise<void> {\n\t\tconst commands: unknown[][] = Array.from({ length: events.length * 2 });\n\t\tfor (let idx = 0; idx < commands.length; idx += 2) {\n\t\t\tconst event = events[idx / 2];\n\t\t\tcommands[idx] = ['xgroup', 'delconsumer', event as string, this.options.group, this.options.name];\n\t\t\tcommands[idx + 1] = ['xcleangroup', event as string, this.options.group];\n\t\t}\n\n\t\tawait this.redisClient.pipeline(commands).exec();\n\n\t\tfor (const event of events) {\n\t\t\tthis.subscribedEvents.delete(event as string);\n\t\t}\n\t}\n\n\t/**\n\t * Begins polling for events, firing them to {@link BaseRedisBroker.emitEvent}\n\t */\n\tprotected async listen(): Promise<void> {\n\t\tif (this.listening) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.listening = true;\n\n\t\t// Enter regular polling\n\t\twhile (this.subscribedEvents.size > 0) {\n\t\t\ttry {\n\t\t\t\tawait this.claimAndEmitDeadEvents();\n\t\t\t} catch (error) {\n\t\t\t\t// @ts-expect-error: Intended\n\t\t\t\tthis.emit('error', error);\n\t\t\t\t// We don't break here to keep the loop running even if dead event processing fails\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// As per docs, '>' means \"give me a new message\"\n\t\t\t\tconst data = await this.readGroup('>', this.options.blockTimeout);\n\t\t\t\tif (!data) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tawait this.processMessages(data);\n\t\t\t} catch (error) {\n\t\t\t\t// @ts-expect-error: Intended\n\t\t\t\tthis.emit('error', error);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tthis.listening = false;\n\t}\n\n\tprivate async readGroup(fromId: string, block: number): Promise<RedisReadGroupData> {\n\t\tconst data = await this.streamReadClient.xreadgroupBuffer(\n\t\t\t'GROUP',\n\t\t\tthis.group,\n\t\t\tthis.options.name,\n\t\t\t'COUNT',\n\t\t\tString(this.options.maxChunk),\n\t\t\t'BLOCK',\n\t\t\tString(block),\n\t\t\t'STREAMS',\n\t\t\t...this.subscribedEvents,\n\t\t\t...Array.from({ length: this.subscribedEvents.size }, () => fromId),\n\t\t);\n\n\t\treturn data ?? [];\n\t}\n\n\tprivate async processMessages(data: RedisReadGroupData): Promise<void> {\n\t\tfor (const [event, messages] of data) {\n\t\t\tconst eventName = event.toString('utf8');\n\n\t\t\tfor (const [id, packet] of messages) {\n\t\t\t\tconst idx = packet.findIndex((value, idx) => value.toString('utf8') === 'data' && idx % 2 === 0);\n\t\t\t\tif (idx < 0) continue;\n\n\t\t\t\tconst payload = packet[idx + 1];\n\t\t\t\tif (!payload) continue;\n\n\t\t\t\tthis.emitEvent(id, this.group, eventName, this.options.decode(payload));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async claimAndEmitDeadEvents(): Promise<void> {\n\t\tfor (const stream of this.subscribedEvents) {\n\t\t\t// Get up to N oldest pending messages (note: a pending message is a message that has been read, but never ACKed)\n\t\t\tconst pending = (await this.streamReadClient.xpending(\n\t\t\t\tstream,\n\t\t\t\tthis.group,\n\t\t\t\t'-',\n\t\t\t\t'+',\n\t\t\t\tthis.options.maxChunk,\n\t\t\t\t// See: https://redis.io/docs/latest/commands/xpending/#extended-form-of-xpending\n\t\t\t)) as [id: string, consumer: string, idleMs: number, deliveredTimes: number][];\n\n\t\t\tfor (const [id, consumer, idleMs, deliveredTimes] of pending) {\n\t\t\t\t// Technically xclaim checks for us anyway, but why not avoid an extra call?\n\t\t\t\tif (idleMs < this.options.messageIdleTime) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (deliveredTimes > this.options.maxDeliveredTimes) {\n\t\t\t\t\t// This message is dead. It has repeatedly failed being processed by a consumer.\n\t\t\t\t\tawait this.streamReadClient.xdel(stream, this.group, id);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Try to claim the message if we don't already own it (this may fail if another consumer has already claimed it)\n\t\t\t\tif (consumer !== this.options.name) {\n\t\t\t\t\tconst claimed = await this.streamReadClient.xclaimBuffer(\n\t\t\t\t\t\tstream,\n\t\t\t\t\t\tthis.group,\n\t\t\t\t\t\tthis.options.name,\n\t\t\t\t\t\tMath.max(this.options.messageIdleTime, 1),\n\t\t\t\t\t\tid,\n\t\t\t\t\t\t'JUSTID',\n\t\t\t\t\t);\n\n\t\t\t\t\t// Another consumer got the message before us\n\t\t\t\t\tif (!claimed?.length) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Fetch message body\n\t\t\t\tconst entries = await this.streamReadClient.xrangeBuffer(stream, id, id);\n\t\t\t\t// No idea how this could happen, frankly!\n\t\t\t\tif (!entries?.length) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst [msgId, fields] = entries[0]!;\n\t\t\t\tconst idx = fields.findIndex((value, idx) => value.toString('utf8') === 'data' && idx % 2 === 0);\n\t\t\t\tif (idx < 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst payload = fields[idx + 1];\n\t\t\t\tif (!payload) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tthis.emitEvent(msgId, this.group, stream, this.options.decode(payload));\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Destroys the broker, closing all connections\n\t */\n\tpublic async destroy() {\n\t\tawait this.unsubscribe([...this.subscribedEvents]);\n\t\tthis.streamReadClient.disconnect();\n\t\tthis.redisClient.disconnect();\n\t}\n\n\t/**\n\t * Handles an incoming Redis event\n\t */\n\tprotected abstract emitEvent(id: Buffer, group: string, event: string, data: unknown): unknown;\n}\n","import { Buffer } from 'node:buffer';\nimport { encode, decode } from '@msgpack/msgpack';\nimport type { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\n\n/**\n * Base options for a broker implementation\n */\nexport interface BaseBrokerOptions {\n\t/**\n\t * Function to use for decoding messages\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tdecode?: (data: Buffer) => unknown;\n\t/**\n\t * Function to use for encoding messages\n\t */\n\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\tencode?: (data: unknown) => Buffer;\n}\n\n/**\n * Default broker options\n */\nexport const DefaultBrokerOptions = {\n\tencode: (data): Buffer => {\n\t\tconst encoded = encode(data);\n\t\treturn Buffer.from(encoded.buffer, encoded.byteOffset, encoded.byteLength);\n\t},\n\tdecode: (data): unknown => decode(data),\n} as const satisfies Required<BaseBrokerOptions>;\n\nexport type ToEventMap<\n\tTRecord extends Record<string, any[]>,\n\tTResponses extends Record<keyof TRecord, any> | undefined = undefined,\n> = {\n\t[TKey in keyof TRecord]: [\n\t\tevent: TResponses extends Record<keyof TRecord, any>\n\t\t\t? { ack(): Promise<void>; reply(data: TResponses[TKey]): Promise<void> }\n\t\t\t: { ack(): Promise<void>; data: TRecord[TKey] },\n\t];\n};\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IBaseBroker<TEvents extends {}> {\n\t/**\n\t * Subscribes to the given events\n\t */\n\tsubscribe(events: (keyof TEvents)[]): Promise<void>;\n\t/**\n\t * Unsubscribes from the given events\n\t */\n\tunsubscribe(events: (keyof TEvents)[]): Promise<void>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface IPubSubBroker<TEvents extends {}>\n\textends IBaseBroker<TEvents>,\n\t\tAsyncEventEmitter<ToEventMap<TEvents>> {\n\t/**\n\t * Publishes an event\n\t */\n\tpublish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void>;\n}\n\nexport interface IRPCBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>\n\textends IBaseBroker<TEvents>,\n\t\tAsyncEventEmitter<ToEventMap<TEvents, TResponses>> {\n\t/**\n\t * Makes an RPC call\n\t */\n\tcall<Event extends keyof TEvents>(\n\t\tevent: Event,\n\t\tdata: TEvents[Event],\n\t\ttimeoutDuration?: number,\n\t): Promise<TResponses[Event]>;\n}\n","import type { Buffer } from 'node:buffer';\nimport type { IPubSubBroker } from '../Broker.js';\nimport { BaseRedisBroker } from './BaseRedis.js';\n\n/**\n * PubSub broker powered by Redis\n *\n * @example\n * ```ts\n * // publisher.js\n * import { PubSubRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new PubSubRedisBroker(new Redis());\n *\n * await broker.publish('test', 'Hello World!');\n * await broker.destroy();\n *\n * // subscriber.js\n * import { PubSubRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new PubSubRedisBroker(new Redis());\n * \tbroker.on('test', ({ data, ack }) => {\n * \tconsole.log(data);\n * \tvoid ack();\n * });\n *\n * await broker.subscribe('subscribers', ['test']);\n * ```\n */\nexport class PubSubRedisBroker<TEvents extends Record<string, any>>\n\textends BaseRedisBroker<TEvents>\n\timplements IPubSubBroker<TEvents>\n{\n\t/**\n\t * {@inheritDoc IPubSubBroker.publish}\n\t */\n\tpublic async publish<Event extends keyof TEvents>(event: Event, data: TEvents[Event]): Promise<void> {\n\t\tawait this.redisClient.xadd(event as string, '*', BaseRedisBroker.STREAM_DATA_KEY, this.options.encode(data));\n\t}\n\n\tprotected emitEvent(id: Buffer, group: string, event: string, data: unknown) {\n\t\tconst payload: { ack(): Promise<void>; data: unknown } = {\n\t\t\tdata,\n\t\t\tack: async () => {\n\t\t\t\tawait this.redisClient.xack(event, group, id);\n\t\t\t},\n\t\t};\n\n\t\t// @ts-expect-error: Intended\n\t\tthis.emit(event, payload);\n\t}\n}\n","import type { Gateway, GatewayDispatchPayload, GatewayDispatchEvents, GatewaySendPayload } from '@discordjs/core';\nimport type { ManagerShardEventsMap, WebSocketShardEvents } from '@discordjs/ws';\nimport { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';\nimport type { PubSubRedisBroker } from './PubSubRedis.js';\n\n// need this to be its own type for some reason, the compiler doesn't behave the same way if we in-line it\ntype _DiscordEvents = {\n\t[K in GatewayDispatchEvents]: GatewayDispatchPayload & {\n\t\tt: K;\n\t};\n};\n\nexport type DiscordEvents = {\n\t[K in keyof _DiscordEvents]: _DiscordEvents[K]['d'];\n};\n\ninterface BrokerIntrinsicProps {\n\tshardId: number;\n}\n\ninterface Events extends DiscordEvents {\n\t// eslint-disable-next-line @typescript-eslint/no-use-before-define\n\t[RedisGateway.GatewaySendEvent]: GatewaySendPayload;\n}\n\nexport type RedisBrokerDiscordEvents = {\n\t[K in keyof Events]: BrokerIntrinsicProps & { payload: Events[K] };\n};\n\n/**\n * RedisGateway is an implementation for core's Gateway interface built on top of our Redis brokers.\n *\n * Some important notes:\n * - Instances for this class are for your consumers/services that need the gateway. naturally, the events passed into\n * `init` are the only ones the core client will be able to emit\n * - You can also opt to use the class as-is without `@discordjs/core`, if you so desire. Events are properly typed\n * - You need to implement your own gateway service. Refer to the example below for how that would look like. This class\n * offers some static methods and properties that help in this errand. It is extremely important that you `publish`\n * events as the receiving service expects, and also that you handle GatewaySend events.\n *\n * @example\n * ```ts\n * // gateway-service/index.ts\n * import { RedisGateway, PubSubRedisBroker, kUseRandomGroupName } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * // the `name` here probably should be env-determined if you need to scale this. see the main README for more information.\n * // also, we use a random group because we do NOT want work-balancing on gateway_send events.\n * const broker = new PubSubRedisBroker(new Redis(), { group: kUseRandomGroupName, name: 'send-consumer-1' });\n * const gateway = new WebSocketManager(gatewayOptionsHere); // see @discordjs/ws for examples.\n *\n * // emit events over the broker\n * gateway.on(WebSocketShardEvents.Dispatch, (...data) => void broker.publish(RedisGateway.toPublishArgs(data));\n *\n * // listen to payloads we should send to Discord\n * broker.on(RedisGateway.GatewaySendEvent, async ({ data: { payload, shardId }, ack }) => {\n * \tawait gateway.send(shardId, payload);\n * \tawait ack();\n * });\n * await broker.subscribe([RedisGateway.GatewaySendEvent]);\n * await gateway.connect();\n * ```\n *\n * ```ts\n * // other-service/index.ts\n * import { RedisGateway, PubSubRedisBroker, kUseRandomGroupName } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * // the name here should absolutely be env-determined, see the main README for more information.\n * const broker = new PubSubRedisBroker(new Redis(), { group: 'my-service-name', name: 'service-name-instance-1' });\n * // unfortunately, we have to know the shard count. ideally this should be an env var\n * const gateway = new RedisGateway(broker, Number.parseInt(process.env.SHARD_COUNT, 10));\n *\n * const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);\n * const client = new Client({ rest, gateway });\n *\n * // set up your client as you normally would with core\n *\n * // subscribe to the events that you want\n * await gateway.init([GatewayDispatchEvents.GuildCreate, GatewayDispatchEvents.MessageCreate]);\n * ```\n */\nexport class RedisGateway\n\textends AsyncEventEmitter<{ dispatch: ManagerShardEventsMap[WebSocketShardEvents.Dispatch] }>\n\timplements Gateway\n{\n\t/**\n\t * Event used over the broker used to tell shards to send a payload to Discord.\n\t */\n\tpublic static readonly GatewaySendEvent = 'gateway_send' as const;\n\n\t/**\n\t * Converts a dispatch event from `@discordjs/ws` to arguments for a `broker.publish` call.\n\t */\n\tpublic static toPublishArgs(\n\t\tdata: [payload: GatewayDispatchPayload, shardId: number],\n\t): [GatewayDispatchEvents, BrokerIntrinsicProps & { payload: GatewayDispatchPayload['d'] }] {\n\t\tconst [payload, shardId] = data;\n\t\treturn [payload.t, { shardId, payload: payload.d }];\n\t}\n\n\tpublic constructor(\n\t\tprivate readonly broker: PubSubRedisBroker<RedisBrokerDiscordEvents>,\n\t\tprivate readonly shardCount: number,\n\t) {\n\t\tsuper();\n\t}\n\n\tpublic getShardCount(): number {\n\t\treturn this.shardCount;\n\t}\n\n\tpublic async send(shardId: number, payload: GatewaySendPayload): Promise<void> {\n\t\tawait this.broker.publish('gateway_send', { payload, shardId });\n\t}\n\n\tpublic async init(events: GatewayDispatchEvents[]) {\n\t\tfor (const event of events) {\n\t\t\t// async_event_emitter nukes our types on this one.\n\t\t\tthis.broker.on(\n\t\t\t\tevent,\n\t\t\t\t({\n\t\t\t\t\tack,\n\t\t\t\t\tdata: { payload, shardId },\n\t\t\t\t}: {\n\t\t\t\t\t// eslint-disable-next-line @typescript-eslint/method-signature-style\n\t\t\t\t\tack: () => Promise<void>;\n\t\t\t\t\tdata: BrokerIntrinsicProps & { payload: any };\n\t\t\t\t}) => {\n\t\t\t\t\t// @ts-expect-error - Union shenanigans\n\t\t\t\t\tthis.emit('dispatch', { shardId, data: payload });\n\t\t\t\t\tvoid ack();\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tawait this.broker.subscribe(events);\n\t}\n}\n","import type { Buffer } from 'node:buffer';\nimport { clearTimeout, setTimeout } from 'node:timers';\nimport type Redis from 'ioredis/built/Redis.js';\nimport type { IRPCBroker } from '../Broker.js';\nimport type { RedisBrokerOptions } from './BaseRedis.js';\nimport { BaseRedisBroker, DefaultRedisBrokerOptions } from './BaseRedis.js';\n\ninterface InternalPromise {\n\treject(error: any): void;\n\tresolve(data: any): void;\n\ttimeout: NodeJS.Timeout;\n}\n\n/**\n * Options specific for an RPC Redis broker\n */\nexport interface RPCRedisBrokerOptions extends RedisBrokerOptions {\n\ttimeout?: number;\n}\n\n/**\n * Default values used for the {@link RPCRedisBrokerOptions}\n */\nexport const DefaultRPCRedisBrokerOptions = {\n\t...DefaultRedisBrokerOptions,\n\ttimeout: 5_000,\n} as const satisfies Required<Omit<RPCRedisBrokerOptions, 'group' | 'name'>>;\n\n/**\n * RPC broker powered by Redis\n *\n * @example\n * ```ts\n * // caller.js\n * import { RPCRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new RPCRedisBroker(new Redis());\n *\n * console.log(await broker.call('testcall', 'Hello World!'));\n * await broker.destroy();\n *\n * // responder.js\n * import { RPCRedisBroker } from '@discordjs/brokers';\n * import Redis from 'ioredis';\n *\n * const broker = new RPCRedisBroker(new Redis());\n * broker.on('testcall', ({ data, ack, reply }) => {\n * \tconsole.log('responder', data);\n * \tvoid ack();\n * \tvoid reply(`Echo: ${data}`);\n * });\n *\n * await broker.subscribe('responders', ['testcall']);\n * ```\n */\nexport class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses extends Record<keyof TEvents, any>>\n\textends BaseRedisBroker<TEvents, TResponses>\n\timplements IRPCBroker<TEvents, TResponses>\n{\n\t/**\n\t * Options this broker is using\n\t */\n\tprotected override readonly options: Required<RPCRedisBrokerOptions>;\n\n\t/**\n\t * @internal\n\t */\n\tprotected readonly promises = new Map<string, InternalPromise>();\n\n\tpublic constructor(redisClient: Redis, options: RPCRedisBrokerOptions) {\n\t\tsuper(redisClient, options);\n\t\tthis.options = { ...DefaultRPCRedisBrokerOptions, ...options };\n\n\t\tthis.streamReadClient.on('messageBuffer', (channel: Buffer, message: Buffer) => {\n\t\t\tconst [, id] = channel.toString().split(':');\n\t\t\tif (id && this.promises.has(id)) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/unbound-method\n\t\t\t\tconst { resolve, timeout } = this.promises.get(id)!;\n\t\t\t\tresolve(this.options.decode(message));\n\t\t\t\tclearTimeout(timeout);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * {@inheritDoc IRPCBroker.call}\n\t */\n\tpublic async call<Event extends keyof TEvents>(\n\t\tevent: Event,\n\t\tdata: TEvents[Event],\n\t\ttimeoutDuration: number = this.options.timeout,\n\t): Promise<TResponses[Event]> {\n\t\tconst id = await this.redisClient.xadd(\n\t\t\tevent as string,\n\t\t\t'*',\n\t\t\tBaseRedisBroker.STREAM_DATA_KEY,\n\t\t\tthis.options.encode(data),\n\t\t);\n\t\t// This id! assertion is valid. From redis docs:\n\t\t// \"The command returns a Null reply when used with the NOMKSTREAM option and the key doesn't exist.\"\n\t\t// See: https://redis.io/commands/xadd/\n\t\tconst rpcChannel = `${event as string}:${id!}`;\n\n\t\t// Construct the error here for better stack traces\n\t\tconst timedOut = new Error(`timed out after ${timeoutDuration}ms`);\n\n\t\tawait this.streamReadClient.subscribe(rpcChannel);\n\t\treturn new Promise<TResponses[Event]>((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => reject(timedOut), timeoutDuration).unref();\n\n\t\t\tthis.promises.set(id!, { resolve, reject, timeout });\n\t\t\t// eslint-disable-next-line promise/prefer-await-to-then\n\t\t}).finally(() => {\n\t\t\tvoid this.streamReadClient.unsubscribe(rpcChannel);\n\t\t\tthis.promises.delete(id!);\n\t\t});\n\t}\n\n\tprotected emitEvent(id: Buffer, event: string, data: unknown) {\n\t\tconst payload: { ack(): Promise<void>; data: unknown; reply(data: unknown): Promise<void> } = {\n\t\t\tdata,\n\t\t\tack: async () => {\n\t\t\t\tawait this.redisClient.xack(event, this.group, id);\n\t\t\t},\n\t\t\treply: async (data) => {\n\t\t\t\tawait this.redisClient.publish(`${event}:${id.toString()}`, this.options.encode(data));\n\t\t\t},\n\t\t};\n\n\t\t// @ts-expect-error: Intended\n\t\tthis.emit(event, payload);\n\t}\n}\n","export * from './brokers/redis/BaseRedis.js';\nexport * from './brokers/redis/PubSubRedis.js';\nexport * from './brokers/redis/RedisGateway.js';\nexport * from './brokers/redis/RPCRedis.js';\n\nexport * from './brokers/Broker.js';\n\n/**\n * The {@link https://github.com/discordjs/discord.js/blob/main/packages/brokers#readme | @discordjs/brokers} version\n * that you are currently using.\n *\n * @privateRemarks This needs to explicitly be `string` so it is not typed as a \"const string\" that gets injected by esbuild.\n */\nexport const version = '2.0.0-pr-11005.1765454364-f3f6d34e7' as string;\n"],"mappings":"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","names":["Buffer","Buffer","idx","AsyncEventEmitter","AsyncEventEmitter","resolve","data"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@discordjs/brokers",
4
- "version": "2.0.0-pr-11006.1765450794-e636950b2",
4
+ "version": "2.0.0-pr-11005.1765454364-f3f6d34e7",
5
5
  "description": "Powerful set of message brokers",
6
6
  "exports": {
7
7
  ".": {
@@ -60,21 +60,23 @@
60
60
  "ioredis": "^5.8.2"
61
61
  },
62
62
  "devDependencies": {
63
- "@favware/cliff-jumper": "^6.0.0",
64
- "@types/node": "^22.19.1",
65
- "@vitest/coverage-v8": "^4.0.15",
63
+ "@favware/cliff-jumper": "^4.1.0",
64
+ "@types/node": "^22.18.13",
65
+ "@vitest/coverage-v8": "^3.2.4",
66
66
  "cross-env": "^10.1.0",
67
67
  "esbuild-plugin-version-injector": "^1.2.1",
68
- "eslint": "^9.39.1",
68
+ "eslint": "^9.38.0",
69
69
  "eslint-config-neon": "^0.2.9",
70
70
  "eslint-formatter-compact": "^9.0.1",
71
71
  "eslint-formatter-pretty": "^7.0.0",
72
- "prettier": "^3.7.4",
73
- "tsup": "^8.5.1",
74
- "turbo": "^2.6.3",
72
+ "prettier": "^3.6.2",
73
+ "tsup": "^8.5.0",
74
+ "turbo": "^2.5.8",
75
75
  "typescript": "~5.9.3",
76
- "vitest": "^4.0.15",
76
+ "vitest": "^3.2.4",
77
77
  "@discordjs/api-extractor": "7.52.7",
78
+ "@discordjs/core": "3.0.0-pr-11005.1765454364-f3f6d34e7",
79
+ "@discordjs/ws": "3.0.0-pr-11005.1765454364-f3f6d34e7",
78
80
  "@discordjs/scripts": "0.1.0"
79
81
  },
80
82
  "engines": {