Skip to content

fix: use Document[] for aggregation pipeline types#38897

Open
amitkumarashutosh wants to merge 2 commits into
RocketChat:developfrom
amitkumarashutosh:fix/messages-aggregation-type-definitions
Open

fix: use Document[] for aggregation pipeline types#38897
amitkumarashutosh wants to merge 2 commits into
RocketChat:developfrom
amitkumarashutosh:fix/messages-aggregation-type-definitions

Conversation

@amitkumarashutosh
Copy link
Copy Markdown
Contributor

@amitkumarashutosh amitkumarashutosh commented Feb 22, 2026

Proposed changes

Replace the brittle aggregation pipeline type workaround in MessagesRaw with the correct Document type from the MongoDB driver.

The following variables are now typed as Document:

  • match, lookup, unwind, group, project, sort

The following arrays are now typed as Document[]:

  • firstParams, params

The following objects are now typed as Filter<IMessage> , UpdateFilter<IMessage>:

  • query, update

Before
const match = { $match: { ... } };
const lookup = { $lookup: { ... } };
const unwind = { $unwind: { ... } };
const group = { $group: { ... } };
const project = { $project: { ... } };
const firstParams: Exclude<Parameters<Collection<IMessage>['aggregate']>[0], undefined> = [match, lookup, unwind];
const sort = { $sort: options.sort || { name: 1 } };
const params = [...firstParams, group, project, sort];
const query = { 'u._id': userId, '$or': [{ 'file._id': { $exists: true } }, { 'files._id': { $exists: true } }], };
const update = { $set: { _hidden: hidden, },};

After
const match : Document = { $match: { ... } };
const lookup : Document = { $lookup: { ... } };
const unwind : Document= { $unwind: { ... } };
const group : Document = { $group: { ... } };
const project : Document = { $project: { ... } };
const firstParams : Document[] = [match, lookup, unwind];
const sort : Document= { $sort: options.sort || { name: 1 } };
const params : Document[] = [...firstParams, group, project, sort];
const query : Filter<IMessage>= { 'u._id': userId, '$or': [{ 'file._id': { $exists: true } }, { 'files._id': { $exists: true } }], };
const update : UpdateFilter<IMessage> = { $set: { _hidden: hidden, },};

Issue(s)

Closes #38896

Steps to reproduce

  • See packages/models/src/models/Messages.ts.

Testing

  • No runtime changes, types only
  • TypeScript compilation should pass with no new errors

Summary by CodeRabbit

  • New Features

    • Added support for optional offset and limit pagination parameters in message queries.
  • Refactor

    • Enhanced internal type consistency across aggregation pipelines and improved code structure for better maintainability.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Feb 22, 2026

⚠️ No Changeset found

Latest commit: dfc2ab4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@dionisio-bot
Copy link
Copy Markdown
Contributor

dionisio-bot Bot commented Feb 22, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 22, 2026

Walkthrough

Reworked typing and return shapes in packages/models/src/models/Messages.ts: aggregation pipeline stages and parameter arrays now use Document/Document[], many query/update literals replaced with Filter<IMessage>/UpdateFilter casts, getTotalOfMessagesSentByDate signature and return type updated to TotalOfMessagesSentByDateResult[], and optional pagination added to relevant aggregations.

Changes

Cohort / File(s) Summary
Messages model — aggregation & typing updates
packages/models/src/models/Messages.ts
Replaced brittle aggregate-stage typings with Document-typed stage constants and Document[] arrays for firstParams/params; changed many inline query/update literals to Filter<IMessage> / UpdateFilter<IMessage> casts; updated getTotalOfMessagesSentByDate signature to accept options?: PaginatedRequest and return Promise<TotalOfMessagesSentByDateResult[]>; applied nullish coalescing (??) and added optional $skip/$limit pagination handling in aggregation pipelines.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through types and tidied the trail,
Replaced loose literals with Document mail,
Counts now return with a clearer report,
Pagination leaps in — a neat little sort. 🎋

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: use Document[] for aggregation pipeline types' directly and accurately summarizes the main change: replacing brittle aggregation pipeline typing with proper Document[] types.
Linked Issues check ✅ Passed All coding requirements from issue #38896 are met: aggregation pipeline stages (match, lookup, unwind, group, project, sort) typed as Document; firstParams and params arrays typed as Document[].
Out of Scope Changes check ✅ Passed Changes include additional type improvements (Filter, UpdateFilter, nullish coalescing) beyond the specific issue requirements, but all are related to the same typing improvement scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

