Skip to content

Conversation

@ricardogarim
Copy link
Contributor

@ricardogarim ricardogarim commented Sep 29, 2025

Summary by CodeRabbit

  • New Features

    • Added federation backfill endpoint: GET /_matrix/federation/v1/backfill/:roomId
      • Query params: v (one or more event IDs), limit (1–100; clamped).
      • Returns origin, origin_server_ts, and pdus for sliding-window history.
  • Refactor

    • Improved event retrieval to support backfill queries with proper ordering and limits.
  • Chores

    • Added DTOs for backfill params, queries, responses, and errors; re-exported publicly.
    • Event payload schema: origin field is now optional.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 29, 2025

Walkthrough

Adds backfill support: two repository methods to locate/backfill events, a service method to assemble backfill PDUs (KeyRepository removed), DTOs for params/query/response, a new federation backfill route, and a DTO re-export. EventBaseDto origin field changed to an optional/hidden form.

Changes

Cohort / File(s) Summary of changes
Federation SDK: Repository
packages/federation-sdk/src/repositories/event.repository.ts
Adds findNewestEventForBackfill(roomId, eventIds) and findEventsForBackfill(roomId, depth, originServerTs, limit) to locate the newest event among IDs and to stream events for backfill by depth/origin_server_ts; streamlines MongoDB imports.
Federation SDK: Service
packages/federation-sdk/src/services/event.service.ts
Removes KeyRepository from constructor; adds getBackfillEvents(roomId, eventIds, limit) which clamps limit, queries repository for backfill events, maps to PDUs and returns { origin, origin_server_ts, pdus }, with error logging and rethrowing.
Homeserver: Controller
packages/homeserver/src/controllers/federation/transactions.controller.ts
Adds GET /_matrix/federation/v1/backfill/:roomId route that parses v and limit, validates input, delegates to eventService.getBackfillEvents, and returns success or error DTOs.
Homeserver: DTOs (Federation Backfill)
packages/homeserver/src/dtos/federation/backfill.dto.ts, packages/homeserver/src/dtos/index.ts
New BackfillParamsDto, BackfillQueryDto, BackfillResponseDto, BackfillErrorResponseDto; re-exported from DTO index.
Homeserver: Common Event DTO
packages/homeserver/src/dtos/common/event.dto.ts
Changes EventBaseDto origin field to HiddenOptional(t.Optional(t.String())) (now optional/hidden) and adds internal helpers TOptional and HiddenOptional.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Federation Client
  participant HS as Homeserver Controller
  participant ES as EventService
  participant ER as EventRepository
  participant DB as MongoDB

  Note over C,HS: GET /_matrix/federation/v1/backfill/:roomId?v=...&limit=...
  C->>HS: HTTP GET Backfill
  HS->>HS: Validate params (roomId, v[], limit)
  HS->>ES: getBackfillEvents(roomId, eventIds, limit)
  ES->>ER: findNewestEventForBackfill(roomId, eventIds)  -- optional
  ER-->>ES: newestEvent | null
  ES->>ER: findEventsForBackfill(roomId, depth, originServerTs, limit)
  ER->>DB: Query by depth and origin_server_ts (<=), sort desc, limit
  DB-->>ER: Events (cursor)
  ER-->>ES: EventStore cursor
  ES->>ES: Map events -> PDUs, set origin & origin_server_ts
  ES-->>HS: { origin, origin_server_ts, pdus }
  HS-->>C: 200 OK (BackfillResponseDto)

  alt Error
    ES->>ES: Log error and rethrow
    HS-->>C: 500 (BackfillErrorResponseDto)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • rodrigok

Poem

A rabbit nibbles through old threads,
Finds PDUs in mossy beds.
Depth and ticks guide tiny paws—
Backfill burrows, joins the cause.
Hooray! History hops home. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request includes some changes that appear unrelated to implementing the backfill route. The removal of the keyRepository dependency from EventService and the modification of the EventBaseDto origin field from required to optional are not explained by the linked issue requirement to "implement backfill route." While these changes may be necessary refactoring or cleanup, they represent additional modifications beyond the stated objective and should be justified or moved to separate PRs. Consider explaining why the keyRepository was removed from EventService and why the EventBaseDto origin field was changed to optional. If these changes are necessary for the backfill functionality, document this relationship in the PR description. If they are independent improvements or refactoring, consider moving them to separate pull requests to maintain a clear scope and make code review more focused.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat: implement backfill endpoint" accurately describes the main change in the changeset. The changes introduce a complete backfill feature including a new Federation API endpoint, supporting repository methods, service layer logic, and DTOs. The title is concise, specific, and clearly conveys the primary purpose of the changes without being overly broad or vague.
Linked Issues Check ✅ Passed The linked issue FDR-163 requires implementation of a backfill route, which is fully addressed by the changes. The PR introduces the Federation API endpoint at /_matrix/federation/v1/backfill/:roomId with complete supporting infrastructure including repository methods for querying events, service layer logic with proper limit validation and error handling, and comprehensive DTOs for request/response handling. All coding-related requirements from the linked issue are met.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/backfill-endpoint

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

