Skip to content

refactor: use delay to inject repositories#293

Merged
ggazzo merged 3 commits intomainfrom
refactor-use-delay-for-repositories
Nov 3, 2025
Merged

refactor: use delay to inject repositories#293
ggazzo merged 3 commits intomainfrom
refactor-use-delay-for-repositories

Conversation

@sampaiodiego
Copy link
Member

@sampaiodiego sampaiodiego commented Nov 3, 2025

Summary by CodeRabbit

  • New Features

    • Added configurable EDU options for typing and presence processing in the federation module.
  • Refactor

    • Updated dependency injection patterns across internal services to optimize initialization and reduce coupling.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 3, 2025

Walkthrough

This PR refactors dependency injection patterns across federation SDK services, converting several dependencies to lazy/delayed injection using tsyringe decorators, removing unused dependencies from multiple services, and relocating EventService's startup invocation from the service to the SDK bootstrap flow.

Changes

Cohort / File(s) Summary
Delayed Dependency Injection Addition
packages/federation-sdk/src/services/event-authorization.service.ts, event-fetcher.service.ts, server.service.ts, state.service.ts, room.service.ts
Added @inject(delay(...)) decorators to repository parameters to enable lazy dependency resolution. Updated tsyringe imports to include delay and inject.
Dependency Injection Removal
packages/federation-sdk/src/sdk.ts, services/federation.service.ts
Removed @inject(delay(...)) decorators from constructor parameters; removed delay and inject from tsyringe imports. Reverted to direct dependency resolution.
Unused Dependency Removal
packages/federation-sdk/src/services/edu.service.ts, invite.service.ts, message.service.ts, profiles.service.ts, send-join.service.ts
Removed unused constructor dependencies (EventEmitterService, EventService, ConfigService, EventRepository, RoomService, FederationService) and corresponding imports.
EventService Refactoring
packages/federation-sdk/src/services/event.service.ts
Converted EventRepository, EventStagingRepository, and LockRepository from constructor parameters to property-based DI with @inject(delay(...)) decorators. Removed internal startup setTimeout. Made processOldStagedEvents() public (was private).
SDK Bootstrap Reorganization
packages/federation-sdk/src/index.ts
Added EventService import and scheduled a 5-second timeout post-initialization to resolve EventService from container and invoke processOldStagedEvents().
Configuration Extension
packages/homeserver/src/homeserver.module.ts
Added edu configuration block with processTyping and processPresence flags derived from environment variables.

Sequence Diagram(s)

sequenceDiagram
    participant Bootstrap as SDK Bootstrap<br/>(index.ts)
    participant Container as tsyringe Container
    participant EventSvc as EventService
    participant DB as Database

    rect rgb(230, 240, 250)
    Note over Bootstrap,DB: Before: Internal Startup in EventService
    Bootstrap->>Container: Initialize & register
    Container->>EventSvc: Instantiate (constructor)
    EventSvc->>EventSvc: setTimeout(...processOldStagedEvents)
    EventSvc->>DB: processOldStagedEvents (async)
    end

    rect rgb(240, 250, 230)
    Note over Bootstrap,DB: After: Deferred Startup in Bootstrap
    Bootstrap->>Container: Initialize & register
    Bootstrap->>EventSvc: (no immediate processing)
    Bootstrap->>Bootstrap: setTimeout(5s)
    Bootstrap->>Container: Resolve EventService
    Container->>EventSvc: Return instance
    Bootstrap->>EventSvc: processOldStagedEvents()
    EventSvc->>DB: processOldStagedEvents (async)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • EventService startup flow reorganization: Verify that moving processOldStagedEvents() invocation from internal setTimeout to external bootstrap call doesn't introduce race conditions or timing issues with database initialization.
  • Property-based DI conversion: Ensure that converting repository dependencies to @inject(delay(...)) on private fields (room.service.ts, event.service.ts, state.service.ts) maintains proper lazy loading and doesn't cause circular dependency issues.
  • Removed dependencies in send-join.service.ts: Confirm that EventService and FederationService removals don't leave dangling references or break existing functionality in the class body.
  • Public visibility change: Validate that exposing processOldStagedEvents() as public in EventService doesn't create unintended access patterns or require additional error handling.

Possibly related PRs

Suggested reviewers

  • ggazzo
  • rodrigok

🐰 Dependencies arranged with care,
Lazy loading everywhere,
Startup moved with grace and flair,
EventService now plays fair! 🌟

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'refactor: use delay to inject repositories' accurately captures the main objective of the changeset. The changes across multiple service files demonstrate a systematic refactoring to use tsyringe's delay and inject decorators for repository dependencies, replacing direct constructor injection with delayed injection patterns. The title is concise, specific, and clearly conveys the primary refactoring effort without unnecessary noise.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-use-delay-for-repositories

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.

