From d89ea96ff5a6c757926c785e170a79a9608914aa Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 12:23:50 +0100 Subject: [PATCH 01/76] SGv2.1 refactor --- src/lib/Client.js | 6 +- src/lib/settings/Configuration.js | 24 +---- src/lib/settings/Gateway.js | 42 ++++---- src/lib/settings/GatewayDriver.js | 63 ++++++------ src/lib/settings/SchemaFolder.js | 77 ++++++++++----- src/lib/util/constants.js | 39 -------- src/lib/util/util.js | 15 +++ src/providers/collection.js | 154 ------------------------------ typings/index.d.ts | 29 +++--- 9 files changed, 145 insertions(+), 304 deletions(-) delete mode 100644 src/providers/collection.js diff --git a/src/lib/Client.js b/src/lib/Client.js index 2a9d346df2..059adfaaf1 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -413,9 +413,9 @@ class KlasaClient extends Discord.Client { // Add the gateways await Promise.all([ - this.gateways.add('guilds', constants.GATEWAY_RESOLVERS.GUILDS, this.gateways.guildsSchema, this.options.gateways.guilds, false), - this.gateways.add('users', constants.GATEWAY_RESOLVERS.USERS, undefined, this.options.gateways.users, false), - this.gateways.add('clientStorage', constants.GATEWAY_RESOLVERS.CLIENT_STORAGE, this.gateways.clientStorageSchema, this.options.gateways.clientStorage, false) + this.gateways.add('guilds', this.gateways.guildsSchema, this.options.gateways.guilds, false), + this.gateways.add('users', undefined, this.options.gateways.users, false), + this.gateways.add('clientStorage', this.gateways.clientStorageSchema, this.options.gateways.clientStorage, false) ]); // Automatic Prefix editing detection. diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 9e10ea13c6..4831baf146 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -1,4 +1,4 @@ -const { isObject, makeObject, deepClone, tryParse } = require('../util/util'); +const { isObject, makeObject, deepClone, tryParse, getIdentifier } = require('../util/util'); /** * Creating your own Configuration instances if often discouraged and unneeded. SettingGateway handles them internally for you. @@ -323,7 +323,7 @@ class Configuration { if (path.array === true) throw 'This key is array type.'; const parsed = await path.parse(value, guild); - const parsedID = Configuration.getIdentifier(parsed); + const parsedID = getIdentifier(parsed); await this._setValue(parsedID, path, route); return { parsed, parsedID, array: null, path, route }; } @@ -347,7 +347,7 @@ class Configuration { } const parsed = await path.parse(value, guild); - const parsedID = path.type !== 'any' ? Configuration.getIdentifier(parsed) : parsed; + const parsedID = path.type !== 'any' ? getIdentifier(parsed) : parsed; // Handle entry creation if it does not exist. if (!this._existsInDB) await this.gateway.createEntry(this.id); @@ -444,7 +444,7 @@ class Configuration { } else { const promise = schema[key].array && schema[key].type !== 'any' ? Promise.all(object[key].map(entry => schema[key].parse(entry, guild) - .then(Configuration.getIdentifier) + .then(getIdentifier) .catch(error => list.errors.push([schema[key].path, error])))) : schema[key].parse(object[key], guild); @@ -452,7 +452,7 @@ class Configuration { .then(parsed => { const parsedID = schema[key].array ? parsed.filter(entry => typeof entry !== 'undefined') : - Configuration.getIdentifier(parsed); + getIdentifier(parsed); updateObject[key] = cache[key] = parsedID; list.keys.push(schema[key].path); list.values.push(parsedID); @@ -585,20 +585,6 @@ class Configuration { } } - /** - * Get the identifier of a value. - * @since 0.5.0 - * @param {*} value The value to get the identifier from - * @returns {*} - * @private - */ - static getIdentifier(value) { - if (typeof value !== 'object' || value === null) return value; - if (value.id) return value.id; - if (value.name) return value.name; - return value; - } - } module.exports = Configuration; diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index ad64cf4b3d..7f4b10398f 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -2,7 +2,8 @@ const GatewayStorage = require('./GatewayStorage'); const Configuration = require('./Configuration'); const SchemaPiece = require('./SchemaPiece'); const SchemaFolder = require('./SchemaFolder'); -const discord = require('discord.js'); +const { Collection, Guild, GuildChannel, Message, Role, GuildMember } = require('discord.js'); +const { getIdentifier } = require('../util/util'); /** * You should never create a Gateway instance by yourself. @@ -42,11 +43,10 @@ class Gateway extends GatewayStorage { * @since 0.0.1 * @param {GatewayDriver} store The GatewayDriver instance which initiated this instance * @param {string} type The name of this Gateway - * @param {Function} validateFunction The function that validates the entries' values * @param {Object} schema The initial schema for this instance * @param {GatewayOptions} options The options for this schema */ - constructor(store, type, validateFunction, schema, options) { + constructor(store, type, schema, options) { super(store.client, type, options.provider); /** @@ -63,15 +63,15 @@ class Gateway extends GatewayStorage { /** * @since 0.3.0 - * @type {Function} + * @type {Object} */ - this.validate = validateFunction; + this.defaultSchema = schema; /** - * @since 0.3.0 - * @type {Object} + * @since 0.0.1 + * @type {external:Collection} */ - this.defaultSchema = schema; + this.cache = new Collection(); } /** @@ -85,16 +85,6 @@ class Gateway extends GatewayStorage { return Configuration; } - /** - * Get the cache-provider that manages the cache data. - * @since 0.0.1 - * @type {Provider} - * @readonly - */ - get cache() { - return this.client.providers.get('collection'); - } - /** * @since 0.0.1 * @type {SettingResolver} @@ -125,7 +115,7 @@ class Gateway extends GatewayStorage { configs.existsInDB = true; if (this.client.listenerCount('configCreateEntry')) this.client.emit('configCreateEntry', configs); }) - .catch(error => this.client.emit('log', error, 'error')); + .catch(error => this.client.emit('error', error)); return configs; } return entry; @@ -140,7 +130,8 @@ class Gateway extends GatewayStorage { * @returns {Promise} */ async createEntry(input) { - const target = await this.validate(input).then(output => output && output.id ? output.id : output); + const target = getIdentifier(input); + if (!target) throw new TypeError('The selected target could not be resolved to a string.'); const cache = this.cache.get(this.type, target); if (cache && cache.existsInDB) return cache; await this.provider.create(this.type, target); @@ -202,7 +193,9 @@ class Gateway extends GatewayStorage { } } } - const target = await this.validate(input).then(output => output && output.id ? output.id : output); + const target = getIdentifier(input); + if (!target) throw new TypeError('The selected target could not be resolved to a string.'); + const cache = this.cache.get(this.type, target); if (cache) return cache.sync(); @@ -291,8 +284,11 @@ class Gateway extends GatewayStorage { */ _resolveGuild(guild) { if (typeof guild === 'object') { - if (guild instanceof discord.Guild) return guild; - if (guild instanceof discord.GuildChannel || guild instanceof discord.Message || guild instanceof discord.Role || guild instanceof discord.GuildMember) return guild.guild; + if (guild instanceof Guild) return guild; + if (guild instanceof GuildChannel || + guild instanceof Message || + guild instanceof Role || + guild instanceof GuildMember) return guild.guild; } if (typeof guild === 'string' && /^\d{17,19}$/.test(guild)) return this.client.guilds.get(guild); return null; diff --git a/src/lib/settings/GatewayDriver.js b/src/lib/settings/GatewayDriver.js index 8667a3263d..9910384e6f 100644 --- a/src/lib/settings/GatewayDriver.js +++ b/src/lib/settings/GatewayDriver.js @@ -60,10 +60,10 @@ class GatewayDriver { this.types = Object.getOwnPropertyNames(SettingResolver.prototype).slice(1); /** - * All the caches added - * @type {string[]} + * All the gateways added + * @type {Set} */ - this.caches = []; + this.keys = new Set(); /** * If the driver is ready @@ -176,21 +176,39 @@ class GatewayDriver { } /** - * Add a new instance of SettingGateway, with its own validateFunction and schema. + * Registers a new Gateway. + * @param {string} name The name for the new gateway + * @param {Object} [schema={}] The schema for use in this gateway + * @param {GatewayDriverAddOptions} [options={}] The options for the new gateway + * @returns {Gateway} + */ + register(name, schema = {}, options = {}) { + if (typeof name !== 'string') throw 'You must pass a name for your new gateway and it must be a string.'; + + if (name in this) throw 'There is already a Gateway with that name.'; + if (!this.client.methods.util.isObject(schema)) throw 'Schema must be a valid object or left undefined for an empty object.'; + + options.provider = this._checkProvider(options.provider || this.client.options.providers.default); + const provider = this.client.providers.get(options.provider); + if (provider.cache) throw `The provider ${provider.name} is designed for caching, not persistent data. Please try again with another.`; + + const gateway = new Gateway(this, name, schema, options); + this.keys.add(name); + this[name] = gateway; + + return gateway; + } + + /** + * Registers a new Gateway and inits it. * @since 0.3.0 * @param {string} name The name for the new instance - * @param {Function} validateFunction The function that validates the input * @param {Object} [schema={}] The schema * @param {GatewayDriverAddOptions} [options={}] A provider to use. If not specified it'll use the one in the client * @param {boolean} [download=true] Whether this Gateway should download the data from the database at init * @returns {Gateway} * @example * // Add a new SettingGateway instance, called 'users', which input takes users, and stores a quote which is a string between 2 and 140 characters. - * const validate = async function(resolver, user) { - * const result = await resolver.user(user); - * if (!result) throw 'The parameter expects either a User ID or a User Object.'; - * return result; - * }; * const schema = { * quote: { * type: 'String', @@ -200,25 +218,11 @@ class GatewayDriver { * max: 140, * }, * }; - * GatewayDriver.add('users', validate, schema); + * GatewayDriver.add('users', schema); */ - async add(name, validateFunction, schema = {}, options = {}, download = true) { - if (typeof name !== 'string') throw 'You must pass a name for your new gateway and it must be a string.'; - - if (this[name] !== undefined && this[name] !== null) throw 'There is already a Gateway with that name.'; - if (typeof validateFunction !== 'function') throw 'You must pass a validate function.'; - validateFunction = validateFunction.bind(this); - if (!this.client.methods.util.isObject(schema)) throw 'Schema must be a valid object or left undefined for an empty object.'; - - options.provider = this._checkProvider(options.provider || this.client.options.providers.default); - const provider = this.client.providers.get(options.provider); - if (provider.cache) throw `The provider ${provider.name} is designed for caching, not persistent data. Please try again with another.`; - options.cache = this._checkProvider('collection'); - - const gateway = new Gateway(this, name, validateFunction, schema, options); + async add(name, schema = {}, options = {}, download = true) { + const gateway = this.register(name, schema, options); await gateway.init(download); - this.caches.push(name); - this[name] = gateway; return gateway; } @@ -229,12 +233,13 @@ class GatewayDriver { * @returns {Promise>>>} * @private */ - _ready() { + async _ready() { if (this.ready) throw 'Configuration has already run the ready method.'; this.ready = true; const promises = []; for (const cache of this.caches) { - this[cache].ready = true; + // If the gateway did not init yet, init it now + if (!this[cache].ready) await this[cache].init(); promises.push(this[cache]._ready()); } return Promise.all(promises); diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index cdcfdddbab..a25cf3008f 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -49,14 +49,6 @@ class SchemaFolder extends Schema { */ Object.defineProperty(this, 'defaults', { value: {}, writable: true }); - /** - * A Set containing all keys' names which value is either a SchemaFolder or a SchemaPiece instance. - * @since 0.5.0 - * @type {Set} - * @name SchemaFolder#keys - */ - Object.defineProperty(this, 'keys', { value: new Set(), writable: true }); - /** * A pre-processed array with all keys' names. * @since 0.5.0 @@ -144,7 +136,7 @@ class SchemaFolder extends Schema { * @returns {boolean} */ hasKey(key) { - return this.keys.has(key); + return this.keyArray.includes(key); } /** @@ -299,13 +291,13 @@ class SchemaFolder extends Schema { /** * Get all the pathes from this schema's children. * @since 0.5.0 - * @param {string[]} [array=[]] The array to push. * @returns {string[]} */ - getKeys(array = []) { - for (const key of this.keyArray) { - if (this[key].type === 'Folder') this[key].getKeys(array); - else array.push(this[key].path); + getAllKeys() { + const array = []; + for (const value of this.values()) { + if (value.type === 'Folder') array.push(...value.keyArray()); + else array.push(value.path); } return array; } @@ -313,13 +305,13 @@ class SchemaFolder extends Schema { /** * Get all the SchemaPieces instances from this schema's children. Used for SQL. * @since 0.5.0 - * @param {string[]} [array=[]] The array to push. * @returns {SchemaPiece[]} */ - getValues(array = []) { - for (const key of this.keyArray) { - if (this[key].type === 'Folder') this[key].getValues(array); - else array.push(this[key]); + getAllValues() { + const array = []; + for (const value of this.values()) { + if (value.type === 'Folder') array.push(...value.getAllValues()); + else array.push(value); } return array; } @@ -347,7 +339,6 @@ class SchemaFolder extends Schema { this[key] = piece; this.defaults[key] = piece.type === 'Folder' ? piece.defaults : options.default; - this.keys.add(key); this.keyArray.push(key); this.keyArray.sort((a, b) => a.localeCompare(b)); @@ -364,7 +355,6 @@ class SchemaFolder extends Schema { const index = this.keyArray.indexOf(key); if (index === -1) throw new Error(`The key '${key}' does not exist.`); - this.keys.delete(key); this.keyArray.splice(index, 1); delete this[key]; delete this.defaults[key]; @@ -440,7 +430,6 @@ class SchemaFolder extends Schema { this[key] = piece; this.defaults[key] = piece.default; } - this.keys.add(key); this.keyArray.push(key); } this.keyArray.sort((a, b) => a.localeCompare(b)); @@ -449,6 +438,50 @@ class SchemaFolder extends Schema { return true; } + /** + * Returns a new Iterator object that contains the `[key, value]` pairs for each element contained in this folder. + * Identical to [Map.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) + * @since 0.5.0 + * @generator + * @yields {[string, (SchemaFolder|SchemaPiece)]} + */ + *entries() { + for (const key of this.keyArray) yield [key, this[key]]; + } + + /** + * Returns a new Iterator object that contains the values for each element contained in this folder. + * Identical to [Map.values()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) + * @since 0.5.0 + * @generator + * @yields {(SchemaFolder|SchemaPiece)} + */ + *values() { + for (const key of this.keyArray) yield this[key]; + } + + /** + * Returns a new Iterator object that contains the keys for each element contained in this folder. + * Identical to [Map.keys()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) + * @since 0.5.0 + * @generator + * @yields {string} + */ + *keys() { + for (const key of this.keyArray) yield key; + } + + /** + * Returns a new Iterator object that contains the `[key, value]` pairs for each element contained in this folder. + * Identical to [Map.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) + * @since 0.5.0 + * @generator + * @returns {IterableIterator<[string, (SchemaFolder|SchemaPiece)]>} + */ + [Symbol.iterator]() { + return this.entries(); + } + /** * Get a JSON object containing all the objects from this schema's children. * @since 0.5.0 diff --git a/src/lib/util/constants.js b/src/lib/util/constants.js index db7ec15eac..0a605de9bd 100644 --- a/src/lib/util/constants.js +++ b/src/lib/util/constants.js @@ -1,5 +1,4 @@ const { dirname } = require('path'); -const { Guild, User, Client } = require('discord.js'); exports.DEFAULTS = { @@ -129,44 +128,6 @@ exports.DEFAULTS = { }; -exports.GATEWAY_RESOLVERS = { - - GUILDS: async function validateGuild(guildResolvable) { - if (guildResolvable) { - let value; - - if (typeof guildResolvable === 'string' && /^\d{17,19}$/.test(guildResolvable)) value = this.client.guilds.get(guildResolvable); - else if (guildResolvable instanceof Guild) value = guildResolvable; - if (value) return value; - } - - throw new Error('The parameter expects either a Guild ID or a Guild Instance.'); - }, - - USERS: async function validateUser(userResolvable) { - if (userResolvable) { - let value; - - if (typeof userResolvable === 'string' && /^\d{17,19}$/.test(userResolvable)) value = await this.client.users.fetch(userResolvable); - else if (userResolvable instanceof User) value = userResolvable; - if (value) return value; - } - - throw new Error('The parameter expects either a User ID or a User Instance.'); - }, - - CLIENT_STORAGE: async function validateClient(clientResolvable) { - if (typeof clientResolvable === 'string' && clientResolvable === this.client.user.id) return this.client.user; - if (clientResolvable instanceof Client) return clientResolvable.user; - if (typeof clientResolvable === 'object' && - typeof clientResolvable.client !== 'undefined' && - clientResolvable.client instanceof Client) return clientResolvable.client.user; - - throw new Error('The parameter expects either a Client Instance.'); - } - -}; - exports.TIME = { SECOND: 1000, MINUTE: 1000 * 60, diff --git a/src/lib/util/util.js b/src/lib/util/util.js index aa8cbbc675..a66ec9d96d 100644 --- a/src/lib/util/util.js +++ b/src/lib/util/util.js @@ -294,6 +294,21 @@ class Util { } } + /** + * Get the identifier of a value. + * @since 0.5.0 + * @param {*} value The value to get the identifier from + * @returns {string} + */ + static getIdentifier(value) { + if (typeof value === 'string') return value; + if (Util.isObject(value)) { + if ('id' in value) return value.id; + if ('name' in value) return value.name; + } + return null; + } + /** * Turn a dotted path into a json object. * @since 0.5.0 diff --git a/src/providers/collection.js b/src/providers/collection.js deleted file mode 100644 index a49fcc5581..0000000000 --- a/src/providers/collection.js +++ /dev/null @@ -1,154 +0,0 @@ -const { Provider } = require('klasa'); -const { Collection } = require('discord.js'); - -module.exports = class extends Provider { - - constructor(...args) { - super(...args, { - description: 'Allows you to use JSON functionality throughout Klasa', - cache: true - }); - this.database = new Collection(); - } - - /* Table methods */ - - /** - * Checks if a table exists. - * @param {string} table The name of the table you want to check - * @returns {boolean} - */ - hasTable(table) { - return this.database.has(table); - } - - /** - * Get the raw data from a table. - * @param {string} table The table to get the data from - * @returns {Collection} - */ - getTable(table) { - return this.database.get(table) || this.createTable(table).get(table); - } - - /** - * Creates a new table. - * @param {string} table The name for the new table - * @returns {Collection>} - */ - createTable(table) { - return this.database.set(table, new Collection()); - } - - /** - * Recursively deletes a table. - * @param {string} table The table's name to delete - * @returns {boolean} - */ - deleteTable(table) { - return this.database.delete(table); - } - - /* Entry methods */ - - /** - * Get all the values from a table. - * @param {string} table The name of the table to fetch from - * @returns {Object[]} - */ - getAll(table) { - return this.getTable(table); - } - - /** - * Get all the keys from a table. - * @param {string} table The name of the table to fetch the keys from - * @returns {string[]} - */ - getKeys(table) { - return Array.from(this.getTable(table).keys()); - } - - /** - * Get all the values from a table. - * @param {string} table The name of the table to fetch the values from - * @returns {any[]} - */ - getValues(table) { - return Array.from(this.getTable(table).values()); - } - - /** - * Get a entry from a table. - * @param {string} table The name of the table - * @param {string} entry The entry name - * @returns {?Object} - */ - get(table, entry) { - const collection = this.getTable(table); - return collection.get(entry) || null; - } - - /** - * Check if the entry exists. - * @param {string} table The name of the table - * @param {string} entry The entry name - * @returns {boolean} - */ - has(table, entry) { - return Boolean(this.get(table, entry)); - } - - /** - * Get a random entry from a table. - * @param {string} table The name of the table - * @returns {Object} - */ - getRandom(table) { - const array = this.getAll(table); - return array[Math.floor(Math.random() * array.length)]; - } - - /** - * Insert a new entry into a table. - * @param {string} table The name of the table - * @param {string} entry The entry name - * @param {Object} data The object with all properties you want to insert into the entry - * @returns {void} - */ - create(table, entry, data) { - const collection = this.getTable(table); - return collection.set(entry, data); - } - - set(...args) { - return this.create(...args); - } - - insert(...args) { - return this.create(...args); - } - - /** - * Update a entry from a table. - * @param {string} table The name of the table - * @param {string} entry The entry name - * @param {Object} data The object with all the properties you want to update - * @returns {Collection} - */ - update(table, entry, data) { - return this.getTable(table).set(entry, data); - } - - /** - * Delete a entry from the table. - * @param {string} table The name of the table - * @param {string} entry The entry name - * @returns {boolean} - */ - delete(table, entry) { - const collection = this.getTable(table); - return collection.delete(entry); - } - -}; diff --git a/typings/index.d.ts b/typings/index.d.ts index 371a040a9f..97d058ec25 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -539,17 +539,15 @@ declare module 'klasa' { private static _merge(data: any, folder: SchemaFolder | SchemaPiece): any; private static _clone(data: any, schema: SchemaFolder): any; private static _patch(inst: any, data: any, schema: SchemaFolder): void; - private static getIdentifier(value: any): any; } export class Gateway extends GatewayStorage { - private constructor(store: GatewayDriver, type: string, validateFunction: Function, schema: object, options: GatewayDriverAddOptions); + private constructor(store: GatewayDriver, type: string, schema: object, options: GatewayDriverAddOptions); public store: GatewayDriver; public options: GatewayDriverAddOptions; - public validate: Function; public defaultSchema: object; - public readonly cache: Provider; public readonly resolver: SettingResolver; + public readonly cache: Collection; public getEntry(input: string, create?: boolean): object | Configuration; public createEntry(input: string): Promise; @@ -571,7 +569,7 @@ declare module 'klasa' { public readonly client: KlasaClient; public resolver: SettingResolver; public types: string[]; - public caches: string[]; + public keys: Set; public ready: boolean; public readonly guildsSchema: { @@ -591,7 +589,8 @@ declare module 'klasa' { public users: Gateway; public clientStorage: Gateway; - public add(name: string, validateFunction: Function, schema?: object, options?: GatewayDriverAddOptions, download?: boolean): Promise; + public register(name: string, schema?: object, options?: GatewayDriverAddOptions): Gateway; + public add(name: string, schema?: object, options?: GatewayDriverAddOptions, download?: boolean): Promise; private _ready(): Promise>>>; private _checkProvider(engine: string): string; } @@ -633,7 +632,6 @@ declare module 'klasa' { private constructor(client: KlasaClient, gateway: Gateway, object: any, parent: SchemaFolder, key: string); public readonly type: 'Folder'; public defaults: object; - public keys: Set; public keyArray: string[]; public readonly configurableKeys: string[]; @@ -647,14 +645,19 @@ declare module 'klasa' { public getList(msg: KlasaMessage): string; public getDefaults(data?: object): object; public getSQL(array?: string[]): string[]; - public getKeys(array?: string[]): string[]; - public getValues(array?: SchemaPiece[]): SchemaPiece[]; + public getAllKeys(array?: string[]): string[]; + public getAllValues(array?: SchemaPiece[]): SchemaPiece[]; public resolveString(): string; private _addKey(key: string, options: SchemaFolderAddOptions, type: typeof Schema | typeof SchemaFolder): void; private _removeKey(key: string): void; private _init(options: object): true; + public entries(): IterableIterator<[string, SchemaFolder | SchemaPiece]>; + public values(): IterableIterator; + public keys(): IterableIterator; + public [Symbol.iterator](): IterableIterator<[string, SchemaFolder | SchemaPiece]>; + public toJSON(): any; public toString(): string; } @@ -1233,11 +1236,6 @@ declare module 'klasa' { CLIENT: KlasaConstantsClient, CONSOLE: KlasaConsoleConfig }; - GATEWAY_RESOLVERS: { - GUILDS: (guildResolvable: string | KlasaGuild) => KlasaGuild, - USERS: (userResolvable: string | KlasaUser) => KlasaUser, - CLIENT_STORAGE: (clientResolvable: string | KlasaClient) => ClientUser - }; CRON: { allowedNum: number[][]; partRegex: RegExp; @@ -1468,6 +1466,7 @@ declare module 'klasa' { } class Util { + private static initClean(client: KlasaClient): void; public static applyToClass(base: object, structure: object, skips?: string[]): void; public static clean(text: string): string; public static codeBlock(lang: string, expression: string): string; @@ -1477,6 +1476,7 @@ declare module 'klasa' { public static getDeepTypeName(input: any): string; public static getDeepTypeProxy(input: Proxy): string; public static getDeepTypeSetOrMap(input: Array | Set | WeakSet, basic?: string): string; + public static getIdentifier(value: any): string; public static getTypeName(input: any): string; public static isClass(input: Function): boolean; public static isFunction(input: Function): boolean; @@ -1491,7 +1491,6 @@ declare module 'klasa' { public static sleep(delay: number, args?: T): Promise; public static toTitleCase(str: string): string; public static tryParse(value: string): object; - private static initClean(client: KlasaClient): void; } export { Util as util }; From 783a2c79d7031f2c187b641845433efb13375b9d Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 12:38:25 +0100 Subject: [PATCH 02/76] Fixed deploy --- src/lib/settings/SchemaFolder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index a25cf3008f..30b332c01a 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -443,7 +443,7 @@ class SchemaFolder extends Schema { * Identical to [Map.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) * @since 0.5.0 * @generator - * @yields {[string, (SchemaFolder|SchemaPiece)]} + * @yields {(string|SchemaFolder|SchemaPiece)[]} */ *entries() { for (const key of this.keyArray) yield [key, this[key]]; @@ -476,7 +476,7 @@ class SchemaFolder extends Schema { * Identical to [Map.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) * @since 0.5.0 * @generator - * @returns {IterableIterator<[string, (SchemaFolder|SchemaPiece)]>} + * @returns {IterableIterator<(string|SchemaFolder|SchemaPiece)[]>} */ [Symbol.iterator]() { return this.entries(); From 1d5c940cb7a2c78f3cd94cdc84ee710415d28067 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 12:42:16 +0100 Subject: [PATCH 03/76] Now for real --- src/lib/settings/SchemaFolder.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 30b332c01a..27204280ab 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -443,7 +443,7 @@ class SchemaFolder extends Schema { * Identical to [Map.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) * @since 0.5.0 * @generator - * @yields {(string|SchemaFolder|SchemaPiece)[]} + * @yields {Array} */ *entries() { for (const key of this.keyArray) yield [key, this[key]]; @@ -476,7 +476,7 @@ class SchemaFolder extends Schema { * Identical to [Map.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) * @since 0.5.0 * @generator - * @returns {IterableIterator<(string|SchemaFolder|SchemaPiece)[]>} + * @returns {IterableIterator>} */ [Symbol.iterator]() { return this.entries(); From d29b9e75a806a0bcb71a96fafb7de4a51ed38257 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 13:25:34 +0100 Subject: [PATCH 04/76] Make unused gateways lightning fast, updated changelog, fixed cache --- CHANGELOG.md | 9 +++++++ .../UnderstandingSettingGateway.md | 21 ++-------------- src/lib/extensions/KlasaGuild.js | 2 +- src/lib/extensions/KlasaUser.js | 2 +- src/lib/settings/Gateway.js | 25 +++++++++---------- typings/index.d.ts | 2 +- 6 files changed, 26 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a89adfe07d..d913fa582c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ NOTE: For the contributors, you add new entries to this document following this ### Added +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `GatewayDriver#register` to be able to register new gateways without events (directly in your `app.js`). (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `util.getIdentifier` as a replacement for the function validator. (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `SchemaFolder#keys()`, `SchemaFolder#values()`, `SchemaFolder#entries()` and `SchemaFolder#[@@iterator]()`. Identical to Map's respective methods. (kyranet) - [[#176](https://github.com/dirigeants/klasa/pull/176)] Added `categorychannel` type to `ArgResolver`. (kyranet) - [[#166](https://github.com/dirigeants/klasa/pull/166)] Added support for TypeScript's `export default` in the loader. (kyranet) - [[#162](https://github.com/dirigeants/klasa/pull/162)] Added better dependent arguments support. (bdistin) @@ -81,6 +84,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Changed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Renamed `Gateway#getKeys()` and `Gateway#getValues()` to `Gateway#getAllKeys()` and `Gateway#getAllValues()` respectively. (kyranet) - [[#176](https://github.com/dirigeants/klasa/pull/176)] Marked several constructors as private (singleton, abstract or discouraged). (kyranet) - [[#162](https://github.com/dirigeants/klasa/pull/162)] Modified the built-in conf command to use `dependant arguments`-like arguments using custom arguments and messages. (bdistin) - [[#162](https://github.com/dirigeants/klasa/pull/162)] **[BREAKING]** Refactored ArgResolver. Now they take `arg`, `possible` and `msg` as parameters instead of `arg`, `currentUsage`, `possible`, `repeat` and `msg`. Repeating handling is now done in the backends. (bdistin) @@ -133,6 +137,10 @@ NOTE: For the contributors, you add new entries to this document following this ### Removed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the resolver functions from constants. (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed `SchemaFolder#keys` (`Map`) to reduce RAM usage and key caching duplication. (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed SettingGateway function validators. (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed Collection cache provider (will be moved to klasa-pieces). (kyranet) - [[#159](https://github.com/dirigeants/klasa/pull/159)] Removed deprecated property `GatewayOptions.cache` to be locked to `'collection'`. (kyranet) - [[#158](https://github.com/dirigeants/klasa/pull/158)] `Configuration#updateMany` is now under `Configuration#update`, in favor of a much less confusing naming. (kyranet) - [[`5b0c468362`](https://github.com/dirigeants/klasa/commit/5b0c46836200a57577bbd4aaa5cd463089a3bff0)] Removed `KlasaClient.sharded` as `Client.shard` is now fixed. (bdistin) @@ -154,6 +162,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Fixed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed Gateway syncing keys even when it's unused. (kyranet) - [[#176](https://github.com/dirigeants/klasa/pull/176)] Fixed internal parser ignoring `0` as `min` and/or `max` due to falsy value check. (kyranet) - [[#170](https://github.com/dirigeants/klasa/pull/170)] Fixed guild resolvables not working correctly. (kyranet) - [[#165](https://github.com/dirigeants/klasa/pull/165)] Updated url for peer dependency `discord.js` and fixed all `JSDocs`. (kyranet) diff --git a/guides/Getting Started/UnderstandingSettingGateway.md b/guides/Getting Started/UnderstandingSettingGateway.md index 0fb1320e71..09f1dc1303 100644 --- a/guides/Getting Started/UnderstandingSettingGateway.md +++ b/guides/Getting Started/UnderstandingSettingGateway.md @@ -195,19 +195,8 @@ async () => { By using {@link GatewayDriver}, (available from `client.gateways`). Let's say I want to add a new Gateway instance called `channels` that stores data to complement our permissions. -You'll want a validate function to ensure what you're inputting is a valid channel, and you would want a channel specific schema to handle our channel specific permissions, like the two below. ```javascript -// Must use the function keyword or be a method of a class. -async function validate(channelResolvable) { - // 'this' is referred to the GatewayDriver's instance, it has access - // to client, resolver... - const result = await this.resolver.channel(channelResolvable); - if (result) return result; - - throw 'The parameter expects either a Channel ID or a Channel Instance.'; -} - // Define the schema for the new Gateway. const schema = { disabledCommands: { @@ -230,15 +219,9 @@ const schema = { }; // Now, we create it: -this.client.gateways.add('channels', validate, schema); +this.client.gateways.add('channels', schema); ``` -> Since [[#43](https://github.com/dirigeants/klasa/pull/43)], validate only accepts a single argument, instead of resolver being the first one. - -> The `validate` function must be a [**function**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/function), not a [**Arrow Function**](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions), the difference between them is that an arrow function binds `this` to wherever the function has been created (for example, the `exports` from your eval command, if you are doing this with eval), while the normal functions does not do this. - -> If the `validate` function does not resolve **Guild** type, you might want to use the third argument of {@link Configuration#update}, which takes a Guild resolvable. - And then, you can access to it by: ```javascript @@ -250,7 +233,7 @@ this.client.gateways.channels; This is new from the SettingGateway v2 (check [#43](https://github.com/dirigeants/klasa/pull/43)), when creating a new Gateway (check above for how to do it), there's an extra parameter in `client.gateways.add` called `options`. It's optional, but it accepts an object with one key: `provider`, which is the Provider/SQLProvider (json, leveldb, rethinkdb...). For example: ```javascript -this.client.gateways.add('channels', validate, schema, { provider: 'rethinkdb' }); +this.client.gateways.add('channels', schema, { provider: 'rethinkdb' }); ``` The code above will create a new Gateway instance called 'channels', which will use RethinkDB to store the persistent data. diff --git a/src/lib/extensions/KlasaGuild.js b/src/lib/extensions/KlasaGuild.js index e42e3c7336..f6a44fc9ac 100644 --- a/src/lib/extensions/KlasaGuild.js +++ b/src/lib/extensions/KlasaGuild.js @@ -18,7 +18,7 @@ module.exports = Structures.extend('Guild', Guild => { * @since 0.5.0 * @type {Configuration} */ - this.configs = this.client.gateways.guilds.cache.get('guilds', this.id) || this.client.gateways.guilds.insertEntry(this.id); + this.configs = this.client.gateways.guilds.cache.get(this.id) || this.client.gateways.guilds.insertEntry(this.id); } /** diff --git a/src/lib/extensions/KlasaUser.js b/src/lib/extensions/KlasaUser.js index 334ad8b8d8..7fbb91f76b 100644 --- a/src/lib/extensions/KlasaUser.js +++ b/src/lib/extensions/KlasaUser.js @@ -18,7 +18,7 @@ module.exports = Structures.extend('User', User => { * @since 0.5.0 * @type {Configuration} */ - this.configs = this.client.gateways.users.cache.get('users', this.id) || this.client.gateways.users.insertEntry(this.id); + this.configs = this.client.gateways.users.cache.get(this.id) || this.client.gateways.users.insertEntry(this.id); } } diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index 7f4b10398f..7ed97e9364 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -132,12 +132,12 @@ class Gateway extends GatewayStorage { async createEntry(input) { const target = getIdentifier(input); if (!target) throw new TypeError('The selected target could not be resolved to a string.'); - const cache = this.cache.get(this.type, target); + const cache = this.cache.get(target); if (cache && cache.existsInDB) return cache; await this.provider.create(this.type, target); const configs = cache || new this.Configuration(this, { id: target }); configs.existsInDB = true; - if (!cache) this.cache.set(this.type, target, configs); + if (!cache) this.cache.set(target, configs); if (this.client.listenerCount('configCreateEntry')) this.client.emit('configCreateEntry', configs); return configs; } @@ -151,8 +151,8 @@ class Gateway extends GatewayStorage { */ insertEntry(id, data = {}) { const configs = new this.Configuration(this, Object.assign(data, { id })); - this.cache.set(this.type, id, configs); - if (this.ready) configs.sync().catch(err => this.client.emit('error', err)); + this.cache.set(id, configs); + if (this.ready && this.schema.keyArray.length) configs.sync().catch(err => this.client.emit('error', err)); return configs; } @@ -163,7 +163,7 @@ class Gateway extends GatewayStorage { * @returns {Promise} */ async deleteEntry(input) { - const configs = this.cache.get(this.type, input); + const configs = this.cache.get(input); if (!configs) return false; await configs.destroy(); @@ -179,28 +179,28 @@ class Gateway extends GatewayStorage { */ async sync(input, download) { if (typeof input === 'undefined') { - if (!download) return Promise.all(this.cache.getValues(this.type).map(entry => entry.sync())); + if (!download) return Promise.all(this.cache.map(entry => entry.sync())); const entries = await this.provider.getAll(this.type); for (const entry of entries) { - const cache = this.cache.get(this.type, entry); + const cache = this.cache.get(entry); if (cache) { if (!cache.existsInDB) cache.existsInDB = true; cache._patch(entry); } else { const newEntry = new this.Configuration(this, entry); newEntry.existsInDB = true; - this.cache.set(this.type, entry.id, newEntry); + this.cache.set(entry.id, newEntry); } } } const target = getIdentifier(input); if (!target) throw new TypeError('The selected target could not be resolved to a string.'); - const cache = this.cache.get(this.type, target); + const cache = this.cache.get(target); if (cache) return cache.sync(); const configs = new this.Configuration(this, { id: target }); - this.cache.set(this.type, target, configs); + this.cache.set(target, configs); return configs.sync(); } @@ -249,7 +249,6 @@ class Gateway extends GatewayStorage { await this.initSchema(); await this.initTable(); - if (!this.cache.hasTable(this.type)) this.cache.createTable(this.type); if (download) await this.sync(); this.ready = true; @@ -262,12 +261,12 @@ class Gateway extends GatewayStorage { * @private */ async _ready() { - if (typeof this.client[this.type] === 'undefined') return null; + if (!this.schema.keyArray.length || typeof this.client[this.type] === 'undefined') return null; const promises = []; const keys = await this.provider.getKeys(this.type); for (let i = 0; i < keys.length; i++) { const structure = this.client[this.type].get(keys[i]); - if (structure) promises.push(structure.configs.sync().then(() => this.cache.set(this.type, keys[i], structure.configs))); + if (structure) promises.push(structure.configs.sync().then(() => this.cache.set(keys[i], structure.configs))); } const results = await Promise.all(promises); if (!this.ready) this.ready = true; diff --git a/typings/index.d.ts b/typings/index.d.ts index 97d058ec25..ca2b37df49 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1466,7 +1466,6 @@ declare module 'klasa' { } class Util { - private static initClean(client: KlasaClient): void; public static applyToClass(base: object, structure: object, skips?: string[]): void; public static clean(text: string): string; public static codeBlock(lang: string, expression: string): string; @@ -1491,6 +1490,7 @@ declare module 'klasa' { public static sleep(delay: number, args?: T): Promise; public static toTitleCase(str: string): string; public static tryParse(value: string): object; + private static initClean(client: KlasaClient): void; } export { Util as util }; From 596cc50567a2f0e1cb2fc12853c77021068e21a2 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 17:46:54 +0100 Subject: [PATCH 05/76] SchemaPiece#modify -> edit, GatewayDriver#types type from string[] to Set, massive documentation overhaul --- CHANGELOG.md | 2 + guides/.docconfig.json | 19 ++ .../UnderstandingSettingGateway.md | 238 +++--------------- .../SettingGatewayConfigurationUpdate.md | 38 +++ .../SettingGateway/SettingGatewayKeyTypes.md | 65 +++++ .../UnderstandingSchemaFolders.md | 160 ++++++++++++ .../UnderstandingSchemaPieces.md | 67 +++++ src/lib/Client.js | 2 +- src/lib/settings/GatewayDriver.js | 4 +- src/lib/settings/SchemaPiece.js | 10 +- typings/index.d.ts | 6 +- 11 files changed, 394 insertions(+), 217 deletions(-) create mode 100644 guides/SettingGateway/SettingGatewayConfigurationUpdate.md create mode 100644 guides/SettingGateway/SettingGatewayKeyTypes.md create mode 100644 guides/SettingGateway/UnderstandingSchemaFolders.md create mode 100644 guides/SettingGateway/UnderstandingSchemaPieces.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d913fa582c..e669a33d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,8 @@ NOTE: For the contributors, you add new entries to this document following this ### Changed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Changed the type for `GatewayDriver#types` from `string[]` to `Set`. (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Renamed `SchemaPiece#modify()` to `SchemaPiece#edit()`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Renamed `Gateway#getKeys()` and `Gateway#getValues()` to `Gateway#getAllKeys()` and `Gateway#getAllValues()` respectively. (kyranet) - [[#176](https://github.com/dirigeants/klasa/pull/176)] Marked several constructors as private (singleton, abstract or discouraged). (kyranet) - [[#162](https://github.com/dirigeants/klasa/pull/162)] Modified the built-in conf command to use `dependant arguments`-like arguments using custom arguments and messages. (bdistin) diff --git a/guides/.docconfig.json b/guides/.docconfig.json index aa63538bc1..62b680bb84 100644 --- a/guides/.docconfig.json +++ b/guides/.docconfig.json @@ -79,6 +79,25 @@ "path": "CommandsCustomResolvers.md" }] }, +{ + "name": "Advanced SettingGateway", + "files": [{ + "name": "Understanding SchemaFolders", + "path": "UnderstandingSchemaFolders.md" + }, + { + "name": "Understanding SchemaPieces", + "path": "UnderstandingSchemaPieces.md" + }, + { + "name": "SettingGateway's Types", + "path": "SettingGatewayKeyTypes.md" + }, + { + "name": "Updating your Configuration", + "path": "SettingGatewayConfigurationUpdate.md" + }] +}, { "name": "Other Subjects", "files": [{ diff --git a/guides/Getting Started/UnderstandingSettingGateway.md b/guides/Getting Started/UnderstandingSettingGateway.md index 09f1dc1303..109f1a81a6 100644 --- a/guides/Getting Started/UnderstandingSettingGateway.md +++ b/guides/Getting Started/UnderstandingSettingGateway.md @@ -1,204 +1,50 @@ # SettingGateway -The SettingGateway is designed to provide users a very useful interface for managing data. Each instance is able to handle a completely different schema and database. +What is SettingGateway? It is an interface that connects your Discord bot with a database and ensures maximum performance by using a very refined cache system that is always up to date with the database. In a point of view, SettingGateway can be understood as an abstracted [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) as it's able to run any kind database (with a compatible {@link Provider}) and manage the data efficiently. -By default, Klasa uses the [json](https://github.com/dirigeants/klasa/blob/master/src/providers/json.js) provider. Do not be fooled and insta-replace with SQLite, Klasa's JSON provider writes the data [atomically](https://en.wikipedia.org/wiki/Atomicity_(database_systems) ), in other words, it is very rare for the data to corrupt. +By default, Klasa uses the [json](https://github.com/dirigeants/klasa/blob/master/src/providers/json.js) provider. Do not be fooled and insta-replace with SQLite, Klasa's JSON provider writes the data [atomically](https://en.wikipedia.org/wiki/Atomicity_(database_systems) ). In other words, it is very rare for the data to corrupt. -## Key types +Thanks to the abstraction of SettingGateway, the developer has many options, for example, if you want to change the database that manages the data, you just change one line of code, without needing to rewrite everything that relies on it, nor you need to rewrite the interface itself in order to be able to work with a different database. -The types supported for Gateway's keys are the same as the name of the properties from the {@link SettingResolver}, by extending it, you'll also extend the available key types. +## Database Engine -| Name | Type | Description | -| ---------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- | -| **any** | Anything, no type restriction | Resolves anything, even objects, the usage of this type will make a key unconfigurable | -| **boolean** | A boolean resolvable | Resolves a boolean primitive value | -| **channel** | A {@link Channel} instance or id | Resolves a channel. Be careful with using this, as it accepts any type of channel | -| **command** | A {@link Command} instance or name | Resolves a Command | -| **emoji** | An {@link Emoji} instance or name | Resolves a custom emoji | -| **float** | A floating point number | Resolves a [float](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) | -| **guild** | A {@link KlasaGuild} instance or id | Resolves a KlasaGuild (which extends Guild) | -| **integer** | An integer number | Resolves an [integer](https://en.wikipedia.org/wiki/Integer) number | -| **language** | A {@link Language} instance or name | Resolves a language | -| **member** | A {@link GuildMember} instance or id | Resolves a GuildMember | -| **msg** | A {@link KlasaMessage} instance or id | Resolves a KlasaMessage (which extends Message) | -| **role** | A {@link Role} instance or id | Resolves a Role | -| **string** | A string resolvable | Resolves a string | -| **textchannel** | A {@link TextChannel} instance or id | Resolves a TextChannel | -| **url** | An URL resolvable | Resolves a URL with Node.js' URL parser | -| **user** | A {@link KlasaUser} instance or id | Resolves a KlasaUser (which extends User) | -| **voicechannel** | A {@link VoiceChannel} instance or id | Resolves a VoiceChannel | +As mentioned before, SettingGateway is abstracted, it does not rely on a very specific database, but can use any of them. In a production bot, you may want to use a process-based database such as rethinkdb, mongodb or postgresql, you can check and download them from the [klasa-pieces](https://github.com/dirigeants/klasa-pieces/) repository so you don't need to make one from scratch. -> To extend the types, you may extend {@link SettingResolver} by making an extendable, check how to make them here: {@tutorial CreatingExtendables}. - -## Change the *provider's engine*. - -For example, let's say I have downloaded the *rethinkdb* provider and I want to work with it, then we go to your main script file (`app.js`, `bot.js`..., wherever you declare the new Klasa.Client), and write the following code: +Now... how we update it? Go to your main file, where {@link KlasaClient} is initialized, and add a new option to your {@link KlasaClientOptions}. The following code snippet as an example: ```javascript -const client = new Klasa.Client({ providers: { default: 'rethinkdb' } }); +const client = new KlasaClient({ providers: { default: 'rethinkdb' } }); ``` -Your Klasa's configuration will look something like this: +If you have other options, such as a prefix, then your main file would look like this: ```javascript -const client = new Klasa.Client({ +const { KlasaClient } = require('klasa'); + +new KlasaClient({ prefix: 'k!', providers: { default: 'rethinkdb' } -}); - -client.login('A_BEAUTIFUL_TOKEN_AINT_IT?'); +}).login('A_BEAUTIFUL_TOKEN_AINT_IT?'); ``` And now, you're using rethinkdb's provider to store the data from SettingGateway. -What happens when I use an engine that does not exist as a provider? Simple, SettingGateway will throw an error. Should this happen, make sure you typed the provider's name correctly. - -## Add new 'keys' to the guild configuration's schema. - -You can easily add keys to the schema by doing this: - -```javascript -this.client.gateways.guilds.schema.addKey(key, options, force); -``` - -Where: - -- `key` is the key's name to add, `String` type. -- `options` is an object containing the options for the key, such as `type`, `default`, `sql`, `array`... -- `force` (defaults to `true`) is whether SchemaManager should update all documents/rows to match the new schema, using the `options.default` value. - -For example, let's say I want to add a new configuration key, called `modlogs`, which takes a channel. - -```javascript -this.client.gateways.guilds.schema.addKey('modlogs', { type: 'TextChannel' }); -``` - -This will create a new configuration key, called `modlogs`, and will take a `TextChannel` type. +## Creating Gateways -> The force parameter defaults to `true` instead of `false`. It's recommended to leave it as true to avoid certain unwanted actions. +Another advantage of using this interface is that it can handle multiple databases simultaneously, for example, Klasa handles 3 gateways at the same time: `clientStorage` for Client, `guilds` for Guild and `users` for User. Plus, there's the possibility to add a new {@link Gateway} by using {@link KlasaClient#gateways}: -But now, I want to add another key, with name of `users`, *so I can set a list of blacklisted users who won't be able to use commands*, which will take an array of Users. +Let's say I want to add a new Gateway instance called `channels` that stores data to complement our permissions, and I want the **postgresql** provider to handle it. ```javascript -this.client.gateways.guilds.schema.addKey('users', { type: 'User', array: true }); -``` - -> `options.array` defaults to `false`, and when `options.default` is not specified, it defaults to `null`, however, when `options.array` is `true`, `options.default` defaults to `[]` (empty array). - -What have we done? `client.gateways.guilds.schema` is a {@link SchemaFolder} instance (also called Folder type) which can manage itself, such as adding keys/folders to itself (it certainly follows the OOP paradigm). - -## Editing keys from the guild configuration. - -Now that I have a new key called `modlogs`, I want to configure it outside the `conf` command, how can we do this? +const { KlasaClient } = require('klasa'); -```javascript -msg.guild.configs.update('modlogs', '267727088465739778', msg.guild); -``` - -Check: {@link Configuration#update} - -> You can use a Channel instance, {@link SettingResolver} will make sure the input is valid and the database gets an **ID** and not an object. - -Now, I want to **add** a new user user to the `users` key, which takes an array. - -```javascript -msg.guild.configs.update('users', '146048938242211840', { action: 'add' }); -``` - -That will add the user `'146048938242211840'` to the `users` array. To remove it: - -```javascript -msg.guild.configs.update('users', '146048938242211840', { action: 'remove' }); -``` - -> Additionally, if no 'action' option is passed to {@link Configuration.ConfigurationUpdateOptions}, it'll assume the `auto` mode, which will add or remove depending on the existence of the key. - -## Removing a key from the guild configuration. - -I have a key which is useless for me, so I *want* to remove it from the schema. - -```javascript -this.client.gateways.guilds.schema.removeKey('users'); -``` - -## Create a new folder in the schema - -It's very similar to how you create a new key, but it only accepts three arguments: - -```javascript -this.client.gateways.guilds.schema.addFolder(name, object, force); -``` - -So, let's say I want a key called 'modlogs' into the 'channels' folder for organization. There are two ways to do it: - -### Slower - -```javascript -async () => { - const { schema } = this.client.gateways.guilds; - - await schema.addFolder('channels'); - await schema.channels.addKey('modlogs', { type: 'TextChannel' }); - console.log(schema.channels.modlogs.toJSON()); - // { - // type: 'textchannel', - // array: false, - // default: null, - // min: null, - // max: null, - // configurable: true - // } -}; -``` - -### Faster - -```javascript -async () => { - const { schema } = this.client.gateways.guilds; - - await schema.addFolder('channels', { modlogs: { type: 'TextChannel' } }); - console.log(schema.channels.modlogs.toJSON()); - // { - // type: 'textchannel', - // array: false, - // default: null, - // min: null, - // max: null, - // configurable: true - // } -}; -``` - -Now, how we do configure it with the built-in conf command? Easy: - -```sh -k!conf set channels.modlogs #modlogs -``` - -## Add a key to the guild configuration's schema if it doesn't exist. - -In [Klasa-Pieces](https://github.com/dirigeants/klasa-pieces/), specially, some pieces require a key from the configuration to work, however, the creator of the pieces does not know if the user who downloads the piece has it, so this function becomes useful in this case. - -```javascript -async () => { - const { schema } = this.client.gateways.guilds; - - if (!schema.hasKey('modlog')) { - await schema.addKey('modlog', { type: 'TextChannel' }); - } -}; -``` - -## How can I create new Gateway instances? - -By using {@link GatewayDriver}, (available from `client.gateways`). - -Let's say I want to add a new Gateway instance called `channels` that stores data to complement our permissions. +const client = new KlasaClient({ + prefix: 'k!', + providers: { default: 'rethinkdb' } +}); -```javascript -// Define the schema for the new Gateway. -const schema = { +// Now, we create it: +this.client.gateways.register('channels', { disabledCommands: { type: 'Command', default: [], @@ -216,31 +62,22 @@ const schema = { min: 0, max: 30 } -}; +}, { provider: 'postgresql' }); -// Now, we create it: -this.client.gateways.add('channels', schema); +client.login('A_BEAUTIFUL_TOKEN_AINT_IT?'); ``` +> **Note**: You can have any schema, check the links below to understand how to expand it later. + And then, you can access to it by: ```javascript this.client.gateways.channels; ``` -## Using different providers in different gateways - -This is new from the SettingGateway v2 (check [#43](https://github.com/dirigeants/klasa/pull/43)), when creating a new Gateway (check above for how to do it), there's an extra parameter in `client.gateways.add` called `options`. It's optional, but it accepts an object with one key: `provider`, which is the Provider/SQLProvider (json, leveldb, rethinkdb...). For example: - -```javascript -this.client.gateways.add('channels', schema, { provider: 'rethinkdb' }); -``` - -The code above will create a new Gateway instance called 'channels', which will use RethinkDB to store the persistent data. - ## Customizing the options for each built-in gateway -This is available in 0.5.0 since the PR [#152](https://github.com/dirigeants/klasa/pull/152), and you're able to configure the three built-in gateways: `guilds`, `users` and `clientStorage`. The option to configure them is {@link KlasaClient.KlasaClientOptions.gateways}, where you would add the option `gateways` to your KlasaClientOptions: +This is available in 0.5.0 since the PR [#152](https://github.com/dirigeants/klasa/pull/152), and you're able to configure the three built-in gateways: `guilds`, `users` and `clientStorage`. The option to configure them is {@link KlasaClientOptions.gateways}, where you would add the option `gateways` to your KlasaClientOptions: ```javascript const client = new Klasa.Client({ @@ -257,20 +94,9 @@ client.login('A_BEAUTIFUL_TOKEN_AINT_IT?'); Where the *clientStorage* gateway would take the default options (json provider), the *guilds* gateway would use the rethinkdb provider, and finally the *users* one would use the postgresql provider. These options are {@link GatewayDriver.GatewayDriverAddOptions}. -## Modifying a SchemaPiece's parameters - -Once created, it's possible since 0.5.0 to modify a {@link SchemaPiece}'s parameter, it's as simple as doing {@link SchemaPiece#update} which takes the same options for adding a key with {@link SchemaFolder#addKey} but with one exception: `array` and `type` can't change. - -For example, let's say we dislike the current prefix and we want to change it to `s!` for the next entries, then you can simply do: - -```javascript -this.client.gateways.guilds.schema.prefix.modify({ default: 's!' }); -``` - -### The Type Issue - -The main reason for why we don't support modifying the parameters `array` and `type` is: - -> Changing the type is very complex. For example, in SQL, if we changed the type from `TEXT`, `VARCHAR`, or any other string type to a numeric one such as `INTEGER`, we could risk the database potentially throwing an error or setting them to null, which would result in data loss. We would then need to download all of the data first, and insert them back with the new type. The same thing happens in NoSQL. +## Further Reading: -Changing the value of `array` from a non-string datatype can result on the issue above, and it's a very slow process. Therefore, it's much better to just remove the key and add it back. +- {@tutorial UnderstandingSchemaPieces} +- {@tutorial UnderstandingSchemaFolders} +- {@tutorial SettingGatewayKeyTypes} +- {@tutorial SettingGatewayConfigurationUpdate} diff --git a/guides/SettingGateway/SettingGatewayConfigurationUpdate.md b/guides/SettingGateway/SettingGatewayConfigurationUpdate.md new file mode 100644 index 0000000000..161303f06c --- /dev/null +++ b/guides/SettingGateway/SettingGatewayConfigurationUpdate.md @@ -0,0 +1,38 @@ +# Updating your configuration + +Once we have our schema done with all the keys, folders and types needed, we may want to update our configuration via SettingGateway, all of this is done via {@link Configuration#update}. However, how can I update it? Use any of the following code snippets: + +```javascript +// Updating the value of a key +// This key is contained in the roles folder, and the second value is a role id, we also need +// to pass a GuildResolvable. +msg.guild.configs.update('roles.administrator', '339943234405007361', msg.guild); + +// Updating an array +// userBlacklist, as mentioned in another tutorial, it's a piece with an array or users. Using +// the following code will add or remove it, depending on the existence of the key in the configuration. +msg.guild.configs.update('userBlacklist', '272689325521502208'); + +// Ensuring the function call adds (error if it exists) +msg.guild.configs.update('userBlacklist', '272689325521502208', { action: 'add' }); + +// Ensuring the function call removes (error if it not exists) +msg.guild.configs.update('userBlacklist', '272689325521502208', { action: 'remove' }); + +// Updating it with a json object +// It's the same as the first code snippet. However, this pattern is slower. +msg.guild.configs.update({ roles: { administrator: '339943234405007361' } }, msg.guild); + +// Updating multiple keys (only possible with json object) +msg.guild.configs.update({ prefix: 'k!', language: 'es-ES' }); +``` + +> **Note**: Some types require a Guild instance to work, for example, *channels*, *roles* and *members*. + +> Additionally, if no 'action' option is passed to {@link Configuration.ConfigurationUpdateOptions}, it'll assume the `auto` mode, which will add or remove depending on the existence of the key. + +## Further Reading: + +- {@tutorial UnderstandingSchemaPieces} +- {@tutorial UnderstandingSchemaFolders} +- {@tutorial SettingGatewayKeyTypes} diff --git a/guides/SettingGateway/SettingGatewayKeyTypes.md b/guides/SettingGateway/SettingGatewayKeyTypes.md new file mode 100644 index 0000000000..6f1b7c4d57 --- /dev/null +++ b/guides/SettingGateway/SettingGatewayKeyTypes.md @@ -0,0 +1,65 @@ +# SettingGateway's Types + +By default, there are several built-in types that the developer can use, and with the possibility to add custom types via {@link Extendable}s as explained below. The built-in types are: + +| Name | Type | Description | +| :-----------------: | :------------------------------------------------ | ---------------------------------------------------------------------------------------- | +| **any** | Anything, no type restriction | Resolves anything, even objects, the usage of this type will make a key unconfigurable | +| **boolean** | A {@link Boolean} resolvable | Resolves a boolean primitive value | +| **categorychannel** | A {@link external:CategoryChannel} instance or id | Resolves a CategoryChannel | +| **channel** | A {@link external:Channel} instance or id | Resolves a channel. Be careful with using this, as it accepts any type of channel | +| **command** | A {@link Command} instance or name | Resolves a Command | +| **emoji** | An {@link external:Emoji} instance or name | Resolves a custom emoji | +| **float** | A floating point number | Resolves a [float](https://en.wikipedia.org/wiki/Double-precision_floating-point_format) | +| **guild** | A {@link KlasaGuild} instance or id | Resolves a KlasaGuild (which extends Guild) | +| **integer** | An integer number | Resolves an [integer](https://en.wikipedia.org/wiki/Integer) number | +| **language** | A {@link Language} instance or name | Resolves a language | +| **member** | A {@link external:GuildMember} instance or id | Resolves a GuildMember | +| **msg** | A {@link KlasaMessage} instance or id | Resolves a KlasaMessage (which extends Message) | +| **role** | A {@link external:Role} instance or id | Resolves a Role | +| **string** | A {@link external:StringResolvable} | Resolves a string | +| **textchannel** | A {@link external:TextChannel} instance or id | Resolves a TextChannel | +| **url** | An URL resolvable | Resolves a URL with Node.js' URL parser | +| **user** | A {@link KlasaUser} instance or id | Resolves a KlasaUser (which extends User) | +| **voicechannel** | A {@link external:VoiceChannel} instance or id | Resolves a VoiceChannel | + +## Adding new types + +To add new keys, you use an {@link Extendable} extending {@link SettingResolver}. If you don't know how to create an extendable, check the following tutorial: {@tutorial CreatingExtendables}. The following extendable is a template for this: + +```javascript +const { Extendable } = require('klasa'); + +module.exports = class extends Extendable { + + constructor(...args) { + super(...args, ['SettingResolver'], { + name: 'typeName', + klasa: true + }); + } + + /** + * Resolves my custom type! + * @param {*} data The data to resolve + * @param {KlasaGuild} guild The guild to resolve for + * @param {string} name The name of the key being resolved + * @param {Object} minMax The minimum and maximum + * @param {?number} minMax.min The minimum value + * @param {?number} minMax.max The maximum value + * @returns {*} + */ + extend(data, guild, name, { min, max } = {}) { + // The content + } + +}; +``` + +> **Note**: If a type does not load, you can add the type name to {@link GatewayDriver#types}, but it must be before the {@link SchemaPiece}s init as they check if the type is included in that Set. + +## Further Reading: + +- {@tutorial UnderstandingSchemaPieces} +- {@tutorial UnderstandingSchemaFolders} +- {@tutorial SettingGatewayConfigurationUpdate} diff --git a/guides/SettingGateway/UnderstandingSchemaFolders.md b/guides/SettingGateway/UnderstandingSchemaFolders.md new file mode 100644 index 0000000000..b284451e84 --- /dev/null +++ b/guides/SettingGateway/UnderstandingSchemaFolders.md @@ -0,0 +1,160 @@ +# Understanding Schema + +A schema works like a diagram or a blueprint, in SettingGateway, the schema defines the keys present in the configuration for a specific gateway. This feature serves multiple purposes: + +1. Define what keys does the {@link Gateway} manage and their properties. +1. Define what type the keys must hold. +1. Define the SQL schema when using a SQL database. +1. Speed up performance when iterating over keys. + +## Adding keys + +Adding keys with the schema is like adding a piece into a box, but you can also have boxes inside another boxes. That being said, you get the box you want to modify and insert the new pieces or boxes into it. The methods to achieve that are {@link SchemaFolder#addKey} to add pieces (keys) and {@link SchemaFolder#addFolder} to add boxes (more folders). + +You would normally use these two methods using the following snippet: + +```javascript +// Add a new key +this.client.gateways.gatewayName.schema.addKey(name, options, force); + +// Add a new folder +this.client.gateways.gatewayName.schema.addFolder(name, options, force); +``` + +The parameters are: + +- **name**: The name of the new key. If it conflicts with a pre-existent key, this will error. +- **options**: The options for the new key or folder. +- **force**: Whether this change should affect all entries. It requires a lot of processing but ensures the changes are correctly applied in both cache and database. + +You can also extend any of the three built-in {@link Gateway}s from Klasa, for example, if you want to add a new key called **modlogs** that accepts only text channels, for your guild configs, you would use the following code: + +```javascript +this.client.gateways.guilds.schema.addKey('modlogs', { type: 'TextChannel' }); +``` + +Where you're doing the following steps: + +1. Access to {@link KlasaClient#gateways}, type of {@link GatewayDriver}, which holds all gateways. +1. Access to the guilds' {@link Gateway}, which manages the per-guild configuration. +1. Access to the guilds' schema via {@link Gateway#schema}, which manages the gateway's schema. +1. Add a new key called **modlogs** in the root of the schema, with type of **TextChannel**. + +And you would have a perfectly configured modlogs key in your configs. However, you can also have an array of the same type. For example, you want to have a configurable array of users blacklisted in a guild, in a key named **userBlacklist**: + +```javascript +this.client.gateways.guilds.schema.addKey('userBlacklist', { type: 'User', array: true }); +``` + +And now you can access to any of them in your guild configs like in the following snippet! + +```javascript +msg.guild.configs.modlogs; // null +msg.guild.configs.userBlacklist; // [] +``` + +## Removing keys + +Removing keys with the schema is quite easy, as you would access to the {@link SchemaFolder} that holds it and remove it by its name (remember that `force` is optional and defaults to `true`) using {@link SchemaFolder#removeKey} as the following example: + +```javascript +this.client.gateways.gatewayName.schema.removeKey(name, force); +``` + +In case you have a key you do not longer use and you want to get rid of it, for example, the recently created **userBlacklist** key for guild configs, you would run the following code: + +```javascript +this.client.gateways.guilds.schema.removeKey('userBlacklist'); +``` + +And the property `userBlacklist` for all guild configs will be deleted, that being said: + +```javascript +msg.guild.configs.userBlacklist; // undefined +'userBlacklist' in msg.guild.configs; // false +``` + +## Adding folders + +Folder creation is very similar to key creation, but with one key difference: it has no options for itself, but instead, it can create its children keys (like you can add a box with another boxes and pieces, into another). You can add a new key inside a new folder in two different ways: + +### Slower + +You can create a folder, then create the keys, however, this will iterate over all entries twice: + +```javascript +async function init() { + const { schema } = this.client.gateways.guilds; + + await schema.addFolder('channels'); + await schema.channels.addKey('modlogs', { type: 'TextChannel' }); + console.log(schema.channels.modlogs.toJSON()); + // { + // type: 'textchannel', + // array: false, + // default: null, + // min: null, + // max: null, + // configurable: true + // } +}; +``` + +### Faster + +However, it's possible to create a folder with all the sub-keys (and even more nested folders) with the folder creation. + +```javascript +async function init() { + const { schema } = this.client.gateways.guilds; + + await schema.addFolder('channels', { modlogs: { type: 'TextChannel' } }); + console.log(schema.channels.modlogs.toJSON()); + // { + // type: 'textchannel', + // array: false, + // default: null, + // min: null, + // max: null, + // configurable: true + // } +}; +``` + +> **Reminder**: To access to a key inside a folder in your configuration command, you use the access operator (`.`). For example: *k!conf set channels.modlogs #modlogs* + +## Removing folders + +It's exactly the same as {@link SchemaFolder#removeKey}, but using {@link SchemaFolder#removeFolder} instead. With the following syntax: + +```javascript +this.client.gateways.gatewayName.schema.removeFolder(name, force); +``` + +To remove a folder, like the aforementioned **channels** folder, you would run the following code: + +```javascript +this.client.gateways.guilds.schema.removeFolder('channels'); +``` + +This will remove all the data from all the sub-keys and sub-folders, even very nested ones. + +## Ensuring the existence of a key. + +In [Klasa-Pieces](https://github.com/dirigeants/klasa-pieces/), specially, some pieces require a key from the configuration to work, however, the creator of the pieces does not know if the user who downloads the piece has it, so this function becomes useful in this case. + +```javascript +async function init() { + const { schema } = this.client.gateways.guilds; + + if (!schema.hasKey('modlog')) { + await schema.addKey('modlog', { type: 'TextChannel' }); + } +}; +``` + +## Further Reading: + +- {@tutorial UnderstandingSchemaPieces} +- {@tutorial SettingGatewayKeyTypes} +- {@tutorial SettingGatewayConfigurationUpdate} diff --git a/guides/SettingGateway/UnderstandingSchemaPieces.md b/guides/SettingGateway/UnderstandingSchemaPieces.md new file mode 100644 index 0000000000..b4d5a809f3 --- /dev/null +++ b/guides/SettingGateway/UnderstandingSchemaPieces.md @@ -0,0 +1,67 @@ +# Understanding Schema's Keys + +As mentioned in the previous tutorial, {@tutorial UnderstandingSchema}, SettingGateway's schema is divided in two parts: **folders** and **pieces**. Pieces are contained into folders, but it cannot have keys nor folders, instead, this holds the key's metadata such as its type, if it's configurable by the configuration command... you can check more information in the documentation: {@link SchemaPiece}. + +## Key options + +There are multiple options that configure the piece, they are: + +| Option | Description | +| :----------: | :------------------------------------------------------------------------- | +| array | Whether the values should be stored in an array | +| configurable | Whether this key can be configured with the built-in configuration command | +| default | The default value for this key | +| max | The maximum value for this key, only applies for string and numbers | +| min | The minimum value for this key, only applies for string and numbers | +| sql | The SQL datatype for this key | +| type | The type for this key | + +> Check {@tutorial SettingGatewayKeyTypes} for the supported types and how to extend them. + +## Default option + +*The default option is optional, but, what is its default value?* + +The default option is one of the last options to default, **array** defaults to `false`, **max** and **min** defaults to `null`, **configurable** defaults to either `true` or `false`, the latter if **type** is `any`; and **type** is always obligatory. + +- If **array** is true, default will be an empty array: `[]`. +- If **type** is boolean, default will be `false`. +- In any other case, it will be `null`. + +After default, the sql type is calculated with a valid datatype. Keep in mind that it uses standard SQL types, but may work better for PostgreSQL. In any case, if you want to use a type that is very specific to your database, consider including this option. + +## Editing key options + +Once created, it's possible since 0.5.0 to edit a {@link SchemaPiece}'s options, it's as simple as running {@link SchemaPiece#edit} which takes the same options for adding a key with {@link SchemaFolder#addKey} but with one exception: `array` and `type` can't change. The syntax is the following: + +```javascript +this.client.gateways.gatewayName.schema.keyName.edit(options); +``` + +For example, let's say we dislike the current prefix and we want to change it to `s!` for the next entries, then you can simply do: + +```javascript +this.client.gateways.guilds.schema.prefix.edit({ default: 's!' }); +``` + +Where you're doing the following steps: + +1. Access to {@link KlasaClient#gateways}, type of {@link GatewayDriver}, which holds all gateways. +1. Access to the guilds' {@link Gateway}, which manages the per-guild configuration. +1. Access to the guilds' schema via {@link Gateway#schema}, which manages the gateway's schema. +1. Access to the key we want to edit, in this case, the **prefix** key, which is type of {@link SchemaPiece}. +1. Call {@link SchemaPiece#edit} with the option `default` and the new value: `'s!'`. + +### The Type Issue + +The main reason for why we don't support editing the options `array` and `type` is: + +> Changing the type is very complex. For example, in SQL, if we changed the type from `TEXT`, `VARCHAR`, or any other string type to a numeric one such as `INTEGER`, we could risk the database potentially throwing an error or setting them to null, which would result in data loss. We would then need to download all of the data first, and insert them back with the new type. The same thing happens in NoSQL. + +Changing the value of `array` from a non-string datatype can result on the issue above, and it's a very slow process. Therefore, it's much better to just remove the key and add it back. + +## Further Reading: + +- {@tutorial UnderstandingSchemaFolders} +- {@tutorial SettingGatewayKeyTypes} +- {@tutorial SettingGatewayConfigurationUpdate} diff --git a/src/lib/Client.js b/src/lib/Client.js index 059adfaaf1..5aae0edcd5 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -420,7 +420,7 @@ class KlasaClient extends Discord.Client { // Automatic Prefix editing detection. if (typeof this.options.prefix === 'string' && this.options.prefix !== this.gateways.guilds.schema.prefix.default) { - await this.gateways.guilds.schema.prefix.modify({ default: this.options.prefix }); + await this.gateways.guilds.schema.prefix.edit({ default: this.options.prefix }); } if (this.gateways.guilds.schema.hasKey('disabledCommands')) { const languageStore = this.languages; diff --git a/src/lib/settings/GatewayDriver.js b/src/lib/settings/GatewayDriver.js index 9910384e6f..9ea10f2ef1 100644 --- a/src/lib/settings/GatewayDriver.js +++ b/src/lib/settings/GatewayDriver.js @@ -55,9 +55,9 @@ class GatewayDriver { /** * All the types accepted for the Gateway. - * @type {string[]} + * @type {Set} */ - this.types = Object.getOwnPropertyNames(SettingResolver.prototype).slice(1); + this.types = new Set(Object.getOwnPropertyNames(SettingResolver.prototype).slice(1)); /** * All the gateways added diff --git a/src/lib/settings/SchemaPiece.js b/src/lib/settings/SchemaPiece.js index 708bcc3be2..498f9eddfd 100644 --- a/src/lib/settings/SchemaPiece.js +++ b/src/lib/settings/SchemaPiece.js @@ -9,7 +9,7 @@ const fs = require('fs-nextra'); class SchemaPiece extends Schema { /** - * @typedef {Object} SchemaPieceModifyOptions + * @typedef {Object} SchemaPieceEditOptions * @property {*} [default] The new default value * @property {number} [min] The new minimum range value * @property {number} [max] The new maximum range value @@ -170,12 +170,12 @@ class SchemaPiece extends Schema { /** * Modify this SchemaPiece's properties. * @since 0.5.0 - * @param {SchemaPieceModifyOptions} options The new options + * @param {SchemaPieceEditOptions} options The new options * @returns {Promise} */ - async modify(options) { + async edit(options) { // Check if the 'options' parameter is an object. - if (!isObject(options)) throw new TypeError(`SchemaPiece#modify expected an object as a parameter. Got: ${typeof options}`); + if (!isObject(options)) throw new TypeError(`SchemaPiece#edit expected an object as a parameter. Got: ${typeof options}`); const edited = new Set(); if (typeof options.sql === 'string' && this.sql[1] !== options.sql) { @@ -225,7 +225,7 @@ class SchemaPiece extends Schema { */ _schemaCheckType(type) { if (typeof type !== 'string') throw new TypeError(`[KEY] ${this} - Parameter type must be a string.`); - if (!this.client.gateways.types.includes(type)) throw new TypeError(`[KEY] ${this} - ${type} is not a valid type.`); + if (!this.client.gateways.types.has(type)) throw new TypeError(`[KEY] ${this} - ${type} is not a valid type.`); } /** diff --git a/typings/index.d.ts b/typings/index.d.ts index ca2b37df49..0bfab7f119 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -568,7 +568,7 @@ declare module 'klasa' { private constructor(client: KlasaClient); public readonly client: KlasaClient; public resolver: SettingResolver; - public types: string[]; + public types: Set; public keys: Set; public ready: boolean; @@ -676,7 +676,7 @@ declare module 'klasa' { public setValidator(fn: Function): this; public parse(value: any, guild: KlasaGuild): Promise; public resolveString(msg: KlasaMessage): string; - public modify(options: SchemaPieceModifyOptions): Promise; + public modify(options: SchemaPieceEditOptions): Promise; private _schemaCheckType(type: string): void; private _schemaCheckArray(array: boolean): void; @@ -1701,7 +1701,7 @@ declare module 'klasa' { configurable?: boolean; }; - export type SchemaPieceModifyOptions = { + export type SchemaPieceEditOptions = { default?: any; min?: number; max?: number; From 39c66065a79fe11d364f8af0b63e85dfb9276297 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 17:57:09 +0100 Subject: [PATCH 06/76] MDLint --- CHANGELOG.md | 1 + .../SettingGateway/SettingGatewayKeyTypes.md | 17 +++++----- .../UnderstandingSchemaFolders.md | 18 +++++++---- src/lib/parsers/SettingResolver.js | 32 +++++++++---------- typings/index.d.ts | 9 +++++- 5 files changed, 45 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e669a33d90..4aa521697c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -164,6 +164,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Fixed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed SettingResolver's return types. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed Gateway syncing keys even when it's unused. (kyranet) - [[#176](https://github.com/dirigeants/klasa/pull/176)] Fixed internal parser ignoring `0` as `min` and/or `max` due to falsy value check. (kyranet) - [[#170](https://github.com/dirigeants/klasa/pull/170)] Fixed guild resolvables not working correctly. (kyranet) diff --git a/guides/SettingGateway/SettingGatewayKeyTypes.md b/guides/SettingGateway/SettingGatewayKeyTypes.md index 6f1b7c4d57..bd1a6bd6cf 100644 --- a/guides/SettingGateway/SettingGatewayKeyTypes.md +++ b/guides/SettingGateway/SettingGatewayKeyTypes.md @@ -32,26 +32,27 @@ const { Extendable } = require('klasa'); module.exports = class extends Extendable { - constructor(...args) { - super(...args, ['SettingResolver'], { + constructor(...args) { + super(...args, ['SettingResolver'], { name: 'typeName', klasa: true }); - } + } /** * Resolves my custom type! * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @param {Object} minMax The minimum and maximum + * @param {Object} [minMax={}] The minimum and maximum * @param {?number} minMax.min The minimum value * @param {?number} minMax.max The maximum value - * @returns {*} + * @returns {Promise<*>} */ - extend(data, guild, name, { min, max } = {}) { - // The content - } + async extend(data, guild, name, { min, max } = {}) { + // The content + return data; + } }; ``` diff --git a/guides/SettingGateway/UnderstandingSchemaFolders.md b/guides/SettingGateway/UnderstandingSchemaFolders.md index b284451e84..4247565db8 100644 --- a/guides/SettingGateway/UnderstandingSchemaFolders.md +++ b/guides/SettingGateway/UnderstandingSchemaFolders.md @@ -49,8 +49,10 @@ this.client.gateways.guilds.schema.addKey('userBlacklist', { type: 'User', array And now you can access to any of them in your guild configs like in the following snippet! ```javascript -msg.guild.configs.modlogs; // null -msg.guild.configs.userBlacklist; // [] +msg.guild.configs.modlogs; +// null +msg.guild.configs.userBlacklist; +// [] ``` ## Removing keys @@ -70,8 +72,10 @@ this.client.gateways.guilds.schema.removeKey('userBlacklist'); And the property `userBlacklist` for all guild configs will be deleted, that being said: ```javascript -msg.guild.configs.userBlacklist; // undefined -'userBlacklist' in msg.guild.configs; // false +msg.guild.configs.userBlacklist; +// undefined +'userBlacklist' in msg.guild.configs; +// false ``` ## Adding folders @@ -97,7 +101,7 @@ async function init() { // max: null, // configurable: true // } -}; +} ``` ### Faster @@ -118,7 +122,7 @@ async function init() { // max: null, // configurable: true // } -}; +} ``` > **Reminder**: To access to a key inside a folder in your configuration command, you use the access operator (`.`). For example: *k!conf set channels.modlogs #modlogs* @@ -150,7 +154,7 @@ async function init() { if (!schema.hasKey('modlog')) { await schema.addKey('modlog', { type: 'TextChannel' }); } -}; +} ``` ## Further Reading: diff --git a/src/lib/parsers/SettingResolver.js b/src/lib/parsers/SettingResolver.js index 8e6e0435c0..9f378dcdbb 100644 --- a/src/lib/parsers/SettingResolver.js +++ b/src/lib/parsers/SettingResolver.js @@ -12,7 +12,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {KlasaUser} + * @returns {Promise} */ async user(data, guild, name) { const result = await super.user(data); @@ -26,7 +26,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {external:Channel} + * @returns {Promise} */ async channel(data, guild, name) { const result = await super.channel(data); @@ -40,7 +40,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {external:TextChannel} + * @returns {Promise} */ async textchannel(data, guild, name) { const result = await super.channel(data); @@ -54,7 +54,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {external:VoiceChannel} + * @returns {Promise} */ async voicechannel(data, guild, name) { const result = await super.channel(data); @@ -68,7 +68,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {external:CategoryChannel} + * @returns {Promise} */ async categorychannel(data, guild, name) { const result = await super.channel(data); @@ -82,7 +82,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {KlasaGuild} + * @returns {Promise} */ async guild(data, guild, name) { const result = await super.guild(data); @@ -96,7 +96,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {external:Role} + * @returns {Promise} */ async role(data, guild, name) { const result = await super.role(data, guild) || (guild ? guild.roles.find('name', data) : null); @@ -110,7 +110,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {boolean} + * @returns {Promise} */ async boolean(data, guild, name) { const result = await super.boolean(data); @@ -127,7 +127,7 @@ class SettingResolver extends Resolver { * @param {Object} minMax The minimum and maximum * @param {?number} minMax.min The minimum value * @param {?number} minMax.max The maximum value - * @returns {string} + * @returns {Promise} */ async string(data, guild, name, { min, max } = {}) { const result = await super.string(data); @@ -141,10 +141,10 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @param {Object} minMax The minimum and maximum + * @param {Object} [minMax={}] The minimum and maximum * @param {?number} minMax.min The minimum value * @param {?number} minMax.max The maximum value - * @returns {number} + * @returns {Promise} */ async integer(data, guild, name, { min, max } = {}) { const result = await super.integer(data); @@ -159,10 +159,10 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @param {Object} minMax The minimum and maximum + * @param {Object} [minMax={}] The minimum and maximum * @param {?number} minMax.min The minimum value * @param {?number} minMax.max The maximum value - * @returns {number} + * @returns {Promise} */ async float(data, guild, name, { min, max } = {}) { const result = await super.float(data); @@ -177,7 +177,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {string} + * @returns {Promise} */ async url(data, guild, name) { const result = await super.url(data); @@ -191,7 +191,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {string} + * @returns {Promise} */ async command(data, guild, name) { const command = this.client.commands.get(data.toLowerCase()); @@ -205,7 +205,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {string} + * @returns {Promise} */ async language(data, guild, name) { const language = this.client.languages.get(data); diff --git a/typings/index.d.ts b/typings/index.d.ts index 0bfab7f119..9ebe7a19aa 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -25,10 +25,11 @@ declare module 'klasa' { Role, Snowflake, StringResolvable, - TextChannel as DiscordTextChannel, User as DiscordUser, UserResolvable, + TextChannel as DiscordTextChannel, VoiceChannel as DiscordVoiceChannel, + CategoryChannel as DiscordCategoryChannel, WebhookClient } from 'discord.js'; @@ -284,6 +285,10 @@ declare module 'klasa' { public readonly guild: KlasaGuild; } + export class KlasaCategoryChannel extends DiscordCategoryChannel { + public readonly guild: KlasaGuild; + } + export class KlasaDMChannel extends DiscordDMChannel { public readonly attachable: boolean; public readonly embedable: boolean; @@ -433,6 +438,7 @@ declare module 'klasa' { public user(data: any, guild: KlasaGuild, name: string): Promise; public user(input: KlasaUser | GuildMember | KlasaMessage | Snowflake): Promise; public voicechannel(data: any, guild: KlasaGuild, name: string): Promise; + public categorychannel(data: any, guild: KlasaGuild, name: string): Promise; public static maxOrMin(guild: KlasaGuild, value: number, min: number, max: number, name: string, suffix: string): boolean; } @@ -1619,6 +1625,7 @@ declare module 'klasa' { | KlasaMessage | KlasaTextChannel | KlasaVoiceChannel + | KlasaCategoryChannel | GuildMember | Role; From 25e43ed84ebcf0185e3ef2cb9c8079dee5a70efb Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 18:04:49 +0100 Subject: [PATCH 07/76] Fixed deploy --- .../SettingGatewayConfigurationUpdate.md | 0 .../SettingGatewayKeyTypes.md | 0 .../UnderstandingSchemaFolders.md | 0 .../UnderstandingSchemaPieces.md | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename guides/{SettingGateway => Advanced SettingGateway}/SettingGatewayConfigurationUpdate.md (100%) rename guides/{SettingGateway => Advanced SettingGateway}/SettingGatewayKeyTypes.md (100%) rename guides/{SettingGateway => Advanced SettingGateway}/UnderstandingSchemaFolders.md (100%) rename guides/{SettingGateway => Advanced SettingGateway}/UnderstandingSchemaPieces.md (100%) diff --git a/guides/SettingGateway/SettingGatewayConfigurationUpdate.md b/guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md similarity index 100% rename from guides/SettingGateway/SettingGatewayConfigurationUpdate.md rename to guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md diff --git a/guides/SettingGateway/SettingGatewayKeyTypes.md b/guides/Advanced SettingGateway/SettingGatewayKeyTypes.md similarity index 100% rename from guides/SettingGateway/SettingGatewayKeyTypes.md rename to guides/Advanced SettingGateway/SettingGatewayKeyTypes.md diff --git a/guides/SettingGateway/UnderstandingSchemaFolders.md b/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md similarity index 100% rename from guides/SettingGateway/UnderstandingSchemaFolders.md rename to guides/Advanced SettingGateway/UnderstandingSchemaFolders.md diff --git a/guides/SettingGateway/UnderstandingSchemaPieces.md b/guides/Advanced SettingGateway/UnderstandingSchemaPieces.md similarity index 100% rename from guides/SettingGateway/UnderstandingSchemaPieces.md rename to guides/Advanced SettingGateway/UnderstandingSchemaPieces.md From d51dc6c248cc3e73a0107171d2776028a956c1e0 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 18:11:06 +0100 Subject: [PATCH 08/76] Docs fixes --- guides/Getting Started/UnderstandingSettingGateway.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/guides/Getting Started/UnderstandingSettingGateway.md b/guides/Getting Started/UnderstandingSettingGateway.md index 109f1a81a6..54735c03b7 100644 --- a/guides/Getting Started/UnderstandingSettingGateway.md +++ b/guides/Getting Started/UnderstandingSettingGateway.md @@ -44,7 +44,7 @@ const client = new KlasaClient({ }); // Now, we create it: -this.client.gateways.register('channels', { +client.gateways.register('channels', { disabledCommands: { type: 'Command', default: [], @@ -72,7 +72,7 @@ client.login('A_BEAUTIFUL_TOKEN_AINT_IT?'); And then, you can access to it by: ```javascript -this.client.gateways.channels; +client.gateways.channels; ``` ## Customizing the options for each built-in gateway @@ -80,16 +80,14 @@ this.client.gateways.channels; This is available in 0.5.0 since the PR [#152](https://github.com/dirigeants/klasa/pull/152), and you're able to configure the three built-in gateways: `guilds`, `users` and `clientStorage`. The option to configure them is {@link KlasaClientOptions.gateways}, where you would add the option `gateways` to your KlasaClientOptions: ```javascript -const client = new Klasa.Client({ +new Klasa.Client({ prefix: 'k!', providers: { default: 'json' }, gateways: { guilds: { provider: 'rethinkdb' }, users: { provider: 'postgresql' } } -}); - -client.login('A_BEAUTIFUL_TOKEN_AINT_IT?'); +}).login('A_BEAUTIFUL_TOKEN_AINT_IT?'); ``` Where the *clientStorage* gateway would take the default options (json provider), the *guilds* gateway would use the rethinkdb provider, and finally the *users* one would use the postgresql provider. These options are {@link GatewayDriver.GatewayDriverAddOptions}. From 4f00b79928edc2aecf9398f93eaf69aa8d51d0d1 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 20:11:06 +0100 Subject: [PATCH 09/76] Removed ConfigUpdateEntryMany, tweaked docs, added toJSON to Gateway and GatewayDriver --- CHANGELOG.md | 2 + .../SettingGatewayConfigurationUpdate.md | 2 +- .../SettingGatewayKeyTypes.md | 2 +- .../UnderstandingSchemaPieces.md | 2 +- .../UnderstandingSettingGateway.md | 2 +- src/lib/Client.js | 17 ++------ src/lib/settings/Configuration.js | 32 +++++++------- src/lib/settings/Gateway.js | 21 ++++++++++ src/lib/settings/GatewayDriver.js | 25 +++++++++++ src/lib/settings/SchemaFolder.js | 2 +- typings/index.d.ts | 42 +++++++++++++++---- 11 files changed, 107 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa521697c..62d5b55db7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Added +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `Gateway#toJSON()` and `GatewayDriver#toJSON()`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `GatewayDriver#register` to be able to register new gateways without events (directly in your `app.js`). (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `util.getIdentifier` as a replacement for the function validator. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `SchemaFolder#keys()`, `SchemaFolder#values()`, `SchemaFolder#entries()` and `SchemaFolder#[@@iterator]()`. Identical to Map's respective methods. (kyranet) @@ -139,6 +140,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Removed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the `ConfigUpdateEntryMany` typedef in favor of a more constant type. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the resolver functions from constants. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed `SchemaFolder#keys` (`Map`) to reduce RAM usage and key caching duplication. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed SettingGateway function validators. (kyranet) diff --git a/guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md b/guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md index 161303f06c..4c87270698 100644 --- a/guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md +++ b/guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md @@ -29,7 +29,7 @@ msg.guild.configs.update({ prefix: 'k!', language: 'es-ES' }); > **Note**: Some types require a Guild instance to work, for example, *channels*, *roles* and *members*. -> Additionally, if no 'action' option is passed to {@link Configuration.ConfigurationUpdateOptions}, it'll assume the `auto` mode, which will add or remove depending on the existence of the key. +> Additionally, if no 'action' option is passed to {@link ConfigurationUpdateOptions}, it'll assume the `auto` mode, which will add or remove depending on the existence of the key. ## Further Reading: diff --git a/guides/Advanced SettingGateway/SettingGatewayKeyTypes.md b/guides/Advanced SettingGateway/SettingGatewayKeyTypes.md index bd1a6bd6cf..c7ea5fc3ad 100644 --- a/guides/Advanced SettingGateway/SettingGatewayKeyTypes.md +++ b/guides/Advanced SettingGateway/SettingGatewayKeyTypes.md @@ -3,7 +3,7 @@ By default, there are several built-in types that the developer can use, and with the possibility to add custom types via {@link Extendable}s as explained below. The built-in types are: | Name | Type | Description | -| :-----------------: | :------------------------------------------------ | ---------------------------------------------------------------------------------------- | +| ------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------- | | **any** | Anything, no type restriction | Resolves anything, even objects, the usage of this type will make a key unconfigurable | | **boolean** | A {@link Boolean} resolvable | Resolves a boolean primitive value | | **categorychannel** | A {@link external:CategoryChannel} instance or id | Resolves a CategoryChannel | diff --git a/guides/Advanced SettingGateway/UnderstandingSchemaPieces.md b/guides/Advanced SettingGateway/UnderstandingSchemaPieces.md index b4d5a809f3..49ba3cd09d 100644 --- a/guides/Advanced SettingGateway/UnderstandingSchemaPieces.md +++ b/guides/Advanced SettingGateway/UnderstandingSchemaPieces.md @@ -7,7 +7,7 @@ As mentioned in the previous tutorial, {@tutorial UnderstandingSchema}, SettingG There are multiple options that configure the piece, they are: | Option | Description | -| :----------: | :------------------------------------------------------------------------- | +| ------------ | -------------------------------------------------------------------------- | | array | Whether the values should be stored in an array | | configurable | Whether this key can be configured with the built-in configuration command | | default | The default value for this key | diff --git a/guides/Getting Started/UnderstandingSettingGateway.md b/guides/Getting Started/UnderstandingSettingGateway.md index 54735c03b7..9e17350082 100644 --- a/guides/Getting Started/UnderstandingSettingGateway.md +++ b/guides/Getting Started/UnderstandingSettingGateway.md @@ -2,7 +2,7 @@ What is SettingGateway? It is an interface that connects your Discord bot with a database and ensures maximum performance by using a very refined cache system that is always up to date with the database. In a point of view, SettingGateway can be understood as an abstracted [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping) as it's able to run any kind database (with a compatible {@link Provider}) and manage the data efficiently. -By default, Klasa uses the [json](https://github.com/dirigeants/klasa/blob/master/src/providers/json.js) provider. Do not be fooled and insta-replace with SQLite, Klasa's JSON provider writes the data [atomically](https://en.wikipedia.org/wiki/Atomicity_(database_systems) ). In other words, it is very rare for the data to corrupt. +By default, Klasa uses the [json](https://github.com/dirigeants/klasa/blob/master/src/providers/json.js) provider. Do not be fooled and insta-replace with SQLite, Klasa's JSON provider writes the data [atomically](https://en.wikipedia.org/wiki/Atomicity_%28database_systems%29). In other words, it is very rare for the data to corrupt. Thanks to the abstraction of SettingGateway, the developer has many options, for example, if you want to change the database that manages the data, you just change one line of code, without needing to rewrite everything that relies on it, nor you need to rewrite the interface itself in order to be able to work with a different database. diff --git a/src/lib/Client.js b/src/lib/Client.js index 5aae0edcd5..29f4d2f2eb 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -110,14 +110,6 @@ class KlasaClient extends Discord.Client { * @memberof KlasaClient */ - /** - * @typedef {Object} ConfigUpdateEntryMany - * @property {'MANY'} type The type for config updates made with the updateMany pattern - * @property {string[]} keys The keys changed - * @property {Array<*>} values The values changed - * @memberof KlasaClient - */ - /** * Constructs the klasa client * @since 0.0.1 @@ -632,24 +624,23 @@ KlasaClient.defaultPermissionLevels = new PermLevels() */ /** - * Emitted when {@link Configuration.update} is run, the parameter path will be an object with the following format: - * `{ type: 'MANY', keys: string[], values: Array<*> }` + * Emitted when {@link Configuration#update} is run. * @event KlasaClient#configUpdateEntry * @since 0.5.0 * @param {Configuration} oldEntry The old configuration entry * @param {Configuration} newEntry The new configuration entry - * @param {(string|ConfigUpdateEntryMany)} path The path of the key which changed + * @param {string[]} path The path of the key which changed */ /** - * Emitted when {@link Gateway.deleteEntry} is run. + * Emitted when {@link Gateway#deleteEntry} is run. * @event KlasaClient#configDeleteEntry * @since 0.5.0 * @param {Configuration} entry The entry which got deleted */ /** - * Emitted when {@link Gateway.createEntry} is run or when {@link Gateway.getEntry} + * Emitted when {@link Gateway#createEntry} is run or when {@link Gateway#getEntry} * with the create parameter set to true creates the entry. * @event KlasaClient#configCreateEntry * @since 0.5.0 diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 4831baf146..2e7b7f1d1a 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -274,7 +274,7 @@ class Configuration { this._parseUpdateMany(this, object, this.gateway.schema, guild, list, updateObject); await Promise.all(list.promises); - if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, { type: 'MANY', keys: list.keys, values: list.values }); + if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, list.keys); if (this.gateway.sql) await this.gateway.provider.update(this.gateway.type, this.id, list.keys, list.values); else await this.gateway.provider.update(this.gateway.type, this.id, updateObject); if (list.errors.length) throw { updated: { keys: list.keys, values: list.values }, errors: list.errors }; @@ -372,7 +372,7 @@ class Configuration { } } - if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, path.path); + if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, [path.path]); return { parsed, parsedID, array: cache, path, route }; } @@ -424,23 +424,25 @@ class Configuration { _parseUpdateMany(cache, object, schema, guild, list, updateObject) { for (const key of Object.keys(object)) { if (!schema.hasKey(key)) continue; + // Check if it's a folder, and recursively iterate over it if (schema[key].type === 'Folder') { if (!(key in updateObject)) updateObject = updateObject[key] = {}; this._parseUpdateMany(cache[key], object[key], schema[key], guild, list, updateObject); + // If the value is null, reset it + } else if (object[key] === null) { + list.promises.push(this.reset(key) + .then(({ value, path }) => { + updateObject[key] = cache[key] = value; + list.keys.push(path); + list.values.push(value); + }) + .catch(error => list.errors.push([schema.path, error]))); + // Throw an error if it's not array (nor type any) but an array was given + } else if (!schema[key].array && schema[key].type !== 'any' && Array.isArray(object[key])) { + list.errors.push([schema[key].path, new Error(`${schema[key].path} does not expect an array as value.`)]); + // Throw an error if the key requests an array and none is given } else if (schema[key].array && !Array.isArray(object[key])) { list.errors.push([schema[key].path, new Error(`${schema[key].path} expects an array as value.`)]); - } else if (!schema[key].array && schema[key].array !== 'any' && Array.isArray(object[key])) { - list.errors.push([schema[key].path, new Error(`${schema[key].path} does not expect an array as value.`)]); - } else if (object[key] === null) { - list.promises.push( - this.reset(key) - .then(({ value, path }) => { - updateObject[key] = cache[key] = value; - list.keys.push(path); - list.values.push(value); - }) - .catch(error => list.errors.push([schema.path, error])) - ); } else { const promise = schema[key].array && schema[key].type !== 'any' ? Promise.all(object[key].map(entry => schema[key].parse(entry, guild) @@ -479,7 +481,7 @@ class Configuration { for (let i = 0; i < route.length - 1; i++) cache = cache[route[i]] || {}; cache[route[route.length - 1]] = parsedID; - if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, path.path); + if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, [path.path]); } /** diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index 7ed97e9364..2a371e48ae 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -39,6 +39,14 @@ class Gateway extends GatewayStorage { * @memberof Gateway */ + /** + * @typedef {Object} GatewayJSON + * @property {string} type The name of this gateway + * @property {GatewayOptions} options The options for this gateway + * @property {Object} schema The current schema + * @memberof Gateway + */ + /** * @since 0.0.1 * @param {GatewayDriver} store The GatewayDriver instance which initiated this instance @@ -321,6 +329,19 @@ class Gateway extends GatewayStorage { if (force) await route.force(action, key, piece); } + /** + * Get a JSON object containing the schema and options. + * @since 0.5.0 + * @returns {GatewayJSON} + */ + toJSON() { + return { + type: this.type, + options: this.options, + schema: this.schema.toJSON() + }; + } + /** * Stringify a value or the instance itself. * @since 0.5.0 diff --git a/src/lib/settings/GatewayDriver.js b/src/lib/settings/GatewayDriver.js index 9ea10f2ef1..0101de1977 100644 --- a/src/lib/settings/GatewayDriver.js +++ b/src/lib/settings/GatewayDriver.js @@ -257,6 +257,31 @@ class GatewayDriver { throw `This provider (${engine}) does not exist in your system.`; } + /** + * The GatewayDriver with all gateways, types and keys as JSON. + * @since 0.5.0 + * @returns {Object} + */ + toJSON() { + const object = { + types: [...this.types], + keys: [...this.keys], + ready: this.ready + }; + for (const key of this.keys) object[key] = this[key].toJSON(); + + return object; + } + + /** + * The stringified GatewayDriver with all the managed gateways. + * @since 0.5.0 + * @returns {string} + */ + toString() { + return `GatewayDriver(${[...this.keys].join(', ')})`; + } + } module.exports = GatewayDriver; diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 27204280ab..80e83d76bc 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -485,7 +485,7 @@ class SchemaFolder extends Schema { /** * Get a JSON object containing all the objects from this schema's children. * @since 0.5.0 - * @returns {any} + * @returns {Object} */ toJSON() { return Object.assign({ type: 'Folder' }, ...this.keyArray.map(key => ({ [key]: this[key].toJSON() }))); diff --git a/typings/index.d.ts b/typings/index.d.ts index 9ebe7a19aa..70c87b9274 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -130,7 +130,7 @@ declare module 'klasa' { // SettingGateway Events public on(event: 'configCreateEntry', listener: (entry: Configuration) => void): this; public on(event: 'configDeleteEntry', listener: (entry: Configuration) => void): this; - public on(event: 'configUpdateEntry', listener: (oldEntry: Configuration, newEntry: Configuration, path: string | ConfigUpdateEntryMany) => void): this; + public on(event: 'configUpdateEntry', listener: (oldEntry: Configuration, newEntry: Configuration, path: string[]) => void): this; // Schema Events public on(event: 'schemaKeyAdd', listener: (key: SchemaFolder | SchemaPiece) => void): this; @@ -548,9 +548,9 @@ declare module 'klasa' { } export class Gateway extends GatewayStorage { - private constructor(store: GatewayDriver, type: string, schema: object, options: GatewayDriverAddOptions); + private constructor(store: GatewayDriver, type: string, schema: object, options: GatewayOptions); public store: GatewayDriver; - public options: GatewayDriverAddOptions; + public options: GatewayOptions; public defaultSchema: object; public readonly resolver: SettingResolver; public readonly cache: Collection; @@ -567,6 +567,7 @@ declare module 'klasa' { private _resolveGuild(guild: GatewayGuildResolvable): KlasaGuild; private _shardSync(path: string[], data: any, action: 'add' | 'delete' | 'update', force: boolean): Promise; + public toJSON(): GatewayJSON; public toString(): string; } @@ -599,6 +600,9 @@ declare module 'klasa' { public add(name: string, schema?: object, options?: GatewayDriverAddOptions, download?: boolean): Promise; private _ready(): Promise>>>; private _checkProvider(engine: string): string; + + public toJSON(): GatewayDriverJSON; + public toString(): string; } export abstract class GatewayStorage { @@ -1611,6 +1615,17 @@ declare module 'klasa' { }; // Settings + export type GatewayOptions = { + provider: Provider; + nice?: boolean; + }; + + export type GatewayJSON = { + type: string; + options: GatewayOptions; + schema: SchemaFolderJSON; + }; + export type GatewayGetPathOptions = { avoidUnconfigurable?: boolean; piece?: boolean; @@ -1670,12 +1685,6 @@ declare module 'klasa' { updated: ConfigurationUpdateObjectList; }; - export type ConfigUpdateEntryMany = { - type: 'MANY'; - keys: string[]; - values: any[]; - }; - export type GatewayGuildResolvable = KlasaGuild | KlasaTextChannel | KlasaVoiceChannel @@ -1726,6 +1735,21 @@ declare module 'klasa' { configurable: boolean; }; + export type SchemaFolderJSON = { + type: 'Folder'; + [k: string]: SchemaPieceJSON | SchemaFolderJSON; + }; + + export type GatewayDriverJSON = { + types: string[]; + keys: string[]; + ready: boolean; + guilds: GatewayJSON; + users: GatewayJSON; + clientStorage: GatewayJSON; + [k: string]: GatewayJSON; + }; + // Structures export type CommandOptions = { aliases?: string[]; From e149295139b692fe12bf9f7c414caad36749a3be Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 9 Feb 2018 15:01:37 -0600 Subject: [PATCH 10/76] only decent way to document Symbol.iterator --- src/lib/settings/SchemaFolder.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 80e83d76bc..002047eea3 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -442,7 +442,6 @@ class SchemaFolder extends Schema { * Returns a new Iterator object that contains the `[key, value]` pairs for each element contained in this folder. * Identical to [Map.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) * @since 0.5.0 - * @generator * @yields {Array} */ *entries() { @@ -453,7 +452,6 @@ class SchemaFolder extends Schema { * Returns a new Iterator object that contains the values for each element contained in this folder. * Identical to [Map.values()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) * @since 0.5.0 - * @generator * @yields {(SchemaFolder|SchemaPiece)} */ *values() { @@ -464,7 +462,6 @@ class SchemaFolder extends Schema { * Returns a new Iterator object that contains the keys for each element contained in this folder. * Identical to [Map.keys()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) * @since 0.5.0 - * @generator * @yields {string} */ *keys() { @@ -474,10 +471,15 @@ class SchemaFolder extends Schema { /** * Returns a new Iterator object that contains the `[key, value]` pairs for each element contained in this folder. * Identical to [Map.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) + * @name @@iterator * @since 0.5.0 + * @method + * @instance * @generator * @returns {IterableIterator>} + * @memberof SchemaFolder */ + [Symbol.iterator]() { return this.entries(); } From 156890c263b5a98cde0f0c325f65ea845b5fb667 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 22:46:17 +0100 Subject: [PATCH 11/76] Requested changes --- .../SettingGatewayConfigurationUpdate.md | 4 +-- .../SettingGatewayKeyTypes.md | 4 +-- .../UnderstandingSchemaFolders.md | 31 +++++++------------ .../UnderstandingSchemaPieces.md | 2 +- .../UnderstandingSettingGateway.md | 2 +- 5 files changed, 18 insertions(+), 25 deletions(-) diff --git a/guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md b/guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md index 4c87270698..ef502ef960 100644 --- a/guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md +++ b/guides/Advanced SettingGateway/SettingGatewayConfigurationUpdate.md @@ -9,14 +9,14 @@ Once we have our schema done with all the keys, folders and types needed, we may msg.guild.configs.update('roles.administrator', '339943234405007361', msg.guild); // Updating an array -// userBlacklist, as mentioned in another tutorial, it's a piece with an array or users. Using +// userBlacklist, as mentioned in another tutorial, it's a piece with an array of users. Using // the following code will add or remove it, depending on the existence of the key in the configuration. msg.guild.configs.update('userBlacklist', '272689325521502208'); // Ensuring the function call adds (error if it exists) msg.guild.configs.update('userBlacklist', '272689325521502208', { action: 'add' }); -// Ensuring the function call removes (error if it not exists) +// Ensuring the function call removes (error if it doesn't exist) msg.guild.configs.update('userBlacklist', '272689325521502208', { action: 'remove' }); // Updating it with a json object diff --git a/guides/Advanced SettingGateway/SettingGatewayKeyTypes.md b/guides/Advanced SettingGateway/SettingGatewayKeyTypes.md index c7ea5fc3ad..bb59b9a2b8 100644 --- a/guides/Advanced SettingGateway/SettingGatewayKeyTypes.md +++ b/guides/Advanced SettingGateway/SettingGatewayKeyTypes.md @@ -4,7 +4,7 @@ By default, there are several built-in types that the developer can use, and wit | Name | Type | Description | | ------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| **any** | Anything, no type restriction | Resolves anything, even objects, the usage of this type will make a key unconfigurable | +| **any** | Anything, no type restriction | Resolves anything, even objects. The usage of this type will make a key unconfigurable | | **boolean** | A {@link Boolean} resolvable | Resolves a boolean primitive value | | **categorychannel** | A {@link external:CategoryChannel} instance or id | Resolves a CategoryChannel | | **channel** | A {@link external:Channel} instance or id | Resolves a channel. Be careful with using this, as it accepts any type of channel | @@ -25,7 +25,7 @@ By default, there are several built-in types that the developer can use, and wit ## Adding new types -To add new keys, you use an {@link Extendable} extending {@link SettingResolver}. If you don't know how to create an extendable, check the following tutorial: {@tutorial CreatingExtendables}. The following extendable is a template for this: +To add new types, you use an {@link Extendable} extending {@link SettingResolver}. If you don't know how to create an extendable, check the following tutorial: {@tutorial CreatingExtendables}. The following extendable is a template for this: ```javascript const { Extendable } = require('klasa'); diff --git a/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md b/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md index 4247565db8..4aa0c4c6e1 100644 --- a/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md +++ b/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md @@ -2,14 +2,14 @@ A schema works like a diagram or a blueprint, in SettingGateway, the schema defines the keys present in the configuration for a specific gateway. This feature serves multiple purposes: -1. Define what keys does the {@link Gateway} manage and their properties. +1. Define what keys the {@link Gateway} manages and their properties. 1. Define what type the keys must hold. 1. Define the SQL schema when using a SQL database. 1. Speed up performance when iterating over keys. ## Adding keys -Adding keys with the schema is like adding a piece into a box, but you can also have boxes inside another boxes. That being said, you get the box you want to modify and insert the new pieces or boxes into it. The methods to achieve that are {@link SchemaFolder#addKey} to add pieces (keys) and {@link SchemaFolder#addFolder} to add boxes (more folders). +Adding keys with the schema is like adding a piece into a box, but you can also have boxes inside other boxes. That being said, you get the box you want to modify and insert the new pieces or boxes into it. The methods to achieve that are {@link SchemaFolder#addKey} to add pieces (keys) and {@link SchemaFolder#addFolder} to add boxes (folders). You would normally use these two methods using the following snippet: @@ -24,10 +24,10 @@ this.client.gateways.gatewayName.schema.addFolder(name, options, force); The parameters are: - **name**: The name of the new key. If it conflicts with a pre-existent key, this will error. -- **options**: The options for the new key or folder. -- **force**: Whether this change should affect all entries. It requires a lot of processing but ensures the changes are correctly applied in both cache and database. +- **options**: The options for the new key or folder. Check {@link SchemaFolderAddOptions}. +- **force**: Whether this change should affect all entries. It requires a lot of processing but ensures the changes are correctly applied in both the cache and database. -You can also extend any of the three built-in {@link Gateway}s from Klasa, for example, if you want to add a new key called **modlogs** that accepts only text channels, for your guild configs, you would use the following code: +You can also extend any of the three built-in {@link Gateway}s from Klasa. For example, if you want to add a new key called **modlogs** that accepts only text channels, for your guild configs, you would use the following code: ```javascript this.client.gateways.guilds.schema.addKey('modlogs', { type: 'TextChannel' }); @@ -38,7 +38,7 @@ Where you're doing the following steps: 1. Access to {@link KlasaClient#gateways}, type of {@link GatewayDriver}, which holds all gateways. 1. Access to the guilds' {@link Gateway}, which manages the per-guild configuration. 1. Access to the guilds' schema via {@link Gateway#schema}, which manages the gateway's schema. -1. Add a new key called **modlogs** in the root of the schema, with type of **TextChannel**. +1. Add a new key called **modlogs** in the root of the schema, with a type of **TextChannel**. And you would have a perfectly configured modlogs key in your configs. However, you can also have an array of the same type. For example, you want to have a configurable array of users blacklisted in a guild, in a key named **userBlacklist**: @@ -46,7 +46,7 @@ And you would have a perfectly configured modlogs key in your configs. However, this.client.gateways.guilds.schema.addKey('userBlacklist', { type: 'User', array: true }); ``` -And now you can access to any of them in your guild configs like in the following snippet! +And now you can have access to any of them in your guild configs like in the following snippet! ```javascript msg.guild.configs.modlogs; @@ -57,7 +57,7 @@ msg.guild.configs.userBlacklist; ## Removing keys -Removing keys with the schema is quite easy, as you would access to the {@link SchemaFolder} that holds it and remove it by its name (remember that `force` is optional and defaults to `true`) using {@link SchemaFolder#removeKey} as the following example: +Removing keys with the schema is quite easy, as you would have access to the {@link SchemaFolder} that holds it and remove it by its name (remember that `force` is optional and defaults to `true`) using {@link SchemaFolder#removeKey} as in the following example: ```javascript this.client.gateways.gatewayName.schema.removeKey(name, force); @@ -69,18 +69,11 @@ In case you have a key you do not longer use and you want to get rid of it, for this.client.gateways.guilds.schema.removeKey('userBlacklist'); ``` -And the property `userBlacklist` for all guild configs will be deleted, that being said: - -```javascript -msg.guild.configs.userBlacklist; -// undefined -'userBlacklist' in msg.guild.configs; -// false -``` +And the property `userBlacklist` for all guild configs will be deleted. ## Adding folders -Folder creation is very similar to key creation, but with one key difference: it has no options for itself, but instead, it can create its children keys (like you can add a box with another boxes and pieces, into another). You can add a new key inside a new folder in two different ways: +Folder creation is very similar to key creation, but with one key difference: it has no options for itself, but instead, it can create its children keys (just like you can add a box with other boxes and pieces, into another). You can add a new key inside a new folder in two different ways: ### Slower @@ -125,7 +118,7 @@ async function init() { } ``` -> **Reminder**: To access to a key inside a folder in your configuration command, you use the access operator (`.`). For example: *k!conf set channels.modlogs #modlogs* +> **Reminder**: To access a key inside a folder in your configuration command, you use the access operator (`.`). For example: *k!conf set channels.modlogs #modlogs* ## Removing folders @@ -145,7 +138,7 @@ This will remove all the data from all the sub-keys and sub-folders, even very n ## Ensuring the existence of a key. -In [Klasa-Pieces](https://github.com/dirigeants/klasa-pieces/), specially, some pieces require a key from the configuration to work, however, the creator of the pieces does not know if the user who downloads the piece has it, so this function becomes useful in this case. +In [klasa pieces](https://github.com/dirigeants/klasa-pieces/) specially, some pieces require a key from the configuration to work, however, the creator of the pieces does not know if the user who downloads the piece has it, so this function becomes useful in this case. ```javascript async function init() { diff --git a/guides/Advanced SettingGateway/UnderstandingSchemaPieces.md b/guides/Advanced SettingGateway/UnderstandingSchemaPieces.md index 49ba3cd09d..0990620ccd 100644 --- a/guides/Advanced SettingGateway/UnderstandingSchemaPieces.md +++ b/guides/Advanced SettingGateway/UnderstandingSchemaPieces.md @@ -1,6 +1,6 @@ # Understanding Schema's Keys -As mentioned in the previous tutorial, {@tutorial UnderstandingSchema}, SettingGateway's schema is divided in two parts: **folders** and **pieces**. Pieces are contained into folders, but it cannot have keys nor folders, instead, this holds the key's metadata such as its type, if it's configurable by the configuration command... you can check more information in the documentation: {@link SchemaPiece}. +As mentioned in the previous tutorial, {@tutorial UnderstandingSchema}, SettingGateway's schema is divided in two parts: **folders** and **pieces**. Pieces are contained in folders, but they cannot have keys nor folders. Instead, this holds the key's metadata such as its type, if it's configurable by the configuration command... you can check more information in the documentation: {@link SchemaPiece}. ## Key options diff --git a/guides/Getting Started/UnderstandingSettingGateway.md b/guides/Getting Started/UnderstandingSettingGateway.md index 9e17350082..ddd1fac7e7 100644 --- a/guides/Getting Started/UnderstandingSettingGateway.md +++ b/guides/Getting Started/UnderstandingSettingGateway.md @@ -10,7 +10,7 @@ Thanks to the abstraction of SettingGateway, the developer has many options, for As mentioned before, SettingGateway is abstracted, it does not rely on a very specific database, but can use any of them. In a production bot, you may want to use a process-based database such as rethinkdb, mongodb or postgresql, you can check and download them from the [klasa-pieces](https://github.com/dirigeants/klasa-pieces/) repository so you don't need to make one from scratch. -Now... how we update it? Go to your main file, where {@link KlasaClient} is initialized, and add a new option to your {@link KlasaClientOptions}. The following code snippet as an example: +Now... how do we update it? Go to your main file, where {@link KlasaClient} is initialized, and add a new option to your {@link KlasaClientOptions}. The following code snippet as an example: ```javascript const client = new KlasaClient({ providers: { default: 'rethinkdb' } }); From b875eac45962ac84dbeadcf11c8e3f8b5d19e8d6 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 22:58:51 +0100 Subject: [PATCH 12/76] IterableIterator -> Iterator --- src/lib/settings/SchemaFolder.js | 2 +- typings/index.d.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 002047eea3..628bfaf661 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -476,7 +476,7 @@ class SchemaFolder extends Schema { * @method * @instance * @generator - * @returns {IterableIterator>} + * @returns {Iterator>} * @memberof SchemaFolder */ diff --git a/typings/index.d.ts b/typings/index.d.ts index 70c87b9274..516490f2b4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -663,10 +663,10 @@ declare module 'klasa' { private _removeKey(key: string): void; private _init(options: object): true; - public entries(): IterableIterator<[string, SchemaFolder | SchemaPiece]>; - public values(): IterableIterator; - public keys(): IterableIterator; - public [Symbol.iterator](): IterableIterator<[string, SchemaFolder | SchemaPiece]>; + public entries(): Iterator<[string, SchemaFolder | SchemaPiece]>; + public values(): Iterator; + public keys(): Iterator; + public [Symbol.iterator](): Iterator<[string, SchemaFolder | SchemaPiece]>; public toJSON(): any; public toString(): string; From 379e089f3255aa5d7dbd387e13f960b8f85ff872 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 23:11:24 +0100 Subject: [PATCH 13/76] Added iterator for Schedule --- src/lib/schedule/Schedule.js | 15 +++++++++++++++ typings/index.d.ts | 2 ++ 2 files changed, 17 insertions(+) diff --git a/src/lib/schedule/Schedule.js b/src/lib/schedule/Schedule.js index 2897aa362c..18f86405e0 100644 --- a/src/lib/schedule/Schedule.js +++ b/src/lib/schedule/Schedule.js @@ -243,6 +243,21 @@ class Schedule { else if (!this._interval) this._interval = this.client.setInterval(this.execute.bind(this), this.timeInterval); } + /** + * Returns a new Iterator object that contains the values for each element contained in the task queue. + * @name @@iterator + * @since 0.5.0 + * @method + * @instance + * @generator + * @returns {Iterator} + * @memberof Schedule + */ + + *[Symbol.iterator]() { + for (let i = 0; i < this.tasks.length; i++) yield this.tasks[i]; + } + } module.exports = Schedule; diff --git a/typings/index.d.ts b/typings/index.d.ts index 516490f2b4..abcfa6a1cc 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -482,6 +482,8 @@ declare module 'klasa' { private _insert(task: ScheduledTask): ScheduledTask; private _clearInterval(): void; private _checkInterval(): void; + + public [Symbol.iterator](): Iterator; } export class ScheduledTask { From 9f888cd2cbc7d57cb607baccc532cb7da9483671 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 23:12:04 +0100 Subject: [PATCH 14/76] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d5b55db7..2f2cf082b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Added +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Added property `Symbol.iterator` to Schedule. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `Gateway#toJSON()` and `GatewayDriver#toJSON()`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `GatewayDriver#register` to be able to register new gateways without events (directly in your `app.js`). (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `util.getIdentifier` as a replacement for the function validator. (kyranet) From b58902ad0e31689157b5c6193bfc451b96a87f56 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 9 Feb 2018 23:58:39 +0100 Subject: [PATCH 15/76] Removed `SchemaFolder#getList` and replaced it to `Configuration#list`. --- CHANGELOG.md | 1 + src/commands/Admin/conf.js | 3 +- src/commands/General/User Configs/userconf.js | 3 +- src/lib/settings/Configuration.js | 39 ++++++++++++++++++- src/lib/settings/SchemaFolder.js | 36 +---------------- typings/index.d.ts | 2 +- 6 files changed, 45 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f2cf082b4..ddce7b6388 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,6 +141,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Removed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed `SchemaFolder#getList` and replaced it to `Configuration#list`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the `ConfigUpdateEntryMany` typedef in favor of a more constant type. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the resolver functions from constants. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed `SchemaFolder#keys` (`Map`) to reduce RAM usage and key caching duplication. (kyranet) diff --git a/src/commands/Admin/conf.js b/src/commands/Admin/conf.js index 45b18a8665..a9ca7acd81 100644 --- a/src/commands/Admin/conf.js +++ b/src/commands/Admin/conf.js @@ -46,7 +46,8 @@ module.exports = class extends Command { list(msg, [key]) { const { path } = this.client.gateways.guilds.getPath(key, { avoidUnconfigurable: true, piece: false }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_SERVER', key ? `: ${key.split('.').map(toTitleCase).join('/')}` : '', codeBlock('asciidoc', path.getList(msg)))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_SERVER', key ? `: ${key.split('.').map(toTitleCase).join('/')}` : '', + codeBlock('asciidoc', msg.guild.configs.list(msg, path)))); } }; diff --git a/src/commands/General/User Configs/userconf.js b/src/commands/General/User Configs/userconf.js index a01674e85d..2c7ed7e5c5 100644 --- a/src/commands/General/User Configs/userconf.js +++ b/src/commands/General/User Configs/userconf.js @@ -44,7 +44,8 @@ module.exports = class extends Command { list(msg, [key]) { const { path } = this.client.gateways.users.getPath(key, { avoidUnconfigurable: true, piece: false }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_USER', key ? `: ${key.split('.').map(toTitleCase).join('/')}` : '', codeBlock('asciidoc', path.getList(msg)))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_USER', key ? `: ${key.split('.').map(toTitleCase).join('/')}` : '', + codeBlock('asciidoc', msg.author.configs.list(msg, path)))); } }; diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 2e7b7f1d1a..70c6dc11dd 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -1,4 +1,5 @@ -const { isObject, makeObject, deepClone, tryParse, getIdentifier } = require('../util/util'); +const { isObject, makeObject, deepClone, tryParse, getIdentifier, toTitleCase } = require('../util/util'); +const SchemaFolder = require('./SchemaFolder'); /** * Creating your own Configuration instances if often discouraged and unneeded. SettingGateway handles them internally for you. @@ -254,6 +255,42 @@ class Configuration { return this._updateSingle(key, value, guild, options); } + /** + * Get a list. + * @since 0.5.0 + * @param {KlasaMessage} msg The Message instance + * @param {(SchemaFolder|string)} path The path to resolve + * @returns {string} + */ + list(msg, path) { + const folder = path instanceof SchemaFolder ? path : this.gateway.getPath(path, { piece: false }).path; + const array = []; + const folders = []; + const keys = {}; + let longest = 0; + for (const [key, value] of folder.entries()) { + if (value.type === 'Folder') { + folders.push(`// ${key}`); + } else if (value.configurable) { + if (!(value.type in keys)) keys[value.type] = []; + if (key.length > longest) longest = key.length; + keys[value.type].push(key); + } + } + const keysTypes = Object.keys(keys); + if (folders.length === 0 && keysTypes.length === 0) return ''; + if (folders.length) array.push('= Folders =', ...folders.sort(), ''); + if (keysTypes.length) { + for (const keyType of keysTypes.sort()) { + keys[keyType].sort(); + array.push(`= ${toTitleCase(keyType)}s =`); + for (const key of keys[keyType]) array.push(`${key.padEnd(longest)} :: ${folder[key].resolveString(msg)}`); + array.push(''); + } + } + return array.join('\n'); + } + /** * Update multiple keys given a JSON object. * @since 0.5.0 diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 628bfaf661..98949b3122 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -1,6 +1,6 @@ const SchemaPiece = require('./SchemaPiece'); const Schema = require('./Schema'); -const { toTitleCase, deepClone } = require('../util/util'); +const { deepClone } = require('../util/util'); const fs = require('fs-nextra'); /** @@ -225,40 +225,6 @@ class SchemaFolder extends Schema { throw new TypeError(`Action must be either 'add' or 'delete'. Got: ${action}`); } - /** - * Get a list. - * @since 0.5.0 - * @param {KlasaMessage} msg The Message instance - * @returns {string} - */ - getList(msg) { - const array = []; - const folders = []; - const keys = {}; - let longest = 0; - for (const key of this.keyArray) { - if (this[key].type === 'Folder') { - folders.push(`// ${key}`); - } else if (this[key].configurable) { - if (!(this[key].type in keys)) keys[this[key].type] = []; - if (key.length > longest) longest = key.length; - keys[this[key].type].push(key); - } - } - const keysTypes = Object.keys(keys); - if (folders.length === 0 && keysTypes.length === 0) return ''; - if (folders.length) array.push('= Folders =', ...folders.sort(), ''); - if (keysTypes.length) { - for (const keyType of keysTypes.sort()) { - keys[keyType].sort(); - array.push(`= ${toTitleCase(keyType)}s =`); - for (let i = 0; i < keys[keyType].length; i++) array.push(`${keys[keyType][i].padEnd(longest)} :: ${this[keys[keyType][i]].resolveString(msg)}`); - array.push(''); - } - } - return array.join('\n'); - } - /** * Get a JSON object with all the default values. * @since 0.5.0 diff --git a/typings/index.d.ts b/typings/index.d.ts index abcfa6a1cc..dafb2bd1a4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -530,6 +530,7 @@ declare module 'klasa' { public update(key: object, guild?: GatewayGuildResolvable): Promise; public update(key: string, value?: any, options?: ConfigurationUpdateOptions): Promise; public update(key: string, value?: any, guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; + public list(msg: KlasaMessage, path: SchemaFolder | string): string; private _updateMany(object: any, guild?: GatewayGuildResolvable): Promise; private _reset(key: string, guild: GatewayGuildResolvable, avoidUnconfigurable: boolean): Promise; @@ -654,7 +655,6 @@ declare module 'klasa' { public addKey(key: string, options: SchemaFolderAddOptions, force?: boolean): Promise; public removeKey(key: string, force?: boolean): Promise; public force(action: 'add' | 'edit' | 'delete', key: string, piece: SchemaFolder | SchemaPiece): Promise; - public getList(msg: KlasaMessage): string; public getDefaults(data?: object): object; public getSQL(array?: string[]): string[]; public getAllKeys(array?: string[]): string[]; From 7a4e081b37e1025ebee98bbbefe8b5f64590d906 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 10 Feb 2018 00:52:10 +0100 Subject: [PATCH 16/76] First functional version --- src/lib/settings/GatewayDriver.js | 76 +++++++++++++++++++++++-------- typings/index.d.ts | 4 +- 2 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/lib/settings/GatewayDriver.js b/src/lib/settings/GatewayDriver.js index 0101de1977..ad391bf1c7 100644 --- a/src/lib/settings/GatewayDriver.js +++ b/src/lib/settings/GatewayDriver.js @@ -47,6 +47,16 @@ class GatewayDriver { */ Object.defineProperty(this, 'client', { value: client }); + /** + * The register creation queue. + * @since 0.5.0 + * @name GatewayDriver#_queue + * @type {Map} + * @readonly + * @private + */ + Object.defineProperty(this, '_queue', { value: new Map() }); + /** * The resolver instance this Gateway uses to parse the data. * @type {SettingResolver} @@ -177,26 +187,25 @@ class GatewayDriver { /** * Registers a new Gateway. + * @since 0.5.0 * @param {string} name The name for the new gateway * @param {Object} [schema={}] The schema for use in this gateway * @param {GatewayDriverAddOptions} [options={}] The options for the new gateway - * @returns {Gateway} + * @chainable + * @returns {this} */ register(name, schema = {}, options = {}) { - if (typeof name !== 'string') throw 'You must pass a name for your new gateway and it must be a string.'; - - if (name in this) throw 'There is already a Gateway with that name.'; - if (!this.client.methods.util.isObject(schema)) throw 'Schema must be a valid object or left undefined for an empty object.'; - - options.provider = this._checkProvider(options.provider || this.client.options.providers.default); - const provider = this.client.providers.get(options.provider); - if (provider.cache) throw `The provider ${provider.name} is designed for caching, not persistent data. Please try again with another.`; - - const gateway = new Gateway(this, name, schema, options); - this.keys.add(name); - this[name] = gateway; + if (!this.ready) { + if (this._queue.has(name)) throw new Error(`There is already a Gateway with the name '${name}'.`); + this._queue.set(name, () => { + this._register(name, schema, options); + this._queue.delete(name); + }); + } else { + this._register(name, schema, options); + } - return gateway; + return this; } /** @@ -221,7 +230,7 @@ class GatewayDriver { * GatewayDriver.add('users', schema); */ async add(name, schema = {}, options = {}, download = true) { - const gateway = this.register(name, schema, options); + const gateway = this._register(name, schema, options); await gateway.init(download); return gateway; @@ -234,17 +243,44 @@ class GatewayDriver { * @private */ async _ready() { - if (this.ready) throw 'Configuration has already run the ready method.'; + if (this.ready) throw new Error('Configuration has already run the ready method.'); this.ready = true; const promises = []; - for (const cache of this.caches) { + for (const register of this._queue.values()) register(); + for (const key of this.keys) { // If the gateway did not init yet, init it now - if (!this[cache].ready) await this[cache].init(); - promises.push(this[cache]._ready()); + if (!this[key].ready) await this[key].init(); + promises.push(this[key]._ready()); } return Promise.all(promises); } + /** + * Registers a new Gateway + * @since 0.5.0 + * @param {string} name The name for the new gateway + * @param {Object} schema The schema for use in this gateway + * @param {GatewayDriverAddOptions} options The options for the new gateway + * @returns {Gateway} + * @private + */ + _register(name, schema, options) { + if (typeof name !== 'string') throw new Error('You must pass a name for your new gateway and it must be a string.'); + + if (this[name] !== undefined && this[name] !== null) throw new Error(`There is already a Gateway with the name '${name}'.`); + if (!this.client.methods.util.isObject(schema)) throw new Error('Schema must be a valid object or left undefined for an empty object.'); + + options.provider = this._checkProvider(options.provider || this.client.options.providers.default); + const provider = this.client.providers.get(options.provider); + if (provider.cache) throw new Error(`The provider ${provider.name} is designed for caching, not persistent data. Please try again with another.`); + + const gateway = new Gateway(this, name, schema, options); + this.keys.add(name); + this[name] = gateway; + + return gateway; + } + /** * Check if a provider exists. * @since 0.5.0 @@ -254,7 +290,7 @@ class GatewayDriver { */ _checkProvider(engine) { if (this.client.providers.has(engine)) return engine; - throw `This provider (${engine}) does not exist in your system.`; + throw new Error(`This provider (${engine}) does not exist in your system.`); } /** diff --git a/typings/index.d.ts b/typings/index.d.ts index dafb2bd1a4..6f5f807d40 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -581,6 +581,7 @@ declare module 'klasa' { public types: Set; public keys: Set; public ready: boolean; + private _queue: Map Gateway)>; public readonly guildsSchema: { prefix: SchemaPieceJSON, @@ -599,8 +600,9 @@ declare module 'klasa' { public users: Gateway; public clientStorage: Gateway; - public register(name: string, schema?: object, options?: GatewayDriverAddOptions): Gateway; + public register(name: string, schema?: object, options?: GatewayDriverAddOptions): this; public add(name: string, schema?: object, options?: GatewayDriverAddOptions, download?: boolean): Promise; + private _register(name: string, schema?: object, options?: GatewayDriverAddOptions): Gateway; private _ready(): Promise>>>; private _checkProvider(engine: string): string; From 80209f130c53f2e0097886a9643a09b541b54b4b Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 10 Feb 2018 03:14:22 +0100 Subject: [PATCH 17/76] Removed the abstract method `resolveString()` from SchemaFolder and SchemaPiece. --- CHANGELOG.md | 1 + src/lib/settings/Configuration.js | 42 +++++++++++++++++++++++++- src/lib/settings/SchemaFolder.js | 8 ----- src/lib/settings/SchemaPiece.js | 50 ------------------------------- typings/index.d.ts | 3 +- 5 files changed, 43 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddce7b6388..4c8e7a81da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,6 +141,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Removed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the abstract method `resolveString()` from SchemaFolder and SchemaPiece. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed `SchemaFolder#getList` and replaced it to `Configuration#list`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the `ConfigUpdateEntryMany` typedef in favor of a more constant type. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the resolver functions from constants. (kyranet) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 70c6dc11dd..24fe91b093 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -1,5 +1,6 @@ const { isObject, makeObject, deepClone, tryParse, getIdentifier, toTitleCase } = require('../util/util'); const SchemaFolder = require('./SchemaFolder'); +const SchemaPiece = require('./SchemaPiece'); /** * Creating your own Configuration instances if often discouraged and unneeded. SettingGateway handles them internally for you. @@ -284,13 +285,52 @@ class Configuration { for (const keyType of keysTypes.sort()) { keys[keyType].sort(); array.push(`= ${toTitleCase(keyType)}s =`); - for (const key of keys[keyType]) array.push(`${key.padEnd(longest)} :: ${folder[key].resolveString(msg)}`); + for (const key of keys[keyType]) array.push(`${key.padEnd(longest)} :: ${this._resolveString(msg, folder[key])}`); array.push(''); } } return array.join('\n'); } + /** + * Resolve a string. + * @since 0.5.0 + * @param {KlasaMessage} msg The Message to use + * @param {(SchemaPiece|string)} path The path to resolve + * @returns {string} + * @private + */ + _resolveString(msg, path) { + const piece = path instanceof SchemaPiece ? path : this.gateway.getPath(path, { piece: true }).path; + const value = this.get(piece.path); + if (value === null) return 'Not set'; + if (piece.array && value.length === 0) return 'None'; + + let resolver; + switch (this.type) { + case 'Folder': resolver = () => 'Folder'; + break; + case 'user': resolver = (val) => (this.client.users.get(val) || { username: (val && val.username) || val }).username; + break; + case 'categorychannel': + case 'textchannel': + case 'voicechannel': + case 'channel': resolver = (val) => (msg.guild.channels.get(val) || { name: (val && val.name) || val }).name; + break; + case 'role': resolver = (val) => (msg.guild.roles.get(val) || { name: (val && val.name) || val }).name; + break; + case 'guild': resolver = (val) => (val && val.name) || val; + break; + case 'boolean': resolver = (val) => val ? 'Enabled' : 'Disabled'; + break; + default: + resolver = (val) => val; + } + + if (this.array) return `[ ${value.map(resolver).join(' | ')} ]`; + return resolver(value); + } + /** * Update multiple keys given a JSON object. * @since 0.5.0 diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 98949b3122..95cccc02b0 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -282,14 +282,6 @@ class SchemaFolder extends Schema { return array; } - /** - * @since 0.5.0 - * @returns {string} - */ - resolveString() { - return this.toString(); - } - /** * Add a key to the instance. * @since 0.5.0 diff --git a/src/lib/settings/SchemaPiece.js b/src/lib/settings/SchemaPiece.js index 498f9eddfd..f519bdd3b9 100644 --- a/src/lib/settings/SchemaPiece.js +++ b/src/lib/settings/SchemaPiece.js @@ -134,39 +134,6 @@ class SchemaPiece extends Schema { return resolved; } - /** - * Resolve a string. - * @since 0.5.0 - * @param {KlasaMessage} msg The Message to use - * @returns {string} - */ - resolveString(msg) { - const value = this.constructor._resolveConfigs(this.gateway.type, msg).get(this.path); - if (value === null) return 'Not set'; - - let resolver = (val) => val; - switch (this.type) { - case 'Folder': resolver = () => 'Folder'; - break; - case 'user': resolver = (val) => (this.client.users.get(val) || { username: val && val.username ? val.username : val }).username; - break; - case 'textchannel': - case 'voicechannel': - case 'channel': resolver = (val) => (msg.guild.channels.get(val) || { name: val && val.name ? val.name : val }).name; - break; - case 'role': resolver = (val) => (msg.guild.roles.get(val) || { name: val && val.name ? val.name : val }).name; - break; - case 'guild': resolver = (val) => val && val.name ? val.name : val; - break; - case 'boolean': resolver = (val) => val === true ? 'Enabled' : 'Disabled'; - break; - // no default - } - - if (this.array && Array.isArray(value)) return value.length > 0 ? `[ ${value.map(resolver).join(' | ')} ]` : 'None'; - return resolver(value); - } - /** * Modify this SchemaPiece's properties. * @since 0.5.0 @@ -373,23 +340,6 @@ class SchemaPiece extends Schema { return ''; } - /** - * Gets a configuration instance from KlasaMessage depending on the schema.gateway type. - * @since 0.5.0 - * @param {string} type The type of gateway - * @param {KlasaMessage} msg The message context to resolve from - * @returns {Configuration} - * @private - */ - static _resolveConfigs(type, msg) { - switch (type) { - case 'users': return msg.author.configs; - case 'guilds': return msg.guildConfigs; - case 'clientStorage': return msg.client.configs; - default: return null; - } - } - } module.exports = SchemaPiece; diff --git a/typings/index.d.ts b/typings/index.d.ts index 6f5f807d40..2715e617bc 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -532,6 +532,7 @@ declare module 'klasa' { public update(key: string, value?: any, guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; public list(msg: KlasaMessage, path: SchemaFolder | string): string; + private _resolveString(msg: KlasaMessage, path: SchemaPiece | string): string; private _updateMany(object: any, guild?: GatewayGuildResolvable): Promise; private _reset(key: string, guild: GatewayGuildResolvable, avoidUnconfigurable: boolean): Promise; private _parseReset(key: string, guild: KlasaGuild, options: ConfigurationPathResult): Promise; @@ -661,7 +662,6 @@ declare module 'klasa' { public getSQL(array?: string[]): string[]; public getAllKeys(array?: string[]): string[]; public getAllValues(array?: SchemaPiece[]): SchemaPiece[]; - public resolveString(): string; private _addKey(key: string, options: SchemaFolderAddOptions, type: typeof Schema | typeof SchemaFolder): void; private _removeKey(key: string): void; @@ -689,7 +689,6 @@ declare module 'klasa' { public setValidator(fn: Function): this; public parse(value: any, guild: KlasaGuild): Promise; - public resolveString(msg: KlasaMessage): string; public modify(options: SchemaPieceEditOptions): Promise; private _schemaCheckType(type: string): void; From 8584951f4061b3afc55280dae9b795cea77aea6b Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 10 Feb 2018 03:20:59 +0100 Subject: [PATCH 18/76] Fixed path.resolveString --- src/commands/Admin/conf.js | 8 ++++---- src/commands/General/User Configs/userconf.js | 8 ++++---- src/lib/settings/Configuration.js | 4 ++-- typings/index.d.ts | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/commands/Admin/conf.js b/src/commands/Admin/conf.js index a9ca7acd81..5e3fc4ad73 100644 --- a/src/commands/Admin/conf.js +++ b/src/commands/Admin/conf.js @@ -26,22 +26,22 @@ module.exports = class extends Command { get(msg, [key]) { const { path } = this.client.gateways.guilds.getPath(key, { avoidUnconfigurable: true, piece: true }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_GET', path.path, path.resolveString(msg))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_GET', path.path, msg.guild.configs.resolveString(msg, path))); } async set(msg, [key, ...valueToSet]) { const { path } = await msg.guild.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, path.resolveString(msg))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, msg.guild.configs.resolveString(msg, path))); } async remove(msg, [key, ...valueToRemove]) { const { path } = await msg.guild.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, path.resolveString(msg))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, msg.guild.configs.resolveString(msg, path))); } async reset(msg, [key]) { const { path } = await msg.guild.configs.reset(key, true); - return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', path.path, path.resolveString(msg))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', path.path, msg.guild.configs.resolveString(msg, path))); } list(msg, [key]) { diff --git a/src/commands/General/User Configs/userconf.js b/src/commands/General/User Configs/userconf.js index 2c7ed7e5c5..b67aeec0c4 100644 --- a/src/commands/General/User Configs/userconf.js +++ b/src/commands/General/User Configs/userconf.js @@ -24,22 +24,22 @@ module.exports = class extends Command { get(msg, [key]) { const { path } = this.client.gateways.users.getPath(key, { avoidUnconfigurable: true, piece: true }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_GET', path.path, path.resolveString(msg))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_GET', path.path, msg.author.configs.resolveString(msg, path))); } async set(msg, [key, ...valueToSet]) { const { path } = await msg.author.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, path.resolveString(msg))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, msg.author.configs.resolveString(msg, path))); } async remove(msg, [key, ...valueToRemove]) { const { path } = await msg.author.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, path.resolveString(msg))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, msg.author.configs.resolveString(msg, path))); } async reset(msg, [key]) { const { path } = await msg.author.configs.reset(key, true); - return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', path.path, path.resolveString(msg))); + return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', path.path, msg.author.configs.resolveString(msg, path))); } list(msg, [key]) { diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 24fe91b093..1242a17f2a 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -285,7 +285,7 @@ class Configuration { for (const keyType of keysTypes.sort()) { keys[keyType].sort(); array.push(`= ${toTitleCase(keyType)}s =`); - for (const key of keys[keyType]) array.push(`${key.padEnd(longest)} :: ${this._resolveString(msg, folder[key])}`); + for (const key of keys[keyType]) array.push(`${key.padEnd(longest)} :: ${this.resolveString(msg, folder[key])}`); array.push(''); } } @@ -300,7 +300,7 @@ class Configuration { * @returns {string} * @private */ - _resolveString(msg, path) { + resolveString(msg, path) { const piece = path instanceof SchemaPiece ? path : this.gateway.getPath(path, { piece: true }).path; const value = this.get(piece.path); if (value === null) return 'Not set'; diff --git a/typings/index.d.ts b/typings/index.d.ts index 2715e617bc..5f14c39c2d 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -531,8 +531,8 @@ declare module 'klasa' { public update(key: string, value?: any, options?: ConfigurationUpdateOptions): Promise; public update(key: string, value?: any, guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; public list(msg: KlasaMessage, path: SchemaFolder | string): string; + public resolveString(msg: KlasaMessage, path: SchemaPiece | string): string; - private _resolveString(msg: KlasaMessage, path: SchemaPiece | string): string; private _updateMany(object: any, guild?: GatewayGuildResolvable): Promise; private _reset(key: string, guild: GatewayGuildResolvable, avoidUnconfigurable: boolean): Promise; private _parseReset(key: string, guild: KlasaGuild, options: ConfigurationPathResult): Promise; @@ -1740,7 +1740,7 @@ declare module 'klasa' { export type SchemaFolderJSON = { type: 'Folder'; - [k: string]: SchemaPieceJSON | SchemaFolderJSON; + [k: string]: SchemaPieceJSON | SchemaFolderJSON | string; }; export type GatewayDriverJSON = { @@ -1750,7 +1750,7 @@ declare module 'klasa' { guilds: GatewayJSON; users: GatewayJSON; clientStorage: GatewayJSON; - [k: string]: GatewayJSON; + [k: string]: GatewayJSON | any; }; // Structures From 4bd6f4c77b3d6e3bdf789f77bb9f2defe1714499 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 10 Feb 2018 03:23:35 +0100 Subject: [PATCH 19/76] whoops --- src/lib/settings/Configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 1242a17f2a..19b9864f0b 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -327,7 +327,7 @@ class Configuration { resolver = (val) => val; } - if (this.array) return `[ ${value.map(resolver).join(' | ')} ]`; + if (piece.array) return `[ ${value.map(resolver).join(' | ')} ]`; return resolver(value); } From 77fb2e4e0140ba6d210cd02f241f916451fd92b1 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 10 Feb 2018 03:33:19 +0100 Subject: [PATCH 20/76] Fixed disabledCommands' validator --- src/lib/Client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/Client.js b/src/lib/Client.js index 29f4d2f2eb..7b817b6166 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -416,8 +416,9 @@ class KlasaClient extends Discord.Client { } if (this.gateways.guilds.schema.hasKey('disabledCommands')) { const languageStore = this.languages; + const commandStore = this.commands; this.gateways.guilds.schema.disabledCommands.setValidator(function (command, guild) { // eslint-disable-line - if (command && command.guarded) throw (guild ? guild.language : languageStore.default).language.get('COMMAND_CONF_GUARDED', command.name); + if ((cmd => cmd && cmd.guarded)(commandStore.get(command))) throw (guild ? guild.language : languageStore.default).get('COMMAND_CONF_GUARDED', command); }); } From 1a7a8d4074ed17a13e28ec6e1e4041a0a3bf9404 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 10 Feb 2018 21:06:19 +0100 Subject: [PATCH 21/76] Updated typings --- typings/index.d.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/typings/index.d.ts b/typings/index.d.ts index 5f14c39c2d..0618303bc0 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -758,6 +758,8 @@ declare module 'klasa' { public disable(): Piece; public reload(): Promise; public unload(): any; + + public toJSON(): object; public toString(): string; } @@ -780,6 +782,8 @@ declare module 'klasa' { public disable(): Piece; public reload(): Promise; public unload(): any; + + public toJSON(): object; public toString(): string; } @@ -803,6 +807,8 @@ declare module 'klasa' { public disable(): Piece; public reload(): Promise; public unload(): any; + + public toJSON(): object; public toString(): string; } @@ -823,6 +829,8 @@ declare module 'klasa' { public disable(): Piece; public reload(): Promise; public unload(): any; + + public toJSON(): object; public toString(): string; } @@ -843,6 +851,8 @@ declare module 'klasa' { public disable(): Piece; public reload(): Promise; public unload(): any; + + public toJSON(): object; public toString(): string; } @@ -864,6 +874,8 @@ declare module 'klasa' { public disable(): Piece; public reload(): Promise; public unload(): any; + + public toJSON(): object; public toString(): string; } @@ -887,6 +899,9 @@ declare module 'klasa' { public disable(): Piece; public reload(): Promise; public unload(): any; + + public shouldRun(msg: KlasaMessage): boolean; + public toJSON(): object; public toString(): string; } @@ -911,6 +926,8 @@ declare module 'klasa' { public disable(): Piece; public reload(): Promise; public unload(): any; + + public toJSON(): object; public toString(): string; } @@ -931,6 +948,8 @@ declare module 'klasa' { public disable(): Piece; public reload(): Promise; public unload(): any; + + public toJSON(): object; public toString(): string; } From 6b54a740cbf2b6882ec8866bce6aaa6e4efebff5 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 10 Feb 2018 21:53:42 +0100 Subject: [PATCH 22/76] Fixed Configuration#resolveString not parsing the keys correctly --- src/lib/settings/Configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 19b9864f0b..a8a5cd1b25 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -307,7 +307,7 @@ class Configuration { if (piece.array && value.length === 0) return 'None'; let resolver; - switch (this.type) { + switch (piece.type) { case 'Folder': resolver = () => 'Folder'; break; case 'user': resolver = (val) => (this.client.users.get(val) || { username: (val && val.username) || val }).username; From 2ae8c118e655592ae201a409e96727c8b40be7cb Mon Sep 17 00:00:00 2001 From: kyraNET Date: Wed, 14 Feb 2018 20:23:30 +0100 Subject: [PATCH 23/76] removeKey/Folder -> remove, hasKey -> has, improvements, code reduction --- CHANGELOG.md | 3 +- .../UnderstandingSchemaFolders.md | 22 +-- src/lib/Client.js | 2 +- src/lib/schedule/Schedule.js | 2 +- src/lib/settings/Configuration.js | 8 +- src/lib/settings/Gateway.js | 2 +- src/lib/settings/SchemaFolder.js | 135 +++++++----------- src/lib/settings/SchemaPiece.js | 6 +- typings/index.d.ts | 19 ++- 9 files changed, 79 insertions(+), 120 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c8e7a81da..05d861ff87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,7 +88,7 @@ NOTE: For the contributors, you add new entries to this document following this - [[#179](https://github.com/dirigeants/klasa/pull/179)] Changed the type for `GatewayDriver#types` from `string[]` to `Set`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Renamed `SchemaPiece#modify()` to `SchemaPiece#edit()`. (kyranet) -- [[#179](https://github.com/dirigeants/klasa/pull/179)] Renamed `Gateway#getKeys()` and `Gateway#getValues()` to `Gateway#getAllKeys()` and `Gateway#getAllValues()` respectively. (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Renamed `Gateway#getKeys()` and `Gateway#getValues()` to `Gateway#keys(true)` and `Gateway#values(true)` respectively, which return iterators. (kyranet) - [[#176](https://github.com/dirigeants/klasa/pull/176)] Marked several constructors as private (singleton, abstract or discouraged). (kyranet) - [[#162](https://github.com/dirigeants/klasa/pull/162)] Modified the built-in conf command to use `dependant arguments`-like arguments using custom arguments and messages. (bdistin) - [[#162](https://github.com/dirigeants/klasa/pull/162)] **[BREAKING]** Refactored ArgResolver. Now they take `arg`, `possible` and `msg` as parameters instead of `arg`, `currentUsage`, `possible`, `repeat` and `msg`. Repeating handling is now done in the backends. (bdistin) @@ -141,6 +141,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Removed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] **[BREAKING]** Removed `SchemaFolder#removeKey` and `SchemaFolder#removeFolder` in favor to a more consistent `Schema#remove`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the abstract method `resolveString()` from SchemaFolder and SchemaPiece. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed `SchemaFolder#getList` and replaced it to `Configuration#list`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the `ConfigUpdateEntryMany` typedef in favor of a more constant type. (kyranet) diff --git a/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md b/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md index 4aa0c4c6e1..94159c1c87 100644 --- a/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md +++ b/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md @@ -57,16 +57,16 @@ msg.guild.configs.userBlacklist; ## Removing keys -Removing keys with the schema is quite easy, as you would have access to the {@link SchemaFolder} that holds it and remove it by its name (remember that `force` is optional and defaults to `true`) using {@link SchemaFolder#removeKey} as in the following example: +Removing keys with the schema is quite easy, as you would have access to the {@link SchemaFolder} that holds it and remove it by its name (remember that `force` is optional and defaults to `true`) using {@link SchemaFolder#remove} as in the following example: ```javascript -this.client.gateways.gatewayName.schema.removeKey(name, force); +this.client.gateways.gatewayName.schema.remove(name, force); ``` In case you have a key you do not longer use and you want to get rid of it, for example, the recently created **userBlacklist** key for guild configs, you would run the following code: ```javascript -this.client.gateways.guilds.schema.removeKey('userBlacklist'); +this.client.gateways.guilds.schema.remove('userBlacklist'); ``` And the property `userBlacklist` for all guild configs will be deleted. @@ -122,19 +122,7 @@ async function init() { ## Removing folders -It's exactly the same as {@link SchemaFolder#removeKey}, but using {@link SchemaFolder#removeFolder} instead. With the following syntax: - -```javascript -this.client.gateways.gatewayName.schema.removeFolder(name, force); -``` - -To remove a folder, like the aforementioned **channels** folder, you would run the following code: - -```javascript -this.client.gateways.guilds.schema.removeFolder('channels'); -``` - -This will remove all the data from all the sub-keys and sub-folders, even very nested ones. +Removing folders is the same as removing keys, check {@link SchemaFolder#remove}, the difference is that, while removing a key will remove one value from the schema, removing a folder will remove it with all its nested keys and folders, even very nested ones. ## Ensuring the existence of a key. @@ -144,7 +132,7 @@ In [klasa pieces](https://github.com/dirigeants/klasa-pieces/) specially, some p async function init() { const { schema } = this.client.gateways.guilds; - if (!schema.hasKey('modlog')) { + if (!schema.has('modlog')) { await schema.addKey('modlog', { type: 'TextChannel' }); } } diff --git a/src/lib/Client.js b/src/lib/Client.js index 7b817b6166..5c8b3d0374 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -414,7 +414,7 @@ class KlasaClient extends Discord.Client { if (typeof this.options.prefix === 'string' && this.options.prefix !== this.gateways.guilds.schema.prefix.default) { await this.gateways.guilds.schema.prefix.edit({ default: this.options.prefix }); } - if (this.gateways.guilds.schema.hasKey('disabledCommands')) { + if (this.gateways.guilds.schema.has('disabledCommands')) { const languageStore = this.languages; const commandStore = this.commands; this.gateways.guilds.schema.disabledCommands.setValidator(function (command, guild) { // eslint-disable-line diff --git a/src/lib/schedule/Schedule.js b/src/lib/schedule/Schedule.js index 18f86405e0..8f32978fb6 100644 --- a/src/lib/schedule/Schedule.js +++ b/src/lib/schedule/Schedule.js @@ -65,7 +65,7 @@ class Schedule { */ async init() { const { schema } = this.client.gateways.clientStorage; - if (!schema.hasKey('schedules')) { + if (!schema.has('schedules')) { await schema.addKey('schedules', { type: 'any', default: [], diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index a8a5cd1b25..ac64233d37 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -141,13 +141,13 @@ class Configuration { * @returns {*} */ get(key) { - if (!key.includes('.')) return this.gateway.schema.hasKey(key) ? this[key] : undefined; + if (!key.includes('.')) return this.gateway.schema.has(key) ? this[key] : undefined; const path = key.split('.'); let refSetting = this; // eslint-disable-line consistent-this let refSchema = this.gateway.schema; for (const currKey of path) { - if (refSchema.type !== 'Folder' || !refSchema.hasKey(currKey)) return undefined; + if (refSchema.type !== 'Folder' || !refSchema.has(currKey)) return undefined; refSetting = refSetting[currKey]; refSchema = refSchema[currKey]; } @@ -171,7 +171,7 @@ class Configuration { */ async resetConfiguration() { if (this._existsInDB) await this.gateway.provider.delete(this.gateway.type, this.id); - for (const key of this.gateway.schema.keyArray) this[key] = Configuration._merge(undefined, this.gateway.schema[key]); + for (const [key, value] of this.gateway.schema) this[key] = Configuration._merge(undefined, value); return this; } @@ -500,7 +500,7 @@ class Configuration { */ _parseUpdateMany(cache, object, schema, guild, list, updateObject) { for (const key of Object.keys(object)) { - if (!schema.hasKey(key)) continue; + if (!schema.has(key)) continue; // Check if it's a folder, and recursively iterate over it if (schema[key].type === 'Folder') { if (!(key in updateObject)) updateObject = updateObject[key] = {}; diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index 2a371e48ae..a73ed9df8e 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -227,7 +227,7 @@ class Gateway extends GatewayStorage { for (let i = 0; i < route.length; i++) { const currKey = route[i]; - if (typeof path[currKey] === 'undefined' || !path.hasKey(currKey)) throw `The key ${route.slice(0, i + 1).join('.')} does not exist in the current schema.`; + if (typeof path[currKey] === 'undefined' || !path.has(currKey)) throw `The key ${route.slice(0, i + 1).join('.')} does not exist in the current schema.`; if (path[currKey].type === 'Folder') { path = path[currKey]; diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 95cccc02b0..3b4a69cfed 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -80,10 +80,10 @@ class SchemaFolder extends Schema { * @returns {Promise} */ async addFolder(key, object = {}, force = true) { - if (this.hasKey(key)) throw `The key ${key} already exists in the current schema.`; + if (this.has(key)) throw `The key ${key} already exists in the current schema.`; if (typeof this[key] !== 'undefined') throw `The key ${key} conflicts with a property of Schema.`; - const folder = this._addKey(key, object, SchemaFolder); + const folder = this._add(key, object, SchemaFolder); await fs.outputJSONAtomic(this.gateway.filePath, this.gateway.schema.toJSON()); if (this.gateway.sql) { @@ -101,31 +101,33 @@ class SchemaFolder extends Schema { } /** - * Remove a nested folder. + * Remove a key * @since 0.5.0 - * @param {string} key The folder's name to remove + * @param {string} key The key's name to remove * @param {boolean} [force=true] Whether this function call should modify all entries from the database * @returns {Promise} */ - async removeFolder(key, force = true) { - if (this.hasKey(key) === false) throw new Error(`The key ${key} does not exist in the current schema.`); - if (this[key].type !== 'Folder') throw new Error(`The key ${key} is not Folder type.`); + async remove(key, force = true) { + if (!this.has(key)) throw new Error(`The key ${key} does not exist in the current schema.`); - const folder = this[key]; - this._removeKey(key); + // Get the key, remove it from the configs and update the persistent schema + const piece = this[key]; + this._remove(key); await fs.outputJSONAtomic(this.gateway.filePath, this.gateway.schema.toJSON()); + // A SQL database has the advantage of being able to update all keys along the schema, so force is ignored if (this.gateway.sql) { - if (folder.keyArray.length > 0) { - if (typeof this.gateway.provider.removeColumn === 'function') await this.gateway.provider.removeColumn(this.gateway.type, folder.getKeys()); - else throw new Error('The method \'removeColumn\' in your provider is required in order to remove columns.'); + if (piece.type !== 'Folder' || (piece.type === 'Folder' && piece.keyArray.length > 0)) { + await this.gateway.provider.removeColumn(this.gateway.type, piece.type === 'Folder' ? + [...piece.keys(true)] : key); } } else if (force || this.gateway.type === 'clientStorage') { - await this.force('delete', key, folder); + // If force, or if the gateway is clientStorage, it should update all entries + await this.force('delete', key, piece); } - await this._shardSyncSchema(folder, 'delete', force); - if (this.client.listenerCount('schemaKeyRemove')) this.client.emit('schemaKeyRemove', folder); + await this._shardSyncSchema(piece, 'delete', force); + if (this.client.listenerCount('schemaKeyRemove')) this.client.emit('schemaKeyRemove', piece); return this.gateway.schema; } @@ -135,7 +137,7 @@ class SchemaFolder extends Schema { * @param {string} key The key to check * @returns {boolean} */ - hasKey(key) { + has(key) { return this.keyArray.includes(key); } @@ -148,7 +150,7 @@ class SchemaFolder extends Schema { * @returns {Promise} */ async addKey(key, options, force = true) { - this._addKey(key, this._verifyKeyOptions(key, options), SchemaPiece); + this._add(key, this._verifyKeyOptions(key, options), SchemaPiece); await fs.outputJSONAtomic(this.gateway.filePath, this.gateway.schema.toJSON()); if (this.gateway.sql) { @@ -163,31 +165,6 @@ class SchemaFolder extends Schema { return this.gateway.schema; } - /** - * Remove a key from this folder. - * @since 0.5.0 - * @param {string} key The key's name to remove - * @param {boolean} [force=true] Whether this function call should modify all entries from the database - * @returns {Promise} - */ - async removeKey(key, force = true) { - if (this.hasKey(key) === false) throw `The key ${key} does not exist in the current schema.`; - const schemaPiece = this[key]; - this._removeKey(key); - await fs.outputJSONAtomic(this.gateway.filePath, this.gateway.schema.toJSON()); - - if (this.gateway.sql) { - if (typeof this.gateway.provider.removeColumn === 'function') await this.gateway.provider.removeColumn(this.gateway.type, key); - else throw new Error('The method \'removeColumn\' in your provider is required in order to remove columns.'); - } else if (force) { - await this.force('delete', key, schemaPiece); - } - - await this._shardSyncSchema(schemaPiece, 'delete', force); - if (this.client.listenerCount('schemaKeyRemove')) this.client.emit('schemaKeyRemove', schemaPiece); - return this.gateway.schema; - } - /** * Modifies all entries from the database. * @since 0.5.0 @@ -254,34 +231,6 @@ class SchemaFolder extends Schema { return array; } - /** - * Get all the pathes from this schema's children. - * @since 0.5.0 - * @returns {string[]} - */ - getAllKeys() { - const array = []; - for (const value of this.values()) { - if (value.type === 'Folder') array.push(...value.keyArray()); - else array.push(value.path); - } - return array; - } - - /** - * Get all the SchemaPieces instances from this schema's children. Used for SQL. - * @since 0.5.0 - * @returns {SchemaPiece[]} - */ - getAllValues() { - const array = []; - for (const value of this.values()) { - if (value.type === 'Folder') array.push(...value.getAllValues()); - else array.push(value); - } - return array; - } - /** * Add a key to the instance. * @since 0.5.0 @@ -291,8 +240,8 @@ class SchemaFolder extends Schema { * @returns {(SchemaFolder|SchemaPiece)} * @private */ - _addKey(key, options, Piece) { - if (this.hasKey(key)) throw new Error(`The key '${key}' already exists.`); + _add(key, options, Piece) { + if (this.has(key)) throw new Error(`The key '${key}' already exists.`); const piece = new Piece(this.client, this.gateway, options, this, key); this[key] = piece; this.defaults[key] = piece.type === 'Folder' ? piece.defaults : options.default; @@ -309,7 +258,7 @@ class SchemaFolder extends Schema { * @param {string} key The name of the key * @private */ - _removeKey(key) { + _remove(key) { const index = this.keyArray.indexOf(key); if (index === -1) throw new Error(`The key '${key}' does not exist.`); @@ -341,11 +290,11 @@ class SchemaFolder extends Schema { * @since 0.5.0 * @param {string} key The name for the key * @param {SchemaFolderAddOptions} options The key's options to apply - * @returns {addOptions} + * @returns {SchemaFolderAddOptions} * @private */ _verifyKeyOptions(key, options) { - if (this.hasKey(key)) throw `The key ${key} already exists in the current schema.`; + if (this.has(key)) throw `The key ${key} already exists in the current schema.`; if (typeof this[key] !== 'undefined') throw `The key ${key} conflicts with a property of Schema.`; if (!options) throw 'You must pass an options argument to this method.'; if (typeof options.type !== 'string') throw 'The option type is required and must be a string.'; @@ -400,30 +349,54 @@ class SchemaFolder extends Schema { * Returns a new Iterator object that contains the `[key, value]` pairs for each element contained in this folder. * Identical to [Map.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries) * @since 0.5.0 + * @param {boolean} recursive Whether the iteration should be recursive * @yields {Array} */ - *entries() { - for (const key of this.keyArray) yield [key, this[key]]; + *entries(recursive = false) { + if (recursive) { + for (const key of this.keyArray) { + if (this[key].type === 'Folder') yield* this[key].entries(true); + else yield [key, this[key]]; + } + } else { + for (const key of this.keyArray) yield [key, this[key]]; + } } /** * Returns a new Iterator object that contains the values for each element contained in this folder. * Identical to [Map.values()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/values) * @since 0.5.0 + * @param {boolean} recursive Whether the iteration should be recursive * @yields {(SchemaFolder|SchemaPiece)} */ - *values() { - for (const key of this.keyArray) yield this[key]; + *values(recursive = false) { + if (recursive) { + for (const key of this.keyArray) { + if (this[key].type === 'Folder') yield* this[key].values(true); + else yield this[key]; + } + } else { + for (const key of this.keyArray) yield this[key]; + } } /** * Returns a new Iterator object that contains the keys for each element contained in this folder. * Identical to [Map.keys()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys) * @since 0.5.0 + * @param {boolean} recursive Whether the iteration should be recursive * @yields {string} */ - *keys() { - for (const key of this.keyArray) yield key; + *keys(recursive = false) { + if (recursive) { + for (const key of this.keyArray) { + if (this[key].type === 'Folder') yield* this[key].keys(true); + else yield key; + } + } else { + for (const key of this.keyArray) yield key; + } } /** diff --git a/src/lib/settings/SchemaPiece.js b/src/lib/settings/SchemaPiece.js index f519bdd3b9..6fb974972f 100644 --- a/src/lib/settings/SchemaPiece.js +++ b/src/lib/settings/SchemaPiece.js @@ -34,7 +34,7 @@ class SchemaPiece extends Schema { * @since 0.5.0 * @param {KlasaClient} client The client which initialized this instance * @param {Gateway} gateway The Gateway that manages this schema instance - * @param {AddOptions} options The object containing the properties for this schema instance + * @param {SchemaFolderAddOptions} options The object containing the properties for this schema instance * @param {SchemaFolder} parent The parent which holds this instance * @param {string} key The name of the key */ @@ -209,7 +209,7 @@ class SchemaPiece extends Schema { /** * Checks if options.default is valid. * @since 0.5.0 - * @param {AddOptions} options The options to validate + * @param {SchemaFolderAddOptions} options The options to validate * @throws {TypeError} * @private */ @@ -278,7 +278,7 @@ class SchemaPiece extends Schema { /** * Check if the key is properly configured. * @since 0.5.0 - * @param {AddOptions} options The options to parse + * @param {SchemaFolderAddOptions} options The options to parse * @returns {true} * @throws {TypeError} * @private diff --git a/typings/index.d.ts b/typings/index.d.ts index 0618303bc0..1177348d5f 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -652,24 +652,21 @@ declare module 'klasa' { public readonly configurableKeys: string[]; - public addFolder(key: string, object?: object, force?: boolean): Promise; - public removeFolder(key: string, force?: boolean): Promise; - public hasKey(key: string): boolean; public addKey(key: string, options: SchemaFolderAddOptions, force?: boolean): Promise; - public removeKey(key: string, force?: boolean): Promise; + public addFolder(key: string, object?: object, force?: boolean): Promise; + public has(key: string): boolean; + public remove(key: string, force?: boolean): Promise; public force(action: 'add' | 'edit' | 'delete', key: string, piece: SchemaFolder | SchemaPiece): Promise; public getDefaults(data?: object): object; public getSQL(array?: string[]): string[]; - public getAllKeys(array?: string[]): string[]; - public getAllValues(array?: SchemaPiece[]): SchemaPiece[]; - private _addKey(key: string, options: SchemaFolderAddOptions, type: typeof Schema | typeof SchemaFolder): void; - private _removeKey(key: string): void; + private _add(key: string, options: SchemaFolderAddOptions, type: typeof Schema | typeof SchemaFolder): void; + private _remove(key: string): void; private _init(options: object): true; - public entries(): Iterator<[string, SchemaFolder | SchemaPiece]>; - public values(): Iterator; - public keys(): Iterator; + public entries(recursive?: boolean): Iterator<[string, SchemaFolder | SchemaPiece]>; + public values(recursive?: boolean): Iterator; + public keys(recursive?: boolean): Iterator; public [Symbol.iterator](): Iterator<[string, SchemaFolder | SchemaPiece]>; public toJSON(): any; From baae345b6d273bd3f6b5bf2c858d0d59f9403c0d Mon Sep 17 00:00:00 2001 From: kyraNET Date: Wed, 14 Feb 2018 22:04:03 +0100 Subject: [PATCH 24/76] More memory improvements, bugfixes --- src/events/configUpdateEntry.js | 4 ++-- src/lib/Client.js | 4 ++-- src/lib/settings/Configuration.js | 9 --------- src/lib/settings/Gateway.js | 6 +++--- typings/index.d.ts | 1 - 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/events/configUpdateEntry.js b/src/events/configUpdateEntry.js index ad36b5a139..212a4c30a5 100644 --- a/src/events/configUpdateEntry.js +++ b/src/events/configUpdateEntry.js @@ -4,14 +4,14 @@ module.exports = class extends Event { run(configs) { if (!this.client.shard) return; - if (configs.type === 'users') { + if (configs.gateway.type === 'users') { this.client.shard.broadcastEval(` if (this.shard.id !== ${this.client.shard.id}) { const user = this.users.get('${configs.id}'); if (user) user.configs.sync(); } `); - } else if (configs.type === 'clientStorage') { + } else if (configs.gateway.type === 'clientStorage') { this.client.shard.broadcastEval(` if (this.shard.id !== ${this.client.shard.id}) { this.configs.sync(); diff --git a/src/lib/Client.js b/src/lib/Client.js index 5c8b3d0374..a7c295c8b4 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -437,8 +437,8 @@ class KlasaClient extends Discord.Client { if (!this.options.ownerID) this.options.ownerID = this.user.bot ? this.application.owner.id : this.user.id; // Client-wide settings - this.configs = this.gateways.clientStorage.cache.get('clientStorage', this.user.id) || this.gateways.clientStorage.insertEntry(this.user.id); - await this.configs.sync().then(() => this.gateways.clientStorage.cache.set(this.type, this.user.id, this.configs)); + this.configs = this.gateways.clientStorage.cache.get(this.user.id) || this.gateways.clientStorage.insertEntry(this.user.id); + await this.configs.sync(); // Init all the pieces await Promise.all(this.pieceStores.filter(store => !['providers', 'extendables'].includes(store.name)).map(store => store.init())); diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index ac64233d37..1598b01d68 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -91,15 +91,6 @@ class Configuration { */ Object.defineProperty(this, 'gateway', { value: manager }); - /** - * The type of the Gateway. - * @since 0.5.0 - * @type {string} - * @name Configuration#type - * @readonly - */ - Object.defineProperty(this, 'type', { value: manager.type }); - /** * The ID that identifies this instance. * @since 0.5.0 diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index a73ed9df8e..cdbceb30de 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -113,10 +113,10 @@ class Gateway extends GatewayStorage { getEntry(input, create = false) { if (input === 'default') return this.defaults; if (create) { - const entry = this.cache.get(this.type, input); + const entry = this.cache.get(input); if (!entry) { const configs = new this.Configuration(this, { id: input }); - this.cache.set(this.type, input, configs); + this.cache.set(input, configs); // Silently create a new entry. The new data does not matter as Configuration default all the keys. this.provider.create(this.type, input) .then(() => { @@ -128,7 +128,7 @@ class Gateway extends GatewayStorage { } return entry; } - return this.cache.get(this.type, input) || this.defaults; + return this.cache.get(input) || this.defaults; } /** diff --git a/typings/index.d.ts b/typings/index.d.ts index 1177348d5f..2dcdccabf2 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -515,7 +515,6 @@ declare module 'klasa' { public constructor(manager: Gateway, data: any); public readonly client: KlasaClient; public readonly gateway: Gateway; - public readonly type: string; public readonly id: string; private _existsInDB: boolean; private _syncStatus?: Promise; From 74846a509f1648214a9e0d1435ef373ef57e8689 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Thu, 15 Feb 2018 01:30:08 +0100 Subject: [PATCH 25/76] perf(iterators), removed Configuration#resetConfiguration --- CHANGELOG.md | 2 ++ src/commands/General/Chat Bot Info/help.js | 2 +- src/lib/settings/Configuration.js | 28 +++++++++------------- src/lib/structures/CommandStore.js | 2 +- src/lib/util/util.js | 6 ++--- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05d861ff87..7f8d362c0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,6 +141,8 @@ NOTE: For the contributors, you add new entries to this document following this ### Removed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] **[BREAKING]** Removed `Configuration#resetConfiguration()`. (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] **[PERF-MEM]** Removed `Configuration#type`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] **[BREAKING]** Removed `SchemaFolder#removeKey` and `SchemaFolder#removeFolder` in favor to a more consistent `Schema#remove`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed the abstract method `resolveString()` from SchemaFolder and SchemaPiece. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Removed `SchemaFolder#getList` and replaced it to `Configuration#list`. (kyranet) diff --git a/src/commands/General/Chat Bot Info/help.js b/src/commands/General/Chat Bot Info/help.js index f78286b07f..845eeba987 100644 --- a/src/commands/General/Chat Bot Info/help.js +++ b/src/commands/General/Chat Bot Info/help.js @@ -41,7 +41,7 @@ module.exports = class extends Command { async buildHelp(msg) { const help = {}; - const commandNames = Array.from(this.client.commands.keys()); + const commandNames = [...this.client.commands.keys()]; const longest = commandNames.reduce((long, str) => Math.max(long, str.length), 0); await Promise.all(this.client.commands.map((command) => diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 1598b01d68..af17c988a8 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -155,17 +155,6 @@ class Configuration { return new this.gateway.Configuration(this.gateway, this.gateway.Configuration._clone(this, this.gateway.schema)); } - /** - * Factory resets the current configuration. - * @since 0.5.0 - * @returns {Promise} - */ - async resetConfiguration() { - if (this._existsInDB) await this.gateway.provider.delete(this.gateway.type, this.id); - for (const [key, value] of this.gateway.schema) this[key] = Configuration._merge(undefined, value); - return this; - } - /** * Sync the data from the database with the cache. * @since 0.5.0 @@ -199,15 +188,20 @@ class Configuration { /** * Reset a value from an entry. * @since 0.5.0 - * @param {string} key The key to reset - * @param {boolean} [avoidUnconfigurable=false] Whether the Gateway should avoid configuring the selected key + * @param {string} [resetKey] The key to reset + * @param {boolean} [avoidUnconfigurable] Whether the Gateway should avoid configuring the selected key * @returns {Promise} */ - async reset(key, avoidUnconfigurable = false) { - const { parsedID, parsed, path } = await this._reset(key, avoidUnconfigurable); + async reset(resetKey, avoidUnconfigurable) { + if (typeof resetKey === 'undefined') { + if (this._existsInDB) await this.gateway.provider.delete(this.gateway.type, this.id); + for (const [key, value] of this.gateway.schema) this[key] = Configuration._merge(undefined, value); + return { value: this, path: '' }; + } + const { parsedID, parsed, path } = await this._reset(resetKey, typeof avoidUnconfigurable !== 'boolean' ? false : avoidUnconfigurable); await (this.gateway.sql ? - this.gateway.provider.update(this.gateway.type, this.id, key, parsedID) : - this.gateway.provider.update(this.gateway.type, this.id, makeObject(key, parsedID))); + this.gateway.provider.update(this.gateway.type, this.id, resetKey, parsedID) : + this.gateway.provider.update(this.gateway.type, this.id, makeObject(resetKey, parsedID))); return { value: parsed, path }; } diff --git a/src/lib/structures/CommandStore.js b/src/lib/structures/CommandStore.js index fa3b5ab6bd..10c25d3bdb 100644 --- a/src/lib/structures/CommandStore.js +++ b/src/lib/structures/CommandStore.js @@ -155,7 +155,7 @@ class CommandStore extends Collection { const files = await fs.scan(dir, { filter: (stats, path) => stats.isFile() && extname(path) === '.js' }).catch(() => { fs.ensureDir(dir).catch(err => store.client.emit('error', err)); }); if (!files) return true; - return Promise.all(Array.from(files.keys()).map(file => store.load(dir, relative(dir, file).split(sep)))); + return Promise.all([...files.keys()].map(file => store.load(dir, relative(dir, file).split(sep)))); } } diff --git a/src/lib/util/util.js b/src/lib/util/util.js index a66ec9d96d..41b4115c99 100644 --- a/src/lib/util/util.js +++ b/src/lib/util/util.js @@ -243,8 +243,8 @@ class Util { const typeValue = Util.getDeepTypeName(value); if (!typeValues.has(typeValue)) typeValues.add(typeValue); } - const typeK = typeKeys.size === 0 || typeKeys.has('any') ? 'any' : Array.from(typeKeys).sort().join(' | '); - const typeV = typeValues.size === 0 || typeValues.has('any') ? 'any' : Array.from(typeValues).sort().join(' | '); + const typeK = typeKeys.size === 0 || typeKeys.has('any') ? 'any' : [...typeKeys].sort().join(' | '); + const typeV = typeValues.size === 0 || typeValues.has('any') ? 'any' : [...typeValues].sort().join(' | '); return `${basic}<${typeK}, ${typeV}>`; } @@ -263,7 +263,7 @@ class Util { const type = Util.getDeepTypeName(value); if (!types.has(type)) types.add(type); } - const typeV = types.size === 0 || types.has('Object') ? 'any' : Array.from(types).sort().join(' | '); + const typeV = types.size === 0 || types.has('Object') ? 'any' : [...types].sort().join(' | '); return `${basic}<${typeV}>`; } From eca5fd4e79dd5e1b5fa2cfb8222cff3e5851c8b1 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sun, 18 Feb 2018 11:40:36 +0100 Subject: [PATCH 26/76] SchemaFolder#add --- CHANGELOG.md | 1 + .../UnderstandingSchemaFolders.md | 21 +++-- src/lib/settings/SchemaFolder.js | 76 ++++++++++--------- typings/index.d.ts | 3 +- 4 files changed, 50 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dbf45fd1f..792b0eb34e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -142,6 +142,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Removed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] **[BREAKING]** Removed `SchemaFolder#addKey` and `SchemaFolder#addFolder` in favor to a more consistent `Schema#add`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] **[BREAKING]** Removed `Configuration#resetConfiguration()`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] **[PERF-MEM]** Removed `Configuration#type`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] **[BREAKING]** Removed `SchemaFolder#removeKey` and `SchemaFolder#removeFolder` in favor to a more consistent `Schema#remove`. (kyranet) diff --git a/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md b/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md index 94159c1c87..2297132c3e 100644 --- a/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md +++ b/guides/Advanced SettingGateway/UnderstandingSchemaFolders.md @@ -9,16 +9,13 @@ A schema works like a diagram or a blueprint, in SettingGateway, the schema defi ## Adding keys -Adding keys with the schema is like adding a piece into a box, but you can also have boxes inside other boxes. That being said, you get the box you want to modify and insert the new pieces or boxes into it. The methods to achieve that are {@link SchemaFolder#addKey} to add pieces (keys) and {@link SchemaFolder#addFolder} to add boxes (folders). +Adding keys with the schema is like adding a piece into a box, but you can also have boxes inside other boxes. That being said, you get the box you want to modify and insert the new pieces or boxes into it. The methods to achieve that are {@link SchemaFolder#add} to add pieces (keys) and boxes (folders). You would normally use these two methods using the following snippet: ```javascript -// Add a new key -this.client.gateways.gatewayName.schema.addKey(name, options, force); - -// Add a new folder -this.client.gateways.gatewayName.schema.addFolder(name, options, force); +// Add a new key or folder +this.client.gateways.gatewayName.schema.add(name, options, force); ``` The parameters are: @@ -30,7 +27,7 @@ The parameters are: You can also extend any of the three built-in {@link Gateway}s from Klasa. For example, if you want to add a new key called **modlogs** that accepts only text channels, for your guild configs, you would use the following code: ```javascript -this.client.gateways.guilds.schema.addKey('modlogs', { type: 'TextChannel' }); +this.client.gateways.guilds.schema.add('modlogs', { type: 'TextChannel' }); ``` Where you're doing the following steps: @@ -43,7 +40,7 @@ Where you're doing the following steps: And you would have a perfectly configured modlogs key in your configs. However, you can also have an array of the same type. For example, you want to have a configurable array of users blacklisted in a guild, in a key named **userBlacklist**: ```javascript -this.client.gateways.guilds.schema.addKey('userBlacklist', { type: 'User', array: true }); +this.client.gateways.guilds.schema.add('userBlacklist', { type: 'User', array: true }); ``` And now you can have access to any of them in your guild configs like in the following snippet! @@ -83,8 +80,8 @@ You can create a folder, then create the keys, however, this will iterate over a async function init() { const { schema } = this.client.gateways.guilds; - await schema.addFolder('channels'); - await schema.channels.addKey('modlogs', { type: 'TextChannel' }); + await schema.add('channels', {}); + await schema.channels.add('modlogs', { type: 'TextChannel' }); console.log(schema.channels.modlogs.toJSON()); // { // type: 'textchannel', @@ -105,7 +102,7 @@ However, it's possible to create a folder with all the sub-keys (and even more n async function init() { const { schema } = this.client.gateways.guilds; - await schema.addFolder('channels', { modlogs: { type: 'TextChannel' } }); + await schema.add('channels', { modlogs: { type: 'TextChannel' } }); console.log(schema.channels.modlogs.toJSON()); // { // type: 'textchannel', @@ -133,7 +130,7 @@ async function init() { const { schema } = this.client.gateways.guilds; if (!schema.has('modlog')) { - await schema.addKey('modlog', { type: 'TextChannel' }); + await schema.add('modlog', { type: 'TextChannel' }); } } ``` diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 3b4a69cfed..046896e15b 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -1,6 +1,6 @@ const SchemaPiece = require('./SchemaPiece'); const Schema = require('./Schema'); -const { deepClone } = require('../util/util'); +const { deepClone, isObject } = require('../util/util'); const fs = require('fs-nextra'); /** @@ -75,28 +75,54 @@ class SchemaFolder extends Schema { * Create a new nested folder. * @since 0.5.0 * @param {string} key The name's key for the folder - * @param {Object} [object={}] An object containing all the SchemaFolders/SchemaPieces literals for this folder + * @param {Object} options An object containing the options for the new piece or folder. Check {@tutorial UnderstandingSchemaFolders} * @param {boolean} [force=true] Whether this function call should modify all entries from the database - * @returns {Promise} + * @returns {SchemaFolder} + * @example + * // Add a new SchemaPiece + * SchemaFolder.add('modlog', { + * type: 'TextChannel' + * }); + * + * // Add an empty new SchemaFolder + * SchemaFolder.add('channels', {}); + * + * // Optionally, you can set the type Folder, + * // if no type is set, 'Folder' will be implied. + * SchemaFolder.add('channels', { + * type: 'Folder' + * }); + * + * // Add a new SchemaFolder with a modlog key inside + * SchemaFolder.add('channels', { + * modlog: { + * type: 'TextChannel' + * } + * }); */ - async addFolder(key, object = {}, force = true) { + async add(key, options, force = true) { if (this.has(key)) throw `The key ${key} already exists in the current schema.`; if (typeof this[key] !== 'undefined') throw `The key ${key} conflicts with a property of Schema.`; + if (!options || !isObject) throw `The options object is required.`; + if (!options.type || String(options.type).toLowerCase() === 'folder') options.type = 'Folder'; - const folder = this._add(key, object, SchemaFolder); + // Create the piece and save the current schema + const piece = options.type === 'Folder' ? + this._add(key, options, SchemaFolder) : + this._add(key, this._verifyKeyOptions(key, options), SchemaPiece); await fs.outputJSONAtomic(this.gateway.filePath, this.gateway.schema.toJSON()); if (this.gateway.sql) { - if (folder.keyArray.length > 0) { - if (typeof this.gateway.provider.addColumn === 'function') await this.gateway.provider.addColumn(this.gateway.type, folder.getSQL()); - else throw new Error('The method \'addColumn\' in your provider is required in order to add new columns.'); + if (piece.type !== 'Folder' || piece.keyArray.length) { + await this.gateway.provider.addColumn(this.gateway.type, piece.type === 'Folder' ? + piece.getSQL() : piece.sql[1]); } } else if (force || this.gateway.type === 'clientStorage') { - await this.force('add', key, folder); + await this.force('add', key, piece); } - await this._shardSyncSchema(folder, 'add', force); - if (this.client.listenerCount('schemaKeyAdd')) this.client.emit('schemaKeyAdd', folder); + await this._shardSyncSchema(piece, 'add', force); + this.client.emit('schemaKeyAdd', piece); return this.gateway.schema; } @@ -105,7 +131,7 @@ class SchemaFolder extends Schema { * @since 0.5.0 * @param {string} key The key's name to remove * @param {boolean} [force=true] Whether this function call should modify all entries from the database - * @returns {Promise} + * @returns {SchemaFolder} */ async remove(key, force = true) { if (!this.has(key)) throw new Error(`The key ${key} does not exist in the current schema.`); @@ -127,7 +153,7 @@ class SchemaFolder extends Schema { } await this._shardSyncSchema(piece, 'delete', force); - if (this.client.listenerCount('schemaKeyRemove')) this.client.emit('schemaKeyRemove', piece); + this.client.emit('schemaKeyRemove', piece); return this.gateway.schema; } @@ -141,30 +167,6 @@ class SchemaFolder extends Schema { return this.keyArray.includes(key); } - /** - * Add a new key to this folder. - * @since 0.5.0 - * @param {string} key The name for the key - * @param {SchemaFolderAddOptions} options The key's options to apply - * @param {boolean} [force=true] Whether this function call should modify all entries from the database - * @returns {Promise} - */ - async addKey(key, options, force = true) { - this._add(key, this._verifyKeyOptions(key, options), SchemaPiece); - await fs.outputJSONAtomic(this.gateway.filePath, this.gateway.schema.toJSON()); - - if (this.gateway.sql) { - if (typeof this.gateway.provider.addColumn === 'function') await this.gateway.provider.addColumn(this.gateway.type, key, this[key].sql[1]); - else throw new Error('The method \'addColumn\' in your provider is required in order to add new columns.'); - } else if (force || this.gateway.type === 'clientStorage') { - await this.force('add', key, this[key]); - } - - await this._shardSyncSchema(this[key], 'add', force); - if (this.client.listenerCount('schemaKeyAdd')) this.client.emit('schemaKeyAdd', this[key]); - return this.gateway.schema; - } - /** * Modifies all entries from the database. * @since 0.5.0 diff --git a/typings/index.d.ts b/typings/index.d.ts index 08cf116d69..3c5a32c9e2 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -651,8 +651,7 @@ declare module 'klasa' { public readonly configurableKeys: string[]; - public addKey(key: string, options: SchemaFolderAddOptions, force?: boolean): Promise; - public addFolder(key: string, object?: object, force?: boolean): Promise; + public add(key: string, options: SchemaFolderAddOptions | { [k: string]: SchemaFolderAddOptions }, force?: boolean): Promise; public has(key: string): boolean; public remove(key: string, force?: boolean): Promise; public force(action: 'add' | 'edit' | 'delete', key: string, piece: SchemaFolder | SchemaPiece): Promise; From ffdbe341e829a2bc52b5a6e2c34360294ee254f7 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sun, 18 Feb 2018 12:13:52 +0100 Subject: [PATCH 27/76] Forgot to call the function --- src/lib/settings/SchemaFolder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 046896e15b..e80fbe3dc8 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -103,7 +103,7 @@ class SchemaFolder extends Schema { async add(key, options, force = true) { if (this.has(key)) throw `The key ${key} already exists in the current schema.`; if (typeof this[key] !== 'undefined') throw `The key ${key} conflicts with a property of Schema.`; - if (!options || !isObject) throw `The options object is required.`; + if (!options || !isObject(options)) throw `The options object is required.`; if (!options.type || String(options.type).toLowerCase() === 'folder') options.type = 'Folder'; // Create the piece and save the current schema From d8b7c6dbab606d8d39e903273150a106b60a93da Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sun, 18 Feb 2018 15:12:47 +0100 Subject: [PATCH 28/76] Configuration#reset --- CHANGELOG.md | 1 + src/lib/settings/Configuration.js | 53 +++++++++++++++++++++++++++---- src/lib/settings/SchemaFolder.js | 2 +- src/lib/util/util.js | 21 ++++++++++++ typings/index.d.ts | 5 ++- 5 files changed, 73 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 792b0eb34e..6dba18aecc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Added +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `util.arraysEqual`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added property `Symbol.iterator` to Schedule. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `Gateway#toJSON()` and `GatewayDriver#toJSON()`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `GatewayDriver#register` to be able to register new gateways without events (directly in your `app.js`). (kyranet) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index af17c988a8..970db7582d 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -1,6 +1,7 @@ -const { isObject, makeObject, deepClone, tryParse, getIdentifier, toTitleCase } = require('../util/util'); +const { isObject, makeObject, deepClone, tryParse, getIdentifier, toTitleCase, arraysEqual } = require('../util/util'); const SchemaFolder = require('./SchemaFolder'); const SchemaPiece = require('./SchemaPiece'); +const invalidValue = Symbol('invalid'); /** * Creating your own Configuration instances if often discouraged and unneeded. SettingGateway handles them internally for you. @@ -158,7 +159,7 @@ class Configuration { /** * Sync the data from the database with the cache. * @since 0.5.0 - * @returns {Promise} + * @returns {this} */ async sync() { this._syncStatus = this.gateway.provider.get(this.gateway.type, this.id); @@ -174,7 +175,7 @@ class Configuration { /** * Delete this entry from the database and cache. * @since 0.5.0 - * @returns {Promise} + * @returns {this} */ async destroy() { if (this._existsInDB) { @@ -190,13 +191,26 @@ class Configuration { * @since 0.5.0 * @param {string} [resetKey] The key to reset * @param {boolean} [avoidUnconfigurable] Whether the Gateway should avoid configuring the selected key - * @returns {Promise} + * @returns {ConfigurationUpdateResult|ConfigurationUpdateObjectList} */ async reset(resetKey, avoidUnconfigurable) { if (typeof resetKey === 'undefined') { - if (this._existsInDB) await this.gateway.provider.delete(this.gateway.type, this.id); - for (const [key, value] of this.gateway.schema) this[key] = Configuration._merge(undefined, value); - return { value: this, path: '' }; + // Handle entry creation if it does not exist. + if (!this._existsInDB) return { keys: [], values: [] }; + + const oldClone = this.client.listenerCount('configUpdateEntry') ? this.clone() : null; + const list = { keys: [], values: [] }; + this._resetAll(this.gateway.schema, this, list); + + if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, list.keys); + if (this.gateway.sql) { + await this.gateway.provider.update(this.gateway.type, this.id, list.keys, list.values); + } else { + const updateObject = Object.assign(list.keys.map((key, i) => makeObject(key, list.values[i]))); + await this.gateway.provider.update(this.gateway.type, this.id, updateObject); + } + + return { keys: list.keys, values: list.values }; } const { parsedID, parsed, path } = await this._reset(resetKey, typeof avoidUnconfigurable !== 'boolean' ? false : avoidUnconfigurable); await (this.gateway.sql ? @@ -316,6 +330,31 @@ class Configuration { return resolver(value); } + /** + * Resets all keys recursively + * @since 0.5.0 + * @param {SchemaFolder} schema The SchemaFolder to iterate + * @param {Object} configs The configs in the selected folder + * @param {ConfigurationUpdateManyList} list The list + * @private + */ + _resetAll(schema, configs, list) { + for (const [key, piece] of schema) { + if (piece.type === 'Folder') { + this._resetAll(piece, configs[key], list); + continue; + } + const value = (piece.array ? !arraysEqual(configs[key], piece.default, true) : configs[key] !== piece.default) ? + deepClone(piece.default) : + invalidValue; + if (value === invalidValue) continue; + + configs[key] = value; + list.keys.push(piece.path); + list.values.push(configs[key]); + } + } + /** * Update multiple keys given a JSON object. * @since 0.5.0 diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index e80fbe3dc8..48107eff4f 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -104,7 +104,7 @@ class SchemaFolder extends Schema { if (this.has(key)) throw `The key ${key} already exists in the current schema.`; if (typeof this[key] !== 'undefined') throw `The key ${key} conflicts with a property of Schema.`; if (!options || !isObject(options)) throw `The options object is required.`; - if (!options.type || String(options.type).toLowerCase() === 'folder') options.type = 'Folder'; + if (typeof options.type !== 'string' || options.type.toLowerCase() === 'folder') options.type = 'Folder'; // Create the piece and save the current schema const piece = options.type === 'Folder' ? diff --git a/src/lib/util/util.js b/src/lib/util/util.js index 41b4115c99..59ab25803d 100644 --- a/src/lib/util/util.js +++ b/src/lib/util/util.js @@ -327,6 +327,27 @@ class Util { return object; } + /** + * Compare if both arrays are equal + * @param {any[]} arr1 The first array to compare + * @param {any[]} arr2 The second array to compare + * @param {boolean} clone Whether this check should clone the second array + * @returns {boolean} + */ + static arraysEqual(arr1, arr2, clone = false) { + if (arr1 === arr2) return true; + if (arr1.length !== arr2.length) return false; + // Clone the array + if (clone) arr2 = arr2.slice(0); + + for (const item of arr1) { + const ind = arr2.indexOf(item); + if (ind !== -1) arr2.splice(ind, 1); + } + + return arr2.length === 0; + } + /** * Sets default properties on an object that aren't already specified. * @since 0.5.0 diff --git a/typings/index.d.ts b/typings/index.d.ts index 3c5a32c9e2..73ecd99449 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -525,13 +525,15 @@ declare module 'klasa' { public sync(): Promise; public destroy(): Promise; + public reset(): Promise; public reset(key: string, avoidUnconfigurable?: boolean): Promise; - public update(key: object, guild?: GatewayGuildResolvable): Promise; + public update(key: object, guild?: GatewayGuildResolvable): Promise; public update(key: string, value?: any, options?: ConfigurationUpdateOptions): Promise; public update(key: string, value?: any, guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; public list(msg: KlasaMessage, path: SchemaFolder | string): string; public resolveString(msg: KlasaMessage, path: SchemaPiece | string): string; + private _resetAll(schema: SchemaFolder, configs: object, list: ConfigurationUpdateManyList): void; private _updateMany(object: any, guild?: GatewayGuildResolvable): Promise; private _reset(key: string, guild: GatewayGuildResolvable, avoidUnconfigurable: boolean): Promise; private _parseReset(key: string, guild: KlasaGuild, options: ConfigurationPathResult): Promise; @@ -1301,6 +1303,7 @@ declare module 'klasa' { public static isObject(input: object): boolean; public static isThenable(input: Promise): boolean; public static makeObject(path: string, value: any): object; + public static arraysEqual(arr1: any[], arr2: any[], clone?: boolean): boolean; public static mergeDefault(def: object, given?: object): object; public static mergeObjects(objTarget: object, objSource: object): object; public static regExpEsc(str: string): string; From 2e4f237e3a86e79d0ddf562b55311545b65da674 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sun, 18 Feb 2018 18:30:52 +0100 Subject: [PATCH 29/76] Bugfixes --- src/lib/Client.js | 2 +- src/lib/schedule/Schedule.js | 2 +- src/lib/settings/SchemaFolder.js | 2 +- src/lib/settings/SchemaPiece.js | 2 +- src/lib/util/util.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/Client.js b/src/lib/Client.js index 6533903a2d..c703d7598b 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -492,7 +492,7 @@ class KlasaClient extends Discord.Client { let commandMessages = 0; for (const channel of this.channels.values()) { - if (!channel.messages) continue; + if (!channel.messages.size) continue; channels++; for (const message of channel.messages.values()) { diff --git a/src/lib/schedule/Schedule.js b/src/lib/schedule/Schedule.js index 8f32978fb6..1e4fa0ba4e 100644 --- a/src/lib/schedule/Schedule.js +++ b/src/lib/schedule/Schedule.js @@ -66,7 +66,7 @@ class Schedule { async init() { const { schema } = this.client.gateways.clientStorage; if (!schema.has('schedules')) { - await schema.addKey('schedules', { + await schema.add('schedules', { type: 'any', default: [], min: null, diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 48107eff4f..6b674a0055 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -4,7 +4,7 @@ const { deepClone, isObject } = require('../util/util'); const fs = require('fs-nextra'); /** - * You should never create an instance of this class. Use {@link SchemaFolder#addFolder} instead. + * You should never create an instance of this class. Use {@link SchemaFolder#add} instead. * The schema class that stores (nested) folders and keys for SettingGateway usage. This class also implements multiple helpers. */ class SchemaFolder extends Schema { diff --git a/src/lib/settings/SchemaPiece.js b/src/lib/settings/SchemaPiece.js index 0d11224db1..a4472d42de 100644 --- a/src/lib/settings/SchemaPiece.js +++ b/src/lib/settings/SchemaPiece.js @@ -3,7 +3,7 @@ const Schema = require('./Schema'); const fs = require('fs-nextra'); /** - * You should never create an instance of this class. Use {@link SchemaFolder#addKey} instead. + * You should never create an instance of this class. Use {@link SchemaFolder#add} instead. * The SchemaPiece class that contains the data for a key and several helpers. */ class SchemaPiece extends Schema { diff --git a/src/lib/util/util.js b/src/lib/util/util.js index 59ab25803d..e9ec07e42b 100644 --- a/src/lib/util/util.js +++ b/src/lib/util/util.js @@ -257,7 +257,7 @@ class Util { * @returns {string} */ static getDeepTypeSetOrArray(input, basic = Util.getTypeName(input)) { - if (!(input instanceof Array || input instanceof Set || input instanceof WeakSet)) return basic; + if (!(Array.isArray(input) || input instanceof Set || input instanceof WeakSet)) return basic; const types = new Set(); for (const value of input) { const type = Util.getDeepTypeName(value); From c4a653e0c29e6cf25586ec2c37a0d2b18945a617 Mon Sep 17 00:00:00 2001 From: Kyra Date: Sun, 18 Feb 2018 19:28:38 +0100 Subject: [PATCH 30/76] Update Client.js --- src/lib/Client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Client.js b/src/lib/Client.js index c703d7598b..6533903a2d 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -492,7 +492,7 @@ class KlasaClient extends Discord.Client { let commandMessages = 0; for (const channel of this.channels.values()) { - if (!channel.messages.size) continue; + if (!channel.messages) continue; channels++; for (const message of channel.messages.values()) { From 22ec3d584878ad6cb075b8a1618f8fc4c0ce9c23 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sun, 18 Feb 2018 19:40:02 +0100 Subject: [PATCH 31/76] Updated resolvers to not include Promise<> in returns when async --- src/lib/parsers/ArgResolver.js | 60 +++++++++++++++--------------- src/lib/parsers/SettingResolver.js | 34 ++++++++--------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/lib/parsers/ArgResolver.js b/src/lib/parsers/ArgResolver.js index 5e3d67b4e5..97895468b0 100644 --- a/src/lib/parsers/ArgResolver.js +++ b/src/lib/parsers/ArgResolver.js @@ -14,7 +14,7 @@ class ArgResolver extends Resolver { * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command * @param {Function} custom The custom resolver - * @returns {Promise} + * @returns {*} */ async custom(arg, possible, msg, custom) { try { @@ -32,7 +32,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Piece} */ async piece(arg, possible, msg) { for (const store of this.client.pieceStores.values()) { @@ -48,7 +48,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Store} */ async store(arg, possible, msg) { const store = this.client.pieceStores.get(arg); @@ -64,7 +64,7 @@ class ArgResolver extends Resolver { * @param {KlasaMessage} msg The message that triggered the command * @returns {Promise} */ - async command(...args) { + command(...args) { return this.cmd(...args); } @@ -74,7 +74,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Command} */ async cmd(arg, possible, msg) { const command = this.client.commands.get(arg); @@ -88,7 +88,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Event} */ async event(arg, possible, msg) { const event = this.client.events.get(arg); @@ -102,7 +102,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Extendable} */ async extendable(arg, possible, msg) { const extendable = this.client.extendables.get(arg); @@ -116,7 +116,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Finalizer} */ async finalizer(arg, possible, msg) { const finalizer = this.client.finalizers.get(arg); @@ -130,7 +130,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Inhibitor} */ async inhibitor(arg, possible, msg) { const inhibitor = this.client.inhibitors.get(arg); @@ -144,7 +144,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Monitor} */ async monitor(arg, possible, msg) { const monitor = this.client.monitors.get(arg); @@ -158,7 +158,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Language} */ async language(arg, possible, msg) { const language = this.client.languages.get(arg); @@ -172,7 +172,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Provider} */ async provider(arg, possible, msg) { const provider = this.client.providers.get(arg); @@ -186,7 +186,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Task} */ async task(arg, possible, msg) { const task = this.client.tasks.get(arg); @@ -212,7 +212,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {KlasaMessage} */ async msg(arg, possible, msg) { const message = await super.msg(arg, msg.channel); @@ -238,7 +238,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {KlasaUser} */ async user(arg, possible, msg) { const user = await super.user(arg); @@ -252,7 +252,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {external:GuildMember} */ async member(arg, possible, msg) { const member = await super.member(arg, msg.guild); @@ -266,7 +266,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {external:Channel} */ async channel(arg, possible, msg) { const channel = await super.channel(arg); @@ -280,7 +280,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {external:Emoji} */ async emoji(arg, possible, msg) { const emoji = await super.emoji(arg); @@ -294,7 +294,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {KlasaGuild} */ async guild(arg, possible, msg) { const guild = await super.guild(arg); @@ -308,7 +308,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {external:Role} */ async role(arg, possible, msg) { const role = await super.role(arg, msg.guild); @@ -322,7 +322,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {string} */ async literal(arg, possible, msg) { arg = arg.toLowerCase(); @@ -348,7 +348,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {boolean} */ async bool(arg, possible, msg) { const boolean = await super.boolean(arg); @@ -374,7 +374,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {string} */ async str(arg, possible, msg) { const { min, max } = possible; @@ -401,7 +401,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {number} */ async int(arg, possible, msg) { const { min, max } = possible; @@ -442,7 +442,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {number} */ async float(arg, possible, msg) { const { min, max } = possible; @@ -460,7 +460,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {RegExpExecArray} */ async reg(arg, possible, msg) { const results = possible.regex.exec(arg); @@ -499,7 +499,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {string} */ async url(arg, possible, msg) { const hyperlink = await super.url(arg); @@ -513,7 +513,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Date} */ async date(arg, possible, msg) { const date = new Date(arg); @@ -527,7 +527,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Date} */ async duration(arg, possible, msg) { const date = new Duration(arg).fromNow; @@ -541,7 +541,7 @@ class ArgResolver extends Resolver { * @param {string} arg The argument to parse * @param {Possible} possible This current usage possible * @param {KlasaMessage} msg The message that triggered the command - * @returns {Promise} + * @returns {Date} */ async time(arg, possible, msg) { const date = await Promise.all([ diff --git a/src/lib/parsers/SettingResolver.js b/src/lib/parsers/SettingResolver.js index 9f378dcdbb..d88ff754f7 100644 --- a/src/lib/parsers/SettingResolver.js +++ b/src/lib/parsers/SettingResolver.js @@ -12,7 +12,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {KlasaUser} */ async user(data, guild, name) { const result = await super.user(data); @@ -26,7 +26,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {external:Channel} */ async channel(data, guild, name) { const result = await super.channel(data); @@ -40,7 +40,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {external:TextChannel} */ async textchannel(data, guild, name) { const result = await super.channel(data); @@ -54,7 +54,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {external:VoiceChannel} */ async voicechannel(data, guild, name) { const result = await super.channel(data); @@ -68,7 +68,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {external:CategoryChannel} */ async categorychannel(data, guild, name) { const result = await super.channel(data); @@ -82,7 +82,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {KlasaGuild} */ async guild(data, guild, name) { const result = await super.guild(data); @@ -96,7 +96,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {external:Role} */ async role(data, guild, name) { const result = await super.role(data, guild) || (guild ? guild.roles.find('name', data) : null); @@ -110,7 +110,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {boolean} */ async boolean(data, guild, name) { const result = await super.boolean(data); @@ -127,7 +127,7 @@ class SettingResolver extends Resolver { * @param {Object} minMax The minimum and maximum * @param {?number} minMax.min The minimum value * @param {?number} minMax.max The maximum value - * @returns {Promise} + * @returns {string} */ async string(data, guild, name, { min, max } = {}) { const result = await super.string(data); @@ -144,7 +144,7 @@ class SettingResolver extends Resolver { * @param {Object} [minMax={}] The minimum and maximum * @param {?number} minMax.min The minimum value * @param {?number} minMax.max The maximum value - * @returns {Promise} + * @returns {number} */ async integer(data, guild, name, { min, max } = {}) { const result = await super.integer(data); @@ -162,7 +162,7 @@ class SettingResolver extends Resolver { * @param {Object} [minMax={}] The minimum and maximum * @param {?number} minMax.min The minimum value * @param {?number} minMax.max The maximum value - * @returns {Promise} + * @returns {number} */ async float(data, guild, name, { min, max } = {}) { const result = await super.float(data); @@ -177,7 +177,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {string} */ async url(data, guild, name) { const result = await super.url(data); @@ -191,7 +191,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {string} */ async command(data, guild, name) { const command = this.client.commands.get(data.toLowerCase()); @@ -205,7 +205,7 @@ class SettingResolver extends Resolver { * @param {*} data The data to resolve * @param {KlasaGuild} guild The guild to resolve for * @param {string} name The name of the key being resolved - * @returns {Promise} + * @returns {string} */ async language(data, guild, name) { const language = this.client.languages.get(data); @@ -217,10 +217,10 @@ class SettingResolver extends Resolver { * Resolves anything, even objects. * @since 0.5.0 * @param {*} data Raw content to pass - * @returns {Promise<*>} + * @returns {*} */ - any(data) { - return Promise.resolve(data); + async any(data) { + return data; } /** From a43bbba80ba8bd04d0b14c3f6c4bd3260b204e1e Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sun, 18 Feb 2018 20:08:56 +0100 Subject: [PATCH 32/76] Removed Promise<> in all JSDocs for methods with async --- src/lib/Client.js | 2 +- src/lib/extensions/KlasaMessage.js | 6 +++--- src/lib/schedule/Schedule.js | 4 ++-- src/lib/schedule/ScheduledTask.js | 4 ++-- src/lib/settings/Configuration.js | 14 +++++++------- src/lib/settings/Gateway.js | 9 +++++---- src/lib/settings/GatewayDriver.js | 2 +- src/lib/settings/GatewayStorage.js | 2 +- src/lib/settings/SchemaPiece.js | 4 ++-- src/lib/structures/Command.js | 4 ++-- src/lib/structures/Inhibitor.js | 4 ++-- src/lib/structures/Provider.js | 2 +- src/lib/structures/Task.js | 6 +++--- src/lib/usage/TextPrompt.js | 12 ++++++------ 14 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/lib/Client.js b/src/lib/Client.js index 6533903a2d..7c01de3d1b 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -402,7 +402,7 @@ class KlasaClient extends Discord.Client { * Use this to login to Discord with your bot * @since 0.0.1 * @param {string} token Your bot token - * @returns {Promise} + * @returns {string} */ async login(token) { const timer = new Stopwatch(); diff --git a/src/lib/extensions/KlasaMessage.js b/src/lib/extensions/KlasaMessage.js index 51b48b35e9..c302e4a73b 100644 --- a/src/lib/extensions/KlasaMessage.js +++ b/src/lib/extensions/KlasaMessage.js @@ -113,7 +113,7 @@ module.exports = Structures.extend('Message', Message => { * Awaits a response from the author. * @param {string} text The text to prompt the author * @param {number} [time=30000] The time to wait before giving up - * @returns {Promise} + * @returns {KlasaMessage} */ async prompt(text, time = 30000) { const message = await this.channel.send(text); @@ -126,7 +126,7 @@ module.exports = Structures.extend('Message', Message => { /** * The usable commands by the author in this message's context * @since 0.0.1 - * @returns {Promise>} The filtered CommandStore + * @returns {Collection} The filtered CommandStore */ async usableCommands() { const col = new Collection(); @@ -144,7 +144,7 @@ module.exports = Structures.extend('Message', Message => { * Checks if the author of this message, has applicable permission in this message's context of at least min * @since 0.0.1 * @param {number} min The minimum level required - * @returns {Promise} + * @returns {boolean} */ async hasAtLeastPermissionLevel(min) { const { permission } = await this.client.permissionLevels.run(this, min); diff --git a/src/lib/schedule/Schedule.js b/src/lib/schedule/Schedule.js index 1e4fa0ba4e..b4ea5dbbfb 100644 --- a/src/lib/schedule/Schedule.js +++ b/src/lib/schedule/Schedule.js @@ -137,7 +137,7 @@ class Schedule { * @param {string} taskName The name of the task * @param {(Date|number|string)} time The time or Cron pattern * @param {ScheduledTaskOptions} options The options for the ScheduleTask instance - * @returns {Promise} + * @returns {ScheduledTask} * @example * // Create a new reminder that ends in 2018-03-09T12:30:00.000Z (UTC) * Schedule.create('reminder', new Date(Date.UTC(2018, 2, 9, 12, 30)), { @@ -168,7 +168,7 @@ class Schedule { * Delete a Task by its ID * @since 0.5.0 * @param {string} id The ID to search for - * @returns {Promise} + * @returns {this} */ async delete(id) { const _task = this._tasks.find(entry => entry.id === id); diff --git a/src/lib/schedule/ScheduledTask.js b/src/lib/schedule/ScheduledTask.js index 4e8cc1eb05..c425517550 100644 --- a/src/lib/schedule/ScheduledTask.js +++ b/src/lib/schedule/ScheduledTask.js @@ -102,7 +102,7 @@ class ScheduledTask { /** * Run the current task and bump it if needed * @since 0.5.0 - * @returns {Promise} + * @returns {this} */ async run() { if (!this.task.enabled) return this; @@ -119,7 +119,7 @@ class ScheduledTask { * Update the task * @since 0.5.0 * @param {ScheduledTaskUpdateOptions} options The options to update - * @returns {Promise} + * @returns {this} * @example * // Update the data from the current scheduled task. Let's say I want to change the reminder content to remind me * // another thing diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 970db7582d..086399287c 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -360,8 +360,8 @@ class Configuration { * @since 0.5.0 * @param {Object} object A JSON object to iterate and parse * @param {GuildResolvable} [guild] A guild resolvable - * @returns {Promise} - * @throws {Promise} + * @returns {ConfigurationUpdateObjectList} + * @throws {ConfigurationUpdateObjectResult} * @private */ async _updateMany(object, guild) { @@ -387,7 +387,7 @@ class Configuration { * @since 0.5.0 * @param {string} key The key to reset * @param {boolean} avoidUnconfigurable Whether the Gateway should avoid configuring the selected key - * @returns {Promise} + * @returns {ConfigurationParseResult} * @private */ async _reset(key, avoidUnconfigurable) { @@ -401,7 +401,7 @@ class Configuration { * @since 0.5.0 * @param {string} key The key to edit * @param {ConfigurationPathResult} options The options - * @returns {Promise} + * @returns {ConfigurationParseResult} * @private */ async _parseReset(key, { path, route }) { @@ -417,7 +417,7 @@ class Configuration { * @param {*} value The new value * @param {GuildResolvable} guild The guild to take * @param {ConfigurationPathResult} options The options - * @returns {Promise} + * @returns {ConfigurationParseResult} * @private */ async _parseUpdateOne(key, value, guild, { path, route }) { @@ -438,7 +438,7 @@ class Configuration { * @param {GuildResolvable} guild The guild to take * @param {number} arrayPosition The array position to update * @param {ConfigurationPathResult} options The options - * @returns {Promise} + * @returns {ConfigurationParseResult} * @private */ async _parseUpdateArray(action, key, value, guild, arrayPosition, { path, route }) { @@ -487,7 +487,7 @@ class Configuration { * @param {boolean} [options.avoidUnconfigurable=false] Whether the Gateway should avoid configuring the selected key * @param {('add'|'remove'|'auto')} [options.action='auto'] Whether the value should be added or removed to the array * @param {number} [options.arrayPosition=null] The array position to update - * @returns {Promise} + * @returns {ConfigurationUpdateResult} * @private */ async _updateSingle(key, value, guild, { avoidUnconfigurable = false, action = 'auto', arrayPosition = null } = {}) { diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index 2e3aab93e0..c23eb3f240 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -135,7 +135,7 @@ class Gateway extends GatewayStorage { * Create a new entry into the database with an optional content (defaults to this Gateway's defaults). * @since 0.5.0 * @param {string} input The name of the key to create - * @returns {Promise} + * @returns {Configuration} */ async createEntry(input) { const target = getIdentifier(input); @@ -168,7 +168,7 @@ class Gateway extends GatewayStorage { * Delete an entry from the database and cache. * @since 0.5.0 * @param {string} input The name of the key to fetch and delete - * @returns {Promise} + * @returns {boolean} */ async deleteEntry(input) { const configs = this.cache.get(input); @@ -183,7 +183,7 @@ class Gateway extends GatewayStorage { * @since 0.0.1 * @param {(Object|string)} [input] An object containing a id property, like discord.js objects, or a string * @param {boolean} [download] Whether the sync should download data from the database - * @returns {Promise<*>} + * @returns {?Configuration} */ async sync(input, download) { if (typeof input === 'undefined') { @@ -200,6 +200,7 @@ class Gateway extends GatewayStorage { this.cache.set(entry.id, newEntry); } } + return null; } const target = getIdentifier(input); if (!target) throw new TypeError('The selected target could not be resolved to a string.'); @@ -265,7 +266,7 @@ class Gateway extends GatewayStorage { /** * Readies up all Configuration instances in this gateway * @since 0.5.0 - * @returns {Promise>>} + * @returns {Array>} * @private */ async _ready() { diff --git a/src/lib/settings/GatewayDriver.js b/src/lib/settings/GatewayDriver.js index ad391bf1c7..b5dedc811e 100644 --- a/src/lib/settings/GatewayDriver.js +++ b/src/lib/settings/GatewayDriver.js @@ -239,7 +239,7 @@ class GatewayDriver { /** * Readies up all Gateways and Configuration instances * @since 0.5.0 - * @returns {Promise>>>} + * @returns {Array>>} * @private */ async _ready() { diff --git a/src/lib/settings/GatewayStorage.js b/src/lib/settings/GatewayStorage.js index edda248dbc..e5619e1321 100644 --- a/src/lib/settings/GatewayStorage.js +++ b/src/lib/settings/GatewayStorage.js @@ -138,7 +138,7 @@ class GatewayStorage { /** * Inits the schema, creating a file if it does not exist, and returning the current schema or the default. * @since 0.5.0 - * @returns {Promise} + * @returns {SchemaFolder} * @private */ async initSchema() { diff --git a/src/lib/settings/SchemaPiece.js b/src/lib/settings/SchemaPiece.js index a4472d42de..b0f8f2a6a9 100644 --- a/src/lib/settings/SchemaPiece.js +++ b/src/lib/settings/SchemaPiece.js @@ -126,7 +126,7 @@ class SchemaPiece extends Schema { * @since 0.5.0 * @param {string} value The value to parse * @param {KlasaGuild} guild A Guild instance required for the resolver to work - * @returns {Promise<*>} + * @returns {*} */ async parse(value, guild) { const resolved = await this.gateway.resolver[this.type](value, guild, this.key, { min: this.min, max: this.max }); @@ -138,7 +138,7 @@ class SchemaPiece extends Schema { * Modify this SchemaPiece's properties. * @since 0.5.0 * @param {SchemaPieceEditOptions} options The new options - * @returns {Promise} + * @returns {this} */ async edit(options) { // Check if the 'options' parameter is an object. diff --git a/src/lib/structures/Command.js b/src/lib/structures/Command.js index b8cc39d53f..428527dcb4 100644 --- a/src/lib/structures/Command.js +++ b/src/lib/structures/Command.js @@ -273,7 +273,7 @@ class Command extends Piece { * @since 0.0.1 * @param {KlasaMessage} msg The command message mapped on top of the message used to trigger this command * @param {any[]} params The fully resolved parameters based on your usage / usageDelim - * @returns {Promise} You should return the response message whenever possible + * @returns {KlasaMessage|KlasaMessage[]} You should return the response message whenever possible * @abstract */ async run(msg) { @@ -284,7 +284,7 @@ class Command extends Piece { /** * The init method to be optionally overwritten in actual commands * @since 0.0.1 - * @returns {Promise<*>} + * @returns {*} * @abstract */ async init() { diff --git a/src/lib/structures/Inhibitor.js b/src/lib/structures/Inhibitor.js index 5ccbb2172b..b6102e13dd 100644 --- a/src/lib/structures/Inhibitor.js +++ b/src/lib/structures/Inhibitor.js @@ -41,7 +41,7 @@ class Inhibitor extends Piece { * @since 0.0.1 * @param {KlasaMessage} msg The message that triggered this inhibitor * @param {Command} cmd The command to run - * @returns {Promise<(void|string)>} + * @returns {(void|string)} * @abstract */ async run() { @@ -52,7 +52,7 @@ class Inhibitor extends Piece { /** * The init method to be optionally overwritten in actual inhibitors * @since 0.0.1 - * @returns {Promise} + * @returns {void} * @abstract */ async init() { diff --git a/src/lib/structures/Provider.js b/src/lib/structures/Provider.js index 6a103b52a5..02c5ee7fee 100644 --- a/src/lib/structures/Provider.js +++ b/src/lib/structures/Provider.js @@ -46,7 +46,7 @@ class Provider extends Piece { /** * The init method to be optionally overwritten in actual provider pieces * @since 0.0.1 - * @returns {Promise} + * @returns {void} * @abstract */ async init() { diff --git a/src/lib/structures/Task.js b/src/lib/structures/Task.js index b27539a628..de91884417 100644 --- a/src/lib/structures/Task.js +++ b/src/lib/structures/Task.js @@ -32,17 +32,17 @@ class Task extends Piece { * The run method to be overwritten in actual Task pieces * @since 0.5.0 * @param {*} data The data from the ScheduledTask instance - * @returns {Promise} + * @returns {void} * @abstract */ - run() { + async run() { // Defined in extension Classes } /** * The init method to be optionally overwritten in actual Task pieces * @since 0.5.0 - * @returns {Promise} + * @returns {void} * @abstract */ async init() { diff --git a/src/lib/usage/TextPrompt.js b/src/lib/usage/TextPrompt.js index 5f967c8f02..ad97b10b66 100644 --- a/src/lib/usage/TextPrompt.js +++ b/src/lib/usage/TextPrompt.js @@ -132,7 +132,7 @@ class TextPrompt { * Runs the custom prompt. * @since 0.5.0 * @param {string} prompt The message to initially prompt with - * @returns {Promise} The parameters resolved + * @returns {any[]} The parameters resolved */ async run(prompt) { const message = await this.message.prompt(prompt, this.promptTime); @@ -144,7 +144,7 @@ class TextPrompt { * Collects missing required arguments. * @since 0.5.0 * @param {string} prompt The reprompt error - * @returns {Promise} + * @returns {any[]} * @private */ async reprompt(prompt) { @@ -171,7 +171,7 @@ class TextPrompt { /** * Collects repeating arguments. * @since 0.5.0 - * @returns {Promise} + * @returns {any[]} * @private */ async repeatingPrompt() { @@ -199,7 +199,7 @@ class TextPrompt { /** * Validates and resolves args into parameters * @since 0.0.1 - * @returns {Promise} The resolved parameters + * @returns {any[]} The resolved parameters * @private */ async validateArgs() { @@ -222,7 +222,7 @@ class TextPrompt { * Validates and resolves args into parameters, when multiple types of usage is defined * @since 0.0.1 * @param {number} index The id of the possible usage currently being checked - * @returns {Promise} The resolved parameters + * @returns {any[]} The resolved parameters * @private */ async multiPossibles(index) { @@ -275,7 +275,7 @@ class TextPrompt { * Decides if the prompter should reprompt or throw the error found while validating. * @since 0.5.0 * @param {string} err The error found while validating - * @returns {Promise} + * @returns {any[]} * @private */ async handleError(err) { From c686e160005b7cc1c199a49bdf51ff8c47926611 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 18:44:24 +0100 Subject: [PATCH 33/76] Fixed SchemaFolder#add --- src/lib/settings/SchemaFolder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index af1622b856..bf6ac7c33c 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -301,7 +301,7 @@ class SchemaFolder extends Schema { if (!options) throw 'You must pass an options argument to this method.'; if (typeof options.type !== 'string') throw 'The option type is required and must be a string.'; options.type = options.type.toLowerCase(); - if (!this.client.gateways.types.includes(options.type)) throw `The type ${options.type} is not supported.`; + if (!this.client.gateways.types.has(options.type)) throw `The type ${options.type} is not supported.`; if (typeof options.min !== 'undefined' && isNaN(options.min)) throw 'The option min must be a number.'; if (typeof options.max !== 'undefined' && isNaN(options.max)) throw 'The option max must be a number.'; if (typeof options.array !== 'undefined' && typeof options.array !== 'boolean') throw 'The option array must be a boolean.'; From a016927af7325f8115601c57d7cdb501ca280028 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 18:49:34 +0100 Subject: [PATCH 34/76] Fixed old cache methods --- src/lib/settings/Configuration.js | 2 +- src/lib/settings/SchemaFolder.js | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 086399287c..e172589510 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -182,7 +182,7 @@ class Configuration { await this.gateway.provider.delete(this.gateway.type, this.id); if (this.client.listenerCount('configDeleteEntry')) this.client.emit('configDeleteEntry', this); } - this.gateway.cache.delete(this.gateway.type, this.id); + this.gateway.cache.delete(this.id); return this; } diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index bf6ac7c33c..1fef6761b4 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -179,13 +179,11 @@ class SchemaFolder extends Schema { force(action, key, piece) { if (!(piece instanceof SchemaPiece) && !(piece instanceof SchemaFolder)) throw new TypeError(`'schemaPiece' must be an instance of 'SchemaPiece' or an instance of 'SchemaFolder'.`); - const values = this.gateway.cache.getValues(this.gateway.type); const path = piece.path.split('.'); if (action === 'add' || action === 'edit') { const defValue = this.defaults[key]; - for (let i = 0; i < values.length; i++) { - let value = values[i]; + for (let value of this.gateway.cache.values()) { for (let j = 0; j < path.length - 1; j++) value = value[path[j]]; value[path[path.length - 1]] = deepClone(defValue); } @@ -193,8 +191,7 @@ class SchemaFolder extends Schema { } if (action === 'delete') { - for (let i = 0; i < values.length; i++) { - let value = values[i]; + for (let value of this.gateway.cache.values()) { for (let j = 0; j < path.length - 1; j++) value = value[path[j]]; delete value[path[path.length - 1]]; } From 72c0cdfc369290104434ec535c1a282c7a0ef2c5 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 19:05:09 +0100 Subject: [PATCH 35/76] Fixed Configuration#reset() not creating the right object --- src/lib/settings/Configuration.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index e172589510..2fc6d04ddc 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -202,12 +202,14 @@ class Configuration { const list = { keys: [], values: [] }; this._resetAll(this.gateway.schema, this, list); - if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, list.keys); - if (this.gateway.sql) { - await this.gateway.provider.update(this.gateway.type, this.id, list.keys, list.values); - } else { - const updateObject = Object.assign(list.keys.map((key, i) => makeObject(key, list.values[i]))); - await this.gateway.provider.update(this.gateway.type, this.id, updateObject); + if (list.keys.length) { + if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, list.keys); + if (this.gateway.sql) { + await this.gateway.provider.update(this.gateway.type, this.id, list.keys, list.values); + } else { + const updateObject = Object.assign({}, ...list.keys.map((key, i) => makeObject(key, list.values[i]))); + await this.gateway.provider.update(this.gateway.type, this.id, updateObject); + } } return { keys: list.keys, values: list.values }; From 1f07d15cff2d631fcf1f08bd68304826910ff758 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 19:54:41 +0100 Subject: [PATCH 36/76] Added `Configuration#reset(string[]);` --- CHANGELOG.md | 1 + src/lib/settings/Configuration.js | 43 +++++++++++++++++++------------ typings/index.d.ts | 4 +-- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 700fcd7925..477d5fc834 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Added +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Added support for `Configuration#reset(string[]);` to reset multiple keys. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `util.arraysEqual`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added property `Symbol.iterator` to Schedule. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `Gateway#toJSON()` and `GatewayDriver#toJSON()`. (kyranet) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 2fc6d04ddc..9b12986f94 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -189,18 +189,27 @@ class Configuration { /** * Reset a value from an entry. * @since 0.5.0 - * @param {string} [resetKey] The key to reset + * @param {(string|string[])} [resetKey] The key to reset * @param {boolean} [avoidUnconfigurable] Whether the Gateway should avoid configuring the selected key * @returns {ConfigurationUpdateResult|ConfigurationUpdateObjectList} + * // Reset all keys for this instance + * Configuration#reset(); + * + * // Reset multiple keys for this instance + * Configuration#reset(['prefix', 'channels.modlog']); + * + * // Reset a key + * Configuration#reset('prefix'); */ async reset(resetKey, avoidUnconfigurable) { - if (typeof resetKey === 'undefined') { + if (typeof resetKey === 'undefined') resetKey = [...this.gateway.schema.keys(true)]; + if (Array.isArray(resetKey)) { // Handle entry creation if it does not exist. if (!this._existsInDB) return { keys: [], values: [] }; const oldClone = this.client.listenerCount('configUpdateEntry') ? this.clone() : null; const list = { keys: [], values: [] }; - this._resetAll(this.gateway.schema, this, list); + this._resetKeys(resetKey, list); if (list.keys.length) { if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, list.keys); @@ -335,25 +344,27 @@ class Configuration { /** * Resets all keys recursively * @since 0.5.0 - * @param {SchemaFolder} schema The SchemaFolder to iterate - * @param {Object} configs The configs in the selected folder + * @param {string[]} schema The SchemaFolder to iterate * @param {ConfigurationUpdateManyList} list The list * @private */ - _resetAll(schema, configs, list) { - for (const [key, piece] of schema) { - if (piece.type === 'Folder') { - this._resetAll(piece, configs[key], list); - continue; - } - const value = (piece.array ? !arraysEqual(configs[key], piece.default, true) : configs[key] !== piece.default) ? - deepClone(piece.default) : + _resetKeys(keys, list) { + for (const key of keys) { + const { path, route } = this.gateway.getPath(key, { piece: true }); + + let self = this; // eslint-disable-line consistent-this + for (let i = 0; i < route.length - 1; i++) self = self[route[i]] || {}; + const currentValue = self[route[route.length - 1]]; + + const value = (path.array ? !arraysEqual(currentValue, path.default, true) : currentValue !== path.default) ? + deepClone(path.default) : invalidValue; if (value === invalidValue) continue; - configs[key] = value; - list.keys.push(piece.path); - list.values.push(configs[key]); + self[route[route.length - 1]] = value; + + list.keys.push(path.path); + list.values.push(value); } } diff --git a/typings/index.d.ts b/typings/index.d.ts index 41f7348c3d..5eaaf138a3 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -525,7 +525,7 @@ declare module 'klasa' { public sync(): Promise; public destroy(): Promise; - public reset(): Promise; + public reset(keys?: string[]): Promise; public reset(key: string, avoidUnconfigurable?: boolean): Promise; public update(key: object, guild?: GatewayGuildResolvable): Promise; public update(key: string, value?: any, options?: ConfigurationUpdateOptions): Promise; @@ -533,7 +533,7 @@ declare module 'klasa' { public list(msg: KlasaMessage, path: SchemaFolder | string): string; public resolveString(msg: KlasaMessage, path: SchemaPiece | string): string; - private _resetAll(schema: SchemaFolder, configs: object, list: ConfigurationUpdateManyList): void; + private _resetKeys(keys: string[], list: ConfigurationUpdateManyList): void; private _updateMany(object: any, guild?: GatewayGuildResolvable): Promise; private _reset(key: string, guild: GatewayGuildResolvable, avoidUnconfigurable: boolean): Promise; private _parseReset(key: string, guild: KlasaGuild, options: ConfigurationPathResult): Promise; From f2d8f34781fae9dd0749841c7c93184a5b1ef5e7 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 19:59:00 +0100 Subject: [PATCH 37/76] Fixed an edge case in super-nested keys --- src/lib/settings/Configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 9b12986f94..f2d76a16b3 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -691,7 +691,7 @@ class Configuration { * @param {SchemaFolder} schema A SchemaFolder instance * @private */ - static _patch(inst, data, schema) { + static _patch(inst = {}, data, schema) { for (let i = 0; i < schema.keyArray.length; i++) { const key = schema.keyArray[i]; if (typeof data[key] === 'undefined') continue; From 4427dca4c2fd83093ce3001a322848f1889ab20c Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 20:11:52 +0100 Subject: [PATCH 38/76] Fixed deep merger --- src/lib/settings/Configuration.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index f2d76a16b3..41a366fe50 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -643,10 +643,10 @@ class Configuration { */ static _merge(data, schema) { if (schema.type === 'Folder') { - if (typeof data === 'undefined') data = {}; - for (let i = 0; i < schema.keyArray.length; i++) { - const key = schema.keyArray[i]; - data[key] = Configuration._merge(data[key], schema[key]); + if (!data) data = {}; + for (const [key, piece] of schema.entries()) { + if (!data[key]) data[key] = {}; + Configuration._merge(data[key], schema[key]); } } else if (typeof data === 'undefined') { // It's a SchemaPiece instance, so it has a property of 'key'. @@ -691,9 +691,8 @@ class Configuration { * @param {SchemaFolder} schema A SchemaFolder instance * @private */ - static _patch(inst = {}, data, schema) { - for (let i = 0; i < schema.keyArray.length; i++) { - const key = schema.keyArray[i]; + static _patch(inst, data, schema) { + for (const key of schema.keys()) { if (typeof data[key] === 'undefined') continue; inst[key] = schema[key].type === 'Folder' ? Configuration._patch(inst[key], data[key], schema[key]) : From 17c5094a1a25a5fd93520a1114c2e697ece746f2 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 21:37:40 +0100 Subject: [PATCH 39/76] Fix Configuration._patch --- CHANGELOG.md | 1 + src/lib/settings/Configuration.js | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 477d5fc834..6c5c98f36f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -180,6 +180,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Fixed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed `Configuration._patch` not patching after the second nested folder. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed SettingResolver's return types. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed Gateway syncing keys even when it's unused. (kyranet) - [[#184](https://github.com/dirigeants/klasa/pull/184)] Fixed classes and options missing methods and properties in typings. (kyranet) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 41a366fe50..d76d566437 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -344,7 +344,7 @@ class Configuration { /** * Resets all keys recursively * @since 0.5.0 - * @param {string[]} schema The SchemaFolder to iterate + * @param {string[]} keys The SchemaFolder to iterate * @param {ConfigurationUpdateManyList} list The list * @private */ @@ -610,8 +610,9 @@ class Configuration { for (let i = 0; i < schema.keyArray.length; i++) { const key = schema.keyArray[i]; if (typeof data[key] === 'undefined') continue; - if (schema[key].type === 'Folder') Configuration._patch(this[key], data[key], schema[key]); - else this[key] = data[key]; + this[key] = schema[key].type === 'Folder' ? + Configuration._patch(this[key], data[key], schema[key]) : + data[key]; } } @@ -646,7 +647,7 @@ class Configuration { if (!data) data = {}; for (const [key, piece] of schema.entries()) { if (!data[key]) data[key] = {}; - Configuration._merge(data[key], schema[key]); + Configuration._merge(data[key], piece); } } else if (typeof data === 'undefined') { // It's a SchemaPiece instance, so it has a property of 'key'. @@ -689,6 +690,7 @@ class Configuration { * @param {Object} inst The reference of the Configuration instance * @param {Object} data The original object * @param {SchemaFolder} schema A SchemaFolder instance + * @returns {Object} * @private */ static _patch(inst, data, schema) { @@ -698,6 +700,8 @@ class Configuration { Configuration._patch(inst[key], data[key], schema[key]) : data[key]; } + + return inst; } } From c4248efa8a70916f960db6de10706b0659c784eb Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 21:50:31 +0100 Subject: [PATCH 40/76] Fixed critical bug in SGv2 shard support --- CHANGELOG.md | 1 + src/events/configUpdateEntry.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c5c98f36f..1edadc5896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -180,6 +180,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Fixed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed the **configUpdateEntry** event (used to sync configuration instances across shards) running in non-sharded bots, now it will be disabled if the bot is not sharded. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed `Configuration._patch` not patching after the second nested folder. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed SettingResolver's return types. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed Gateway syncing keys even when it's unused. (kyranet) diff --git a/src/events/configUpdateEntry.js b/src/events/configUpdateEntry.js index 212a4c30a5..ceff85223d 100644 --- a/src/events/configUpdateEntry.js +++ b/src/events/configUpdateEntry.js @@ -20,4 +20,8 @@ module.exports = class extends Event { } } + init() { + if (!this.client.shard) this.disable(); + } + }; From 0f85f8600d32789afce078f92ecf9094e23b5b18 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 22:14:48 +0100 Subject: [PATCH 41/76] Generate better SchemaPiece defaults --- src/lib/settings/SchemaPiece.js | 17 +++++++++++++++-- typings/index.d.ts | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib/settings/SchemaPiece.js b/src/lib/settings/SchemaPiece.js index b0f8f2a6a9..75918bdc7c 100644 --- a/src/lib/settings/SchemaPiece.js +++ b/src/lib/settings/SchemaPiece.js @@ -63,7 +63,7 @@ class SchemaPiece extends Schema { * @type {*} * @name SchemaPiece#default */ - this.default = typeof options.default !== 'undefined' ? options.default : this.type === 'boolean' ? false : null; + this.default = typeof options.default !== 'undefined' ? options.default : this._generateDefault(); /** * The minimum value for this key. @@ -183,6 +183,18 @@ class SchemaPiece extends Schema { return this; } + /** + * Generate a default value if none is given + * @since 0.5.0 + * @returns {(Array<*>|false|null)} + * @private + */ + _generateDefault() { + if (this.array) return []; + if (this.type === 'boolean') return false; + return null; + } + /** * Checks if options.type is valid. * @since 0.5.0 @@ -285,6 +297,8 @@ class SchemaPiece extends Schema { */ _init(options) { if (this._inited) throw new TypeError(`[INIT] ${this} - Is already init. Aborting re-init.`); + this._inited = true; + // Check if the 'options' parameter is an object. if (!isObject(options)) throw new TypeError(`SchemaPiece#init expected an object as a parameter. Got: ${typeof options}`); this._schemaCheckType(this.type); @@ -294,7 +308,6 @@ class SchemaPiece extends Schema { this._schemaCheckConfigurable(this.configurable); this.sql[1] = this._generateSQLDatatype(options.sql); - this._inited = true; return true; } diff --git a/typings/index.d.ts b/typings/index.d.ts index 5eaaf138a3..d5e7d39137 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -688,6 +688,7 @@ declare module 'klasa' { public parse(value: any, guild: KlasaGuild): Promise; public modify(options: SchemaPieceEditOptions): Promise; + private _generateDefault(): [] | false | null; private _schemaCheckType(type: string): void; private _schemaCheckArray(array: boolean): void; private _schemaCheckDefault(options: SchemaFolderAddOptions): void; From bd6040a260581a8c3320f64b7c4381807ac76ee5 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 22:46:50 +0100 Subject: [PATCH 42/76] Fixed a bug --- src/lib/settings/Configuration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index d76d566437..761fc8275b 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -657,8 +657,8 @@ class Configuration { // Some SQL databases are unable to store Arrays... if (data === null) return deepClone(schema.default); if (typeof data === 'string') return tryParse(data); - this.client.emit('wtf', - new TypeError(`${this} - ${schema.path} | Expected an array, null, or undefined. Got: ${Object.prototype.toString.call(data)}`)); + schema.client.emit('wtf', + new TypeError(`${schema.path} | Expected an array, null, or undefined. Got: ${Object.prototype.toString.call(data)}`)); } return data; From ff927c1ccbe15fcb40fbd2a18f59e4802b1b5f45 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 23:15:15 +0100 Subject: [PATCH 43/76] Better defaults --- src/lib/settings/Configuration.js | 2 +- src/lib/settings/SchemaFolder.js | 38 +++++++++++++++---------------- src/lib/settings/SchemaPiece.js | 20 ++++++---------- 3 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 761fc8275b..07f81cdd39 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -434,7 +434,7 @@ class Configuration { * @private */ async _parseUpdateOne(key, value, guild, { path, route }) { - if (path.array === true) throw 'This key is array type.'; + if (path.array === true) throw new Error('This key is array type.'); const parsed = await path.parse(value, guild); const parsedID = getIdentifier(parsed); diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 1fef6761b4..88ccabb069 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -1,6 +1,6 @@ const SchemaPiece = require('./SchemaPiece'); const Schema = require('./Schema'); -const { deepClone, isObject } = require('../util/util'); +const { deepClone, isObject, isNumber } = require('../util/util'); const fs = require('fs-nextra'); /** @@ -85,7 +85,7 @@ class SchemaFolder extends Schema { * }); * * // Add an empty new SchemaFolder - * SchemaFolder.add('channels', {}); + * SchemaFolder.add('channels'); * * // Optionally, you can set the type Folder, * // if no type is set, 'Folder' will be implied. @@ -100,10 +100,10 @@ class SchemaFolder extends Schema { * } * }); */ - async add(key, options, force = true) { - if (this.has(key)) throw `The key ${key} already exists in the current schema.`; - if (typeof this[key] !== 'undefined') throw `The key ${key} conflicts with a property of Schema.`; - if (!options || !isObject(options)) throw `The options object is required.`; + async add(key, options = {}, force = true) { + if (this.has(key)) throw new Error(`The key ${key} already exists in the current schema.`); + if (typeof this[key] !== 'undefined') throw new Error(`The key ${key} conflicts with a property of Schema.`); + if (!options || !isObject(options)) throw new Error(`The options object is required.`); if (typeof options.type !== 'string' || options.type.toLowerCase() === 'folder') options.type = 'Folder'; // Create the piece and save the current schema @@ -293,22 +293,22 @@ class SchemaFolder extends Schema { * @private */ _verifyKeyOptions(key, options) { - if (this.has(key)) throw `The key ${key} already exists in the current schema.`; - if (typeof this[key] !== 'undefined') throw `The key ${key} conflicts with a property of Schema.`; - if (!options) throw 'You must pass an options argument to this method.'; - if (typeof options.type !== 'string') throw 'The option type is required and must be a string.'; + if (!options) throw new Error('You must pass an options argument to this method.'); + if (typeof options.type !== 'string') throw new TypeError('The option type is required and must be a string.'); options.type = options.type.toLowerCase(); - if (!this.client.gateways.types.has(options.type)) throw `The type ${options.type} is not supported.`; - if (typeof options.min !== 'undefined' && isNaN(options.min)) throw 'The option min must be a number.'; - if (typeof options.max !== 'undefined' && isNaN(options.max)) throw 'The option max must be a number.'; - if (typeof options.array !== 'undefined' && typeof options.array !== 'boolean') throw 'The option array must be a boolean.'; - if (typeof options.configurable !== 'undefined' && typeof options.configurable !== 'boolean') throw 'The option configurable must be a boolean.'; + if (!this.client.gateways.types.has(options.type)) throw new TypeError(`The type ${options.type} is not supported.`); + if ('min' in options && options.min !== null && !isNumber(options.min)) throw new TypeError('The option min must be a number or null.'); + if ('max' in options && options.max !== null && !isNumber(options.max)) throw new TypeError('The option max must be a number or null.'); + if ('array' in options && typeof options.array !== 'boolean') throw new TypeError('The option array must be a boolean.'); + if ('configurable' in options && typeof options.configurable !== 'boolean') throw new TypeError('The option configurable must be a boolean.'); + if (!('array' in options)) options.array = Array.isArray(options.default); + if (options.array) { - if (typeof options.default === 'undefined') options.default = []; - else if (!Array.isArray(options.default)) throw 'The option default must be an array if the array option is set to true.'; + if (!('default' in options)) options.default = []; + else if (!Array.isArray(options.default)) throw new TypeError('The option default must be an array if the array option is set to true.'); } else { - if (typeof options.default === 'undefined') options.default = options.type === 'boolean' ? false : null; - options.array = false; + if (!('default' in options)) options.default = options.type === 'boolean' ? false : null; + if (Array.isArray(options.default)) throw new TypeError('The option default must not be an array if the array option is set to false.'); } return options; } diff --git a/src/lib/settings/SchemaPiece.js b/src/lib/settings/SchemaPiece.js index 75918bdc7c..9e818df9bb 100644 --- a/src/lib/settings/SchemaPiece.js +++ b/src/lib/settings/SchemaPiece.js @@ -55,7 +55,7 @@ class SchemaPiece extends Schema { * @type {boolean} * @name SchemaPiece#array */ - this.array = typeof options.array !== 'undefined' ? options.array : false; + this.array = 'array' in options ? options.array : Array.isArray(options.default); /** * What this key should provide by default. @@ -63,7 +63,7 @@ class SchemaPiece extends Schema { * @type {*} * @name SchemaPiece#default */ - this.default = typeof options.default !== 'undefined' ? options.default : this._generateDefault(); + this.default = 'default' in options ? options.default : this._generateDefault(); /** * The minimum value for this key. @@ -71,7 +71,7 @@ class SchemaPiece extends Schema { * @type {?number} * @name SchemaPiece#min */ - this.min = typeof options.min !== 'undefined' && isNaN(options.min) === false ? options.min : null; + this.min = 'min' in options ? options.min : null; /** * The maximum value for this key. @@ -79,7 +79,7 @@ class SchemaPiece extends Schema { * @type {?number} * @name SchemaPiece#max */ - this.max = typeof options.max !== 'undefined' && isNaN(options.max) === false ? options.max : null; + this.max = 'max' in options ? options.max : null; /** * A tuple of strings containing the path and the datatype. @@ -87,7 +87,7 @@ class SchemaPiece extends Schema { * @type {string[]} * @name SchemaPiece#sql */ - this.sql = [this.path]; + this.sql = [this.path, null]; /** * Whether this key should be configureable by the config command. When type is any, this key defaults to false. @@ -95,7 +95,7 @@ class SchemaPiece extends Schema { * @type {boolean} * @name SchemaPiece#configurable */ - this.configurable = typeof options.configurable !== 'undefined' ? options.configurable : this.type !== 'any'; + this.configurable = 'configurable' in options ? options.configurable : this.type !== 'any'; /** * The validator function for this instance. @@ -130,7 +130,7 @@ class SchemaPiece extends Schema { */ async parse(value, guild) { const resolved = await this.gateway.resolver[this.type](value, guild, this.key, { min: this.min, max: this.max }); - if (this.validator) this.validator(resolved, guild); + if (this.validator) await this.validator(resolved, guild); return resolved; } @@ -301,12 +301,6 @@ class SchemaPiece extends Schema { // Check if the 'options' parameter is an object. if (!isObject(options)) throw new TypeError(`SchemaPiece#init expected an object as a parameter. Got: ${typeof options}`); - this._schemaCheckType(this.type); - this._schemaCheckArray(this.array); - this._schemaCheckDefault(this); - this._schemaCheckLimits(this.min, this.max); - this._schemaCheckConfigurable(this.configurable); - this.sql[1] = this._generateSQLDatatype(options.sql); return true; From 3b8944e93e3ea13736b84bded8bc218874efa783 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 23:18:39 +0100 Subject: [PATCH 44/76] Fixed example running off the site --- src/lib/settings/GatewayDriver.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/settings/GatewayDriver.js b/src/lib/settings/GatewayDriver.js index b5dedc811e..659ca49570 100644 --- a/src/lib/settings/GatewayDriver.js +++ b/src/lib/settings/GatewayDriver.js @@ -217,7 +217,8 @@ class GatewayDriver { * @param {boolean} [download=true] Whether this Gateway should download the data from the database at init * @returns {Gateway} * @example - * // Add a new SettingGateway instance, called 'users', which input takes users, and stores a quote which is a string between 2 and 140 characters. + * // Add a new SettingGateway instance, called 'users', which input takes users, + * and stores a quote which is a string between 2 and 140 characters. * const schema = { * quote: { * type: 'String', From 982c4a147d597eca1027268e508de0e0ae8bf400 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 23:25:16 +0100 Subject: [PATCH 45/76] Fixed example for GatewayDriver#add --- src/lib/settings/GatewayDriver.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/lib/settings/GatewayDriver.js b/src/lib/settings/GatewayDriver.js index 659ca49570..77d636bab2 100644 --- a/src/lib/settings/GatewayDriver.js +++ b/src/lib/settings/GatewayDriver.js @@ -218,17 +218,19 @@ class GatewayDriver { * @returns {Gateway} * @example * // Add a new SettingGateway instance, called 'users', which input takes users, - * and stores a quote which is a string between 2 and 140 characters. - * const schema = { - * quote: { - * type: 'String', - * default: null, - * array: false, - * min: 2, - * max: 140, - * }, - * }; - * GatewayDriver.add('users', schema); + * // and stores a quote which is a string between 2 and 140 characters. + * GatewayDriver.add('channels', { + * disabledCommands: { + * type: 'Command', + * default: [] + * }, + * commandThrottle: { + * type: 'Integer', + * default: 5, + * min: 0, + * max: 60 + * } + * }); */ async add(name, schema = {}, options = {}, download = true) { const gateway = this._register(name, schema, options); From 3d24e03e4f4f9755f63bfd65b406935a26dbcd13 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 23:31:27 +0100 Subject: [PATCH 46/76] Give the example some sense --- src/lib/settings/GatewayDriver.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/settings/GatewayDriver.js b/src/lib/settings/GatewayDriver.js index 77d636bab2..5b6b67ef7f 100644 --- a/src/lib/settings/GatewayDriver.js +++ b/src/lib/settings/GatewayDriver.js @@ -217,9 +217,9 @@ class GatewayDriver { * @param {boolean} [download=true] Whether this Gateway should download the data from the database at init * @returns {Gateway} * @example - * // Add a new SettingGateway instance, called 'users', which input takes users, - * // and stores a quote which is a string between 2 and 140 characters. - * GatewayDriver.add('channels', { + * // Add a new SettingGateway instance, called 'channels', that stores + * // disabled commands and a command throttle for custom ratelimits. + * this.client.gateways.add('channels', { * disabledCommands: { * type: 'Command', * default: [] From 4c78f13172974c7380877b9eb2533ac805733b51 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 23:39:43 +0100 Subject: [PATCH 47/76] (typedef) emoji -> Emoji --- src/lib/util/ReactionHandler.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/util/ReactionHandler.js b/src/lib/util/ReactionHandler.js index e4fead962f..5160344717 100644 --- a/src/lib/util/ReactionHandler.js +++ b/src/lib/util/ReactionHandler.js @@ -8,7 +8,7 @@ class ReactionHandler extends ReactionCollector { /** * A single unicode character - * @typedef {string} emoji + * @typedef {string} Emoji * @memberof ReactionHandler */ @@ -32,7 +32,7 @@ class ReactionHandler extends ReactionCollector { * @param {Function} filter A filter function to determine which emoji reactions should be handled * @param {ReactionHandlerOptions} options The options for this ReactionHandler * @param {(RichDisplay|RichMenu)} display The RichDisplay or RichMenu that this handler is for - * @param {emoji[]} emojis The emojis which should be used in this handler + * @param {Emoji[]} emojis The emojis which should be used in this handler */ constructor(msg, filter, options, display, emojis) { super(msg, filter, options); @@ -47,7 +47,7 @@ class ReactionHandler extends ReactionCollector { /** * An emoji to method map, to map custom emojis to static method names * @since 0.4.0 - * @type {Map} + * @type {Map} */ this.methodMap = new Map(Object.entries(this.display.emojis).map(([key, value]) => [value, key])); @@ -324,7 +324,7 @@ class ReactionHandler extends ReactionCollector { /** * The action to take when the "first" emoji is reacted * @since 0.4.0 - * @param {emoji[]} emojis The remaining emojis to react + * @param {Emoji[]} emojis The remaining emojis to react * @returns {null} * @private */ From ed97624f87aae8b85de76339f3be016fd6cba42d Mon Sep 17 00:00:00 2001 From: kyraNET Date: Mon, 19 Feb 2018 23:44:20 +0100 Subject: [PATCH 48/76] Fixed Emoji in RM and RD, RichMenuEmojisObject extend RichDisplayEmojisObject --- src/lib/util/RichDisplay.js | 20 ++++++++++---------- src/lib/util/RichMenu.js | 35 ++++++++++++++--------------------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/lib/util/RichDisplay.js b/src/lib/util/RichDisplay.js index baeaeeee62..aa6bec0dc5 100644 --- a/src/lib/util/RichDisplay.js +++ b/src/lib/util/RichDisplay.js @@ -8,19 +8,19 @@ class RichDisplay { /** * A single unicode character - * @typedef {string} emoji + * @typedef {string} Emoji * @memberof RichDisplay */ /** * @typedef {Object} RichDisplayEmojisObject - * @property {emoji} first The emoji for the 'first' button - * @property {emoji} back The emoji for the 'back' button - * @property {emoji} forward The emoji for the 'forward' button - * @property {emoji} last The emoji for the 'last' button - * @property {emoji} jump The emoji for the 'jump' button - * @property {emoji} info The emoji for the 'info' button - * @property {emoji} stop The emoji for the 'stop' button + * @property {Emoji} first The emoji for the 'first' button + * @property {Emoji} back The emoji for the 'back' button + * @property {Emoji} forward The emoji for the 'forward' button + * @property {Emoji} last The emoji for the 'last' button + * @property {Emoji} jump The emoji for the 'jump' button + * @property {Emoji} info The emoji for the 'info' button + * @property {Emoji} stop The emoji for the 'stop' button * @memberof RichDisplay */ @@ -226,11 +226,11 @@ class RichDisplay { /** * Determines the emojis to use in this display * @since 0.4.0 - * @param {emoji[]} emojis An array of emojis to use + * @param {Emoji[]} emojis An array of emojis to use * @param {boolean} stop Whether the stop emoji should be included * @param {boolean} jump Whether the jump emoji should be included * @param {boolean} firstLast Whether the first & last emojis should be included - * @returns {emoji[]} + * @returns {Emoji[]} * @private */ _determineEmojis(emojis, stop, jump, firstLast) { diff --git a/src/lib/util/RichMenu.js b/src/lib/util/RichMenu.js index 25b2a5636e..19bbfe7895 100644 --- a/src/lib/util/RichMenu.js +++ b/src/lib/util/RichMenu.js @@ -8,29 +8,22 @@ class RichMenu extends RichDisplay { /** * A single unicode character - * @typedef {string} emoji + * @typedef {string} Emoji * @memberof RichMenu */ /** - * @typedef {Object} RichMenuEmojisObject - * @property {emoji} first The emoji for the 'first' button - * @property {emoji} back The emoji for the 'back' button - * @property {emoji} forward The emoji for the 'forward' button - * @property {emoji} last The emoji for the 'last' button - * @property {emoji} jump The emoji for the 'jump' button - * @property {emoji} info The emoji for the 'info' button - * @property {emoji} stop The emoji for the 'stop' button - * @property {emoji} zero The emoji for the 'zero' button - * @property {emoji} one The emoji for the 'one' button - * @property {emoji} two The emoji for the 'two' button - * @property {emoji} three The emoji for the 'three' button - * @property {emoji} four The emoji for the 'four' button - * @property {emoji} five The emoji for the 'five' button - * @property {emoji} six The emoji for the 'six' button - * @property {emoji} seven The emoji for the 'seven' button - * @property {emoji} eight The emoji for the 'eight' button - * @property {emoji} nine The emoji for the 'nine' button + * @typedef {RichDisplayEmojisObject} RichMenuEmojisObject + * @property {Emoji} zero The emoji for the 'zero' button + * @property {Emoji} one The emoji for the 'one' button + * @property {Emoji} two The emoji for the 'two' button + * @property {Emoji} three The emoji for the 'three' button + * @property {Emoji} four The emoji for the 'four' button + * @property {Emoji} five The emoji for the 'five' button + * @property {Emoji} six The emoji for the 'six' button + * @property {Emoji} seven The emoji for the 'seven' button + * @property {Emoji} eight The emoji for the 'eight' button + * @property {Emoji} nine The emoji for the 'nine' button * @memberof RichMenu */ @@ -134,11 +127,11 @@ class RichMenu extends RichDisplay { /** * Determines the emojis to use in this menu * @since 0.4.0 - * @param {emoji[]} emojis An array of emojis to use + * @param {Emoji[]} emojis An array of emojis to use * @param {boolean} stop Whether the stop emoji should be included * @param {boolean} jump Whether the jump emoji should be included * @param {boolean} firstLast Whether the first & last emojis should be included - * @returns {emoji[]} + * @returns {Emoji[]} * @private */ _determineEmojis(emojis, stop, jump, firstLast) { From 861efbf4f69f94aaf13a09c296426f0866c81f9a Mon Sep 17 00:00:00 2001 From: kyraNET Date: Thu, 22 Feb 2018 22:33:15 +0100 Subject: [PATCH 49/76] Way much safer and faster Configuration#_merge --- src/lib/settings/Configuration.js | 27 ++++++-------- src/lib/settings/SchemaFolder.js | 60 +++++++++++++++++-------------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 07f81cdd39..2504c10ab5 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -120,8 +120,7 @@ class Configuration { Object.defineProperty(this, '_syncStatus', { value: null, writable: true }); const { schema } = this.gateway; - for (let i = 0; i < schema.keyArray.length; i++) { - const key = schema.keyArray[i]; + for (const key of schema.keyArray) { this[key] = Configuration._merge(data[key], schema[key]); } } @@ -643,22 +642,18 @@ class Configuration { * @private */ static _merge(data, schema) { - if (schema.type === 'Folder') { - if (!data) data = {}; - for (const [key, piece] of schema.entries()) { + for (const [key, piece] of schema) { + if (piece.type === 'Folder') { if (!data[key]) data[key] = {}; - Configuration._merge(data[key], piece); + data[key] = Configuration._merge(data[key], piece); + } else if (typeof data[key] === 'undefined' || data[key] === null) { + data[key] = deepClone(piece.default); + } else if (piece.array) { + if (typeof data[key] === 'string') data[key] = tryParse(data[key]); + if (Array.isArray(data[key])) continue; + piece.client.emit('wtf', + new TypeError(`${piece.path} | Expected an array, null, or undefined. Got: ${Object.prototype.toString.call(data[key])}`)); } - } else if (typeof data === 'undefined') { - // It's a SchemaPiece instance, so it has a property of 'key'. - data = deepClone(schema.default); - } else if (schema.array) { - if (Array.isArray(data)) return data; - // Some SQL databases are unable to store Arrays... - if (data === null) return deepClone(schema.default); - if (typeof data === 'string') return tryParse(data); - schema.client.emit('wtf', - new TypeError(`${schema.path} | Expected an array, null, or undefined. Got: ${Object.prototype.toString.call(data)}`)); } return data; diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 88ccabb069..f7459abfc8 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -41,14 +41,6 @@ class SchemaFolder extends Schema { */ Object.defineProperty(this, 'type', { value: 'Folder' }); - /** - * The default values for this schema instance and children. - * @since 0.5.0 - * @type {Object} - * @name SchemaFolder#defaults - */ - Object.defineProperty(this, 'defaults', { value: {}, writable: true }); - /** * A pre-processed array with all keys' names. * @since 0.5.0 @@ -71,6 +63,20 @@ class SchemaFolder extends Schema { return this.keyArray.filter(key => this[key].type === 'Folder' || this[key].configurable); } + /** + * The default values for this schema instance and children. + * @since 0.5.0 + * @type {Object} + * @readonly + */ + get defaults() { + const defaults = {}; + for (const [key, value] of this) { + defaults[key] = value.type === 'Folder' ? value.defaults() : value.default; + } + return defaults; + } + /** * Create a new nested folder. * @since 0.5.0 @@ -117,7 +123,7 @@ class SchemaFolder extends Schema { await this.gateway.provider.addColumn(this.gateway.type, piece.type === 'Folder' ? piece.getSQL() : piece.sql[1]); } - } else if (force || this.gateway.type === 'clientStorage') { + } else if (force || (this.gateway.type === 'clientStorage' && this.client.shard)) { await this.force('add', key, piece); } @@ -137,8 +143,7 @@ class SchemaFolder extends Schema { if (!this.has(key)) throw new Error(`The key ${key} does not exist in the current schema.`); // Get the key, remove it from the configs and update the persistent schema - const piece = this[key]; - this._remove(key); + const piece = this._remove(key); await fs.outputJSONAtomic(this.gateway.filePath, this.gateway.schema.toJSON()); // A SQL database has the advantage of being able to update all keys along the schema, so force is ignored @@ -147,7 +152,7 @@ class SchemaFolder extends Schema { await this.gateway.provider.removeColumn(this.gateway.type, piece.type === 'Folder' ? [...piece.keys(true)] : key); } - } else if (force || this.gateway.type === 'clientStorage') { + } else if (force || (this.gateway.type === 'clientStorage' && this.client.shard)) { // If force, or if the gateway is clientStorage, it should update all entries await this.force('delete', key, piece); } @@ -177,7 +182,9 @@ class SchemaFolder extends Schema { * @private */ force(action, key, piece) { - if (!(piece instanceof SchemaPiece) && !(piece instanceof SchemaFolder)) throw new TypeError(`'schemaPiece' must be an instance of 'SchemaPiece' or an instance of 'SchemaFolder'.`); + if (!(piece instanceof SchemaPiece) && !(piece instanceof SchemaFolder)) { + throw new TypeError(`'schemaPiece' must be an instance of 'SchemaPiece' or an instance of 'SchemaFolder'.`); + } const path = piece.path.split('.'); @@ -241,20 +248,20 @@ class SchemaFolder extends Schema { */ _add(key, options, Piece) { if (this.has(key)) throw new Error(`The key '${key}' already exists.`); - const piece = new Piece(this.client, this.gateway, options, this, key); - this[key] = piece; - this.defaults[key] = piece.type === 'Folder' ? piece.defaults : options.default; + this[key] = new Piece(this.client, this.gateway, options, this, key); - this.keyArray.push(key); - this.keyArray.sort((a, b) => a.localeCompare(b)); + const index = this.keyArray.findIndex(entry => entry.localeCompare(key)); + if (index === -1) this.keyArray.push(key); + else this.keyArray.splice(index, 0, key); - return piece; + return this[key]; } /** * Remove a key from the instance. * @since 0.5.0 * @param {string} key The name of the key + * @returns {(SchemaFolder|SchemaPiece)} * @private */ _remove(key) { @@ -262,8 +269,10 @@ class SchemaFolder extends Schema { if (index === -1) throw new Error(`The key '${key}' does not exist.`); this.keyArray.splice(index, 1); + const piece = this[key]; delete this[key]; - delete this.defaults[key]; + + return piece; } /** @@ -303,13 +312,12 @@ class SchemaFolder extends Schema { if ('configurable' in options && typeof options.configurable !== 'boolean') throw new TypeError('The option configurable must be a boolean.'); if (!('array' in options)) options.array = Array.isArray(options.default); - if (options.array) { - if (!('default' in options)) options.default = []; - else if (!Array.isArray(options.default)) throw new TypeError('The option default must be an array if the array option is set to true.'); - } else { - if (!('default' in options)) options.default = options.type === 'boolean' ? false : null; - if (Array.isArray(options.default)) throw new TypeError('The option default must not be an array if the array option is set to false.'); + if ('default' in options && Array.isArray(options) !== options.array) { + throw new TypeError(options.array ? + 'The option default must be an array if the array option is set to true.' : + 'The option default must not be an array if the array option is set to false.'); } + return options; } From be7d9e8ff316406fc3d9a70aa5215a4597e43fcb Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 17:39:06 +0100 Subject: [PATCH 50/76] Configuration refactor --- CHANGELOG.md | 3 + package.json | 2 +- src/commands/Admin/conf.js | 20 +- src/commands/General/User Configs/userconf.js | 20 +- src/lib/settings/Configuration.js | 389 +++++++----------- src/lib/settings/Gateway.js | 32 +- src/lib/settings/GatewayStorage.js | 14 - src/lib/settings/SchemaFolder.js | 34 +- typings/index.d.ts | 63 +-- 9 files changed, 202 insertions(+), 375 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1edadc5896..35cee30f27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Changed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Refactored Configuration's internals for maximum consistency and reduced code duplication. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Changed the type for `GatewayDriver#types` from `string[]` to `Set`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Renamed `SchemaPiece#modify()` to `SchemaPiece#edit()`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Renamed `Gateway#getKeys()` and `Gateway#getValues()` to `Gateway#keys(true)` and `Gateway#values(true)` respectively, which return iterators. (kyranet) @@ -180,6 +181,8 @@ NOTE: For the contributors, you add new entries to this document following this ### Fixed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed update/reset methods in Configuration not emitting `configEntryCreate` when the entry does not exist. (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed the updateMany pattern in Configuration not accepting a guild. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed the **configUpdateEntry** event (used to sync configuration instances across shards) running in non-sharded bots, now it will be disabled if the bot is not sharded. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed `Configuration._patch` not patching after the second nested folder. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed SettingResolver's return types. (kyranet) diff --git a/package.json b/package.json index 7ac74dde21..0d895aafbd 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "devDependencies": { "@types/node": "^9.4.6", "docgen": "github:dirigeants/docsgen", - "eslint": "^4.17.0", + "eslint": "^4.18.1", "eslint-config-klasa": "github:dirigeants/klasa-lint", "eslint-plugin-markdown": "^1.0.0-beta.6", "markdownlint-cli": "^0.7.1", diff --git a/src/commands/Admin/conf.js b/src/commands/Admin/conf.js index 5e3fc4ad73..daf6a1f45b 100644 --- a/src/commands/Admin/conf.js +++ b/src/commands/Admin/conf.js @@ -25,29 +25,29 @@ module.exports = class extends Command { } get(msg, [key]) { - const { path } = this.client.gateways.guilds.getPath(key, { avoidUnconfigurable: true, piece: true }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_GET', path.path, msg.guild.configs.resolveString(msg, path))); + const { piece } = this.client.gateways.guilds.getPath(key, { avoidUnconfigurable: true, piece: true }); + return msg.sendMessage(msg.language.get('COMMAND_CONF_GET', piece.path, msg.guild.configs.resolveString(msg, piece))); } async set(msg, [key, ...valueToSet]) { - const { path } = await msg.guild.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, msg.guild.configs.resolveString(msg, path))); + const { piece } = await msg.guild.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', piece.path, msg.guild.configs.resolveString(msg, piece))); } async remove(msg, [key, ...valueToRemove]) { - const { path } = await msg.guild.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, msg.guild.configs.resolveString(msg, path))); + const { piece } = await msg.guild.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', piece.path, msg.guild.configs.resolveString(msg, piece))); } async reset(msg, [key]) { - const { path } = await msg.guild.configs.reset(key, true); - return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', path.path, msg.guild.configs.resolveString(msg, path))); + const { piece } = await msg.guild.configs.reset(key, true); + return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', piece.path, msg.guild.configs.resolveString(msg, piece))); } list(msg, [key]) { - const { path } = this.client.gateways.guilds.getPath(key, { avoidUnconfigurable: true, piece: false }); + const { piece } = this.client.gateways.guilds.getPath(key, { avoidUnconfigurable: true, piece: false }); return msg.sendMessage(msg.language.get('COMMAND_CONF_SERVER', key ? `: ${key.split('.').map(toTitleCase).join('/')}` : '', - codeBlock('asciidoc', msg.guild.configs.list(msg, path)))); + codeBlock('asciidoc', msg.guild.configs.list(msg, piece)))); } }; diff --git a/src/commands/General/User Configs/userconf.js b/src/commands/General/User Configs/userconf.js index b67aeec0c4..55d82993f5 100644 --- a/src/commands/General/User Configs/userconf.js +++ b/src/commands/General/User Configs/userconf.js @@ -23,29 +23,29 @@ module.exports = class extends Command { } get(msg, [key]) { - const { path } = this.client.gateways.users.getPath(key, { avoidUnconfigurable: true, piece: true }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_GET', path.path, msg.author.configs.resolveString(msg, path))); + const { piece } = this.client.gateways.users.getPath(key, { avoidUnconfigurable: true, piece: true }); + return msg.sendMessage(msg.language.get('COMMAND_CONF_GET', piece.path, msg.author.configs.resolveString(msg, piece))); } async set(msg, [key, ...valueToSet]) { - const { path } = await msg.author.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, msg.author.configs.resolveString(msg, path))); + const { piece } = await msg.author.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', piece.path, msg.author.configs.resolveString(msg, piece))); } async remove(msg, [key, ...valueToRemove]) { - const { path } = await msg.author.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', path.path, msg.author.configs.resolveString(msg, path))); + const { piece } = await msg.author.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', piece.path, msg.author.configs.resolveString(msg, piece))); } async reset(msg, [key]) { - const { path } = await msg.author.configs.reset(key, true); - return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', path.path, msg.author.configs.resolveString(msg, path))); + const { piece } = await msg.author.configs.reset(key, true); + return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', piece.path, msg.author.configs.resolveString(msg, piece))); } list(msg, [key]) { - const { path } = this.client.gateways.users.getPath(key, { avoidUnconfigurable: true, piece: false }); + const { piece } = this.client.gateways.users.getPath(key, { avoidUnconfigurable: true, piece: false }); return msg.sendMessage(msg.language.get('COMMAND_CONF_USER', key ? `: ${key.split('.').map(toTitleCase).join('/')}` : '', - codeBlock('asciidoc', msg.author.configs.list(msg, path)))); + codeBlock('asciidoc', msg.author.configs.list(msg, piece)))); } }; diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 2504c10ab5..621043cf50 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -1,7 +1,6 @@ -const { isObject, makeObject, deepClone, tryParse, getIdentifier, toTitleCase, arraysEqual } = require('../util/util'); +const { isObject, makeObject, deepClone, tryParse, getIdentifier, toTitleCase, arraysEqual, mergeObjects, getDeepTypeName } = require('../util/util'); const SchemaFolder = require('./SchemaFolder'); const SchemaPiece = require('./SchemaPiece'); -const invalidValue = Symbol('invalid'); /** * Creating your own Configuration instances if often discouraged and unneeded. SettingGateway handles them internally for you. @@ -11,8 +10,15 @@ class Configuration { /** * @typedef {Object} ConfigurationUpdateResult - * @property {*} value The parsed value - * @property {SchemaPiece} path The SchemaPiece that manages the updated value + * @property {Error[]} errors The errors caught from parsing + * @property {ConfigurationUpdateResultEntry[]} updated The updated keys + * @memberof Configuration + */ + + /** + * @typedef {Object} ConfigurationUpdateResultEntry + * @property {any[]} data A tuple containing the path of the updated key and the new value + * @property {SchemaPiece} piece The SchemaPiece instance that manages the updated key * @memberof Configuration */ @@ -25,49 +31,6 @@ class Configuration { * @memberof Configuration */ - /** - * @typedef {Object} ConfigurationUpdateObjectResult - * @property {ConfigurationUpdateObjectList} updated An object containing all keys and values updated - * @property {Error[]} errors All the errors caught by the parser - * @memberof Configuration - */ - - /** - * @typedef {Object} ConfigurationUpdateObjectList - * @property {string[]} keys An array of all updated keys - * @property {Array<*>} values An array of all updated values - * @memberof Configuration - */ - - /** - * @typedef {Object} ConfigurationPathResult - * @property {string} path The path of the updated key - * @property {string[]} route The path split by dots - * @memberof Configuration - * @private - */ - - /** - * @typedef {Object} ConfigurationUpdateManyList - * @property {Error[]} errors An array containing all the errors caught - * @property {Array>} promises An array of promises to resolve - * @property {string[]} keys An array of all keys pending for update - * @property {Array<*>} values An array of all values pending for update - * @memberof Configuration - * @private - */ - - /** - * @typedef {Object} ConfigurationParseResult - * @property {*} parsed The parsed value - * @property {(string|number|object)} parsedID The parsed ID value for DB storage - * @property {(null|Array<*>)} array The updated array. Can be null if the key doesn't hold an array - * @property {string} path The path of the updated key - * @property {string[]} route The path split by dots - * @memberof Configuration - * @private - */ - /** * @since 0.5.0 * @param {Gateway} manager The Gateway that manages this Configuration instance @@ -133,17 +96,7 @@ class Configuration { */ get(key) { if (!key.includes('.')) return this.gateway.schema.has(key) ? this[key] : undefined; - - const path = key.split('.'); - let refSetting = this; // eslint-disable-line consistent-this - let refSchema = this.gateway.schema; - for (const currKey of path) { - if (refSchema.type !== 'Folder' || !refSchema.has(currKey)) return undefined; - refSetting = refSetting[currKey]; - refSchema = refSchema[currKey]; - } - - return refSetting; + return this.get(key.split('.'), false); } /** @@ -188,9 +141,9 @@ class Configuration { /** * Reset a value from an entry. * @since 0.5.0 - * @param {(string|string[])} [resetKey] The key to reset + * @param {(string|string[])} [keys] The key to reset * @param {boolean} [avoidUnconfigurable] Whether the Gateway should avoid configuring the selected key - * @returns {ConfigurationUpdateResult|ConfigurationUpdateObjectList} + * @returns {ConfigurationUpdateResult} * // Reset all keys for this instance * Configuration#reset(); * @@ -200,33 +153,30 @@ class Configuration { * // Reset a key * Configuration#reset('prefix'); */ - async reset(resetKey, avoidUnconfigurable) { - if (typeof resetKey === 'undefined') resetKey = [...this.gateway.schema.keys(true)]; - if (Array.isArray(resetKey)) { - // Handle entry creation if it does not exist. - if (!this._existsInDB) return { keys: [], values: [] }; - - const oldClone = this.client.listenerCount('configUpdateEntry') ? this.clone() : null; - const list = { keys: [], values: [] }; - this._resetKeys(resetKey, list); - - if (list.keys.length) { - if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, list.keys); - if (this.gateway.sql) { - await this.gateway.provider.update(this.gateway.type, this.id, list.keys, list.values); - } else { - const updateObject = Object.assign({}, ...list.keys.map((key, i) => makeObject(key, list.values[i]))); - await this.gateway.provider.update(this.gateway.type, this.id, updateObject); + async reset(keys, avoidUnconfigurable = true) { + // If the entry does not exist in the DB, it'll never be able to reset a key + if (!this._existsInDB) return { errors: [], updated: [] }; + + if (typeof keys === 'string') keys = [keys]; + else if (typeof keys === 'undefined') keys = [...this.gateway.schema.keys(true)]; + if (Array.isArray(keys)) { + const result = { errors: [], updated: [] }; + const entries = new Array(keys.length); + for (let i = 0; i < keys.length; i++) entries[i] = [keys[i], null]; + for (const [key, value] of entries) { + const path = this.gateway.getPath(key, { piece: false, avoidUnconfigurable, errors: false }); + if (!path) { + result.errors.push(`The path ${key} does not exist in the current schema, or does not correspond to a piece.`); + continue; } + const newValue = value === null ? deepClone(path.piece.default) : value; + const { updated } = this._setValueByPath(path.piece, newValue, path.piece.path); + if (updated) result.updated.push({ data: [path.piece.path, newValue], piece: path.piece }); } - - return { keys: list.keys, values: list.values }; + if (result.updated.length) await this._save(result); + return result; } - const { parsedID, parsed, path } = await this._reset(resetKey, typeof avoidUnconfigurable !== 'boolean' ? false : avoidUnconfigurable); - await (this.gateway.sql ? - this.gateway.provider.update(this.gateway.type, this.id, resetKey, parsedID) : - this.gateway.provider.update(this.gateway.type, this.id, makeObject(resetKey, parsedID))); - return { value: parsed, path }; + throw new TypeError(`Invalid value. Expected string or Array. Got: ${getDeepTypeName(keys)}`); } /** @@ -236,8 +186,7 @@ class Configuration { * @param {*} [value] The value to parse and save * @param {GuildResolvable} [guild] A guild resolvable * @param {ConfigurationUpdateOptions} [options={}] The options for the update - * @returns {Promise<(ConfigurationUpdateResult|ConfigurationUpdateObjectList)>} - * @throws {Promise} + * @returns {Promise} * @example * // Updating the value of a key * Configuration#update('roles.administrator', '339943234405007361', msg.guild); @@ -251,18 +200,39 @@ class Configuration { * // Updating it with a json object: * Configuration#update({ roles: { administrator: '339943234405007361' } }, msg.guild); * - * // Updating multiple keys (only possible with json object): + * // Updating multiple keys (with json object): * Configuration#update({ prefix: 'k!', language: 'es-ES' }, msg.guild); + * + * // Updating multiple keys (with arrays): + * Configuration#update(['prefix', 'language'], ['k!', 'es-ES']); */ - update(key, value, guild, options) { + async update(key, value, guild, options) { if (typeof options === 'undefined' && isObject(guild)) { options = guild; guild = undefined; } if (guild) guild = this.gateway._resolveGuild(guild); + if (typeof key === 'string') { + key = [key]; + value = [value]; + } else if (isObject(key)) { + return this._updateMany(key, guild); + } - if (isObject(key)) return this._updateMany(key, value); - return this._updateSingle(key, value, guild, options); + if (Array.isArray(key)) { + if (!Array.isArray(value) || key.length !== value.length) throw new Error(`Expected an array of ${key.length} entries. Got: ${value.length}.`); + + // Create the result with all the promises to resolve + const result = { errors: [], updated: [] }; + const mps = new Array(key.length); + for (let i = 0; i < key.length; i++) mps[i] = this._parseSingle(key[i], value[i], guild, options, result); + await Promise.all(mps); + + // If at least one key updated, save it to the database + if (result.updated.length) await this._save(result); + return result; + } + throw new TypeError(`Invalid value. Expected object, string or Array. Got: ${getDeepTypeName(key)}`); } /** @@ -340,31 +310,37 @@ class Configuration { return resolver(value); } - /** - * Resets all keys recursively - * @since 0.5.0 - * @param {string[]} keys The SchemaFolder to iterate - * @param {ConfigurationUpdateManyList} list The list - * @private - */ - _resetKeys(keys, list) { - for (const key of keys) { - const { path, route } = this.gateway.getPath(key, { piece: true }); - - let self = this; // eslint-disable-line consistent-this - for (let i = 0; i < route.length - 1; i++) self = self[route[i]] || {}; - const currentValue = self[route[route.length - 1]]; + _get(route, piece = true) { + if (typeof route === 'string') route = route.split('.'); + let refCache = this, refSchema = this.gateway.schema; // eslint-disable-line consistent-this + for (const key of route) { + if (refSchema.type !== 'Folder' || !refSchema.has(key)) return undefined; + refCache = refCache[key]; + refSchema = refSchema[key]; + } - const value = (path.array ? !arraysEqual(currentValue, path.default, true) : currentValue !== path.default) ? - deepClone(path.default) : - invalidValue; - if (value === invalidValue) continue; + return piece && refSchema.type !== 'Folder' ? refCache : undefined; + } - self[route[route.length - 1]] = value; + async _save({ updated }) { + if (!updated.length) return; + if (!this._existsInDB) { + await this.gateway.createEntry(this.id); + if (this.client.listenerCount('configCreateEntry')) this.client.emit('configCreateEntry', this); + } + const oldClone = this.client.listenerCount('configUpdateEntry') ? this.clone() : null; - list.keys.push(path.path); - list.values.push(value); + if (this.gateway.sql) { + const keys = new Array(updated.length), values = new Array(updated.length); + for (let i = 0; i < updated.length; i++)[keys[i], values[i]] = updated[i]; + await this.gateway.provider.update(this.gateway.type, this.id, keys, values); + } else { + const updateObject = {}; + for (const [key, value] of updated) mergeObjects(updateObject, makeObject(key, value)); + await this.gateway.provider.update(this.gateway.type, this.id, updateObject); } + + if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, updated); } /** @@ -372,8 +348,7 @@ class Configuration { * @since 0.5.0 * @param {Object} object A JSON object to iterate and parse * @param {GuildResolvable} [guild] A guild resolvable - * @returns {ConfigurationUpdateObjectList} - * @throws {ConfigurationUpdateObjectResult} + * @returns {ConfigurationUpdateResult} * @private */ async _updateMany(object, guild) { @@ -395,132 +370,45 @@ class Configuration { } /** - * Reset a value from an entry. - * @since 0.5.0 - * @param {string} key The key to reset - * @param {boolean} avoidUnconfigurable Whether the Gateway should avoid configuring the selected key - * @returns {ConfigurationParseResult} + * Parse a single value + * @param {string} key The key to update + * @param {*} value The new value for the key + * @param {?KlasaGuild} guild The Guild instance for key resolving + * @param {ConfigurationUpdateOptions} options The options for parsing this value + * @param {ConfigurationUpdateResult} list The list to update * @private */ - async _reset(key, avoidUnconfigurable) { - if (typeof key !== 'string') throw new TypeError(`The argument key must be a string. Received: ${typeof key}`); - const pathData = this.gateway.getPath(key, { avoidUnconfigurable, piece: true }); - return this._parseReset(key, pathData); - } - - /** - * Parse the data for reset. - * @since 0.5.0 - * @param {string} key The key to edit - * @param {ConfigurationPathResult} options The options - * @returns {ConfigurationParseResult} - * @private - */ - async _parseReset(key, { path, route }) { - const parsedID = deepClone(path.default); - await this._setValue(parsedID, path, route); - return { parsed: parsedID, parsedID, array: null, path, route }; - } - - /** - * Update a single key - * @since 0.5.0 - * @param {string} key The key to edit - * @param {*} value The new value - * @param {GuildResolvable} guild The guild to take - * @param {ConfigurationPathResult} options The options - * @returns {ConfigurationParseResult} - * @private - */ - async _parseUpdateOne(key, value, guild, { path, route }) { - if (path.array === true) throw new Error('This key is array type.'); - - const parsed = await path.parse(value, guild); - const parsedID = getIdentifier(parsed); - await this._setValue(parsedID, path, route); - return { parsed, parsedID, array: null, path, route }; - } - - /** - * Update an array - * @since 0.5.0 - * @param {('add'|'remove'|'auto')} action Whether the value should be added or removed to the array - * @param {string} key The key to edit - * @param {*} value The new value - * @param {GuildResolvable} guild The guild to take - * @param {number} arrayPosition The array position to update - * @param {ConfigurationPathResult} options The options - * @returns {ConfigurationParseResult} - * @private - */ - async _parseUpdateArray(action, key, value, guild, arrayPosition, { path, route }) { - if (path.array === false) { - if (guild) throw guild.language.get('COMMAND_CONF_KEY_NOT_ARRAY'); - throw new Error('The key is not an array.'); + async _parseSingle(key, value, guild, { avoidUnconfigurable = false, action = 'auto', arrayPosition = null }, list) { + const { piece, route } = this.gateway.getPath(key, { avoidUnconfigurable, piece: true }); + let parsed, parsedID; + if (value === null) { + parsed = parsedID = deepClone(piece.default); + } else { + parsed = await piece.parse(value, guild); + parsedID = piece.type === 'any' ? parsed : getIdentifier(parsed); } - const parsed = await path.parse(value, guild); - const parsedID = path.type !== 'any' ? getIdentifier(parsed) : parsed; - - // Handle entry creation if it does not exist. - if (!this._existsInDB) await this.gateway.createEntry(this.id); - const oldClone = this.client.listenerCount('configUpdateEntry') ? this.clone() : null; - - let cache = this; // eslint-disable-line consistent-this - for (let i = 0; i < route.length - 1; i++) cache = cache[route[i]] || {}; - cache = cache[route[route.length - 1]] || []; - - if (typeof arrayPosition === 'number') { - if (arrayPosition >= cache.length) throw new Error(`The option arrayPosition should be a number between 0 and ${cache.length - 1}`); - cache[arrayPosition] = parsedID; - } else { - if (action === 'auto') action = cache.includes(parsedID) ? 'remove' : 'add'; - if (action === 'add') { - if (cache.includes(parsedID)) throw `The value ${parsedID} for the key ${path.path} already exists.`; - cache.push(parsedID); + if (piece.array) { + const array = this._get(route, true); + if (typeof arrayPosition === 'number') { + if (arrayPosition >= array.length) throw new Error(`The option arrayPosition should be a number between 0 and ${array.length - 1}`); + array[arrayPosition] = parsedID; } else { - const index = cache.indexOf(parsedID); - if (index === -1) throw `The value ${parsedID} for the key ${path.path} does not exist.`; - cache.splice(index, 1); + if (action === 'auto') action = array.includes(parsedID) ? 'remove' : 'add'; + if (action === 'add') { + if (array.includes(parsedID)) throw `The value ${parsedID} for the key ${piece.path} already exists.`; + array.push(parsedID); + } else { + const index = array.indexOf(parsedID); + if (index === -1) throw `The value ${parsedID} for the key ${piece.path} does not exist.`; + array.splice(index, 1); + } } + list.updated.push({ data: [piece.path, parsedID], piece: piece }); + } else { + const { updated } = this._setValueByPath(piece, parsedID, piece.path); + if (updated) list.updated.push({ data: [piece.path, parsedID], piece: piece }); } - - if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, [path.path]); - return { parsed, parsedID, array: cache, path, route }; - } - - /** - * Update an array - * @since 0.5.0 - * @param {string} key The key to edit - * @param {*} value The new value - * @param {GuildResolvable} guild The guild to take - * @param {Object} [options={}] The options - * @param {boolean} [options.avoidUnconfigurable=false] Whether the Gateway should avoid configuring the selected key - * @param {('add'|'remove'|'auto')} [options.action='auto'] Whether the value should be added or removed to the array - * @param {number} [options.arrayPosition=null] The array position to update - * @returns {ConfigurationUpdateResult} - * @private - */ - async _updateSingle(key, value, guild, { avoidUnconfigurable = false, action = 'auto', arrayPosition = null } = {}) { - if (typeof key !== 'string') throw new TypeError(`The argument key must be a string. Received: ${typeof key}`); - if (typeof guild === 'boolean') { - avoidUnconfigurable = guild; - guild = undefined; - } - - const pathData = this.gateway.getPath(key, { avoidUnconfigurable, piece: true }); - if (action === 'remove' && !pathData.path.array) return this._parseReset(key, pathData); - const { parsedID, array, parsed } = value === null || (action === 'remove' && !pathData.path.array) ? - this._parseReset(key, pathData) : - pathData.path.array === true ? - await this._parseUpdateArray(action, key, value, guild, arrayPosition, pathData) : - await this._parseUpdateOne(key, value, guild, pathData); - - if (this.gateway.sql) await this.gateway.provider.update(this.gateway.type, this.id, key, array || parsedID); - else await this.gateway.provider.update(this.gateway.type, this.id, makeObject(key, array || parsedID)); - - return { value: parsed, path: pathData.path }; } /** @@ -530,7 +418,7 @@ class Configuration { * @param {Object} object The key to edit * @param {SchemaFolder} schema The new value * @param {GuildResolvable} guild The guild to take - * @param {ConfigurationUpdateManyList} list The options + * @param {ConfigurationUpdateResult} list The options * @param {*} updateObject The object to update * @private */ @@ -578,23 +466,24 @@ class Configuration { } /** - * Set a value at a certain path - * @since 0.5.0 - * @param {string} parsedID The parsed ID or result - * @param {SchemaPiece} path The SchemaPiece which handles the key to modify - * @param {string[]} route The route of the key to modify + * Set a value by its path + * @param {SchemaPiece} piece The piece that manages the key + * @param {*} parsedID The parsed ID value + * @param {(string|string[])} path The path of the key + * @returns {{ updated: boolean, old: any }} * @private */ - async _setValue(parsedID, path, route) { - // Handle entry creation if it does not exist. - if (!this._existsInDB) await this.gateway.createEntry(this.id); - const oldClone = this.client.listenerCount('configUpdateEntry') ? this.clone() : null; - + _setValueByPath(piece, parsedID, path) { + if (typeof path === 'string') path = path.split('.'); + const lastKey = path.pop(); let cache = this; // eslint-disable-line consistent-this - for (let i = 0; i < route.length - 1; i++) cache = cache[route[i]] || {}; - cache[route[route.length - 1]] = parsedID; - - if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, [path.path]); + for (const key of path) cache = cache[key] || {}; + const old = cache[lastKey]; + if (piece.array ? !arraysEqual(old, path.default, true) : old !== piece.default) { + cache[lastKey] = parsedID; + return { updated: true, old }; + } + return { updated: false, old }; } /** @@ -670,10 +559,8 @@ class Configuration { static _clone(data, schema) { const clone = {}; - for (let i = 0; i < schema.keyArray.length; i++) { - const key = schema.keyArray[i]; - if (schema[key].type === 'Folder') clone[key] = Configuration._clone(data[key], schema[key]); - else clone[key] = deepClone(data[key]); + for (const [key, piece] of schema) { + clone[key] = piece.type === 'Folder' ? Configuration._clone(data[key], piece) : deepClone(data[key]); } return clone; diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index c23eb3f240..ee95940ece 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -29,7 +29,7 @@ class Gateway extends GatewayStorage { /** * @typedef {Object} GatewayGetPathResult - * @property {SchemaPiece} path The resolved path + * @property {SchemaPiece} piece The piece resolved from the path * @property {string[]} route The resolved path split by dots * @memberof Gateway */ @@ -218,33 +218,41 @@ class Gateway extends GatewayStorage { * @since 0.5.0 * @param {string} [key=null] A string to resolve * @param {GatewayGetPathOptions} [options={}] Whether the Gateway should avoid configuring the selected key - * @returns {GatewayPathResult} + * @returns {?GatewayGetPathResult} */ - getPath(key = '', { avoidUnconfigurable = false, piece = true } = {}) { - if (key === '') return { path: this.schema, route: [] }; - if (typeof key !== 'string') throw new TypeError('The value for the argument \'key\' must be a string.'); + getPath(key = '', { avoidUnconfigurable = false, piece = true, errors = false } = {}) { + if (key === '') return { piece: this.schema, route: [] }; const route = key.split('.'); let path = this.schema; for (let i = 0; i < route.length; i++) { const currKey = route[i]; - if (typeof path[currKey] === 'undefined' || !path.has(currKey)) throw `The key ${route.slice(0, i + 1).join('.')} does not exist in the current schema.`; + if (typeof piece[currKey] === 'undefined' || !piece.has(currKey)) { + if (errors) throw `The key ${route.slice(0, i + 1).join('.')} does not exist in the current schema.`; + return null; + } if (path[currKey].type === 'Folder') { path = path[currKey]; } else if (piece) { - if (avoidUnconfigurable && !path[currKey].configurable) throw `The key ${path[currKey].path} is not configurable in the current schema.`; - return { path: path[currKey], route: path[currKey].path.split('.') }; + if (avoidUnconfigurable && !piece[currKey].configurable) { + if (errors) throw `The key ${piece[currKey].path} is not configurable in the current schema.`; + return null; + } + return { piece: path[currKey], route: path[currKey].path.split('.') }; } } if (piece && path.type === 'Folder') { - const keys = path.configurableKeys; - if (keys.length === 0) throw `This group is not configurable.`; - throw `Please, choose one of the following keys: '${keys.join('\', \'')}'`; + const keys = piece.configurableKeys; + if (keys.length === 0) { + if (errors) throw `This group is not configurable.`; + return null; + } + if (errors) throw `Please, choose one of the following keys: '${keys.join('\', \'')}'`; } - return { path, route: path.path.split('.') }; + return { piece, route: path.path.split('.') }; } /** diff --git a/src/lib/settings/GatewayStorage.js b/src/lib/settings/GatewayStorage.js index e5619e1321..440755bf1f 100644 --- a/src/lib/settings/GatewayStorage.js +++ b/src/lib/settings/GatewayStorage.js @@ -179,20 +179,6 @@ class GatewayStorage { return object; } - /** - * Make an error that can or not have a valid Guild. - * @since 0.5.0 - * @param {KlasaGuild} guild The guild to get the language from - * @param {(string|number)} code The code of the error - * @param {(string|Error)} error The error - * @returns {string} - * @private - */ - static throwError(guild, code, error) { - if (guild && guild.language && typeof guild.language.get === 'function') return guild.language.get(code); - return `ERROR: [${code}]: ${error}`; - } - /** * Parse SQL values. * @since 0.5.0 diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index f7459abfc8..8d7c9b3d93 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -1,6 +1,6 @@ const SchemaPiece = require('./SchemaPiece'); const Schema = require('./Schema'); -const { deepClone, isObject, isNumber } = require('../util/util'); +const { deepClone, isObject } = require('../util/util'); const fs = require('fs-nextra'); /** @@ -113,9 +113,7 @@ class SchemaFolder extends Schema { if (typeof options.type !== 'string' || options.type.toLowerCase() === 'folder') options.type = 'Folder'; // Create the piece and save the current schema - const piece = options.type === 'Folder' ? - this._add(key, options, SchemaFolder) : - this._add(key, this._verifyKeyOptions(key, options), SchemaPiece); + const piece = this._add(key, options, options.type === 'Folder' ? SchemaFolder : SchemaPiece); await fs.outputJSONAtomic(this.gateway.filePath, this.gateway.schema.toJSON()); if (this.gateway.sql) { @@ -293,34 +291,6 @@ class SchemaFolder extends Schema { `); } - /** - * Verifies the key add options. - * @since 0.5.0 - * @param {string} key The name for the key - * @param {SchemaFolderAddOptions} options The key's options to apply - * @returns {SchemaFolderAddOptions} - * @private - */ - _verifyKeyOptions(key, options) { - if (!options) throw new Error('You must pass an options argument to this method.'); - if (typeof options.type !== 'string') throw new TypeError('The option type is required and must be a string.'); - options.type = options.type.toLowerCase(); - if (!this.client.gateways.types.has(options.type)) throw new TypeError(`The type ${options.type} is not supported.`); - if ('min' in options && options.min !== null && !isNumber(options.min)) throw new TypeError('The option min must be a number or null.'); - if ('max' in options && options.max !== null && !isNumber(options.max)) throw new TypeError('The option max must be a number or null.'); - if ('array' in options && typeof options.array !== 'boolean') throw new TypeError('The option array must be a boolean.'); - if ('configurable' in options && typeof options.configurable !== 'boolean') throw new TypeError('The option configurable must be a boolean.'); - if (!('array' in options)) options.array = Array.isArray(options.default); - - if ('default' in options && Array.isArray(options) !== options.array) { - throw new TypeError(options.array ? - 'The option default must be an array if the array option is set to true.' : - 'The option default must not be an array if the array option is set to false.'); - } - - return options; - } - /** * Method called in initialization to populate the instance with the keys from the schema. * @since 0.5.0 diff --git a/typings/index.d.ts b/typings/index.d.ts index d5e7d39137..5e879b2e77 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -520,28 +520,25 @@ declare module 'klasa' { private _syncStatus?: Promise; public get(key: string): any; + public get(key: string): T; public clone(): Configuration; - public resetConfiguration(): Promise; public sync(): Promise; public destroy(): Promise; - public reset(keys?: string[]): Promise; - public reset(key: string, avoidUnconfigurable?: boolean): Promise; - public update(key: object, guild?: GatewayGuildResolvable): Promise; - public update(key: string, value?: any, options?: ConfigurationUpdateOptions): Promise; - public update(key: string, value?: any, guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; + public reset(key?: string | string[], avoidUnconfigurable?: boolean): Promise; + public update(key: object, guild?: GatewayGuildResolvable): Promise; + public update(key: string, value: any, guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; + public update(key: string[], value: any[], guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; public list(msg: KlasaMessage, path: SchemaFolder | string): string; public resolveString(msg: KlasaMessage, path: SchemaPiece | string): string; - private _resetKeys(keys: string[], list: ConfigurationUpdateManyList): void; + private _get(route: string | string[], piece?: boolean): object; + private _get(route: string | string[], piece?: boolean): T; + private _save(data: ConfigurationUpdateResult): Promise; private _updateMany(object: any, guild?: GatewayGuildResolvable): Promise; - private _reset(key: string, guild: GatewayGuildResolvable, avoidUnconfigurable: boolean): Promise; - private _parseReset(key: string, guild: KlasaGuild, options: ConfigurationPathResult): Promise; - private _parseUpdateOne(key: string, value: any, guild: KlasaGuild, options: ConfigurationPathResult): Promise; - private _parseUpdateArray(action: 'add' | 'remove' | 'auto', key: string, value: any, guild: KlasaGuild, arrayPosition: number, options: ConfigurationPathResult): Promise; + private _parseSingle(key: string, value: any, guild: KlasaGuild | null, options: ConfigurationUpdateOptions, list: ConfigurationUpdateResult): Promise; private _parseUpdateMany(cache: any, object: any, schema: SchemaFolder, guild: KlasaGuild, list: ConfigurationUpdateManyResult, updateObject: object): void; - private _updateSingle(key: string, value: any, guild: KlasaGuild, options: ConfigurationUpdateOptions): Promise; - private _setValue(parsedID: string, path: SchemaPiece, route: string[]): Promise; + private _setValueByPath(piece: SchemaPiece, parsedID: any, path: string | string[]): { updated: boolean, old: any }; private _patch(data: any): void; public toJSON(): object; @@ -565,7 +562,7 @@ declare module 'klasa' { public insertEntry(id: string, data?: object): Configuration; public deleteEntry(input: string): Promise; public sync(input?: object | string, download?: boolean): Promise; - public getPath(key?: string, options?: GatewayGetPathOptions): GatewayGetPathResult; + public getPath(key?: string, options?: GatewayGetPathOptions): GatewayGetPathResult | null; private init(download?: boolean): Promise; private _ready(): Promise>>; @@ -631,7 +628,6 @@ declare module 'klasa' { private initSchema(): Promise; private parseEntry(entry: any): any; - private static throwError(guild: KlasaGuild, code: string | number, error: string | Error): string; private static _parseSQLValue(value: any, schemaPiece: SchemaPiece): any; } @@ -662,6 +658,7 @@ declare module 'klasa' { private _add(key: string, options: SchemaFolderAddOptions, type: typeof Schema | typeof SchemaFolder): void; private _remove(key: string): void; + private _shardSyncSchema(piece: SchemaFolder | SchemaPiece, action: 'add' | 'delete' | 'update', force: boolean): Promise; private _init(options: object): true; public entries(recursive?: boolean): Iterator<[string, SchemaFolder | SchemaPiece]>; @@ -1433,10 +1430,11 @@ declare module 'klasa' { export type GatewayGetPathOptions = { avoidUnconfigurable?: boolean; piece?: boolean; + errors?: boolean; }; export type GatewayGetPathResult = { - path: SchemaPiece; + piece: SchemaPiece; route: string[]; }; @@ -1455,38 +1453,13 @@ declare module 'klasa' { }; export type ConfigurationUpdateResult = { - path: SchemaPiece; - value: any; - }; - - export type ConfigurationUpdateObjectResult = { - updated: ConfigurationUpdateObjectList; errors: Error[]; + updated: ConfigurationUpdateResultEntry[]; }; - export type ConfigurationUpdateObjectList = { - keys: string[]; - values: any[]; - }; - - type ConfigurationParseResult = { - array?: any[]; - entryID: string; - parsed: any; - parsedID: string | number | object; - settings: Configuration; - } & ConfigurationPathResult; - - type ConfigurationUpdateManyList = { - errors: Error[]; - keys: string[]; - promises: Array>; - values: any[]; - }; - - export type ConfigurationUpdateManyResult = { - errors: Error[]; - updated: ConfigurationUpdateObjectList; + export type ConfigurationUpdateResultEntry = { + data: [string, any]; + piece: SchemaPiece; }; export type GatewayGuildResolvable = KlasaGuild From 67aa0f179cc92d358867980bd12509b2142c0a18 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 18:02:38 +0100 Subject: [PATCH 51/76] Fix Configuration merge --- src/lib/settings/Configuration.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 621043cf50..0a485b6dfa 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -82,10 +82,8 @@ class Configuration { */ Object.defineProperty(this, '_syncStatus', { value: null, writable: true }); - const { schema } = this.gateway; - for (const key of schema.keyArray) { - this[key] = Configuration._merge(data[key], schema[key]); - } + Configuration._merge(data, this.gateway.schema); + for (const key of this.gateway.schema.keys()) this[key] = data[key]; } /** From 1fe5c56624fd3284d07e931847263820bd6d8a0b Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 19:05:31 +0100 Subject: [PATCH 52/76] Fix a little issue --- src/lib/settings/SchemaFolder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 8d7c9b3d93..58f70ba056 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -72,7 +72,7 @@ class SchemaFolder extends Schema { get defaults() { const defaults = {}; for (const [key, value] of this) { - defaults[key] = value.type === 'Folder' ? value.defaults() : value.default; + defaults[key] = value.type === 'Folder' ? value.defaults : value.default; } return defaults; } From 3eac3abc320bb8aa3ff972a593b0adc4834b186d Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 19:19:28 +0100 Subject: [PATCH 53/76] Return a better error if getPath returns null in silent mode --- src/lib/settings/Configuration.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 0a485b6dfa..343e261afe 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -377,7 +377,12 @@ class Configuration { * @private */ async _parseSingle(key, value, guild, { avoidUnconfigurable = false, action = 'auto', arrayPosition = null }, list) { - const { piece, route } = this.gateway.getPath(key, { avoidUnconfigurable, piece: true }); + const path = this.gateway.getPath(key, { piece: false, avoidUnconfigurable, errors: false }); + if (!path) { + list.errors.push(`The path ${key} does not exist in the current schema, or does not correspond to a piece.`); + return; + } + const { piece, route } = path; let parsed, parsedID; if (value === null) { parsed = parsedID = deepClone(piece.default); From 9c00073c2ddb9b49b2ff2d98340386b78ab76082 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 19:27:47 +0100 Subject: [PATCH 54/76] Fix Gateway#getPath --- src/lib/settings/Gateway.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index ee95940ece..68b3af1fcb 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -220,26 +220,26 @@ class Gateway extends GatewayStorage { * @param {GatewayGetPathOptions} [options={}] Whether the Gateway should avoid configuring the selected key * @returns {?GatewayGetPathResult} */ - getPath(key = '', { avoidUnconfigurable = false, piece = true, errors = false } = {}) { + getPath(key = '', { avoidUnconfigurable = false, piece = true, errors = true } = {}) { if (key === '') return { piece: this.schema, route: [] }; const route = key.split('.'); - let path = this.schema; + let { schema } = this; for (let i = 0; i < route.length; i++) { const currKey = route[i]; - if (typeof piece[currKey] === 'undefined' || !piece.has(currKey)) { + if (typeof schema[currKey] === 'undefined' || !schema.has(currKey)) { if (errors) throw `The key ${route.slice(0, i + 1).join('.')} does not exist in the current schema.`; return null; } - if (path[currKey].type === 'Folder') { - path = path[currKey]; + if (schema[currKey].type === 'Folder') { + schema = schema[currKey]; } else if (piece) { if (avoidUnconfigurable && !piece[currKey].configurable) { - if (errors) throw `The key ${piece[currKey].path} is not configurable in the current schema.`; + if (errors) throw `The key ${schema[currKey].path} is not configurable in the current schema.`; return null; } - return { piece: path[currKey], route: path[currKey].path.split('.') }; + return { piece: schema[currKey], route: schema[currKey].path.split('.') }; } } From e46b2591a913d74e01457beb5040114440b9fc92 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 19:28:04 +0100 Subject: [PATCH 55/76] Actually fix it --- src/lib/settings/Gateway.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index 68b3af1fcb..f5431080e2 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -243,7 +243,7 @@ class Gateway extends GatewayStorage { } } - if (piece && path.type === 'Folder') { + if (piece && schema.type === 'Folder') { const keys = piece.configurableKeys; if (keys.length === 0) { if (errors) throw `This group is not configurable.`; @@ -252,7 +252,7 @@ class Gateway extends GatewayStorage { if (errors) throw `Please, choose one of the following keys: '${keys.join('\', \'')}'`; } - return { piece, route: path.path.split('.') }; + return { piece, route: schema.path.split('.') }; } /** From 78d8895c64bd3fa536cf4e622db12baecd8fc584 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 19:31:19 +0100 Subject: [PATCH 56/76] Whoops --- src/lib/settings/Configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 343e261afe..5a384979a8 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -377,7 +377,7 @@ class Configuration { * @private */ async _parseSingle(key, value, guild, { avoidUnconfigurable = false, action = 'auto', arrayPosition = null }, list) { - const path = this.gateway.getPath(key, { piece: false, avoidUnconfigurable, errors: false }); + const path = this.gateway.getPath(key, { piece: true, avoidUnconfigurable, errors: false }); if (!path) { list.errors.push(`The path ${key} does not exist in the current schema, or does not correspond to a piece.`); return; From a2840d3477040f4d38994b7d383d70a35f2e6ecb Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 19:39:06 +0100 Subject: [PATCH 57/76] Fixed save not accessing to the right tuple --- src/lib/settings/Configuration.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 5a384979a8..2bb187d48f 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -320,6 +320,12 @@ class Configuration { return piece && refSchema.type !== 'Folder' ? refCache : undefined; } + /** + * Save the data to the database. + * @since 0.5.0 + * @param {ConfigurationUpdateResult} result The data to save + * @private + */ async _save({ updated }) { if (!updated.length) return; if (!this._existsInDB) { @@ -334,7 +340,7 @@ class Configuration { await this.gateway.provider.update(this.gateway.type, this.id, keys, values); } else { const updateObject = {}; - for (const [key, value] of updated) mergeObjects(updateObject, makeObject(key, value)); + for (const entry of updated) mergeObjects(updateObject, makeObject(entry.data[0], entry.data[1])); await this.gateway.provider.update(this.gateway.type, this.id, updateObject); } From 5014d9ebcac8e0e3f10079ed5d9bea98eebb340e Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 21:34:02 +0100 Subject: [PATCH 58/76] Refactored the updateMany pattern --- .eslintrc.json | 7 +++ CHANGELOG.md | 1 + src/lib/settings/Configuration.js | 97 ++++++++++++++----------------- typings/index.d.ts | 6 +- 4 files changed, 54 insertions(+), 57 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 7f252f6c83..60a8545949 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,10 @@ { + "parserOptions": { + "ecmaFeatures": { + "globalReturn": true, + "experimentalObjectRestSpread": true + }, + "ecmaVersion": 2017 + }, "extends": "klasa" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 35cee30f27..cfb1a1ecd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -181,6 +181,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Fixed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed `null` values in *updateMany*'s pattern not updating nested keys plus individual queries. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed update/reset methods in Configuration not emitting `configEntryCreate` when the entry does not exist. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed the updateMany pattern in Configuration not accepting a guild. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed the **configUpdateEntry** event (used to sync configuration instances across shards) running in non-sharded bots, now it will be disabled if the bot is not sharded. (kyranet) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 2bb187d48f..f82684ce5a 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -168,7 +168,7 @@ class Configuration { continue; } const newValue = value === null ? deepClone(path.piece.default) : value; - const { updated } = this._setValueByPath(path.piece, newValue, path.piece.path); + const { updated } = this._setValueByPath(path.piece, newValue); if (updated) result.updated.push({ data: [path.piece.path, newValue], piece: path.piece }); } if (result.updated.length) await this._save(result); @@ -308,6 +308,14 @@ class Configuration { return resolver(value); } + /** + * Get a value from the cache. + * @since 0.5.0 + * @param {(string|string[])} route The route to get + * @param {boolean} piece Whether the get should resolve a piece or a folder + * @returns {*} + * @private + */ _get(route, piece = true) { if (typeof route === 'string') route = route.split('.'); let refCache = this, refSchema = this.gateway.schema; // eslint-disable-line consistent-this @@ -336,7 +344,7 @@ class Configuration { if (this.gateway.sql) { const keys = new Array(updated.length), values = new Array(updated.length); - for (let i = 0; i < updated.length; i++)[keys[i], values[i]] = updated[i]; + for (let i = 0; i < updated.length; i++) [keys[i], values[i]] = updated[i]; await this.gateway.provider.update(this.gateway.type, this.id, keys, values); } else { const updateObject = {}; @@ -356,25 +364,20 @@ class Configuration { * @private */ async _updateMany(object, guild) { - const list = { errors: [], promises: [], keys: [], values: [] }; + const result = { errors: [], updated: [], promises: [] }; - // Handle entry creation if it does not exist. - if (!this._existsInDB) await this.gateway.createEntry(this.id); + this._parseUpdateMany(this, object, this.gateway.schema, guild, result); + await Promise.all(result.promises); + delete result.promises; - const oldClone = this.client.listenerCount('configUpdateEntry') ? this.clone() : null; - const updateObject = {}; - this._parseUpdateMany(this, object, this.gateway.schema, guild, list, updateObject); - await Promise.all(list.promises); - - if (oldClone !== null) this.client.emit('configUpdateEntry', oldClone, this, list.keys); - if (this.gateway.sql) await this.gateway.provider.update(this.gateway.type, this.id, list.keys, list.values); - else await this.gateway.provider.update(this.gateway.type, this.id, updateObject); - if (list.errors.length) throw { updated: { keys: list.keys, values: list.values }, errors: list.errors }; - return { keys: list.keys, values: list.values }; + // If at least one key updated, save it to the database + if (result.updated.length) await this._save(result); + return result; } /** * Parse a single value + * @since 0.5.0 * @param {string} key The key to update * @param {*} value The new value for the key * @param {?KlasaGuild} guild The Guild instance for key resolving @@ -415,7 +418,7 @@ class Configuration { } list.updated.push({ data: [piece.path, parsedID], piece: piece }); } else { - const { updated } = this._setValueByPath(piece, parsedID, piece.path); + const { updated } = this._setValueByPath(piece, parsedID); if (updated) list.updated.push({ data: [piece.path, parsedID], piece: piece }); } } @@ -427,63 +430,49 @@ class Configuration { * @param {Object} object The key to edit * @param {SchemaFolder} schema The new value * @param {GuildResolvable} guild The guild to take - * @param {ConfigurationUpdateResult} list The options - * @param {*} updateObject The object to update + * @param {ConfigurationUpdateResult} result The options * @private */ - _parseUpdateMany(cache, object, schema, guild, list, updateObject) { + _parseUpdateMany(cache, object, schema, guild, result) { for (const key of Object.keys(object)) { if (!schema.has(key)) continue; - // Check if it's a folder, and recursively iterate over it if (schema[key].type === 'Folder') { - if (!(key in updateObject)) updateObject = updateObject[key] = {}; - this._parseUpdateMany(cache[key], object[key], schema[key], guild, list, updateObject); - // If the value is null, reset it + // Check if it's a folder, and recursively iterate over it + this._parseUpdateMany(cache[key], object[key], schema[key], guild, result); } else if (object[key] === null) { - list.promises.push(this.reset(key) - .then(({ value, path }) => { - updateObject[key] = cache[key] = value; - list.keys.push(path); - list.values.push(value); - }) - .catch(error => list.errors.push([schema.path, error]))); + // If the value is null, reset it + const defaultValue = deepClone(schema[key].default); + if (this._setValueByPath(schema[key], defaultValue).updated) { + result.updated.push({ data: [schema[key].path, defaultValue], piece: schema[key] }); + } // Throw an error if it's not array (nor type any) but an array was given - } else if (!schema[key].array && schema[key].type !== 'any' && Array.isArray(object[key])) { - list.errors.push([schema[key].path, new Error(`${schema[key].path} does not expect an array as value.`)]); - // Throw an error if the key requests an array and none is given - } else if (schema[key].array && !Array.isArray(object[key])) { - list.errors.push([schema[key].path, new Error(`${schema[key].path} expects an array as value.`)]); + } else if (schema[key].array !== Array.isArray(object[key])) { + result.errors.push(new TypeError(schema[key].array ? + `${schema[key].path} expects an array as value.` : + `${schema[key].path} does not expect an array as value.`)); } else { - const promise = schema[key].array && schema[key].type !== 'any' ? - Promise.all(object[key].map(entry => schema[key].parse(entry, guild) - .then(getIdentifier) - .catch(error => list.errors.push([schema[key].path, error])))) : - schema[key].parse(object[key], guild); - - list.promises.push(promise - .then(parsed => { - const parsedID = schema[key].array ? - parsed.filter(entry => typeof entry !== 'undefined') : - getIdentifier(parsed); - updateObject[key] = cache[key] = parsedID; - list.keys.push(schema[key].path); - list.values.push(parsedID); - }) - .catch(error => list.errors.push([schema.path, error]))); + const getID = schema[key].type !== 'any' ? getIdentifier : entry => entry; + result.promises.push((schema[key].array ? + Promise.all(object[key].map(entry => schema[key].parse(entry, guild).then(getID))) : + schema[key].parse(object[key], guild)).then(parsed => { + if (this._setValueByPath(schema[key], parsed).updated) { + result.updated.push({ data: [schema[key].path, parsed], piece: schema[key] }); + } + }).catch(error => { result.errors.push(error); })); } } } /** * Set a value by its path + * @since 0.5.0 * @param {SchemaPiece} piece The piece that manages the key * @param {*} parsedID The parsed ID value - * @param {(string|string[])} path The path of the key * @returns {{ updated: boolean, old: any }} * @private */ - _setValueByPath(piece, parsedID, path) { - if (typeof path === 'string') path = path.split('.'); + _setValueByPath(piece, parsedID) { + const path = piece.path.split('.'); const lastKey = path.pop(); let cache = this; // eslint-disable-line consistent-this for (const key of path) cache = cache[key] || {}; diff --git a/typings/index.d.ts b/typings/index.d.ts index 5e879b2e77..ad45435126 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -535,10 +535,10 @@ declare module 'klasa' { private _get(route: string | string[], piece?: boolean): object; private _get(route: string | string[], piece?: boolean): T; private _save(data: ConfigurationUpdateResult): Promise; - private _updateMany(object: any, guild?: GatewayGuildResolvable): Promise; + private _updateMany(object: any, guild?: GatewayGuildResolvable): Promise; private _parseSingle(key: string, value: any, guild: KlasaGuild | null, options: ConfigurationUpdateOptions, list: ConfigurationUpdateResult): Promise; - private _parseUpdateMany(cache: any, object: any, schema: SchemaFolder, guild: KlasaGuild, list: ConfigurationUpdateManyResult, updateObject: object): void; - private _setValueByPath(piece: SchemaPiece, parsedID: any, path: string | string[]): { updated: boolean, old: any }; + private _parseUpdateMany(cache: any, object: any, schema: SchemaFolder, guild: KlasaGuild, list: ConfigurationUpdateResult): void; + private _setValueByPath(piece: SchemaPiece, parsedID: any): { updated: boolean, old: any }; private _patch(data: any): void; public toJSON(): object; From 2f4309bac74b0424fad58f6e6ad32cc1bfdbec38 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 21:35:03 +0100 Subject: [PATCH 59/76] Revert accidental .eslintrc.json change --- .eslintrc.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 60a8545949..7f252f6c83 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,10 +1,3 @@ { - "parserOptions": { - "ecmaFeatures": { - "globalReturn": true, - "experimentalObjectRestSpread": true - }, - "ecmaVersion": 2017 - }, "extends": "klasa" } From 540f975753143ab1b5772c26d369bbabb0691e96 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 21:41:29 +0100 Subject: [PATCH 60/76] Fixed getPath, again --- src/lib/settings/Configuration.js | 4 ++-- src/lib/settings/Gateway.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index f82684ce5a..dfb4033cb0 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -151,7 +151,7 @@ class Configuration { * // Reset a key * Configuration#reset('prefix'); */ - async reset(keys, avoidUnconfigurable = true) { + async reset(keys, avoidUnconfigurable = false) { // If the entry does not exist in the DB, it'll never be able to reset a key if (!this._existsInDB) return { errors: [], updated: [] }; @@ -162,7 +162,7 @@ class Configuration { const entries = new Array(keys.length); for (let i = 0; i < keys.length; i++) entries[i] = [keys[i], null]; for (const [key, value] of entries) { - const path = this.gateway.getPath(key, { piece: false, avoidUnconfigurable, errors: false }); + const path = this.gateway.getPath(key, { piece: true, avoidUnconfigurable, errors: false }); if (!path) { result.errors.push(`The path ${key} does not exist in the current schema, or does not correspond to a piece.`); continue; diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index f5431080e2..d0ab476963 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -252,7 +252,7 @@ class Gateway extends GatewayStorage { if (errors) throw `Please, choose one of the following keys: '${keys.join('\', \'')}'`; } - return { piece, route: schema.path.split('.') }; + return { piece: schema, route: schema.path.split('.') }; } /** From abe0ddc7b25bc9e6227a29aeddbc55fc9d450ff9 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 21:48:04 +0100 Subject: [PATCH 61/76] Fix getPath, finally --- src/lib/settings/Gateway.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index d0ab476963..193e1617dc 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -235,7 +235,7 @@ class Gateway extends GatewayStorage { if (schema[currKey].type === 'Folder') { schema = schema[currKey]; } else if (piece) { - if (avoidUnconfigurable && !piece[currKey].configurable) { + if (avoidUnconfigurable && !schema[currKey].configurable) { if (errors) throw `The key ${schema[currKey].path} is not configurable in the current schema.`; return null; } @@ -244,7 +244,7 @@ class Gateway extends GatewayStorage { } if (piece && schema.type === 'Folder') { - const keys = piece.configurableKeys; + const keys = schema.configurableKeys; if (keys.length === 0) { if (errors) throw `This group is not configurable.`; return null; From 9c745d995b542ea7a57272849628644aba7477aa Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 21:50:58 +0100 Subject: [PATCH 62/76] Fixed another typo in Configuration#_setValueByPath --- src/lib/settings/Configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index dfb4033cb0..5579678997 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -477,7 +477,7 @@ class Configuration { let cache = this; // eslint-disable-line consistent-this for (const key of path) cache = cache[key] || {}; const old = cache[lastKey]; - if (piece.array ? !arraysEqual(old, path.default, true) : old !== piece.default) { + if (piece.array ? !arraysEqual(old, piece.default, true) : old !== piece.default) { cache[lastKey] = parsedID; return { updated: true, old }; } From 983794db9102234e3989de3a187cd66cf839bd62 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Fri, 23 Feb 2018 22:29:40 +0100 Subject: [PATCH 63/76] Fix Configuration#_setValueByPath not updating correctly --- src/lib/settings/Configuration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 5579678997..9826413410 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -204,7 +204,7 @@ class Configuration { * // Updating multiple keys (with arrays): * Configuration#update(['prefix', 'language'], ['k!', 'es-ES']); */ - async update(key, value, guild, options) { + async update(key, value, guild, options = {}) { if (typeof options === 'undefined' && isObject(guild)) { options = guild; guild = undefined; @@ -477,7 +477,7 @@ class Configuration { let cache = this; // eslint-disable-line consistent-this for (const key of path) cache = cache[key] || {}; const old = cache[lastKey]; - if (piece.array ? !arraysEqual(old, piece.default, true) : old !== piece.default) { + if (piece.array ? !arraysEqual(old, parsedID, true) : old !== parsedID) { cache[lastKey] = parsedID; return { updated: true, old }; } From 4d3bf30939f320fd3c26055011d88c248bc5bb50 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 00:23:34 +0100 Subject: [PATCH 64/76] Fixed Util.deepClone and SchemaFolder#_shardSyncSchema --- CHANGELOG.md | 2 ++ src/lib/settings/SchemaFolder.js | 12 +++++------- src/lib/util/util.js | 2 +- typings/index.d.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb1a1ecd3..f71c56847c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -181,6 +181,8 @@ NOTE: For the contributors, you add new entries to this document following this ### Fixed +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed `Util.deepClone` not cloning full objects. (kyranet) +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed `SchemaFolder#_shardSyncSchema` not passing the action as string. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed `null` values in *updateMany*'s pattern not updating nested keys plus individual queries. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed update/reset methods in Configuration not emitting `configEntryCreate` when the entry does not exist. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Fixed the updateMany pattern in Configuration not accepting a guild. (kyranet) diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 58f70ba056..761f087da5 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -60,7 +60,7 @@ class SchemaFolder extends Schema { */ get configurableKeys() { if (this.keyArray.length === 0) return []; - return this.keyArray.filter(key => this[key].type === 'Folder' || this[key].configurable); + return this.keyArray.filter(key => this[key].type === 'Folder' ? this[key].configurableKeys.length : this[key].configurable); } /** @@ -173,7 +173,7 @@ class SchemaFolder extends Schema { /** * Modifies all entries from the database. * @since 0.5.0 - * @param {('add'|'edit'|'delete')} action The action to perform + * @param {('add'|'delete')} action The action to perform * @param {string} key The key * @param {(SchemaPiece|SchemaFolder)} piece The SchemaPiece instance to handle * @returns {Promise<*>} @@ -186,8 +186,8 @@ class SchemaFolder extends Schema { const path = piece.path.split('.'); - if (action === 'add' || action === 'edit') { - const defValue = this.defaults[key]; + if (action === 'add') { + const defValue = piece.type === 'Folder' ? piece.defaults : piece.default; for (let value of this.gateway.cache.values()) { for (let j = 0; j < path.length - 1; j++) value = value[path[j]]; value[path[path.length - 1]] = deepClone(defValue); @@ -286,7 +286,7 @@ class SchemaFolder extends Schema { await this.client.shard.broadcastEval(` if (this.shard.id !== ${this.client.shard.id}) { this.gateways.${this.gateway.type}._shardSync( - ${JSON.stringify(piece.path.split('.'))}, ${JSON.stringify(piece)}, ${action}, ${force}); + ${JSON.stringify(piece.path.split('.'))}, ${JSON.stringify(piece)}, '${action}', ${force}); } `); } @@ -308,11 +308,9 @@ class SchemaFolder extends Schema { if (object[key].type === 'Folder') { const folder = new SchemaFolder(this.client, this.gateway, object[key], this, key); this[key] = folder; - this.defaults[key] = folder.defaults; } else { const piece = new SchemaPiece(this.client, this.gateway, object[key], this, key); this[key] = piece; - this.defaults[key] = piece.default; } this.keyArray.push(key); } diff --git a/src/lib/util/util.js b/src/lib/util/util.js index e9ec07e42b..26b18fd89f 100644 --- a/src/lib/util/util.js +++ b/src/lib/util/util.js @@ -103,7 +103,7 @@ class Util { } if (Util.isObject(source)) { const output = {}; - for (const key in source) output[key] = source[key]; + for (const key in source) output[key] = Util.deepClone(source[key]); return output; } if (source instanceof Map || source instanceof WeakMap) { diff --git a/typings/index.d.ts b/typings/index.d.ts index ad45435126..323b1c1003 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -652,7 +652,7 @@ declare module 'klasa' { public add(key: string, options: SchemaFolderAddOptions | { [k: string]: SchemaFolderAddOptions }, force?: boolean): Promise; public has(key: string): boolean; public remove(key: string, force?: boolean): Promise; - public force(action: 'add' | 'edit' | 'delete', key: string, piece: SchemaFolder | SchemaPiece): Promise; + public force(action: 'add' | 'delete', key: string, piece: SchemaFolder | SchemaPiece): Promise; public getDefaults(data?: object): object; public getSQL(array?: string[]): string[]; From fa130681076146a7a77f22b8bbab888db05052b2 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 00:48:18 +0100 Subject: [PATCH 65/76] Added overload for Configuration#reset, added COMMAND_CONF_NOCHANGE to language --- src/commands/Admin/conf.js | 18 ++++++++++++------ src/commands/General/User Configs/userconf.js | 18 ++++++++++++------ src/languages/en-US.js | 1 + src/lib/settings/Configuration.js | 16 +++++++++++++--- typings/index.d.ts | 1 + 5 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/commands/Admin/conf.js b/src/commands/Admin/conf.js index daf6a1f45b..a93e4a153a 100644 --- a/src/commands/Admin/conf.js +++ b/src/commands/Admin/conf.js @@ -30,18 +30,24 @@ module.exports = class extends Command { } async set(msg, [key, ...valueToSet]) { - const { piece } = await msg.guild.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', piece.path, msg.guild.configs.resolveString(msg, piece))); + const { errors, updated } = await msg.guild.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); + if (errors.length) return msg.sendMessage(errors[0]); + if (!updated.length) return msg.sendMessage(msg.language.get('COMMAND_CONF_NOCHANGE', key)); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', key, msg.guild.configs.resolveString(msg, updated[0].piece))); } async remove(msg, [key, ...valueToRemove]) { - const { piece } = await msg.guild.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', piece.path, msg.guild.configs.resolveString(msg, piece))); + const { errors, updated } = await msg.guild.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); + if (errors.length) return msg.sendMessage(errors[0]); + if (!updated.length) return msg.sendMessage(msg.language.get('COMMAND_CONF_NOCHANGE', key)); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', key, msg.guild.configs.resolveString(msg, updated[0].piece))); } async reset(msg, [key]) { - const { piece } = await msg.guild.configs.reset(key, true); - return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', piece.path, msg.guild.configs.resolveString(msg, piece))); + const { errors, updated } = await msg.guild.configs.reset(key, msg.guild, true); + if (errors.length) return msg.sendMessage(errors[0]); + if (!updated.length) return msg.sendMessage(msg.language.get('COMMAND_CONF_NOCHANGE', key)); + return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', key, msg.guild.configs.resolveString(msg, updated[0].piece))); } list(msg, [key]) { diff --git a/src/commands/General/User Configs/userconf.js b/src/commands/General/User Configs/userconf.js index 55d82993f5..092a89d49c 100644 --- a/src/commands/General/User Configs/userconf.js +++ b/src/commands/General/User Configs/userconf.js @@ -28,18 +28,24 @@ module.exports = class extends Command { } async set(msg, [key, ...valueToSet]) { - const { piece } = await msg.author.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', piece.path, msg.author.configs.resolveString(msg, piece))); + const { errors, updated } = await msg.author.configs.update(key, valueToSet.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'add' }); + if (errors.length) return msg.sendMessage(errors[0]); + if (!updated.length) return msg.sendMessage(msg.language.get('COMMAND_CONF_NOCHANGE', key)); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', key, msg.author.configs.resolveString(msg, updated[0].piece))); } async remove(msg, [key, ...valueToRemove]) { - const { piece } = await msg.author.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); - return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', piece.path, msg.author.configs.resolveString(msg, piece))); + const { errors, updated } = await msg.author.configs.update(key, valueToRemove.join(' '), msg.guild, { avoidUnconfigurable: true, action: 'remove' }); + if (errors.length) return msg.sendMessage(errors[0]); + if (!updated.length) return msg.sendMessage(msg.language.get('COMMAND_CONF_NOCHANGE', key)); + return msg.sendMessage(msg.language.get('COMMAND_CONF_UPDATED', key, msg.author.configs.resolveString(msg, updated[0].piece))); } async reset(msg, [key]) { - const { piece } = await msg.author.configs.reset(key, true); - return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', piece.path, msg.author.configs.resolveString(msg, piece))); + const { errors, updated } = await msg.author.configs.reset(key, true); + if (errors.length) return msg.sendMessage(errors[0]); + if (!updated.length) return msg.sendMessage(msg.language.get('COMMAND_CONF_NOCHANGE', key)); + return msg.sendMessage(msg.language.get('COMMAND_CONF_RESET', key, msg.author.configs.resolveString(msg, updated[0].piece))); } list(msg, [key]) { diff --git a/src/languages/en-US.js b/src/languages/en-US.js index 6bf0cc649f..377353ff9f 100644 --- a/src/languages/en-US.js +++ b/src/languages/en-US.js @@ -143,6 +143,7 @@ module.exports = class extends Language { COMMAND_CONF_GET_NOEXT: (key) => `The key **${key}** does not seem to exist.`, COMMAND_CONF_GET: (key, value) => `The value for the key **${key}** is: \`${value}\``, COMMAND_CONF_RESET: (key, response) => `The key **${key}** has been reset to: \`${response}\``, + COMMAND_CONF_NOCHANGE: (key) => `The value for **${key}** has not been changed, as it already has that value.`, COMMAND_CONF_SERVER_DESCRIPTION: 'Define per-server configuration.', COMMAND_CONF_SERVER: (key, list) => `**Server Configuration${key}**\n${list}`, COMMAND_CONF_USER_DESCRIPTION: 'Define per-user configuration.', diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 9826413410..de10562e5e 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -140,6 +140,7 @@ class Configuration { * Reset a value from an entry. * @since 0.5.0 * @param {(string|string[])} [keys] The key to reset + * @param {KlasaGuild} [guild] A KlasaGuild instance for multilanguage support * @param {boolean} [avoidUnconfigurable] Whether the Gateway should avoid configuring the selected key * @returns {ConfigurationUpdateResult} * // Reset all keys for this instance @@ -151,7 +152,12 @@ class Configuration { * // Reset a key * Configuration#reset('prefix'); */ - async reset(keys, avoidUnconfigurable = false) { + async reset(keys, guild, avoidUnconfigurable = false) { + if (typeof guild === 'boolean') { + avoidUnconfigurable = guild; + guild = undefined; + } + // If the entry does not exist in the DB, it'll never be able to reset a key if (!this._existsInDB) return { errors: [], updated: [] }; @@ -164,7 +170,9 @@ class Configuration { for (const [key, value] of entries) { const path = this.gateway.getPath(key, { piece: true, avoidUnconfigurable, errors: false }); if (!path) { - result.errors.push(`The path ${key} does not exist in the current schema, or does not correspond to a piece.`); + result.errors.push(guild && guild.language ? + guild.language.get('COMMAND_CONF_GET_NOEXT', key) : + `The path ${key} does not exist in the current schema, or does not correspond to a piece.`); continue; } const newValue = value === null ? deepClone(path.piece.default) : value; @@ -388,7 +396,9 @@ class Configuration { async _parseSingle(key, value, guild, { avoidUnconfigurable = false, action = 'auto', arrayPosition = null }, list) { const path = this.gateway.getPath(key, { piece: true, avoidUnconfigurable, errors: false }); if (!path) { - list.errors.push(`The path ${key} does not exist in the current schema, or does not correspond to a piece.`); + list.errors.push(guild && guild.language ? + guild.language.get('COMMAND_CONF_GET_NOEXT', key) : + `The path ${key} does not exist in the current schema, or does not correspond to a piece.`); return; } const { piece, route } = path; diff --git a/typings/index.d.ts b/typings/index.d.ts index 323b1c1003..dc0d1ede5a 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -526,6 +526,7 @@ declare module 'klasa' { public destroy(): Promise; public reset(key?: string | string[], avoidUnconfigurable?: boolean): Promise; + public reset(key?: string | string[], guild: KlasaGuild, avoidUnconfigurable?: boolean): Promise; public update(key: object, guild?: GatewayGuildResolvable): Promise; public update(key: string, value: any, guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; public update(key: string[], value: any[], guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; From cad228d3cb7cd808fb5182407356966d4fc549e1 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 00:59:52 +0100 Subject: [PATCH 66/76] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f71c56847c..f96049d8b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ NOTE: For the contributors, you add new entries to this document following this ### Added +- [[#179](https://github.com/dirigeants/klasa/pull/179)] Added the key `COMMAND_CONF_NOCHANGE` to the **en-US** language file. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added support for `Configuration#reset(string[]);` to reset multiple keys. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added `util.arraysEqual`. (kyranet) - [[#179](https://github.com/dirigeants/klasa/pull/179)] Added property `Symbol.iterator` to Schedule. (kyranet) From fbc9625cd51faa12108947116ee60ae6513d902a Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 01:02:11 +0100 Subject: [PATCH 67/76] Ao's request --- src/languages/en-US.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en-US.js b/src/languages/en-US.js index 377353ff9f..60eccb872a 100644 --- a/src/languages/en-US.js +++ b/src/languages/en-US.js @@ -143,7 +143,7 @@ module.exports = class extends Language { COMMAND_CONF_GET_NOEXT: (key) => `The key **${key}** does not seem to exist.`, COMMAND_CONF_GET: (key, value) => `The value for the key **${key}** is: \`${value}\``, COMMAND_CONF_RESET: (key, response) => `The key **${key}** has been reset to: \`${response}\``, - COMMAND_CONF_NOCHANGE: (key) => `The value for **${key}** has not been changed, as it already has that value.`, + COMMAND_CONF_NOCHANGE: (key) => `The value for **${key}** was already that value.`, COMMAND_CONF_SERVER_DESCRIPTION: 'Define per-server configuration.', COMMAND_CONF_SERVER: (key, list) => `**Server Configuration${key}**\n${list}`, COMMAND_CONF_USER_DESCRIPTION: 'Define per-user configuration.', From 2f7d631d7be06c25c3c5b3a315183a06893145df Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 01:19:41 +0100 Subject: [PATCH 68/76] Add KlasaClient#options docs, updated events, fixed typo in KlasaUser docs --- src/lib/Client.js | 15 +++++++++++---- src/lib/extensions/KlasaUser.js | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/lib/Client.js b/src/lib/Client.js index 046029a6c2..fdd6a13c4a 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -133,6 +133,13 @@ class KlasaClient extends Discord.Client { config = util.mergeDefault(constants.DEFAULTS.CLIENT, config); super(config); + /** + * The options for this guild instance. + * @since 0.5.0 + * @name KlasaClient#options + * @type {KlasaClientOptions} + */ + /** * The directory to the node_modules folder where Klasa exists * @since 0.0.1 @@ -638,12 +645,12 @@ KlasaClient.defaultPermissionLevels = new PermLevels() */ /** - * Emitted when {@link Configuration#update} is run. + * Emitted when {@link Configuration#update} or {@link Configuration#reset} is run. * @event KlasaClient#configUpdateEntry * @since 0.5.0 * @param {Configuration} oldEntry The old configuration entry * @param {Configuration} newEntry The new configuration entry - * @param {string[]} path The path of the key which changed + * @param {ConfigurationUpdateResultEntry[]} path The path of the key which changed */ /** @@ -654,8 +661,8 @@ KlasaClient.defaultPermissionLevels = new PermLevels() */ /** - * Emitted when {@link Gateway#createEntry} is run or when {@link Gateway#getEntry} - * with the create parameter set to true creates the entry. + * Emitted when {@link Gateway#createEntry} is run, when {@link Gateway#getEntry} + * with the create parameter set to true creates the entry, or an entry with no persistence gets updated. * @event KlasaClient#configCreateEntry * @since 0.5.0 * @param {Configuration} entry The entry which got created diff --git a/src/lib/extensions/KlasaUser.js b/src/lib/extensions/KlasaUser.js index 7fbb91f76b..ec60fa8550 100644 --- a/src/lib/extensions/KlasaUser.js +++ b/src/lib/extensions/KlasaUser.js @@ -14,7 +14,7 @@ module.exports = Structures.extend('User', User => { super(...args); /** - * The guild level configs for this context (guild || default) + * The user level configs for this context (user || default) * @since 0.5.0 * @type {Configuration} */ From 58a2109f323e57327b49a7ea1d081c336f1dc6d2 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 01:21:02 +0100 Subject: [PATCH 69/76] Typo --- src/lib/Client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Client.js b/src/lib/Client.js index fdd6a13c4a..78aa91a64f 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -134,7 +134,7 @@ class KlasaClient extends Discord.Client { super(config); /** - * The options for this guild instance. + * The options the client was instantiated with. * @since 0.5.0 * @name KlasaClient#options * @type {KlasaClientOptions} From a4d6db299f44bd109ff76b974e8920daabf3dd4e Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 01:28:26 +0100 Subject: [PATCH 70/76] Removed all `@memberof` from jsdocs, removed duplicated/useless typedefs --- src/lib/Client.js | 8 -------- src/lib/permissions/PermissionLevels.js | 1 - src/lib/schedule/Schedule.js | 2 -- src/lib/schedule/ScheduledTask.js | 3 --- src/lib/settings/Configuration.js | 3 --- src/lib/settings/Gateway.js | 17 +++-------------- src/lib/settings/GatewayDriver.js | 3 --- src/lib/settings/SchemaFolder.js | 2 -- src/lib/settings/SchemaPiece.js | 2 -- src/lib/structures/Command.js | 1 - src/lib/structures/Event.js | 1 - src/lib/structures/Extendable.js | 1 - src/lib/structures/Finalizer.js | 1 - src/lib/structures/Inhibitor.js | 1 - src/lib/structures/Language.js | 1 - src/lib/structures/Monitor.js | 1 - src/lib/structures/Provider.js | 1 - src/lib/structures/Task.js | 1 - src/lib/structures/base/Piece.js | 1 - src/lib/usage/TextPrompt.js | 1 - src/lib/util/Colors.js | 3 --- src/lib/util/KlasaConsole.js | 7 ------- src/lib/util/ReactionHandler.js | 2 -- src/lib/util/RichDisplay.js | 8 -------- src/lib/util/RichMenu.js | 9 --------- src/lib/util/Timestamp.js | 1 - src/lib/util/util.js | 1 - 27 files changed, 3 insertions(+), 80 deletions(-) diff --git a/src/lib/Client.js b/src/lib/Client.js index 78aa91a64f..a1b9cfcb3e 100644 --- a/src/lib/Client.js +++ b/src/lib/Client.js @@ -58,19 +58,16 @@ class KlasaClient extends Discord.Client { * @property {(string|Function)} [readyMessage=`Successfully initialized. Ready to serve ${this.guilds.size} guilds.`] readyMessage to be passed throughout Klasa's ready event * @property {RegExp} [regexPrefix] The regular expression prefix if one is provided * @property {boolean} [typing=false] Whether the bot should type while processing commands - * @memberof KlasaClient */ /** * @typedef {Object} KlasaProvidersOptions * @property {string} [default] The default provider to use - * @memberof KlasaClient */ /** * @typedef {Object} KlasaClientOptionsClock * @property {number} [interval] The interval in milliseconds for the clock to check the tasks - * @memberof KlasaClient */ /** @@ -78,7 +75,6 @@ class KlasaClient extends Discord.Client { * @property {GatewayDriverAddOptions} [clientStorage] The options for clientStorage's gateway * @property {GatewayDriverAddOptions} [guilds] The options for guilds' gateway * @property {GatewayDriverAddOptions} [users] The options for users' gateway - * @memberof KlasaClient */ /** @@ -88,7 +84,6 @@ class KlasaClient extends Discord.Client { * @property {boolean} [useColor=false] Whether the client console should use colors * @property {Colors} [colors] Color formats to use * @property {(boolean|string)} [timestamps=true] Whether to use timestamps or not, or the Timestamp format of the timestamp you want to use - * @memberof KlasaClient */ /** @@ -99,7 +94,6 @@ class KlasaClient extends Discord.Client { * @property {boolean} [verbose=false] If the verbose event should be enabled by default * @property {boolean} [warn=true] If the warn event should be enabled by default * @property {boolean} [wtf=true] If the wtf event should be enabled by default - * @memberof KlasaClient */ /** @@ -112,7 +106,6 @@ class KlasaClient extends Discord.Client { * @property {LanguageOptions} [languages={}] The default language options * @property {MonitorOptions} [monitors={}] The default monitor options * @property {ProviderOptions} [providers={}] The default provider options - * @memberof KlasaClient */ /** @@ -120,7 +113,6 @@ class KlasaClient extends Discord.Client { * @property {number} [promptLimit=Infinity] The number of re-prompts before custom prompt gives up * @property {number} [promptTime=30000] The time-limit for re-prompting custom prompts * @property {boolean} [quotedStringSupport=false] Whether the custom prompt should respect quoted strings - * @memberof KlasaClient */ /** diff --git a/src/lib/permissions/PermissionLevels.js b/src/lib/permissions/PermissionLevels.js index 1b7ba19523..a0cd64a8e3 100644 --- a/src/lib/permissions/PermissionLevels.js +++ b/src/lib/permissions/PermissionLevels.js @@ -14,7 +14,6 @@ class PermissionLevels extends Collection { * @typedef {Object} PermissionLevelsData * @property {boolean} broke Whether the loop broke execution of higher levels * @property {boolean} permission Whether the permission level check passed or not - * @memberof PermissionLevels */ /** diff --git a/src/lib/schedule/Schedule.js b/src/lib/schedule/Schedule.js index b9415bcd76..5fbff0151d 100644 --- a/src/lib/schedule/Schedule.js +++ b/src/lib/schedule/Schedule.js @@ -11,7 +11,6 @@ class Schedule { * @property {string} [id] The ID for the task. By default, it generates one in base36 * @property {string} [repeat] The {@link Cron} pattern * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution - * @memberof Schedule */ /** @@ -250,7 +249,6 @@ class Schedule { * @instance * @generator * @returns {Iterator} - * @memberof Schedule */ *[Symbol.iterator]() { diff --git a/src/lib/schedule/ScheduledTask.js b/src/lib/schedule/ScheduledTask.js index df36e97b2a..fc46c58bc7 100644 --- a/src/lib/schedule/ScheduledTask.js +++ b/src/lib/schedule/ScheduledTask.js @@ -9,7 +9,6 @@ class ScheduledTask { * @typedef {Object} ScheduledTaskOptions * @property {string} [id] The ID for the task. By default, it generates one in base36 * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution - * @memberof ScheduledTask */ /** @@ -17,7 +16,6 @@ class ScheduledTask { * @property {string} [repeat] The {@link Cron} pattern * @property {Date} [time] The time the current task ends at * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution - * @memberof ScheduledTask */ /** @@ -27,7 +25,6 @@ class ScheduledTask { * @property {number} time The UNIX timestamp for when this task ends at * @property {string} [repeat] The {@link Cron} pattern * @property {*} [data] The data to pass to the Task piece when the ScheduledTask is ready for execution - * @memberof ScheduledTask */ /** diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index de10562e5e..7ccc23e403 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -12,14 +12,12 @@ class Configuration { * @typedef {Object} ConfigurationUpdateResult * @property {Error[]} errors The errors caught from parsing * @property {ConfigurationUpdateResultEntry[]} updated The updated keys - * @memberof Configuration */ /** * @typedef {Object} ConfigurationUpdateResultEntry * @property {any[]} data A tuple containing the path of the updated key and the new value * @property {SchemaPiece} piece The SchemaPiece instance that manages the updated key - * @memberof Configuration */ /** @@ -28,7 +26,6 @@ class Configuration { * @property {('add'|'remove'|'auto')} [action='auto'] Whether the update (when using arrays) should add or remove, * leave it as 'auto' to add or remove depending on the existence of the key in the array * @property {number} [arrayPosition] The position of the array to replace - * @memberof Configuration */ /** diff --git a/src/lib/settings/Gateway.js b/src/lib/settings/Gateway.js index 193e1617dc..ec433a2829 100644 --- a/src/lib/settings/Gateway.js +++ b/src/lib/settings/Gateway.js @@ -13,38 +13,27 @@ const { getIdentifier } = require('../util/util'); */ class Gateway extends GatewayStorage { - /** - * @typedef {Object} GatewayOptions - * @property {Provider} [provider] The provider to use - * @property {boolean} [nice=false] Whether the JSON provider should use sequential or burst mode - * @memberof Gateway - */ - /** * @typedef {Object} GatewayGetPathOptions * @property {boolean} [avoidUnconfigurable=false] Whether the getPath should avoid unconfigurable keys * @property {boolean} [piece=true] Whether the getPath should return pieces or folders - * @memberof Gateway */ /** * @typedef {Object} GatewayGetPathResult * @property {SchemaPiece} piece The piece resolved from the path * @property {string[]} route The resolved path split by dots - * @memberof Gateway */ /** * @typedef {(KlasaGuild|KlasaMessage|external:TextChannel|external:VoiceChannel|external:CategoryChannel|external:GuildMember|external:Role)} GuildResolvable - * @memberof Gateway */ /** * @typedef {Object} GatewayJSON * @property {string} type The name of this gateway - * @property {GatewayOptions} options The options for this gateway + * @property {GatewayDriverAddOptions} options The options for this gateway * @property {Object} schema The current schema - * @memberof Gateway */ /** @@ -52,7 +41,7 @@ class Gateway extends GatewayStorage { * @param {GatewayDriver} store The GatewayDriver instance which initiated this instance * @param {string} type The name of this Gateway * @param {Object} schema The initial schema for this instance - * @param {GatewayOptions} options The options for this schema + * @param {GatewayDriverAddOptions} options The options for this schema */ constructor(store, type, schema, options) { super(store.client, type, options.provider); @@ -65,7 +54,7 @@ class Gateway extends GatewayStorage { /** * @since 0.5.0 - * @type {GatewayOptions} + * @type {GatewayDriverAddOptions} */ this.options = options; diff --git a/src/lib/settings/GatewayDriver.js b/src/lib/settings/GatewayDriver.js index 5b6b67ef7f..c26b5d7894 100644 --- a/src/lib/settings/GatewayDriver.js +++ b/src/lib/settings/GatewayDriver.js @@ -11,7 +11,6 @@ class GatewayDriver { * @typedef {Object} GatewayDriverAddOptions * @property {string} [provider] The name of the provider to use * @property {boolean} [nice=false] Whether the JSON provider should use sequential or burst mode - * @memberof GatewayDriver */ /** @@ -20,7 +19,6 @@ class GatewayDriver { * @property {SchemaPieceJSON} language The per-guild's configurable language key * @property {SchemaPieceJSON} disableNaturalPrefix The per-guild's configurable disableNaturalPrefix key * @property {SchemaPieceJSON} disabledCommands The per-guild's configurable disabledCommands key - * @memberof GatewayDriver * @private */ @@ -29,7 +27,6 @@ class GatewayDriver { * @property {SchemaPieceJSON} userBlacklist The client's configurable user blacklist key * @property {SchemaPieceJSON} guildBlacklist The client's configurable guild blacklist key * @property {SchemaPieceJSON} schedules The schedules where {@link ScheduledTask}s are stored at - * @memberof GatewayDriver * @private */ diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 761f087da5..6e2cbeaa1c 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -18,7 +18,6 @@ class SchemaFolder extends Schema { * @property {boolean} [array] Whether the key should be stored as Array or not * @property {string} [sql] The datatype of the key * @property {boolean} [configurable] Whether the key should be configurable by the config command or not - * @memberof SchemaFolder */ /** @@ -383,7 +382,6 @@ class SchemaFolder extends Schema { * @instance * @generator * @returns {Iterator>} - * @memberof SchemaFolder */ [Symbol.iterator]() { diff --git a/src/lib/settings/SchemaPiece.js b/src/lib/settings/SchemaPiece.js index 9e818df9bb..2d73c1821b 100644 --- a/src/lib/settings/SchemaPiece.js +++ b/src/lib/settings/SchemaPiece.js @@ -15,7 +15,6 @@ class SchemaPiece extends Schema { * @property {number} [max] The new maximum range value * @property {boolean} [configurable] The new configurable value * @property {string} [sql] The new sql datatype - * @memberof SchemaPiece */ /** @@ -27,7 +26,6 @@ class SchemaPiece extends Schema { * @property {string} sql A tuple containing the name of the column and its data type * @property {boolean} array Whether the key should be stored as Array or not * @property {boolean} configurable Whether the key should be configurable by the config command or not - * @memberof SchemaPiece */ /** diff --git a/src/lib/structures/Command.js b/src/lib/structures/Command.js index ff16c19da4..786de70bd6 100644 --- a/src/lib/structures/Command.js +++ b/src/lib/structures/Command.js @@ -32,7 +32,6 @@ class Command extends Piece { * @property {boolean} [subcommands=false] Whether to enable sub commands or not * @property {string} [usage=''] The usage string for the command * @property {?string} [usageDelim=undefined] The string to delimit the command input for usage - * @memberof Command */ /** diff --git a/src/lib/structures/Event.js b/src/lib/structures/Event.js index e5abe86fd4..d2095f2b81 100644 --- a/src/lib/structures/Event.js +++ b/src/lib/structures/Event.js @@ -10,7 +10,6 @@ class Event extends Piece { /** * @typedef {PieceOptions} EventOptions - * @memberof Event */ /** diff --git a/src/lib/structures/Extendable.js b/src/lib/structures/Extendable.js index 9461a1b1dc..4f79ceaa91 100644 --- a/src/lib/structures/Extendable.js +++ b/src/lib/structures/Extendable.js @@ -12,7 +12,6 @@ class Extendable extends Piece { /** * @typedef {PieceOptions} ExtendableOptions * @property {boolean} [klasa=false] If the extendable is for Klasa instead of Discord.js - * @memberof Extendable */ /** diff --git a/src/lib/structures/Finalizer.js b/src/lib/structures/Finalizer.js index a267aa5d9b..1199988c2b 100644 --- a/src/lib/structures/Finalizer.js +++ b/src/lib/structures/Finalizer.js @@ -10,7 +10,6 @@ class Finalizer extends Piece { /** * @typedef {PieceOptions} FinalizerOptions - * @memberof Finalizer */ /** diff --git a/src/lib/structures/Inhibitor.js b/src/lib/structures/Inhibitor.js index 3f89be949d..3c59175cd8 100644 --- a/src/lib/structures/Inhibitor.js +++ b/src/lib/structures/Inhibitor.js @@ -11,7 +11,6 @@ class Inhibitor extends Piece { /** * @typedef {PieceOptions} InhibitorOptions * @property {boolean} [spamProtection=false] If this inhibitor is meant for spamProtection (disables the inhibitor while generating help) - * @memberof Inhibitor */ /** diff --git a/src/lib/structures/Language.js b/src/lib/structures/Language.js index 235014c560..4662e44413 100644 --- a/src/lib/structures/Language.js +++ b/src/lib/structures/Language.js @@ -13,7 +13,6 @@ class Language extends Piece { /** * @typedef {PieceOptions} LanguageOptions - * @memberof Language */ /** diff --git a/src/lib/structures/Monitor.js b/src/lib/structures/Monitor.js index 6b9b851fbc..5fee9833e9 100644 --- a/src/lib/structures/Monitor.js +++ b/src/lib/structures/Monitor.js @@ -14,7 +14,6 @@ class Monitor extends Piece { * @property {boolean} [ignoreSelf=true] Whether the monitor ignores itself or not * @property {boolean} [ignoreOthers=true] Whether the monitor ignores others or not * @property {boolean} [ignoreWebhooks=true] Whether the monitor ignores webhooks or not - * @memberof Monitor */ /** diff --git a/src/lib/structures/Provider.js b/src/lib/structures/Provider.js index 79e4ce149f..bad5259581 100644 --- a/src/lib/structures/Provider.js +++ b/src/lib/structures/Provider.js @@ -11,7 +11,6 @@ class Provider extends Piece { /** * @typedef {PieceOptions} ProviderOptions * @property {boolean} [sql=false] If the provider provides to a sql data source - * @memberof Provider */ /** diff --git a/src/lib/structures/Task.js b/src/lib/structures/Task.js index 836de0e7bd..bb5cc5833f 100644 --- a/src/lib/structures/Task.js +++ b/src/lib/structures/Task.js @@ -10,7 +10,6 @@ class Task extends Piece { /** * @typedef {PieceOptions} TaskOptions - * @memberof Task */ /** diff --git a/src/lib/structures/base/Piece.js b/src/lib/structures/base/Piece.js index c647d87019..62e1ab94e0 100644 --- a/src/lib/structures/base/Piece.js +++ b/src/lib/structures/base/Piece.js @@ -18,7 +18,6 @@ class Piece { * @typedef {Object} PieceOptions * @property {string} [name=theFileName] The name of the event * @property {boolean} [enabled=true] Whether the event is enabled or not - * @memberof Piece */ /** diff --git a/src/lib/usage/TextPrompt.js b/src/lib/usage/TextPrompt.js index e086a36ba5..77911a53d9 100644 --- a/src/lib/usage/TextPrompt.js +++ b/src/lib/usage/TextPrompt.js @@ -11,7 +11,6 @@ class TextPrompt { * @property {number} [promptLimit=Infinity] The number of re-prompts before this TextPrompt gives up * @property {number} [promptTime=30000] The time-limit for re-prompting * @property {boolean} [quotedStringSupport=false] Whether this prompt should respect quoted strings - * @memberof TextPrompt */ /** diff --git a/src/lib/util/Colors.js b/src/lib/util/Colors.js index 7dc9d26f7b..efb3a8dd34 100644 --- a/src/lib/util/Colors.js +++ b/src/lib/util/Colors.js @@ -11,19 +11,16 @@ class Colors { * @property {(string|string[])} style The style or styles to apply * @property {ColorsFormatType} background The format for the background * @property {ColorsFormatType} text The format for the text - * @memberof Colors */ /** * @typedef {(string|number|string[]|number[])} ColorsFormatType - * @memberof Colors */ /** * @typedef {Object} ColorsFormatData * @property {string[]} opening The opening format data styles * @property {string[]} closing The closing format data styles - * @memberof Colors * @private */ diff --git a/src/lib/util/KlasaConsole.js b/src/lib/util/KlasaConsole.js index 8e06fbb227..d54ba416c1 100644 --- a/src/lib/util/KlasaConsole.js +++ b/src/lib/util/KlasaConsole.js @@ -17,7 +17,6 @@ class KlasaConsole extends Console { * @property {NodeJS.WritableStream} [stderr] The WritableStrwam for the error logs * @property {(boolean|string)} [timestamps] If false, it won't use timestamps. Otherwise it will use 'YYYY-MM-DD HH:mm:ss' if true or custom if string is given * @property {boolean} [useColor] Whether the timestamps should use colours - * @memberof KlasaConsole */ /** @@ -28,7 +27,6 @@ class KlasaConsole extends Console { * @property {KlasaConsoleColorObjects} verbose An object containing a message and time color object * @property {KlasaConsoleColorObjects} warn An object containing a message and time color object * @property {KlasaConsoleColorObjects} wtf An object containing a message and time Color Object - * @memberof KlasaConsole */ /** @@ -36,7 +34,6 @@ class KlasaConsole extends Console { * @property {string} [type='log'] The method from Console this color object should call * @property {KlasaConsoleMessageObject} message A message object containing colors and styles * @property {KlasaConsoleTimeObject} time A time object containing colors and styles - * @memberof KlasaConsole */ /** @@ -44,7 +41,6 @@ class KlasaConsole extends Console { * @property {KlasaConsoleColorTypes} background The background color. Can be a basic string like "red", a hex string, or a RGB array * @property {KlasaConsoleColorTypes} text The text color. Can be a basic string like "red", a hex string, or a RGB array * @property {KlasaConsoleStyleTypes} style A style string from KlasaConsoleStyleTypes - * @memberof KlasaConsole */ /** @@ -52,7 +48,6 @@ class KlasaConsole extends Console { * @property {KlasaConsoleColorTypes} background The background color. Can be a basic string like "red", a hex string, or a RGB array * @property {KlasaConsoleColorTypes} text The text color. Can be a basic string like "red", a hex string, a RGB array, or HSL array * @property {KlasaConsoleStyleTypes} style A style string from KlasaConsoleStyleTypes - * @memberof KlasaConsole */ /** @@ -75,7 +70,6 @@ class KlasaConsole extends Console { * @property {string} lightmagenta The light magenta colour * @property {string} lightcyan The light cyan colour * @property {string} white The white colour - * @memberof KlasaConsole */ /** @@ -88,7 +82,6 @@ class KlasaConsole extends Console { * @property {string} inverse Inverse colours style * @property {string} hidden Hidden text style * @property {string} strikethrough Strikethrough text style - * @memberof KlasaConsole */ /** diff --git a/src/lib/util/ReactionHandler.js b/src/lib/util/ReactionHandler.js index 5160344717..68da972e60 100644 --- a/src/lib/util/ReactionHandler.js +++ b/src/lib/util/ReactionHandler.js @@ -9,7 +9,6 @@ class ReactionHandler extends ReactionCollector { /** * A single unicode character * @typedef {string} Emoji - * @memberof ReactionHandler */ /** @@ -22,7 +21,6 @@ class ReactionHandler extends ReactionCollector { * @property {number} [maxEmojis] The maximum number of emojis to collect * @property {number} [maxUsers] The maximum number of users to react * @property {number} [time] The maximum amount of time before this RichMenu should expire - * @memberof RichMenu */ /** diff --git a/src/lib/util/RichDisplay.js b/src/lib/util/RichDisplay.js index aa6bec0dc5..76d2781034 100644 --- a/src/lib/util/RichDisplay.js +++ b/src/lib/util/RichDisplay.js @@ -6,12 +6,6 @@ const ReactionHandler = require('./ReactionHandler'); */ class RichDisplay { - /** - * A single unicode character - * @typedef {string} Emoji - * @memberof RichDisplay - */ - /** * @typedef {Object} RichDisplayEmojisObject * @property {Emoji} first The emoji for the 'first' button @@ -21,7 +15,6 @@ class RichDisplay { * @property {Emoji} jump The emoji for the 'jump' button * @property {Emoji} info The emoji for the 'info' button * @property {Emoji} stop The emoji for the 'stop' button - * @memberof RichDisplay */ /** @@ -36,7 +29,6 @@ class RichDisplay { * @property {number} [maxEmojis] The maximum number of emojis to collect * @property {number} [maxUsers] The maximum number of users to react * @property {number} [time] The maximum amount of time before this RichDisplay should expire - * @memberof RichDisplay */ /** diff --git a/src/lib/util/RichMenu.js b/src/lib/util/RichMenu.js index 19bbfe7895..3140203908 100644 --- a/src/lib/util/RichMenu.js +++ b/src/lib/util/RichMenu.js @@ -6,12 +6,6 @@ const RichDisplay = require('./RichDisplay'); */ class RichMenu extends RichDisplay { - /** - * A single unicode character - * @typedef {string} Emoji - * @memberof RichMenu - */ - /** * @typedef {RichDisplayEmojisObject} RichMenuEmojisObject * @property {Emoji} zero The emoji for the 'zero' button @@ -24,7 +18,6 @@ class RichMenu extends RichDisplay { * @property {Emoji} seven The emoji for the 'seven' button * @property {Emoji} eight The emoji for the 'eight' button * @property {Emoji} nine The emoji for the 'nine' button - * @memberof RichMenu */ /** @@ -32,7 +25,6 @@ class RichMenu extends RichDisplay { * @property {string} name The name of the option * @property {string} body The description of the option * @property {boolean} [inline=false] Whether the option should be inline - * @memberof RichMenu */ /** @@ -45,7 +37,6 @@ class RichMenu extends RichDisplay { * @property {number} [maxEmojis] The maximum number of emojis to collect * @property {number} [maxUsers] The maximum number of users to react * @property {number} [time] The maximum amount of time before this RichMenu should expire - * @memberof RichMenu */ /** diff --git a/src/lib/util/Timestamp.js b/src/lib/util/Timestamp.js index 59df7ec4ee..a07b6a9a6a 100644 --- a/src/lib/util/Timestamp.js +++ b/src/lib/util/Timestamp.js @@ -9,7 +9,6 @@ class Timestamp { * @typedef {Object} TimestampObject * @property {string} type The type of the current variable * @property {string} [content] The content of the type. Only accessible if the type is 'literal' - * @memberof Timestamp */ /** diff --git a/src/lib/util/util.js b/src/lib/util/util.js index 26b18fd89f..08c3da5daf 100644 --- a/src/lib/util/util.js +++ b/src/lib/util/util.js @@ -382,7 +382,6 @@ class Util { * @property {string|number} [killSignal='SIGTERM'] The kill signal * @property {number} [uid] Sets the user identity of the process * @property {number} [gid] Sets the group identity of the process - * @memberof Util */ /** From c1953c5b7269c2464d907768a92c90a5cdefbe7f Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 10:39:56 +0100 Subject: [PATCH 71/76] Don't show folders if they don't have any configurable keys --- src/lib/settings/Configuration.js | 4 ++-- src/lib/settings/SchemaFolder.js | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 7ccc23e403..b3cecc0616 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -246,14 +246,14 @@ class Configuration { * @returns {string} */ list(msg, path) { - const folder = path instanceof SchemaFolder ? path : this.gateway.getPath(path, { piece: false }).path; + const folder = path instanceof SchemaFolder ? path : this.gateway.getPath(path, { piece: false }).piece; const array = []; const folders = []; const keys = {}; let longest = 0; for (const [key, value] of folder.entries()) { if (value.type === 'Folder') { - folders.push(`// ${key}`); + if (value.configurableKeys.length) folders.push(`// ${key}`); } else if (value.configurable) { if (!(value.type in keys)) keys[value.type] = []; if (key.length > longest) longest = key.length; diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 6e2cbeaa1c..8391f21f35 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -325,7 +325,9 @@ class SchemaFolder extends Schema { * @since 0.5.0 * @param {boolean} recursive Whether the iteration should be recursive * @yields {Array} + * @returns {Iterator} */ + *entries(recursive = false) { if (recursive) { for (const key of this.keyArray) { @@ -343,7 +345,9 @@ class SchemaFolder extends Schema { * @since 0.5.0 * @param {boolean} recursive Whether the iteration should be recursive * @yields {(SchemaFolder|SchemaPiece)} + * @returns {Iterator} */ + *values(recursive = false) { if (recursive) { for (const key of this.keyArray) { @@ -361,7 +365,9 @@ class SchemaFolder extends Schema { * @since 0.5.0 * @param {boolean} recursive Whether the iteration should be recursive * @yields {string} + * @returns {Iterator} */ + *keys(recursive = false) { if (recursive) { for (const key of this.keyArray) { From e9f54f0acd886afa2bc806aa4efe89442ebb22bd Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 10:54:21 +0100 Subject: [PATCH 72/76] Fixed Configuration#resolveString not getting the right values --- src/lib/settings/Configuration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index b3cecc0616..75c62535fc 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -91,7 +91,7 @@ class Configuration { */ get(key) { if (!key.includes('.')) return this.gateway.schema.has(key) ? this[key] : undefined; - return this.get(key.split('.'), false); + return this.get(key.split('.'), true); } /** @@ -283,7 +283,7 @@ class Configuration { * @private */ resolveString(msg, path) { - const piece = path instanceof SchemaPiece ? path : this.gateway.getPath(path, { piece: true }).path; + const piece = path instanceof SchemaPiece ? path : this.gateway.getPath(path, { piece: true }).piece; const value = this.get(piece.path); if (value === null) return 'Not set'; if (piece.array && value.length === 0) return 'None'; From 484177a53fcc7ac0703245585bec924529b408e0 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 10:55:01 +0100 Subject: [PATCH 73/76] Fixed ESLint --- src/lib/settings/SchemaFolder.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/lib/settings/SchemaFolder.js b/src/lib/settings/SchemaFolder.js index 8391f21f35..6e2cbeaa1c 100644 --- a/src/lib/settings/SchemaFolder.js +++ b/src/lib/settings/SchemaFolder.js @@ -325,9 +325,7 @@ class SchemaFolder extends Schema { * @since 0.5.0 * @param {boolean} recursive Whether the iteration should be recursive * @yields {Array} - * @returns {Iterator} */ - *entries(recursive = false) { if (recursive) { for (const key of this.keyArray) { @@ -345,9 +343,7 @@ class SchemaFolder extends Schema { * @since 0.5.0 * @param {boolean} recursive Whether the iteration should be recursive * @yields {(SchemaFolder|SchemaPiece)} - * @returns {Iterator} */ - *values(recursive = false) { if (recursive) { for (const key of this.keyArray) { @@ -365,9 +361,7 @@ class SchemaFolder extends Schema { * @since 0.5.0 * @param {boolean} recursive Whether the iteration should be recursive * @yields {string} - * @returns {Iterator} */ - *keys(recursive = false) { if (recursive) { for (const key of this.keyArray) { From 120970c9de8bc4d851c6aec13cd53c22f3e68258 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 10:58:46 +0100 Subject: [PATCH 74/76] Fixed Configuration#get calling itself recursively instead of Configuration#_get --- src/lib/settings/Configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 75c62535fc..7c5e81e853 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -91,7 +91,7 @@ class Configuration { */ get(key) { if (!key.includes('.')) return this.gateway.schema.has(key) ? this[key] : undefined; - return this.get(key.split('.'), true); + return this._get(key.split('.'), true); } /** From f61d9e25ab40c391a1d06707604d883803876ef8 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 12:14:18 +0100 Subject: [PATCH 75/76] Fixed resetAll pattern not resetting nested keys --- src/lib/settings/Configuration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/settings/Configuration.js b/src/lib/settings/Configuration.js index 7c5e81e853..24e9276f8a 100644 --- a/src/lib/settings/Configuration.js +++ b/src/lib/settings/Configuration.js @@ -159,7 +159,7 @@ class Configuration { if (!this._existsInDB) return { errors: [], updated: [] }; if (typeof keys === 'string') keys = [keys]; - else if (typeof keys === 'undefined') keys = [...this.gateway.schema.keys(true)]; + else if (typeof keys === 'undefined') keys = [...this.gateway.schema.values(true)].map(piece => piece.path); if (Array.isArray(keys)) { const result = { errors: [], updated: [] }; const entries = new Array(keys.length); From 48161622ec0e38c262d8c2de278f28b84ce85c00 Mon Sep 17 00:00:00 2001 From: kyraNET Date: Sat, 24 Feb 2018 15:47:39 +0100 Subject: [PATCH 76/76] Typings fixes --- typings/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index c69f3d599e..743e58ea3b 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -526,7 +526,7 @@ declare module 'klasa' { public destroy(): Promise; public reset(key?: string | string[], avoidUnconfigurable?: boolean): Promise; - public reset(key?: string | string[], guild: KlasaGuild, avoidUnconfigurable?: boolean): Promise; + public reset(key?: string | string[], guild?: KlasaGuild, avoidUnconfigurable?: boolean): Promise; public update(key: object, guild?: GatewayGuildResolvable): Promise; public update(key: string, value: any, guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; public update(key: string[], value: any[], guild?: GatewayGuildResolvable, options?: ConfigurationUpdateOptions): Promise; @@ -686,7 +686,7 @@ declare module 'klasa' { public parse(value: any, guild: KlasaGuild): Promise; public modify(options: SchemaPieceEditOptions): Promise; - private _generateDefault(): [] | false | null; + private _generateDefault(): Array | false | null; private _schemaCheckType(type: string): void; private _schemaCheckArray(array: boolean): void; private _schemaCheckDefault(options: SchemaFolderAddOptions): void;