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

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 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:

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 $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


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.tsaccount-encryption.extension.ts
    • llm-encryption.middleware.tsllm-encryption.extension.ts
    • account-encryption.middleware.spec.tsaccount-encryption.extension.spec.ts
    • llm-encryption.middleware.spec.tsllm-encryption.extension.spec.ts