-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
feat: Add response error codes #9500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
I will reformat the title to use the proper commit message syntax. |
Thanks for opening this pull request! |
da8af05 to
ab03153
Compare
30866c4 to
f8adefc
Compare
📝 WalkthroughWalkthroughThis 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this 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
responseObjectparameter 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 passesresponseas the 10th parametersrc/RestWrite.js(createSession): Missing responseObject — will beundefinedsrc/Auth.js: Missing responseObject — will beundefinedsrc/Routers/UsersRouter.js: Missing responseObject — will beundefinedsrc/Controllers/PushController.js(2 calls): Missing responseObject — will beundefinedAll remaining call sites need to be updated to pass the responseObject parameter (or
undefined/nullif not available). This is a breaking change that impacts multiple modules.src/rest.js (1)
61-159: Addcontextandresponseparameters to the RestQuery call indel()The prefetch query at lines 80-86 doesn't pass
contextorresponseto RestQuery, while other routes (find, get) do. This means the beforeFind trigger will run with an empty context object instead of the one passed todel(), 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: Alignsilent/verbose/logLevelsderivation (current defaults are internally inconsistent).Right now
defaultConfiguration.silentis hardcoded tofalse(Line 115), butdefaultConfiguration.verboseis still derived from the localsilentvariable (Line 116), andif (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.silentfrom the computedsilentvariable again, or- remove/adjust the
if (silent) { ... }block and computeverbose/logLevelsoffdefaultConfiguration.silent.
99-118: PR title: add a scope for changelog clarity.Suggestion:
feat(cloudcode): add trigger response HTTP status codes and headers(orfeat(rest): ...if the main surface is REST responses).src/Triggers/TriggerResponse.js (2)
2-4: Consider returningthisfrom setter methods for fluent chaining.The
status()method doesn't returnthis, preventing fluent API usage likeresponse.status(201).setHeader('X-Custom', 'value'). This is a common pattern for builder-style classes.status(status) { this._status = status; + return this; }
5-8: Returnthisfor method chaining consistency.Same as
status(), returningthisenables 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 newresponseparameter.The new
responseparameter 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 objectsrc/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, unlikecleanInput. If the error object is large or contains circular references, this could cause issues:
- Large error objects may bloat logs
- 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.headersis truthy but not an object (e.g., an array or primitive),Object.entriescould 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 returntruefornull.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
Errorobjects loses stack trace information. This pattern appears throughout the file. Consider throwingErrorobjects or relying onresolveErrorto 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 != nullis true only whenresult === 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 missingparseObject.Line 65 returns
{}whenparseObjectis falsy, but line 70 returnsundefinedwhen 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 withgetRequestObjectcontext copying
getRequestObject()copies context viaObject.assign({}, context)(see src/Triggers/Trigger.js snippet), butgetRequestQueryObject()usescontext: 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
📒 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.jssrc/Triggers/FileTrigger.jssrc/RestQuery.jssrc/Triggers/Validator.jssrc/Triggers/QueryTrigger.jssrc/rest.jssrc/Triggers/Trigger.jssrc/Routers/ClassesRouter.jssrc/Routers/FilesRouter.jssrc/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.jsspec/CloudCode.spec.jssrc/Triggers/QueryTrigger.jssrc/Routers/ClassesRouter.jssrc/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.jssrc/Triggers/Validator.jssrc/Triggers/QueryTrigger.jssrc/Triggers/Trigger.jssrc/Routers/ClassesRouter.jssrc/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.jssrc/Routers/FunctionsRouter.jssrc/Triggers/Logger.jsspec/helper.jssrc/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.jssrc/Routers/FunctionsRouter.jssrc/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.jssrc/Routers/FunctionsRouter.jssrc/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.jssrc/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.jssrc/Routers/FunctionsRouter.jssrc/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.jsexplicitly 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
./../triggersto../triggers. Both resolve identically; the new form is more conventional.src/Routers/UsersRouter.js (2)
271-280: LGTM - TriggerResponse integration for beforeLogin.The
beforeLoginResponseinstance is correctly created and passed tomaybeRunTrigger, allowing the beforeLogin trigger to set custom status codes and headers. The trigger signature aligns withsrc/Triggers/Trigger.jswhich acceptsresponseObjectas 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
addConnectTriggerto the genericaddTriggerwith'@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
triggerResponseis 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
_headersvia the|| {}fallback. The default status of 201 is appropriate for resource creation. TheLocationheader 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
responseparameter is properly passed through tomaybeRunQueryTrigger, enabling the beforeFind trigger to customize response metadata. The conditional execution based onrunBeforeFindis preserved.src/Triggers/ConfigTrigger.js (1)
10-16: LGTM - Validation and skipWithMasterKey handling.The pattern of running validation first and checking
skipWithMasterKeybefore executing the trigger is consistent with other trigger implementations. ReturningconfigObjectwhen 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
truncateLogMessagefor 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
fetchis 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.resolveErrorfor standardizationThe pattern of passing both
requestandresponseto 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 aresponseproperty. The usage in lines 154-158, wrapping the encoded result as{ response: { result: Parse._encode(result) } }, properly matches the method's implementation which extracts theresponseproperty 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
getRequestFileObjectfunction follows the established pattern for building request objects, properly handling auth cases and spreading file object properties.
31-36: TheresponseObjectparameter is used correctly in thebeforeSavefile trigger.The
responseObjectis 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, thebeforeSavefile trigger receives aTriggerResponseobject, 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 aresponseObjectparameter, 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
TriggerResponsewrapping is clean and consistent. The control flow is straightforward.
150-157: LGTM!The delete handler correctly creates a
TriggerResponse, passes it torest.del, and returns an empty response object.src/Triggers/Utils.js (2)
1-9: LGTM!The
getClassNamefunction handles the common cases for resolving class names with appropriate fallbacks.
45-64: LGTM overall fortoJSONwithObjects.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
maybeRunValidatorfunction properly handles both object-based and function validators, with appropriate error normalization viaresolveError.
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
getRequestObjectfunction properly constructs the request object with appropriate context copying and auth augmentation for supported trigger types.
166-175: LGTM!The
runTriggerhelper 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, whilelogTriggerSuccessBeforeHook(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 intoRestQuerylooks consistent for find/get
162-218: RestWrite constructor parameters are correct in both create and updateThe 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. Usingundefinedfor 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
.jsfiles 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.
| 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); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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'); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| logTriggerSuccessBeforeHook( | ||
| triggerType, | ||
| 'Parse.File', | ||
| { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize }, | ||
| result, | ||
| auth, | ||
| config.logLevels.triggerBeforeSuccess | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| logTriggerErrorBeforeHook( | ||
| triggerType, | ||
| 'Parse.File', | ||
| { ...fileObject.file.toJSON(), fileSize: fileObject.fileSize }, | ||
| auth, | ||
| error, | ||
| config.logLevels.triggerBeforeError | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| } 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| const error = new Parse.Error(code, message.message || message); | ||
| if (message instanceof Error) { | ||
| error.stack = message.stack; | ||
| } | ||
| return error; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| const stateController = Parse.CoreManager.getObjectStateController(); | ||
| const [pending] = stateController.getPendingOps(object._getStateIdentifier()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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:
- Parse JS SDK CoreManager / controllers docs. [parseplatform.org / Parse-SDK-JS docs]. [1]
- Parse-SDK-JS repository (controllers and CoreManager usage). [github.com/parse-community/Parse-SDK-JS]. [2]
- Parse.Object public API (get/set/save, local datastore pin/unpin). [parseplatform.org Parse.Object]. [3]
- 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:
-
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]
-
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]
-
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]
-
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]
-
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]
-
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:
-
- Inspect a repository (package.json, exports, .d.ts) and produce an API-diff plan, or
-
- 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.
| const getType = fn => { | ||
| const match = fn && fn.toString().match(/\^\s*function (\w+)/); | ||
| return (match ? match[1] : '').toLowerCase(); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 50Repository: 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 -20Repository: 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.jsRepository: 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 -20Repository: 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 -5Repository: parse-community/parse-server
Length of output: 104
🏁 Script executed:
# Check for getUserRoles method definition
rg -n "getUserRoles" --type jsRepository: 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 -40Repository: 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 -10Repository: 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 -20Repository: 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.jsRepository: 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 -60Repository: 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 -80Repository: parse-community/parse-server
Length of output: 2161
🏁 Script executed:
# Look at DatabaseController to understand trigger execution flow
rg -n "import.*runTrigger" src/ --type jsRepository: 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 -30Repository: 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.jsRepository: parse-community/parse-server
Length of output: 536
🏁 Script executed:
# Check line 373-376 context in LiveQuery
sed -n '370,380p' src/LiveQuery/ParseLiveQueryServer.jsRepository: parse-community/parse-server
Length of output: 487
🏁 Script executed:
# Check line 795-800 context
sed -n '790,805p' src/LiveQuery/ParseLiveQueryServer.jsRepository: 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 -20Repository: parse-community/parse-server
Length of output: 518
🏁 Script executed:
# Check if builtInTriggerValidator has any auth validation
sed -n '155,190p' src/Triggers/Validator.jsRepository: 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.
|
Superseded by #9980 |
Pull Request
Issue
Closes: #9479
Approach
This is a feature alongside a refactor to promote more modularity in the cloud triggers.
Tasks
Summary by CodeRabbit
Release Notes
New Features
Tests
✏️ Tip: You can customize this high-level summary in your review settings.