@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 33.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.46%. Comparing base (7a2ad55) to head (e221009).

Files with missing lines Patch % Lines
...kages/federation-sdk/src/services/state.service.ts 33.33% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #293      +/-   ##
==========================================
- Coverage   60.47%   60.46%   -0.02%     
==========================================
  Files          67       67              
  Lines        6670     6672       +2     
==========================================
  Hits         4034     4034              
- Misses       2636     2638       +2     

☔ 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: 1

📜 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 7a2ad55 and e221009.

📒 Files selected for processing (15)
  • packages/federation-sdk/src/index.ts (2 hunks)
  • packages/federation-sdk/src/sdk.ts (2 hunks)
  • packages/federation-sdk/src/services/edu.service.ts (0 hunks)
  • packages/federation-sdk/src/services/event-authorization.service.ts (2 hunks)
  • packages/federation-sdk/src/services/event-fetcher.service.ts (2 hunks)
  • packages/federation-sdk/src/services/event.service.ts (3 hunks)
  • packages/federation-sdk/src/services/federation.service.ts (1 hunks)
  • packages/federation-sdk/src/services/invite.service.ts (1 hunks)
  • packages/federation-sdk/src/services/message.service.ts (0 hunks)
  • packages/federation-sdk/src/services/profiles.service.ts (0 hunks)
  • packages/federation-sdk/src/services/room.service.ts (2 hunks)
  • packages/federation-sdk/src/services/send-join.service.ts (0 hunks)
  • packages/federation-sdk/src/services/server.service.ts (1 hunks)
  • packages/federation-sdk/src/services/state.service.ts (2 hunks)
  • packages/homeserver/src/homeserver.module.ts (1 hunks)
💤 Files with no reviewable changes (4)
  • packages/federation-sdk/src/services/edu.service.ts
  • packages/federation-sdk/src/services/message.service.ts
  • packages/federation-sdk/src/services/profiles.service.ts
  • packages/federation-sdk/src/services/send-join.service.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/media.service.ts:21-31
Timestamp: 2025-09-14T13:15:46.588Z
Learning: In the RocketChat homeserver project, ricardogarim prefers to defer timeout and retry enhancements for media downloads when there's already a TODO to make timeouts configurable, indicating they manage technical debt systematically rather than implementing every suggested improvement immediately.
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/core/src/utils/fetch.ts:12-47
Timestamp: 2025-09-14T13:30:07.786Z
Learning: In the RocketChat/homeserver codebase, the team prefers to keep implementations simple and functional rather than adding complex optimizations prematurely. Performance improvements are deferred to future work when there's clear evidence they're needed.
📚 Learning: 2025-10-10T22:18:31.655Z
Learnt from: sampaiodiego
Repo: RocketChat/homeserver PR: 224
File: packages/federation-sdk/src/services/event-authorization.service.ts:261-268
Timestamp: 2025-10-10T22:18:31.655Z
Learning: In packages/federation-sdk/src/services/state.service.ts, the method `StateService.getLatestRoomState(roomId: string)` has return type `Promise<State>` and never returns undefined. If the state is not found, it throws an error with message "No state found for room ${roomId}" instead of returning undefined.

Applied to files:

  • packages/federation-sdk/src/services/state.service.ts
  • packages/federation-sdk/src/services/invite.service.ts
  • packages/federation-sdk/src/services/event.service.ts
  • packages/federation-sdk/src/services/room.service.ts
📚 Learning: 2025-09-14T13:34:12.448Z
Learnt from: ricardogarim
Repo: RocketChat/homeserver PR: 184
File: packages/federation-sdk/src/services/event-authorization.service.ts:213-223
Timestamp: 2025-09-14T13:34:12.448Z
Learning: ACL events (m.room.server_acl) are not yet implemented in the homeserver codebase, so ACL-related code currently handles the simpler case where no ACL events exist in room state.

Applied to files:

  • packages/federation-sdk/src/services/invite.service.ts
🧬 Code graph analysis (1)
packages/federation-sdk/src/services/server.service.ts (2)
packages/federation-sdk/src/services/event.service.ts (1)
  • singleton (43-884)
packages/federation-sdk/src/services/room.service.ts (1)
  • singleton (43-1559)
🔇 Additional comments (5)
packages/homeserver/src/homeserver.module.ts (1)

93-96: Verify the asymmetric default behavior is intentional.

The two EDU feature flags have different default behaviors:

  • processTyping defaults to enabled (opt-out: disabled only when EDU_PROCESS_TYPING === 'false')
  • processPresence defaults to disabled (opt-in: enabled only when EDU_PROCESS_PRESENCE === 'true')

This asymmetry may be intentional based on feature maturity or stability concerns, but please confirm this is the desired behavior rather than an oversight.

