From 253708bea4927574f8d26be3832930aa0fab6abb Mon Sep 17 00:00:00 2001 From: SebC99 Date: Fri, 19 Jun 2020 21:31:44 +0200 Subject: [PATCH 1/6] Remove schemaCache --- .../Storage/Mongo/MongoStorageAdapter.js | 15 +++- src/Config.js | 9 +- src/Controllers/DatabaseController.js | 10 +-- src/Controllers/SchemaController.js | 82 +++++++++---------- src/Controllers/index.js | 12 +-- 5 files changed, 59 insertions(+), 69 deletions(-) diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 7b8d2a3ef3..432a34ae8d 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -130,6 +130,8 @@ export class MongoStorageAdapter implements StorageAdapter { _uri: string; _collectionPrefix: string; _mongoOptions: Object; + _stream: any; + _onchange: any; // Public connectionPromise: ?Promise; database: any; @@ -147,6 +149,7 @@ export class MongoStorageAdapter implements StorageAdapter { this._mongoOptions = mongoOptions; this._mongoOptions.useNewUrlParser = true; this._mongoOptions.useUnifiedTopology = true; + this._onchange = () => {}; // MaxTimeMS is not a global MongoDB client option, it is applied per operation. this._maxTimeMS = mongoOptions.maxTimeMS; @@ -154,6 +157,10 @@ export class MongoStorageAdapter implements StorageAdapter { delete mongoOptions.maxTimeMS; } + watch(callback) { + this._onchange = callback; + } + connect() { if (this.connectionPromise) { return this.connectionPromise; @@ -219,7 +226,13 @@ export class MongoStorageAdapter implements StorageAdapter { _schemaCollection(): Promise { return this.connect() .then(() => this._adaptiveCollection(MongoSchemaCollectionName)) - .then((collection) => new MongoSchemaCollection(collection)); + .then((collection) => { + if (!this._stream) { + this._stream = collection._mongoCollection.watch(); + this._stream.on('change', this._onchange); + } + return new MongoSchemaCollection(collection); + }); } classExists(name: string) { diff --git a/src/Config.js b/src/Config.js index 2077626ff8..978bd0b44d 100644 --- a/src/Config.js +++ b/src/Config.js @@ -3,7 +3,6 @@ // mount is the URL for the root of the API; includes http, domain, etc. import AppCache from './cache'; -import SchemaCache from './Controllers/SchemaCache'; import DatabaseController from './Controllers/DatabaseController'; import net from 'net'; @@ -27,14 +26,8 @@ export class Config { config.applicationId = applicationId; Object.keys(cacheInfo).forEach(key => { if (key == 'databaseController') { - const schemaCache = new SchemaCache( - cacheInfo.cacheController, - cacheInfo.schemaCacheTTL, - cacheInfo.enableSingleSchemaCache - ); config.database = new DatabaseController( - cacheInfo.databaseController.adapter, - schemaCache + cacheInfo.databaseController.adapter ); } else { config[key] = cacheInfo[key]; diff --git a/src/Controllers/DatabaseController.js b/src/Controllers/DatabaseController.js index b56a8d3efc..09518c7afd 100644 --- a/src/Controllers/DatabaseController.js +++ b/src/Controllers/DatabaseController.js @@ -422,13 +422,11 @@ const relationSchema = { class DatabaseController { adapter: StorageAdapter; - schemaCache: any; schemaPromise: ?Promise; _transactionalSession: ?any; - constructor(adapter: StorageAdapter, schemaCache: any) { + constructor(adapter: StorageAdapter) { this.adapter = adapter; - this.schemaCache = schemaCache; // We don't want a mutable this.schema, because then you could have // one request that uses different schemas for different parts of // it. Instead, use loadSchema to get a schema. @@ -469,7 +467,6 @@ class DatabaseController { } this.schemaPromise = SchemaController.load( this.adapter, - this.schemaCache, options ); this.schemaPromise.then( @@ -1011,10 +1008,7 @@ class DatabaseController { */ deleteEverything(fast: boolean = false): Promise { this.schemaPromise = null; - return Promise.all([ - this.adapter.deleteAllClasses(fast), - this.schemaCache.clear(), - ]); + return this.adapter.deleteAllClasses(fast); } // Returns a promise for a list of related ids given an owning id. diff --git a/src/Controllers/SchemaController.js b/src/Controllers/SchemaController.js index 435a4b5570..e1c1a89a27 100644 --- a/src/Controllers/SchemaController.js +++ b/src/Controllers/SchemaController.js @@ -695,15 +695,15 @@ const typeToString = (type: SchemaField | string): string => { // the mongo format and the Parse format. Soon, this will all be Parse format. export default class SchemaController { _dbAdapter: StorageAdapter; - schemaData: { [string]: Schema }; _cache: any; + schemaData: { [string]: Schema }; reloadDataPromise: ?Promise; protectedFields: any; userIdRegEx: RegExp; - constructor(databaseAdapter: StorageAdapter, schemaCache: any) { + constructor(databaseAdapter: StorageAdapter, singleSchemaCache: Object) { this._dbAdapter = databaseAdapter; - this._cache = schemaCache; + this._cache = singleSchemaCache; this.schemaData = new SchemaData(); this.protectedFields = Config.get(Parse.applicationId).protectedFields; @@ -713,6 +713,10 @@ export default class SchemaController { const autoIdRegEx = /^[a-zA-Z0-9]{1,}$/; this.userIdRegEx = customIds ? customIdRegEx : autoIdRegEx; + + this._dbAdapter.watch(() => { + this.reloadData({clearCache: true}); + }) } reloadData(options: LoadSchemaOptions = { clearCache: false }): Promise { @@ -741,12 +745,10 @@ export default class SchemaController { if (options.clearCache) { return this.setAllClasses(); } - return this._cache.getAllClasses().then(allClasses => { - if (allClasses && allClasses.length) { - return Promise.resolve(allClasses); - } - return this.setAllClasses(); - }); + if (this._cache.allClasses && this._cache.allClasses.length) { + return Promise.resolve(this._cache.allClasses); + } + return this.setAllClasses(); } setAllClasses(): Promise> { @@ -754,13 +756,7 @@ export default class SchemaController { .getAllClasses() .then(allSchemas => allSchemas.map(injectDefaultSchema)) .then(allSchemas => { - /* eslint-disable no-console */ - this._cache - .setAllClasses(allSchemas) - .catch(error => - console.error('Error saving schema to cache:', error) - ); - /* eslint-enable no-console */ + this._cache.allClasses = allSchemas; return allSchemas; }); } @@ -770,34 +766,28 @@ export default class SchemaController { allowVolatileClasses: boolean = false, options: LoadSchemaOptions = { clearCache: false } ): Promise { - let promise = Promise.resolve(); if (options.clearCache) { - promise = this._cache.clear(); + this._cache.allClasses = undefined; } - return promise.then(() => { - if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) { - const data = this.schemaData[className]; - return Promise.resolve({ - className, - fields: data.fields, - classLevelPermissions: data.classLevelPermissions, - indexes: data.indexes, - }); - } - return this._cache.getOneSchema(className).then(cached => { - if (cached && !options.clearCache) { - return Promise.resolve(cached); - } - return this.setAllClasses().then(allSchemas => { - const oneSchema = allSchemas.find( - schema => schema.className === className - ); - if (!oneSchema) { - return Promise.reject(undefined); - } - return oneSchema; - }); + if (allowVolatileClasses && volatileClasses.indexOf(className) > -1) { + const data = this.schemaData[className]; + return Promise.resolve({ + className, + fields: data.fields, + classLevelPermissions: data.classLevelPermissions, + indexes: data.indexes, }); + } + const oneSchema = (this._cache.allClasses || []).find(schema => schema.className === className); + if (oneSchema && !options.clearCache) { + return Promise.resolve(oneSchema); + } + return this.setAllClasses().then(allSchemas => { + const oneSchema = allSchemas.find(schema => schema.className === className); + if (!oneSchema) { + return Promise.reject(undefined); + } + return oneSchema; }); } @@ -1296,7 +1286,10 @@ export default class SchemaController { ); }); }) - .then(() => this._cache.clear()); + .then(() => { + this._cache.allClasses = undefined; + return Promise.resolve(); + }); } // Validates an object provided in REST format. @@ -1531,13 +1524,14 @@ export default class SchemaController { } } +const singleSchemaCache = {}; + // Returns a promise for a new Schema. const load = ( dbAdapter: StorageAdapter, - schemaCache: any, options: any ): Promise => { - const schema = new SchemaController(dbAdapter, schemaCache); + const schema = new SchemaController(dbAdapter, singleSchemaCache); return schema.reloadData(options).then(() => schema); }; diff --git a/src/Controllers/index.js b/src/Controllers/index.js index d10ad8001c..4683447516 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -41,7 +41,7 @@ export function getControllers(options: ParseServerOptions) { const cacheController = getCacheController(options); const analyticsController = getAnalyticsController(options); const liveQueryController = getLiveQueryController(options); - const databaseController = getDatabaseController(options, cacheController); + const databaseController = getDatabaseController(options); const hooksController = getHooksController(options, databaseController); const authDataManager = getAuthDataManager(options); const parseGraphQLController = getParseGraphQLController(options, { @@ -165,15 +165,12 @@ export function getLiveQueryController( } export function getDatabaseController( - options: ParseServerOptions, - cacheController: CacheController + options: ParseServerOptions ): DatabaseController { const { databaseURI, databaseOptions, - collectionPrefix, - schemaCacheTTL, - enableSingleSchemaCache, + collectionPrefix } = options; let { databaseAdapter } = options; if ( @@ -193,8 +190,7 @@ export function getDatabaseController( databaseAdapter = loadAdapter(databaseAdapter); } return new DatabaseController( - databaseAdapter, - new SchemaCache(cacheController, schemaCacheTTL, enableSingleSchemaCache) + databaseAdapter ); } From d87816c45b5b5509c05989a2d1f3a18f6befd4f1 Mon Sep 17 00:00:00 2001 From: SebC99 Date: Fri, 19 Jun 2020 23:44:37 +0200 Subject: [PATCH 2/6] Remove the new databaseController on each request --- src/Config.js | 4 +-- src/Controllers/SchemaCache.js | 59 ---------------------------------- src/Controllers/index.js | 1 - 3 files changed, 1 insertion(+), 63 deletions(-) delete mode 100644 src/Controllers/SchemaCache.js diff --git a/src/Config.js b/src/Config.js index 978bd0b44d..93eb5d5a8e 100644 --- a/src/Config.js +++ b/src/Config.js @@ -26,9 +26,7 @@ export class Config { config.applicationId = applicationId; Object.keys(cacheInfo).forEach(key => { if (key == 'databaseController') { - config.database = new DatabaseController( - cacheInfo.databaseController.adapter - ); + config.database = cacheInfo.databaseController; } else { config[key] = cacheInfo[key]; } diff --git a/src/Controllers/SchemaCache.js b/src/Controllers/SchemaCache.js deleted file mode 100644 index 55d70eabaa..0000000000 --- a/src/Controllers/SchemaCache.js +++ /dev/null @@ -1,59 +0,0 @@ -const MAIN_SCHEMA = '__MAIN_SCHEMA'; -const SCHEMA_CACHE_PREFIX = '__SCHEMA'; - -import { randomString } from '../cryptoUtils'; -import defaults from '../defaults'; - -export default class SchemaCache { - cache: Object; - - constructor( - cacheController, - ttl = defaults.schemaCacheTTL, - singleCache = false - ) { - this.ttl = ttl; - if (typeof ttl == 'string') { - this.ttl = parseInt(ttl); - } - this.cache = cacheController; - this.prefix = SCHEMA_CACHE_PREFIX; - if (!singleCache) { - this.prefix += randomString(20); - } - } - - getAllClasses() { - if (!this.ttl) { - return Promise.resolve(null); - } - return this.cache.get(this.prefix + MAIN_SCHEMA); - } - - setAllClasses(schema) { - if (!this.ttl) { - return Promise.resolve(null); - } - return this.cache.put(this.prefix + MAIN_SCHEMA, schema); - } - - getOneSchema(className) { - if (!this.ttl) { - return Promise.resolve(null); - } - return this.cache.get(this.prefix + MAIN_SCHEMA).then(cachedSchemas => { - cachedSchemas = cachedSchemas || []; - const schema = cachedSchemas.find(cachedSchema => { - return cachedSchema.className === className; - }); - if (schema) { - return Promise.resolve(schema); - } - return Promise.resolve(null); - }); - } - - clear() { - return this.cache.del(this.prefix + MAIN_SCHEMA); - } -} diff --git a/src/Controllers/index.js b/src/Controllers/index.js index 4683447516..7967b7f065 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -15,7 +15,6 @@ import { PushController } from './PushController'; import { PushQueue } from '../Push/PushQueue'; import { PushWorker } from '../Push/PushWorker'; import DatabaseController from './DatabaseController'; -import SchemaCache from './SchemaCache'; // Adapters import { GridFSBucketAdapter } from '../Adapters/Files/GridFSBucketAdapter'; From a3653fb229cc773a9a197fd595ed3b79ece4a89b Mon Sep 17 00:00:00 2001 From: SebC99 Date: Thu, 25 Jun 2020 16:05:11 +0200 Subject: [PATCH 3/6] attempt to improve flow type --- spec/SchemaCache.spec.js | 77 ------------------- spec/dev.js | 3 - .../Storage/Mongo/MongoStorageAdapter.js | 2 +- .../Postgres/PostgresStorageAdapter.js | 4 + src/Adapters/Storage/StorageAdapter.js | 1 + src/Options/Definitions.js | 7 -- src/Options/docs.js | 1 - src/Options/index.js | 3 - src/PromiseRouter.js | 10 --- 9 files changed, 6 insertions(+), 102 deletions(-) delete mode 100644 spec/SchemaCache.spec.js diff --git a/spec/SchemaCache.spec.js b/spec/SchemaCache.spec.js deleted file mode 100644 index 26897e03f7..0000000000 --- a/spec/SchemaCache.spec.js +++ /dev/null @@ -1,77 +0,0 @@ -const CacheController = require('../lib/Controllers/CacheController.js') - .default; -const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter') - .default; -const SchemaCache = require('../lib/Controllers/SchemaCache').default; - -describe('SchemaCache', () => { - let cacheController; - - beforeEach(() => { - const cacheAdapter = new InMemoryCacheAdapter({}); - cacheController = new CacheController(cacheAdapter, 'appId'); - }); - - it('can retrieve a single schema after all schemas stored', done => { - const schemaCache = new SchemaCache(cacheController); - const allSchemas = [ - { - className: 'Class1', - }, - { - className: 'Class2', - }, - ]; - schemaCache - .setAllClasses(allSchemas) - .then(() => { - return schemaCache.getOneSchema('Class2'); - }) - .then(schema => { - expect(schema).not.toBeNull(); - done(); - }); - }); - - it("doesn't persist cached data by default", done => { - const schemaCache = new SchemaCache(cacheController); - const schema = { - className: 'Class1', - }; - schemaCache.setAllClasses([schema]).then(() => { - const anotherSchemaCache = new SchemaCache(cacheController); - return anotherSchemaCache.getOneSchema(schema.className).then(schema => { - expect(schema).toBeNull(); - done(); - }); - }); - }); - - it('can persist cached data', done => { - const schemaCache = new SchemaCache(cacheController, 5000, true); - const schema = { - className: 'Class1', - }; - schemaCache.setAllClasses([schema]).then(() => { - const anotherSchemaCache = new SchemaCache(cacheController, 5000, true); - return anotherSchemaCache.getOneSchema(schema.className).then(schema => { - expect(schema).not.toBeNull(); - done(); - }); - }); - }); - - it('should not store if ttl is null', async () => { - const ttl = null; - const schemaCache = new SchemaCache(cacheController, ttl); - expect(await schemaCache.getAllClasses()).toBeNull(); - expect(await schemaCache.setAllClasses()).toBeNull(); - expect(await schemaCache.getOneSchema()).toBeNull(); - }); - - it('should convert string ttl to number', async () => { - const ttl = '5000'; - const schemaCache = new SchemaCache(cacheController, ttl); - expect(schemaCache.ttl).toBe(5000); - }); -}); diff --git a/spec/dev.js b/spec/dev.js index 31425dec40..2ef48884ff 100644 --- a/spec/dev.js +++ b/spec/dev.js @@ -4,12 +4,9 @@ const Parse = require('parse/node'); const className = 'AnObject'; const defaultRoleName = 'tester'; -let schemaCache; - module.exports = { /* AnObject */ className, - schemaCache, /** * Creates and returns new user. diff --git a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js index 432a34ae8d..43e7be502f 100644 --- a/src/Adapters/Storage/Mongo/MongoStorageAdapter.js +++ b/src/Adapters/Storage/Mongo/MongoStorageAdapter.js @@ -157,7 +157,7 @@ export class MongoStorageAdapter implements StorageAdapter { delete mongoOptions.maxTimeMS; } - watch(callback) { + watch(callback: () => void):void { this._onchange = callback; } diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 6b58120778..10e1b2b4d2 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -857,6 +857,10 @@ export class PostgresStorageAdapter implements StorageAdapter { this.canSortOnJoinTables = false; } + watch(callback: () => void):void { + // this._onchange = callback; + } + //Note that analyze=true will run the query, executing INSERTS, DELETES, etc. createExplainableQuery(query: string, analyze: boolean = false) { if (analyze) { diff --git a/src/Adapters/Storage/StorageAdapter.js b/src/Adapters/Storage/StorageAdapter.js index 0256841b23..4584374d6a 100644 --- a/src/Adapters/Storage/StorageAdapter.js +++ b/src/Adapters/Storage/StorageAdapter.js @@ -123,6 +123,7 @@ export interface StorageAdapter { explain?: boolean ): Promise; performInitialization(options: ?any): Promise; + watch(callback:() => void): void; // Indexing createIndexes(className: string, indexes: any, conn: ?any): Promise; diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 725002034c..0c28c3c008 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -141,13 +141,6 @@ module.exports.ParseServerOptions = { action: parsers.booleanParser, default: false, }, - enableSingleSchemaCache: { - env: 'PARSE_SERVER_ENABLE_SINGLE_SCHEMA_CACHE', - help: - 'Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA, defaults to false, i.e. unique schema cache per request.', - action: parsers.booleanParser, - default: false, - }, expireInactiveSessions: { env: 'PARSE_SERVER_EXPIRE_INACTIVE_SESSIONS', help: diff --git a/src/Options/docs.js b/src/Options/docs.js index 9e5b6ac8f8..12976eae13 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -25,7 +25,6 @@ * @property {Number} emailVerifyTokenValidityDuration Email verification token validity duration, in seconds * @property {Boolean} enableAnonymousUsers Enable (or disable) anon users, defaults to true * @property {Boolean} enableExpressErrorHandler Enables the default express error handler for all errors - * @property {Boolean} enableSingleSchemaCache Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA, defaults to false, i.e. unique schema cache per request. * @property {Boolean} expireInactiveSessions Sets wether we should expire the inactive sessions, defaults to true * @property {String} fileKey Key for your files * @property {Adapter} filesAdapter Adapter module for the files sub-system diff --git a/src/Options/index.js b/src/Options/index.js index 3aa5b7fc6b..f146571449 100644 --- a/src/Options/index.js +++ b/src/Options/index.js @@ -159,9 +159,6 @@ export interface ParseServerOptions { :ENV: PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS :DEFAULT: false */ directAccess: ?boolean; - /* Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA, defaults to false, i.e. unique schema cache per request. - :DEFAULT: false */ - enableSingleSchemaCache: ?boolean; /* Enables the default express error handler for all errors :DEFAULT: false */ enableExpressErrorHandler: ?boolean; diff --git a/src/PromiseRouter.js b/src/PromiseRouter.js index d67bf30d0a..5b15e88bdd 100644 --- a/src/PromiseRouter.js +++ b/src/PromiseRouter.js @@ -153,7 +153,6 @@ function makeExpressHandler(appId, promiseHandler) { promiseHandler(req) .then( result => { - clearSchemaCache(req); if (!result.response && !result.location && !result.text) { log.error( 'the handler did not include a "response" or a "location" field' @@ -188,17 +187,14 @@ function makeExpressHandler(appId, promiseHandler) { res.json(result.response); }, error => { - clearSchemaCache(req); next(error); } ) .catch(e => { - clearSchemaCache(req); log.error(`Error generating response. ${inspect(e)}`, { error: e }); next(e); }); } catch (e) { - clearSchemaCache(req); log.error(`Error handling request: ${inspect(e)}`, { error: e }); next(e); } @@ -216,9 +212,3 @@ function maskSensitiveUrl(req) { } return maskUrl; } - -function clearSchemaCache(req) { - if (req.config && !req.config.enableSingleSchemaCache) { - req.config.database.schemaCache.clear(); - } -} From f7b6d3e6f446f11d0df65b482c5bb881dd03cf08 Mon Sep 17 00:00:00 2001 From: dplewis Date: Fri, 17 Jul 2020 12:31:27 -0500 Subject: [PATCH 4/6] fix lint --- .../Postgres/PostgresStorageAdapter.js | 196 ++-- src/Config.js | 5 +- src/Options/Definitions.js | 1037 +++++++++-------- 3 files changed, 630 insertions(+), 608 deletions(-) diff --git a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js index 9d9623eae1..105f0e846c 100644 --- a/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js +++ b/src/Adapters/Storage/Postgres/PostgresStorageAdapter.js @@ -24,7 +24,7 @@ const debug = function (...args: any) { import { StorageAdapter } from '../StorageAdapter'; import type { SchemaType, QueryType, QueryOptions } from '../StorageAdapter'; -const parseTypeToPostgresType = (type) => { +const parseTypeToPostgresType = type => { switch (type.type) { case 'String': return 'text'; @@ -79,7 +79,7 @@ const mongoAggregateToPostgres = { $year: 'YEAR', }; -const toPostgresValue = (value) => { +const toPostgresValue = value => { if (typeof value === 'object') { if (value.__type === 'Date') { return value.iso; @@ -91,7 +91,7 @@ const toPostgresValue = (value) => { return value; }; -const transformValue = (value) => { +const transformValue = value => { if (typeof value === 'object' && value.__type === 'Pointer') { return value.objectId; } @@ -121,7 +121,7 @@ const defaultCLPS = Object.freeze({ protectedFields: { '*': [] }, }); -const toParseSchema = (schema) => { +const toParseSchema = schema => { if (schema.className === '_User') { delete schema.fields._hashed_password; } @@ -145,7 +145,7 @@ const toParseSchema = (schema) => { }; }; -const toPostgresSchema = (schema) => { +const toPostgresSchema = schema => { if (!schema) { return schema; } @@ -159,8 +159,8 @@ const toPostgresSchema = (schema) => { return schema; }; -const handleDotFields = (object) => { - Object.keys(object).forEach((fieldName) => { +const handleDotFields = object => { + Object.keys(object).forEach(fieldName => { if (fieldName.indexOf('.') > -1) { const components = fieldName.split('.'); const first = components.shift(); @@ -186,7 +186,7 @@ const handleDotFields = (object) => { return object; }; -const transformDotFieldToComponents = (fieldName) => { +const transformDotFieldToComponents = fieldName => { return fieldName.split('.').map((cmpt, index) => { if (index === 0) { return `"${cmpt}"`; @@ -195,7 +195,7 @@ const transformDotFieldToComponents = (fieldName) => { }); }; -const transformDotField = (fieldName) => { +const transformDotField = fieldName => { if (fieldName.indexOf('.') === -1) { return `"${fieldName}"`; } @@ -205,7 +205,7 @@ const transformDotField = (fieldName) => { return name; }; -const transformAggregateField = (fieldName) => { +const transformAggregateField = fieldName => { if (typeof fieldName !== 'string') { return fieldName; } @@ -218,7 +218,7 @@ const transformAggregateField = (fieldName) => { return fieldName.substr(1); }; -const validateKeys = (object) => { +const validateKeys = object => { if (typeof object == 'object') { for (const key in object) { if (typeof object[key] == 'object') { @@ -236,10 +236,10 @@ const validateKeys = (object) => { }; // Returns the list of join tables on a schema -const joinTablesForSchema = (schema) => { +const joinTablesForSchema = schema => { const list = []; if (schema) { - Object.keys(schema.fields).forEach((field) => { + Object.keys(schema.fields).forEach(field => { if (schema.fields[field].type === 'Relation') { list.push(`_Join:${field}:${schema.className}`); } @@ -343,7 +343,7 @@ const buildWhereClause = ({ } else if (['$or', '$nor', '$and'].includes(fieldName)) { const clauses = []; const clauseValues = []; - fieldValue.forEach((subQuery) => { + fieldValue.forEach(subQuery => { const clause = buildWhereClause({ schema, query: subQuery, @@ -490,13 +490,13 @@ const buildWhereClause = ({ }; if (fieldValue.$in) { createConstraint( - _.flatMap(fieldValue.$in, (elt) => elt), + _.flatMap(fieldValue.$in, elt => elt), false ); } if (fieldValue.$nin) { createConstraint( - _.flatMap(fieldValue.$nin, (elt) => elt), + _.flatMap(fieldValue.$nin, elt => elt), true ); } @@ -711,7 +711,7 @@ const buildWhereClause = ({ ); } points = points - .map((point) => { + .map(point => { if (point instanceof Array && point.length === 2) { Parse.GeoPoint._validate(point[1], point[0]); return `(${point[0]}, ${point[1]})`; @@ -799,7 +799,7 @@ const buildWhereClause = ({ index += 2; } - Object.keys(ParseToPosgresComparator).forEach((cmp) => { + Object.keys(ParseToPosgresComparator).forEach(cmp => { if (fieldValue[cmp] || fieldValue[cmp] === 0) { const pgComparator = ParseToPosgresComparator[cmp]; const postgresValue = toPostgresValue(fieldValue[cmp]); @@ -857,7 +857,7 @@ export class PostgresStorageAdapter implements StorageAdapter { this.canSortOnJoinTables = false; } - watch(callback: () => void):void { + watch(/* callback: () => void */): void { // this._onchange = callback; } @@ -883,7 +883,7 @@ export class PostgresStorageAdapter implements StorageAdapter { .none( 'CREATE TABLE IF NOT EXISTS "_SCHEMA" ( "className" varChar(120), "schema" jsonb, "isParseClass" bool, PRIMARY KEY ("className") )' ) - .catch((error) => { + .catch(error => { if ( error.code === PostgresDuplicateRelationError || error.code === PostgresUniqueIndexViolationError || @@ -900,13 +900,13 @@ export class PostgresStorageAdapter implements StorageAdapter { return this._client.one( 'SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = $1)', [name], - (a) => a.exists + a => a.exists ); } async setClassLevelPermissions(className: string, CLPs: any) { const self = this; - await this._client.task('set-class-level-permissions', async (t) => { + await this._client.task('set-class-level-permissions', async t => { await self._ensureSchemaCollectionExists(t); const values = [ className, @@ -938,7 +938,7 @@ export class PostgresStorageAdapter implements StorageAdapter { } const deletedIndexes = []; const insertedIndexes = []; - Object.keys(submittedIndexes).forEach((name) => { + Object.keys(submittedIndexes).forEach(name => { const field = submittedIndexes[name]; if (existingIndexes[name] && field.__op !== 'Delete') { throw new Parse.Error( @@ -956,7 +956,7 @@ export class PostgresStorageAdapter implements StorageAdapter { deletedIndexes.push(name); delete existingIndexes[name]; } else { - Object.keys(field).forEach((key) => { + Object.keys(field).forEach(key => { if (!Object.prototype.hasOwnProperty.call(fields, key)) { throw new Parse.Error( Parse.Error.INVALID_QUERY, @@ -971,7 +971,7 @@ export class PostgresStorageAdapter implements StorageAdapter { }); } }); - await conn.tx('set-indexes-with-schema-format', async (t) => { + await conn.tx('set-indexes-with-schema-format', async t => { if (insertedIndexes.length > 0) { await self.createIndexes(className, insertedIndexes, t); } @@ -989,7 +989,7 @@ export class PostgresStorageAdapter implements StorageAdapter { async createClass(className: string, schema: SchemaType, conn: ?any) { conn = conn || this._client; return conn - .tx('create-class', async (t) => { + .tx('create-class', async t => { const q1 = this.createTable(className, schema, t); const q2 = t.none( 'INSERT INTO "_SCHEMA" ("className", "schema", "isParseClass") VALUES ($, $, true)', @@ -1009,7 +1009,7 @@ export class PostgresStorageAdapter implements StorageAdapter { .then(() => { return toParseSchema(schema); }) - .catch((err) => { + .catch(err => { if (err.data[0].result.code === PostgresTransactionAbortedError) { err = err.data[1].result; } @@ -1046,7 +1046,7 @@ export class PostgresStorageAdapter implements StorageAdapter { } let index = 2; const relations = []; - Object.keys(fields).forEach((fieldName) => { + Object.keys(fields).forEach(fieldName => { const parseType = fields[fieldName]; // Skip when it's a relation // We'll create the tables later @@ -1069,7 +1069,7 @@ export class PostgresStorageAdapter implements StorageAdapter { const values = [className, ...valuesArray]; debug(qs, values); - return conn.task('create-table', async (t) => { + return conn.task('create-table', async t => { try { await self._ensureSchemaCollectionExists(t); await t.none(qs, values); @@ -1079,9 +1079,9 @@ export class PostgresStorageAdapter implements StorageAdapter { } // ELSE: Table already exists, must have been created by a different request. Ignore the error. } - await t.tx('create-table-tx', (tx) => { + await t.tx('create-table-tx', tx => { return tx.batch( - relations.map((fieldName) => { + relations.map(fieldName => { return tx.none( 'CREATE TABLE IF NOT EXISTS $ ("relatedId" varChar(120), "owningId" varChar(120), PRIMARY KEY("relatedId", "owningId") )', { joinTable: `_Join:${fieldName}:${className}` } @@ -1097,15 +1097,15 @@ export class PostgresStorageAdapter implements StorageAdapter { conn = conn || this._client; const self = this; - await conn.tx('schema-upgrade', async (t) => { + await conn.tx('schema-upgrade', async t => { const columns = await t.map( 'SELECT column_name FROM information_schema.columns WHERE table_name = $', { className }, - (a) => a.column_name + a => a.column_name ); const newColumns = Object.keys(schema.fields) - .filter((item) => columns.indexOf(item) === -1) - .map((fieldName) => + .filter(item => columns.indexOf(item) === -1) + .map(fieldName => self.addFieldIfNotExists( className, fieldName, @@ -1128,7 +1128,7 @@ export class PostgresStorageAdapter implements StorageAdapter { debug('addFieldIfNotExists', { className, fieldName, type }); conn = conn || this._client; const self = this; - await conn.tx('add-field-if-not-exists', async (t) => { + await conn.tx('add-field-if-not-exists', async t => { if (type.type !== 'Relation') { try { await t.none( @@ -1187,7 +1187,7 @@ export class PostgresStorageAdapter implements StorageAdapter { }, ]; return this._client - .tx((t) => t.none(this._pgp.helpers.concat(operations))) + .tx(t => t.none(this._pgp.helpers.concat(operations))) .then(() => className.indexOf('_Join:') != 0); // resolves with false when _Join table } @@ -1198,7 +1198,7 @@ export class PostgresStorageAdapter implements StorageAdapter { debug('deleteAllClasses'); await this._client - .task('delete-all-classes', async (t) => { + .task('delete-all-classes', async t => { try { const results = await t.any('SELECT * FROM "_SCHEMA"'); const joins = results.reduce((list: Array, schema: any) => { @@ -1214,14 +1214,14 @@ export class PostgresStorageAdapter implements StorageAdapter { '_GraphQLConfig', '_Audience', '_Idempotency', - ...results.map((result) => result.className), + ...results.map(result => result.className), ...joins, ]; - const queries = classes.map((className) => ({ + const queries = classes.map(className => ({ query: 'DROP TABLE IF EXISTS $', values: { className }, })); - await t.tx((tx) => tx.none(helpers.concat(queries))); + await t.tx(tx => tx.none(helpers.concat(queries))); } catch (error) { if (error.code !== PostgresRelationDoesNotExistError) { throw error; @@ -1269,7 +1269,7 @@ export class PostgresStorageAdapter implements StorageAdapter { }) .join(', DROP COLUMN'); - await this._client.tx('delete-fields', async (t) => { + await this._client.tx('delete-fields', async t => { await t.none( 'UPDATE "_SCHEMA" SET "schema" = $ WHERE "className" = $', { schema, className } @@ -1285,9 +1285,9 @@ export class PostgresStorageAdapter implements StorageAdapter { // rejection reason are TBD. async getAllClasses() { const self = this; - return this._client.task('get-all-classes', async (t) => { + return this._client.task('get-all-classes', async t => { await self._ensureSchemaCollectionExists(t); - return await t.map('SELECT * FROM "_SCHEMA"', null, (row) => + return await t.map('SELECT * FROM "_SCHEMA"', null, row => toParseSchema({ className: row.className, ...row.schema }) ); }); @@ -1302,7 +1302,7 @@ export class PostgresStorageAdapter implements StorageAdapter { .any('SELECT * FROM "_SCHEMA" WHERE "className" = $', { className, }) - .then((result) => { + .then(result => { if (result.length !== 1) { throw undefined; } @@ -1328,7 +1328,7 @@ export class PostgresStorageAdapter implements StorageAdapter { validateKeys(object); - Object.keys(object).forEach((fieldName) => { + Object.keys(object).forEach(fieldName => { if (object[fieldName] === null) { return; } @@ -1430,7 +1430,7 @@ export class PostgresStorageAdapter implements StorageAdapter { } return `$${index + 2 + columnsArray.length}${termination}`; }); - const geoPointsInjects = Object.keys(geoPoints).map((key) => { + const geoPointsInjects = Object.keys(geoPoints).map(key => { const value = geoPoints[key]; valuesArray.push(value.longitude, value.latitude); const l = valuesArray.length + columnsArray.length; @@ -1451,7 +1451,7 @@ export class PostgresStorageAdapter implements StorageAdapter { ) .none(qs, values) .then(() => ({ ops: [object] })) - .catch((error) => { + .catch(error => { if (error.code === PostgresUniqueIndexViolationError) { const err = new Parse.Error( Parse.Error.DUPLICATE_VALUE, @@ -1502,8 +1502,8 @@ export class PostgresStorageAdapter implements StorageAdapter { ? transactionalSession.t : this._client ) - .one(qs, values, (a) => +a.count) - .then((count) => { + .one(qs, values, a => +a.count) + .then(count => { if (count === 0) { throw new Parse.Error( Parse.Error.OBJECT_NOT_FOUND, @@ -1513,7 +1513,7 @@ export class PostgresStorageAdapter implements StorageAdapter { return count; } }) - .catch((error) => { + .catch(error => { if (error.code !== PostgresRelationDoesNotExistError) { throw error; } @@ -1539,7 +1539,7 @@ export class PostgresStorageAdapter implements StorageAdapter { query, update, transactionalSession - ).then((val) => val[0]); + ).then(val => val[0]); } // Apply the update to all objects that match the given Parse Query. @@ -1560,7 +1560,7 @@ export class PostgresStorageAdapter implements StorageAdapter { // Set flag for dot notation fields const dotNotationOptions = {}; - Object.keys(update).forEach((fieldName) => { + Object.keys(update).forEach(fieldName => { if (fieldName.indexOf('.') > -1) { const components = fieldName.split('.'); const first = components.shift(); @@ -1711,7 +1711,7 @@ export class PostgresStorageAdapter implements StorageAdapter { ) { // Gather keys to increment const keysToIncrement = Object.keys(originalUpdate) - .filter((k) => { + .filter(k => { // choose top level fields that have a delete operation set // Note that Object.keys is iterating over the **original** update object // and that some of the keys of the original update could be null or undefined: @@ -1724,26 +1724,26 @@ export class PostgresStorageAdapter implements StorageAdapter { k.split('.')[0] === fieldName ); }) - .map((k) => k.split('.')[1]); + .map(k => k.split('.')[1]); let incrementPatterns = ''; if (keysToIncrement.length > 0) { incrementPatterns = ' || ' + keysToIncrement - .map((c) => { + .map(c => { const amount = fieldValue[c].amount; return `CONCAT('{"${c}":', COALESCE($${index}:name->>'${c}','0')::int + ${amount}, '}')::jsonb`; }) .join(' || '); // Strip the keys - keysToIncrement.forEach((key) => { + keysToIncrement.forEach(key => { delete fieldValue[key]; }); } const keysToDelete: Array = Object.keys(originalUpdate) - .filter((k) => { + .filter(k => { // choose top level fields that have a delete operation set. const value = originalUpdate[k]; return ( @@ -1753,7 +1753,7 @@ export class PostgresStorageAdapter implements StorageAdapter { k.split('.')[0] === fieldName ); }) - .map((k) => k.split('.')[1]); + .map(k => k.split('.')[1]); const deletePatterns = keysToDelete.reduce( (p: string, c: string, i: number) => { @@ -1838,7 +1838,7 @@ export class PostgresStorageAdapter implements StorageAdapter { schema, createValue, transactionalSession - ).catch((error) => { + ).catch(error => { // ignore duplicate value errors as it's upsert if (error.code !== Parse.Error.DUPLICATE_VALUE) { throw error; @@ -1893,7 +1893,7 @@ export class PostgresStorageAdapter implements StorageAdapter { if (sort) { const sortCopy: any = sort; const sorting = Object.keys(sort) - .map((key) => { + .map(key => { const transformKey = transformDotFieldToComponents(key).join('->'); // Using $idx pattern gives: non-integer constant in ORDER BY if (sortCopy[key] === 1) { @@ -1942,18 +1942,18 @@ export class PostgresStorageAdapter implements StorageAdapter { debug(qs, values); return this._client .any(qs, values) - .catch((error) => { + .catch(error => { // Query on non existing table, don't crash if (error.code !== PostgresRelationDoesNotExistError) { throw error; } return []; }) - .then((results) => { + .then(results => { if (explain) { return results; } - return results.map((object) => + return results.map(object => this.postgresObjectToParseObject(className, object, schema) ); }); @@ -1962,7 +1962,7 @@ export class PostgresStorageAdapter implements StorageAdapter { // Converts from a postgres-format object to a REST-format object. // Does not strip out anything based on a lack of authentication. postgresObjectToParseObject(className: string, object: any, schema: any) { - Object.keys(schema.fields).forEach((fieldName) => { + Object.keys(schema.fields).forEach(fieldName => { if (schema.fields[fieldName].type === 'Pointer' && object[fieldName]) { object[fieldName] = { objectId: object[fieldName], @@ -1986,7 +1986,7 @@ export class PostgresStorageAdapter implements StorageAdapter { if (object[fieldName] && schema.fields[fieldName].type === 'Polygon') { let coords = object[fieldName]; coords = coords.substr(2, coords.length - 4).split('),('); - coords = coords.map((point) => { + coords = coords.map(point => { return [ parseFloat(point.split(',')[1]), parseFloat(point.split(',')[0]), @@ -2076,7 +2076,7 @@ export class PostgresStorageAdapter implements StorageAdapter { const qs = `ALTER TABLE $1:name ADD CONSTRAINT $2:name UNIQUE (${constraintPatterns.join()})`; return this._client .none(qs, [className, constraintName, ...fieldNames]) - .catch((error) => { + .catch(error => { if ( error.code === PostgresDuplicateRelationError && error.message.includes(constraintName) @@ -2127,14 +2127,14 @@ export class PostgresStorageAdapter implements StorageAdapter { } return this._client - .one(qs, values, (a) => { + .one(qs, values, a => { if (a.approximate_row_count != null) { return +a.approximate_row_count; } else { return +a.count; } }) - .catch((error) => { + .catch(error => { if (error.code !== PostgresRelationDoesNotExistError) { throw error; } @@ -2183,16 +2183,16 @@ export class PostgresStorageAdapter implements StorageAdapter { debug(qs, values); return this._client .any(qs, values) - .catch((error) => { + .catch(error => { if (error.code === PostgresMissingColumnError) { return []; } throw error; }) - .then((results) => { + .then(results => { if (!isNested) { - results = results.filter((object) => object[field] !== null); - return results.map((object) => { + results = results.filter(object => object[field] !== null); + return results.map(object => { if (!isPointerField) { return object[field]; } @@ -2204,10 +2204,10 @@ export class PostgresStorageAdapter implements StorageAdapter { }); } const child = fieldName.split('.')[1]; - return results.map((object) => object[column][child]); + return results.map(object => object[column][child]); }) - .then((results) => - results.map((object) => + .then(results => + results.map(object => this.postgresObjectToParseObject(className, object, schema) ) ); @@ -2344,7 +2344,7 @@ export class PostgresStorageAdapter implements StorageAdapter { if (stage.$match.$or) { const collapse = {}; - stage.$match.$or.forEach((element) => { + stage.$match.$or.forEach(element => { for (const key in element) { collapse[key] = element[key]; } @@ -2354,7 +2354,7 @@ export class PostgresStorageAdapter implements StorageAdapter { for (const field in stage.$match) { const value = stage.$match[field]; const matchPatterns = []; - Object.keys(ParseToPosgresComparator).forEach((cmp) => { + Object.keys(ParseToPosgresComparator).forEach(cmp => { if (value[cmp]) { const pgComparator = ParseToPosgresComparator[cmp]; matchPatterns.push( @@ -2394,7 +2394,7 @@ export class PostgresStorageAdapter implements StorageAdapter { const sort = stage.$sort; const keys = Object.keys(sort); const sorting = keys - .map((key) => { + .map(key => { const transformer = sort[key] === 1 ? 'ASC' : 'DESC'; const order = `$${index}:name ${transformer}`; index += 1; @@ -2422,14 +2422,14 @@ export class PostgresStorageAdapter implements StorageAdapter { ? this.createExplainableQuery(originalQuery) : originalQuery; debug(qs, values); - return this._client.any(qs, values).then((a) => { + return this._client.any(qs, values).then(a => { if (explain) { return a; } - const results = a.map((object) => + const results = a.map(object => this.postgresObjectToParseObject(className, object, schema) ); - results.forEach((result) => { + results.forEach(result => { if (!Object.prototype.hasOwnProperty.call(result, 'objectId')) { result.objectId = null; } @@ -2451,9 +2451,9 @@ export class PostgresStorageAdapter implements StorageAdapter { async performInitialization({ VolatileClassesSchemas }: any) { // TODO: This method needs to be rewritten to make proper use of connections (@vitaly-t) debug('performInitialization'); - const promises = VolatileClassesSchemas.map((schema) => { + const promises = VolatileClassesSchemas.map(schema => { return this.createTable(schema.className, schema) - .catch((err) => { + .catch(err => { if ( err.code === PostgresDuplicateRelationError || err.code === Parse.Error.INVALID_CLASS_NAME @@ -2466,7 +2466,7 @@ export class PostgresStorageAdapter implements StorageAdapter { }); return Promise.all(promises) .then(() => { - return this._client.tx('perform-initialization', (t) => { + return this._client.tx('perform-initialization', t => { return t.batch([ t.none(sql.misc.jsonObjectSetKeys), t.none(sql.array.add), @@ -2478,10 +2478,10 @@ export class PostgresStorageAdapter implements StorageAdapter { ]); }); }) - .then((data) => { + .then(data => { debug(`initializationDone in ${data.duration}`); }) - .catch((error) => { + .catch(error => { /* eslint-disable no-console */ console.error(error); }); @@ -2492,9 +2492,9 @@ export class PostgresStorageAdapter implements StorageAdapter { indexes: any, conn: ?any ): Promise { - return (conn || this._client).tx((t) => + return (conn || this._client).tx(t => t.batch( - indexes.map((i) => { + indexes.map(i => { return t.none('CREATE INDEX $1:name ON $2:name ($3:name)', [ i.name, className, @@ -2521,11 +2521,11 @@ export class PostgresStorageAdapter implements StorageAdapter { } async dropIndexes(className: string, indexes: any, conn: any): Promise { - const queries = indexes.map((i) => ({ + const queries = indexes.map(i => ({ query: 'DROP INDEX $1:name', values: i, })); - await (conn || this._client).tx((t) => + await (conn || this._client).tx(t => t.none(this._pgp.helpers.concat(queries)) ); } @@ -2545,11 +2545,11 @@ export class PostgresStorageAdapter implements StorageAdapter { } async createTransactionalSession(): Promise { - return new Promise((resolve) => { + return new Promise(resolve => { const transactionalSession = {}; - transactionalSession.result = this._client.tx((t) => { + transactionalSession.result = this._client.tx(t => { transactionalSession.t = t; - transactionalSession.promise = new Promise((resolve) => { + transactionalSession.promise = new Promise(resolve => { transactionalSession.resolve = resolve; }); transactionalSession.batch = []; @@ -2581,7 +2581,7 @@ export class PostgresStorageAdapter implements StorageAdapter { fieldNames: string[], indexName: ?string, caseInsensitive: boolean = false, - options?: Object = {}, + options?: Object = {} ): Promise { const conn = options.conn !== undefined ? options.conn : this._client; const defaultIndexName = `parse_default_${fieldNames.sort().join('_')}`; @@ -2595,7 +2595,7 @@ export class PostgresStorageAdapter implements StorageAdapter { const qs = `CREATE INDEX $1:name ON $2:name (${constraintPatterns.join()})`; await conn .none(qs, [indexNameOptions.name, className, ...fieldNames]) - .catch((error) => { + .catch(error => { if ( error.code === PostgresDuplicateRelationError && error.message.includes(indexNameOptions.name) @@ -2648,7 +2648,7 @@ function convertPolygonToSQL(polygon) { ); } const points = polygon - .map((point) => { + .map(point => { Parse.GeoPoint._validate(parseFloat(point[1]), parseFloat(point[0])); return `(${point[1]}, ${point[0]})`; }) @@ -2725,7 +2725,7 @@ function isAnyValueRegexStartsWith(values) { function createLiteralRegex(remaining) { return remaining .split('') - .map((c) => { + .map(c => { const regex = RegExp('[0-9 ]|\\p{L}', 'u'); // Support all unicode letter chars if (c.match(regex) !== null) { // don't escape alphanumeric characters diff --git a/src/Config.js b/src/Config.js index 945c9a73fd..1901e488a6 100644 --- a/src/Config.js +++ b/src/Config.js @@ -3,7 +3,6 @@ // mount is the URL for the root of the API; includes http, domain, etc. import AppCache from './cache'; -import DatabaseController from './Controllers/DatabaseController'; import net from 'net'; import { IdempotencyOptions } from './Options/Definitions'; @@ -105,7 +104,9 @@ export class Config { } static validateIdempotencyOptions(idempotencyOptions) { - if (!idempotencyOptions) { return; } + if (!idempotencyOptions) { + return; + } if (idempotencyOptions.ttl === undefined) { idempotencyOptions.ttl = IdempotencyOptions.ttl.default; } else if (!isNaN(idempotencyOptions.ttl) && idempotencyOptions.ttl <= 0) { diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index 3879198d68..51247f50aa 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -3,522 +3,543 @@ This code has been generated by resources/buildConfigDefinitions.js Do not edit manually, but update Options/index.js */ -var parsers = require("./parsers"); +var parsers = require('./parsers'); module.exports.ParseServerOptions = { - "accountLockout": { - "env": "PARSE_SERVER_ACCOUNT_LOCKOUT", - "help": "account lockout policy for failed login attempts", - "action": parsers.objectParser - }, - "allowClientClassCreation": { - "env": "PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION", - "help": "Enable (or disable) client class creation, defaults to true", - "action": parsers.booleanParser, - "default": true - }, - "allowCustomObjectId": { - "env": "PARSE_SERVER_ALLOW_CUSTOM_OBJECT_ID", - "help": "Enable (or disable) custom objectId", - "action": parsers.booleanParser, - "default": false - }, - "allowHeaders": { - "env": "PARSE_SERVER_ALLOW_HEADERS", - "help": "Add headers to Access-Control-Allow-Headers", - "action": parsers.arrayParser - }, - "allowOrigin": { - "env": "PARSE_SERVER_ALLOW_ORIGIN", - "help": "Sets the origin to Access-Control-Allow-Origin" - }, - "analyticsAdapter": { - "env": "PARSE_SERVER_ANALYTICS_ADAPTER", - "help": "Adapter module for the analytics", - "action": parsers.moduleOrObjectParser - }, - "appId": { - "env": "PARSE_SERVER_APPLICATION_ID", - "help": "Your Parse Application ID", - "required": true - }, - "appName": { - "env": "PARSE_SERVER_APP_NAME", - "help": "Sets the app name" - }, - "auth": { - "env": "PARSE_SERVER_AUTH_PROVIDERS", - "help": "Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication", - "action": parsers.objectParser - }, - "cacheAdapter": { - "env": "PARSE_SERVER_CACHE_ADAPTER", - "help": "Adapter module for the cache", - "action": parsers.moduleOrObjectParser - }, - "cacheMaxSize": { - "env": "PARSE_SERVER_CACHE_MAX_SIZE", - "help": "Sets the maximum size for the in memory cache, defaults to 10000", - "action": parsers.numberParser("cacheMaxSize"), - "default": 10000 - }, - "cacheTTL": { - "env": "PARSE_SERVER_CACHE_TTL", - "help": "Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds)", - "action": parsers.numberParser("cacheTTL"), - "default": 5000 - }, - "clientKey": { - "env": "PARSE_SERVER_CLIENT_KEY", - "help": "Key for iOS, MacOS, tvOS clients" - }, - "cloud": { - "env": "PARSE_SERVER_CLOUD", - "help": "Full path to your cloud code main.js" - }, - "cluster": { - "env": "PARSE_SERVER_CLUSTER", - "help": "Run with cluster, optionally set the number of processes default to os.cpus().length", - "action": parsers.numberOrBooleanParser - }, - "collectionPrefix": { - "env": "PARSE_SERVER_COLLECTION_PREFIX", - "help": "A collection prefix for the classes", - "default": "" - }, - "customPages": { - "env": "PARSE_SERVER_CUSTOM_PAGES", - "help": "custom pages for password validation and reset", - "action": parsers.objectParser, - "default": {} - }, - "databaseAdapter": { - "env": "PARSE_SERVER_DATABASE_ADAPTER", - "help": "Adapter module for the database", - "action": parsers.moduleOrObjectParser - }, - "databaseOptions": { - "env": "PARSE_SERVER_DATABASE_OPTIONS", - "help": "Options to pass to the mongodb client", - "action": parsers.objectParser - }, - "databaseURI": { - "env": "PARSE_SERVER_DATABASE_URI", - "help": "The full URI to your database. Supported databases are mongodb or postgres.", - "required": true, - "default": "mongodb://localhost:27017/parse" - }, - "directAccess": { - "env": "PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS", - "help": "Replace HTTP Interface when using JS SDK in current node runtime, defaults to false. Caution, this is an experimental feature that may not be appropriate for production.", - "action": parsers.booleanParser, - "default": false - }, - "dotNetKey": { - "env": "PARSE_SERVER_DOT_NET_KEY", - "help": "Key for Unity and .Net SDK" - }, - "emailAdapter": { - "env": "PARSE_SERVER_EMAIL_ADAPTER", - "help": "Adapter module for email sending", - "action": parsers.moduleOrObjectParser - }, - "emailVerifyTokenValidityDuration": { - "env": "PARSE_SERVER_EMAIL_VERIFY_TOKEN_VALIDITY_DURATION", - "help": "Email verification token validity duration, in seconds", - "action": parsers.numberParser("emailVerifyTokenValidityDuration") - }, - "enableAnonymousUsers": { - "env": "PARSE_SERVER_ENABLE_ANON_USERS", - "help": "Enable (or disable) anonymous users, defaults to true", - "action": parsers.booleanParser, - "default": true - }, - "enableExpressErrorHandler": { - "env": "PARSE_SERVER_ENABLE_EXPRESS_ERROR_HANDLER", - "help": "Enables the default express error handler for all errors", - "action": parsers.booleanParser, - "default": false - }, - "enableSingleSchemaCache": { - "env": "PARSE_SERVER_ENABLE_SINGLE_SCHEMA_CACHE", - "help": "Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA, defaults to false, i.e. unique schema cache per request.", - "action": parsers.booleanParser, - "default": false - }, - "expireInactiveSessions": { - "env": "PARSE_SERVER_EXPIRE_INACTIVE_SESSIONS", - "help": "Sets wether we should expire the inactive sessions, defaults to true", - "action": parsers.booleanParser, - "default": true - }, - "fileKey": { - "env": "PARSE_SERVER_FILE_KEY", - "help": "Key for your files" - }, - "filesAdapter": { - "env": "PARSE_SERVER_FILES_ADAPTER", - "help": "Adapter module for the files sub-system", - "action": parsers.moduleOrObjectParser - }, - "graphQLPath": { - "env": "PARSE_SERVER_GRAPHQL_PATH", - "help": "Mount path for the GraphQL endpoint, defaults to /graphql", - "default": "/graphql" - }, - "graphQLSchema": { - "env": "PARSE_SERVER_GRAPH_QLSCHEMA", - "help": "Full path to your GraphQL custom schema.graphql file" - }, - "host": { - "env": "PARSE_SERVER_HOST", - "help": "The host to serve ParseServer on, defaults to 0.0.0.0", - "default": "0.0.0.0" - }, - "idempotencyOptions": { - "env": "PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS", - "help": "Options for request idempotency to deduplicate identical requests that may be caused by network issues. Caution, this is an experimental feature that may not be appropriate for production.", - "action": parsers.objectParser, - "default": {} - }, - "javascriptKey": { - "env": "PARSE_SERVER_JAVASCRIPT_KEY", - "help": "Key for the Javascript SDK" - }, - "jsonLogs": { - "env": "JSON_LOGS", - "help": "Log as structured JSON objects", - "action": parsers.booleanParser - }, - "liveQuery": { - "env": "PARSE_SERVER_LIVE_QUERY", - "help": "parse-server's LiveQuery configuration object", - "action": parsers.objectParser - }, - "liveQueryServerOptions": { - "env": "PARSE_SERVER_LIVE_QUERY_SERVER_OPTIONS", - "help": "Live query server configuration options (will start the liveQuery server)", - "action": parsers.objectParser - }, - "loggerAdapter": { - "env": "PARSE_SERVER_LOGGER_ADAPTER", - "help": "Adapter module for the logging sub-system", - "action": parsers.moduleOrObjectParser - }, - "logLevel": { - "env": "PARSE_SERVER_LOG_LEVEL", - "help": "Sets the level for logs" - }, - "logsFolder": { - "env": "PARSE_SERVER_LOGS_FOLDER", - "help": "Folder for the logs (defaults to './logs'); set to null to disable file based logging", - "default": "./logs" - }, - "masterKey": { - "env": "PARSE_SERVER_MASTER_KEY", - "help": "Your Parse Master Key", - "required": true - }, - "masterKeyIps": { - "env": "PARSE_SERVER_MASTER_KEY_IPS", - "help": "Restrict masterKey to be used by only these ips, defaults to [] (allow all ips)", - "action": parsers.arrayParser, - "default": [] - }, - "maxLimit": { - "env": "PARSE_SERVER_MAX_LIMIT", - "help": "Max value for limit option on queries, defaults to unlimited", - "action": parsers.numberParser("maxLimit") - }, - "maxLogFiles": { - "env": "PARSE_SERVER_MAX_LOG_FILES", - "help": "Maximum number of logs to keep. If not set, no logs will be removed. This can be a number of files or number of days. If using days, add 'd' as the suffix. (default: null)", - "action": parsers.objectParser - }, - "maxUploadSize": { - "env": "PARSE_SERVER_MAX_UPLOAD_SIZE", - "help": "Max file size for uploads, defaults to 20mb", - "default": "20mb" - }, - "middleware": { - "env": "PARSE_SERVER_MIDDLEWARE", - "help": "middleware for express server, can be string or function" - }, - "mountGraphQL": { - "env": "PARSE_SERVER_MOUNT_GRAPHQL", - "help": "Mounts the GraphQL endpoint", - "action": parsers.booleanParser, - "default": false - }, - "mountPath": { - "env": "PARSE_SERVER_MOUNT_PATH", - "help": "Mount path for the server, defaults to /parse", - "default": "/parse" - }, - "mountPlayground": { - "env": "PARSE_SERVER_MOUNT_PLAYGROUND", - "help": "Mounts the GraphQL Playground - never use this option in production", - "action": parsers.booleanParser, - "default": false - }, - "objectIdSize": { - "env": "PARSE_SERVER_OBJECT_ID_SIZE", - "help": "Sets the number of characters in generated object id's, default 10", - "action": parsers.numberParser("objectIdSize"), - "default": 10 - }, - "passwordPolicy": { - "env": "PARSE_SERVER_PASSWORD_POLICY", - "help": "Password policy for enforcing password related rules", - "action": parsers.objectParser - }, - "playgroundPath": { - "env": "PARSE_SERVER_PLAYGROUND_PATH", - "help": "Mount path for the GraphQL Playground, defaults to /playground", - "default": "/playground" - }, - "port": { - "env": "PORT", - "help": "The port to run the ParseServer, defaults to 1337.", - "action": parsers.numberParser("port"), - "default": 1337 - }, - "preserveFileName": { - "env": "PARSE_SERVER_PRESERVE_FILE_NAME", - "help": "Enable (or disable) the addition of a unique hash to the file names", - "action": parsers.booleanParser, - "default": false - }, - "preventLoginWithUnverifiedEmail": { - "env": "PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL", - "help": "Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false", - "action": parsers.booleanParser, - "default": false - }, - "protectedFields": { - "env": "PARSE_SERVER_PROTECTED_FIELDS", - "help": "Protected fields that should be treated with extra security when fetching details.", - "action": parsers.objectParser, - "default": { - "_User": { - "*": ["email"] - } - } - }, - "publicServerURL": { - "env": "PARSE_PUBLIC_SERVER_URL", - "help": "Public URL to your parse server with http:// or https://." - }, - "push": { - "env": "PARSE_SERVER_PUSH", - "help": "Configuration for push, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#push-notifications", - "action": parsers.objectParser - }, - "readOnlyMasterKey": { - "env": "PARSE_SERVER_READ_ONLY_MASTER_KEY", - "help": "Read-only key, which has the same capabilities as MasterKey without writes" - }, - "restAPIKey": { - "env": "PARSE_SERVER_REST_API_KEY", - "help": "Key for REST calls" - }, - "revokeSessionOnPasswordReset": { - "env": "PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET", - "help": "When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.", - "action": parsers.booleanParser, - "default": true - }, - "scheduledPush": { - "env": "PARSE_SERVER_SCHEDULED_PUSH", - "help": "Configuration for push scheduling, defaults to false.", - "action": parsers.booleanParser, - "default": false - }, - "schemaCacheTTL": { - "env": "PARSE_SERVER_SCHEMA_CACHE_TTL", - "help": "The TTL for caching the schema for optimizing read/write operations. You should put a long TTL when your DB is in production. default to 5000; set 0 to disable.", - "action": parsers.numberParser("schemaCacheTTL"), - "default": 5000 - }, - "serverCloseComplete": { - "env": "PARSE_SERVER_SERVER_CLOSE_COMPLETE", - "help": "Callback when server has closed" - }, - "serverStartComplete": { - "env": "PARSE_SERVER_SERVER_START_COMPLETE", - "help": "Callback when server has started" - }, - "serverURL": { - "env": "PARSE_SERVER_URL", - "help": "URL to your parse server with http:// or https://.", - "required": true - }, - "sessionLength": { - "env": "PARSE_SERVER_SESSION_LENGTH", - "help": "Session duration, in seconds, defaults to 1 year", - "action": parsers.numberParser("sessionLength"), - "default": 31536000 - }, - "silent": { - "env": "SILENT", - "help": "Disables console output", - "action": parsers.booleanParser - }, - "startLiveQueryServer": { - "env": "PARSE_SERVER_START_LIVE_QUERY_SERVER", - "help": "Starts the liveQuery server", - "action": parsers.booleanParser - }, - "userSensitiveFields": { - "env": "PARSE_SERVER_USER_SENSITIVE_FIELDS", - "help": "Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields", - "action": parsers.arrayParser - }, - "verbose": { - "env": "VERBOSE", - "help": "Set the logging to verbose", - "action": parsers.booleanParser - }, - "verifyUserEmails": { - "env": "PARSE_SERVER_VERIFY_USER_EMAILS", - "help": "Enable (or disable) user email validation, defaults to false", - "action": parsers.booleanParser, - "default": false - }, - "webhookKey": { - "env": "PARSE_SERVER_WEBHOOK_KEY", - "help": "Key sent with outgoing webhook calls" - } + accountLockout: { + env: 'PARSE_SERVER_ACCOUNT_LOCKOUT', + help: 'account lockout policy for failed login attempts', + action: parsers.objectParser, + }, + allowClientClassCreation: { + env: 'PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION', + help: 'Enable (or disable) client class creation, defaults to true', + action: parsers.booleanParser, + default: true, + }, + allowCustomObjectId: { + env: 'PARSE_SERVER_ALLOW_CUSTOM_OBJECT_ID', + help: 'Enable (or disable) custom objectId', + action: parsers.booleanParser, + default: false, + }, + allowHeaders: { + env: 'PARSE_SERVER_ALLOW_HEADERS', + help: 'Add headers to Access-Control-Allow-Headers', + action: parsers.arrayParser, + }, + allowOrigin: { + env: 'PARSE_SERVER_ALLOW_ORIGIN', + help: 'Sets the origin to Access-Control-Allow-Origin', + }, + analyticsAdapter: { + env: 'PARSE_SERVER_ANALYTICS_ADAPTER', + help: 'Adapter module for the analytics', + action: parsers.moduleOrObjectParser, + }, + appId: { + env: 'PARSE_SERVER_APPLICATION_ID', + help: 'Your Parse Application ID', + required: true, + }, + appName: { + env: 'PARSE_SERVER_APP_NAME', + help: 'Sets the app name', + }, + auth: { + env: 'PARSE_SERVER_AUTH_PROVIDERS', + help: + 'Configuration for your authentication providers, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#oauth-and-3rd-party-authentication', + action: parsers.objectParser, + }, + cacheAdapter: { + env: 'PARSE_SERVER_CACHE_ADAPTER', + help: 'Adapter module for the cache', + action: parsers.moduleOrObjectParser, + }, + cacheMaxSize: { + env: 'PARSE_SERVER_CACHE_MAX_SIZE', + help: 'Sets the maximum size for the in memory cache, defaults to 10000', + action: parsers.numberParser('cacheMaxSize'), + default: 10000, + }, + cacheTTL: { + env: 'PARSE_SERVER_CACHE_TTL', + help: + 'Sets the TTL for the in memory cache (in ms), defaults to 5000 (5 seconds)', + action: parsers.numberParser('cacheTTL'), + default: 5000, + }, + clientKey: { + env: 'PARSE_SERVER_CLIENT_KEY', + help: 'Key for iOS, MacOS, tvOS clients', + }, + cloud: { + env: 'PARSE_SERVER_CLOUD', + help: 'Full path to your cloud code main.js', + }, + cluster: { + env: 'PARSE_SERVER_CLUSTER', + help: + 'Run with cluster, optionally set the number of processes default to os.cpus().length', + action: parsers.numberOrBooleanParser, + }, + collectionPrefix: { + env: 'PARSE_SERVER_COLLECTION_PREFIX', + help: 'A collection prefix for the classes', + default: '', + }, + customPages: { + env: 'PARSE_SERVER_CUSTOM_PAGES', + help: 'custom pages for password validation and reset', + action: parsers.objectParser, + default: {}, + }, + databaseAdapter: { + env: 'PARSE_SERVER_DATABASE_ADAPTER', + help: 'Adapter module for the database', + action: parsers.moduleOrObjectParser, + }, + databaseOptions: { + env: 'PARSE_SERVER_DATABASE_OPTIONS', + help: 'Options to pass to the mongodb client', + action: parsers.objectParser, + }, + databaseURI: { + env: 'PARSE_SERVER_DATABASE_URI', + help: + 'The full URI to your database. Supported databases are mongodb or postgres.', + required: true, + default: 'mongodb://localhost:27017/parse', + }, + directAccess: { + env: 'PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS', + help: + 'Replace HTTP Interface when using JS SDK in current node runtime, defaults to false. Caution, this is an experimental feature that may not be appropriate for production.', + action: parsers.booleanParser, + default: false, + }, + dotNetKey: { + env: 'PARSE_SERVER_DOT_NET_KEY', + help: 'Key for Unity and .Net SDK', + }, + emailAdapter: { + env: 'PARSE_SERVER_EMAIL_ADAPTER', + help: 'Adapter module for email sending', + action: parsers.moduleOrObjectParser, + }, + emailVerifyTokenValidityDuration: { + env: 'PARSE_SERVER_EMAIL_VERIFY_TOKEN_VALIDITY_DURATION', + help: 'Email verification token validity duration, in seconds', + action: parsers.numberParser('emailVerifyTokenValidityDuration'), + }, + enableAnonymousUsers: { + env: 'PARSE_SERVER_ENABLE_ANON_USERS', + help: 'Enable (or disable) anonymous users, defaults to true', + action: parsers.booleanParser, + default: true, + }, + enableExpressErrorHandler: { + env: 'PARSE_SERVER_ENABLE_EXPRESS_ERROR_HANDLER', + help: 'Enables the default express error handler for all errors', + action: parsers.booleanParser, + default: false, + }, + expireInactiveSessions: { + env: 'PARSE_SERVER_EXPIRE_INACTIVE_SESSIONS', + help: + 'Sets wether we should expire the inactive sessions, defaults to true', + action: parsers.booleanParser, + default: true, + }, + fileKey: { + env: 'PARSE_SERVER_FILE_KEY', + help: 'Key for your files', + }, + filesAdapter: { + env: 'PARSE_SERVER_FILES_ADAPTER', + help: 'Adapter module for the files sub-system', + action: parsers.moduleOrObjectParser, + }, + graphQLPath: { + env: 'PARSE_SERVER_GRAPHQL_PATH', + help: 'Mount path for the GraphQL endpoint, defaults to /graphql', + default: '/graphql', + }, + graphQLSchema: { + env: 'PARSE_SERVER_GRAPH_QLSCHEMA', + help: 'Full path to your GraphQL custom schema.graphql file', + }, + host: { + env: 'PARSE_SERVER_HOST', + help: 'The host to serve ParseServer on, defaults to 0.0.0.0', + default: '0.0.0.0', + }, + idempotencyOptions: { + env: 'PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS', + help: + 'Options for request idempotency to deduplicate identical requests that may be caused by network issues. Caution, this is an experimental feature that may not be appropriate for production.', + action: parsers.objectParser, + default: {}, + }, + javascriptKey: { + env: 'PARSE_SERVER_JAVASCRIPT_KEY', + help: 'Key for the Javascript SDK', + }, + jsonLogs: { + env: 'JSON_LOGS', + help: 'Log as structured JSON objects', + action: parsers.booleanParser, + }, + liveQuery: { + env: 'PARSE_SERVER_LIVE_QUERY', + help: "parse-server's LiveQuery configuration object", + action: parsers.objectParser, + }, + liveQueryServerOptions: { + env: 'PARSE_SERVER_LIVE_QUERY_SERVER_OPTIONS', + help: + 'Live query server configuration options (will start the liveQuery server)', + action: parsers.objectParser, + }, + loggerAdapter: { + env: 'PARSE_SERVER_LOGGER_ADAPTER', + help: 'Adapter module for the logging sub-system', + action: parsers.moduleOrObjectParser, + }, + logLevel: { + env: 'PARSE_SERVER_LOG_LEVEL', + help: 'Sets the level for logs', + }, + logsFolder: { + env: 'PARSE_SERVER_LOGS_FOLDER', + help: + "Folder for the logs (defaults to './logs'); set to null to disable file based logging", + default: './logs', + }, + masterKey: { + env: 'PARSE_SERVER_MASTER_KEY', + help: 'Your Parse Master Key', + required: true, + }, + masterKeyIps: { + env: 'PARSE_SERVER_MASTER_KEY_IPS', + help: + 'Restrict masterKey to be used by only these ips, defaults to [] (allow all ips)', + action: parsers.arrayParser, + default: [], + }, + maxLimit: { + env: 'PARSE_SERVER_MAX_LIMIT', + help: 'Max value for limit option on queries, defaults to unlimited', + action: parsers.numberParser('maxLimit'), + }, + maxLogFiles: { + env: 'PARSE_SERVER_MAX_LOG_FILES', + help: + "Maximum number of logs to keep. If not set, no logs will be removed. This can be a number of files or number of days. If using days, add 'd' as the suffix. (default: null)", + action: parsers.objectParser, + }, + maxUploadSize: { + env: 'PARSE_SERVER_MAX_UPLOAD_SIZE', + help: 'Max file size for uploads, defaults to 20mb', + default: '20mb', + }, + middleware: { + env: 'PARSE_SERVER_MIDDLEWARE', + help: 'middleware for express server, can be string or function', + }, + mountGraphQL: { + env: 'PARSE_SERVER_MOUNT_GRAPHQL', + help: 'Mounts the GraphQL endpoint', + action: parsers.booleanParser, + default: false, + }, + mountPath: { + env: 'PARSE_SERVER_MOUNT_PATH', + help: 'Mount path for the server, defaults to /parse', + default: '/parse', + }, + mountPlayground: { + env: 'PARSE_SERVER_MOUNT_PLAYGROUND', + help: 'Mounts the GraphQL Playground - never use this option in production', + action: parsers.booleanParser, + default: false, + }, + objectIdSize: { + env: 'PARSE_SERVER_OBJECT_ID_SIZE', + help: "Sets the number of characters in generated object id's, default 10", + action: parsers.numberParser('objectIdSize'), + default: 10, + }, + passwordPolicy: { + env: 'PARSE_SERVER_PASSWORD_POLICY', + help: 'Password policy for enforcing password related rules', + action: parsers.objectParser, + }, + playgroundPath: { + env: 'PARSE_SERVER_PLAYGROUND_PATH', + help: 'Mount path for the GraphQL Playground, defaults to /playground', + default: '/playground', + }, + port: { + env: 'PORT', + help: 'The port to run the ParseServer, defaults to 1337.', + action: parsers.numberParser('port'), + default: 1337, + }, + preserveFileName: { + env: 'PARSE_SERVER_PRESERVE_FILE_NAME', + help: 'Enable (or disable) the addition of a unique hash to the file names', + action: parsers.booleanParser, + default: false, + }, + preventLoginWithUnverifiedEmail: { + env: 'PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL', + help: + 'Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false', + action: parsers.booleanParser, + default: false, + }, + protectedFields: { + env: 'PARSE_SERVER_PROTECTED_FIELDS', + help: + 'Protected fields that should be treated with extra security when fetching details.', + action: parsers.objectParser, + default: { + _User: { + '*': ['email'], + }, + }, + }, + publicServerURL: { + env: 'PARSE_PUBLIC_SERVER_URL', + help: 'Public URL to your parse server with http:// or https://.', + }, + push: { + env: 'PARSE_SERVER_PUSH', + help: + 'Configuration for push, as stringified JSON. See http://docs.parseplatform.org/parse-server/guide/#push-notifications', + action: parsers.objectParser, + }, + readOnlyMasterKey: { + env: 'PARSE_SERVER_READ_ONLY_MASTER_KEY', + help: + 'Read-only key, which has the same capabilities as MasterKey without writes', + }, + restAPIKey: { + env: 'PARSE_SERVER_REST_API_KEY', + help: 'Key for REST calls', + }, + revokeSessionOnPasswordReset: { + env: 'PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET', + help: + "When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.", + action: parsers.booleanParser, + default: true, + }, + scheduledPush: { + env: 'PARSE_SERVER_SCHEDULED_PUSH', + help: 'Configuration for push scheduling, defaults to false.', + action: parsers.booleanParser, + default: false, + }, + schemaCacheTTL: { + env: 'PARSE_SERVER_SCHEMA_CACHE_TTL', + help: + 'The TTL for caching the schema for optimizing read/write operations. You should put a long TTL when your DB is in production. default to 5000; set 0 to disable.', + action: parsers.numberParser('schemaCacheTTL'), + default: 5000, + }, + serverCloseComplete: { + env: 'PARSE_SERVER_SERVER_CLOSE_COMPLETE', + help: 'Callback when server has closed', + }, + serverStartComplete: { + env: 'PARSE_SERVER_SERVER_START_COMPLETE', + help: 'Callback when server has started', + }, + serverURL: { + env: 'PARSE_SERVER_URL', + help: 'URL to your parse server with http:// or https://.', + required: true, + }, + sessionLength: { + env: 'PARSE_SERVER_SESSION_LENGTH', + help: 'Session duration, in seconds, defaults to 1 year', + action: parsers.numberParser('sessionLength'), + default: 31536000, + }, + silent: { + env: 'SILENT', + help: 'Disables console output', + action: parsers.booleanParser, + }, + startLiveQueryServer: { + env: 'PARSE_SERVER_START_LIVE_QUERY_SERVER', + help: 'Starts the liveQuery server', + action: parsers.booleanParser, + }, + userSensitiveFields: { + env: 'PARSE_SERVER_USER_SENSITIVE_FIELDS', + help: + 'Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields', + action: parsers.arrayParser, + }, + verbose: { + env: 'VERBOSE', + help: 'Set the logging to verbose', + action: parsers.booleanParser, + }, + verifyUserEmails: { + env: 'PARSE_SERVER_VERIFY_USER_EMAILS', + help: 'Enable (or disable) user email validation, defaults to false', + action: parsers.booleanParser, + default: false, + }, + webhookKey: { + env: 'PARSE_SERVER_WEBHOOK_KEY', + help: 'Key sent with outgoing webhook calls', + }, }; module.exports.CustomPagesOptions = { - "choosePassword": { - "env": "PARSE_SERVER_CUSTOM_PAGES_CHOOSE_PASSWORD", - "help": "choose password page path" - }, - "invalidLink": { - "env": "PARSE_SERVER_CUSTOM_PAGES_INVALID_LINK", - "help": "invalid link page path" - }, - "invalidVerificationLink": { - "env": "PARSE_SERVER_CUSTOM_PAGES_INVALID_VERIFICATION_LINK", - "help": "invalid verification link page path" - }, - "linkSendFail": { - "env": "PARSE_SERVER_CUSTOM_PAGES_LINK_SEND_FAIL", - "help": "verification link send fail page path" - }, - "linkSendSuccess": { - "env": "PARSE_SERVER_CUSTOM_PAGES_LINK_SEND_SUCCESS", - "help": "verification link send success page path" - }, - "parseFrameURL": { - "env": "PARSE_SERVER_CUSTOM_PAGES_PARSE_FRAME_URL", - "help": "for masking user-facing pages" - }, - "passwordResetSuccess": { - "env": "PARSE_SERVER_CUSTOM_PAGES_PASSWORD_RESET_SUCCESS", - "help": "password reset success page path" - }, - "verifyEmailSuccess": { - "env": "PARSE_SERVER_CUSTOM_PAGES_VERIFY_EMAIL_SUCCESS", - "help": "verify email success page path" - } + choosePassword: { + env: 'PARSE_SERVER_CUSTOM_PAGES_CHOOSE_PASSWORD', + help: 'choose password page path', + }, + invalidLink: { + env: 'PARSE_SERVER_CUSTOM_PAGES_INVALID_LINK', + help: 'invalid link page path', + }, + invalidVerificationLink: { + env: 'PARSE_SERVER_CUSTOM_PAGES_INVALID_VERIFICATION_LINK', + help: 'invalid verification link page path', + }, + linkSendFail: { + env: 'PARSE_SERVER_CUSTOM_PAGES_LINK_SEND_FAIL', + help: 'verification link send fail page path', + }, + linkSendSuccess: { + env: 'PARSE_SERVER_CUSTOM_PAGES_LINK_SEND_SUCCESS', + help: 'verification link send success page path', + }, + parseFrameURL: { + env: 'PARSE_SERVER_CUSTOM_PAGES_PARSE_FRAME_URL', + help: 'for masking user-facing pages', + }, + passwordResetSuccess: { + env: 'PARSE_SERVER_CUSTOM_PAGES_PASSWORD_RESET_SUCCESS', + help: 'password reset success page path', + }, + verifyEmailSuccess: { + env: 'PARSE_SERVER_CUSTOM_PAGES_VERIFY_EMAIL_SUCCESS', + help: 'verify email success page path', + }, }; module.exports.LiveQueryOptions = { - "classNames": { - "env": "PARSE_SERVER_LIVEQUERY_CLASSNAMES", - "help": "parse-server's LiveQuery classNames", - "action": parsers.arrayParser - }, - "pubSubAdapter": { - "env": "PARSE_SERVER_LIVEQUERY_PUB_SUB_ADAPTER", - "help": "LiveQuery pubsub adapter", - "action": parsers.moduleOrObjectParser - }, - "redisOptions": { - "env": "PARSE_SERVER_LIVEQUERY_REDIS_OPTIONS", - "help": "parse-server's LiveQuery redisOptions", - "action": parsers.objectParser - }, - "redisURL": { - "env": "PARSE_SERVER_LIVEQUERY_REDIS_URL", - "help": "parse-server's LiveQuery redisURL" - }, - "wssAdapter": { - "env": "PARSE_SERVER_LIVEQUERY_WSS_ADAPTER", - "help": "Adapter module for the WebSocketServer", - "action": parsers.moduleOrObjectParser - } + classNames: { + env: 'PARSE_SERVER_LIVEQUERY_CLASSNAMES', + help: "parse-server's LiveQuery classNames", + action: parsers.arrayParser, + }, + pubSubAdapter: { + env: 'PARSE_SERVER_LIVEQUERY_PUB_SUB_ADAPTER', + help: 'LiveQuery pubsub adapter', + action: parsers.moduleOrObjectParser, + }, + redisOptions: { + env: 'PARSE_SERVER_LIVEQUERY_REDIS_OPTIONS', + help: "parse-server's LiveQuery redisOptions", + action: parsers.objectParser, + }, + redisURL: { + env: 'PARSE_SERVER_LIVEQUERY_REDIS_URL', + help: "parse-server's LiveQuery redisURL", + }, + wssAdapter: { + env: 'PARSE_SERVER_LIVEQUERY_WSS_ADAPTER', + help: 'Adapter module for the WebSocketServer', + action: parsers.moduleOrObjectParser, + }, }; module.exports.LiveQueryServerOptions = { - "appId": { - "env": "PARSE_LIVE_QUERY_SERVER_APP_ID", - "help": "This string should match the appId in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same appId." - }, - "cacheTimeout": { - "env": "PARSE_LIVE_QUERY_SERVER_CACHE_TIMEOUT", - "help": "Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details, defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).", - "action": parsers.numberParser("cacheTimeout") - }, - "keyPairs": { - "env": "PARSE_LIVE_QUERY_SERVER_KEY_PAIRS", - "help": "A JSON object that serves as a whitelist of keys. It is used for validating clients when they try to connect to the LiveQuery server. Check the following Security section and our protocol specification for details.", - "action": parsers.objectParser - }, - "logLevel": { - "env": "PARSE_LIVE_QUERY_SERVER_LOG_LEVEL", - "help": "This string defines the log level of the LiveQuery server. We support VERBOSE, INFO, ERROR, NONE, defaults to INFO." - }, - "masterKey": { - "env": "PARSE_LIVE_QUERY_SERVER_MASTER_KEY", - "help": "This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey." - }, - "port": { - "env": "PARSE_LIVE_QUERY_SERVER_PORT", - "help": "The port to run the LiveQuery server, defaults to 1337.", - "action": parsers.numberParser("port"), - "default": 1337 - }, - "pubSubAdapter": { - "env": "PARSE_LIVE_QUERY_SERVER_PUB_SUB_ADAPTER", - "help": "LiveQuery pubsub adapter", - "action": parsers.moduleOrObjectParser - }, - "redisOptions": { - "env": "PARSE_LIVE_QUERY_SERVER_REDIS_OPTIONS", - "help": "parse-server's LiveQuery redisOptions", - "action": parsers.objectParser - }, - "redisURL": { - "env": "PARSE_LIVE_QUERY_SERVER_REDIS_URL", - "help": "parse-server's LiveQuery redisURL" - }, - "serverURL": { - "env": "PARSE_LIVE_QUERY_SERVER_SERVER_URL", - "help": "This string should match the serverURL in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same serverURL." - }, - "websocketTimeout": { - "env": "PARSE_LIVE_QUERY_SERVER_WEBSOCKET_TIMEOUT", - "help": "Number of milliseconds between ping/pong frames. The WebSocket server sends ping/pong frames to the clients to keep the WebSocket alive. This value defines the interval of the ping/pong frame from the server to clients, defaults to 10 * 1000 ms (10 s).", - "action": parsers.numberParser("websocketTimeout") - }, - "wssAdapter": { - "env": "PARSE_LIVE_QUERY_SERVER_WSS_ADAPTER", - "help": "Adapter module for the WebSocketServer", - "action": parsers.moduleOrObjectParser - } + appId: { + env: 'PARSE_LIVE_QUERY_SERVER_APP_ID', + help: + 'This string should match the appId in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same appId.', + }, + cacheTimeout: { + env: 'PARSE_LIVE_QUERY_SERVER_CACHE_TIMEOUT', + help: + "Number in milliseconds. When clients provide the sessionToken to the LiveQuery server, the LiveQuery server will try to fetch its ParseUser's objectId from parse server and store it in the cache. The value defines the duration of the cache. Check the following Security section and our protocol specification for details, defaults to 30 * 24 * 60 * 60 * 1000 ms (~30 days).", + action: parsers.numberParser('cacheTimeout'), + }, + keyPairs: { + env: 'PARSE_LIVE_QUERY_SERVER_KEY_PAIRS', + help: + 'A JSON object that serves as a whitelist of keys. It is used for validating clients when they try to connect to the LiveQuery server. Check the following Security section and our protocol specification for details.', + action: parsers.objectParser, + }, + logLevel: { + env: 'PARSE_LIVE_QUERY_SERVER_LOG_LEVEL', + help: + 'This string defines the log level of the LiveQuery server. We support VERBOSE, INFO, ERROR, NONE, defaults to INFO.', + }, + masterKey: { + env: 'PARSE_LIVE_QUERY_SERVER_MASTER_KEY', + help: + 'This string should match the masterKey in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same masterKey.', + }, + port: { + env: 'PARSE_LIVE_QUERY_SERVER_PORT', + help: 'The port to run the LiveQuery server, defaults to 1337.', + action: parsers.numberParser('port'), + default: 1337, + }, + pubSubAdapter: { + env: 'PARSE_LIVE_QUERY_SERVER_PUB_SUB_ADAPTER', + help: 'LiveQuery pubsub adapter', + action: parsers.moduleOrObjectParser, + }, + redisOptions: { + env: 'PARSE_LIVE_QUERY_SERVER_REDIS_OPTIONS', + help: "parse-server's LiveQuery redisOptions", + action: parsers.objectParser, + }, + redisURL: { + env: 'PARSE_LIVE_QUERY_SERVER_REDIS_URL', + help: "parse-server's LiveQuery redisURL", + }, + serverURL: { + env: 'PARSE_LIVE_QUERY_SERVER_SERVER_URL', + help: + 'This string should match the serverURL in use by your Parse Server. If you deploy the LiveQuery server alongside Parse Server, the LiveQuery server will try to use the same serverURL.', + }, + websocketTimeout: { + env: 'PARSE_LIVE_QUERY_SERVER_WEBSOCKET_TIMEOUT', + help: + 'Number of milliseconds between ping/pong frames. The WebSocket server sends ping/pong frames to the clients to keep the WebSocket alive. This value defines the interval of the ping/pong frame from the server to clients, defaults to 10 * 1000 ms (10 s).', + action: parsers.numberParser('websocketTimeout'), + }, + wssAdapter: { + env: 'PARSE_LIVE_QUERY_SERVER_WSS_ADAPTER', + help: 'Adapter module for the WebSocketServer', + action: parsers.moduleOrObjectParser, + }, }; module.exports.IdempotencyOptions = { - "paths": { - "env": "PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_PATHS", - "help": "An array of paths for which the feature should be enabled. The mount path must not be included, for example instead of `/parse/functions/myFunction` specifiy `functions/myFunction`. The entries are interpreted as regular expression, for example `functions/.*` matches all functions, `jobs/.*` matches all jobs, `classes/.*` matches all classes, `.*` matches all paths.", - "action": parsers.arrayParser, - "default": [] - }, - "ttl": { - "env": "PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_TTL", - "help": "The duration in seconds after which a request record is discarded from the database, defaults to 300s.", - "action": parsers.numberParser("ttl"), - "default": 300 - } + paths: { + env: 'PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_PATHS', + help: + 'An array of paths for which the feature should be enabled. The mount path must not be included, for example instead of `/parse/functions/myFunction` specifiy `functions/myFunction`. The entries are interpreted as regular expression, for example `functions/.*` matches all functions, `jobs/.*` matches all jobs, `classes/.*` matches all classes, `.*` matches all paths.', + action: parsers.arrayParser, + default: [], + }, + ttl: { + env: 'PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_TTL', + help: + 'The duration in seconds after which a request record is discarded from the database, defaults to 300s.', + action: parsers.numberParser('ttl'), + default: 300, + }, }; From a97ff4f7cda1a027361de79cfa7a4ef23c75d5d4 Mon Sep 17 00:00:00 2001 From: dplewis Date: Fri, 17 Jul 2020 16:29:25 -0500 Subject: [PATCH 5/6] remove unused tests --- spec/RedisCacheAdapter.spec.js | 394 ++------------------------------- 1 file changed, 20 insertions(+), 374 deletions(-) diff --git a/spec/RedisCacheAdapter.spec.js b/spec/RedisCacheAdapter.spec.js index d4ff0327ab..09b3506099 100644 --- a/spec/RedisCacheAdapter.spec.js +++ b/spec/RedisCacheAdapter.spec.js @@ -1,6 +1,5 @@ const RedisCacheAdapter = require('../lib/Adapters/Cache/RedisCacheAdapter') .default; -const Config = require('../lib/Config'); /* To run this test part of the complete suite @@ -19,7 +18,7 @@ describe_only(() => { }); } - it('should get/set/clear', (done) => { + it('should get/set/clear', done => { const cache = new RedisCacheAdapter({ ttl: NaN, }); @@ -27,78 +26,78 @@ describe_only(() => { cache .put(KEY, VALUE) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(VALUE)) + .then(value => expect(value).toEqual(VALUE)) .then(() => cache.clear()) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(null)) + .then(value => expect(value).toEqual(null)) .then(done); }); - it('should expire after ttl', (done) => { + it('should expire after ttl', done => { const cache = new RedisCacheAdapter(null, 50); cache .put(KEY, VALUE) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(VALUE)) + .then(value => expect(value).toEqual(VALUE)) .then(wait.bind(null, 52)) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(null)) + .then(value => expect(value).toEqual(null)) .then(done); }); - it('should not store value for ttl=0', (done) => { + it('should not store value for ttl=0', done => { const cache = new RedisCacheAdapter(null, 5); cache .put(KEY, VALUE, 0) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(null)) + .then(value => expect(value).toEqual(null)) .then(done); }); - it('should not expire when ttl=Infinity', (done) => { + it('should not expire when ttl=Infinity', done => { const cache = new RedisCacheAdapter(null, 1); cache .put(KEY, VALUE, Infinity) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(VALUE)) + .then(value => expect(value).toEqual(VALUE)) .then(wait.bind(null, 5)) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(VALUE)) + .then(value => expect(value).toEqual(VALUE)) .then(done); }); - it('should fallback to default ttl', (done) => { + it('should fallback to default ttl', done => { const cache = new RedisCacheAdapter(null, 1); let promise = Promise.resolve(); - [-100, null, undefined, 'not number', true].forEach((ttl) => { + [-100, null, undefined, 'not number', true].forEach(ttl => { promise = promise.then(() => cache .put(KEY, VALUE, ttl) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(VALUE)) + .then(value => expect(value).toEqual(VALUE)) .then(wait.bind(null, 5)) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(null)) + .then(value => expect(value).toEqual(null)) ); }); promise.then(done); }); - it('should find un-expired records', (done) => { + it('should find un-expired records', done => { const cache = new RedisCacheAdapter(null, 5); cache .put(KEY, VALUE) .then(() => cache.get(KEY)) - .then((value) => expect(value).toEqual(VALUE)) + .then(value => expect(value).toEqual(VALUE)) .then(wait.bind(null, 1)) .then(() => cache.get(KEY)) - .then((value) => expect(value).not.toEqual(null)) + .then(value => expect(value).not.toEqual(null)) .then(done); }); @@ -129,7 +128,7 @@ describe_only(() => { return Object.keys(cache.queue.queue).length; } - it('it should clear completed operations from queue', (done) => { + it('it should clear completed operations from queue', done => { const cache = new RedisCacheAdapter({ ttl: NaN }); // execute a bunch of operations in sequence @@ -151,7 +150,7 @@ describe_only(() => { promise.then(() => expect(getQueueCount(cache)).toEqual(0)).then(done); }); - it('it should count per key chained operations correctly', (done) => { + it('it should count per key chained operations correctly', done => { const cache = new RedisCacheAdapter({ ttl: NaN }); let key1Promise = Promise.resolve(); @@ -174,356 +173,3 @@ describe_only(() => { .then(done); }); }); - -describe_only(() => { - return process.env.PARSE_SERVER_TEST_CACHE === 'redis'; -})('Redis Performance', function () { - let cacheAdapter; - let getSpy; - let putSpy; - let delSpy; - - beforeEach(async () => { - cacheAdapter = new RedisCacheAdapter(); - await reconfigureServer({ - cacheAdapter, - }); - await cacheAdapter.clear(); - - getSpy = spyOn(cacheAdapter, 'get').and.callThrough(); - putSpy = spyOn(cacheAdapter, 'put').and.callThrough(); - delSpy = spyOn(cacheAdapter, 'del').and.callThrough(); - }); - - it('test new object', async () => { - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - expect(getSpy.calls.count()).toBe(3); - expect(putSpy.calls.count()).toBe(3); - expect(delSpy.calls.count()).toBe(1); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test new object multiple fields', async () => { - const container = new Container({ - dateField: new Date(), - arrayField: [], - numberField: 1, - stringField: 'hello', - booleanField: true, - }); - await container.save(); - expect(getSpy.calls.count()).toBe(3); - expect(putSpy.calls.count()).toBe(3); - expect(delSpy.calls.count()).toBe(1); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test update existing fields', async () => { - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - object.set('foo', 'barz'); - await object.save(); - expect(getSpy.calls.count()).toBe(3); - expect(putSpy.calls.count()).toBe(1); - expect(delSpy.calls.count()).toBe(2); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test saveAll / destroyAll', async () => { - const object = new TestObject(); - await object.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - const objects = []; - for (let i = 0; i < 10; i++) { - const object = new TestObject(); - object.set('number', i); - objects.push(object); - } - await Parse.Object.saveAll(objects); - expect(getSpy.calls.count()).toBe(21); - expect(putSpy.calls.count()).toBe(11); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - await Parse.Object.destroyAll(objects); - expect(getSpy.calls.count()).toBe(11); - expect(putSpy.calls.count()).toBe(1); - expect(delSpy.calls.count()).toBe(3); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test saveAll / destroyAll batch', async () => { - const object = new TestObject(); - await object.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - const objects = []; - for (let i = 0; i < 10; i++) { - const object = new TestObject(); - object.set('number', i); - objects.push(object); - } - await Parse.Object.saveAll(objects, { batchSize: 5 }); - expect(getSpy.calls.count()).toBe(22); - expect(putSpy.calls.count()).toBe(7); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - await Parse.Object.destroyAll(objects, { batchSize: 5 }); - expect(getSpy.calls.count()).toBe(12); - expect(putSpy.calls.count()).toBe(2); - expect(delSpy.calls.count()).toBe(5); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test add new field to existing object', async () => { - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - object.set('new', 'barz'); - await object.save(); - expect(getSpy.calls.count()).toBe(3); - expect(putSpy.calls.count()).toBe(2); - expect(delSpy.calls.count()).toBe(2); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test add multiple fields to existing object', async () => { - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - object.set({ - dateField: new Date(), - arrayField: [], - numberField: 1, - stringField: 'hello', - booleanField: true, - }); - await object.save(); - expect(getSpy.calls.count()).toBe(3); - expect(putSpy.calls.count()).toBe(2); - expect(delSpy.calls.count()).toBe(2); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test user', async () => { - const user = new Parse.User(); - user.setUsername('testing'); - user.setPassword('testing'); - await user.signUp(); - - expect(getSpy.calls.count()).toBe(8); - expect(putSpy.calls.count()).toBe(2); - expect(delSpy.calls.count()).toBe(1); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test allowClientCreation false', async () => { - const object = new TestObject(); - await object.save(); - await reconfigureServer({ - cacheAdapter, - allowClientClassCreation: false, - }); - await cacheAdapter.clear(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - delSpy.calls.reset(); - - object.set('foo', 'bar'); - await object.save(); - expect(getSpy.calls.count()).toBe(4); - expect(putSpy.calls.count()).toBe(2); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - const query = new Parse.Query(TestObject); - await query.get(object.id); - expect(getSpy.calls.count()).toBe(3); - expect(putSpy.calls.count()).toBe(1); - expect(delSpy.calls.count()).toBe(2); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test query', async () => { - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - delSpy.calls.reset(); - - const query = new Parse.Query(TestObject); - await query.get(object.id); - expect(getSpy.calls.count()).toBe(2); - expect(putSpy.calls.count()).toBe(1); - expect(delSpy.calls.count()).toBe(1); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test query include', async () => { - const child = new TestObject(); - await child.save(); - - const object = new TestObject(); - object.set('child', child); - await object.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - const query = new Parse.Query(TestObject); - query.include('child'); - await query.get(object.id); - - expect(getSpy.calls.count()).toBe(4); - expect(putSpy.calls.count()).toBe(1); - expect(delSpy.calls.count()).toBe(3); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('query relation without schema', async () => { - const child = new Parse.Object('ChildObject'); - await child.save(); - - const parent = new Parse.Object('ParentObject'); - const relation = parent.relation('child'); - relation.add(child); - await parent.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - - const objects = await relation.query().find(); - expect(objects.length).toBe(1); - expect(objects[0].id).toBe(child.id); - - expect(getSpy.calls.count()).toBe(2); - expect(putSpy.calls.count()).toBe(1); - expect(delSpy.calls.count()).toBe(3); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test delete object', async () => { - const object = new TestObject(); - object.set('foo', 'bar'); - await object.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - delSpy.calls.reset(); - - await object.destroy(); - expect(getSpy.calls.count()).toBe(2); - expect(putSpy.calls.count()).toBe(1); - expect(delSpy.calls.count()).toBe(1); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(0); - }); - - it('test schema update class', async () => { - const container = new Container(); - await container.save(); - - getSpy.calls.reset(); - putSpy.calls.reset(); - delSpy.calls.reset(); - - const config = Config.get('test'); - const schema = await config.database.loadSchema(); - await schema.reloadData(); - - const levelPermissions = { - find: { '*': true }, - get: { '*': true }, - create: { '*': true }, - update: { '*': true }, - delete: { '*': true }, - addField: { '*': true }, - protectedFields: { '*': [] }, - }; - - await schema.updateClass( - 'Container', - { - fooOne: { type: 'Number' }, - fooTwo: { type: 'Array' }, - fooThree: { type: 'Date' }, - fooFour: { type: 'Object' }, - fooFive: { type: 'Relation', targetClass: '_User' }, - fooSix: { type: 'String' }, - fooSeven: { type: 'Object' }, - fooEight: { type: 'String' }, - fooNine: { type: 'String' }, - fooTeen: { type: 'Number' }, - fooEleven: { type: 'String' }, - fooTwelve: { type: 'String' }, - fooThirteen: { type: 'String' }, - fooFourteen: { type: 'String' }, - fooFifteen: { type: 'String' }, - fooSixteen: { type: 'String' }, - fooEighteen: { type: 'String' }, - fooNineteen: { type: 'String' }, - }, - levelPermissions, - {}, - config.database - ); - expect(getSpy.calls.count()).toBe(3); - expect(putSpy.calls.count()).toBe(3); - expect(delSpy.calls.count()).toBe(0); - - const keys = await cacheAdapter.getAllKeys(); - expect(keys.length).toBe(1); - }); -}); From 873168ff5517c85a3b505c0e30de58881ccd02c1 Mon Sep 17 00:00:00 2001 From: Diamond Lewis Date: Fri, 13 Nov 2020 09:44:01 -0600 Subject: [PATCH 6/6] Resolve Conflicts --- src/Options/Definitions.js | 7 ------- src/Options/docs.js | 1 - 2 files changed, 8 deletions(-) diff --git a/src/Options/Definitions.js b/src/Options/Definitions.js index c3c1271786..3072b08bfe 100644 --- a/src/Options/Definitions.js +++ b/src/Options/Definitions.js @@ -142,13 +142,6 @@ module.exports.ParseServerOptions = { action: parsers.booleanParser, default: false, }, - enableSingleSchemaCache: { - env: 'PARSE_SERVER_ENABLE_SINGLE_SCHEMA_CACHE', - help: - 'Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA, defaults to false, i.e. unique schema cache per request.', - action: parsers.booleanParser, - default: false, - }, encryptionKey: { env: 'PARSE_SERVER_ENCRYPTION_KEY', help: 'Key for encrypting your files', diff --git a/src/Options/docs.js b/src/Options/docs.js index 9d8553d9f6..3e4573c1f1 100644 --- a/src/Options/docs.js +++ b/src/Options/docs.js @@ -26,7 +26,6 @@ * @property {Number} emailVerifyTokenValidityDuration Email verification token validity duration, in seconds * @property {Boolean} enableAnonymousUsers Enable (or disable) anonymous users, defaults to true * @property {Boolean} enableExpressErrorHandler Enables the default express error handler for all errors - * @property {Boolean} enableSingleSchemaCache Use a single schema cache shared across requests. Reduces number of queries made to _SCHEMA, defaults to false, i.e. unique schema cache per request. * @property {String} encryptionKey Key for encrypting your files * @property {Boolean} expireInactiveSessions Sets wether we should expire the inactive sessions, defaults to true * @property {String} fileKey Key for your files