# Prisma Middleware Migration Plan: $use to $extends ## Problem Summary The application fails to start with the following error: ``` TypeError: prisma.$use is not a function at registerAccountEncryptionMiddleware (/app/apps/api/dist/prisma/account-encryption.middleware.js:45:12) ``` ### Root Cause The project uses **Prisma 6.19.2**, which removed the deprecated `$use()` middleware API. The `$use()` method was deprecated in Prisma4.16.0 and removed in Prisma5.0.0. The replacement is **Prisma Client Extensions** using the `$extends()` API. ### Affected Files | File | Purpose | | ---------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | | [`account-encryption.middleware.ts`](apps/api/src/prisma/account-encryption.middleware.ts) | Encrypts/decrypts OAuth tokens in Account table | | [`llm-encryption.middleware.ts`](apps/api/src/prisma/llm-encryption.middleware.ts) | Encrypts/decrypts API keys in LlmProviderInstance.config | | [`prisma.service.ts`](apps/api/src/prisma/prisma.service.ts) | Registers both middleware functions | | [`account-encryption.middleware.spec.ts`](apps/api/src/prisma/account-encryption.middleware.spec.ts) | Unit tests for account encryption | | [`llm-encryption.middleware.spec.ts`](apps/api/src/prisma/llm-encryption.middleware.spec.ts) | Unit tests for LLM encryption | | [`prisma.service.spec.ts`](apps/api/src/prisma/prisma.service.spec.ts) | Unit tests for PrismaService | --- ## Migration Strategy ### Current Architecture (Broken) ```mermaid flowchart TD A[PrismaService.onModuleInit] --> B[$connect to database] B --> C[registerAccountEncryptionMiddleware] B --> D[registerLlmEncryptionMiddleware] C --> E[prisma.$use - REMOVED IN PRISMA5] D --> F[prisma.$use - REMOVED IN PRISMA5] E --> G[ERROR: $use is not a function] F --> G ``` ### Target Architecture (Client Extensions) ```mermaid flowchart TD A[PrismaService] --> B[Create Extended Client] B --> C[prisma.$extends with Account query override] B --> D[prisma.$extends with LlmProviderInstance query override] C --> E[Extended Client with transparent encryption] D --> E E --> F[All queries use extended client automatically] ``` --- ## Implementation Tasks ### Phase 1: Create Extension Functions #### Task 1.1: Create Account Encryption Extension Replace [`account-encryption.middleware.ts`](apps/api/src/prisma/account-encryption.middleware.ts) with a Client Extension: **Key changes:** - Remove `registerAccountEncryptionMiddleware` function - Create `createAccountEncryptionExtension` function that returns a Prisma extension - Use `prisma.$extends({ query: { account: { ... } } })` pattern - Override `$allOperations` or specific operations: `create`, `update`, `upsert`, `findUnique`, `findFirst`, `findMany` **Extension structure:** ```typescript export function createAccountEncryptionExtension(prisma: PrismaClient, vaultService: VaultService) { return prisma.$extends({ query: { account: { async $allOperations({ model, operation, args, query }) { // Pre-operation: encrypt on writes // Execute: call original query // Post-operation: decrypt on reads }, }, }, }); } ``` #### Task 1.2: Create LLM Encryption Extension Replace [`llm-encryption.middleware.ts`](apps/api/src/prisma/llm-encryption.middleware.ts) with similar Client Extension pattern for `LlmProviderInstance` model. ### Phase 2: Update PrismaService #### Task 2.1: Modify PrismaService to Use Extensions Update [`prisma.service.ts`](apps/api/src/prisma/prisma.service.ts:28-45): **Current code:** ```typescript async onModuleInit() { await this.$connect(); registerAccountEncryptionMiddleware(this, this.vaultService); registerLlmEncryptionMiddleware(this, this.vaultService); } ``` **New approach:** - Create extended client in constructor or onModuleInit - Store extended client as property - Export extended client for use throughout application **Challenge:** PrismaService extends PrismaClient. We need to decide: - Option A: Return extended client from a getter method - Option B: Create a wrapper that delegates to extended client - Option C: Use composition instead of inheritance **Recommended: Option A with factory pattern** ```typescript @Injectable() export class PrismaService { private readonly baseClient: PrismaClient; private readonly extendedClient: ExtendedPrismaClient; constructor(vaultService: VaultService) { this.baseClient = new PrismaClient({...}); this.extendedClient = createExtendedClient(this.baseClient, vaultService); } // Delegate all PrismaClient methods to extended client get $queryRaw() { return this.extendedClient.$queryRaw; } // ... other delegates } ``` ### Phase 3: Update Unit Tests #### Task 3.1: Update account-encryption.middleware.spec.ts - Change mock from `$use` to `$extends` - Update test structure to work with extension pattern - Test that extension correctly intercepts queries #### Task 3.2: Update llm-encryption.middleware.spec.ts Same updates as Task 3.1 for LLM encryption. #### Task 3.3: Update prisma.service.spec.ts - Remove `$use` mock - Update to test extension registration ### Phase 4: Integration Testing #### Task 4.1: Verify Encryption/Decryption Works - Run existing integration tests - Verify OAuth tokens are encrypted at rest - Verify LLM API keys are encrypted at rest - Verify transparent decryption on read #### Task 4.2: Test Backward Compatibility - Verify existing encrypted data can be decrypted - Verify plaintext data is encrypted on next write --- ## Detailed Implementation Checklist ### Files to Modify - [ ] `apps/api/src/prisma/account-encryption.middleware.ts` - Rename to `account-encryption.extension.ts` or keep name - Replace `$use` with `$extends` pattern - [ ] `apps/api/src/prisma/llm-encryption.middleware.ts` - Rename to `llm-encryption.extension.ts` or keep name - Replace `$use` with `$extends` pattern - [ ] `apps/api/src/prisma/prisma.service.ts` - Refactor to use extended client - Maintain backward compatibility for existing code - [ ] `apps/api/src/prisma/account-encryption.middleware.spec.ts` - Update mocks and test structure - [ ] `apps/api/src/prisma/llm-encryption.middleware.spec.ts` - Update mocks and test structure - [ ] `apps/api/src/prisma/prisma.service.spec.ts` - Update mocks and test structure - [ ] `apps/api/src/prisma/index.ts` (if exists) - Update exports if file names change --- ## Risk Assessment | Risk | Impact | Mitigation | | ------------------------- | -------- | ----------------------------------------- | | Breaking existing queries | High | Comprehensive test coverage before/after | | Type safety issues | Medium | Use Prisma generated types with extension | | Performance regression | Low | Extension overhead is minimal | | Data corruption | Critical | Test with real encryption/decryption | --- ## References - [Prisma Client Extensions Documentation](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions) - [Migration Guide: Middleware to Extensions](https://www.prisma.io/docs/concepts/components/prisma-client/middleware) - Original TODO comments in code pointing to this migration --- ## Execution Order 1. **Code Mode**: Implement extension functions (Phase 1) 2. **Code Mode**: Update PrismaService (Phase 2) 3. **Code Mode**: Update unit tests (Phase 3) 4. **Debug Mode**: Integration testing and verification (Phase 4) --- ## User Decisions - **File naming**: Rename middleware files to `.extension.ts` for clarity - `account-encryption.middleware.ts` → `account-encryption.extension.ts` - `llm-encryption.middleware.ts` → `llm-encryption.extension.ts` - `account-encryption.middleware.spec.ts` → `account-encryption.extension.spec.ts` - `llm-encryption.middleware.spec.ts` → `llm-encryption.extension.spec.ts`