Skip to content

Add SDK Adapter#10256

Open
dblythy wants to merge 4 commits intoparse-community:alphafrom
dblythy:feature/sdk-adapter
Open

Add SDK Adapter#10256
dblythy wants to merge 4 commits intoparse-community:alphafrom
dblythy:feature/sdk-adapter

Conversation

@dblythy
Copy link
Member

@dblythy dblythy commented Mar 20, 2026

Summary

  • Add SDK adapter

Test plan

  • Verify SDK adapter functionality works as expected

Summary by CodeRabbit

  • New Features

    • New dedicated cloud SDK import path and package export entries for cleaner imports.
    • Cloud Code runs per-server instance (no global SDK mutation), improving isolation.
    • New cloud SDK surface with typed handler APIs and a central in-memory trigger store for functions, jobs, triggers, live-query handlers, and validators.
    • Added a Legacy Cloud adapter exposing familiar cloud registration APIs.
  • Documentation

    • Added Cloud Code SDK Adapter guide with usage examples and handler contracts.

@parse-github-assistant
Copy link

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

Tip

  • Keep pull requests small. Large PRs will be rejected. Break complex features into smaller, incremental PRs.
  • Use Test Driven Development. Write failing tests before implementing functionality. Ensure tests pass.
  • Group code into logical blocks. Add a short comment before each block to explain its purpose.
  • We offer conceptual guidance. Coding is up to you. PRs must be merge-ready for human review.
  • Our review focuses on concept, not quality. PRs with code issues will be rejected. Use an AI agent.
  • Human review time is precious. Avoid review ping-pong. Inspect and test your AI-generated code.

Note

Please respond to review comments from AI agents just like you would to comments from a human reviewer. Let the reviewer resolve their own comments, unless they have reviewed and accepted your commit, or agreed with your explanation for why the feedback was incorrect.

Caution

Pull requests must be written using an AI agent with human supervision. Pull requests written entirely by a human will likely be rejected, because of lower code quality, higher review effort and the higher risk of introducing bugs. Please note that AI review comments on this pull request alone do not satisfy this requirement.

@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

Refactors cloud code from a global Parse mutation to per-ParseServer instances: adds LegacyCloud, a centralized TriggerStore, new TypeScript cloud types and a parse-server/cloud entry, and removes the old Parse.Cloud.js implementation.

Changes

Cohort / File(s) Summary
Package configuration
package.json
Adds an exports map exposing "."./lib/index.js and "./cloud"{ "types": "./types/cloud.d.ts", "default": "./lib/cloud.js" }.
ParseServer integration
src/ParseServer.ts
Removes global addParseCloud()/global.Parse mutation; constructs this.legacyCloud, uses this.legacyCloud.Parse during start, and calls this.legacyCloud.bindToParseCloud() when cloud is enabled.
Legacy Cloud implementation
src/cloud-code/LegacyCloud.ts
New LegacyCloud class initializing parse/node Parse, exposing Parse.Cloud-style APIs (functions, jobs, triggers, auth hooks, live query hooks), validator and rate-limit wiring, bindToParseCloud() and cloudSendEmail. Review validation and rate-limit logic.
Trigger storage / API surface
src/cloud-code/TriggerStore.ts, src/triggers.js
New per-appId in-memory TriggerStore managing functions, jobs, triggers, validators, liveQuery handlers; triggers.js now delegates to TriggerStore. Check name-validation behavior and removal of duplicate-warning logic.
Cloud SDK types & entrypoint
src/cloud-code/types.ts, src/cloud.ts
Adds SDK-agnostic TypeScript request/handler types and re-exports TriggerStore, TriggerType, HookType and types via parse-server/cloud.
Trigger validation helper
src/cloud-code/validateTrigger.ts
Adds class-name vs trigger-type enforcement function (throws on invalid combinations).
Removed legacy module
src/cloud-code/Parse.Cloud.js
Removed legacy Parse.Cloud implementation; functionality migrated to LegacyCloud and TriggerStore.
Docs
src/cloud-code/README.md
New documentation describing Cloud Code SDK Adapter pattern and TriggerStore surface. Review examples for API expectations.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Dev as Cloud code author
participant PS as ParseServer
participant LC as LegacyCloud
participant Parse as parse/node(Parse)
participant TS as TriggerStore

Dev->>PS: start(ParseServer with cloud option)
PS->>LC: new LegacyCloud() / initialize(config)
LC->>Parse: require('parse/node') and Parse.initialize(...)
LC->>LC: set Parse.serverURL, store appId
LC->>PS: return legacyCloud.Parse
PS->>LC: bindToParseCloud()
LC->>Parse: assign Parse.Cloud methods (define, beforeSave, job, etc.)
Note right of LC: Parse.Cloud methods proxy to TriggerStore
Parse->>TS: addFunction/addTrigger/addJob(...)
Dev->>Parse: register cloud handlers (define/beforeSave/job)
Parse->>TS: handlers stored per appId
Runtime->>TS: trigger lookup & invocation (on request / job / live query)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • mtrezza
  • Moumouls
🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete and does not follow the provided template structure; it lacks Issue, Approach, and Tasks sections. Complete the description by adding Issue (linked issue or problem statement), Approach (detailed explanation of changes), and Tasks sections as specified in the template.
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Add SDK Adapter' is vague and does not clearly convey what specific functionality or component is being added. Use a more descriptive title that specifies what the SDK adapter does, e.g., 'Add Cloud Code SDK adapter pattern' or 'Add LegacyCloud class for cloud code integration'.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Mar 20, 2026

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

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Around line 6-9: The "./cloud" export in package.json is missing a types
mapping so TypeScript can't find declarations; update the package.json exports
entry for "./cloud" (the "./cloud": "./lib/cloud.js" mapping) to include a types
field pointing to the declaration file (for example "types":
"./types/cloud.d.ts"), or alternatively add a typesVersions mapping that maps
the "./cloud" subpath to the declaration file; ensure the declaration file path
matches your built .d.ts location and keep the existing "./cloud" value intact
while adding the types mapping.

In `@src/cloud-code/LegacyCloud.ts`:
- Around line 120-159: The define and defineTrigger wrappers drop
validator.rateLimit because they forward the raw validator to triggers without
calling validateValidator() or applyRateLimit(); update define (around the
FunctionHandler wrapping and triggers.addFunction) and defineTrigger (around
wrappedHandler and triggers.addTrigger/addConnectTrigger) to first call
validateValidator(validator) then call applyRateLimit(validated,
nameOrClassIdentifier, this._appId) (or the equivalent helper used by
cloudDefine/cloudTrigger) and pass the validated/resulting validator (or ensure
applyRateLimit is applied) into
triggers.addFunction/addTrigger/addConnectTrigger so rate limits are enforced
for SDK-registered handlers.
- Around line 264-272: The setter for Parse.Server currently overwrites the
cached config with only newVal and appId, causing loss of required fields;
update the setter to first load the existing config via
Config.get(Parse.applicationId), merge that existing config with newVal
(ensuring appId is preserved/assigned), and then call Config.put(mergedConfig)
so partial assignments (e.g., Parse.Server = { enableSingleSchemaCache: true })
only modify specified keys and retain controllers and other required fields.
- Around line 319-339: The cloudAuthTrigger function parses an optional
Validator into the local validator variable but never registers it with
triggers.addTrigger, so validators for auth triggers (e.g., beforeLogin /
beforePasswordResetRequest) never run; update the call to triggers.addTrigger in
cloudAuthTrigger to pass the parsed validator as the validation handler (use the
validator variable) alongside type, className, and this._appId, and keep
applyRateLimit using that same validator—ensure you use the same
handler/validator ordering expected by triggers.addTrigger so existing overload
logic (string/object handler resolution) still works.
- Around line 22-33: getRoute currently maps system classes via ROUTE_MAP but
still appends the class name, causing e.g. `_User` -> `/users/_User...`; change
the logic in getRoute (and keep ROUTE_MAP) so that when parseClass is in
ROUTE_MAP you return the base segment `/${route}` (except the special '@File'
case which should remain `/${route}{/*id}` and keep '@Config' as `/${route}`),
and only for unmapped classes return `/${route}/${parseClass}{/*id}`; update
getRoute to check ROUTE_MAP[parseClass] first and return the appropriate base
route to ensure rate-limit paths match real REST routes.

In `@src/cloud-code/types.ts`:
- Around line 126-128: ObjectTriggerHandler currently allows Partial<T> | void
which incorrectly permits non-beforeSave handlers (afterSave, beforeDelete,
afterDelete) to return patches; create a distinct handler type for beforeSave
(e.g., BeforeSaveObjectTriggerHandler<T> = (request: ObjectTriggerRequest<T>) =>
BeforeSaveResult<T>) and change ObjectTriggerHandler<T> to be the void-only type
for afterSave/beforeDelete/afterDelete (i.e., (request: ObjectTriggerRequest<T>)
=> AfterTriggerResult). Update TriggerHandlerMap to use
BeforeSaveObjectTriggerHandler for the beforeSave key and ObjectTriggerHandler
for the other keys, referencing the existing types ObjectTriggerRequest,
BeforeSaveResult, AfterTriggerResult, ObjectTriggerHandler, and
TriggerHandlerMap in your changes.

