chore: [Booking Refactor - Preparation - 0]Move modules required by RegularBookingService to moduleLoader format#23740
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
WalkthroughThis PR refactors DI wiring to a module loader pattern. It introduces a public ModuleLoader type and extends bindModuleToClassOnToken with overloads for single or multiple dependencies, shifting to constructor-based deps. Attribute, Host, Ooo, and User modules now export moduleLoader objects that depend on prismaModuleLoader. LuckyUser switches from static DI tokens to a depsMap of repository moduleLoaders and exports its own moduleLoader. The container’s LuckyUser retrieval loads the module via luckyUserServiceModuleLoader.loadModule(container) and resolves using luckyUserServiceModuleLoader.token. Possibly related PRs
Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. ✨ Finishing touches
🧪 Generate unit tests
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. Comment |
|
Hey there and thank you for opening this pull request! 👋🏼 We require pull request titles to follow the Conventional Commits specification and it looks like your proposed title needs to be adjusted. Details: |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
E2E results are ready! |
3122f8c to
0216266
Compare
3774f99 to
a12e086
Compare
| container.load(DI_TOKENS.PRISMA_MODULE, prismaModule); | ||
| container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule); | ||
| container.load(DI_TOKENS.HOST_REPOSITORY_MODULE, hostRepositoryModule); | ||
| container.load(DI_TOKENS.OOO_REPOSITORY_MODULE, oooRepositoryModule); | ||
| container.load(DI_TOKENS.USER_REPOSITORY_MODULE, userRepositoryModule); | ||
| container.load(DI_TOKENS.ATTRIBUTE_REPOSITORY_MODULE, attributeRepositoryModule); | ||
| container.load(DI_TOKENS.LUCKY_USER_SERVICE_MODULE, luckyUserServiceModule); |
There was a problem hiding this comment.
These modules are now automatically loaded by LuckyUser modiule which needs these. Also, now container doesn't need to do import management, that is the job of the module itself
| * It assumes that there is a single dependency, passed as argument to the constructor | ||
| * | ||
| * @returns A function that can be used to load the dependencies into the container automatically. | ||
| */ | ||
| export function bindModuleToClassOnToken<TClass extends new (deps: any) => any>({ |
There was a problem hiding this comment.
Added two separate signatures to support dep and depsMap, in a mutually exclusive way by TypeScript
| moduleToken, | ||
| token, | ||
| classs: PrismaAttributeRepository, | ||
| dep: prismaModuleLoader, |
There was a problem hiding this comment.
We use dep instead of depsMap because there is only a single argument defined for PrismaAttributeRepository. So, dep is for such simplistic scenarios. If ever this repository needs to inject more values, it should start using the depsMap which would require the class to define deps as a property on the instance and that would be passed to the constructor
| export const attributeRepositoryModule = createModule(); | ||
| attributeRepositoryModule | ||
| .bind(DI_TOKENS.ATTRIBUTE_REPOSITORY) | ||
| .toClass(PrismaAttributeRepository, [DI_TOKENS.PRISMA_CLIENT]); |
There was a problem hiding this comment.
We remove the manual TOKEN access here and instead we would just use the import from prisma.module
a12e086 to
9a03dc1
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
packages/lib/di/di.ts (1)
31-50: Single-dependency overload reads cleanly.This removes ambiguity at call sites and matches the repository modules’ usage in this PR.
packages/lib/di/modules/Attribute.ts (1)
10-16: Correct use ofdeppath for single dependency; matches the intended API.This mirrors the pattern discussed in prior comments—good call using
depuntil multiple deps are needed.
🧹 Nitpick comments (6)
packages/lib/di/di.ts (1)
57-112: Load dependencies first; small ergonomics tweaks.
- Load deps before calling container.load to avoid order surprises.
- Use Object.values when iterating depsMap to simplify and avoid inherited keys.
- Optional: consider symbol-keyed deps if needed.
Apply:
@@ - if (dep) { - module.bind(token).toClass(classs, [dep.token]); - } else if (depsMap) { - const depsObject = Object.fromEntries(Object.entries(depsMap).map(([key, value]) => [key, value.token])); - module.bind(token).toClass(classs, depsObject); - } + if (dep) { + module.bind(token).toClass(classs, [dep.token]); + } else if (depsMap) { + const entries = Object.entries(depsMap) as [string, ModuleLoader][]; + const depsObject = Object.fromEntries(entries.map(([key, value]) => [key, value.token])); + module.bind(token).toClass(classs, depsObject); + } @@ - return function loadModule(container: Container) { - container.load(moduleToken, module); - - if (dep) { - dep.loadModule(container); - } else if (depsMap) { - for (const key in depsMap) { - const loadModule = depsMap[key as keyof typeof depsMap].loadModule; - loadModule(container); - } - } - }; + return function loadModule(container: Container) { + if (dep) { + dep.loadModule(container); + } else if (depsMap) { + for (const loader of Object.values(depsMap)) { + loader.loadModule(container); + } + } + container.load(moduleToken, module); + };Confirmed @evyweb/ioctopus supports both array and object-map forms for toClass. (npmjs.com)
packages/lib/di/modules/Host.ts (1)
18-21: Minor: prevent accidental mutation of the loader object.Optional: freeze or mark as readonly to guard against accidental reassignment.
-export const moduleLoader: ModuleLoader = { - token, - loadModule, -}; +export const moduleLoader: ModuleLoader = Object.freeze({ + token, + loadModule, +}) as ModuleLoader;packages/lib/di/modules/Attribute.ts (1)
18-21: Nit: make the exported loader immutable for safety.Same small hardening as in Host.
-export const moduleLoader: ModuleLoader = { - token, - loadModule, -}; +export const moduleLoader: ModuleLoader = Object.freeze({ + token, + loadModule, +}) as ModuleLoader;packages/lib/di/containers/LuckyUser.ts (2)
9-10: Ensure idempotent loading to avoid duplicate bindings.If
getLuckyUserService()is called multiple times, consider guardingloadModuleso the module is loaded once per container.- luckyUserServiceModuleLoader.loadModule(container); - return container.get<LuckyUserService>(luckyUserServiceModuleLoader.token); + // Load once per process/container + if (!(globalThis as any).__luckyUserLoaded) { + luckyUserServiceModuleLoader.loadModule(container); + (globalThis as any).__luckyUserLoaded = true; + } + return container.get<LuckyUserService>(luckyUserServiceModuleLoader.token);Alternatively (outside the changed lines), introduce a local guard:
// top-level (outside the function) let luckyUserLoaded = false; function ensureLuckyUserLoaded() { if (!luckyUserLoaded) { luckyUserServiceModuleLoader.loadModule(container); luckyUserLoaded = true; } }Then inside the function:
ensureLuckyUserLoaded(); return container.get<LuckyUserService>(luckyUserServiceModuleLoader.token);
4-4: Tiny ergonomics: add an explicit return type to the getter.Improves API clarity and refactors.
-export function getLuckyUserService() { +export function getLuckyUserService(): LuckyUserService {packages/lib/di/modules/LuckyUser.ts (1)
11-26: Ensure depsMap keys exactly match LuckyUserService constructor depsConstructor uses ILuckyUserService with keys bookingRepository, hostRepository, oooRepository, userRepository, attributeRepository — add a compile-time check using
satisfies:+type LuckyUserDeps = ConstructorParameters<typeof LuckyUserService>[0]; +const depsMapTyped = { + bookingRepository: bookingRepositoryModuleLoader, + hostRepository: hostRepositoryModuleLoader, + oooRepository: oooRepositoryModuleLoader, + userRepository: userRepositoryModuleLoader, + attributeRepository: attributeRepositoryModuleLoader, +} satisfies Record<keyof LuckyUserDeps, ModuleLoader>; -const loadModule = bindModuleToClassOnToken({ +const loadModule = bindModuleToClassOnToken({ module: thisModule, moduleToken, token, classs: LuckyUserService, - depsMap: { - bookingRepository: bookingRepositoryModuleLoader, - hostRepository: hostRepositoryModuleLoader, - oooRepository: oooRepositoryModuleLoader, - userRepository: userRepositoryModuleLoader, - attributeRepository: attributeRepositoryModuleLoader, - }, + depsMap: depsMapTyped, });
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (7)
packages/lib/di/containers/LuckyUser.ts(1 hunks)packages/lib/di/di.ts(2 hunks)packages/lib/di/modules/Attribute.ts(1 hunks)packages/lib/di/modules/Host.ts(1 hunks)packages/lib/di/modules/LuckyUser.ts(1 hunks)packages/lib/di/modules/Ooo.ts(1 hunks)packages/lib/di/modules/User.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
**/*.ts: For Prisma queries, only select data you need; never useinclude, always useselect
Ensure thecredential.keyfield is never returned from tRPC endpoints or APIs
Files:
packages/lib/di/modules/Ooo.tspackages/lib/di/modules/Host.tspackages/lib/di/modules/User.tspackages/lib/di/containers/LuckyUser.tspackages/lib/di/modules/LuckyUser.tspackages/lib/di/di.tspackages/lib/di/modules/Attribute.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()in hot paths like loops
Files:
packages/lib/di/modules/Ooo.tspackages/lib/di/modules/Host.tspackages/lib/di/modules/User.tspackages/lib/di/containers/LuckyUser.tspackages/lib/di/modules/LuckyUser.tspackages/lib/di/di.tspackages/lib/di/modules/Attribute.ts
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.
Files:
packages/lib/di/modules/Ooo.tspackages/lib/di/modules/Host.tspackages/lib/di/modules/User.tspackages/lib/di/containers/LuckyUser.tspackages/lib/di/modules/LuckyUser.tspackages/lib/di/di.tspackages/lib/di/modules/Attribute.ts
🧠 Learnings (1)
📚 Learning: 2025-09-01T08:56:14.071Z
Learnt from: nangelina
PR: calcom/cal.com#23486
File: packages/app-store/kyzon-space/lib/tokenManager.ts:25-31
Timestamp: 2025-09-01T08:56:14.071Z
Learning: In token refresh utilities like tokenManager.ts, accessing credential.key from Prisma is legitimate and necessary for OAuth token refresh flows. These internal utilities need stored credentials to refresh tokens and don't expose them in API responses.
Applied to files:
packages/lib/di/modules/Attribute.ts
🧬 Code graph analysis (5)
packages/lib/di/modules/Ooo.ts (3)
packages/lib/di/di.ts (2)
bindModuleToClassOnToken(57-113)ModuleLoader(4-4)packages/lib/di/tokens.ts (1)
DI_TOKENS(3-58)packages/prisma/prisma.module.ts (1)
moduleLoader(13-19)
packages/lib/di/modules/Host.ts (4)
packages/lib/di/di.ts (2)
bindModuleToClassOnToken(57-113)ModuleLoader(4-4)packages/lib/di/tokens.ts (1)
DI_TOKENS(3-58)packages/lib/server/repository/host.ts (1)
HostRepository(3-40)packages/prisma/prisma.module.ts (1)
moduleLoader(13-19)
packages/lib/di/modules/User.ts (3)
packages/lib/di/di.ts (2)
bindModuleToClassOnToken(57-113)ModuleLoader(4-4)packages/lib/di/tokens.ts (1)
DI_TOKENS(3-58)packages/prisma/prisma.module.ts (1)
moduleLoader(13-19)
packages/lib/di/modules/LuckyUser.ts (7)
packages/lib/di/di.ts (2)
bindModuleToClassOnToken(57-113)ModuleLoader(4-4)packages/lib/di/tokens.ts (1)
DI_TOKENS(3-58)packages/lib/di/modules/Attribute.ts (1)
moduleLoader(18-21)packages/lib/di/modules/Host.ts (1)
moduleLoader(18-21)packages/lib/di/modules/Ooo.ts (1)
moduleLoader(18-21)packages/lib/di/modules/User.ts (1)
moduleLoader(18-21)packages/lib/di/modules/Booking.ts (1)
moduleLoader(11-16)
packages/lib/di/modules/Attribute.ts (3)
packages/lib/di/di.ts (2)
bindModuleToClassOnToken(57-113)ModuleLoader(4-4)packages/lib/di/tokens.ts (1)
DI_TOKENS(3-58)packages/prisma/prisma.module.ts (1)
moduleLoader(13-19)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Detect changes
🔇 Additional comments (12)
packages/lib/di/modules/Ooo.ts (3)
3-3: LGTM on imports and adopting the moduleLoader pattern.Using the Prisma module’s loader and pulling helpers from ../di keeps this consistent with the new DI approach.
Also applies to: 5-5
18-21: Nice, clear public surface for consumers.Exporting
{ token, loadModule }as a namedmoduleLoaderaligns with the standard used elsewhere. No default export—good.
8-16: Confirmed — PrismaOOORepository has a single-parameter constructor; no change needed.
Constructor isconstructor(private prismaClient: PrismaClient)in packages/lib/server/repository/ooo.ts, so using the single-dependency overload (dep: prismaModuleLoader) is correct.packages/lib/di/di.ts (2)
4-5: Good addition: minimal, reusable ModuleLoader type.This clarifies the contract across modules and avoids leaking container specifics.
13-29: Overload for deps-map looks solid; keys inferred from ctor param.The
keyof (TClass extends new (deps: infer TDeps)...)trick gives helpful TS guidance to callers.packages/lib/di/modules/User.ts (3)
3-3: Consistent adoption of prismaModuleLoader and DI helpers.Matches the pattern used in Ooo.ts; good consistency and no default exports.
Also applies to: 5-5
18-21: Public moduleLoader shape is correct and minimal.Token + loader exported as a named export; aligns with guidelines.
8-16: No change required — UserRepository takes a single PrismaClient dependency.Signature: packages/lib/server/repository/user.ts — constructor(private prismaClient: PrismaClient) {}. Keep the single-dep binding (dep: prismaModuleLoader) as-is.
packages/lib/di/modules/Host.ts (1)
10-16: Solid shift to moduleLoader with single-dep; constructor wiring looks correct.Using
dep: prismaModuleLoaderaligns withHostRepository’s single-arg ctor and the newbindModuleToClassOnTokenAPI. Tokens (HOST_REPOSITORY,HOST_REPOSITORY_MODULE) are consistent.packages/lib/di/modules/LuckyUser.ts (3)
28-31: moduleLoader export matches the project patternConsistent with other modules; good to ship.
33-33: Type-only re-export is cleanKeeps runtime lean while exposing the type. LGTM.
4-9: No DI cycle found — imports look safe. Searched packages/lib/di/modules for direct imports of "./LuckyUser" and occurrences of LuckyUserService; no matches found.
9a03dc1 to
c2e60af
Compare
c2e60af to
bb65331
Compare
bb65331 to
072d562
Compare

What does this PR do?
This PR refactors the dependency injection (DI) system for the modules that are needed to be injected in RegularBookingService.
Key Changes:
bindModuleToClassOnTokenfunction with better type safety and flexibility for single/multiple dependenciesmoduleLoaderobject withtokenandloadModulepropertiesLuckyUser.tsby leveraging the new moduleLoader patternMandatory Tasks (DO NOT REMOVE)
Checklist