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:
5
.babelrc
5
.babelrc
@@ -6,8 +6,9 @@
|
|||||||
"presets": [
|
"presets": [
|
||||||
["@babel/preset-env", {
|
["@babel/preset-env", {
|
||||||
"targets": {
|
"targets": {
|
||||||
"node": "14"
|
"node": "14",
|
||||||
}
|
},
|
||||||
|
"exclude": ["proposal-dynamic-import"]
|
||||||
}]
|
}]
|
||||||
],
|
],
|
||||||
"sourceMaps": "inline"
|
"sourceMaps": "inline"
|
||||||
|
|||||||
63
6.0.0.md
Normal file
63
6.0.0.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Parse Server 6 Migration Guide <!-- omit in toc -->
|
||||||
|
|
||||||
|
This document only highlights specific changes that require a longer explanation. For a full list of changes in Parse Server 6 please refer to the [changelog](https://github.com/parse-community/parse-server/blob/alpha/CHANGELOG.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- [Import Statement](#import-statement)
|
||||||
|
- [Asynchronous Initialization](#asynchronous-initialization)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Import Statement
|
||||||
|
|
||||||
|
The import and initialization syntax has been simplified with more intuitive naming and structure.
|
||||||
|
|
||||||
|
*Parse Server 5:*
|
||||||
|
```js
|
||||||
|
// Returns a Parse Server instance
|
||||||
|
const ParseServer = require('parse-server');
|
||||||
|
|
||||||
|
// Returns a Parse Server express middleware
|
||||||
|
const { ParseServer } = require('parse-server');
|
||||||
|
```
|
||||||
|
|
||||||
|
*Parse Server 6:*
|
||||||
|
```js
|
||||||
|
// Both return a Parse Server instance
|
||||||
|
const ParseServer = require('parse-server');
|
||||||
|
const { ParseServer } = require('parse-server');
|
||||||
|
```
|
||||||
|
|
||||||
|
To get the express middleware in Parse Server 6, configure the Parse Server instance, start Parse Server and use its `app` property. See [Asynchronous Initialization](#asynchronous-initialization) for more details.
|
||||||
|
|
||||||
|
## Asynchronous Initialization
|
||||||
|
|
||||||
|
Previously, it was possible to mount Parse Server before it was fully started up and ready to receive requests. This could result in undefined behavior, such as Parse Objects could be saved before Cloud Code was registered. To prevent this, Parse Server 6 requires to be started asynchronously before being mounted.
|
||||||
|
|
||||||
|
*Parse Server 5:*
|
||||||
|
```js
|
||||||
|
// 1. Import Parse Server
|
||||||
|
const { ParseServer } = require('parse-server');
|
||||||
|
|
||||||
|
// 2. Create a Parse Server instance as express middleware
|
||||||
|
const server = new ParseServer(config);
|
||||||
|
|
||||||
|
// 3. Mount express middleware
|
||||||
|
app.use("/parse", server);
|
||||||
|
```
|
||||||
|
|
||||||
|
*Parse Server 6:*
|
||||||
|
```js
|
||||||
|
// 1. Import Parse Server
|
||||||
|
const ParseServer = require('parse-server');
|
||||||
|
|
||||||
|
// 2. Create a Parse Server instance
|
||||||
|
const server = new ParseServer(config);
|
||||||
|
|
||||||
|
// 3. Start up Parse Server asynchronously
|
||||||
|
await server.start();
|
||||||
|
|
||||||
|
// 4. Mount express middleware
|
||||||
|
app.use("/parse", server.app);
|
||||||
|
```
|
||||||
57
README.md
57
README.md
@@ -40,7 +40,7 @@ A big *thank you* 🙏 to our [sponsors](#sponsors) and [backers](#backers) who
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
- [Flavors & Branches](#flavors--branches)
|
- [Flavors \& Branches](#flavors--branches)
|
||||||
- [Long Term Support](#long-term-support)
|
- [Long Term Support](#long-term-support)
|
||||||
- [Getting Started](#getting-started)
|
- [Getting Started](#getting-started)
|
||||||
- [Running Parse Server](#running-parse-server)
|
- [Running Parse Server](#running-parse-server)
|
||||||
@@ -55,6 +55,8 @@ A big *thank you* 🙏 to our [sponsors](#sponsors) and [backers](#backers) who
|
|||||||
- [Running Parse Server elsewhere](#running-parse-server-elsewhere)
|
- [Running Parse Server elsewhere](#running-parse-server-elsewhere)
|
||||||
- [Sample Application](#sample-application)
|
- [Sample Application](#sample-application)
|
||||||
- [Parse Server + Express](#parse-server--express)
|
- [Parse Server + Express](#parse-server--express)
|
||||||
|
- [Parse Server Health](#parse-server-health)
|
||||||
|
- [Status Values](#status-values)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Basic Options](#basic-options)
|
- [Basic Options](#basic-options)
|
||||||
- [Client Key Options](#client-key-options)
|
- [Client Key Options](#client-key-options)
|
||||||
@@ -136,13 +138,13 @@ Parse Server is continuously tested with the most recent releases of Node.js to
|
|||||||
|
|
||||||
Parse Server is continuously tested with the most recent releases of MongoDB to ensure compatibility. We follow the [MongoDB support schedule](https://www.mongodb.com/support-policy) and [MongoDB lifecycle schedule](https://www.mongodb.com/support-policy/lifecycles) and only test against versions that are officially supported and have not reached their end-of-life date. We consider the end-of-life date of a MongoDB "rapid release" to be the same as its major version release.
|
Parse Server is continuously tested with the most recent releases of MongoDB to ensure compatibility. We follow the [MongoDB support schedule](https://www.mongodb.com/support-policy) and [MongoDB lifecycle schedule](https://www.mongodb.com/support-policy/lifecycles) and only test against versions that are officially supported and have not reached their end-of-life date. We consider the end-of-life date of a MongoDB "rapid release" to be the same as its major version release.
|
||||||
|
|
||||||
| Version | Latest Version | End-of-Life | Compatible |
|
| Version | Latest Version | End-of-Life | Compatible |
|
||||||
|-------------|----------------|---------------|--------------|
|
|-------------|----------------|---------------|------------|
|
||||||
| MongoDB 4.0 | 4.0.28 | April 2022 | ✅ Yes |
|
| MongoDB 4.0 | 4.0.28 | April 2022 | ✅ Yes |
|
||||||
| MongoDB 4.2 | 4.2.19 | April 2023 | ✅ Yes |
|
| MongoDB 4.2 | 4.2.19 | April 2023 | ✅ Yes |
|
||||||
| MongoDB 4.4 | 4.4.13 | February 2024 | ✅ Yes |
|
| MongoDB 4.4 | 4.4.13 | February 2024 | ✅ Yes |
|
||||||
| MongoDB 5 | 5.3.2 | October 2024 | ✅ Yes |
|
| MongoDB 5 | 5.3.2 | October 2024 | ✅ Yes |
|
||||||
| MongoDB 6 | 6.0.2 | July 2025 | ✅ Yes |
|
| MongoDB 6 | 6.0.2 | July 2025 | ✅ Yes |
|
||||||
|
|
||||||
#### PostgreSQL
|
#### PostgreSQL
|
||||||
|
|
||||||
@@ -282,11 +284,11 @@ We have provided a basic [Node.js application](https://github.com/parse-communit
|
|||||||
You can also create an instance of Parse Server, and mount it on a new or existing Express website:
|
You can also create an instance of Parse Server, and mount it on a new or existing Express website:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var express = require('express');
|
const express = require('express');
|
||||||
var ParseServer = require('parse-server').ParseServer;
|
const ParseServer = require('parse-server').ParseServer;
|
||||||
var app = express();
|
const app = express();
|
||||||
|
|
||||||
var api = new ParseServer({
|
const server = new ParseServer({
|
||||||
databaseURI: 'mongodb://localhost:27017/dev', // Connection string for your MongoDB database
|
databaseURI: 'mongodb://localhost:27017/dev', // Connection string for your MongoDB database
|
||||||
cloud: './cloud/main.js', // Path to your Cloud Code
|
cloud: './cloud/main.js', // Path to your Cloud Code
|
||||||
appId: 'myAppId',
|
appId: 'myAppId',
|
||||||
@@ -295,8 +297,11 @@ var api = new ParseServer({
|
|||||||
serverURL: 'http://localhost:1337/parse' // Don't forget to change to https if needed
|
serverURL: 'http://localhost:1337/parse' // Don't forget to change to https if needed
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
await server.start();
|
||||||
|
|
||||||
// Serve the Parse API on the /parse URL prefix
|
// Serve the Parse API on the /parse URL prefix
|
||||||
app.use('/parse', api);
|
app.use('/parse', server.app);
|
||||||
|
|
||||||
app.listen(1337, function() {
|
app.listen(1337, function() {
|
||||||
console.log('parse-server-example running on port 1337.');
|
console.log('parse-server-example running on port 1337.');
|
||||||
@@ -305,6 +310,27 @@ app.listen(1337, function() {
|
|||||||
|
|
||||||
For a full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html).
|
For a full list of available options, run `parse-server --help` or take a look at [Parse Server Configurations](http://parseplatform.org/parse-server/api/master/ParseServerOptions.html).
|
||||||
|
|
||||||
|
## Parse Server Health
|
||||||
|
|
||||||
|
Check the Parse Server health by sending a request to the `/parse/health` endpoint.
|
||||||
|
|
||||||
|
The response looks like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Status Values
|
||||||
|
|
||||||
|
| Value | Description |
|
||||||
|
|---------------|-----------------------------------------------------------------------------|
|
||||||
|
| `initialized` | The server has been created but the `start` method has not been called yet. |
|
||||||
|
| `starting` | The server is starting up. |
|
||||||
|
| `ok` | The server started and is running. |
|
||||||
|
| `error` | There was a startup error, see the logs for details. |
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
|
|
||||||
Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options.
|
Parse Server can be configured using the following options. You may pass these as parameters when running a standalone `parse-server`, or by loading a configuration file in JSON format using `parse-server path/to/configuration.json`. If you're using Parse Server on Express, you may also pass these to the `ParseServer` object as options.
|
||||||
@@ -461,7 +487,7 @@ The following paths are already used by Parse Server's built-in features and are
|
|||||||
It’s possible to change the default pages of the app and redirect the user to another path or domain.
|
It’s possible to change the default pages of the app and redirect the user to another path or domain.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var server = ParseServer({
|
const server = ParseServer({
|
||||||
...otherOptions,
|
...otherOptions,
|
||||||
|
|
||||||
customPages: {
|
customPages: {
|
||||||
@@ -851,7 +877,7 @@ Then, create an `index.js` file with the following content:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { default: ParseServer, ParseGraphQLServer } = require('parse-server');
|
const { ParseServer, ParseGraphQLServer } = require('parse-server');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
@@ -875,6 +901,7 @@ app.use('/parse', parseServer.app); // (Optional) Mounts the REST API
|
|||||||
parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API
|
parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API
|
||||||
parseGraphQLServer.applyPlayground(app); // (Optional) Mounts the GraphQL Playground - do NOT use in Production
|
parseGraphQLServer.applyPlayground(app); // (Optional) Mounts the GraphQL Playground - do NOT use in Production
|
||||||
|
|
||||||
|
await parseServer.start();
|
||||||
app.listen(1337, function() {
|
app.listen(1337, function() {
|
||||||
console.log('REST API running on http://localhost:1337/parse');
|
console.log('REST API running on http://localhost:1337/parse');
|
||||||
console.log('GraphQL API running on http://localhost:1337/graphql');
|
console.log('GraphQL API running on http://localhost:1337/graphql');
|
||||||
|
|||||||
@@ -34,7 +34,8 @@
|
|||||||
"jequal": true,
|
"jequal": true,
|
||||||
"create": true,
|
"create": true,
|
||||||
"arrayContains": true,
|
"arrayContains": true,
|
||||||
"databaseAdapter": true
|
"databaseAdapter": true,
|
||||||
|
"databaseURI": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": [0],
|
"no-console": [0],
|
||||||
|
|||||||
@@ -219,17 +219,14 @@ describe('execution', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shoud start Parse Server', done => {
|
it('should start Parse Server', done => {
|
||||||
childProcess = spawn(binPath, [
|
const env = { ...process.env };
|
||||||
'--appId',
|
env.NODE_OPTIONS = '--dns-result-order=ipv4first';
|
||||||
'test',
|
childProcess = spawn(
|
||||||
'--masterKey',
|
binPath,
|
||||||
'test',
|
['--appId', 'test', '--masterKey', 'test', '--databaseURI', databaseURI, '--port', '1339'],
|
||||||
'--databaseURI',
|
{ env }
|
||||||
'mongodb://localhost/test',
|
);
|
||||||
'--port',
|
|
||||||
'1339',
|
|
||||||
]);
|
|
||||||
childProcess.stdout.on('data', data => {
|
childProcess.stdout.on('data', data => {
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
if (data.includes('parse-server running on')) {
|
if (data.includes('parse-server running on')) {
|
||||||
@@ -241,18 +238,24 @@ describe('execution', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shoud start Parse Server with GraphQL', done => {
|
it('should start Parse Server with GraphQL', async done => {
|
||||||
childProcess = spawn(binPath, [
|
const env = { ...process.env };
|
||||||
'--appId',
|
env.NODE_OPTIONS = '--dns-result-order=ipv4first';
|
||||||
'test',
|
childProcess = spawn(
|
||||||
'--masterKey',
|
binPath,
|
||||||
'test',
|
[
|
||||||
'--databaseURI',
|
'--appId',
|
||||||
'mongodb://localhost/test',
|
'test',
|
||||||
'--port',
|
'--masterKey',
|
||||||
'1340',
|
'test',
|
||||||
'--mountGraphQL',
|
'--databaseURI',
|
||||||
]);
|
databaseURI,
|
||||||
|
'--port',
|
||||||
|
'1340',
|
||||||
|
'--mountGraphQL',
|
||||||
|
],
|
||||||
|
{ env }
|
||||||
|
);
|
||||||
let output = '';
|
let output = '';
|
||||||
childProcess.stdout.on('data', data => {
|
childProcess.stdout.on('data', data => {
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
@@ -267,19 +270,25 @@ describe('execution', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shoud start Parse Server with GraphQL and Playground', done => {
|
it('should start Parse Server with GraphQL and Playground', async done => {
|
||||||
childProcess = spawn(binPath, [
|
const env = { ...process.env };
|
||||||
'--appId',
|
env.NODE_OPTIONS = '--dns-result-order=ipv4first';
|
||||||
'test',
|
childProcess = spawn(
|
||||||
'--masterKey',
|
binPath,
|
||||||
'test',
|
[
|
||||||
'--databaseURI',
|
'--appId',
|
||||||
'mongodb://localhost/test',
|
'test',
|
||||||
'--port',
|
'--masterKey',
|
||||||
'1341',
|
'test',
|
||||||
'--mountGraphQL',
|
'--databaseURI',
|
||||||
'--mountPlayground',
|
databaseURI,
|
||||||
]);
|
'--port',
|
||||||
|
'1341',
|
||||||
|
'--mountGraphQL',
|
||||||
|
'--mountPlayground',
|
||||||
|
],
|
||||||
|
{ env }
|
||||||
|
);
|
||||||
let output = '';
|
let output = '';
|
||||||
childProcess.stdout.on('data', data => {
|
childProcess.stdout.on('data', data => {
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const Config = require('../lib/Config');
|
const Config = require('../lib/Config');
|
||||||
const Parse = require('parse/node');
|
const Parse = require('parse/node');
|
||||||
|
const ParseServer = require('../lib/index').ParseServer;
|
||||||
const request = require('../lib/request');
|
const request = require('../lib/request');
|
||||||
const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter')
|
const InMemoryCacheAdapter = require('../lib/Adapters/Cache/InMemoryCacheAdapter')
|
||||||
.InMemoryCacheAdapter;
|
.InMemoryCacheAdapter;
|
||||||
@@ -39,6 +40,47 @@ describe('Cloud Code', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can load cloud code as a module', async () => {
|
||||||
|
process.env.npm_package_type = 'module';
|
||||||
|
await reconfigureServer({ appId: 'test1', cloud: './spec/cloud/cloudCodeModuleFile.js' });
|
||||||
|
const result = await Parse.Cloud.run('cloudCodeInFile');
|
||||||
|
expect(result).toEqual('It is possible to define cloud code in a file.');
|
||||||
|
delete process.env.npm_package_type;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cloud code must be valid type', async () => {
|
||||||
|
await expectAsync(reconfigureServer({ cloud: true })).toBeRejectedWith(
|
||||||
|
"argument 'cloud' must either be a string or a function"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should wait for cloud code to load', async () => {
|
||||||
|
await reconfigureServer({ appId: 'test3' });
|
||||||
|
const initiated = new Date();
|
||||||
|
const parseServer = await new ParseServer({
|
||||||
|
...defaultConfiguration,
|
||||||
|
appId: 'test3',
|
||||||
|
masterKey: 'test',
|
||||||
|
serverURL: 'http://localhost:12668/parse',
|
||||||
|
async cloud() {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
Parse.Cloud.beforeSave('Test', () => {
|
||||||
|
throw 'Cannot save.';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}).start();
|
||||||
|
const express = require('express');
|
||||||
|
const app = express();
|
||||||
|
app.use('/parse', parseServer.app);
|
||||||
|
const server = app.listen(12668);
|
||||||
|
const now = new Date();
|
||||||
|
expect(now.getTime() - initiated.getTime() > 1000).toBeTrue();
|
||||||
|
await expectAsync(new Parse.Object('Test').save()).toBeRejectedWith(
|
||||||
|
new Parse.Error(141, 'Cannot save.')
|
||||||
|
);
|
||||||
|
await new Promise(resolve => server.close(resolve));
|
||||||
|
});
|
||||||
|
|
||||||
it('can create functions', done => {
|
it('can create functions', done => {
|
||||||
Parse.Cloud.define('hello', () => {
|
Parse.Cloud.define('hello', () => {
|
||||||
return 'Hello world!';
|
return 'Hello world!';
|
||||||
|
|||||||
@@ -631,7 +631,7 @@ describe('DefinedSchemas', () => {
|
|||||||
const logger = require('../lib/logger').logger;
|
const logger = require('../lib/logger').logger;
|
||||||
spyOn(DefinedSchemas.prototype, 'wait').and.resolveTo();
|
spyOn(DefinedSchemas.prototype, 'wait').and.resolveTo();
|
||||||
spyOn(logger, 'error').and.callThrough();
|
spyOn(logger, 'error').and.callThrough();
|
||||||
spyOn(Parse.Schema, 'all').and.callFake(() => {
|
spyOn(DefinedSchemas.prototype, 'createDeleteSession').and.callFake(() => {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe_only_db('mongo')('initialization', () => {
|
describe_only_db('mongo')('initialization', () => {
|
||||||
it('can be initialized through ParseServer without liveQueryServerOptions', async function (done) {
|
it('can be initialized through ParseServer without liveQueryServerOptions', async () => {
|
||||||
const parseServer = await ParseServer.start({
|
const parseServer = await ParseServer.startApp({
|
||||||
appId: 'hello',
|
appId: 'hello',
|
||||||
masterKey: 'world',
|
masterKey: 'world',
|
||||||
port: 22345,
|
port: 22345,
|
||||||
@@ -126,19 +126,14 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
classNames: ['Yolo'],
|
classNames: ['Yolo'],
|
||||||
},
|
},
|
||||||
startLiveQueryServer: true,
|
startLiveQueryServer: true,
|
||||||
serverStartComplete: () => {
|
|
||||||
expect(parseServer.liveQueryServer).not.toBeUndefined();
|
|
||||||
expect(parseServer.liveQueryServer.server).toBe(parseServer.server);
|
|
||||||
parseServer.server.close(async () => {
|
|
||||||
await reconfigureServer();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
expect(parseServer.liveQueryServer).not.toBeUndefined();
|
||||||
|
expect(parseServer.liveQueryServer.server).toBe(parseServer.server);
|
||||||
|
await new Promise(resolve => parseServer.server.close(resolve));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can be initialized through ParseServer with liveQueryServerOptions', async function (done) {
|
it('can be initialized through ParseServer with liveQueryServerOptions', async () => {
|
||||||
const parseServer = await ParseServer.start({
|
const parseServer = await ParseServer.startApp({
|
||||||
appId: 'hello',
|
appId: 'hello',
|
||||||
masterKey: 'world',
|
masterKey: 'world',
|
||||||
port: 22346,
|
port: 22346,
|
||||||
@@ -150,17 +145,10 @@ describe('ParseLiveQueryServer', function () {
|
|||||||
liveQueryServerOptions: {
|
liveQueryServerOptions: {
|
||||||
port: 22347,
|
port: 22347,
|
||||||
},
|
},
|
||||||
serverStartComplete: () => {
|
|
||||||
expect(parseServer.liveQueryServer).not.toBeUndefined();
|
|
||||||
expect(parseServer.liveQueryServer.server).not.toBe(parseServer.server);
|
|
||||||
parseServer.liveQueryServer.server.close(
|
|
||||||
parseServer.server.close.bind(parseServer.server, async () => {
|
|
||||||
await reconfigureServer();
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
expect(parseServer.liveQueryServer).not.toBeUndefined();
|
||||||
|
expect(parseServer.liveQueryServer.server).not.toBe(parseServer.server);
|
||||||
|
await new Promise(resolve => parseServer.server.close(resolve));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ describe('Server Url Checks', () => {
|
|||||||
close = true;
|
close = true;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const parseServer = ParseServer.start(newConfiguration);
|
const parseServer = ParseServer.startApp(newConfiguration);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not have unhandled promise rejection in the case of load error', done => {
|
it('does not have unhandled promise rejection in the case of load error', done => {
|
||||||
|
|||||||
3
spec/cloud/cloudCodeModuleFile.js
Normal file
3
spec/cloud/cloudCodeModuleFile.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Parse.Cloud.define('cloudCodeInFile', () => {
|
||||||
|
return 'It is possible to define cloud code in a file.';
|
||||||
|
});
|
||||||
@@ -50,16 +50,19 @@ const { VolatileClassesSchemas } = require('../lib/Controllers/SchemaController'
|
|||||||
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
const mongoURI = 'mongodb://localhost:27017/parseServerMongoAdapterTestDatabase';
|
||||||
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
const postgresURI = 'postgres://localhost:5432/parse_server_postgres_adapter_test_database';
|
||||||
let databaseAdapter;
|
let databaseAdapter;
|
||||||
|
let databaseURI;
|
||||||
// need to bind for mocking mocha
|
// need to bind for mocking mocha
|
||||||
|
|
||||||
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
|
if (process.env.PARSE_SERVER_TEST_DB === 'postgres') {
|
||||||
|
databaseURI = process.env.PARSE_SERVER_TEST_DATABASE_URI || postgresURI;
|
||||||
databaseAdapter = new PostgresStorageAdapter({
|
databaseAdapter = new PostgresStorageAdapter({
|
||||||
uri: process.env.PARSE_SERVER_TEST_DATABASE_URI || postgresURI,
|
uri: databaseURI,
|
||||||
collectionPrefix: 'test_',
|
collectionPrefix: 'test_',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
databaseURI = mongoURI;
|
||||||
databaseAdapter = new MongoStorageAdapter({
|
databaseAdapter = new MongoStorageAdapter({
|
||||||
uri: mongoURI,
|
uri: databaseURI,
|
||||||
collectionPrefix: 'test_',
|
collectionPrefix: 'test_',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -149,49 +152,33 @@ let server;
|
|||||||
let didChangeConfiguration = false;
|
let didChangeConfiguration = false;
|
||||||
|
|
||||||
// Allows testing specific configurations of Parse Server
|
// Allows testing specific configurations of Parse Server
|
||||||
const reconfigureServer = (changedConfiguration = {}) => {
|
const reconfigureServer = async (changedConfiguration = {}) => {
|
||||||
return new Promise((resolve, reject) => {
|
if (server) {
|
||||||
if (server) {
|
await new Promise(resolve => server.close(resolve));
|
||||||
return server.close(() => {
|
server = undefined;
|
||||||
server = undefined;
|
return reconfigureServer(changedConfiguration);
|
||||||
reconfigureServer(changedConfiguration).then(resolve, reject);
|
}
|
||||||
});
|
didChangeConfiguration = Object.keys(changedConfiguration).length !== 0;
|
||||||
}
|
const newConfiguration = Object.assign({}, defaultConfiguration, changedConfiguration, {
|
||||||
try {
|
mountPath: '/1',
|
||||||
let parseServer = undefined;
|
port,
|
||||||
didChangeConfiguration = Object.keys(changedConfiguration).length !== 0;
|
|
||||||
const newConfiguration = Object.assign({}, defaultConfiguration, changedConfiguration, {
|
|
||||||
serverStartComplete: error => {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
Parse.CoreManager.setRESTController(RESTController);
|
|
||||||
resolve(parseServer);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mountPath: '/1',
|
|
||||||
port,
|
|
||||||
});
|
|
||||||
cache.clear();
|
|
||||||
ParseServer.start(newConfiguration).then(_parseServer => {
|
|
||||||
parseServer = _parseServer;
|
|
||||||
parseServer.expressApp.use('/1', err => {
|
|
||||||
console.error(err);
|
|
||||||
fail('should not call next');
|
|
||||||
});
|
|
||||||
server = parseServer.server;
|
|
||||||
server.on('connection', connection => {
|
|
||||||
const key = `${connection.remoteAddress}:${connection.remotePort}`;
|
|
||||||
openConnections[key] = connection;
|
|
||||||
connection.on('close', () => {
|
|
||||||
delete openConnections[key];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
cache.clear();
|
||||||
|
const parseServer = await ParseServer.startApp(newConfiguration);
|
||||||
|
server = parseServer.server;
|
||||||
|
Parse.CoreManager.setRESTController(RESTController);
|
||||||
|
parseServer.expressApp.use('/1', err => {
|
||||||
|
console.error(err);
|
||||||
|
fail('should not call next');
|
||||||
|
});
|
||||||
|
server.on('connection', connection => {
|
||||||
|
const key = `${connection.remoteAddress}:${connection.remotePort}`;
|
||||||
|
openConnections[key] = connection;
|
||||||
|
connection.on('close', () => {
|
||||||
|
delete openConnections[key];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return parseServer;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up a Parse client to talk to our test API server
|
// Set up a Parse client to talk to our test API server
|
||||||
@@ -423,6 +410,7 @@ global.defaultConfiguration = defaultConfiguration;
|
|||||||
global.mockCustomAuthenticator = mockCustomAuthenticator;
|
global.mockCustomAuthenticator = mockCustomAuthenticator;
|
||||||
global.mockFacebookAuthenticator = mockFacebookAuthenticator;
|
global.mockFacebookAuthenticator = mockFacebookAuthenticator;
|
||||||
global.databaseAdapter = databaseAdapter;
|
global.databaseAdapter = databaseAdapter;
|
||||||
|
global.databaseURI = databaseURI;
|
||||||
global.jfail = function (err) {
|
global.jfail = function (err) {
|
||||||
fail(JSON.stringify(err));
|
fail(JSON.stringify(err));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,34 +61,19 @@ describe('server', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails if database is unreachable', done => {
|
it('fails if database is unreachable', async () => {
|
||||||
reconfigureServer({
|
const server = new ParseServer.default({
|
||||||
|
...defaultConfiguration,
|
||||||
databaseAdapter: new MongoStorageAdapter({
|
databaseAdapter: new MongoStorageAdapter({
|
||||||
uri: 'mongodb://fake:fake@localhost:43605/drew3',
|
uri: 'mongodb://fake:fake@localhost:43605/drew3',
|
||||||
mongoOptions: {
|
mongoOptions: {
|
||||||
serverSelectionTimeoutMS: 2000,
|
serverSelectionTimeoutMS: 2000,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}).catch(() => {
|
|
||||||
const config = Config.get('test');
|
|
||||||
config.schemaCache.clear();
|
|
||||||
//Need to use rest api because saving via JS SDK results in fail() not getting called
|
|
||||||
request({
|
|
||||||
method: 'POST',
|
|
||||||
url: 'http://localhost:8378/1/classes/NewClass',
|
|
||||||
headers: {
|
|
||||||
'X-Parse-Application-Id': 'test',
|
|
||||||
'X-Parse-REST-API-Key': 'rest',
|
|
||||||
},
|
|
||||||
body: {},
|
|
||||||
}).then(fail, response => {
|
|
||||||
expect(response.status).toEqual(500);
|
|
||||||
const body = response.data;
|
|
||||||
expect(body.code).toEqual(1);
|
|
||||||
expect(body.message).toEqual('Internal server error.');
|
|
||||||
reconfigureServer().then(done, done);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
const error = await server.start().catch(e => e);
|
||||||
|
expect(`${error}`.includes('MongoServerSelectionError')).toBeTrue();
|
||||||
|
await reconfigureServer();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('mail adapter', () => {
|
describe('mail adapter', () => {
|
||||||
@@ -295,91 +280,47 @@ describe('server', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can create a parse-server v1', done => {
|
it('can create a parse-server v1', async () => {
|
||||||
|
await reconfigureServer({ appId: 'aTestApp' });
|
||||||
const parseServer = new ParseServer.default(
|
const parseServer = new ParseServer.default(
|
||||||
Object.assign({}, defaultConfiguration, {
|
Object.assign({}, defaultConfiguration, {
|
||||||
appId: 'aTestApp',
|
appId: 'aTestApp',
|
||||||
masterKey: 'aTestMasterKey',
|
masterKey: 'aTestMasterKey',
|
||||||
serverURL: 'http://localhost:12666/parse',
|
serverURL: 'http://localhost:12666/parse',
|
||||||
serverStartComplete: () => {
|
|
||||||
expect(Parse.applicationId).toEqual('aTestApp');
|
|
||||||
const app = express();
|
|
||||||
app.use('/parse', parseServer.app);
|
|
||||||
|
|
||||||
const server = app.listen(12666);
|
|
||||||
const obj = new Parse.Object('AnObject');
|
|
||||||
let objId;
|
|
||||||
obj
|
|
||||||
.save()
|
|
||||||
.then(obj => {
|
|
||||||
objId = obj.id;
|
|
||||||
const q = new Parse.Query('AnObject');
|
|
||||||
return q.first();
|
|
||||||
})
|
|
||||||
.then(obj => {
|
|
||||||
expect(obj.id).toEqual(objId);
|
|
||||||
server.close(async () => {
|
|
||||||
await reconfigureServer();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
server.close(async () => {
|
|
||||||
await reconfigureServer();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
await parseServer.start();
|
||||||
|
expect(Parse.applicationId).toEqual('aTestApp');
|
||||||
|
const app = express();
|
||||||
|
app.use('/parse', parseServer.app);
|
||||||
|
const server = app.listen(12666);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
await obj.save();
|
||||||
|
const query = await new Parse.Query('AnObject').first();
|
||||||
|
expect(obj.id).toEqual(query.id);
|
||||||
|
await new Promise(resolve => server.close(resolve));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can create a parse-server v2', done => {
|
it('can create a parse-server v2', async () => {
|
||||||
let objId;
|
await reconfigureServer({ appId: 'anOtherTestApp' });
|
||||||
let server;
|
|
||||||
const parseServer = ParseServer.ParseServer(
|
const parseServer = ParseServer.ParseServer(
|
||||||
Object.assign({}, defaultConfiguration, {
|
Object.assign({}, defaultConfiguration, {
|
||||||
appId: 'anOtherTestApp',
|
appId: 'anOtherTestApp',
|
||||||
masterKey: 'anOtherTestMasterKey',
|
masterKey: 'anOtherTestMasterKey',
|
||||||
serverURL: 'http://localhost:12667/parse',
|
serverURL: 'http://localhost:12667/parse',
|
||||||
serverStartComplete: error => {
|
|
||||||
const promise = error ? Promise.reject(error) : Promise.resolve();
|
|
||||||
promise
|
|
||||||
.then(() => {
|
|
||||||
expect(Parse.applicationId).toEqual('anOtherTestApp');
|
|
||||||
const app = express();
|
|
||||||
app.use('/parse', parseServer);
|
|
||||||
|
|
||||||
server = app.listen(12667);
|
|
||||||
const obj = new Parse.Object('AnObject');
|
|
||||||
return obj.save();
|
|
||||||
})
|
|
||||||
.then(obj => {
|
|
||||||
objId = obj.id;
|
|
||||||
const q = new Parse.Query('AnObject');
|
|
||||||
return q.first();
|
|
||||||
})
|
|
||||||
.then(obj => {
|
|
||||||
expect(obj.id).toEqual(objId);
|
|
||||||
server.close(async () => {
|
|
||||||
await reconfigureServer();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
fail(JSON.stringify(error));
|
|
||||||
if (server) {
|
|
||||||
server.close(async () => {
|
|
||||||
await reconfigureServer();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(Parse.applicationId).toEqual('anOtherTestApp');
|
||||||
|
await parseServer.start();
|
||||||
|
const app = express();
|
||||||
|
app.use('/parse', parseServer.app);
|
||||||
|
const server = app.listen(12667);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
await obj.save();
|
||||||
|
const q = await new Parse.Query('AnObject').first();
|
||||||
|
expect(obj.id).toEqual(q.id);
|
||||||
|
await new Promise(resolve => server.close(resolve));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has createLiveQueryServer', done => {
|
it('has createLiveQueryServer', done => {
|
||||||
@@ -558,6 +499,84 @@ describe('server', () => {
|
|||||||
.catch(done.fail);
|
.catch(done.fail);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can call start', async () => {
|
||||||
|
await reconfigureServer({ appId: 'aTestApp' });
|
||||||
|
const config = {
|
||||||
|
...defaultConfiguration,
|
||||||
|
appId: 'aTestApp',
|
||||||
|
masterKey: 'aTestMasterKey',
|
||||||
|
serverURL: 'http://localhost:12701/parse',
|
||||||
|
};
|
||||||
|
const parseServer = new ParseServer.ParseServer(config);
|
||||||
|
await parseServer.start();
|
||||||
|
expect(Parse.applicationId).toEqual('aTestApp');
|
||||||
|
expect(Parse.serverURL).toEqual('http://localhost:12701/parse');
|
||||||
|
const app = express();
|
||||||
|
app.use('/parse', parseServer.app);
|
||||||
|
const server = app.listen(12701);
|
||||||
|
const testObject = new Parse.Object('TestObject');
|
||||||
|
await expectAsync(testObject.save()).toBeResolved();
|
||||||
|
await new Promise(resolve => server.close(resolve));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('start is required to mount', async () => {
|
||||||
|
await reconfigureServer({ appId: 'aTestApp' });
|
||||||
|
const config = {
|
||||||
|
...defaultConfiguration,
|
||||||
|
appId: 'aTestApp',
|
||||||
|
masterKey: 'aTestMasterKey',
|
||||||
|
serverURL: 'http://localhost:12701/parse',
|
||||||
|
};
|
||||||
|
const parseServer = new ParseServer.ParseServer(config);
|
||||||
|
expect(Parse.applicationId).toEqual('aTestApp');
|
||||||
|
expect(Parse.serverURL).toEqual('http://localhost:12701/parse');
|
||||||
|
const app = express();
|
||||||
|
app.use('/parse', parseServer.app);
|
||||||
|
const server = app.listen(12701);
|
||||||
|
const response = await request({
|
||||||
|
headers: {
|
||||||
|
'X-Parse-Application-Id': 'aTestApp',
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
url: 'http://localhost:12701/parse/classes/TestObject',
|
||||||
|
}).catch(e => new Parse.Error(e.data.code, e.data.error));
|
||||||
|
expect(response).toEqual(
|
||||||
|
new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Invalid server state: initialized')
|
||||||
|
);
|
||||||
|
const health = await request({
|
||||||
|
url: 'http://localhost:12701/parse/health',
|
||||||
|
}).catch(e => e);
|
||||||
|
expect(health.data.status).toBe('initialized');
|
||||||
|
expect(health.status).toBe(503);
|
||||||
|
await new Promise(resolve => server.close(resolve));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can get starting state', async () => {
|
||||||
|
await reconfigureServer({ appId: 'test2', silent: false });
|
||||||
|
const parseServer = new ParseServer.ParseServer({
|
||||||
|
...defaultConfiguration,
|
||||||
|
appId: 'test2',
|
||||||
|
masterKey: 'abc',
|
||||||
|
serverURL: 'http://localhost:12668/parse',
|
||||||
|
async cloud() {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const express = require('express');
|
||||||
|
const app = express();
|
||||||
|
app.use('/parse', parseServer.app);
|
||||||
|
const server = app.listen(12668);
|
||||||
|
const startingPromise = parseServer.start();
|
||||||
|
const health = await request({
|
||||||
|
url: 'http://localhost:12668/parse/health',
|
||||||
|
}).catch(e => e);
|
||||||
|
expect(health.data.status).toBe('starting');
|
||||||
|
expect(health.status).toBe(503);
|
||||||
|
expect(health.headers['retry-after']).toBe('1');
|
||||||
|
await startingPromise;
|
||||||
|
await new Promise(resolve => server.close(resolve));
|
||||||
|
});
|
||||||
|
|
||||||
it('should not fail when Google signin is introduced without the optional clientId', done => {
|
it('should not fail when Google signin is introduced without the optional clientId', done => {
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
|
|||||||
@@ -6,14 +6,20 @@ const ParseServer = require('../../lib/index').ParseServer;
|
|||||||
|
|
||||||
const databaseURI = 'mongodb://doesnotexist:27017/parseServerMongoAdapterTestDatabase';
|
const databaseURI = 'mongodb://doesnotexist:27017/parseServerMongoAdapterTestDatabase';
|
||||||
|
|
||||||
ParseServer.start({
|
(async () => {
|
||||||
appId: 'test',
|
try {
|
||||||
masterKey: 'test',
|
await ParseServer.startApp({
|
||||||
databaseAdapter: new MongoStorageAdapter({
|
appId: 'test',
|
||||||
uri: databaseURI,
|
masterKey: 'test',
|
||||||
mongoOptions: {
|
databaseAdapter: new MongoStorageAdapter({
|
||||||
serverSelectionTimeoutMS: 2000,
|
uri: databaseURI,
|
||||||
},
|
mongoOptions: {
|
||||||
}),
|
serverSelectionTimeoutMS: 2000,
|
||||||
filesAdapter: new GridFSBucketAdapter(databaseURI),
|
},
|
||||||
});
|
}),
|
||||||
|
filesAdapter: new GridFSBucketAdapter(databaseURI),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|||||||
@@ -466,10 +466,6 @@ module.exports.ParseServerOptions = {
|
|||||||
env: 'PARSE_SERVER_SERVER_CLOSE_COMPLETE',
|
env: 'PARSE_SERVER_SERVER_CLOSE_COMPLETE',
|
||||||
help: 'Callback when server has closed',
|
help: 'Callback when server has closed',
|
||||||
},
|
},
|
||||||
serverStartComplete: {
|
|
||||||
env: 'PARSE_SERVER_SERVER_START_COMPLETE',
|
|
||||||
help: 'Callback when server has started',
|
|
||||||
},
|
|
||||||
serverURL: {
|
serverURL: {
|
||||||
env: 'PARSE_SERVER_URL',
|
env: 'PARSE_SERVER_URL',
|
||||||
help: 'URL to your parse server with http:// or https://.',
|
help: 'URL to your parse server with http:// or https://.',
|
||||||
|
|||||||
@@ -85,7 +85,6 @@
|
|||||||
* @property {SchemaOptions} schema Defined schema
|
* @property {SchemaOptions} schema Defined schema
|
||||||
* @property {SecurityOptions} security The security options to identify and report weak security settings.
|
* @property {SecurityOptions} security The security options to identify and report weak security settings.
|
||||||
* @property {Function} serverCloseComplete Callback when server has closed
|
* @property {Function} serverCloseComplete Callback when server has closed
|
||||||
* @property {Function} serverStartComplete Callback when server has started
|
|
||||||
* @property {String} serverURL URL to your parse server with http:// or https://.
|
* @property {String} serverURL URL to your parse server with http:// or https://.
|
||||||
* @property {Number} sessionLength Session duration, in seconds, defaults to 1 year
|
* @property {Number} sessionLength Session duration, in seconds, defaults to 1 year
|
||||||
* @property {Boolean} silent Disables console output
|
* @property {Boolean} silent Disables console output
|
||||||
|
|||||||
@@ -271,8 +271,6 @@ export interface ParseServerOptions {
|
|||||||
:ENV: PARSE_SERVER_PLAYGROUND_PATH
|
:ENV: PARSE_SERVER_PLAYGROUND_PATH
|
||||||
:DEFAULT: /playground */
|
:DEFAULT: /playground */
|
||||||
playgroundPath: ?string;
|
playgroundPath: ?string;
|
||||||
/* Callback when server has started */
|
|
||||||
serverStartComplete: ?(error: ?Error) => void;
|
|
||||||
/* Defined schema
|
/* Defined schema
|
||||||
:ENV: PARSE_SERVER_SCHEMA
|
:ENV: PARSE_SERVER_SCHEMA
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -64,73 +64,85 @@ class ParseServer {
|
|||||||
const {
|
const {
|
||||||
appId = requiredParameter('You must provide an appId!'),
|
appId = requiredParameter('You must provide an appId!'),
|
||||||
masterKey = requiredParameter('You must provide a masterKey!'),
|
masterKey = requiredParameter('You must provide a masterKey!'),
|
||||||
cloud,
|
|
||||||
security,
|
|
||||||
javascriptKey,
|
javascriptKey,
|
||||||
serverURL = requiredParameter('You must provide a serverURL!'),
|
serverURL = requiredParameter('You must provide a serverURL!'),
|
||||||
serverStartComplete,
|
|
||||||
schema,
|
|
||||||
} = options;
|
} = options;
|
||||||
// Initialize the node client SDK automatically
|
// Initialize the node client SDK automatically
|
||||||
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
||||||
Parse.serverURL = serverURL;
|
Parse.serverURL = serverURL;
|
||||||
|
|
||||||
const allControllers = controllers.getControllers(options);
|
const allControllers = controllers.getControllers(options);
|
||||||
|
options.state = 'initialized';
|
||||||
const {
|
|
||||||
loggerController,
|
|
||||||
databaseController,
|
|
||||||
hooksController,
|
|
||||||
liveQueryController,
|
|
||||||
} = allControllers;
|
|
||||||
this.config = Config.put(Object.assign({}, options, allControllers));
|
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.
|
async start() {
|
||||||
databaseController
|
try {
|
||||||
.performInitialization()
|
if (this.config.state === 'ok') {
|
||||||
.then(() => hooksController.load())
|
return this;
|
||||||
.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";
|
|
||||||
}
|
}
|
||||||
}
|
this.config.state = 'starting';
|
||||||
|
Config.put(this.config);
|
||||||
if (security && security.enableCheck && security.enableCheckLog) {
|
const {
|
||||||
new CheckRunner(options.security).run();
|
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) {
|
api.use('/health', function (req, res) {
|
||||||
|
res.status(options.state === 'ok' ? 200 : 503);
|
||||||
|
if (options.state === 'starting') {
|
||||||
|
res.set('Retry-After', 1);
|
||||||
|
}
|
||||||
res.json({
|
res.json({
|
||||||
status: 'ok',
|
status: options.state,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -266,10 +282,16 @@ class ParseServer {
|
|||||||
/**
|
/**
|
||||||
* starts the parse server's express app
|
* starts the parse server's express app
|
||||||
* @param {ParseServerOptions} options to use to start the server
|
* @param {ParseServerOptions} options to use to start the server
|
||||||
* @param {Function} callback called when the server has started
|
|
||||||
* @returns {ParseServer} the parse server instance
|
* @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();
|
const app = express();
|
||||||
if (options.middleware) {
|
if (options.middleware) {
|
||||||
let middleware;
|
let middleware;
|
||||||
@@ -280,7 +302,6 @@ class ParseServer {
|
|||||||
}
|
}
|
||||||
app.use(middleware);
|
app.use(middleware);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(options.mountPath, this.app);
|
app.use(options.mountPath, this.app);
|
||||||
|
|
||||||
if (options.mountGraphQL === true || options.mountPlayground === true) {
|
if (options.mountGraphQL === true || options.mountPlayground === true) {
|
||||||
@@ -308,8 +329,11 @@ class ParseServer {
|
|||||||
parseGraphQLServer.applyPlayground(app);
|
parseGraphQLServer.applyPlayground(app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const server = await new Promise(resolve => {
|
||||||
const server = app.listen(options.port, options.host, callback);
|
app.listen(options.port, options.host, function () {
|
||||||
|
resolve(this);
|
||||||
|
});
|
||||||
|
});
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
|
||||||
if (options.startLiveQueryServer || options.liveQueryServerOptions) {
|
if (options.startLiveQueryServer || options.liveQueryServerOptions) {
|
||||||
@@ -330,12 +354,11 @@ class ParseServer {
|
|||||||
/**
|
/**
|
||||||
* Creates a new ParseServer and starts it.
|
* Creates a new ParseServer and starts it.
|
||||||
* @param {ParseServerOptions} options used to start the server
|
* @param {ParseServerOptions} options used to start the server
|
||||||
* @param {Function} callback called when the server has started
|
|
||||||
* @returns {ParseServer} the parse server instance
|
* @returns {ParseServer} the parse server instance
|
||||||
*/
|
*/
|
||||||
static start(options: ParseServerOptions, callback: ?() => void) {
|
static async startApp(options: ParseServerOptions) {
|
||||||
const parseServer = new ParseServer(options);
|
const parseServer = new ParseServer(options);
|
||||||
return parseServer.start(options, callback);
|
return parseServer.startApp(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { internalCreateSchema, internalUpdateSchema } from '../Routers/SchemasRo
|
|||||||
import { defaultColumns, systemClasses } from '../Controllers/SchemaController';
|
import { defaultColumns, systemClasses } from '../Controllers/SchemaController';
|
||||||
import { ParseServerOptions } from '../Options';
|
import { ParseServerOptions } from '../Options';
|
||||||
import * as Migrations from './Migrations';
|
import * as Migrations from './Migrations';
|
||||||
|
import Auth from '../Auth';
|
||||||
|
import rest from '../rest';
|
||||||
|
|
||||||
export class DefinedSchemas {
|
export class DefinedSchemas {
|
||||||
config: ParseServerOptions;
|
config: ParseServerOptions;
|
||||||
@@ -96,9 +98,10 @@ export class DefinedSchemas {
|
|||||||
}, 20000);
|
}, 20000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hack to force session schema to be created
|
|
||||||
await this.createDeleteSession();
|
await this.createDeleteSession();
|
||||||
this.allCloudSchemas = await Parse.Schema.all();
|
// @flow-disable-next-line
|
||||||
|
const schemaController = await this.config.database.loadSchema();
|
||||||
|
this.allCloudSchemas = await schemaController.getAllClasses();
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
await Promise.all(this.localSchemas.map(async localSchema => this.saveOrUpdate(localSchema)));
|
await Promise.all(this.localSchemas.map(async localSchema => this.saveOrUpdate(localSchema)));
|
||||||
|
|
||||||
@@ -171,9 +174,8 @@ export class DefinedSchemas {
|
|||||||
// Create a fake session since Parse do not create the _Session until
|
// Create a fake session since Parse do not create the _Session until
|
||||||
// a session is created
|
// a session is created
|
||||||
async createDeleteSession() {
|
async createDeleteSession() {
|
||||||
const session = new Parse.Session();
|
const { response } = await rest.create(this.config, Auth.master(this.config), '_Session', {});
|
||||||
await session.save(null, { useMasterKey: true });
|
await rest.del(this.config, Auth.master(this.config), '_Session', response.objectId);
|
||||||
await session.destroy({ useMasterKey: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveOrUpdate(localSchema: Migrations.JSONSchema) {
|
async saveOrUpdate(localSchema: Migrations.JSONSchema) {
|
||||||
|
|||||||
@@ -68,16 +68,26 @@ runner({
|
|||||||
cluster.fork();
|
cluster.fork();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
ParseServer.start(options, () => {
|
ParseServer.startApp(options)
|
||||||
printSuccessMessage();
|
.then(() => {
|
||||||
});
|
printSuccessMessage();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ParseServer.start(options, () => {
|
ParseServer.startApp(options)
|
||||||
logOptions();
|
.then(() => {
|
||||||
console.log('');
|
logOptions();
|
||||||
printSuccessMessage();
|
console.log('');
|
||||||
});
|
printSuccessMessage();
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function printSuccessMessage() {
|
function printSuccessMessage() {
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import { ParseGraphQLServer } from './GraphQL/ParseGraphQLServer';
|
|||||||
// Factory function
|
// Factory function
|
||||||
const _ParseServer = function (options: ParseServerOptions) {
|
const _ParseServer = function (options: ParseServerOptions) {
|
||||||
const server = new ParseServer(options);
|
const server = new ParseServer(options);
|
||||||
return server.app;
|
return server;
|
||||||
};
|
};
|
||||||
// Mount the create liveQueryServer
|
// Mount the create liveQueryServer
|
||||||
_ParseServer.createLiveQueryServer = ParseServer.createLiveQueryServer;
|
_ParseServer.createLiveQueryServer = ParseServer.createLiveQueryServer;
|
||||||
_ParseServer.start = ParseServer.start;
|
_ParseServer.startApp = ParseServer.startApp;
|
||||||
|
|
||||||
const S3Adapter = useExternal('S3Adapter', '@parse/s3-files-adapter');
|
const S3Adapter = useExternal('S3Adapter', '@parse/s3-files-adapter');
|
||||||
const GCSAdapter = useExternal('GCSAdapter', '@parse/gcs-files-adapter');
|
const GCSAdapter = useExternal('GCSAdapter', '@parse/gcs-files-adapter');
|
||||||
|
|||||||
@@ -158,9 +158,18 @@ export function handleParseHeaders(req, res, next) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clientIp = getClientIp(req);
|
const clientIp = getClientIp(req);
|
||||||
|
const config = Config.get(info.appId, mount);
|
||||||
|
if (config.state && config.state !== 'ok') {
|
||||||
|
res.status(500);
|
||||||
|
res.json({
|
||||||
|
code: Parse.Error.INTERNAL_SERVER_ERROR,
|
||||||
|
error: `Invalid server state: ${config.state}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
info.app = AppCache.get(info.appId);
|
info.app = AppCache.get(info.appId);
|
||||||
req.config = Config.get(info.appId, mount);
|
req.config = config;
|
||||||
req.config.headers = req.headers || {};
|
req.config.headers = req.headers || {};
|
||||||
req.config.ip = clientIp;
|
req.config.ip = clientIp;
|
||||||
req.info = info;
|
req.info = info;
|
||||||
|
|||||||
Reference in New Issue
Block a user