feat: Asynchronous initialization of Parse Server (#8232)
BREAKING CHANGE: This release introduces the asynchronous initialization of Parse Server to prevent mounting Parse Server before being ready to receive request; it changes how Parse Server is imported, initialized and started; it also removes the callback `serverStartComplete`; see the [Parse Server 6 migration guide](https://github.com/parse-community/parse-server/blob/alpha/6.0.0.md) for more details (#8232)
This commit is contained in:
@@ -64,73 +64,85 @@ class ParseServer {
|
||||
const {
|
||||
appId = requiredParameter('You must provide an appId!'),
|
||||
masterKey = requiredParameter('You must provide a masterKey!'),
|
||||
cloud,
|
||||
security,
|
||||
javascriptKey,
|
||||
serverURL = requiredParameter('You must provide a serverURL!'),
|
||||
serverStartComplete,
|
||||
schema,
|
||||
} = options;
|
||||
// Initialize the node client SDK automatically
|
||||
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
||||
Parse.serverURL = serverURL;
|
||||
|
||||
const allControllers = controllers.getControllers(options);
|
||||
|
||||
const {
|
||||
loggerController,
|
||||
databaseController,
|
||||
hooksController,
|
||||
liveQueryController,
|
||||
} = allControllers;
|
||||
options.state = 'initialized';
|
||||
this.config = Config.put(Object.assign({}, options, allControllers));
|
||||
logging.setLogger(allControllers.loggerController);
|
||||
}
|
||||
|
||||
logging.setLogger(loggerController);
|
||||
/**
|
||||
* Starts Parse Server as an express app; this promise resolves when Parse Server is ready to accept requests.
|
||||
*/
|
||||
|
||||
// Note: Tests will start to fail if any validation happens after this is called.
|
||||
databaseController
|
||||
.performInitialization()
|
||||
.then(() => hooksController.load())
|
||||
.then(async () => {
|
||||
const startupPromises = [];
|
||||
if (schema) {
|
||||
startupPromises.push(new DefinedSchemas(schema, this.config).execute());
|
||||
}
|
||||
if (
|
||||
options.cacheAdapter &&
|
||||
options.cacheAdapter.connect &&
|
||||
typeof options.cacheAdapter.connect === 'function'
|
||||
) {
|
||||
startupPromises.push(options.cacheAdapter.connect());
|
||||
}
|
||||
startupPromises.push(liveQueryController.connect());
|
||||
await Promise.all(startupPromises);
|
||||
if (serverStartComplete) {
|
||||
serverStartComplete();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (serverStartComplete) {
|
||||
serverStartComplete(error);
|
||||
} else {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
if (cloud) {
|
||||
addParseCloud();
|
||||
if (typeof cloud === 'function') {
|
||||
cloud(Parse);
|
||||
} else if (typeof cloud === 'string') {
|
||||
require(path.resolve(process.cwd(), cloud));
|
||||
} else {
|
||||
throw "argument 'cloud' must either be a string or a function";
|
||||
async start() {
|
||||
try {
|
||||
if (this.config.state === 'ok') {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
if (security && security.enableCheck && security.enableCheckLog) {
|
||||
new CheckRunner(options.security).run();
|
||||
this.config.state = 'starting';
|
||||
Config.put(this.config);
|
||||
const {
|
||||
databaseController,
|
||||
hooksController,
|
||||
cloud,
|
||||
security,
|
||||
schema,
|
||||
cacheAdapter,
|
||||
liveQueryController,
|
||||
} = this.config;
|
||||
try {
|
||||
await databaseController.performInitialization();
|
||||
} catch (e) {
|
||||
if (e.code !== Parse.Error.DUPLICATE_VALUE) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
await hooksController.load();
|
||||
const startupPromises = [];
|
||||
if (schema) {
|
||||
startupPromises.push(new DefinedSchemas(schema, this.config).execute());
|
||||
}
|
||||
if (cacheAdapter?.connect && typeof cacheAdapter.connect === 'function') {
|
||||
startupPromises.push(cacheAdapter.connect());
|
||||
}
|
||||
startupPromises.push(liveQueryController.connect());
|
||||
await Promise.all(startupPromises);
|
||||
if (cloud) {
|
||||
addParseCloud();
|
||||
if (typeof cloud === 'function') {
|
||||
await Promise.resolve(cloud(Parse));
|
||||
} else if (typeof cloud === 'string') {
|
||||
let json;
|
||||
if (process.env.npm_package_json) {
|
||||
json = require(process.env.npm_package_json);
|
||||
}
|
||||
if (process.env.npm_package_type === 'module' || json?.type === 'module') {
|
||||
await import(path.resolve(process.cwd(), cloud)).default;
|
||||
} else {
|
||||
require(path.resolve(process.cwd(), cloud));
|
||||
}
|
||||
} else {
|
||||
throw "argument 'cloud' must either be a string or a function";
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
}
|
||||
if (security && security.enableCheck && security.enableCheckLog) {
|
||||
new CheckRunner(security).run();
|
||||
}
|
||||
this.config.state = 'ok';
|
||||
Config.put(this.config);
|
||||
return this;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.config.state = 'error';
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,8 +194,12 @@ class ParseServer {
|
||||
);
|
||||
|
||||
api.use('/health', function (req, res) {
|
||||
res.status(options.state === 'ok' ? 200 : 503);
|
||||
if (options.state === 'starting') {
|
||||
res.set('Retry-After', 1);
|
||||
}
|
||||
res.json({
|
||||
status: 'ok',
|
||||
status: options.state,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -266,10 +282,16 @@ class ParseServer {
|
||||
/**
|
||||
* starts the parse server's express app
|
||||
* @param {ParseServerOptions} options to use to start the server
|
||||
* @param {Function} callback called when the server has started
|
||||
* @returns {ParseServer} the parse server instance
|
||||
*/
|
||||
async start(options: ParseServerOptions, callback: ?() => void) {
|
||||
|
||||
async startApp(options: ParseServerOptions) {
|
||||
try {
|
||||
await this.start();
|
||||
} catch (e) {
|
||||
console.error('Error on ParseServer.startApp: ', e);
|
||||
throw e;
|
||||
}
|
||||
const app = express();
|
||||
if (options.middleware) {
|
||||
let middleware;
|
||||
@@ -280,7 +302,6 @@ class ParseServer {
|
||||
}
|
||||
app.use(middleware);
|
||||
}
|
||||
|
||||
app.use(options.mountPath, this.app);
|
||||
|
||||
if (options.mountGraphQL === true || options.mountPlayground === true) {
|
||||
@@ -308,8 +329,11 @@ class ParseServer {
|
||||
parseGraphQLServer.applyPlayground(app);
|
||||
}
|
||||
}
|
||||
|
||||
const server = app.listen(options.port, options.host, callback);
|
||||
const server = await new Promise(resolve => {
|
||||
app.listen(options.port, options.host, function () {
|
||||
resolve(this);
|
||||
});
|
||||
});
|
||||
this.server = server;
|
||||
|
||||
if (options.startLiveQueryServer || options.liveQueryServerOptions) {
|
||||
@@ -330,12 +354,11 @@ class ParseServer {
|
||||
/**
|
||||
* Creates a new ParseServer and starts it.
|
||||
* @param {ParseServerOptions} options used to start the server
|
||||
* @param {Function} callback called when the server has started
|
||||
* @returns {ParseServer} the parse server instance
|
||||
*/
|
||||
static start(options: ParseServerOptions, callback: ?() => void) {
|
||||
static async startApp(options: ParseServerOptions) {
|
||||
const parseServer = new ParseServer(options);
|
||||
return parseServer.start(options, callback);
|
||||
return parseServer.startApp(options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user