@discordjs/brokers 2.0.0-pr-11005.1765454364-f3f6d34e7 → 2.0.0-pr-10758.1765463096-d081e1706

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,8 +1,6 @@
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';
6
4
  import Redis$1 from 'ioredis/built/Redis.js';
7
5
 
8
6
  /**
@@ -209,99 +207,6 @@ declare class PubSubRedisBroker<TEvents extends Record<string, any>> extends Bas
209
207
  protected emitEvent(id: Buffer, group: string, event: string, data: unknown): void;
210
208
  }
211
209
 
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
-
305
210
  interface InternalPromise {
306
211
  reject(error: any): void;
307
212
  resolve(data: any): void;
@@ -378,4 +283,4 @@ declare class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses e
378
283
  */
379
284
  declare const version: string;
380
285
 
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 };
286
+ export { type BaseBrokerOptions, BaseRedisBroker, DefaultBrokerOptions, DefaultRPCRedisBrokerOptions, DefaultRedisBrokerOptions, type IBaseBroker, type IPubSubBroker, type IRPCBroker, PubSubRedisBroker, RPCRedisBroker, type RPCRedisBrokerOptions, type RedisBrokerOptions, type ToEventMap, kUseRandomGroupName, version };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,6 @@
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';
6
4
  import Redis$1 from 'ioredis/built/Redis.js';
7
5
 
8
6
  /**
@@ -209,99 +207,6 @@ declare class PubSubRedisBroker<TEvents extends Record<string, any>> extends Bas
209
207
  protected emitEvent(id: Buffer, group: string, event: string, data: unknown): void;
210
208
  }
211
209
 
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
-
305
210
  interface InternalPromise {
306
211
  reject(error: any): void;
307
212
  resolve(data: any): void;
@@ -378,4 +283,4 @@ declare class RPCRedisBroker<TEvents extends Record<string, any[]>, TResponses e
378
283
  */
379
284
  declare const version: string;
380
285
 
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 };
286
+ export { type BaseBrokerOptions, BaseRedisBroker, DefaultBrokerOptions, DefaultRPCRedisBrokerOptions, DefaultRedisBrokerOptions, type IBaseBroker, type IPubSubBroker, type IRPCBroker, PubSubRedisBroker, RPCRedisBroker, type RPCRedisBrokerOptions, type RedisBrokerOptions, type ToEventMap, kUseRandomGroupName, version };
package/dist/index.js CHANGED
@@ -27,7 +27,6 @@ __export(index_exports, {
27
27
  DefaultRedisBrokerOptions: () => DefaultRedisBrokerOptions,
28
28
  PubSubRedisBroker: () => PubSubRedisBroker,
29
29
  RPCRedisBroker: () => RPCRedisBroker,
30
- RedisGateway: () => RedisGateway,
31
30
  kUseRandomGroupName: () => kUseRandomGroupName,
32
31
  version: () => version
33
32
  });
@@ -52,7 +51,7 @@ var DefaultBrokerOptions = {
52
51
  };
53
52
 
54
53
  // src/brokers/redis/BaseRedis.ts
55
- var kUseRandomGroupName = Symbol.for("djs.brokers.useRandomGroupName");
54
+ var kUseRandomGroupName = /* @__PURE__ */ Symbol.for("djs.brokers.useRandomGroupName");
56
55
  var DefaultRedisBrokerOptions = {
57
56
  ...DefaultBrokerOptions,
58
57
  maxChunk: 10,
@@ -270,53 +269,7 @@ var PubSubRedisBroker = class extends BaseRedisBroker {
270
269
  }
271
270
  };
272
271
 
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
-
318
272
  // src/brokers/redis/RPCRedis.ts
319
- var import_node_timers = require("timers");
320
273
  var DefaultRPCRedisBrokerOptions = {
321
274
  ...DefaultRedisBrokerOptions,
322
275
  timeout: 5e3
@@ -341,7 +294,7 @@ var RPCRedisBroker = class extends BaseRedisBroker {
341
294
  if (id && this.promises.has(id)) {
342
295
  const { resolve: resolve2, timeout } = this.promises.get(id);
343
296
  resolve2(this.options.decode(message));
344
- (0, import_node_timers.clearTimeout)(timeout);
297
+ clearTimeout(timeout);
345
298
  }
346
299
  });
347
300
  }
