8.3 KiB
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 |
Encrypts/decrypts OAuth tokens in Account table |
llm-encryption.middleware.ts |
Encrypts/decrypts API keys in LlmProviderInstance.config |
prisma.service.ts |
Registers both middleware functions |
account-encryption.middleware.spec.ts |
Unit tests for account encryption |
llm-encryption.middleware.spec.ts |
Unit tests for LLM encryption |
prisma.service.spec.ts |
Unit tests for PrismaService |
Migration Strategy
Current Architecture (Broken)
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)
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 with a Client Extension:
Key changes:
- Remove
registerAccountEncryptionMiddlewarefunction - Create
createAccountEncryptionExtensionfunction that returns a Prisma extension - Use
prisma.$extends({ query: { account: { ... } } })pattern - Override
$allOperationsor specific operations:create,update,upsert,findUnique,findFirst,findMany
Extension structure:
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 with similar Client Extension pattern for LlmProviderInstance model.
Phase 2: Update PrismaService
Task 2.1: Modify PrismaService to Use Extensions
Update prisma.service.ts:
Current code:
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
@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
$useto$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
$usemock - 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.tsor keep name - Replace
$usewith$extendspattern
- Rename to
-
apps/api/src/prisma/llm-encryption.middleware.ts- Rename to
llm-encryption.extension.tsor keep name - Replace
$usewith$extendspattern
- Rename to
-
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
- Migration Guide: Middleware to Extensions
- Original TODO comments in code pointing to this migration
Execution Order
- Code Mode: Implement extension functions (Phase 1)
- Code Mode: Update PrismaService (Phase 2)
- Code Mode: Update unit tests (Phase 3)
- Debug Mode: Integration testing and verification (Phase 4)
User Decisions
- File naming: Rename middleware files to
.extension.tsfor clarityaccount-encryption.middleware.ts→account-encryption.extension.tsllm-encryption.middleware.ts→llm-encryption.extension.tsaccount-encryption.middleware.spec.ts→account-encryption.extension.spec.tsllm-encryption.middleware.spec.ts→llm-encryption.extension.spec.ts