Skip to content

Commit

Permalink
feat(Sharding*): contexts for broadcastEval (#5756)
Browse files Browse the repository at this point in the history
* feat(ShardClientUtil): add parameterList to broadcastEval

* feat(ShardingManager): add parameterList to broadcastEval

* chore: update typings

* refactor(Sharding*): use BroadcastEvalOptions

* chore: update typings

* docs: use serializable instead of stringifiable

* refactor: don't set broadcastEval default context

Co-authored-by: Antonio Román <kyradiscord@gmail.com>

* chore: fix inaccuracy in typings

* refactor(Sharding*): remove string-based broadcastEval

* fix(ShardingManager): incorrect usage of _broadcastEvalRaw

* refactor(ShardingManager): remove unnecessary method

* refactor(Sharding*): type check the eval script

* fix(ShardingManager): return Promise rejection rather than throwing an error

Co-authored-by: SpaceEEC <spaceeec@yahoo.com>

* chore: fix typings

Co-authored-by: SpaceEEC <spaceeec@yahoo.com>

Co-authored-by: Antonio Román <kyradiscord@gmail.com>
Co-authored-by: SpaceEEC <spaceeec@yahoo.com>
  • Loading branch information
3 people committed Jun 9, 2021
1 parent 7b2e12b commit c6aeebb
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 18 deletions.
1 change: 1 addition & 0 deletions src/errors/Messages.js
Expand Up @@ -24,6 +24,7 @@ const Messages = {
DISALLOWED_INTENTS: 'Privileged intent provided is not enabled or whitelisted.',
SHARDING_NO_SHARDS: 'No shards have been spawned.',
SHARDING_IN_PROCESS: 'Shards are still being spawned.',
SHARDING_INVALID_EVAL_BROADCAST: 'Script to evaluate must be a function',
SHARDING_SHARD_NOT_FOUND: id => `Shard ${id} could not be found.`,
SHARDING_ALREADY_SPAWNED: count => `Already spawned ${count} shards.`,
SHARDING_PROCESS_EXISTS: id => `Shard ${id} already has an active process.`,
Expand Down
2 changes: 1 addition & 1 deletion src/sharding/Shard.js
Expand Up @@ -348,7 +348,7 @@ class Shard extends EventEmitter {
// Shard is requesting an eval broadcast
if (message._sEval) {
const resp = { _sEval: message._sEval, _sEvalShard: message._sEvalShard };
this.manager.broadcastEval(message._sEval, message._sEvalShard).then(
this.manager._performOnShards('eval', [message._sEval], message._sEvalShard).then(
results => this.send({ ...resp, _result: results }),
err => this.send({ ...resp, _error: Util.makePlainError(err) }),
);
Expand Down
18 changes: 11 additions & 7 deletions src/sharding/ShardClientUtil.js
Expand Up @@ -128,29 +128,33 @@ class ShardClientUtil {

/**
* Evaluates a script or function on all shards, or a given shard, in the context of the {@link Client}s.
* @param {string|Function} script JavaScript to run on each shard
* @param {number} [shard] Shard to run script on, all if undefined
* @param {Function} script JavaScript to run on each shard
* @param {BroadcastEvalOptions} [options={}] The options for the broadcast
* @returns {Promise<*>|Promise<Array<*>>} Results of the script execution
* @example
* client.shard.broadcastEval('this.guilds.cache.size')
* client.shard.broadcastEval(client => client.guilds.cache.size)
* .then(results => console.log(`${results.reduce((prev, val) => prev + val, 0)} total guilds`))
* .catch(console.error);
* @see {@link ShardingManager#broadcastEval}
*/
broadcastEval(script, shard) {
broadcastEval(script, options = {}) {
return new Promise((resolve, reject) => {
const parent = this.parentPort || process;
script = typeof script === 'function' ? `(${script})(this)` : script;
if (typeof script !== 'function') {
reject(new TypeError('SHARDING_INVALID_EVAL_BROADCAST'));
return;
}
script = `(${script})(this, ${JSON.stringify(options.context)})`;

const listener = message => {
if (!message || message._sEval !== script || message._sEvalShard !== shard) return;
if (!message || message._sEval !== script || message._sEvalShard !== options.shard) return;
parent.removeListener('message', listener);
if (!message._error) resolve(message._result);
else reject(Util.makeError(message._error));
};
parent.on('message', listener);

this.send({ _sEval: script, _sEvalShard: shard }).catch(err => {
this.send({ _sEval: script, _sEvalShard: options.shard }).catch(err => {
parent.removeListener('message', listener);
reject(err);
});
Expand Down
16 changes: 12 additions & 4 deletions src/sharding/ShardingManager.js
Expand Up @@ -226,14 +226,22 @@ class ShardingManager extends EventEmitter {
return Promise.all(promises);
}

/**
* Options for {@link ShardingManager#broadcastEval} and {@link ShardClientUtil#broadcastEval}.
* @typedef {Object} BroadcastEvalOptions
* @property {number} [shard] Shard to run script on, all if undefined
* @property {*} [context] The JSON-serializable values to call the script with
*/

/**
* Evaluates a script on all shards, or a given shard, in the context of the {@link Client}s.
* @param {string} script JavaScript to run on each shard
* @param {number} [shard] Shard to run on, all if undefined
* @param {Function} script JavaScript to run on each shard
* @param {BroadcastEvalOptions} [options={}] The options for the broadcast
* @returns {Promise<*>|Promise<Array<*>>} Results of the script execution
*/
broadcastEval(script, shard) {
return this._performOnShards('eval', [script], shard);
broadcastEval(script, options = {}) {
if (typeof script !== 'function') return Promise.reject(new TypeError('SHARDING_INVALID_EVAL_BROADCAST'));
return this._performOnShards('eval', [`(${script})(this, ${JSON.stringify(options.context)})`], options.shard);
}

/**
Expand Down
15 changes: 9 additions & 6 deletions typings/index.d.ts
Expand Up @@ -1655,10 +1655,8 @@ declare module 'discord.js' {
public readonly ids: number[];
public mode: ShardingManagerMode;
public parentPort: any | null;
public broadcastEval(script: string): Promise<any[]>;
public broadcastEval(script: string, shard: number): Promise<any>;
public broadcastEval<T>(fn: (client: Client) => T): Promise<T[]>;
public broadcastEval<T>(fn: (client: Client) => T, shard: number): Promise<T>;
public broadcastEval<T, P>(fn: (client: Client, context: P) => T, { shard: undefined, context: P }?: BroadcastEvalOptions): Promise<T[]>;
public broadcastEval<T, P>(fn: (client: Client, context: P) => T, { shard: number, context: P }: BroadcastEvalOptions): Promise<T>;
public fetchClientValues(prop: string): Promise<any[]>;
public fetchClientValues(prop: string, shard: number): Promise<any>;
public respawnAll(options?: { shardDelay?: number; respawnDelay?: number; timeout?: number }): Promise<void>;
Expand All @@ -1681,8 +1679,8 @@ declare module 'discord.js' {
public totalShards: number | 'auto';
public shardList: number[] | 'auto';
public broadcast(message: any): Promise<Shard[]>;
public broadcastEval(script: string): Promise<any[]>;
public broadcastEval(script: string, shard: number): Promise<any>;
public broadcastEval<T, P>(fn: (client: Client, context: P) => T, { shard: undefined, context: P }?: BroadcastEvalOptions): Promise<T[]>;
public broadcastEval<T, P>(fn: (client: Client, context: P) => T, { shard: number, context: P }: BroadcastEvalOptions): Promise<T>;
public createShard(id: number): Shard;
public fetchClientValues(prop: string): Promise<any[]>;
public fetchClientValues(prop: string, shard: number): Promise<any>;
Expand Down Expand Up @@ -2536,6 +2534,11 @@ declare module 'discord.js' {
| N
| Readonly<BitField<T, N>>;

interface BroadcastEvalOptions<T = unknown> {
shard?: number;
context?: T;
}

type BufferResolvable = Buffer | string;

interface ChannelCreationOverwrites {
Expand Down

0 comments on commit c6aeebb

Please sign in to comment.