In `@src/ParseServer.ts`:
- Around line 129-133: ParseServer currently constructs a concrete LegacyCloud
and uses its .Parse and .bindToParseCloud(), which bypasses the
CloudCodeRegistrar contract and prevents custom registrars from being used;
change ParseServer to retrieve the registrar instance via
CloudCodeRegistrar.getInstance() (or the registrar accessor used in this
codebase) instead of new LegacyCloud(), and call only the registrar's contract
methods (e.g., expose Parse via the registrar API and invoke bindToParseCloud()
through the registrar interface) rather than downcasting to LegacyCloud so
custom CloudCodeRegistrar implementations can plug in without being overwritten.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 15231df1-a0ac-4c07-88e1-05844979e470

📥 Commits

Reviewing files that changed from the base of the PR and between a9511a7 and eb653a6.

📒 Files selected for processing (8)
  • package.json
  • src/ParseServer.ts
  • src/cloud-code/CloudCodeRegistrar.ts
  • src/cloud-code/LegacyCloud.ts
  • src/cloud-code/Parse.Cloud.js
  • src/cloud-code/README.md
  • src/cloud-code/types.ts
  • src/cloud.ts
💤 Files with no reviewable changes (1)
  • src/cloud-code/Parse.Cloud.js

Comment on lines +129 to +133
// Initialize the cloud SDK and register it
const cloudSDK = new LegacyCloud();
cloudSDK.initialize({ appId, masterKey, javascriptKey, serverURL });
CloudCodeRegistrar.setInstance(cloudSDK);
const Parse = cloudSDK.Parse;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ParseServer still depends on LegacyCloud, not the registrar contract.

These paths always install LegacyCloud and later downcast the registry entry back to LegacyCloud for .Parse and .bindToParseCloud(). That means a custom CloudCodeRegistrar implementation—the feature this PR is documenting—cannot actually plug in here without being overwritten or failing at startup.

Also applies to: 166-190

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ParseServer.ts` around lines 129 - 133, ParseServer currently constructs
a concrete LegacyCloud and uses its .Parse and .bindToParseCloud(), which
bypasses the CloudCodeRegistrar contract and prevents custom registrars from
being used; change ParseServer to retrieve the registrar instance via
CloudCodeRegistrar.getInstance() (or the registrar accessor used in this
codebase) instead of new LegacyCloud(), and call only the registrar's contract
methods (e.g., expose Parse via the registrar API and invoke bindToParseCloud()
through the registrar interface) rather than downcasting to LegacyCloud so
custom CloudCodeRegistrar implementations can plug in without being overwritten.

dblythy added 3 commits March 21, 2026 00:03
…letion

The SDK adapter refactor replaced Parse.Cloud.js with new modular files
(LegacyCloud.ts, TriggerStore.ts, etc.). Upstream modified the old file,
so we keep our deletion.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (3)
src/cloud-code/LegacyCloud.ts (3)

272-285: ⚠️ Potential issue | 🟠 Major

Auth trigger validators are still dropped.

cloudAuthTrigger() parses validator and even uses it for rateLimit, but the triggers.addTrigger() call omits the validation handler. beforeLogin, afterLogin, afterLogout, and beforePasswordResetRequest therefore never enforce their validator rules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/LegacyCloud.ts` around lines 272 - 285, The auth trigger
registration currently drops the validator when calling triggers.addTrigger, so
update the call to include the parsed validator (the same validator variable
used for rateLimit) so
beforeLogin/afterLogin/afterLogout/beforePasswordResetRequest enforce
validation; ensure you pass the validator alongside type, className, handler and
this._appId (and adjust the addTrigger argument order if necessary) and keep
applyRateLimit using that same validator variable.

214-216: ⚠️ Potential issue | 🟠 Major

Partial Parse.Server writes still clobber the cached config.

Config.put(newVal) replaces the whole app config. A setter call like Parse.Server = { enableSingleSchemaCache: true } will drop controllers and the rest of the live configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/LegacyCloud.ts` around lines 214 - 216, The setter in
LegacyCloud.ts currently calls Config.put(newVal) which replaces the entire app
config and clobbers live state; change it to load the existing config for
Parse.applicationId (e.g., via Config.get or currentConfig), merge the incoming
newVal into that existing config (shallow-merge only provided keys so
controllers and other fields are preserved), ensure appId is set on the merged
object, then call Config.put(mergedConfig) so only supplied properties (like
enableSingleSchemaCache) are updated instead of overwriting the whole config.

27-31: ⚠️ Potential issue | 🟠 Major

System-class rate-limit paths still don't match the real REST routes.

getRoute('_User') still resolves to /users/_User{/*id} and _Session to /sessions/_Session{/*id} here, so validator.rateLimit on those triggers never matches the actual /users or /sessions endpoints.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/LegacyCloud.ts` around lines 27 - 31, getRoute currently
appends the parseClass name to the path even when ROUTE_MAP already maps that
class to a top-level route (so getRoute('_User') -> `/users/_User{/*id}`),
breaking rate-limit matching; update getRoute (function name getRoute, symbol
ROUTE_MAP and parameter parseClass) so that if ROUTE_MAP[parseClass] exists you
return `/${route}{/*id}` (same as the `@File/`@Config branches) instead of
`/${route}/${parseClass}{/*id}`, otherwise fall back to the `classes` behavior;
keep the existing `@File` and `@Config` special cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cloud-code/LegacyCloud.ts`:
- Around line 98-107: LegacyCloud.initialize currently assigns
require('parse/node').Parse to global.Parse which causes cross-server
interference; remove writing to global.Parse and instantiate/retain a
per-instance Parse reference in LegacyCloud instead. Update call sites to pass
explicit appId: change maybeRunValidator to accept and use an explicit
applicationId (call getValidator(functionName, applicationId) instead of relying
on Parse.applicationId), modify runLiveQueryEventHandlers to require an appId
parameter and use it rather than defaulting to Parse.applicationId, and update
ParseLiveQueryServer to call runLiveQueryEventHandlers(this.config.appId, ...)
so live-query handlers run against the correct server; ensure any other places
that depended on global.Parse use the LegacyCloud instance or explicit appId
instead.

In `@src/cloud-code/TriggerStore.ts`:
- Around line 163-167: The validator map (s.validators) can retain stale entries
because addFunction/addTrigger/addConnectTrigger only add validators when
provided and removeFunction/removeTrigger never delete them; update these
methods (addFunction, addTrigger, addConnectTrigger) to explicitly remove any
existing validator entry for the given name when no validator is passed (i.e.,
delete s.validators[name] if validator is undefined) and update the removal
methods (removeFunction, removeTrigger, and the connect removal equivalent) to
also delete the corresponding s.validators[name] when removing the handler so
validators can't persist after a hook is replaced or removed.
- Around line 253-260: The clearAll() implementation currently only clears
TriggerStore._stores and therefore skips unregistering cloud rate-limiters;
update clearAll() to iterate over all stored appIds (keys of
TriggerStore._stores) and call the same cleanup logic as removeAllHooks(appId) —
i.e., for each appId call Config.get(appId)?.unregisterRateLimiters() before
clearing the store — or alternatively call removeAllHooks(appId) for each appId
to ensure unregisterRateLimiters runs for every app; ensure the final step still
clears TriggerStore._stores.

In `@src/cloud-code/validateTrigger.ts`:
- Around line 4-5: The current check in validateTrigger.ts only forbids
beforeSave for className === '_PushStatus' but the error says only afterSave is
allowed; change the predicate to reject any trigger on '_PushStatus' whose type
is not TriggerType.afterSave (i.e., if (className === '_PushStatus' && type !==
TriggerType.afterSave) throw 'Only afterSave is allowed on _PushStatus'),
updating the condition that references type, className, and TriggerType (inside
the validateTrigger function) so all unsupported hook types (afterDelete,
beforeFind, etc.) are consistently blocked.

---

Duplicate comments:
In `@src/cloud-code/LegacyCloud.ts`:
- Around line 272-285: The auth trigger registration currently drops the
validator when calling triggers.addTrigger, so update the call to include the
parsed validator (the same validator variable used for rateLimit) so
beforeLogin/afterLogin/afterLogout/beforePasswordResetRequest enforce
validation; ensure you pass the validator alongside type, className, handler and
this._appId (and adjust the addTrigger argument order if necessary) and keep
applyRateLimit using that same validator variable.
- Around line 214-216: The setter in LegacyCloud.ts currently calls
Config.put(newVal) which replaces the entire app config and clobbers live state;
change it to load the existing config for Parse.applicationId (e.g., via
Config.get or currentConfig), merge the incoming newVal into that existing
config (shallow-merge only provided keys so controllers and other fields are
preserved), ensure appId is set on the merged object, then call
Config.put(mergedConfig) so only supplied properties (like
enableSingleSchemaCache) are updated instead of overwriting the whole config.
- Around line 27-31: getRoute currently appends the parseClass name to the path
even when ROUTE_MAP already maps that class to a top-level route (so
getRoute('_User') -> `/users/_User{/*id}`), breaking rate-limit matching; update
getRoute (function name getRoute, symbol ROUTE_MAP and parameter parseClass) so
that if ROUTE_MAP[parseClass] exists you return `/${route}{/*id}` (same as the
`@File/`@Config branches) instead of `/${route}/${parseClass}{/*id}`, otherwise
fall back to the `classes` behavior; keep the existing `@File` and `@Config` special
cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3821d467-0a53-45a0-8fa0-6c339967b7f1

📥 Commits

Reviewing files that changed from the base of the PR and between eb653a6 and cc7d8b2.

📒 Files selected for processing (8)
  • src/ParseServer.ts
  • src/cloud-code/LegacyCloud.ts
  • src/cloud-code/README.md
  • src/cloud-code/TriggerStore.ts
  • src/cloud-code/types.ts
  • src/cloud-code/validateTrigger.ts
  • src/cloud.ts
  • src/triggers.js
✅ Files skipped from review due to trivial changes (3)
  • src/cloud-code/README.md
  • src/cloud.ts
  • src/cloud-code/types.ts

Comment on lines +98 to +107
initialize(config: { appId: string; masterKey: string; javascriptKey?: string; serverURL: string }): void {
this._appId = config.appId;
this._parse = require('parse/node').Parse;
this._parse.initialize(
config.appId,
config.javascriptKey || 'unused',
config.masterKey
);
this._parse.serverURL = config.serverURL;
(global as any).Parse = this._parse;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the changed surface for remaining process-global Parse dependencies.
rg -n -C2 "require\\('parse/node'\\)\\.Parse|global\\.Parse|Parse\\.applicationId" \
  src/cloud-code/LegacyCloud.ts src/ParseServer.ts src/triggers.js

Repository: parse-community/parse-server

Length of output: 2567


🏁 Script executed:

# Check how TriggerStore is initialized and used - does it properly isolate by appId?
rg -n "class TriggerStore|new TriggerStore|TriggerStore\." src/triggers.js -A 3

Repository: parse-community/parse-server

Length of output: 2265


🏁 Script executed:

# Check the _appId function more closely and how it's used throughout triggers.js
rg -n "_appId|function _appId" src/triggers.js -B 2 -A 5

Repository: parse-community/parse-server

Length of output: 2174


🏁 Script executed:

# Look at how triggers are registered and called - is appId properly passed?
rg -n "addTrigger|runTriggers|getTriggers" src/triggers.js -B 1 -A 3

Repository: parse-community/parse-server

Length of output: 362


🏁 Script executed:

# Check if there are any safeguards or defensive code around Parse.applicationId usage
rg -n "applicationId.*Parse|Parse.*applicationId" src/cloud-code/LegacyCloud.ts src/triggers.js -B 1 -A 1

Repository: parse-community/parse-server

Length of output: 1215


🏁 Script executed:

# Check who calls maybeRunValidator and how they pass applicationId
rg -n "maybeRunValidator" src/ -B 2 -A 2

Repository: parse-community/parse-server

Length of output: 2426


🏁 Script executed:

# Check who calls runLiveQueryEventHandlers and how they pass applicationId
rg -n "runLiveQueryEventHandlers" src/ -B 2 -A 2

Repository: parse-community/parse-server

Length of output: 3391


🏁 Script executed:

# Check how applicationId flows through RestWrite - is it always available?
rg -n "applicationId|auth\.app" src/RestWrite.js | head -30

Repository: parse-community/parse-server

Length of output: 311


🏁 Script executed:

# Check what auth object contains and if it has applicationId
rg -n "interface.*Auth|class.*Auth|auth\\.app" src/Auth.js src/RestWrite.js -A 5 | head -50

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Check ParseLiveQueryServer initialization - does it have access to appId?
rg -n "constructor|new ParseLiveQueryServer|this\.appId|this\.applicationId" src/LiveQuery/ParseLiveQueryServer.ts -B 2 -A 3 | head -60

Repository: parse-community/parse-server

Length of output: 282


🏁 Script executed:

# Look for how applicationId should be passed to ParseLiveQueryServer handlers
rg -n "class ParseLiveQueryServer|constructor.*ParseLiveQueryServer" src/LiveQuery/ParseLiveQueryServer.ts -A 20

Repository: parse-community/parse-server

Length of output: 785


🏁 Script executed:

# Check how auth object is structured - does it have applicationId/appId?
rg -n "auth\.appId|auth\.applicationId" src/ -B 2 -A 2 | head -40

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Verify that maybeRunValidator has the same issue - check all its callers
rg -n "maybeRunValidator" src/triggers.js -B 5 | grep -E "maybeRunValidator|applicationId|auth"

Repository: parse-community/parse-server

Length of output: 1101


🏁 Script executed:

# Check if request or auth parameter in maybeRunValidator could contain applicationId
rg -n "function getValidator|export function getValidator" src/triggers.js -A 10

Repository: parse-community/parse-server

Length of output: 367


🏁 Script executed:

# Check if runTrigger has access to applicationId or config
rg -n "function runTrigger|export.*runTrigger" src/triggers.js -A 15

Repository: parse-community/parse-server

Length of output: 594


🏁 Script executed:

# Check the file trigger and config trigger handlers - they explicitly use config.applicationId
rg -n "fileTrigger|configTrigger" src/triggers.js -B 5 -A 5

Repository: parse-community/parse-server

Length of output: 1790


LegacyCloud is still backed by process-global Parse state, breaking multi-server isolation.

initialize() pulls in require('parse/node').Parse and writes it to global.Parse. A second ParseServer in the same process overwrites Parse.applicationId and Parse.serverURL for the first one.

The trigger system has architectural dependencies on this global state:

  • maybeRunValidator (line 584 in src/triggers.js) calls getValidator(functionName, Parse.applicationId) with no applicationId parameter, forcing fallback to global state
  • runLiveQueryEventHandlers (line 897) defaults to Parse.applicationId when called without explicit appId
  • ParseLiveQueryServer knows its own appId (stored in this.config.appId) but calls runLiveQueryEventHandlers() without passing it, relying on the global default

This causes the first server's validators and live query handlers to execute against the second server's app configuration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/LegacyCloud.ts` around lines 98 - 107, LegacyCloud.initialize
currently assigns require('parse/node').Parse to global.Parse which causes
cross-server interference; remove writing to global.Parse and instantiate/retain
a per-instance Parse reference in LegacyCloud instead. Update call sites to pass
explicit appId: change maybeRunValidator to accept and use an explicit
applicationId (call getValidator(functionName, applicationId) instead of relying
on Parse.applicationId), modify runLiveQueryEventHandlers to require an appId
parameter and use it rather than defaulting to Parse.applicationId, and update
ParseLiveQueryServer to call runLiveQueryEventHandlers(this.config.appId, ...)
so live-query handlers run against the correct server; ensure any other places
that depended on global.Parse use the LegacyCloud instance or explicit appId
instead.

Comment on lines +163 to +167
static addFunction(appId: string, name: string, handler: Handler, validator?: Validator): void {
const s = TriggerStore._getOrCreate(appId);
addToStore(s.functions, name, handler);
if (validator) addToStore(s.validators, name, validator);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clear validator entries when hooks are replaced or removed.

addFunction / addTrigger / addConnectTrigger only write s.validators when a new validator is provided, and removeFunction / removeTrigger never delete the old entry. Because src/triggers.js resolves validators by name independently of the handler, a re-registered hook can inherit stale auth/field rules from the previous definition.

🐛 Proposed fix
   static addFunction(appId: string, name: string, handler: Handler, validator?: Validator): void {
     const s = TriggerStore._getOrCreate(appId);
     addToStore(s.functions, name, handler);
-    if (validator) addToStore(s.validators, name, validator);
+    if (validator) addToStore(s.validators, name, validator);
+    else removeFromStore(s.validators, name);
   }
@@
   static addTrigger(appId: string, type: string, className: string, handler: Handler, validator?: Validator): void {
     validateClassNameForTriggers(className, type);
+    const key = `${type}.${className}`;
     const s = TriggerStore._getOrCreate(appId);
-    addToStore(s.triggers, `${type}.${className}`, handler);
-    if (validator) addToStore(s.validators, `${type}.${className}`, validator);
+    addToStore(s.triggers, key, handler);
+    if (validator) addToStore(s.validators, key, validator);
+    else removeFromStore(s.validators, key);
   }
@@
   static addConnectTrigger(appId: string, type: string, handler: Handler, validator?: Validator): void {
+    const key = `${type}.${ConnectClassName}`;
     const s = TriggerStore._getOrCreate(appId);
-    addToStore(s.triggers, `${type}.${ConnectClassName}`, handler);
-    if (validator) addToStore(s.validators, `${type}.${ConnectClassName}`, validator);
+    addToStore(s.triggers, key, handler);
+    if (validator) addToStore(s.validators, key, validator);
+    else removeFromStore(s.validators, key);
   }
@@
   static removeFunction(appId: string, name: string): void {
     const s = TriggerStore._stores.get(appId);
-    if (s) removeFromStore(s.functions, name);
+    if (s) {
+      removeFromStore(s.functions, name);
+      removeFromStore(s.validators, name);
+    }
   }
@@
   static removeTrigger(appId: string, type: string, className: string): void {
     const s = TriggerStore._stores.get(appId);
-    if (s) removeFromStore(s.triggers, `${type}.${className}`);
+    if (s) {
+      const key = `${type}.${className}`;
+      removeFromStore(s.triggers, key);
+      removeFromStore(s.validators, key);
+    }
   }

Also applies to: 173-184, 243-250

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/TriggerStore.ts` around lines 163 - 167, The validator map
(s.validators) can retain stale entries because
addFunction/addTrigger/addConnectTrigger only add validators when provided and
removeFunction/removeTrigger never delete them; update these methods
(addFunction, addTrigger, addConnectTrigger) to explicitly remove any existing
validator entry for the given name when no validator is passed (i.e., delete
s.validators[name] if validator is undefined) and update the removal methods
(removeFunction, removeTrigger, and the connect removal equivalent) to also
delete the corresponding s.validators[name] when removing the handler so
validators can't persist after a hook is replaced or removed.

Comment on lines +253 to +260
static removeAllHooks(appId: string): void {
TriggerStore._stores.delete(appId);
Config.get(appId)?.unregisterRateLimiters();
}

static clearAll(): void {
TriggerStore._stores.clear();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

clearAll() skips cloud rate-limiter cleanup.

removeAllHooks(appId) unregisters cloud rate limiters, but clearAll() only wipes _stores. Since src/triggers.js::_unregisterAll() now routes through this method, teardown can leave stale cloud rate-limit state behind.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/TriggerStore.ts` around lines 253 - 260, The clearAll()
implementation currently only clears TriggerStore._stores and therefore skips
unregistering cloud rate-limiters; update clearAll() to iterate over all stored
appIds (keys of TriggerStore._stores) and call the same cleanup logic as
removeAllHooks(appId) — i.e., for each appId call
Config.get(appId)?.unregisterRateLimiters() before clearing the store — or
alternatively call removeAllHooks(appId) for each appId to ensure
unregisterRateLimiters runs for every app; ensure the final step still clears
TriggerStore._stores.

Comment on lines +4 to +5
if (type == TriggerType.beforeSave && className === '_PushStatus') {
throw 'Only afterSave is allowed on _PushStatus';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reject unsupported _PushStatus hooks consistently.

The predicate and error text disagree here. As written, this only blocks beforeSave, so unsupported registrations like afterDelete or beforeFind on _PushStatus still get through even though the message says only afterSave is allowed.

🐛 Proposed fix
-  if (type == TriggerType.beforeSave && className === '_PushStatus') {
+  if (className === '_PushStatus' && type !== TriggerType.afterSave) {
     throw 'Only afterSave is allowed on _PushStatus';
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (type == TriggerType.beforeSave && className === '_PushStatus') {
throw 'Only afterSave is allowed on _PushStatus';
if (className === '_PushStatus' && type !== TriggerType.afterSave) {
throw 'Only afterSave is allowed on _PushStatus';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/validateTrigger.ts` around lines 4 - 5, The current check in
validateTrigger.ts only forbids beforeSave for className === '_PushStatus' but
the error says only afterSave is allowed; change the predicate to reject any
trigger on '_PushStatus' whose type is not TriggerType.afterSave (i.e., if
(className === '_PushStatus' && type !== TriggerType.afterSave) throw 'Only
afterSave is allowed on _PushStatus'), updating the condition that references
type, className, and TriggerType (inside the validateTrigger function) so all
unsupported hook types (afterDelete, beforeFind, etc.) are consistently blocked.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/cloud-code/LegacyCloud.ts (2)

100-109: ⚠️ Potential issue | 🟠 Major

LegacyCloud is still backed by the shared Parse SDK singleton.

require('parse/node').Parse gives you the process-wide SDK object. Re-initializing it here and assigning it to global.Parse means the last ParseServer started in-process wins; later Parse.Cloud / Parse.Server bindings then operate against that shared mutable state instead of a single app.

Run this read-only check to confirm the remaining global Parse / Parse.applicationId dependencies:

#!/bin/bash
rg -n -C2 "require\\('parse/node'\\)\\.Parse|global\\.Parse|bindToParseCloud|Parse\\.Cloud\\[" \
  src/cloud-code/LegacyCloud.ts src/ParseServer.ts

rg -n -C2 "Parse\\.applicationId|runLiveQueryEventHandlers\\(|maybeRunValidator\\(" \
  src/triggers.js src/LiveQuery/ParseLiveQueryServer.ts

Expected result: any global.Parse write or Parse.applicationId fallback means the adapter is still process-global instead of app-scoped.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/LegacyCloud.ts` around lines 100 - 109, The initialize method
currently reinitializes and writes the process-wide Parse singleton
(require('parse/node').Parse) and assigns it to global.Parse; stop mutating the
global SDK: keep the Parse SDK instance scoped to the LegacyCloud instance (use
this._parse only), remove the global assignment (delete the line that sets
(global as any).Parse), and ensure all other code paths use this._parse instead
of relying on global.Parse or Parse.applicationId; run the suggested ripgrep
checks from the review to find remaining globals like global.Parse,
Parse.applicationId, Parse.Cloud, bindToParseCloud, runLiveQueryEventHandlers,
and update those call sites to accept/forward the instance from LegacyCloud (or
change them to instance methods) so the adapter is app-scoped rather than
process-global.

27-33: ⚠️ Potential issue | 🟠 Major

The system-class route fix is still incomplete.

getRoute('_User') now returns /users, but cloudBeforeSave() registers both POST and PUT. That covers signup, but it still misses object-level routes like PUT /users/:objectId, so validator.rateLimit will not fire for user updates. @Config is the only mapped route that should stay exact here.

🔧 Suggested fix
 function getRoute(parseClass: string): string {
   const route = ROUTE_MAP[parseClass];
   if (route) {
-    if (parseClass === '@File') return `/${route}{/*id}`;
-    return `/${route}`;
+    if (parseClass === '@Config') return `/${route}`;
+    return `/${route}{/*id}`;
   }
   return `/classes/${parseClass}{/*id}`;
 }

Also applies to: 127-139

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/LegacyCloud.ts` around lines 27 - 33, getRoute currently
returns collection-only routes for mapped system classes (e.g., returning
"/users" for "_User") which prevents registering object-level handlers; update
getRoute so that when ROUTE_MAP[parseClass] exists you return a
collection+object route ("/{route}{/*id}") for all mapped classes except the
special exact-only "@Config" (keep the existing special-case for "@File" too);
adjust the logic in getRoute to check for parseClass === '@File' and parseClass
=== '@Config' and return the appropriate string so object-level routes (e.g.,
PUT /users/:objectId) are generated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cloud-code/LegacyCloud.ts`:
- Around line 187-195: cloudSendEmail currently logs and returns undefined when
no mail adapter exists which causes callers to treat the call as successful;
update cloudSendEmail (in LegacyCloud) to instead reject when emailAdapter is
missing: keep the config.loggerController.error call for logging, then throw or
return Promise.reject(new Error('No mail adapter configured for Parse Server'))
so callers (e.g., await Parse.Cloud.sendEmail(...)) receive a rejected promise
and can handle the failure.

---

Duplicate comments:
In `@src/cloud-code/LegacyCloud.ts`:
- Around line 100-109: The initialize method currently reinitializes and writes
the process-wide Parse singleton (require('parse/node').Parse) and assigns it to
global.Parse; stop mutating the global SDK: keep the Parse SDK instance scoped
to the LegacyCloud instance (use this._parse only), remove the global assignment
(delete the line that sets (global as any).Parse), and ensure all other code
paths use this._parse instead of relying on global.Parse or Parse.applicationId;
run the suggested ripgrep checks from the review to find remaining globals like
global.Parse, Parse.applicationId, Parse.Cloud, bindToParseCloud,
runLiveQueryEventHandlers, and update those call sites to accept/forward the
instance from LegacyCloud (or change them to instance methods) so the adapter is
app-scoped rather than process-global.
- Around line 27-33: getRoute currently returns collection-only routes for
mapped system classes (e.g., returning "/users" for "_User") which prevents
registering object-level handlers; update getRoute so that when
ROUTE_MAP[parseClass] exists you return a collection+object route
("/{route}{/*id}") for all mapped classes except the special exact-only
"@Config" (keep the existing special-case for "@File" too); adjust the logic in
getRoute to check for parseClass === '@File' and parseClass === '@Config' and
return the appropriate string so object-level routes (e.g., PUT
/users/:objectId) are generated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 69ba9810-341e-47d2-ba56-106a3c13355d

📥 Commits

Reviewing files that changed from the base of the PR and between cc7d8b2 and eee657a.

📒 Files selected for processing (4)
  • package.json
  • src/cloud-code/LegacyCloud.ts
  • src/cloud-code/types.ts
  • src/cloud.ts
✅ Files skipped from review due to trivial changes (2)
  • src/cloud.ts
  • src/cloud-code/types.ts

Comment on lines +187 to +195
cloudSendEmail(data: Record<string, unknown>): unknown {
const config = Config.get(this._appId);
const emailAdapter = config.userController.adapter;
if (!emailAdapter) {
config.loggerController.error('Failed to send email because no mail adapter is configured for Parse Server.');
return;
}
return emailAdapter.sendMail(data);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

cloudSendEmail() should reject when mail is unavailable.

When no mail adapter is configured, this logs and returns undefined. await Parse.Cloud.sendEmail(...) will therefore resolve as if the operation succeeded, which makes missing email configuration very hard to detect from Cloud Code.

💡 Suggested fix
   if (!emailAdapter) {
-    config.loggerController.error('Failed to send email because no mail adapter is configured for Parse Server.');
-    return;
+    const message = 'Failed to send email because no mail adapter is configured for Parse Server.';
+    config.loggerController.error(message);
+    return Promise.reject(new Error(message));
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cloud-code/LegacyCloud.ts` around lines 187 - 195, cloudSendEmail
currently logs and returns undefined when no mail adapter exists which causes
callers to treat the call as successful; update cloudSendEmail (in LegacyCloud)
to instead reject when emailAdapter is missing: keep the
config.loggerController.error call for logging, then throw or return
Promise.reject(new Error('No mail adapter configured for Parse Server')) so
callers (e.g., await Parse.Cloud.sendEmail(...)) receive a rejected promise and
can handle the failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants