Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ jobs:
MONGODB_VERSION: 8.3.4
MONGODB_TOPOLOGY: replset
NODE_VERSION: 26.13.0
- name: MongoDB 9, ReplicaSet
MONGODB_VERSION: 9.0.2
MONGODB_TOPOLOGY: replset
NODE_VERSION: 26.13.0
- name: Redis Cache
PARSE_SERVER_TEST_CACHE: redis
MONGODB_VERSION: 8.0.4
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[![auto-release](https://img.shields.io/badge/%F0%9F%9A%80-auto--release-9e34eb.svg)](https://github.com/parse-community/parse-dashboard/releases)

[![Node Version](https://img.shields.io/badge/nodejs-20,_22,_24,_26-green.svg?logo=node.js&style=flat)](https://nodejs.org)
[![MongoDB Version](https://img.shields.io/badge/mongodb-7,_8-green.svg?logo=mongodb&style=flat)](https://www.mongodb.com)
[![MongoDB Version](https://img.shields.io/badge/mongodb-7,_8,_9-green.svg?logo=mongodb&style=flat)](https://www.mongodb.com)
[![Postgres Version](https://img.shields.io/badge/postgresql-16,_17,_18-green.svg?logo=postgresql&style=flat)](https://www.postgresql.org)

[![npm latest version](https://img.shields.io/npm/v/parse-server/latest.svg)](https://www.npmjs.com/package/parse-server)
Expand Down Expand Up @@ -152,6 +152,7 @@ Parse Server is continuously tested with the most recent releases of MongoDB to
| MongoDB 6 | 6.0.19 | July 2025 | <= 8.x (2025) |
| MongoDB 7 | 7.0.16 | August 2026 | <= 9.x (2026) |
| MongoDB 8 | 8.0.4 | TDB | <= 10.x (2027) |
| MongoDB 9 | 9.0.2 | TBD | <= 11.x (2028) |

#### PostgreSQL

Expand Down
110 changes: 26 additions & 84 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"lodash": "4.18.1",
"lru-cache": "11.5.2",
"mime": "4.1.0",
"mongodb": "7.1.0",
"mongodb": "7.7.0",
"mustache": "4.2.0",
"otpauth": "9.5.0",
"parse": "8.7.0",
Expand Down Expand Up @@ -122,6 +122,7 @@
"watch:ts": "tsc --watch",
"test:mongodb:7.0.16": "MONGODB_VERSION=7.0.16 npm run test",
"test:mongodb:8.0.4": "MONGODB_VERSION=8.0.4 npm run test",
"test:mongodb:9.0.2": "MONGODB_VERSION=9.0.2 npm run test",
"test:postgres:testonly": "cross-env PARSE_SERVER_TEST_DB=postgres PARSE_SERVER_TEST_DATABASE_URI=postgres://postgres:password@localhost:5432/parse_server_postgres_adapter_test_database npm run testonly",
"testonly": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=8.0.4} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine",
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=8.0.4} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner exec -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017 -- npm run testonly",
Expand Down
4 changes: 2 additions & 2 deletions spec/MongoCollection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe_only_db('mongo')('MongoCollection', () => {
expect(hasGeoIndex).toBe(true);
});

it_only_mongodb_version('>=8.3')('MongoDB 8.3+ reports the geoNear "no index" error without the field name', async () => {
it_only_mongodb_version('>=8.3 <9')('MongoDB >=8.3 <9 reports the geoNear "no index" error without the field name', async () => {
let error;
try {
await rawCollection.find(geoQuery).toArray();
Expand All @@ -86,7 +86,7 @@ describe_only_db('mongo')('MongoCollection', () => {
expect(error.message).not.toMatch(/field=/);
});

it_only_mongodb_version('<8.3')('older MongoDB reports the geoNear "no index" error with the field name', async () => {
it_only_mongodb_version('<8.3 || >=9')('MongoDB before 8.3 and 9+ report the geoNear "no index" error with the field name', async () => {
let error;
try {
await rawCollection.find(geoQuery).toArray();
Expand Down
44 changes: 17 additions & 27 deletions spec/MongoStorageAdapter.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,43 +71,33 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
});
});

it('find succeeds when query is within maxTimeMS', done => {
const maxTimeMS = 250;
// MongoDB 9 runs server-side JavaScript in a WASM engine with a startup overhead of a few
// hundred milliseconds, so the time limit leaves a wide margin around the sleep duration.
it('find succeeds when query is within maxTimeMS', async () => {
const maxTimeMS = 1000;
const adapter = new MongoStorageAdapter({
uri: databaseURI,
mongoOptions: { maxTimeMS },
});
adapter
.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
.then(() => adapter._rawFind('Foo', { $where: `sleep(${maxTimeMS / 2})` }))
.then(
() => done(),
err => {
done.fail(`maxTimeMS should not affect fast queries ${err}`);
}
);
await adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' });
await expectAsync(
adapter._rawFind('Foo', { $where: `sleep(${maxTimeMS / 4})` })
).toBeResolved();
});

it('find fails when query exceeds maxTimeMS', done => {
const maxTimeMS = 250;
// The error message differs between MongoDB versions, so only the error code is asserted.
it('find fails when query exceeds maxTimeMS', async () => {
const maxTimeMS = 1000;
const adapter = new MongoStorageAdapter({
uri: databaseURI,
mongoOptions: { maxTimeMS },
});
adapter
.createObject('Foo', { fields: {} }, { objectId: 'abcde' })
.then(() => adapter._rawFind('Foo', { $where: `sleep(${maxTimeMS * 2})` }))
.then(
() => {
done.fail('Find succeeded despite taking too long!');
},
err => {
expect(err.name).toEqual('MongoServerError');
expect(err.code).toEqual(50);
expect(err.message).toMatch('operation exceeded time limit');
done();
}
);
await adapter.createObject('Foo', { fields: {} }, { objectId: 'abcde' });
await expectAsync(
adapter._rawFind('Foo', { $where: `sleep(${maxTimeMS * 2})` })
).toBeRejectedWith(
jasmine.objectContaining({ name: 'MongoServerError', code: 50, codeName: 'MaxTimeMSExpired' })
);
});

it('passes batchSize to the MongoDB driver find() call', async () => {
Expand Down
8 changes: 4 additions & 4 deletions src/Adapters/Storage/Mongo/MongoCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const GEO_INDEX_QUERY_OPERATORS = ['$nearSphere', '$near', '$geoNear'];
// requiring a geospatial index. Returns the field name (e.g. 'location'), or
// undefined if none is found. Used as the reliable source of truth for on-demand
// geo index creation, since the MongoDB error message that used to carry the field
// name (`... field=<name> ...`) was dropped in MongoDB 8.3+.
// name (`... field=<name> ...`) is missing in MongoDB 8.3 to 8.x.
//
// A geo-near expression must be top-level or inside `$and`: MongoDB rejects it inside
// `$or` / `$nor` ("geo $near must be top-level expr") and forbids more than one per
Expand Down Expand Up @@ -94,9 +94,9 @@ export default class MongoCollection {
throw error;
}
// Figure out which field needs a geo index.
// Older MongoDB embeds the field name in the error message (`... field=<name> ...`);
// MongoDB 8.3+ shortened the message to `unable to find index for $geoNear query`
// and no longer includes it, so fall back to reading the field from the query itself.
// MongoDB embeds the field name in the error message (`... field=<name> ...`), except
// MongoDB 8.3 to 8.x, which shortened the message to `unable to find index for $geoNear query`,
// so fall back to reading the field from the query itself.
const messageMatch = error.message.match(/field=([A-Za-z_0-9]+) /);
const key = (messageMatch && messageMatch[1]) || findGeoIndexField(query);
if (!key) {
Expand Down