Compare commits
8 Commits
9.2.0-alph
...
9.2.0-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2babb2ac4 | ||
|
|
9833fdb111 | ||
|
|
dc866bed3b | ||
|
|
906ccc3e29 | ||
|
|
5c00a6ab1b | ||
|
|
5d28fcba0c | ||
|
|
db3cbb2113 | ||
|
|
1d3336d128 |
@@ -1,3 +1,17 @@
|
||||
# [9.2.0-alpha.3](https://github.com/parse-community/parse-server/compare/9.2.0-alpha.2...9.2.0-alpha.3) (2026-01-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Upgrade to parse 8.0.3 and @parse/push-adapter 8.2.0 ([#10021](https://github.com/parse-community/parse-server/issues/10021)) ([9833fdb](https://github.com/parse-community/parse-server/commit/9833fdb111c373dc75fc74ea5f9209408186a475))
|
||||
|
||||
# [9.2.0-alpha.2](https://github.com/parse-community/parse-server/compare/9.2.0-alpha.1...9.2.0-alpha.2) (2026-01-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* MongoDB timeout errors unhandled and potentially revealing internal data ([#10020](https://github.com/parse-community/parse-server/issues/10020)) ([1d3336d](https://github.com/parse-community/parse-server/commit/1d3336d128671c974b419b9b34db35ada7d1a44d))
|
||||
|
||||
# [9.2.0-alpha.1](https://github.com/parse-community/parse-server/compare/9.1.1...9.2.0-alpha.1) (2026-01-24)
|
||||
|
||||
|
||||
|
||||
1676
package-lock.json
generated
1676
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "9.2.0-alpha.1",
|
||||
"version": "9.2.0-alpha.3",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -26,7 +26,7 @@
|
||||
"@graphql-tools/schema": "10.0.23",
|
||||
"@graphql-tools/utils": "10.8.6",
|
||||
"@parse/fs-files-adapter": "3.0.0",
|
||||
"@parse/push-adapter": "8.1.0",
|
||||
"@parse/push-adapter": "8.2.0",
|
||||
"bcryptjs": "3.0.2",
|
||||
"commander": "14.0.2",
|
||||
"cors": "2.8.5",
|
||||
@@ -42,13 +42,13 @@
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"jwks-rsa": "3.2.0",
|
||||
"ldapjs": "3.0.7",
|
||||
"lodash": "4.17.21",
|
||||
"lodash": "4.17.23",
|
||||
"lru-cache": "10.4.0",
|
||||
"mime": "4.0.7",
|
||||
"mongodb": "6.20.0",
|
||||
"mustache": "4.2.0",
|
||||
"otpauth": "9.4.0",
|
||||
"parse": "8.0.0",
|
||||
"parse": "8.0.3",
|
||||
"path-to-regexp": "8.3.0",
|
||||
"pg-monitor": "3.0.0",
|
||||
"pg-promise": "12.2.0",
|
||||
@@ -68,7 +68,7 @@
|
||||
"@actions/core": "1.11.1",
|
||||
"@apollo/client": "3.13.8",
|
||||
"@babel/cli": "7.27.0",
|
||||
"@babel/core": "7.27.4",
|
||||
"@babel/core": "7.28.6",
|
||||
"@babel/eslint-parser": "7.28.0",
|
||||
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
|
||||
"@babel/plugin-transform-flow-strip-types": "7.26.5",
|
||||
@@ -108,7 +108,7 @@
|
||||
"prettier": "2.0.5",
|
||||
"semantic-release": "24.2.5",
|
||||
"typescript": "5.8.3",
|
||||
"typescript-eslint": "8.33.1",
|
||||
"typescript-eslint": "8.53.1",
|
||||
"yaml": "2.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
|
||||
@@ -1064,6 +1064,129 @@ describe_only_db('mongo')('MongoStorageAdapter', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('transient error handling', () => {
|
||||
it('should transform MongoWaitQueueTimeoutError to Parse.Error.INTERNAL_SERVER_ERROR', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
await adapter.connect();
|
||||
|
||||
// Create a mock error with the MongoWaitQueueTimeoutError name
|
||||
const mockError = new Error('Timed out while checking out a connection from connection pool');
|
||||
mockError.name = 'MongoWaitQueueTimeoutError';
|
||||
|
||||
try {
|
||||
adapter.handleError(mockError);
|
||||
fail('Expected handleError to throw');
|
||||
} catch (error) {
|
||||
expect(error instanceof Parse.Error).toBe(true);
|
||||
expect(error.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR);
|
||||
expect(error.message).toBe('Database error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should transform MongoServerSelectionError to Parse.Error.INTERNAL_SERVER_ERROR', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
await adapter.connect();
|
||||
|
||||
const mockError = new Error('Server selection timed out');
|
||||
mockError.name = 'MongoServerSelectionError';
|
||||
|
||||
try {
|
||||
adapter.handleError(mockError);
|
||||
fail('Expected handleError to throw');
|
||||
} catch (error) {
|
||||
expect(error instanceof Parse.Error).toBe(true);
|
||||
expect(error.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR);
|
||||
expect(error.message).toBe('Database error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should transform MongoNetworkTimeoutError to Parse.Error.INTERNAL_SERVER_ERROR', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
await adapter.connect();
|
||||
|
||||
const mockError = new Error('Network timeout');
|
||||
mockError.name = 'MongoNetworkTimeoutError';
|
||||
|
||||
try {
|
||||
adapter.handleError(mockError);
|
||||
fail('Expected handleError to throw');
|
||||
} catch (error) {
|
||||
expect(error instanceof Parse.Error).toBe(true);
|
||||
expect(error.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR);
|
||||
expect(error.message).toBe('Database error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should transform MongoNetworkError to Parse.Error.INTERNAL_SERVER_ERROR', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
await adapter.connect();
|
||||
|
||||
const mockError = new Error('Network error');
|
||||
mockError.name = 'MongoNetworkError';
|
||||
|
||||
try {
|
||||
adapter.handleError(mockError);
|
||||
fail('Expected handleError to throw');
|
||||
} catch (error) {
|
||||
expect(error instanceof Parse.Error).toBe(true);
|
||||
expect(error.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR);
|
||||
expect(error.message).toBe('Database error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should transform TransientTransactionError to Parse.Error.INTERNAL_SERVER_ERROR', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
await adapter.connect();
|
||||
|
||||
const mockError = new Error('Transient transaction error');
|
||||
mockError.hasErrorLabel = label => label === 'TransientTransactionError';
|
||||
|
||||
try {
|
||||
adapter.handleError(mockError);
|
||||
fail('Expected handleError to throw');
|
||||
} catch (error) {
|
||||
expect(error instanceof Parse.Error).toBe(true);
|
||||
expect(error.code).toBe(Parse.Error.INTERNAL_SERVER_ERROR);
|
||||
expect(error.message).toBe('Database error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should not transform non-transient errors', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
await adapter.connect();
|
||||
|
||||
const mockError = new Error('Some other error');
|
||||
mockError.name = 'SomeOtherError';
|
||||
|
||||
try {
|
||||
adapter.handleError(mockError);
|
||||
fail('Expected handleError to throw');
|
||||
} catch (error) {
|
||||
expect(error instanceof Parse.Error).toBe(false);
|
||||
expect(error.message).toBe('Some other error');
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle null/undefined errors', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
await adapter.connect();
|
||||
|
||||
try {
|
||||
adapter.handleError(null);
|
||||
fail('Expected handleError to throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeNull();
|
||||
}
|
||||
|
||||
try {
|
||||
adapter.handleError(undefined);
|
||||
fail('Expected handleError to throw');
|
||||
} catch (error) {
|
||||
expect(error).toBeUndefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('MongoDB Client Metadata', () => {
|
||||
it('should not pass metadata to MongoClient by default', async () => {
|
||||
const adapter = new MongoStorageAdapter({ uri: databaseURI });
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('Server Url Checks', () => {
|
||||
parseServerProcess.on('close', async code => {
|
||||
expect(code).toEqual(1);
|
||||
expect(stdout).not.toContain('UnhandledPromiseRejectionWarning');
|
||||
expect(stderr).toContain('MongoServerSelectionError');
|
||||
expect(stderr).toContain('Database error');
|
||||
await reconfigureServer();
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -73,7 +73,7 @@ describe('server', () => {
|
||||
}),
|
||||
});
|
||||
const error = await server.start().catch(e => e);
|
||||
expect(`${error}`.includes('MongoServerSelectionError')).toBeTrue();
|
||||
expect(`${error}`.includes('Database error')).toBeTrue();
|
||||
await reconfigureServer();
|
||||
});
|
||||
|
||||
|
||||
@@ -27,6 +27,36 @@ const ReadPreference = mongodb.ReadPreference;
|
||||
|
||||
const MongoSchemaCollectionName = '_SCHEMA';
|
||||
|
||||
/**
|
||||
* Determines if a MongoDB error is a transient infrastructure error
|
||||
* (connection pool, network, server selection) as opposed to a query-level error.
|
||||
*/
|
||||
function isTransientError(error) {
|
||||
if (!error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Connection pool, network, and server selection errors
|
||||
const transientErrorNames = [
|
||||
'MongoWaitQueueTimeoutError',
|
||||
'MongoServerSelectionError',
|
||||
'MongoNetworkTimeoutError',
|
||||
'MongoNetworkError',
|
||||
];
|
||||
if (transientErrorNames.includes(error.name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for MongoDB's transient transaction error label
|
||||
if (typeof error.hasErrorLabel === 'function') {
|
||||
if (error.hasErrorLabel('TransientTransactionError')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const storageAdapterAllCollections = mongoAdapter => {
|
||||
return mongoAdapter
|
||||
.connect()
|
||||
@@ -252,6 +282,13 @@ export class MongoStorageAdapter implements StorageAdapter {
|
||||
delete this.connectionPromise;
|
||||
logger.error('Received unauthorized error', { error: error });
|
||||
}
|
||||
|
||||
// Transform infrastructure/transient errors into Parse.Error.INTERNAL_SERVER_ERROR
|
||||
if (isTransientError(error)) {
|
||||
logger.error('Database transient error', error);
|
||||
throw new Parse.Error(Parse.Error.INTERNAL_SERVER_ERROR, 'Database error');
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
@@ -378,9 +378,9 @@ export const handleParseSession = async (req, res, next) => {
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
// TODO: Determine the correct error scenario.
|
||||
// Log full error details internally, but don't expose to client
|
||||
req.config.loggerController.error('error getting auth for sessionToken', error);
|
||||
throw new Parse.Error(Parse.Error.UNKNOWN_ERROR, error);
|
||||
next(new Parse.Error(Parse.Error.UNKNOWN_ERROR, 'Unknown error'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user