Skip to content

Comments

fix: enable DI for FeatureOptInService#26061

Merged
eunjae-lee merged 7 commits intomainfrom
fix/di-feature-opt-in-service
Jan 13, 2026
Merged

fix: enable DI for FeatureOptInService#26061
eunjae-lee merged 7 commits intomainfrom
fix/di-feature-opt-in-service

Conversation

@eunjae-lee
Copy link
Contributor

@eunjae-lee eunjae-lee commented Dec 19, 2025

What does this PR do?

This PR adds DI support for FeatureOptInService and FeaturesRepository.

But we're still using FeaturesRepository directly across the project, and I will fix it in a separate PR.

Updates since last revision

  • Renamed FeatureOptInServiceInterface.ts to IFeatureOptInService.ts to follow the codebase convention of using I prefix for interface files
  • Renamed the interface from FeatureOptInServiceInterface to IFeatureOptInService
  • Updated all references to use the new naming convention

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • N/A - I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. Run type checks: yarn type-check:ci --force
  2. Run tests: TZ=UTC yarn test
  3. Verify the DI containers correctly resolve FeatureOptInService and FeaturesRepository

Summary by cubic

Enables DI for FeatureOptInService and FeaturesRepository across the app. Replaces direct instantiation and Prisma usage with moduleLoader-based containers and updates router/tests to resolve the service via DI.

  • Refactors
    • Added DI modules/containers and helpers for FeatureOptInService and FeaturesRepository; introduced feature-specific DI tokens and merged them into DI_TOKENS.
    • Replaced the deprecated Features module with FeaturesRepository module and updated related imports.
    • Added IFeatureOptInService and implemented it in the service.
    • Updated TRPC viewer.featureOptIn router and integration tests to use DI.

Written for commit 259569f. Summary will update on new commits.


Link to Devin run: https://app.devin.ai/sessions/de393c9ea2b8412587a8c4ca8cb2631d
Requested by: @eunjae-lee (eunjae@cal.com)

@github-actions github-actions bot added ❗️ migrations contains migration files and removed size/XXL labels Dec 19, 2025
@eunjae-lee eunjae-lee changed the base branch from main to feat/feature-opt-in-service December 19, 2025 16:58
@vercel
Copy link

vercel bot commented Dec 19, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Review Updated (UTC)
cal Ignored Ignored Jan 6, 2026 3:58pm
cal-companion Ignored Ignored Preview Jan 6, 2026 3:58pm
cal-eu Ignored Ignored Jan 6, 2026 3:58pm

@eunjae-lee eunjae-lee changed the base branch from feat/feature-opt-in-service to main December 19, 2025 17:05
@github-actions
Copy link
Contributor

E2E results are ready!

@eunjae-lee eunjae-lee changed the base branch from main to feat/feature-opt-in-service December 19, 2025 20:19
@eunjae-lee eunjae-lee force-pushed the fix/di-feature-opt-in-service branch from 6fa75c8 to e4288a9 Compare December 22, 2025 11:11
@eunjae-lee eunjae-lee force-pushed the feat/feature-opt-in-service branch from bc58bb0 to d075b06 Compare December 22, 2025 11:11
Copy link
Contributor Author

eunjae-lee commented Dec 22, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions
Copy link
Contributor

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

@github-actions github-actions bot added the Stale label Dec 30, 2025
@eunjae-lee eunjae-lee force-pushed the fix/di-feature-opt-in-service branch from 1ca81e1 to 62f8176 Compare January 6, 2026 15:58
@eunjae-lee eunjae-lee marked this pull request as ready for review January 6, 2026 16:36
@graphite-app graphite-app bot added the core area: core, team members only label Jan 6, 2026
@graphite-app graphite-app bot requested a review from a team January 6, 2026 16:36
@graphite-app graphite-app bot added the consumer label Jan 6, 2026
Copy link
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 12 files

Copy link
Member

@hariombalhara hariombalhara left a comment

Choose a reason for hiding this comment

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

Left a few suggestions

Comment on lines 9 to 12
const container = createContainer();
container.load(DI_TOKENS.PRISMA_MODULE, prismaModule);
container.load(DI_TOKENS.FEATURES_REPOSITORY_MODULE, featuresRepositoryModule);
container.load(DI_TOKENS.FEATURE_OPT_IN_SERVICE_MODULE, featureOptInServiceModule);
Copy link
Member

Choose a reason for hiding this comment

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

This approach to load dependencies isn't build time safe. We might endup in a scenario, where FeaturesOptinService adds a new dependency and we forget to load it here and TS will happily pass.

We should instead use the moduleLoader pattern used in many places now. You would need to convert FeaturesOptInService module to that pattern and then TS would know what dependences are needed by it and would automatically loaded them as well.

Comment on lines 5 to 12
import { createContainer } from "../di";
import { featuresRepositoryModule } from "../modules/FeaturesRepository";

const container = createContainer();
container.load(DI_TOKENS.PRISMA_MODULE, prismaModule);
container.load(DI_TOKENS.FEATURES_REPOSITORY_MODULE, featuresRepositoryModule);

