Files
stack/plans/prisma-middleware-migration-plan.md
Jason Woltje af299abdaf
All checks were successful
ci/woodpecker/push/infra Pipeline was successful
ci/woodpecker/push/orchestrator Pipeline was successful
ci/woodpecker/push/api Pipeline was successful
ci/woodpecker/push/web Pipeline was successful
debug(auth): log session cookie source
2026-02-18 21:36:01 -06:00

239 lines
8.3 KiB
Markdown

# 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`