Skip to content

Conversation

@eunjae-lee
Copy link
Contributor

@eunjae-lee eunjae-lee commented Jan 23, 2026

What does this PR do?

Splits the three flag repositories into raw Prisma repositories and cached repositories that wrap them:

  • FeatureRepositoryPrismaFeatureRepository + CachedFeatureRepository
  • TeamFeatureRepositoryPrismaTeamFeatureRepository + CachedTeamFeatureRepository
  • UserFeatureRepositoryPrismaUserFeatureRepository + CachedUserFeatureRepository

The Prisma repositories contain only raw database access logic. The Cached repositories wrap the Prisma ones and add @Memoize/@Unmemoize decorators for caching behavior.

Architecture:

Container → CachedRepository (with @Memoize/@Unmemoize) → PrismaRepository → Prisma

New repository methods added:

  • CachedFeatureRepository: findBySlug(), update(), checkIfFeatureIsEnabledGlobally()
  • CachedTeamFeatureRepository: checkIfTeamHasFeature(), getEnabledFeatures()
  • CachedUserFeatureRepository: checkIfUserHasFeature(), checkIfUserHasFeatureNonHierarchical()

Updates since last revision

  • Added explicit select clauses to PrismaFeatureRepository queries (findAll, findBySlug, update) to only fetch fields needed for FeatureDto
  • Optimized PrismaUserFeatureRepository.findByUserIdAndFeatureIds to use single findMany query instead of multiple findUnique calls
  • Removed integration tests for methods intentionally removed from FeaturesRepository: getUserFeatureStates, getTeamsFeatureStates, getUserAutoOptIn, getTeamsAutoOptIn, setUserAutoOptIn, setTeamAutoOptIn
  • Added comprehensive unit tests for CachedUserFeatureRepository
  • Fixed cache invalidation: update() now invalidates both KEY.bySlug and KEY.all() caches
  • Fixed cache invalidation: upsert() and delete() now invalidate KEY.enabledFeatures cache
  • Fixed flaky InsightsBookingService.integration-test.ts by adding orderBy: { id: "asc" } to TeamRepository.findAllByParentId

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - internal refactor only.
  • 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 the existing unit tests: TZ=UTC yarn vitest run packages/features/flags/repositories/__tests__/
  2. Verify type checking passes: yarn type-check:ci --force
  3. The DI containers still return the same interface types, so existing functionality should work unchanged

Human Review Checklist

  • Verify select clauses in PrismaFeatureRepository include all fields needed for FeatureDto (slug, enabled, description, type, stale, lastUsedAt, createdAt, updatedAt, updatedBy)
  • Verify @Memoize decorator accepts direct function references (e.g., KEY.all vs () => KEY.all())
  • Verify cache keys in Cached repositories match the original keys exactly
  • Verify DI wiring: containers load CachedRepository modules which load PrismaRepository modules
  • Verify update() invalidates both KEY.bySlug and KEY.all() to prevent stale findAll() results
  • Verify upsert() and delete() invalidate KEY.enabledFeatures to prevent stale getEnabledFeatures() results
  • Verify removed integration tests correspond to methods actually removed from FeaturesRepository

Checklist

  • I have read the contributing guide
  • My code follows the style guidelines of this project
  • I have checked if my changes generate no new warnings
  • My PR is appropriately sized

Link to Devin run: https://app.devin.ai/sessions/e0bf0feaf39846299dd43299f34da7b6
Requested by: @eunjae-lee


Open with Devin

- Rename FeatureRepository to PrismaFeatureRepository (raw DB access)
- Rename TeamFeatureRepository to PrismaTeamFeatureRepository (raw DB access)
- Rename UserFeatureRepository to PrismaUserFeatureRepository (raw DB access)
- Create CachedFeatureRepository with @memoize wrapping PrismaFeatureRepository
- Create CachedTeamFeatureRepository with @Memoize/@Unmemoize wrapping PrismaTeamFeatureRepository
- Create CachedUserFeatureRepository with @Memoize/@Unmemoize wrapping PrismaUserFeatureRepository
- Update DI tokens, modules, and containers for all 6 repositories
- Update imports in FeatureOptInService and related modules
- Update tests to use new repository structure

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

