Skip to content

Commit

Permalink
feat(Managers): add customizable caching for managers (#6013)
Browse files Browse the repository at this point in the history
  • Loading branch information
1Computer1 committed Jul 3, 2021
1 parent ec06ba7 commit 8c7cb0e
Show file tree
Hide file tree
Showing 35 changed files with 498 additions and 316 deletions.
4 changes: 2 additions & 2 deletions src/client/BaseClient.js
Expand Up @@ -2,7 +2,7 @@

const EventEmitter = require('events');
const RESTManager = require('../rest/RESTManager');
const { DefaultOptions } = require('../util/Constants');
const Options = require('../util/Options');
const Util = require('../util/Util');

/**
Expand Down Expand Up @@ -38,7 +38,7 @@ class BaseClient extends EventEmitter {
* The options the client was instantiated with
* @type {ClientOptions}
*/
this.options = Util.mergeDefault(DefaultOptions, options);
this.options = Util.mergeDefault(Options.createDefault(), options);

/**
* The REST manager of the client
Expand Down
12 changes: 7 additions & 5 deletions src/client/Client.js
Expand Up @@ -17,9 +17,10 @@ const VoiceRegion = require('../structures/VoiceRegion');
const Webhook = require('../structures/Webhook');
const Widget = require('../structures/Widget');
const Collection = require('../util/Collection');
const { Events, DefaultOptions, InviteScopes } = require('../util/Constants');
const { Events, InviteScopes } = require('../util/Constants');
const DataResolver = require('../util/DataResolver');
const Intents = require('../util/Intents');
const Options = require('../util/Options');
const Permissions = require('../util/Permissions');
const Structures = require('../util/Structures');

Expand All @@ -35,14 +36,15 @@ class Client extends BaseClient {
super(Object.assign({ _tokenType: 'Bot' }, options));

const data = require('worker_threads').workerData ?? process.env;
const defaults = Options.createDefault();

if (this.options.shards === DefaultOptions.shards) {
if (this.options.shards === defaults.shards) {
if ('SHARDS' in data) {
this.options.shards = JSON.parse(data.SHARDS);
}
}

if (this.options.shardCount === DefaultOptions.shardCount) {
if (this.options.shardCount === defaults.shardCount) {
if ('SHARD_COUNT' in data) {
this.options.shardCount = Number(data.SHARD_COUNT);
} else if (Array.isArray(this.options.shards)) {
Expand Down Expand Up @@ -468,8 +470,8 @@ class Client extends BaseClient {
throw new TypeError('CLIENT_INVALID_OPTION', 'shards', "'auto', a number or array of numbers");
}
if (options.shards && !options.shards.length) throw new RangeError('CLIENT_INVALID_PROVIDED_SHARDS');
if (typeof options.messageCacheMaxSize !== 'number' || isNaN(options.messageCacheMaxSize)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'messageCacheMaxSize', 'a number');
if (typeof options.makeCache !== 'function') {
throw new TypeError('CLIENT_INVALID_OPTION', 'makeCache', 'a function');
}
if (typeof options.messageCacheLifetime !== 'number' || isNaN(options.messageCacheLifetime)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'The messageCacheLifetime', 'a number');
Expand Down
2 changes: 2 additions & 0 deletions src/errors/Messages.js
Expand Up @@ -127,6 +127,8 @@ const Messages = {
INTERACTION_NOT_REPLIED: 'This interaction has not been deferred or replied to.',
INTERACTION_EPHEMERAL_REPLIED: 'Ephemeral responses cannot be fetched or deleted.',
INTERACTION_FETCH_EPHEMERAL: 'Ephemeral responses cannot be fetched.',

NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
};

for (const [name, message] of Object.entries(Messages)) register(name, message);
1 change: 1 addition & 0 deletions src/index.js
Expand Up @@ -23,6 +23,7 @@ module.exports = {
RateLimitError: require('./rest/RateLimitError'),
MessageFlags: require('./util/MessageFlags'),
Intents: require('./util/Intents'),
Options: require('./util/Options'),
Permissions: require('./util/Permissions'),
SnowflakeUtil: require('./util/SnowflakeUtil'),
Structures: require('./util/Structures'),
Expand Down
8 changes: 4 additions & 4 deletions src/managers/ApplicationCommandManager.js
@@ -1,18 +1,18 @@
'use strict';

const ApplicationCommandPermissionsManager = require('./ApplicationCommandPermissionsManager');
const BaseManager = require('./BaseManager');
const CachedManager = require('./CachedManager');
const { TypeError } = require('../errors');
const ApplicationCommand = require('../structures/ApplicationCommand');
const Collection = require('../util/Collection');

/**
* Manages API methods for application commands and stores their cache.
* @extends {BaseManager}
* @extends {CachedManager}
*/
class ApplicationCommandManager extends BaseManager {
class ApplicationCommandManager extends CachedManager {
constructor(client, iterable) {
super(client, iterable, ApplicationCommand);
super(client, ApplicationCommand, iterable);

/**
* The manager for permissions of arbitrary commands on arbitrary guilds
Expand Down
14 changes: 5 additions & 9 deletions src/managers/ApplicationCommandPermissionsManager.js
@@ -1,14 +1,18 @@
'use strict';

const BaseManager = require('./BaseManager');
const { Error, TypeError } = require('../errors');
const Collection = require('../util/Collection');
const { ApplicationCommandPermissionTypes, APIErrors } = require('../util/Constants');

/**
* Manages API methods for permissions of Application Commands.
* @extends {BaseManager}
*/
class ApplicationCommandPermissionsManager {
class ApplicationCommandPermissionsManager extends BaseManager {
constructor(manager) {
super(manager.client);

/**
* The manager or command that this manager belongs to
* @type {ApplicationCommandManager|ApplicationCommand}
Expand All @@ -32,14 +36,6 @@ class ApplicationCommandPermissionsManager {
* @type {?Snowflake}
*/
this.commandID = manager.id ?? null;

/**
* The client that instantiated this Manager
* @name ApplicationCommandPermissionsManager#client
* @type {Client}
* @readonly
*/
Object.defineProperty(this, 'client', { value: manager.client });
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/managers/BaseGuildEmojiManager.js
@@ -1,17 +1,17 @@
'use strict';

const BaseManager = require('./BaseManager');
const CachedManager = require('./CachedManager');
const GuildEmoji = require('../structures/GuildEmoji');
const ReactionEmoji = require('../structures/ReactionEmoji');
const { parseEmoji } = require('../util/Util');

/**
* Holds methods to resolve GuildEmojis and stores their cache.
* @extends {BaseManager}
* @extends {CachedManager}
*/
class BaseGuildEmojiManager extends BaseManager {
class BaseGuildEmojiManager extends CachedManager {
constructor(client, iterable) {
super(client, iterable, GuildEmoji);
super(client, GuildEmoji, iterable);
}

/**
Expand Down
66 changes: 2 additions & 64 deletions src/managers/BaseManager.js
@@ -1,80 +1,18 @@
'use strict';

const Collection = require('../util/Collection');
let Structures;

/**
* Manages the API methods of a data model and holds its cache.
* Manages the API methods of a data model.
* @abstract
*/
class BaseManager {
constructor(client, iterable, holds, cacheType = Collection, ...cacheOptions) {
if (!Structures) Structures = require('../util/Structures');
/**
* The data structure belonging to this manager
* @name BaseManager#holds
* @type {Function}
* @private
* @readonly
*/
Object.defineProperty(this, 'holds', { value: Structures.get(holds.name) ?? holds });

constructor(client) {
/**
* The client that instantiated this Manager
* @name BaseManager#client
* @type {Client}
* @readonly
*/
Object.defineProperty(this, 'client', { value: client });

/**
* The type of Collection of the Manager
* @type {Collection}
*/
this.cacheType = cacheType;

/**
* Holds the cache for the data model
* @type {Collection}
*/
this.cache = new cacheType(...cacheOptions);
if (iterable) for (const i of iterable) this.add(i);
}

add(data, cache = true, { id, extras = [] } = {}) {
const existing = this.cache.get(id ?? data.id);
if (cache) existing?._patch(data);
if (existing) return existing;

const entry = this.holds ? new this.holds(this.client, data, ...extras) : data;
if (cache) this.cache.set(id ?? entry.id, entry);
return entry;
}

/**
* Resolves a data entry to a data Object.
* @param {string|Object} idOrInstance The id or instance of something in this Manager
* @returns {?Object} An instance from this Manager
*/
resolve(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance;
if (typeof idOrInstance === 'string') return this.cache.get(idOrInstance) ?? null;
return null;
}

/**
* Resolves a data entry to an instance ID.
* @param {string|Object} idOrInstance The id or instance of something in this Manager
* @returns {?Snowflake}
*/
resolveID(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance.id;
if (typeof idOrInstance === 'string') return idOrInstance;
return null;
}

valueOf() {
return this.cache;
}
}

Expand Down
43 changes: 43 additions & 0 deletions src/managers/CachedManager.js
@@ -0,0 +1,43 @@
'use strict';

const DataManager = require('./DataManager');

/**
* Manages the API methods of a data model with a mutable cache of instances.
* @extends {DataManager}
* @abstract
*/
class CachedManager extends DataManager {
constructor(client, holds, iterable) {
super(client, holds);

Object.defineProperty(this, '_cache', { value: this.client.options.makeCache(this.constructor, this.holds) });

if (iterable) {
for (const item of iterable) {
this.add(item);
}
}
}

/**
* The cache of items for this manager.
* @type {Collection}
* @abstract
*/
get cache() {
return this._cache;
}

add(data, cache = true, { id, extras = [] } = {}) {
const existing = this.cache.get(id ?? data.id);
if (cache) existing?._patch(data);
if (existing) return existing;

const entry = this.holds ? new this.holds(this.client, data, ...extras) : data;
if (cache) this.cache.set(id ?? entry.id, entry);
return entry;
}
}

module.exports = CachedManager;
8 changes: 4 additions & 4 deletions src/managers/ChannelManager.js
@@ -1,16 +1,16 @@
'use strict';

const BaseManager = require('./BaseManager');
const CachedManager = require('./CachedManager');
const Channel = require('../structures/Channel');
const { Events, ThreadChannelTypes } = require('../util/Constants');

/**
* A manager of channels belonging to a client
* @extends {BaseManager}
* @extends {CachedManager}
*/
class ChannelManager extends BaseManager {
class ChannelManager extends CachedManager {
constructor(client, iterable) {
super(client, iterable, Channel);
super(client, Channel, iterable);
}

/**
Expand Down
65 changes: 65 additions & 0 deletions src/managers/DataManager.js
@@ -0,0 +1,65 @@
'use strict';

const BaseManager = require('./BaseManager');
const { Error } = require('../errors');

let Structures;

/**
* Manages the API methods of a data model along with a collection of instances.
* @extends {BaseManager}
* @abstract
*/
class DataManager extends BaseManager {
constructor(client, holds) {
super(client);

if (!Structures) Structures = require('../util/Structures');

/**
* The data structure belonging to this manager.
* @name DataManager#holds
* @type {Function}
* @private
* @readonly
*/
Object.defineProperty(this, 'holds', { value: Structures.get(holds.name) ?? holds });
}

/**
* The cache of items for this manager.
* @type {Collection}
* @abstract
*/
get cache() {
throw new Error('NOT_IMPLEMENTED', 'get cache', this.constructor.name);
}

/**
* Resolves a data entry to a data Object.
* @param {string|Object} idOrInstance The id or instance of something in this Manager
* @returns {?Object} An instance from this Manager
*/
resolve(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance;
if (typeof idOrInstance === 'string') return this.cache.get(idOrInstance) ?? null;
return null;
}

/**
* Resolves a data entry to an instance ID.
* @param {string|Object} idOrInstance The id or instance of something in this Manager
* @returns {?Snowflake}
*/
resolveID(idOrInstance) {
if (idOrInstance instanceof this.holds) return idOrInstance.id;
if (typeof idOrInstance === 'string') return idOrInstance;
return null;
}

valueOf() {
return this.cache;
}
}

module.exports = DataManager;
8 changes: 4 additions & 4 deletions src/managers/GuildBanManager.js
@@ -1,18 +1,18 @@
'use strict';

const BaseManager = require('./BaseManager');
const CachedManager = require('./CachedManager');
const { TypeError, Error } = require('../errors');
const GuildBan = require('../structures/GuildBan');
const GuildMember = require('../structures/GuildMember');
const Collection = require('../util/Collection');

/**
* Manages API methods for GuildBans and stores their cache.
* @extends {BaseManager}
* @extends {CachedManager}
*/
class GuildBanManager extends BaseManager {
class GuildBanManager extends CachedManager {
constructor(guild, iterable) {
super(guild.client, iterable, GuildBan);
super(guild.client, GuildBan, iterable);

/**
* The guild this Manager belongs to
Expand Down

0 comments on commit 8c7cb0e

Please sign in to comment.