@amitkumarashutosh amitkumarashutosh marked this pull request as ready for review February 22, 2026 12:19
@amitkumarashutosh amitkumarashutosh requested a review from a team as a code owner February 22, 2026 12:19
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 1 file

Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
packages/models/src/models/Messages.ts (1)

237-241: sort stage runs unnecessarily in the onlyCount pipeline path.

Because sort is added to params before the onlyCount guard, the aggregation for a count request executes [..., group, project, sort, { $count: 'total' }], where the sort is a no-op and wastes resources. Moving sort into the non-count branch eliminates the redundant stage.

♻️ Proposed refactor
-		const sort: Document = { $sort: options.sort || { name: 1 } };
-		const params: Document[] = [...firstParams, group, project, sort];
+		const params: Document[] = [...firstParams, group, project];
 		if (onlyCount) {
 			params.push({ $count: 'total' });
 			return this.col.aggregate<{ total: number }>(params, { readPreference: readSecondaryPreferred() });
 		}
+		const sort: Document = { $sort: options.sort || { name: 1 } };
+		params.push(sort);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/models/src/models/Messages.ts` around lines 237 - 241, The pipeline
currently pushes `sort` into the `params` array before checking `onlyCount`,
causing the aggregation to include a redundant `$sort` when counting; update the
logic in the method that builds `params` so `params` is constructed as
[...firstParams, group, project] and only if `onlyCount` push `{ $count: 'total'
}` and call `this.col.aggregate` for the count path, otherwise append `sort`
then call `this.col.aggregate` for the non-count path—use the existing variable
names `params`, `firstParams`, `group`, `project`, `sort`, `onlyCount`, and
`this.col.aggregate` to locate and adjust the branches.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3145c41 and 0a65616.

📒 Files selected for processing (1)
  • packages/models/src/models/Messages.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • packages/models/src/models/Messages.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings by mapping subscription documents to room IDs, never undefined, even when user has no room subscriptions.
Learnt from: Dnouv
Repo: RocketChat/Rocket.Chat PR: 37057
File: packages/apps-engine/src/definition/accessors/IUserRead.ts:23-27
Timestamp: 2025-09-25T09:59:26.461Z
Learning: AppUserBridge.getUserRoomIds in apps/meteor/app/apps/server/bridges/users.ts always returns an array of strings (mapping subscription documents to room IDs), never undefined, even when user has no room subscriptions.
Learnt from: MartinSchoeler
Repo: RocketChat/Rocket.Chat PR: 37557
File: apps/meteor/client/views/admin/ABAC/AdminABACRooms.tsx:115-116
Timestamp: 2025-11-27T17:56:26.050Z
Learning: In Rocket.Chat, the GET /v1/abac/rooms endpoint (implemented in ee/packages/abac/src/index.ts) only returns rooms where abacAttributes exists and is not an empty array (query: { abacAttributes: { $exists: true, $ne: [] } }). Therefore, in components consuming this endpoint (like AdminABACRooms.tsx), room.abacAttributes is guaranteed to be defined for all returned rooms, and optional chaining before calling array methods like .join() is sufficient without additional null coalescing.
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.
Learnt from: ricardogarim
Repo: RocketChat/Rocket.Chat PR: 37205
File: ee/packages/federation-matrix/src/FederationMatrix.ts:296-301
Timestamp: 2025-10-28T16:53:42.761Z
Learning: In the Rocket.Chat federation-matrix integration (ee/packages/federation-matrix/), the createRoom method from rocket.chat/federation-sdk will support a 4-argument signature (userId, roomName, visibility, displayName) in newer versions. Code using this 4-argument call is forward-compatible with planned library updates and should not be flagged as an error.
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37419
File: apps/meteor/server/services/media-call/service.ts:141-141
Timestamp: 2025-11-19T18:20:37.116Z
Learning: In apps/meteor/server/services/media-call/service.ts, the sendHistoryMessage method should use call.caller.id or call.createdBy?.id as the message author, not call.transferredBy?.id. Even for transferred calls, the message should appear in the DM between the two users who are calling each other, not sent by the person who transferred the call.
🔇 Additional comments (1)
packages/models/src/models/Messages.ts (1)

242-247: AI summary inconsistency: pagination logic pre-exists this PR.

The AI summary claims the $skip/$limit stages (lines 242–247) were newly introduced by this change, but those lines carry no ~ marker, indicating they are unchanged pre-existing code. The PR description ("No runtime changes; types-only change") is consistent with the annotation, and the changes are purely type annotations as intended.

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

Nitpick comments:
In `@packages/models/src/models/Messages.ts`:
- Around line 237-241: The pipeline currently pushes `sort` into the `params`
array before checking `onlyCount`, causing the aggregation to include a
redundant `$sort` when counting; update the logic in the method that builds
`params` so `params` is constructed as [...firstParams, group, project] and only
if `onlyCount` push `{ $count: 'total' }` and call `this.col.aggregate` for the
count path, otherwise append `sort` then call `this.col.aggregate` for the
non-count path—use the existing variable names `params`, `firstParams`, `group`,
`project`, `sort`, `onlyCount`, and `this.col.aggregate` to locate and adjust
the branches.

@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.63%. Comparing base (133da0b) to head (dfc2ab4).
⚠️ Report is 11 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop   #38897      +/-   ##
===========================================
+ Coverage    70.56%   70.63%   +0.07%     
===========================================
  Files         3189     3189              
  Lines       112702   112716      +14     
  Branches     20429    20428       -1     
===========================================
+ Hits         79526    79619      +93     
+ Misses       31115    31054      -61     
+ Partials      2061     2043      -18     
Flag Coverage Δ
e2e 60.35% <ø> (-0.02%) ⬇️
e2e-api 47.78% <ø> (ø)
unit 71.21% <ø> (-0.34%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@amitkumarashutosh amitkumarashutosh force-pushed the fix/messages-aggregation-type-definitions branch from f007f25 to e619f5f Compare February 23, 2026 11:04
Copy link
Copy Markdown
Contributor

@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.

Caution

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

⚠️ Outside diff range comments (1)
packages/models/src/models/Messages.ts (1)

337-342: ⚠️ Potential issue | 🟡 Minor

options.offset is silently ignored — pagination is incomplete.

getTotalOfMessagesSentByDate accepts PaginatedRequest which includes offset, but no $skip stage is ever pushed. By contrast, findAllNumberOfTransferredRooms (also updated in this PR) correctly adds $skip:

if (options.offset) {
    params.push({ $skip: options.offset });
}

Without the $skip stage, passing options.offset has no effect and callers expecting offset-based pagination get incorrect results.

🐛 Proposed fix
 		if (options.sort) {
 			params.push({ $sort: options.sort });
 		}
+		if (options.offset) {
+			params.push({ $skip: options.offset });
+		}
 		if (options.count) {
 			params.push({ $limit: options.count });
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/models/src/models/Messages.ts` around lines 337 - 342,
getTotalOfMessagesSentByDate ignores PaginatedRequest.offset so offset-based
pagination is broken; update the aggregation builder in
getTotalOfMessagesSentByDate to push a { $skip: options.offset } stage into
params when options.offset is present (same pattern used in
findAllNumberOfTransferredRooms), ensuring you check options.offset and then
push the $skip stage before $limit/$sort so params reflects offset-based
pagination correctly.
🧹 Nitpick comments (2)
packages/models/src/models/Messages.ts (2)