…o Prisma

- Use direct function references for @memoize key (e.g., KEY.all instead of () => KEY.all())
- Simplify batch methods in Cached repositories to delegate to Prisma repository
- Update tests to reflect the new delegation pattern

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@eunjae-lee eunjae-lee marked this pull request as ready for review January 23, 2026 13:41
@graphite-app graphite-app bot added consumer core area: core, team members only labels Jan 23, 2026
@graphite-app graphite-app bot requested a review from a team January 23, 2026 13:41
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 20 files

@github-actions
Copy link
Contributor

github-actions bot commented Jan 23, 2026

E2E results are ready!

…c results

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>
@graphite-app graphite-app bot requested a review from a team January 23, 2026 13:58
volnei
volnei previously requested changes Jan 24, 2026
Copy link
Contributor

@volnei volnei 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 comment

@github-actions github-actions bot marked this pull request as draft January 24, 2026 00:16
@eunjae-lee
Copy link
Contributor Author

Left a comment

I can't... see any comment! Did you submit the review?

@eunjae-lee eunjae-lee marked this pull request as ready for review January 26, 2026 13:51
@eunjae-lee eunjae-lee requested a review from volnei January 26, 2026 13:52
@eunjae-lee eunjae-lee dismissed volnei’s stale review January 26, 2026 13:52

feedback is not showing here

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 22 files

