Skip to content

Conversation

@dblythy
Copy link
Member

@dblythy dblythy commented Dec 23, 2024

Pull Request

Issue

Closes: #9479

Approach

This is a feature alongside a refactor to promote more modularity in the cloud triggers.

Tasks

  • Add tests
  • Add changes to documentation (guides, repository pages, code comments)
  • Add security check
  • Add new Parse Error codes to Parse JS SDK

Summary by CodeRabbit

Release Notes

  • New Features

    • Cloud code hooks now support setting custom HTTP status codes and response headers across save, delete, find, login, and file operations.
  • Tests

    • Added comprehensive test coverage for custom HTTP status code and header behavior across all major trigger types.

✏️ Tip: You can customize this high-level summary in your review settings.

@parse-github-assistant
Copy link

I will reformat the title to use the proper commit message syntax.

@parse-github-assistant parse-github-assistant bot changed the title feat: add response error codes feat: Add response error codes Dec 23, 2024
@parse-github-assistant
Copy link

Thanks for opening this pull request!

@dblythy dblythy marked this pull request as draft December 23, 2024 06:17
@mtrezza mtrezza marked this pull request as ready for review December 13, 2025 21:03
@coderabbitai
Copy link

coderabbitai bot commented Dec 13, 2025

📝 Walkthrough

Walkthrough

This PR introduces support for custom HTTP status codes and headers in Parse Cloud Code triggers. It modularizes the trigger system across specialized modules (TriggerStore, Trigger, FileTrigger, QueryTrigger, Validator, Logger, Utils), adds a TriggerResponse class for response configuration, and propagates response objects through routers and REST handlers to enable trigger-driven HTTP behavior.

Changes

Cohort / File(s) Summary
Test & Configuration
spec/CloudCode.spec.js, spec/helper.js
Adds comprehensive test suite for custom HTTP status codes and headers across all trigger types (save, delete, find, cloud functions, beforeLogin, file triggers); sets silent to false in default test configuration.
Trigger System Modularization
src/Triggers/TriggerStore.js
src/Triggers/Trigger.js
src/Triggers/QueryTrigger.js
src/Triggers/FileTrigger.js
src/Triggers/ConfigTrigger.js
src/Triggers/Validator.js
src/Triggers/Logger.js
src/Triggers/Utils.js
src/Triggers/TriggerResponse.js
New modular trigger system breaking out monolithic implementation from src/triggers.js. Introduces centralized trigger registry (TriggerStore), core trigger orchestration (Trigger), specialized handlers (Query, File, Config), validation, logging, error handling, and response building utilities. TriggerResponse class enables custom HTTP status codes and headers.
Trigger Aggregation
src/triggers.js
Refactored to re-export APIs from specialized modules instead of containing full implementation; acts as public API aggregator.
Router Integration
src/Routers/ClassesRouter.js
src/Routers/FilesRouter.js
src/Routers/FunctionsRouter.js
src/Routers/UsersRouter.js
Integrated TriggerResponse into handlers for find, get, create, update, delete operations and file/login triggers; converted handlers to async; response object creation now flows through TriggerResponse.toResponseObject to capture custom status codes and headers.
Request/Response Propagation
src/rest.js
src/RestQuery.js
src/RestWrite.js
src/PromiseRouter.js
Extended REST functions and internal handlers to accept and propagate response/responseObject parameters through query and write operations; updated PromiseRouter to use async/await with TriggerResponse result handling.
Minor Updates
src/cloud-code/Parse.Cloud.js
src/Controllers/AdaptableController.js
src/Controllers/LiveQueryController.js
Updated beforeConnect registration to use generic trigger path; disabled adapter prototype mismatch error throwing; corrected import path for getClassName.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Router as Router<br/>(ClassesRouter, etc.)
    participant RestHandler as RestQuery/<br/>RestWrite
    participant TriggerStore as TriggerStore
    participant Trigger as Trigger<br/>Orchestration
    participant CloudCode as Cloud Code<br/>Trigger
    participant TriggerResponse as TriggerResponse
    participant Express as Express<br/>Response

    Client->>Router: HTTP Request
    activate Router
    Router->>Router: Create TriggerResponse
    Router->>RestHandler: Pass response object
    activate RestHandler
    RestHandler->>TriggerStore: Get trigger (beforeFind,<br/>beforeSave, etc.)
    activate TriggerStore
    TriggerStore-->>Trigger: Return trigger
    deactivate TriggerStore
    RestHandler->>Trigger: Execute trigger with<br/>response object
    activate Trigger
    Trigger->>CloudCode: Call user-defined<br/>trigger function
    activate CloudCode
    CloudCode->>TriggerResponse: Call response.status(401)
    CloudCode->>TriggerResponse: Call response.setHeader('X-Custom', 'value')
    CloudCode-->>Trigger: Return result
    deactivate CloudCode
    deactivate Trigger
    RestHandler-->>Router: Return processed data
    deactivate RestHandler
    Router->>TriggerResponse: toResponseObject()
    activate TriggerResponse
    TriggerResponse-->>Router: Return response with<br/>status=401, headers set
    deactivate TriggerResponse
    Router->>Express: res.status(401)<br/>res.set(headers)
    deactivate Router
    Express-->>Client: HTTP 401 + Custom Headers
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

  • New trigger modules (TriggerStore.js, Trigger.js, QueryTrigger.js, FileTrigger.js, ConfigTrigger.js, Validator.js, Logger.js, Utils.js, TriggerResponse.js): Each introduces distinct orchestration logic requiring careful verification of error handling, validation flow, and integration with existing systems.
  • Router refactoring (ClassesRouter, FilesRouter, FunctionsRouter, UsersRouter): Conversion to async/await with TriggerResponse integration requires validation that response handling is consistent across all endpoints and that error paths are preserved.
  • Response object propagation (rest.js, RestQuery.js, RestWrite.js, PromiseRouter.js): Threading a new parameter through multiple layers demands attention to ensure it reaches all trigger invocations and doesn't break existing call sites.
  • TriggerStore validation rules and class restrictions: Complex namespace handling and className/type validation rules need careful review to prevent trigger registration bypasses.

Possibly related PRs

  • parse-community/parse-server#8480: Modifies trigger request construction and hook plumbing by adding per-request context data (similar pattern of propagating request metadata through trigger layers).
  • parse-community/parse-server#9937: Modifies the same core request/response code paths in RestQuery.js and RestWrite.js, suggesting concurrent or related response handling improvements.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The PR description references the linked issue (#9479) and explains the approach as a feature with refactoring for modularity. However, all four tasks remain unchecked, indicating incomplete preparation for merging. Complete or remove unchecked tasks before merging. At minimum, verify whether tests were added and document the new response code functionality.
Out of Scope Changes check ❓ Inconclusive Most changes align with enabling HTTP response control, but some modifications appear tangential: disabling the adapter prototype mismatch error, changing default silent configuration, and simplifying import paths may lack clear connection to the stated feature objective. Clarify the purpose of unrelated changes (AdaptableController, spec/helper.js silent flag, import path simplification) or move them to a separate refactoring PR.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature: adding HTTP response status code control from Cloud Code, which is the core objective of the linked issue #9479.
Linked Issues check ✅ Passed The PR implements the core feature from #9479 by adding response object propagation through triggers and routers, enabling Cloud Code to set HTTP status codes and headers via TriggerResponse integration.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/RestWrite.js (1)

30-44: All RestWrite constructor call sites must be updated to pass the responseObject parameter.

The new responseObject parameter has been added to the RestWrite constructor signature (parameter 10) without a default value. However, only 1 of 6 production call sites currently passes this parameter:

  • src/rest.js: Correctly passes response as the 10th parameter
  • src/RestWrite.js (createSession): Missing responseObject — will be undefined
  • src/Auth.js: Missing responseObject — will be undefined
  • src/Routers/UsersRouter.js: Missing responseObject — will be undefined
  • src/Controllers/PushController.js (2 calls): Missing responseObject — will be undefined

All remaining call sites need to be updated to pass the responseObject parameter (or undefined/null if not available). This is a breaking change that impacts multiple modules.

src/rest.js (1)

61-159: Add context and response parameters to the RestQuery call in del()

The prefetch query at lines 80-86 doesn't pass context or response to RestQuery, while other routes (find, get) do. This means the beforeFind trigger will run with an empty context object instead of the one passed to del(), creating inconsistent trigger behavior.

         const query = await RestQuery({
           method: RestQuery.Method.get,
           config,
           auth,
           className,
           restWhere: { objectId },
+          context,
+          response: responseObject,
         });
🧹 Nitpick comments (16)
spec/helper.js (2)

89-154: Align silent / verbose / logLevels derivation (current defaults are internally inconsistent).

Right now defaultConfiguration.silent is hardcoded to false (Line 115), but defaultConfiguration.verbose is still derived from the local silent variable (Line 116), and if (silent) { defaultConfiguration.logLevels = ... } (Lines 146-154) still executes by default. This creates a mixed state (global silent off, but per-category levels forced to silent) that’s hard to reason about and may change test log volume in unexpected ways depending on logger precedence.

Consider making these consistent by deriving everything from a single source of truth, e.g.:

  • set defaultConfiguration.silent from the computed silent variable again, or
  • remove/adjust the if (silent) { ... } block and compute verbose/logLevels off defaultConfiguration.silent.

99-118: PR title: add a scope for changelog clarity.

Suggestion: feat(cloudcode): add trigger response HTTP status codes and headers (or feat(rest): ... if the main surface is REST responses).

src/Triggers/TriggerResponse.js (2)

2-4: Consider returning this from setter methods for fluent chaining.

The status() method doesn't return this, preventing fluent API usage like response.status(201).setHeader('X-Custom', 'value'). This is a common pattern for builder-style classes.

  status(status) {
    this._status = status;
+   return this;
  }

5-8: Return this for method chaining consistency.

Same as status(), returning this enables fluent chaining.

  setHeader(name, value) {
    this._headers = this._headers || {};
    this._headers[name] = value;
+   return this;
  }
src/RestQuery.js (1)

38-49: Update JSDoc to document the new response parameter.

The new response parameter is added to the function signature but is not documented in the JSDoc comment above (lines 23-36).

Add documentation for the new parameter:

 * @param options.runBeforeFind {boolean} Whether to run the beforeFind trigger
 * @param options.context {object} The context object for the query
+* @param options.response {object} The response object for trigger customization
 * @returns {Promise<_UnsafeRestQuery>} A promise that is resolved with the _UnsafeRestQuery object
src/Triggers/ConfigTrigger.js (1)

17-24: Consider using consistent class name representation in logging.

Line 7 uses getClassName(Parse.Config) to get the class name, but line 19 hardcodes 'Parse.Config' for logging. Consider using the resolved class name for consistency.

      logTriggerSuccessBeforeHook(
        triggerType,
-       'Parse.Config',
+       GlobalConfigClassName,
        configObject,
        result,
        auth,
        config.logLevels.triggerBeforeSuccess
      );

And similarly for the error logging on line 29.

src/Triggers/Logger.js (1)

33-47: Consider truncating error output and handling JSON.stringify failures.

The error message at line 39 uses JSON.stringify(error) without truncation, unlike cleanInput. If the error object is large or contains circular references, this could cause issues:

  1. Large error objects may bloat logs
  2. Circular references will throw an exception
 export function logTriggerErrorBeforeHook(triggerType, className, input, auth, error, logLevel) {
   if (logLevel === 'silent') {
     return;
   }
   const cleanInput = logger.truncateLogMessage(JSON.stringify(input));
+  let errorString;
+  try {
+    errorString = logger.truncateLogMessage(JSON.stringify(error));
+  } catch {
+    errorString = String(error);
+  }
   logger[logLevel](
-    `${triggerType} failed for ${className} for user ${auth?.user?.id}:\n  Input: ${cleanInput}\n  Error: ${JSON.stringify(error)}`,
+    `${triggerType} failed for ${className} for user ${auth?.user?.id}:\n  Input: ${cleanInput}\n  Error: ${errorString}`,
     {
       className,
       triggerType,
       error,
       user: auth?.user?.id,
     }
   );
 }
src/PromiseRouter.js (1)

162-166: Consider defensive check for headers object.

While unlikely, if result.headers is truthy but not an object (e.g., an array or primitive), Object.entries could produce unexpected results.

       if (result.headers) {
+        if (typeof result.headers !== 'object' || result.headers === null) {
+          log.error('result.headers must be an object');
+        } else {
           for (const [header, value] of Object.entries(result.headers)) {
             res.set(header, value);
           }
+        }
       }

This is a minor defensive improvement.

src/Routers/ClassesRouter.js (2)

93-94: Use strict equality for length comparison.

Prefer === over == for consistency with JavaScript best practices.

-    if (!response.results || response.results.length == 0) {
+    if (!response.results || response.results.length === 0) {
       throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
     }

101-101: Use strict equality for objectId comparison.

Same as above—prefer === for type-safe comparison.

-      if (req.auth.user && user.objectId == req.auth.user.id) {
+      if (req.auth.user && user.objectId === req.auth.user.id) {
src/Triggers/Utils.js (1)

11-17: Use strict equality for type check.

typeof data == 'object' should use === for consistency. Also note this will return true for null.

 export function inflate(data, restObject) {
-  const copy = typeof data == 'object' ? data : { className: data };
+  const copy = typeof data === 'object' && data !== null ? data : { className: data };
   for (const key in restObject) {
     copy[key] = restObject[key];
   }
   return Parse.Object.fromJSON(copy);
 }
src/Triggers/Validator.js (2)

30-35: Throwing string literals instead of Error objects.

Throwing strings instead of Error objects loses stack trace information. This pattern appears throughout the file. Consider throwing Error objects or relying on resolveError to wrap them.

 const requiredParam = (params, key) => {
   const value = params[key];
   if (value == null) {
-    throw `Validation failed. Please specify data for ${key}.`;
+    throw new Error(`Validation failed. Please specify data for ${key}.`);
   }
 };

40-48: Confusing boolean logic.

!result && result != null is true only when result === false. The condition and comment intent are unclear—consider simplifying for readability.

-      if (!result && result != null) {
+      if (result === false) {
         throw opt.error || `Validation failed. Invalid value for ${key}.`;
       }
src/Triggers/Trigger.js (2)

64-66: Inconsistent return values for missing parseObject.

Line 65 returns {} when parseObject is falsy, but line 70 returns undefined when trigger is missing. Consider aligning these for consistent caller handling.

     if (!parseObject) {
-      return {};
+      return;
     }

137-144: Remove commented-out code.

Commented-out code without explanation adds noise. Either remove it or add a comment explaining why it's preserved.

src/Triggers/QueryTrigger.js (1)

178-206: Align request shape with getRequestObject context copying

getRequestObject() copies context via Object.assign({}, context) (see src/Triggers/Trigger.js snippet), but getRequestQueryObject() uses context: context || {} (no copy). Consider copying here too to avoid unexpected mutations between trigger phases.

-    context: context || {},
+    context: Object.assign({}, context),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b6ef03 and d7a804b.

📒 Files selected for processing (23)
  • spec/CloudCode.spec.js (1 hunks)
  • spec/helper.js (1 hunks)
  • src/Controllers/AdaptableController.js (1 hunks)
  • src/Controllers/LiveQueryController.js (1 hunks)
  • src/PromiseRouter.js (1 hunks)
  • src/RestQuery.js (2 hunks)
  • src/RestWrite.js (5 hunks)
  • src/Routers/ClassesRouter.js (5 hunks)
  • src/Routers/FilesRouter.js (3 hunks)
  • src/Routers/FunctionsRouter.js (3 hunks)
  • src/Routers/UsersRouter.js (3 hunks)
  • src/Triggers/ConfigTrigger.js (1 hunks)
  • src/Triggers/FileTrigger.js (1 hunks)
  • src/Triggers/Logger.js (1 hunks)
  • src/Triggers/QueryTrigger.js (1 hunks)
  • src/Triggers/Trigger.js (1 hunks)
  • src/Triggers/TriggerResponse.js (1 hunks)
  • src/Triggers/TriggerStore.js (1 hunks)
  • src/Triggers/Utils.js (1 hunks)
  • src/Triggers/Validator.js (1 hunks)
  • src/cloud-code/Parse.Cloud.js (1 hunks)
  • src/rest.js (8 hunks)
  • src/triggers.js (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-11-08T13:46:04.940Z
Learning: When reviewing Parse Server PRs that add new features, always check whether the feature is documented in the README.md file, though for new Parse Server options this is optional rather than required.
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 0
File: :0-0
Timestamp: 2025-11-17T15:02:48.786Z
Learning: For Parse Server PRs, always suggest an Angular commit convention PR title that would make a meaningful changelog entry for developers. Update the PR title suggestion on every commit. The format should be: type(scope): description. Common types include feat, fix, perf, refactor, docs, test, chore. The scope should identify the subsystem (e.g., graphql, rest, push, security). The description should be action-oriented and clearly convey the change's impact to developers.
📚 Learning: 2025-08-27T12:33:06.237Z
Learnt from: EmpiDev
Repo: parse-community/parse-server PR: 9770
File: src/triggers.js:467-477
Timestamp: 2025-08-27T12:33:06.237Z
Learning: In the Parse Server codebase, maybeRunAfterFindTrigger is called in production with Parse.Query objects constructed via withJSON(), so the plain object query handling bug only affects tests, not production code paths.

Applied to files:

  • src/Triggers/ConfigTrigger.js
  • src/Triggers/FileTrigger.js
  • src/RestQuery.js
  • src/Triggers/Validator.js
  • src/Triggers/QueryTrigger.js
  • src/rest.js
  • src/Triggers/Trigger.js
  • src/Routers/ClassesRouter.js
  • src/Routers/FilesRouter.js
  • src/triggers.js
📚 Learning: 2025-04-30T19:31:35.344Z
Learnt from: RahulLanjewar93
Repo: parse-community/parse-server PR: 9744
File: spec/ParseLiveQuery.spec.js:0-0
Timestamp: 2025-04-30T19:31:35.344Z
Learning: In the Parse Server codebase, the functions in QueryTools.js are typically tested through end-to-end behavior tests rather than direct unit tests, even though the functions are exported from the module.

Applied to files:

  • src/Triggers/Utils.js
  • spec/CloudCode.spec.js
  • src/Triggers/QueryTrigger.js
  • src/Routers/ClassesRouter.js
  • src/triggers.js
📚 Learning: 2025-08-26T14:06:31.853Z
Learnt from: EmpiDev
Repo: parse-community/parse-server PR: 9770
File: spec/CloudCode.spec.js:446-469
Timestamp: 2025-08-26T14:06:31.853Z
Learning: In the Parse Server codebase, when handling query objects in maybeRunAfterFindTrigger, objects without a where property that contain options like limit/skip should be treated as query JSON with an empty where clause using the spread pattern { where: {}, ...query }, not nested as { where: query }.

Applied to files:

  • src/RestQuery.js
  • src/Triggers/Validator.js
  • src/Triggers/QueryTrigger.js
  • src/Triggers/Trigger.js
  • src/Routers/ClassesRouter.js
  • src/triggers.js
📚 Learning: 2025-10-16T19:27:05.311Z
Learnt from: Moumouls
Repo: parse-community/parse-server PR: 9883
File: spec/CloudCodeLogger.spec.js:410-412
Timestamp: 2025-10-16T19:27:05.311Z
Learning: In spec/CloudCodeLogger.spec.js, the test "should log cloud function triggers using the silent log level" (around lines 383-420) is known to be flaky and requires the extra `await new Promise(resolve => setTimeout(resolve, 100))` timeout after awaiting `afterSavePromise` for reliability, even though it may appear redundant.

Applied to files:

  • spec/CloudCode.spec.js
  • src/Routers/FunctionsRouter.js
  • src/Triggers/Logger.js
  • spec/helper.js
  • src/triggers.js
📚 Learning: 2025-05-04T20:41:05.147Z
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 9445
File: spec/ParseLiveQuery.spec.js:1312-1338
Timestamp: 2025-05-04T20:41:05.147Z
Learning: New tests in the parse-server repository should use async/await with promise-based patterns rather than callback patterns with `done()`.

Applied to files:

  • spec/CloudCode.spec.js
  • src/Routers/FunctionsRouter.js
  • src/PromiseRouter.js
📚 Learning: 2025-05-09T09:59:06.289Z
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 9445
File: spec/ParseLiveQuery.spec.js:1340-1375
Timestamp: 2025-05-09T09:59:06.289Z
Learning: Tests in the parse-server repository should use promise-based approaches rather than callback patterns with `done()`. Use a pattern where a Promise is created that resolves when the event occurs, then await that promise.

Applied to files:

  • spec/CloudCode.spec.js
  • src/Routers/FunctionsRouter.js
  • src/PromiseRouter.js
📚 Learning: 2025-08-27T09:08:34.252Z
Learnt from: EmpiDev
Repo: parse-community/parse-server PR: 9770
File: src/triggers.js:446-454
Timestamp: 2025-08-27T09:08:34.252Z
Learning: When analyzing function signature changes in Parse Server codebase, verify that call sites are actually incorrect before flagging them. Passing tests are a strong indicator that function calls are already properly aligned with new signatures.

Applied to files:

  • spec/CloudCode.spec.js
  • src/cloud-code/Parse.Cloud.js
📚 Learning: 2025-05-09T09:59:06.289Z
Learnt from: mtrezza
Repo: parse-community/parse-server PR: 9445
File: spec/ParseLiveQuery.spec.js:1340-1375
Timestamp: 2025-05-09T09:59:06.289Z
Learning: New tests in the parse-server repository should use async/await with promise-based patterns rather than callback patterns with `done()`. The preferred pattern is to create a Promise that resolves when an expected event occurs, then await that Promise.

Applied to files:

  • spec/CloudCode.spec.js
  • src/Routers/FunctionsRouter.js
  • src/PromiseRouter.js
🧬 Code graph analysis (14)
src/Triggers/ConfigTrigger.js (5)
src/Triggers/Utils.js (2)
  • getClassName (1-9)
  • error (38-38)
src/Triggers/TriggerStore.js (1)
  • getTrigger (162-167)
src/Triggers/Trigger.js (4)
  • request (14-21)
  • request (73-80)
  • getRequestObject (6-52)
  • error (115-118)
src/Triggers/Validator.js (2)
  • maybeRunValidator (4-28)
  • result (41-41)
src/Triggers/Logger.js (2)
  • logTriggerSuccessBeforeHook (17-31)
  • logTriggerErrorBeforeHook (33-47)
src/Routers/UsersRouter.js (2)
src/Triggers/TriggerResponse.js (1)
  • TriggerResponse (1-18)
src/Triggers/Trigger.js (1)
  • maybeRunTrigger (54-130)
src/Triggers/FileTrigger.js (4)
src/Triggers/Utils.js (2)
  • getClassName (1-9)
  • error (38-38)
src/Triggers/TriggerStore.js (1)
  • getTrigger (162-167)
src/Triggers/Validator.js (2)
  • maybeRunValidator (4-28)
  • result (41-41)
src/Triggers/Logger.js (2)
  • logTriggerSuccessBeforeHook (17-31)
  • logTriggerErrorBeforeHook (33-47)
src/Triggers/Utils.js (6)
src/GraphQL/transformers/mutation.js (1)
  • parseClass (16-16)
src/cloud-code/Parse.Cloud.js (12)
  • data (68-68)
  • className (182-182)
  • className (229-229)
  • className (275-275)
  • className (313-313)
  • className (342-342)
  • className (378-378)
  • className (414-414)
  • className (450-450)
  • className (497-497)
  • className (598-598)
  • className (636-636)
src/Adapters/Storage/Mongo/MongoTransform.js (1)
  • restObject (1136-1136)
src/Triggers/QueryTrigger.js (2)
  • error (64-67)
  • error (170-173)
src/Triggers/Trigger.js (1)
  • error (115-118)
src/RestQuery.js (1)
  • className (964-964)
src/RestQuery.js (3)
src/PromiseRouter.js (1)
  • method (141-141)
src/Triggers/QueryTrigger.js (1)
  • response (47-47)
src/Triggers/Trigger.js (1)
  • response (88-88)
src/Routers/FunctionsRouter.js (2)
src/Triggers/Trigger.js (3)
  • request (14-21)
  • request (73-80)
  • response (88-88)
src/Triggers/TriggerResponse.js (1)
  • TriggerResponse (1-18)
src/Triggers/Validator.js (2)
src/Triggers/TriggerStore.js (1)
  • getValidator (173-175)
src/Triggers/Utils.js (1)
  • resolveError (19-43)
src/Triggers/TriggerResponse.js (5)
src/PromiseRouter.js (1)
  • status (159-159)
src/Triggers/Validator.js (1)
  • value (31-31)
src/RestWrite.js (1)
  • value (1804-1804)
src/Triggers/QueryTrigger.js (1)
  • response (47-47)
src/Triggers/Trigger.js (1)
  • response (88-88)
src/Triggers/QueryTrigger.js (5)
src/Triggers/Trigger.js (6)
  • trigger (68-68)
  • request (14-21)
  • request (73-80)
  • getRequestObject (6-52)
  • object (100-100)
  • error (115-118)
src/Triggers/TriggerStore.js (1)
  • getTrigger (162-167)
src/Triggers/Logger.js (2)
  • logTriggerSuccessBeforeHook (17-31)
  • logTriggerAfterHook (2-15)
src/Triggers/Validator.js (2)
  • maybeRunValidator (4-28)
  • result (41-41)
src/Triggers/Utils.js (3)
  • toJSONwithObjects (45-64)
  • error (38-38)
  • resolveError (19-43)
src/rest.js (2)
src/Triggers/QueryTrigger.js (1)
  • response (47-47)
src/Triggers/Trigger.js (1)
  • response (88-88)
src/Routers/ClassesRouter.js (1)
src/Triggers/TriggerResponse.js (1)
  • TriggerResponse (1-18)
src/Routers/FilesRouter.js (1)
src/Triggers/TriggerResponse.js (1)
  • TriggerResponse (1-18)
src/cloud-code/Parse.Cloud.js (4)
src/RestQuery.js (1)
  • triggers (6-6)
src/Routers/FilesRouter.js (1)
  • triggers (8-8)
spec/ParseLiveQuery.spec.js (1)
  • triggers (6-6)
src/Routers/CloudCodeRouter.js (1)
  • triggers (4-4)
src/Triggers/TriggerStore.js (1)
spec/CloudCode.spec.js (1)
  • Parse (3-3)
🪛 Biome (2.1.2)
src/Triggers/TriggerStore.js

[error] 158-158: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 158-158: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 158-158: Type annotations are a TypeScript only feature. Convert your file to a TypeScript file or remove the syntax.

TypeScript only syntax

(parse)


[error] 158-158: return types can only be used in TypeScript files

(parse)

🪛 GitHub Check: Lint
src/Triggers/QueryTrigger.js

[failure] 5-5:
'logTriggerErrorBeforeHook' is defined but never used

🔇 Additional comments (34)
src/Controllers/AdaptableController.js (1)

62-64: Critical: Adapter validation is disabled and breaks existing tests.

The commented-out throw statement at line 63 disables prototype mismatch validation, which violates the adapter contract. The test suite at lines 32–50 of spec/AdaptableController.spec.js explicitly expects validation to throw when an incompatible adapter (empty function with no methods) is provided, but with the throw disabled, these tests will fail.

This validation is essential—without it, incompatible adapters pass silently and cause runtime failures when their methods are called. Either re-enable the throw or update the tests if this behavior change is intentional and documented.

Note: The Error constructor syntax is also incorrect—Error doesn't accept adapter/mismatches as 2nd/3rd parameters; pass an object or single message string instead.

⛔ Skipped due to learnings
Learnt from: Moumouls
Repo: parse-community/parse-server PR: 9883
File: spec/CloudCodeLogger.spec.js:410-412
Timestamp: 2025-10-16T19:27:05.311Z
Learning: In spec/CloudCodeLogger.spec.js, the test "should log cloud function triggers using the silent log level" (around lines 383-420) is known to be flaky and requires the extra `await new Promise(resolve => setTimeout(resolve, 100))` timeout after awaiting `afterSavePromise` for reliability, even though it may appear redundant.
Learnt from: EmpiDev
Repo: parse-community/parse-server PR: 9770
File: src/triggers.js:446-454
Timestamp: 2025-08-27T09:08:34.252Z
Learning: When analyzing function signature changes in Parse Server codebase, verify that call sites are actually incorrect before flagging them. Passing tests are a strong indicator that function calls are already properly aligned with new signatures.
src/Triggers/TriggerResponse.js (1)

10-17: LGTM - clean response object construction.

The fallback logic for status (this._status || response.status) correctly handles both explicitly set status and inherited status from the response object.

src/Controllers/LiveQueryController.js (1)

3-3: LGTM - import path normalization.

Minor cleanup changing the import path from ./../triggers to ../triggers. Both resolve identically; the new form is more conventional.

src/Routers/UsersRouter.js (2)

271-280: LGTM - TriggerResponse integration for beforeLogin.

The beforeLoginResponse instance is correctly created and passed to maybeRunTrigger, allowing the beforeLogin trigger to set custom status codes and headers. The trigger signature aligns with src/Triggers/Trigger.js which accepts responseObject as the last parameter.


320-320: LGTM - Response wrapped correctly.

The response is properly wrapped using toResponseObject(), which will include any status/headers set by the beforeLogin trigger while preserving the user data.

src/cloud-code/Parse.Cloud.js (1)

532-538: LGTM - Trigger registration unified.

The change from a specialized addConnectTrigger to the generic addTrigger with '@Connect' as the class name aligns with the broader refactoring to centralize trigger registration. The '@Connect' naming convention is consistent with other special classes ('@File', '@Config') used elsewhere in the file (lines 81-82).

src/Routers/FilesRouter.js (2)

193-200: LGTM - TriggerResponse correctly integrated with beforeSave file trigger.

The triggerResponse is properly created and passed to the beforeSave trigger, allowing cloud code to customize the HTTP response status and headers for file uploads.


250-256: LGTM - Response status and headers applied correctly.

The implementation safely handles undefined _headers via the || {} fallback. The default status of 201 is appropriate for resource creation. The Location header is correctly set after custom headers, ensuring it's always present.

src/RestQuery.js (1)

55-66: LGTM - Response parameter correctly threaded to beforeFind trigger.

The response parameter is properly passed through to maybeRunQueryTrigger, enabling the beforeFind trigger to customize response metadata. The conditional execution based on runBeforeFind is preserved.

src/Triggers/ConfigTrigger.js (1)

10-16: LGTM - Validation and skipWithMasterKey handling.

The pattern of running validation first and checking skipWithMasterKey before executing the trigger is consistent with other trigger implementations. Returning configObject when skipping ensures the original config is preserved.

src/Triggers/Logger.js (1)

2-15: LGTM - Well-structured logging utility.

The early return pattern for silent log level and consistent use of truncateLogMessage for input data are good practices. The structured logging metadata (className, triggerType, user) enables effective log filtering.

spec/CloudCode.spec.js (2)

4106-4175: Good test coverage for save and delete hooks with custom HTTP codes.

The tests correctly verify that custom status codes and headers propagate through save and delete hooks. The use of native fetch is appropriate for verifying raw HTTP response details that the Parse SDK would abstract away.


4241-4275: Good test coverage for beforeLogin hook with custom HTTP codes.

Tests properly set up a user, then verify that custom status codes and headers are returned during login. The async/await pattern is correctly used.

src/PromiseRouter.js (1)

137-185: Well-structured async/await refactor with proper error handling.

The refactoring from promise chains to async/await improves readability. Key improvements:

  • Proper validation that handlers return required fields (line 152-155)
  • Clean header iteration using Object.entries (line 163-165)
  • Correct Express error propagation via next(error) (line 183)
  • Template literal for redirect message (line 176)

The spread operator for body cloning ({ ...req.body }) is appropriate for shallow cloning the request body.

src/Routers/FunctionsRouter.js (2)

106-175: Clean async/await refactor with TriggerResponse integration.

The refactoring improves code clarity:

  • Request construction is cleaner with optional chaining (lines 120-121)
  • TriggerResponse properly encapsulates HTTP response configuration
  • Logging is consistent between success and error paths
  • Error handling properly uses triggers.resolveError for standardization

The pattern of passing both request and response to cloud functions (line 139) aligns with the PR objective of allowing developers to set custom HTTP status codes and headers.


154-158: toResponseObject signature is correct. The method expects a parameter object with a response property. The usage in lines 154-158, wrapping the encoded result as { response: { result: Parse._encode(result) } }, properly matches the method's implementation which extracts the response property from the parameter object.

src/RestWrite.js (3)

279-287: Properly propagates responseObject to beforeSave trigger.

The responseObject is correctly passed to maybeRunTrigger, enabling beforeSave hooks to set custom HTTP status codes and headers.


332-340: Properly propagates responseObject to beforeLogin trigger.

Consistent with the beforeSave pattern, enabling beforeLogin hooks to customize HTTP responses.


1668-1677: Properly propagates responseObject to afterSave trigger.

Completes the trigger propagation pattern. Note that afterSave changes to HTTP response may have limited effect since the response might already be sent, but this maintains API consistency across all trigger types.

src/Triggers/FileTrigger.js (2)

6-29: LGTM!

The getRequestFileObject function follows the established pattern for building request objects, properly handling auth cases and spreading file object properties.


31-36: The responseObject parameter is used correctly in the beforeSave file trigger.

The responseObject is properly passed to the file trigger callback at line 43 and is intended to allow triggers to modify HTTP response status and headers. In FilesRouter.js, the beforeSave file trigger receives a TriggerResponse object, which is then used to set the HTTP status code and headers on the response (lines 250-252). Other file trigger types (afterSave, beforeDelete, afterDelete) don't receive a responseObject parameter, which is intentional since responses are already sent or the trigger runs after the operation completes.

src/Routers/ClassesRouter.js (2)

22-49: LGTM!

The async/await refactoring with TriggerResponse wrapping is clean and consistent. The control flow is straightforward.


150-157: LGTM!

The delete handler correctly creates a TriggerResponse, passes it to rest.del, and returns an empty response object.

src/Triggers/Utils.js (2)

1-9: LGTM!

The getClassName function handles the common cases for resolving class names with appropriate fallbacks.


45-64: LGTM overall for toJSONwithObjects.

The function correctly handles edge cases (missing object/toJSON) and properly processes pending operations to include full JSON for nested objects.

src/Triggers/Validator.js (2)

4-28: LGTM!

The maybeRunValidator function properly handles both object-based and function validators, with appropriate error normalization via resolveError.


113-121: Remove this comment. Input validation strictly enforces that only arrays and functions are allowed for these options, preventing the scenario described.

The codebase validates validator parameters at definition time via allowedKeys (Parse.Cloud.js, lines 22-25), throwing an error immediately if non-array/non-function values are provided. The optional chaining with function call syntax is safe and properly protected by input validation.

Likely an incorrect or invalid review comment.

src/Triggers/Trigger.js (3)

6-52: LGTM!

The getRequestObject function properly constructs the request object with appropriate context copying and auth augmentation for supported trigger types.


166-175: LGTM!

The runTrigger helper is a clean, minimal wrapper that properly handles validation and skipWithMasterKey.


90-110: Not redundant logging—intentional dual logging of different information.

The two log calls serve different purposes: logTriggerAfterHook (lines 91–97) logs the input before the trigger executes, while logTriggerSuccessBeforeHook (lines 101–110) logs both the input and the processed result after trigger execution. For after-triggers, this provides visibility into both the pre-trigger state and the post-trigger outcome. The function naming is potentially confusing, but the logging pattern is intentional.

src/rest.js (2)

28-58: Response threading into RestQuery looks consistent for find/get


162-218: RestWrite constructor parameters are correct in both create and update

The parameter order is consistent and correct in both calls. The constructor signature is RestWrite(config, auth, className, query, data, originalData, clientSDK, context, action, responseObject), and both create() and update() pass arguments in the correct order. Using undefined for the action parameter in create() and 'update' in update() is intentional—the constructor comments clarify that query is null for creates and non-null for updates, which correlates with the action parameter values.

src/Triggers/TriggerStore.js (1)

1-208: No actionable issues found; code follows intended architectural patterns.

Verification confirms that type annotations in .js files are permitted by Biome configuration (unsafeParameterDecoratorsEnabled: true), Parse is globally available via ParseServer injection, and linting passes without errors. The code functions correctly as designed.

src/triggers.js (1)

1-36: Aggregator re-export approach is clean and keeps the public surface explicit.

All 25 exported names are confirmed to exist in their respective source modules, and multiple downstream imports (Auth.js, Controllers, Routers, LiveQuery, GraphQL) successfully use these re-exports with no missing or incorrect names.

Comment on lines +4277 to +4294
it('should set custom statusCode in file trigger', async () => {
Parse.Cloud.beforeSave(Parse.File, (req, res) => {
res.status(201);
});

const file = new Parse.File('test.txt', [1, 2, 3]);
const response = await fetch('http://localhost:8378/1/files/test.txt', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'text/plain',
},
body: file.getData()
});

expect(response.status).toBe(201);
});
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

Bug: file.getData() returns a Promise and must be awaited.

Parse.File.getData() returns a Promise, but it's passed directly to the fetch body without awaiting. This will cause the request body to be [object Promise] instead of the actual file data.

   it('should set custom statusCode in file trigger', async () => {
     Parse.Cloud.beforeSave(Parse.File, (req, res) => {
       res.status(201);
     });

     const file = new Parse.File('test.txt', [1, 2, 3]);
     const response = await fetch('http://localhost:8378/1/files/test.txt', {
       method: 'POST',
       headers: {
         'X-Parse-Application-Id': 'test',
         'X-Parse-REST-API-Key': 'rest',
         'Content-Type': 'text/plain',
       },
-      body: file.getData()
+      body: new Uint8Array([1, 2, 3])
     });

     expect(response.status).toBe(201);
   });

Alternatively, since the file hasn't been saved yet, you can use the raw data directly or create a proper buffer/blob for the request body.

🤖 Prompt for AI Agents
In spec/CloudCode.spec.js around lines 4277 to 4294, the test passes
file.getData() (which returns a Promise) directly as the fetch body, causing the
request body to be a Promise instead of the actual bytes; fix by awaiting the
file data before calling fetch (e.g., const data = await file.getData()) and
then pass that resolved data (or convert it to a Buffer/Blob if needed) as the
fetch body so the request sends the actual file bytes.

Comment on lines +4296 to +4313
it('should set custom headers in file trigger', async () => {
Parse.Cloud.beforeSave(Parse.File, (req, res) => {
res.setHeader('X-Custom-Header', 'custom-value');
});

const file = new Parse.File('test.txt', [1, 2, 3]);
const response = await fetch('http://localhost:8378/1/files/test.txt', {
method: 'POST',
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'Content-Type': 'text/plain',
},
body: file.getData()
});

expect(response.headers.get('X-Custom-Header')).toBe('custom-value');
});
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

Same issue: file.getData() returns a Promise.

This test has the same problem as the previous one.

   it('should set custom headers in file trigger', async () => {
     Parse.Cloud.beforeSave(Parse.File, (req, res) => {
       res.setHeader('X-Custom-Header', 'custom-value');
     });

     const file = new Parse.File('test.txt', [1, 2, 3]);
     const response = await fetch('http://localhost:8378/1/files/test.txt', {
       method: 'POST',
       headers: {
         'X-Parse-Application-Id': 'test',
         'X-Parse-REST-API-Key': 'rest',
         'Content-Type': 'text/plain',
       },
-      body: file.getData()
+      body: new Uint8Array([1, 2, 3])
     });

     expect(response.headers.get('X-Custom-Header')).toBe('custom-value');
   });
🤖 Prompt for AI Agents
In spec/CloudCode.spec.js around lines 4296 to 4313, the test passes
file.getData() directly to fetch even though file.getData() returns a Promise;
await the promise before using it as the request body (e.g., const data = await
file.getData(); use data in fetch) so the fetch receives the actual file data
rather than a Promise.

Comment on lines +1 to +7
import { getRequestObject } from './Trigger';
import { maybeRunValidator } from "./Validator";
import { logTriggerSuccessBeforeHook, logTriggerErrorBeforeHook } from './Logger';
import { getClassName } from './Utils';
import { getTrigger } from './TriggerStore';
export async function maybeRunGlobalConfigTrigger(triggerType, auth, configObject, originalConfigObject, config, context) {
const GlobalConfigClassName = getClassName(Parse.Config);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing Parse import will cause ReferenceError.

Parse.Config is referenced on line 7, but Parse is never imported. This will cause a runtime error when maybeRunGlobalConfigTrigger is called.

 import { getRequestObject } from './Trigger';
 import { maybeRunValidator } from "./Validator";
 import { logTriggerSuccessBeforeHook, logTriggerErrorBeforeHook } from './Logger';
 import { getClassName } from './Utils';
 import { getTrigger } from './TriggerStore';
+import Parse from 'parse/node';
+
 export async function maybeRunGlobalConfigTrigger(triggerType, auth, configObject, originalConfigObject, config, context) {
📝 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
import { getRequestObject } from './Trigger';
import { maybeRunValidator } from "./Validator";
import { logTriggerSuccessBeforeHook, logTriggerErrorBeforeHook } from './Logger';
import { getClassName } from './Utils';
import { getTrigger } from './TriggerStore';
export async function maybeRunGlobalConfigTrigger(triggerType, auth, configObject, originalConfigObject, config, context) {
const GlobalConfigClassName = getClassName(Parse.Config);
import { getRequestObject } from './Trigger';
import { maybeRunValidator } from "./Validator";
import { logTriggerSuccessBeforeHook, logTriggerErrorBeforeHook } from './Logger';
import { getClassName } from './Utils';
import { getTrigger } from './TriggerStore';
import Parse from 'parse/node';
export async function maybeRunGlobalConfigTrigger(triggerType, auth, configObject, originalConfigObject, config, context) {
const GlobalConfigClassName = getClassName(Parse.Config);
🤖 Prompt for AI Agents
In src/Triggers/ConfigTrigger.js around lines 1 to 7, the code references
Parse.Config but never imports Parse, causing a ReferenceError at runtime; fix
by adding an import for Parse at the top of the file (e.g., import Parse from
'parse/node';) alongside the other imports so Parse.Config is defined when
maybeRunGlobalConfigTrigger is called.

Comment on lines +44 to +51
logTriggerSuccessBeforeHook(
triggerType,
'Parse.File',
{ ...fileObject.file.toJSON(), fileSize: fileObject.fileSize },
result,
auth,
config.logLevels.triggerBeforeSuccess
);
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

Potential null reference when accessing fileObject.file.

If fileObject.file is undefined, calling toJSON() will throw. Consider adding a guard or ensuring the contract guarantees fileObject.file is always present.

     logTriggerSuccessBeforeHook(
       triggerType,
       'Parse.File',
-      { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize },
+      { ...(fileObject.file?.toJSON() || {}), fileSize: fileObject.fileSize },
       result,
       auth,
       config.logLevels.triggerBeforeSuccess
     );
🤖 Prompt for AI Agents
In src/Triggers/FileTrigger.js around lines 44 to 51, the code calls
fileObject.file.toJSON() which will throw if fileObject.file is undefined; add a
guard so you safely handle missing file: replace the direct toJSON call with a
safe expression that checks fileObject.file (e.g., use optional chaining or an
if/ternary) and fall back to an empty object or minimal metadata, then spread
that result and include fileSize as before; ensure this preserves the same shape
for logTriggerSuccessBeforeHook and does not call toJSON when fileObject.file is
falsy.

Comment on lines +54 to +61
logTriggerErrorBeforeHook(
triggerType,
'Parse.File',
{ ...fileObject.file.toJSON(), fileSize: fileObject.fileSize },
auth,
error,
config.logLevels.triggerBeforeError
);
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

Same defensive check needed in error logging path.

Apply the same guard for fileObject.file in the error handler to prevent masking the original error with a null reference exception.

     logTriggerErrorBeforeHook(
       triggerType,
       'Parse.File',
-      { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize },
+      { ...(fileObject.file?.toJSON() || {}), fileSize: fileObject.fileSize },
       auth,
       error,
       config.logLevels.triggerBeforeError
     );
📝 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
logTriggerErrorBeforeHook(
triggerType,
'Parse.File',
{ ...fileObject.file.toJSON(), fileSize: fileObject.fileSize },
auth,
error,
config.logLevels.triggerBeforeError
);
logTriggerErrorBeforeHook(
triggerType,
'Parse.File',
{ ...(fileObject.file?.toJSON() || {}), fileSize: fileObject.fileSize },
auth,
error,
config.logLevels.triggerBeforeError
);
🤖 Prompt for AI Agents
In src/Triggers/FileTrigger.js around lines 54 to 61, the error-logging call
accesses fileObject.file without checking it and can throw a null reference,
masking the original error; add the same defensive guard used elsewhere (e.g.,
check fileObject.file exists before calling toJSON or use a safe fallback like
undefined or an object with minimal fields) so the logTriggerErrorBeforeHook
receives { ...fileObject.file?.toJSON && fileObject.file.toJSON ?
fileObject.file.toJSON() : {}, fileSize: fileObject.fileSize } (or equivalent
conditional) and ensure the original error is passed through unchanged.

Comment on lines +113 to +128
} catch (e) {

const error = resolveError(e, {
code: Parse.Error.SCRIPT_FAILED,
message: 'Script failed.',
});

logTriggerErrorBeforeHook(
triggerType,
parseObject.className,
parseObject.toJSON(),
auth,
error,
config.logLevels.triggerBeforeError
);
throw error;
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

Null reference risk in error handler.

If the try block fails early (e.g., due to parseObject being null after initial check), parseObject.className and parseObject.toJSON() at lines 122-123 will throw, masking the original error.

   } catch (e) {
-
     const error = resolveError(e, {
       code: Parse.Error.SCRIPT_FAILED,
       message: 'Script failed.',
     });

     logTriggerErrorBeforeHook(
       triggerType,
-      parseObject.className,
-      parseObject.toJSON(),
+      parseObject?.className,
+      parseObject?.toJSON() || {},
       auth,
       error,
       config.logLevels.triggerBeforeError
     );
     throw error;
   }
🤖 Prompt for AI Agents
In src/Triggers/Trigger.js around lines 113 to 128, the catch block assumes
parseObject is non-null and directly accesses parseObject.className and
parseObject.toJSON(), which can throw and mask the original error; guard those
accesses by first checking parseObject (or use optional chaining) and provide
safe fallbacks (e.g., classNameFallback = parseObject?.className || '<unknown>'
and objectJson = parseObject ? safeSerialize(parseObject) : '<no-object>')
before calling logTriggerErrorBeforeHook, or wrap the logging call in its own
try/catch so logging failures don’t override the original error, then rethrow
the original resolved error.

Comment on lines +38 to +42
const error = new Parse.Error(code, message.message || message);
if (message instanceof Error) {
error.stack = message.stack;
}
return error;
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

Potential [object Object] in error message.

When message is an object without a message property, message.message || message falls back to the object itself, which may produce unhelpful error text. Consider stringifying or providing a fallback.

-  const error = new Parse.Error(code, message.message || message);
+  const error = new Parse.Error(code, message.message || JSON.stringify(message));
🤖 Prompt for AI Agents
In src/Triggers/Utils.js around lines 38 to 42, the code constructs a
Parse.Error using message.message || message which can yield "[object Object]"
when message is a plain object; change the creation to normalize the message
into a string first (e.g., if message is an Error use its message, else if
typeof message === 'object' use JSON.stringify(message) or a safe fallback like
String(message) or '<no message>'), then pass that string into new Parse.Error
and preserve the original stack when message is an Error.

Comment on lines +50 to +51
const stateController = Parse.CoreManager.getObjectStateController();
const [pending] = stateController.getPendingOps(object._getStateIdentifier());
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

🌐 Web query:

Parse JavaScript SDK _getStateIdentifier method documentation

💡 Result:

Which JavaScript SDK do you mean? I need the SDK name (or a link/file) that contains the _getStateIdentifier method to parse its documentation.


🌐 Web query:

Parse JavaScript SDK ObjectStateController public API alternative

💡 Result:

Short answer: don't use an internal ObjectStateController — use the SDK's public CoreManager controllers or the Parse.Object API instead.

Details and alternatives:

  • The JS SDK exposes a CoreManager for swapping controller implementations (RESTController, StorageController, LocalDatastoreController, etc.). Use Parse.CoreManager.set / .get to provide or replace official controllers rather than relying on any internal ObjectStateController. [1][2]
  • For per-object state you should use the public Parse.Object methods (get/set/save/fetch/createdAt/updatedAt, static helpers like createWithoutData, and the local datastore pin/unpin APIs) rather than poking at internal state controllers. [3][4]
  • If you need custom persistence behavior (client-side storage or serialization), implement and register a custom StorageController or LocalDatastoreController via CoreManager before initializing the SDK. [1][2]

References:

  1. Parse JS SDK CoreManager / controllers docs. [parseplatform.org / Parse-SDK-JS docs]. [1]
  2. Parse-SDK-JS repository (controllers and CoreManager usage). [github.com/parse-community/Parse-SDK-JS]. [2]
  3. Parse.Object public API (get/set/save, local datastore pin/unpin). [parseplatform.org Parse.Object]. [3]
  4. Parse JS Guide (local datastore and controller example). [parseplatform.org JS guide]. [4]

If you want, tell me which ObjectStateController method(s) you were using and I’ll show the exact CoreManager/controller or Parse.Object API replacement.


🌐 Web query:

Parse JavaScript SDK internal methods stability breaking changes

💡 Result:

Short checklist and tools to parse/internal-methods stability and detect breaking changes in a JavaScript/TypeScript SDK:

  1. Classify surface (public/unstable/internal)

    • Mark internal APIs explicitly (naming, TSDoc @internal, package exports). Use semver rules to treat internal as non-guaranteed. [5][1]
  2. Extract and record the API contract

    • Produce a machine-readable API surface (d.ts or JSON) each release (API Extractor / rollup). Compare current vs previous to find signature/visibility changes. [1]
  3. Static semantic/diff checks (beyond signatures)

    • Run tools that compare semantics or call-chains to detect behavioral breaking changes (research tools: Sembid for semantic breaks, academic snapshot techniques like Gilesi). Use these ideas or tools where available. [3][4]
  4. Automated compatibility checks

    • Use api-compare / API extractor diffing in CI to block PRs that change exported symbols or types unexpectedly. Add lint rules to prevent accidental exports. [1][2]
  5. Tests & snapshots

    • Record runtime API interaction snapshots in client tests (or golden outputs) to detect behavioral regressions when SDK changes. Combine unit + integration + client-snapshot testing. [4]
  6. Policy + release practice

    • Map change types to semver (patch/minor/major); bump major for breaking changes and document migration notes. Deprecate first, then remove after at least one major cycle when possible. Automate changelog generation. [5][2]

Practical tools/links to start with:

  • API Extractor (d.ts rollups, API reports) — use for exported-surface diffs. [1]
  • TypeScript “API breaking changes” guidance / changelogs — concrete examples of internal method removals. [2]
  • Research on semantic/dynamic detection (Sembid, Gilesi) for deeper behavioral breaking-change detection. [3][4]
  • SemVer/npm versioning best practices for release policy. [5]

If you want, I can:

    1. Inspect a repository (package.json, exports, .d.ts) and produce an API-diff plan, or
    1. Provide a CI pipeline snippet that runs API Extractor + fail-on-diff + snapshot tests.

Replace internal Parse SDK API with public alternatives.

The code relies on _getStateIdentifier(), which is a documented internal Parse SDK method. Use the public Parse.CoreManager API or Parse.Object public methods (get, set, save, fetch, createdAt, updatedAt) instead. If custom controller behavior is needed, implement and register a custom StorageController or LocalDatastoreController via Parse.CoreManager before SDK initialization.

🤖 Prompt for AI Agents
In src/Triggers/Utils.js around lines 50-51, remove the use of the internal
_getStateIdentifier() and the direct stateController.getPendingOps(...) call;
instead use only public Parse APIs — inspect object changes via public
Parse.Object methods (get, set, fetch, createdAt, updatedAt, or compare current
attributes to a fetched/original copy) to determine pending modifications, or if
you truly need custom pending-op or persistence behavior implement and register
a custom StorageController or LocalDatastoreController with Parse.CoreManager
(via Parse.CoreManager.setStorageController/setLocalDatastoreController) before
SDK initialization and call that public API; replace the internal call with
either attribute comparison logic using public methods or calls to your
registered controller.

Comment on lines +60 to +63
const getType = fn => {
const match = fn && fn.toString().match(/\^\s*function (\w+)/);
return (match ? match[1] : '').toLowerCase();
};
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

Regex pattern has incorrect escape sequence.

The regex \^\s*function (\w+) uses \^ which matches a literal ^ character, not the start of string. This will likely never match function signatures correctly.

 const getType = fn => {
-  const match = fn && fn.toString().match(/\^\s*function (\w+)/);
+  const match = fn && fn.toString().match(/^\s*function (\w+)/);
   return (match ? match[1] : '').toLowerCase();
 };
📝 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
const getType = fn => {
const match = fn && fn.toString().match(/\^\s*function (\w+)/);
return (match ? match[1] : '').toLowerCase();
};
const getType = fn => {
const match = fn && fn.toString().match(/^\s*function (\w+)/);
return (match ? match[1] : '').toLowerCase();
};
🤖 Prompt for AI Agents
In src/Triggers/Validator.js around lines 60 to 63, the regex uses `\^` which
matches a literal caret instead of the start-of-string anchor; replace the
pattern with an anchored regex that matches a function declaration start and
captures the name (for example use an unescaped ^ and require whitespace before
the name), e.g. change to something like /^\s*function\s+([A-Za-z_$][\w$]*)/ and
keep the rest of the logic (take match[1] or empty string and .toLowerCase()) so
function names are detected correctly.

fieldPromises.push(processFields(options.fields, params, request));
}

const roles = await auth.getUserRoles();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how maybeRunValidator is called to verify auth is always provided
ast-grep --pattern 'maybeRunValidator($_, $_, $_)'

Repository: parse-community/parse-server

Length of output: 714


🏁 Script executed:

# Let's also examine the maybeRunValidator function signature and context around line 179
head -n 200 src/Triggers/Validator.js | tail -n 50

Repository: parse-community/parse-server

Length of output: 1652


🏁 Script executed:

# Find the maybeRunValidator function definition
ast-grep --pattern 'function maybeRunValidator($$$)'

Repository: parse-community/parse-server

Length of output: 1431


🏁 Script executed:

# Also search for export/definition patterns
rg -n "maybeRunValidator\s*=" src/Triggers/Validator.js | head -20

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Check the context where maybeRunValidator is called in Trigger.js
rg -B 5 -A 5 "await maybeRunValidator" src/Triggers/Trigger.js

Repository: parse-community/parse-server

Length of output: 517


🏁 Script executed:

# Check what auth object is - look for its definition/type
rg -n "auth\s*=" src/Triggers/Trigger.js | head -20

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Look for Auth class definition to understand if getUserRoles is guaranteed
fd -e js -e ts | xargs rg -l "class Auth" | head -5

Repository: parse-community/parse-server

Length of output: 104


🏁 Script executed:

# Check for getUserRoles method definition
rg -n "getUserRoles" --type js

Repository: parse-community/parse-server

Length of output: 933


🏁 Script executed:

# Look for where runTrigger and other trigger functions are called from
rg -n "runTrigger\(" src/Triggers/Trigger.js -B 10 | head -40

Repository: parse-community/parse-server

Length of output: 312


🏁 Script executed:

# Check where Trigger.js functions are invoked - look for the entry point
rg -n "from.*Triggers" src/rest.js | head -10

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Let's see how auth is created/passed in the trigger execution flow
rg -n "new Auth" src/ --type js | head -20

Repository: parse-community/parse-server

Length of output: 832


🏁 Script executed:

# Check what happens if auth is not provided - search for null/undefined checks
rg -n "auth\s*\?" src/Triggers/Validator.js

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Find where runTrigger is called and what auth is passed
rg -n "runTrigger\s*\(" src/ -B 3 -A 1 --type js | head -60

Repository: parse-community/parse-server

Length of output: 1926


🏁 Script executed:

# Check if there's ever a case where auth might be undefined in trigger flow
rg -n "maybeRunValidator\s*\(" src/ -B 3 | head -80

Repository: parse-community/parse-server

Length of output: 2161


🏁 Script executed:

# Look at DatabaseController to understand trigger execution flow
rg -n "import.*runTrigger" src/ --type js

Repository: parse-community/parse-server

Length of output: 173


🏁 Script executed:

# Check if auth is always created before triggers are called
rg -n "const auth\s*=" src/rest.js -A 2 | head -30

Repository: parse-community/parse-server

Length of output: 54


🏁 Script executed:

# Check LiveQuery context where auth might be undefined
sed -n '220,230p' src/LiveQuery/ParseLiveQueryServer.js

Repository: parse-community/parse-server

Length of output: 536


🏁 Script executed:

# Check line 373-376 context in LiveQuery
sed -n '370,380p' src/LiveQuery/ParseLiveQueryServer.js

Repository: parse-community/parse-server

Length of output: 487


🏁 Script executed:

# Check line 795-800 context
sed -n '790,805p' src/LiveQuery/ParseLiveQueryServer.js

Repository: parse-community/parse-server

Length of output: 735


🏁 Script executed:

# Check where auth is defined in LiveQuery
rg -n "let auth\s*=|const auth\s*=|auth\s*=" src/LiveQuery/ParseLiveQueryServer.js | head -20

Repository: parse-community/parse-server

Length of output: 518


🏁 Script executed:

# Check if builtInTriggerValidator has any auth validation
sed -n '155,190p' src/Triggers/Validator.js

Repository: parse-community/parse-server

Length of output: 1102


Add null/undefined check before calling auth.getUserRoles().

The auth parameter can be null or undefined when passed from LiveQuery event handlers (e.g., ParseLiveQueryServer.js lines 373, 794), where getAuthFromClient() may return null. Line 179 calls auth.getUserRoles() without validation, which will throw if auth is falsy. Either add a guard (if (!auth) return;) or ensure getAuthFromClient() always returns a valid Auth instance.

🤖 Prompt for AI Agents
In src/Triggers/Validator.js around line 179, the code calls auth.getUserRoles()
without verifying auth may be null/undefined (from LiveQuery handlers). Add a
guard that checks if auth is falsy before calling getUserRoles() — e.g., if
(!auth) handle early return or set roles to an empty array — so the function
doesn't throw when auth is missing; ensure any downstream logic correctly
handles the no-auth case.

@mtrezza
Copy link
Member

mtrezza commented Dec 14, 2025

Superseded by #9980

@mtrezza mtrezza closed this Dec 14, 2025
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.

Return HTTP response status codes from Cloud Code

2 participants