601-604: Remove the explanatory code comment.

Line 601 adds // Dynamic nested key path cannot be statically typed via MatchKeysAndValues<IMessage>. The as unknown as UpdateFilter<IMessage>['$set'] cast is self-explanatory enough in context, and the comment is not needed. As per coding guidelines, code comments should be avoided in the implementation (**/*.{ts,tsx,js}).

♻️ Proposed change
-			// Dynamic nested key path cannot be statically typed via MatchKeysAndValues<IMessage>
 			$set: {
 				[`reactions.${reaction}.federationReactionEventIds.${federationEventId}`]: username,
 			} as unknown as UpdateFilter<IMessage>['$set'],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/models/src/models/Messages.ts` around lines 601 - 604, Remove the
inline explanatory comment preceding the dynamic $set assignment; keep the code
that sets
[`reactions.${reaction}.federationReactionEventIds.${federationEventId}`] =
username with the existing cast to as unknown as UpdateFilter<IMessage>['$set']
and do not add any replacement comment—just delete the line containing "//
Dynamic nested key path cannot be statically typed via
MatchKeysAndValues<IMessage>" so the block only contains the $set assignment and
cast.

35-45: Export TotalOfMessagesSentByDateResult and update IMessagesModel accordingly.

The type is declared locally in Messages.ts but IMessagesModel.getTotalOfMessagesSentByDate returns Promise<any[]>. The implementation provides the correct return type (Promise<TotalOfMessagesSentByDateResult[]>), but callers using the interface receive no type benefit. Export the type (or move it to @rocket.chat/model-typings) and update the interface signature to Promise<TotalOfMessagesSentByDateResult[]> for consistency.

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

In `@packages/models/src/models/Messages.ts` around lines 35 - 45, Export the
TotalOfMessagesSentByDateResult type from Messages.ts (or move it to
`@rocket.chat/model-typings`) and update the
IMessagesModel.getTotalOfMessagesSentByDate signature to return
Promise<TotalOfMessagesSentByDateResult[]> so callers get the concrete type;
ensure the export is named TotalOfMessagesSentByDateResult and update any
imports/usages to reference the exported type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@packages/models/src/models/Messages.ts`:
- Around line 337-342: getTotalOfMessagesSentByDate ignores
PaginatedRequest.offset so offset-based pagination is broken; update the
aggregation builder in getTotalOfMessagesSentByDate to push a { $skip:
options.offset } stage into params when options.offset is present (same pattern
used in findAllNumberOfTransferredRooms), ensuring you check options.offset and
then push the $skip stage before $limit/$sort so params reflects offset-based
pagination correctly.