…methods (#27195)

* refactor: cleanup features repository and add findBySlug, update methods

- Remove unused methods from FeaturesRepository (keep getTeamsWithFeatureEnabled)
- Add findAll(), findBySlug(), update() to IFeatureRepository interface
- Add findAll() with caching to CachedFeatureRepository
- Add findBySlug() with caching to CachedFeatureRepository
- Add update() with Unmemoize to CachedFeatureRepository
- Add checkIfFeatureIsEnabledGlobally() to CachedFeatureRepository
- Update toggleFeatureFlag.handler.ts to use repository instead of raw Prisma
- Add comprehensive unit tests for all new methods

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* fix: update updatedAt timestamp in feature update method

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* refactor: move feature check methods to specialized repositories

- Replace getUserFeaturesStatus with two checkIfUserHasFeature calls in bookings page
- Move checkIfTeamHasFeature to PrismaTeamFeatureRepository with pass-through in CachedTeamFeatureRepository
- Move checkIfUserHasFeature and checkIfUserHasFeatureNonHierarchical to PrismaUserFeatureRepository with pass-throughs in CachedUserFeatureRepository
- Add getEnabledFeatures to PrismaTeamFeatureRepository with caching in CachedTeamFeatureRepository
- Keep FeaturesRepository methods as pass-throughs for backward compatibility
- Update test to expect updatedAt in feature update

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* refactor: remove getUserFeaturesStatus and unused methods from FeaturesRepository

Co-Authored-By: eunjae@cal.com <hey@eunjae.dev>

* restore comment

* fix: invalidate all-features cache on update and enabledFeatures cache on upsert/delete

- CachedFeatureRepository: Add KEY.all() to @Unmemoize keys in update() to prevent stale findAll() results
- CachedTeamFeatureRepository: Add KEY.enabledFeatures(teamId) to @Unmemoize keys in upsert() and delete() to prevent stale getEnabledFeatures() results

Co-Authored-By: unknown <>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Add comprehensive tests for CachedUserFeatureRepository covering:
- findByUserIdAndFeatureId (cache hit, cache miss, not found)
- findByUserIdAndFeatureIds (empty input, multiple features)
- upsert (with cache invalidation)
- delete (with cache invalidation)
- findAutoOptInByUserId (cache hit, cache miss, not found)
- setAutoOptIn (with cache invalidation)

Co-Authored-By: unknown <>
@eunjae-lee
Copy link
Contributor Author

@cubic-dev-ai review this PR

@cubic-dev-ai
Copy link
Contributor

cubic-dev-ai bot commented Jan 26, 2026

@cubic-dev-ai review this PR

@eunjae-lee I have started the AI code review. It will take a few minutes to complete.

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.

2 issues found across 29 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/flags/repositories/PrismaFeatureRepository.ts">

<violation number="1" location="packages/features/flags/repositories/PrismaFeatureRepository.ts:20">
P2: Limit Prisma reads to the fields you return by adding a `select` clause here to avoid fetching unused columns.</violation>

<violation number="2" location="packages/features/flags/repositories/PrismaFeatureRepository.ts:37">
P2: Add a `select` clause to fetch only the fields used in the returned DTO.</violation>
</file>

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

@github-actions
Copy link
Contributor

Devin AI is addressing Cubic AI's review feedback

A Devin session has been created to address the issues identified by Cubic AI.

View Devin Session

Comment on lines 46 to 65
async findByUserIdAndFeatureIds(
userId: number,
featureIds: FeatureId[]
): Promise<Partial<Record<FeatureId, UserFeaturesDto>>> {
const results = await Promise.all(
featureIds.map(async (featureId) => {
const userFeature = await this.findByUserIdAndFeatureId(userId, featureId);
return { featureId, userFeature };
})
);

const result: Partial<Record<FeatureId, UserFeaturesDto>> = {};
for (const { featureId, userFeature } of results) {
if (userFeature !== null) {
result[featureId] = userFeature;
}
}

return result;
}
Copy link
Member

Choose a reason for hiding this comment

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

We should 100% use a find Many here this is a N+1 query.

  const userFeatures = await this.prisma.userFeatures.findMany({
    where: { userId, featureId: { in: featureIds } }
  });

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed: 2bec5d2

Comment on lines +32 to +37
async findByTeamIdsAndFeatureIds(
teamIds: number[],
featureIds: FeatureId[]
): Promise<Partial<Record<FeatureId, Record<number, TeamFeaturesDto>>>> {
return this.prismaTeamFeatureRepository.findByTeamIdsAndFeatureIds(teamIds, featureIds);
}
Copy link
Member

Choose a reason for hiding this comment

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

Is this meant to bypass cache?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's a combination of teamId x featureId. So I think it's quite tricky to invalidate this. Unless we scan the existing keys, it's hard to know which keys to invalidate when something happens to a certain teamId or a certain featureId. And once it happens, we need to find all the keys of teamId x featureId. It seemed too complex. But I'm happy to iterate on this if you have a good idea.

@github-actions github-actions bot marked this pull request as draft January 26, 2026 14:34
devin-ai-integration bot and others added 3 commits January 26, 2026 14:43
Remove integration tests for methods that were intentionally removed:
- getUserFeatureStates
- getTeamsFeatureStates
- getUserAutoOptIn
- getTeamsAutoOptIn
- setUserAutoOptIn
- setTeamAutoOptIn

Co-Authored-By: unknown <>
…:calcom/cal.com into devin/1769164663-split-flag-repositories
- Add explicit select clauses to findAll, findBySlug, and update methods
- Only fetch fields needed for FeatureDto (slug, enabled, description, type, stale, lastUsedAt, createdAt, updatedAt, updatedBy)
- Update tests to expect select clauses
- Fix UserFeatureRepository test to use findMany mock

Co-Authored-By: unknown <>
@eunjae-lee eunjae-lee marked this pull request as ready for review January 26, 2026 15:17
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 30 files

@github-actions github-actions bot marked this pull request as draft January 28, 2026 12:16
@eunjae-lee eunjae-lee marked this pull request as ready for review January 29, 2026 14:17
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 30 files

@eunjae-lee eunjae-lee dismissed stale reviews from hbjORbj and sean-brydon January 29, 2026 16:24

addressed

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 5 additional flags.

Open in Devin Review

Copy link
Member

@sean-brydon sean-brydon left a comment

Choose a reason for hiding this comment

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

Tested works very well - love this approach.

@eunjae-lee eunjae-lee merged commit 29ec2b9 into main Feb 2, 2026
53 checks passed
@eunjae-lee eunjae-lee deleted the devin/1769164663-split-flag-repositories branch February 2, 2026 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants