Compare commits

...
16 Commits
Author SHA1 Message Date
semantic-release-bot 315e157637 chore(release): 9.10.1-alpha.6 [skip ci]
## [9.10.1-alpha.6](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.5...9.10.1-alpha.6) (2026-07-26)

### Bug Fixes

* `Parse.Query.explain` runs afterFind trigger on query plan results ([#10536](https://github.com/parse-community/parse-server/issues/10536)) ([64d58ff](https://github.com/parse-community/parse-server/commit/64d58ff726b42b45e107e27bc1f3b50be82b3189))
2026-07-26 00:55:49 +00:00
Daniel 64d58ff726 fix: Parse.Query.explain runs afterFind trigger on query plan results (#10536) 2026-07-26 02:55:02 +02:00
Manuel 9e4e5dbbc9 ci: Skip CI checks for draft pull requests (#10610) 2026-07-25 15:42:05 +02:00
semantic-release-bot 472136c5ca chore(release): 9.10.1-alpha.5 [skip ci]
## [9.10.1-alpha.5](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.4...9.10.1-alpha.5) (2026-07-25)

### Bug Fixes

* Server crash from unhandled promise rejection when multiple Cloud Code validator fields fail ([#10540](https://github.com/parse-community/parse-server/issues/10540)) ([90c2778](https://github.com/parse-community/parse-server/commit/90c277894f31be8e64a4b049ad503621cf5f2285))
2026-07-25 01:40:12 +00:00
Daniel 90c277894f fix: Server crash from unhandled promise rejection when multiple Cloud Code validator fields fail (#10540) 2026-07-25 03:39:21 +02:00
semantic-release-bot 99a0471aaf chore(release): 9.10.1-alpha.4 [skip ci]
## [9.10.1-alpha.4](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.3...9.10.1-alpha.4) (2026-07-24)

### Bug Fixes

* Install the latest Parse Server version in bootstrap.sh ([#10556](https://github.com/parse-community/parse-server/issues/10556)) ([997ee15](https://github.com/parse-community/parse-server/commit/997ee152c358524bd2b0dded490255742b61a1ba))
2026-07-24 14:24:35 +00:00
Daniel 997ee152c3 fix: Install the latest Parse Server version in bootstrap.sh (#10556) 2026-07-24 16:23:44 +02:00
Daniel 81bdeb8697 refactor: Correct under-reported code coverage for Options/parsers (#10559) 2026-07-24 15:50:39 +02:00
Daniel 5838c07acc ci: Exclude build tooling (resources/) from coverage (#10561) 2026-07-21 01:47:03 +02:00
Daniel 6f3e07ca80 docs: Clarify LiveQuery options in --help output (#10558) 2026-07-21 01:26:37 +02:00
Manuel b9912b0bb5 refactor: Bump @actions/core from 3.0.0 to 3.0.1 (#10593) 2026-07-21 00:45:18 +02:00
Manuel 154e1d48bd refactor: Bump mongodb-runner from 5.9.3 to 6.8.3 (#10591) 2026-07-21 00:19:12 +02:00
Manuel 34c8f759c9 refactor: Bump @babel/preset-env from 7.29.2 to 7.29.7 (#10592) 2026-07-20 23:49:54 +02:00
dependabot[bot] b45f7ea3bf refactor: Bump websocket-driver from 0.7.4 to 0.7.5 (#10583) 2026-07-20 14:05:03 +02:00
Manuel 71e65572cb refactor: Bump @babel/core from 7.29.6 to 7.29.7 (#10588) 2026-07-20 02:52:22 +02:00
Manuel 0ebd93569e refactor: Bump @semantic-release/release-notes-generator from 14.1.0 to 14.1.1 (#10587) 2026-07-19 16:54:16 +02:00
14 changed files with 2021 additions and 1442 deletions
+12
View File
@@ -3,6 +3,7 @@ on:
push:
branches: [release, alpha, beta, next-major, 'release-[0-9]+.x.x']
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches:
- '**'
paths-ignore:
@@ -15,6 +16,7 @@ permissions:
jobs:
check-code-analysis:
name: Code Analysis
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
runs-on: ubuntu-latest
permissions:
actions: read
@@ -36,6 +38,7 @@ jobs:
uses: github/codeql-action/analyze@v2
check-ci:
name: Node Engine Check
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
@@ -52,6 +55,7 @@ jobs:
run: npm run ci:checkNodeEngine
check-lint:
name: Lint
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
@@ -72,6 +76,7 @@ jobs:
- run: npm run lint
check-definitions:
name: Check Definitions
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
@@ -93,6 +98,7 @@ jobs:
run: npm run ci:definitionsCheck
check-circular:
name: Circular Dependencies
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
@@ -113,6 +119,7 @@ jobs:
- run: npm run madge:circular
check-docs:
name: Docs
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
@@ -136,6 +143,7 @@ jobs:
run: npm run docs
check-docker:
name: Docker Build
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 15
runs-on: ubuntu-latest
steps:
@@ -153,6 +161,7 @@ jobs:
platforms: linux/amd64, linux/arm64/v8
check-lock-file-version:
name: NPM Lock File Version
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
@@ -166,6 +175,7 @@ jobs:
fi
check-types:
name: Check Types
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
@@ -206,6 +216,7 @@ jobs:
NODE_VERSION: 22.12.0
fail-fast: false
name: ${{ matrix.name }}
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 20
runs-on: ubuntu-latest
services:
@@ -260,6 +271,7 @@ jobs:
NODE_VERSION: 24.11.0
fail-fast: false
name: ${{ matrix.name }}
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }}
timeout-minutes: 20
runs-on: ubuntu-latest
services:
+2 -1
View File
@@ -4,7 +4,8 @@
"text-summary"
],
"exclude": [
"**/spec/**"
"**/spec/**",
"resources/**"
]
}
+2 -4
View File
@@ -162,9 +162,7 @@ cat > ./package.json << EOF
"scripts": {
"start": "parse-server config.json"
},
"dependencies": {
"parse-server": "^3.9.0"
}
"dependencies": {}
}
EOF
echo "${CHECK} Created package.json"
@@ -195,7 +193,7 @@ fi
echo "\n${CHECK} running npm install\n"
npm install -s
npm install parse-server -s
CURL_CMD=$(cat << EOF
curl -X POST -H 'X-Parse-Application-Id: ${APP_ID}' \\
+21
View File
@@ -1,3 +1,24 @@
## [9.10.1-alpha.6](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.5...9.10.1-alpha.6) (2026-07-26)
### Bug Fixes
* `Parse.Query.explain` runs afterFind trigger on query plan results ([#10536](https://github.com/parse-community/parse-server/issues/10536)) ([64d58ff](https://github.com/parse-community/parse-server/commit/64d58ff726b42b45e107e27bc1f3b50be82b3189))
## [9.10.1-alpha.5](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.4...9.10.1-alpha.5) (2026-07-25)
### Bug Fixes
* Server crash from unhandled promise rejection when multiple Cloud Code validator fields fail ([#10540](https://github.com/parse-community/parse-server/issues/10540)) ([90c2778](https://github.com/parse-community/parse-server/commit/90c277894f31be8e64a4b049ad503621cf5f2285))
## [9.10.1-alpha.4](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.3...9.10.1-alpha.4) (2026-07-24)
### Bug Fixes
* Install the latest Parse Server version in bootstrap.sh ([#10556](https://github.com/parse-community/parse-server/issues/10556)) ([997ee15](https://github.com/parse-community/parse-server/commit/997ee152c358524bd2b0dded490255742b61a1ba))
## [9.10.1-alpha.3](https://github.com/parse-community/parse-server/compare/9.10.1-alpha.2...9.10.1-alpha.3) (2026-07-16)
+1925 -1416
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "9.10.1-alpha.3",
"version": "9.10.1-alpha.6",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
@@ -63,14 +63,14 @@
"ws": "8.21.0"
},
"devDependencies": {
"@actions/core": "3.0.0",
"@actions/core": "3.0.1",
"@apollo/client": "3.13.8",
"@babel/cli": "7.28.6",
"@babel/core": "7.29.6",
"@babel/core": "7.29.7",
"@babel/eslint-parser": "7.28.6",
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
"@babel/plugin-transform-flow-strip-types": "7.27.1",
"@babel/preset-env": "7.29.2",
"@babel/preset-env": "7.29.7",
"@babel/preset-typescript": "7.27.1",
"@saithodev/semantic-release-backmerge": "4.0.1",
"@semantic-release/changelog": "6.0.3",
@@ -78,7 +78,7 @@
"@semantic-release/git": "10.0.1",
"@semantic-release/github": "12.0.6",
"@semantic-release/npm": "13.0.0",
"@semantic-release/release-notes-generator": "14.1.0",
"@semantic-release/release-notes-generator": "14.1.1",
"all-node-versions": "13.0.1",
"apollo-upload-client": "18.0.1",
"clean-jsdoc-theme": "4.3.0",
@@ -99,7 +99,7 @@
"madge": "8.0.0",
"mock-files-adapter": "file:spec/dependencies/mock-files-adapter",
"mock-mail-adapter": "file:spec/dependencies/mock-mail-adapter",
"mongodb-runner": "5.9.3",
"mongodb-runner": "6.8.3",
"node-abort-controller": "3.1.1",
"node-fetch": "3.3.2",
"nyc": "17.1.0",
+2 -1
View File
@@ -9,7 +9,6 @@
* To rebuild the definitions file, run
* `$ node resources/buildConfigDefinitions.js`
*/
const parsers = require('../src/Options/parsers');
/** The types of nested options. */
const nestedOptionTypes = [
@@ -190,6 +189,8 @@ function mapperFor(elt, t) {
}
function parseDefaultValue(elt, value, t) {
/* istanbul ignore next: lazy require (not module scope) so specs don't double-instrument parsers.js; only reached by `npm run definitions` */
const parsers = require('../src/Options/parsers');
let literalValue;
if (t.isStringTypeAnnotation(elt)) {
if (value == '""' || value == "''") {
+21
View File
@@ -504,6 +504,27 @@ describe('cloud validator', () => {
});
});
it('does not leave an unhandled rejection when multiple fields fail validation (#8826)', async () => {
const rejections = [];
const onUnhandledRejection = reason => rejections.push(reason);
process.on('unhandledRejection', onUnhandledRejection);
try {
Parse.Cloud.define('hello', () => 'Hello world!', {
fields: {
type: { type: String, options: ['Option A', 'Option B'] },
project: { required: true },
},
});
await expectAsync(Parse.Cloud.run('hello', { type: 'Invalid' })).toBeRejectedWith(
jasmine.objectContaining({ code: Parse.Error.VALIDATION_ERROR })
);
await new Promise(resolve => setTimeout(resolve, 100));
expect(rejections).toEqual([]);
} finally {
process.removeListener('unhandledRejection', onUnhandledRejection);
}
});
it('set params options function', done => {
Parse.Cloud.define(
'hello',
+16
View File
@@ -5375,6 +5375,22 @@ describe('Parse.Query testing', () => {
expect(result.executionStats).not.toBeUndefined();
});
it_only_db('mongo')('does not run afterFind on explain results', async () => {
let afterFindCalled = false;
Parse.Cloud.afterFind('AfterFindExplain', () => {
afterFindCalled = true;
return []; // empty return would drop the explain plan if the trigger ran
});
const obj = new Parse.Object('AfterFindExplain');
await obj.save();
const query = new Parse.Query('AfterFindExplain');
query.equalTo('objectId', obj.id);
query.explain();
const result = await query.find({ useMasterKey: true });
expect(result.executionStats).not.toBeUndefined(); // plan passed through untouched
expect(afterFindCalled).toBe(false); // afterFind skipped for explain
});
it('should query with distinct within eachBatch and direct access enabled', async () => {
await reconfigureServer({
directAccess: true,
+3 -3
View File
@@ -346,13 +346,13 @@ module.exports.ParseServerOptions = {
},
liveQuery: {
env: 'PARSE_SERVER_LIVE_QUERY',
help: "parse-server's LiveQuery configuration object",
help: "Configuration for LiveQuery on this Parse Server, for example `{ classNames: ['MyClass'] }`. `classNames` lists the classes that publish create/update/delete events to subscribers; without it no events are pushed, even while a LiveQuery server is running. Combine with `startLiveQueryServer` to run a LiveQuery server.",
action: parsers.objectParser,
type: 'LiveQueryOptions',
},
liveQueryServerOptions: {
env: 'PARSE_SERVER_LIVE_QUERY_SERVER_OPTIONS',
help: 'Live query server configuration options (will start the liveQuery server)',
help: 'Configuration options for the LiveQuery server. Providing this also starts the LiveQuery server (like `startLiveQueryServer`); events are still only published for the classes set in `liveQuery.classNames`.',
action: parsers.objectParser,
type: 'LiveQueryServerOptions',
},
@@ -639,7 +639,7 @@ module.exports.ParseServerOptions = {
},
startLiveQueryServer: {
env: 'PARSE_SERVER_START_LIVE_QUERY_SERVER',
help: 'Starts the liveQuery server',
help: 'Starts a LiveQuery server alongside this Parse Server. Events are only delivered for the classes set in `liveQuery.classNames`, so a minimal working setup is `liveQuery: { classNames: [...] }` together with `startLiveQueryServer: true`.',
action: parsers.booleanParser,
},
trustProxy: {
+3 -3
View File
@@ -65,8 +65,8 @@
* @property {InstallationOptions} installation Options controlling how Parse Server deduplicates `_Installation` records that share the same `deviceToken`.
* @property {String} javascriptKey Key for the Javascript SDK
* @property {Boolean} jsonLogs Log as structured JSON objects
* @property {LiveQueryOptions} liveQuery parse-server's LiveQuery configuration object
* @property {LiveQueryServerOptions} liveQueryServerOptions Live query server configuration options (will start the liveQuery server)
* @property {LiveQueryOptions} liveQuery Configuration for LiveQuery on this Parse Server, for example `{ classNames: ['MyClass'] }`. `classNames` lists the classes that publish create/update/delete events to subscribers; without it no events are pushed, even while a LiveQuery server is running. Combine with `startLiveQueryServer` to run a LiveQuery server.
* @property {LiveQueryServerOptions} liveQueryServerOptions Configuration options for the LiveQuery server. Providing this also starts the LiveQuery server (like `startLiveQueryServer`); events are still only published for the classes set in `liveQuery.classNames`.
* @property {Adapter<LoggerAdapter>} loggerAdapter Adapter module for the logging sub-system
* @property {String} logLevel Sets the level for logs
* @property {LogLevels} logLevels (Optional) Overrides the log levels used internally by Parse Server to log events.
@@ -115,7 +115,7 @@
* @property {String} serverURL The URL to Parse Server.<br><br>⚠️ Certain server features or adapters may require Parse Server to be able to call itself by making requests to the URL set in `serverURL`. If a feature requires this, it is mentioned in the documentation. In that case ensure that the URL is accessible from the server itself.
* @property {Number} sessionLength Session duration, in seconds, defaults to 1 year
* @property {Boolean} silent Disables console output
* @property {Boolean} startLiveQueryServer Starts the liveQuery server
* @property {Boolean} startLiveQueryServer Starts a LiveQuery server alongside this Parse Server. Events are only delivered for the classes set in `liveQuery.classNames`, so a minimal working setup is `liveQuery: { classNames: [...] }` together with `startLiveQueryServer: true`.
* @property {Any} trustProxy The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the <a href="https://expressjs.com/en/guide/behind-proxies.html">express trust proxy settings</a> documentation. Defaults to `false`.
* @property {String[]} userSensitiveFields Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields
* @property {Boolean} verbose Set the logging to verbose
+3 -3
View File
@@ -288,7 +288,7 @@ export interface ParseServerOptions {
/* custom pages for password validation and reset
:DEFAULT: {} */
customPages: ?CustomPagesOptions;
/* parse-server's LiveQuery configuration object */
/* Configuration for LiveQuery on this Parse Server, for example `{ classNames: ['MyClass'] }`. `classNames` lists the classes that publish create/update/delete events to subscribers; without it no events are pushed, even while a LiveQuery server is running. Combine with `startLiveQueryServer` to run a LiveQuery server. */
liveQuery: ?LiveQueryOptions;
/* Session duration, in seconds, defaults to 1 year
:DEFAULT: 31536000 */
@@ -347,9 +347,9 @@ export interface ParseServerOptions {
/* The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the <a href="https://expressjs.com/en/guide/behind-proxies.html">express trust proxy settings</a> documentation. Defaults to `false`.
:DEFAULT: false */
trustProxy: ?any;
/* Starts the liveQuery server */
/* Starts a LiveQuery server alongside this Parse Server. Events are only delivered for the classes set in `liveQuery.classNames`, so a minimal working setup is `liveQuery: { classNames: [...] }` together with `startLiveQueryServer: true`. */
startLiveQueryServer: ?boolean;
/* Live query server configuration options (will start the liveQuery server) */
/* Configuration options for the LiveQuery server. Providing this also starts the LiveQuery server (like `startLiveQueryServer`); events are still only published for the classes set in `liveQuery.classNames`. */
liveQueryServerOptions: ?LiveQueryServerOptions;
/* 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.
:ENV: PARSE_SERVER_EXPERIMENTAL_IDEMPOTENCY_OPTIONS
+2 -2
View File
@@ -1121,8 +1121,8 @@ _UnsafeRestQuery.prototype.runAfterFindTrigger = function () {
if (!hasAfterFindHook) {
return Promise.resolve();
}
// Skip Aggregate and Distinct Queries
if (this.findOptions.pipeline || this.findOptions.distinct) {
// Skip Aggregate, Distinct and Explain Queries
if (this.findOptions.pipeline || this.findOptions.distinct || this.findOptions.explain) {
return Promise.resolve();
}
+3 -3
View File
@@ -823,7 +823,7 @@ async function builtInTriggerValidator(options, request, auth) {
requiredParam(key);
}
} else {
const optionPromises = [];
const optionValidations = [];
for (const key in options.fields) {
const opt = options.fields[key];
let val = params[key];
@@ -858,12 +858,12 @@ async function builtInTriggerValidator(options, request, auth) {
}
}
if (opt.options) {
optionPromises.push(validateOptions(opt, key, val));
optionValidations.push([opt, key, val]);
}
}
}
}
await Promise.all(optionPromises);
await Promise.all(optionValidations.map(([o, k, v]) => validateOptions(o, k, v)));
}
let userRoles = options.requireAnyUserRoles;
let requireAllRoles = options.requireAllUserRoles;