export function getFeaturesRepository() {
Copy link
Member

Choose a reason for hiding this comment

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

We can convert it to use moduleLoader format too

Comment on lines 38 to 39
FEATURE_OPT_IN_SERVICE: Symbol("FeatureOptInService"),
FEATURE_OPT_IN_SERVICE_MODULE: Symbol("FeatureOptInServiceModule"),
Copy link
Member

Choose a reason for hiding this comment

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

The convention is to expose tokens.ts file from the feature package itself and just impor tthat here.

See e.g. https://github.com/calcom/cal.com/blob/rajiv/cal-7046-api-v2-teams-invite-link-endpoint/packages/features/oauth/di/tokens.ts#L8-L9

Copy link
Member

Choose a reason for hiding this comment

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

nit: I think the convention is to name interface files starting with I

@github-actions github-actions bot marked this pull request as draft January 12, 2026 12:58
devin-ai-integration bot and others added 4 commits January 12, 2026 14:31
…eLoader pattern

- Create feature-specific tokens in feature-opt-in/di/tokens.ts and flags/di/tokens.ts
- Update modules to use bindModuleToClassOnToken for type-safe dependency injection
- Simplify containers to use moduleLoader.loadModule() for automatic dependency loading
- Import feature-specific tokens in central tokens.ts using spread operator

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
Follow the codebase convention of using 'I' prefix for interface files and names.

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
The FeaturesRepository module was refactored to use moduleLoader pattern but
AvailableSlots.ts still imports featuresRepositoryModule. This adds the export
to maintain backward compatibility.

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@eunjae-lee
Copy link
Contributor Author

@hariombalhara addressed your feedback !

@vercel vercel bot temporarily deployed to Preview – cal-companion January 13, 2026 09:26 Inactive
@vercel vercel bot temporarily deployed to Preview – dev January 13, 2026 09:27 Inactive
@eunjae-lee eunjae-lee marked this pull request as ready for review January 13, 2026 09:50
Copy link
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.

1 issue found across 15 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/features/feature-opt-in/services/FeatureOptInService.integration-test.ts">

<violation number="1" location="packages/features/feature-opt-in/services/FeatureOptInService.integration-test.ts:82">
P2: Potential test isolation issue: `getFeaturesRepository()` and `getFeatureOptInService()` use separate DI containers, creating different `FeaturesRepository` instances. The `clearFeaturesCache(featuresRepository)` call only clears the cache on the standalone repository instance, not the one used internally by the service. This could cause test flakiness if the service's internal repository has cached stale data.

Consider either:
1. Using a shared DI container for both
2. Exposing a method to get the repository instance from the service for cache clearing
3. Creating a helper that clears both caches</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


const featuresRepository = new FeaturesRepository(prisma);
const service = new FeatureOptInService(featuresRepository);
const featuresRepository = getFeaturesRepository();
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 13, 2026

Choose a reason for hiding this comment

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

P2: Potential test isolation issue: getFeaturesRepository() and getFeatureOptInService() use separate DI containers, creating different FeaturesRepository instances. The clearFeaturesCache(featuresRepository) call only clears the cache on the standalone repository instance, not the one used internally by the service. This could cause test flakiness if the service's internal repository has cached stale data.

Consider either:

  1. Using a shared DI container for both
  2. Exposing a method to get the repository instance from the service for cache clearing
  3. Creating a helper that clears both caches
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/features/feature-opt-in/services/FeatureOptInService.integration-test.ts, line 82:

<comment>Potential test isolation issue: `getFeaturesRepository()` and `getFeatureOptInService()` use separate DI containers, creating different `FeaturesRepository` instances. The `clearFeaturesCache(featuresRepository)` call only clears the cache on the standalone repository instance, not the one used internally by the service. This could cause test flakiness if the service's internal repository has cached stale data.

Consider either:
1. Using a shared DI container for both
2. Exposing a method to get the repository instance from the service for cache clearing
3. Creating a helper that clears both caches</comment>

<file context>
@@ -77,8 +79,8 @@ async function setup(): Promise<TestEntities> {
 
-  const featuresRepository = new FeaturesRepository(prisma);
-  const service = new FeatureOptInService(featuresRepository);
+  const featuresRepository = getFeaturesRepository();
+  const service = getFeatureOptInService();
 
</file context>
Fix with Cubic

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for the review! However, I believe this concern is not actually valid for this specific case.

The FeaturesRepository cache is a static property (class-level, not instance-level):

private static featuresCache: { data: any[]; expiry: number } | null = null;

And clearCache() clears this static property:

private clearCache() {
  FeaturesRepository.featuresCache = null;
}

Since the cache is static, calling clearFeaturesCache(featuresRepository) clears the cache for all instances of FeaturesRepository, including the one inside the service. The tests work correctly as-is because the cache is shared at the class level, not the instance level.

Additionally, there's a follow-up PR coming that will replace this static cache with a proper Redis cache, which will make this even cleaner.

@github-actions
Copy link
Contributor

Devin AI is addressing Cubic AI's review feedback

New feedback has been sent to the existing Devin session.

View Devin Session

Copy link
Member

@hariombalhara hariombalhara left a comment

Choose a reason for hiding this comment

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

LGTM !!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

consumer core area: core, team members only ❗️ migrations contains migration files ready-for-e2e size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants