239 lines
8.3 KiB
Markdown
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`
|