parse-server
parse-server copied to clipboard
feat: Enable cross-trigger transactions via shared context
Transaction Support Across Parse Server Triggers
This modification extends Parse Server to support multi-operation transactions across beforeSave, afterSave, and other Cloud Code triggers using a shared transactional context.
✨ Features
- ✅ Create a transaction in
beforeSave - ✅ Reuse the same MongoDB/PostgreSQL session in nested
.save()calls - ✅ Preserve context across triggers (
beforeSave,afterSave, etc.) - ✅ Explicit control over
commitandaborttiming - ✅ Integration with Parse Server’s internal
RestWritelogic
🧠 Why
Out-of-the-box, Parse Server creates a new DatabaseController per internal operation, which leads to:
- Loss of transactional session context across chained
.save()s - Inability to group multiple object writes into a true transaction (with in trigger event)
This patch ensures the transaction session is persisted across triggers and reused consistently, enabling ACID-safe operations.
🛠 How It Works
1. Modify getRequestObject in triggers.js
Inject transactional helpers into the request.context:
request.context = Object.assign({
createTransactionalSession: config.database.createTransactionalSession.bind(config.database),
commitTransactionalSession: config.database.commitTransactionalSession.bind(config.database),
abortTransactionalSession: config.database.abortTransactionalSession.bind(config.database),
}, context);
2. Extend DatabaseController.js
Add support for:
// new method
setTransactionalSession(session) {
this._transactionalSession = session;
}
createTransactionalSession() {
return this.adapter.createTransactionalSession().then(session => {
this._transactionalSession = session;
return this._transactionalSession ; // add this line
});
}
commitTransactionalSession() {
// currently impl.
}
abortTransactionalSession() {
// currently impl.
}
3. Patch RestWrite.execute() in RestWrite.js
Apply the shared transaction session before executing write logic:
if (this.context.transaction) {
this.config.database.setTransactionalSession(this.context.transaction);
}
✅ Usage Example in Cloud Code
Parse.Cloud.beforeSave('TestObject', async (request) => {
const session = await request.context.createTransactionalSession();
const context = Object.assign(request.context, { transaction: session });
try {
const obj1 = new Parse.Object('Dependent_TestObject_1');
obj1.set('name', request.object.get('name'));
await obj1.save(null, { context });
} catch (err) {
await request.context.abortTransactionalSession();
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Transaction failed');
}
});
Parse.Cloud.afterSave('TestObject', async (request) => {
const { transaction, commitTransactionalSession, abortTransactionalSession } = request.context;
const context = { transaction };
try {
const obj2 = new Parse.Object('Dependent_TestObject_2');
obj2.set('name', request.object.get('name'));
await obj2.save(null, { context });
await commitTransactionalSession();
} catch (err) {
await abortTransactionalSession();
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'Transaction failed');
}
});
🧪 Behavior
context.transactionis injected into every.save()call- Nested triggers can access and reuse the transaction session
RestWriteensures internal DB calls are linked to the correct transaction- Final commit/abort logic is handled manually in the final trigger (usually
afterSave)
Summary by CodeRabbit
-
New Features
- Added support for transactional database sessions during write operations, enhancing data consistency and isolation.
- Exposed transactional session management methods within trigger contexts for greater control during trigger execution.
- Introduced automated Gitpod development environment setup for easier onboarding and development.
-
Bug Fixes
- Ensured transactional sessions are properly cleared after operations, preventing potential session leakage.
I will reformat the title to use the proper commit message syntax.
🚀 Thanks for opening this pull request!
📝 Walkthrough
Walkthrough
Adds a Gitpod config. DatabaseController gains setTransactionalSession and its createTransactionalSession now returns the session. RestWrite.execute sets/clears a transactional session when context.transaction exists. Trigger request contexts are extended with bound transactional session methods.
Changes
| Cohort / File(s) | Change Summary |
|---|---|
Gitpod config.gitpod.yml |
New file: configures Gitpod init task (npm install, npm run build) and start command (npm run start). |
Database controllersrc/Controllers/DatabaseController.js |
Added setTransactionalSession(transactionalSession) method; createTransactionalSession() now assigns and returns the transactional session instance. |
Rest write flowsrc/RestWrite.js |
RestWrite.prototype.execute() updated to call database.setTransactionalSession(context.transaction) when present and to clear it in a finally block. |
Trigger request contextsrc/triggers.js |
Trigger request objects now merge bound methods from config.database: createTransactionalSession, commitTransactionalSession, abortTransactionalSession into request.context. |
Sequence Diagram(s)
sequenceDiagram
participant Client
participant RestWrite
participant DatabaseController
Client->>RestWrite: execute(context)
alt context has transaction
RestWrite->>DatabaseController: setTransactionalSession(context.transaction)
end
RestWrite->>DatabaseController: perform write operations
alt context has transaction
RestWrite->>DatabaseController: setTransactionalSession(null) (finally)
end
sequenceDiagram
participant Trigger
participant getRequestObject
participant DatabaseController
Trigger->>getRequestObject: build request for trigger
getRequestObject->>DatabaseController: bind createTransactionalSession/commit/abort
getRequestObject->>Trigger: return request with extended request.context
Estimated code review effort
🎯 3 (Moderate) | ⏱️ ~20 minutes
Pre-merge checks and finishing touches
❌ Failed checks (2 warnings)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Description Check | ⚠️ Warning | While the PR description is substantive and detailed, providing comprehensive explanations of features, implementation approach, and usage examples, it does not follow the required template structure specified in the repository. The description is missing critical template sections: the "Issue" section with a "Closes:" link to reference the related GitHub issue, and the "Tasks" checklist that documents which validation activities (tests, documentation, security checks, etc.) have been completed. Although the content covers the "Approach" implicitly through the "How It Works" section, it lacks the formal structure and sections required by the template. | Reorganize the PR description to follow the repository template: add an "Issue" section at the top with "Closes: # |
| Docstring Coverage | ⚠️ Warning | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (1 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title Check | ✅ Passed | The pull request title "feat: Enable cross-trigger transactions via shared context" is concise, specific, and directly reflects the main objective of the changeset. It accurately summarizes the primary change from the developer's perspective—enabling multi-operation transactions that work across multiple Parse Server triggers by sharing transactional context. The title clearly distinguishes the main feature being introduced without being vague or off-topic. |
✨ Finishing touches
- [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
📜 Recent review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📥 Commits
Reviewing files that changed from the base of the PR and between c23c3648d159bc6f714b42114e94e6065dea896d and f6ada16a1344a9768c3b61c3c0bbb714455bbc55.
📒 Files selected for processing (2)
src/Controllers/DatabaseController.js(1 hunks)src/triggers.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/triggers.js
🔇 Additional comments (2)
src/Controllers/DatabaseController.js (2)
1689-1691: LGTM - Setter implementation is correct.The
setTransactionalSessionmethod provides the necessary external control for transaction lifecycle management as outlined in the PR objectives. The race condition concern raised in previous reviews has been properly addressed and withdrawn after confirming thatDatabaseControlleris instantiated per-request.
1696-1696: LGTM - Return statement enables direct session access.Returning the created transactional session allows callers to immediately use the session without an additional getter call, which aligns with the PR's goal to expose the session for cross-trigger transaction support.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
:white_check_mark: Snyk checks have passed. No issues have been found so far.
| Status | Scanner | Total (0) | ||||
|---|---|---|---|---|---|---|
| :white_check_mark: | Open Source Security | 0 | 0 | 0 | 0 | 0 issues |
:computer: Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.
#up