@@ -359,7 +312,7 @@ var RPCRedisBroker = class extends BaseRedisBroker {
359
312
  const timedOut = new Error(`timed out after ${timeoutDuration}ms`);
360
313
  await this.streamReadClient.subscribe(rpcChannel);
361
314
  return new Promise((resolve2, reject) => {
362
- const timeout = (0, import_node_timers.setTimeout)(() => reject(timedOut), timeoutDuration).unref();
315
+ const timeout = setTimeout(() => reject(timedOut), timeoutDuration).unref();
363
316
  this.promises.set(id, { resolve: resolve2, reject, timeout });
364
317
  }).finally(() => {
365
318
  void this.streamReadClient.unsubscribe(rpcChannel);
@@ -381,7 +334,7 @@ var RPCRedisBroker = class extends BaseRedisBroker {
381
334
  };
382
335
 
383
336
  // src/index.ts
384
- var version = "2.0.0-pr-11005.1765454364-f3f6d34e7";
337
+ var version = "2.0.0-pr-10758.1765463096-d081e1706";
385
338
  // Annotate the CommonJS export names for ESM import in node:
386
339
  0 && (module.exports = {
387
340
  BaseRedisBroker,
@@ -390,7 +343,6 @@ var version = "2.0.0-pr-11005.1765454364-f3f6d34e7";
390
343
  DefaultRedisBrokerOptions,
391
344
  PubSubRedisBroker,
392
345
  RPCRedisBroker,
393
- RedisGateway,
394
346
  kUseRandomGroupName,
395
347
  version
396
348
  });
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/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":"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","names":["idx","import_async_event_emitter","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/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-10758.1765463096-d081e1706' 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"]}
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.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
4
+ // ../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.55.2_@types+node@22.19.2__jiti@2.6.1_postcss@8.5._f366b06a2b111551fa9c30aa00a19284/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 = Symbol.for("djs.brokers.useRandomGroupName");
30
+ var kUseRandomGroupName = /* @__PURE__ */ Symbol.for("djs.brokers.useRandomGroupName");
31
31
  var DefaultRedisBrokerOptions = {
32
32
  ...DefaultBrokerOptions,
33
33
  maxChunk: 10,
@@ -245,53 +245,7 @@ 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
-
293
248
  // src/brokers/redis/RPCRedis.ts
294
- import { clearTimeout, setTimeout } from "timers";
295
249
  var DefaultRPCRedisBrokerOptions = {
296
250
  ...DefaultRedisBrokerOptions,
297
251
  timeout: 5e3
@@ -356,7 +310,7 @@ var RPCRedisBroker = class extends BaseRedisBroker {
356
310
  };
357
311
 
358
312
  // src/index.ts
359
- var version = "2.0.0-pr-11005.1765454364-f3f6d34e7";
313
+ var version = "2.0.0-pr-10758.1765463096-d081e1706";
360
314
  export {
361
315
  BaseRedisBroker,
362
316
  DefaultBrokerOptions,
@@ -364,7 +318,6 @@ export {
364
318
  DefaultRedisBrokerOptions,
365
319
  PubSubRedisBroker,
366
320
  RPCRedisBroker,
367
- RedisGateway,
368
321
  kUseRandomGroupName,
369
322
  version
370
323
  };
@@ -1 +1 @@
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"]}
1
+ {"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.5.1_@microsoft+api-extractor@7.55.2_@types+node@22.19.2__jiti@2.6.1_postcss@8.5._f366b06a2b111551fa9c30aa00a19284/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-10758.1765463096-d081e1706' as string;\n"],"mappings":"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","names":["Buffer","Buffer","idx","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-11005.1765454364-f3f6d34e7",
4
+ "version": "2.0.0-pr-10758.1765463096-d081e1706",
5
5
  "description": "Powerful set of message brokers",
6
6
  "exports": {
7
7
  ".": {
@@ -60,23 +60,21 @@
60
60
  "ioredis": "^5.8.2"
61
61
  },
62
62
  "devDependencies": {
63
- "@favware/cliff-jumper": "^4.1.0",
64
- "@types/node": "^22.18.13",
65
- "@vitest/coverage-v8": "^3.2.4",
63
+ "@favware/cliff-jumper": "^6.0.0",
64
+ "@types/node": "^22.19.2",
65
+ "@vitest/coverage-v8": "^4.0.15",
66
66
  "cross-env": "^10.1.0",
67
67
  "esbuild-plugin-version-injector": "^1.2.1",
68
- "eslint": "^9.38.0",
68
+ "eslint": "^9.39.1",
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.6.2",
73
- "tsup": "^8.5.0",
74
- "turbo": "^2.5.8",
72
+ "prettier": "^3.7.4",
73
+ "tsup": "^8.5.1",
74
+ "turbo": "^2.6.3",
75
75
  "typescript": "~5.9.3",
76
- "vitest": "^3.2.4",
76
+ "vitest": "^4.0.15",
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",
80
78
  "@discordjs/scripts": "0.1.0"
81
79
  },
82
80
  "engines": {