parse-server icon indicating copy to clipboard operation
parse-server copied to clipboard

feat: Enable cross-trigger transactions via shared context

Open Yumcoder-dev opened this issue 5 months ago • 4 comments
trafficstars

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 commit and abort timing
  • ✅ Integration with Parse Server’s internal RestWrite logic

🧠 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.transaction is injected into every .save() call
  • Nested triggers can access and reuse the transaction session
  • RestWrite ensures 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.

Yumcoder-dev avatar Jun 10 '25 22:06 Yumcoder-dev

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 controller
src/Controllers/DatabaseController.js
Added setTransactionalSession(transactionalSession) method; createTransactionalSession() now assigns and returns the transactional session instance.
Rest write flow
src/RestWrite.js
RestWrite.prototype.execute() updated to call database.setTransactionalSession(context.transaction) when present and to clear it in a finally block.
Trigger request context
src/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: #" to link this PR to the related GitHub issue, include an "Approach" section with the high-level description of changes, and add the "Tasks" checklist indicating which activities have been completed (tests, documentation, security checks, etc.). The current detailed content can be preserved as supplementary explanation but should be structured within the template's required sections.
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 setTransactionalSession method 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 that DatabaseController is 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot] avatar Jun 10 '25 22:06 coderabbitai[bot]

:white_check_mark: Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low 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.

parseplatformorg avatar Jun 10 '25 22:06 parseplatformorg

#up

Yumcoder-dev avatar Oct 17 '25 12:10 Yumcoder-dev