</review_comment_end -->

packages/federation-sdk/src/services/invite.service.ts (1)

35-40: EventService removal is verified as safe.

The verification confirms that EventService had no references in the file and was not needed. EventAuthorizationService is properly used elsewhere in the service (line 218), and the constructor change is correct.

packages/federation-sdk/src/services/event-authorization.service.ts (1)

12-12: LGTM! Delayed injection applied correctly.

The delayed injection pattern for UploadRepository is appropriate here since the repository is only accessed in methods (e.g., canAccessMedia at line 355) and not during construction. This pattern helps avoid potential circular dependency issues.

Also applies to: 35-36

packages/federation-sdk/src/services/event.service.ts (2)

26-26: LGTM! Delayed injection correctly applied to repositories.

The delayed injection pattern for EventRepository, EventStagingRepository, and LockRepository is correctly implemented. Since these repositories are only accessed within methods (not during construction), the delayed resolution is safe and helps manage potential circular dependencies.

Also applies to: 55-61


669-669: No action required — processOldStagedEvents() is correctly called during SDK bootstrap.

Verified that the method is invoked in packages/federation-sdk/src/index.ts:328 within the SDK initialization flow, 5 seconds after database initialization completes. The method visibility change from private to public is properly utilized.

Comment on lines +324 to +329

// once the db is initialized we look for old staged events and try to process them
setTimeout(async () => {
const eventService = container.resolve(EventService);
await eventService.processOldStagedEvents();
}, 5000);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Consider improving the initialization pattern for processing old staged events.

The hard-coded 5-second delay and lack of error handling present several concerns:

  1. Hard-coded delay: The 5000ms timeout is arbitrary. If the database initialization takes longer than expected, this could still execute too early.
  2. Missing error handling: The async callback doesn't catch errors from processOldStagedEvents(), which could lead to silent failures.
  3. Fire-and-forget pattern: The Promise returned by the async function is not awaited, so errors won't surface to callers.

Consider one of these alternatives:

Option 1: Make it part of the initialization flow

 	// this is required to initialize the listener and register the queue handler
 	container.resolve(StagingAreaListener);
-
-	// once the db is initialized we look for old staged events and try to process them
-	setTimeout(async () => {
-		const eventService = container.resolve(EventService);
-		await eventService.processOldStagedEvents();
-	}, 5000);
+
+	// Process old staged events after DB initialization
+	try {
+		const eventService = container.resolve(EventService);
+		await eventService.processOldStagedEvents();
+	} catch (error) {
+		// Log error but don't block initialization
+		console.error('Failed to process old staged events:', error);
+	}
 }

Option 2: If the delay is truly necessary, make it configurable and add error handling

-	// once the db is initialized we look for old staged events and try to process them
-	setTimeout(async () => {
-		const eventService = container.resolve(EventService);
-		await eventService.processOldStagedEvents();
-	}, 5000);
+	// Process old staged events after allowing time for other services to initialize
+	const processingDelay = process.env.STAGED_EVENTS_DELAY_MS || 5000;
+	setTimeout(async () => {
+		try {
+			const eventService = container.resolve(EventService);
+			await eventService.processOldStagedEvents();
+		} catch (error) {
+			console.error('Failed to process old staged events:', error);
+		}
+	}, processingDelay);
📝 Committable suggestion

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

Suggested change
// once the db is initialized we look for old staged events and try to process them
setTimeout(async () => {
const eventService = container.resolve(EventService);
await eventService.processOldStagedEvents();
}, 5000);
// Process old staged events after allowing time for other services to initialize
const processingDelay = process.env.STAGED_EVENTS_DELAY_MS || 5000;
setTimeout(async () => {
try {
const eventService = container.resolve(EventService);
await eventService.processOldStagedEvents();
} catch (error) {
console.error('Failed to process old staged events:', error);
}
}, processingDelay);
🤖 Prompt for AI Agents
In packages/federation-sdk/src/index.ts around lines 324 to 329, the current
setTimeout with a hard-coded 5000ms and an unhandled async callback risks
running before DB is ready and will swallow errors; change this so processing
old staged events is part of the initialization flow (preferred) by invoking
await eventService.processOldStagedEvents() from the async init function after
the DB init completes and propagate or log errors, or if a delay is truly
required, replace the hard-coded setTimeout with a configurable retry/delay
mechanism and wrap the call in try/catch that logs and rethrows or reports
failures, and ensure callers can await the returned Promise instead of
fire-and-forgetting it.

@ggazzo ggazzo merged commit fc10b0c into main Nov 3, 2025
3 checks passed
@ggazzo ggazzo deleted the refactor-use-delay-for-repositories branch November 3, 2025 17:04
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.

3 participants