---

Nitpick comments:
In `@packages/models/src/models/Messages.ts`:
- Around line 601-604: Remove the inline explanatory comment preceding the
dynamic $set assignment; keep the code that sets
[`reactions.${reaction}.federationReactionEventIds.${federationEventId}`] =
username with the existing cast to as unknown as UpdateFilter<IMessage>['$set']
and do not add any replacement comment—just delete the line containing "//
Dynamic nested key path cannot be statically typed via
MatchKeysAndValues<IMessage>" so the block only contains the $set assignment and
cast.
- Around line 35-45: Export the TotalOfMessagesSentByDateResult type from
Messages.ts (or move it to `@rocket.chat/model-typings`) and update the
IMessagesModel.getTotalOfMessagesSentByDate signature to return
Promise<TotalOfMessagesSentByDateResult[]> so callers get the concrete type;
ensure the export is named TotalOfMessagesSentByDateResult and update any
imports/usages to reference the exported type.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a65616 and e619f5f.

📒 Files selected for processing (1)
  • packages/models/src/models/Messages.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • packages/models/src/models/Messages.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38219
File: packages/core-typings/src/cloud/Announcement.ts:5-6
Timestamp: 2026-01-17T01:51:47.764Z
Learning: In packages/core-typings/src/cloud/Announcement.ts, the AnnouncementSchema.createdBy field intentionally overrides IBannerSchema.createdBy (object with _id and optional username) with a string enum ['cloud', 'system'] to match existing runtime behavior. This is documented as technical debt with a FIXME comment at apps/meteor/app/cloud/server/functions/syncWorkspace/handleCommsSync.ts:53 and should not be flagged as an error until the runtime behavior is corrected.
🔇 Additional comments (1)
packages/models/src/models/Messages.ts (1)

199-243: Core aggregation typing fix is correct.

Document/Document[] are the right types for MongoDB aggregation pipeline stages. The FIXME comment has been resolved and the workaround using Exclude<Parameters<Collection<IMessage>['aggregate']>[0], undefined> is cleanly replaced.

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.

Use Document[] for aggregation pipeline types in MessagesRaw

1 participant