Tip

🧪 Early access (models): enabled

We are currently testing Sonnet 4.5 code review models, which should lead to better review quality. However, this model may result in higher noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

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

@ricardogarim ricardogarim marked this pull request as ready for review September 29, 2025 10:42
@codecov-commenter
Copy link

codecov-commenter commented Sep 29, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.74%. Comparing base (b52b385) to head (133e1fe).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #234   +/-   ##
=======================================
  Coverage   81.74%   81.74%           
=======================================
  Files          63       63           
  Lines        4695     4695           
=======================================
  Hits         3838     3838           
  Misses        857      857           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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

Copy link
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.

Actionable comments posted: 2

🧹 Nitpick comments (6)
packages/federation-sdk/src/repositories/event.repository.ts (2)

385-423: Depth/TS window can return unrelated branch events; consider DAG-anchored traversal

Selecting by depth/time alone may return events from different branches not reachable via prev_events from the anchors. Prefer a small, bounded graph walk from the provided eventIds following prev_events until limit is met, de-duping along the way. This better matches Matrix backfill intent.

Sketch:

// Pseudocode
const queue = [...new Set(eventIds)];
const seen = new Set<EventID>(queue);
const results: EventStore[] = [];

while (queue.length && results.length < limit) {
  const batchIds = queue.splice(0, 25);
  const nodes = await this.collection.find({ _id: { $in: batchIds }, 'event.room_id': roomId }).toArray();
  for (const n of nodes) {
    for (const prev of n.event.prev_events ?? []) {
      if (!seen.has(prev)) { seen.add(prev); queue.push(prev as EventID); }
    }
    // push predecessors only (exclude anchors)
    if (!eventIds.includes(n._id) && results.length < limit) results.push(n);
  }
}
// Optionally sort by depth desc, ts desc before return.

This stays within limit (max 100) and returns only reachable history.


385-423: Indexing for backfill queries

To keep these queries efficient at scale, ensure a compound index exists:

  • { 'event.room_id': 1, 'event.depth': 1, 'event.origin_server_ts': 1 }

Migration snippet (Mongo shell/Compass):

db.getCollection('events').createIndex(
  { 'event.room_id': 1, 'event.depth': 1, 'event.origin_server_ts': 1 }
);
packages/federation-sdk/src/services/event.service.ts (1)

831-846: Harden limit parsing and sanitize anchors

If limit is ever undefined/NaN at runtime, Math.min/Math.max yields NaN and Mongo .limit(NaN) will error. Also normalize anchors.

Apply this diff:

-      const parsedLimit = Math.min(Math.max(1, limit), 100);
+      const parsedLimit = Number.isFinite(limit) ? Math.min(Math.max(1, limit), 100) : 100;
+      // Normalize anchors: trim, dedupe, drop empties
+      const normalized = Array.from(
+        new Set(eventIds.map(String).map((s) => s.trim()).filter(Boolean))
+      ) as EventID[];
+      if (normalized.length === 0) {
+        throw new Error('M_BAD_REQUEST');
+      }
-
-      const events = await this.eventRepository.findEventsForBackfill(
+      const events = await this.eventRepository.findEventsForBackfill(
         roomId,
-        eventIds,
+        normalized,
         parsedLimit,
       );
packages/homeserver/src/controllers/federation/transactions.controller.ts (1)

95-113: Make limit optional with sensible default and normalize v

Docs imply optional limit; DTO currently makes it required. Default to 100 here and de-dupe/validate anchors.

Apply this diff:

-          const limit = query.limit;
+          const limit = Number.isFinite(query.limit) ? query.limit : 100;
           const eventIdParam = query.v;
           if (!eventIdParam) {
             set.status = 400;
             return {
               errcode: 'M_BAD_REQUEST',
               error: 'Event ID must be provided in v query parameter',
             };
           }

-          const eventIds = Array.isArray(eventIdParam)
-            ? eventIdParam
-            : [eventIdParam];
+          const eventIds = Array.from(
+            new Set(
+              (Array.isArray(eventIdParam) ? eventIdParam : [eventIdParam])
+                .map((s) => s.trim())
+                .filter(Boolean)
+            )
+          );
packages/homeserver/src/dtos/federation/backfill.dto.ts (2)

8-11: Optional limit with default; tighten v constraints

Align with endpoint behavior: make limit optional (default 100) and ensure v isn’t empty.

Apply this diff:

-export const BackfillQueryDto = t.Object({
-  limit: t.Number({ minimum: 1, maximum: 100 }),
-  v: t.Union([t.String(), t.Array(t.String())]),
-});
+export const BackfillQueryDto = t.Object({
+  limit: t.Optional(t.Number({ minimum: 1, maximum: 100, default: 100 })),
+  v: t.Union([
+    t.String({ minLength: 1 }),
+    t.Array(t.String({ minLength: 1 }), { minItems: 1 }),
+  ]),
+});

13-17: Use integer for origin_server_ts

Federation timestamps are millisecond integers. Prefer Integer to ensure schema enforcement.

Apply this diff:

 export const BackfillResponseDto = t.Object({
   origin: t.String(),
-  origin_server_ts: t.Number(),
+  origin_server_ts: t.Integer(),
   pdus: t.Array(EventBaseDto),
 });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b52b385 and 0476631.

📒 Files selected for processing (6)
  • packages/federation-sdk/src/repositories/event.repository.ts (2 hunks)
  • packages/federation-sdk/src/services/event.service.ts (1 hunks)
  • packages/homeserver/src/controllers/federation/transactions.controller.ts (2 hunks)
  • packages/homeserver/src/dtos/common/event.dto.ts (0 hunks)
  • packages/homeserver/src/dtos/federation/backfill.dto.ts (1 hunks)
  • packages/homeserver/src/dtos/index.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/homeserver/src/dtos/common/event.dto.ts
🧰 Additional context used
🧬 Code graph analysis (4)
packages/federation-sdk/src/repositories/event.repository.ts (3)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/state_resolution/definitions/definitions.ts (1)
  • EventStore (96-98)
packages/core/src/models/event.model.ts (1)
  • EventStore (21-25)
packages/federation-sdk/src/services/event.service.ts (2)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/types/v3-11.ts (1)
  • Pdu (729-729)
packages/homeserver/src/dtos/federation/backfill.dto.ts (1)
packages/homeserver/src/dtos/common/event.dto.ts (1)
  • EventBaseDto (19-35)
packages/homeserver/src/controllers/federation/transactions.controller.ts (2)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/homeserver/src/dtos/federation/backfill.dto.ts (4)
  • BackfillParamsDto (4-6)
  • BackfillQueryDto (8-11)
  • BackfillResponseDto (13-17)
  • BackfillErrorResponseDto (19-22)
🔇 Additional comments (1)
packages/homeserver/src/dtos/index.ts (1)

7-7: Re-export looks good

Backfill DTOs are now exposed via the public DTO index; no issues spotted.

Copy link
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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/homeserver/src/dtos/common/event.dto.ts (2)

19-23: Clarify the purpose of HiddenOptional helper.

The HiddenOptional function currently returns the schema unchanged (no-op). If the intent is to hide the field from schema introspection or OpenAPI documentation, this implementation doesn't achieve that. Consider either:

  1. Implementing the hiding logic (e.g., setting metadata that Elysia respects for documentation generation).
  2. Removing this helper if it's not needed yet.
  3. Adding a comment explaining the future intent if this is a placeholder.

If hiding from OpenAPI is the goal, you may need to explore Elysia's schema metadata options or transformers. Otherwise, this could be simplified to just t.Optional(t.String()) on line 35.


35-35: Consider restoring the field description.

The origin field previously had a description ('Origin server') that has been removed. While making the field optional aligns with the backfill feature, consider preserving the description for better API documentation:

-origin: HiddenOptional(t.Optional(t.String())),
+origin: HiddenOptional(t.Optional(t.String({ description: 'Origin server' }))),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0476631 and 43abd6b.

📒 Files selected for processing (3)
  • packages/federation-sdk/src/repositories/event.repository.ts (2 hunks)
  • packages/federation-sdk/src/services/event.service.ts (1 hunks)
  • packages/homeserver/src/dtos/common/event.dto.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/federation-sdk/src/services/event.service.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-29T16:12:21.316Z
Learnt from: ricardogarim
PR: RocketChat/homeserver#234
File: packages/federation-sdk/src/repositories/event.repository.ts:407-423
Timestamp: 2025-09-29T16:12:21.316Z
Learning: The Matrix Federation API backfill endpoint should include the anchor events provided in the 'v' parameter along with preceding events, as explicitly stated in the Matrix specification: "including the given event(s)".

Applied to files:

  • packages/federation-sdk/src/repositories/event.repository.ts
🧬 Code graph analysis (1)
packages/federation-sdk/src/repositories/event.repository.ts (3)
packages/room/src/types/_common.ts (1)
  • EventID (8-8)
packages/room/src/state_resolution/definitions/definitions.ts (1)
  • EventStore (96-98)
packages/core/src/models/event.model.ts (1)
  • EventStore (21-25)
🔇 Additional comments (3)
packages/federation-sdk/src/repositories/event.repository.ts (3)

2-2: LGTM: Import cleanup.

The removal of unused imports (EventBase, Filter, FindOptions, UpdateResult) improves code clarity and reduces clutter.

Also applies to: 9-9


385-401: LGTM: Correctly identifies the newest anchor event.

The query correctly finds the latest event among the provided IDs by sorting on depth (primary) and origin_server_ts (tiebreaker), which aligns with Matrix event ordering semantics.


403-424: LGTM: Backfill query correctly implements Matrix Federation spec.

The query correctly retrieves events preceding (and including at equal depth) the reference point. The use of $lte for events at the same depth is correct per the Matrix specification, which explicitly states backfill should include the anchor events.

Based on learnings.

Copy link
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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/homeserver/src/dtos/common/event.dto.ts (2)

19-23: Clarify the purpose of the HiddenOptional helper.

The HiddenOptional function is an identity function that returns its input unchanged. The name suggests it "hides" optional fields, but the implementation provides no such behavior at runtime.

If this is intended to:

  • Exclude the field from OpenAPI/Swagger documentation: Document this intent with a comment and verify it works with your Elysia version.
  • Mark fields for special processing: Explain the processing logic.
  • Serve as documentation: Consider using a more descriptive JSDoc comment instead of a no-op wrapper.

Please clarify the intended behavior and add a comment explaining why this wrapper exists. If it has no functional purpose, consider removing it and using t.Optional() directly.


35-35: Consider restoring the field description.

The origin field previously had a description ('Origin server') that has been removed. This metadata is valuable for:

  • Generated API documentation (OpenAPI/Swagger)
  • Developer understanding when using this DTO
  • IDE tooltips and autocomplete hints

If the HiddenOptional wrapper is intended to suppress the field from documentation, consider adding a code comment explaining this decision. Otherwise, restore the description:

-origin: HiddenOptional(t.Optional(t.String())),
+origin: HiddenOptional(t.Optional(t.String({ description: 'Origin server' }))),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 43abd6b and 133e1fe.

📒 Files selected for processing (1)
  • packages/homeserver/src/dtos/common/event.dto.ts (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Code Quality Checks(lint, test, tsc)
🔇 Additional comments (2)
packages/homeserver/src/dtos/common/event.dto.ts (2)

1-1: Remove unused TSchema import and reconsider the origin field design.

Verification confirms:

  1. TSchema is unused – it appears only in the import statement and is never referenced in the file. Remove it.

  2. HiddenOptional is a no-op wrapper – this function simply returns its input unchanged (lines 21-23). The name suggests it should hide fields from documentation, but it provides no actual functionality.

  3. origin field may not belong in PDU schema – According to the Matrix specification, the origin field was removed from the PDU/event schema and exists only at the transaction level. Making it optional with HiddenOptional(t.Optional(t.String())) (line 35) may not align with the spec intent. Consider removing this field entirely from EventBaseDto rather than making it optional, or verify whether your implementation requires this deviation from the spec.

  4. Lost field description – the origin field previously had a description that was removed during this change.

- import { TSchema, t } from 'elysia';
+ import { t } from 'elysia';

35-35: The original concern is incorrect—making origin optional in EventBaseDto does not break existing code.

After verification, the origin field in EventBaseDto (the API/wire format) is independent from how origin is accessed in the codebase. All internal code uses PersistentEventBase objects where .origin is a computed getter that extracts the domain from the sender field (see packages/room/src/manager/event-wrapper.ts:88-93), not from the origin field in the PDU.

Specifically:

  • packages/room/src/manager/room-state.ts:126 accesses createEvent.origin, which calls the getter that extracts from sender, not the PDU field
  • Authorization rules at packages/room/src/authorizartion-rules/rules.ts:97,727 similarly access the computed property
  • Federation services that access event.origin work with raw PDU objects from API responses where origin may legitimately be optional (e.g., in backfill responses per Matrix spec)

The change correctly reflects that the origin field is optional in Matrix PDU wire format while maintaining backward compatibility with all existing code paths.

Likely an incorrect or invalid review comment.

@ggazzo ggazzo merged commit b02df77 into main Sep 29, 2025
3 of 8 checks passed
@ggazzo ggazzo deleted the feat/backfill-endpoint branch September 29, 2025 20:05
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.

4 participants