Skip to content

Prisma 6 → 7 Upgrade — Design Spec

Date: 2026-04-21 Status: Draft — pending review Scope: Monorepo-wide coordinated upgrade of prisma and @prisma/client from ^6.19.3 to the latest Prisma 7.x (^7.8.0), and migration of 4 services from the deprecated prisma-client-js generator to the new prisma-client TypeScript-native generator. Depends on: Prisma 5 → 6 upgrade (PR #22, merged).


1. Scope, Goals, Non-Goals

Goal

Bump the entire SA Platform monorepo from Prisma 6.x to the latest Prisma 7.x, and migrate the 4 services using a custom generator output path (auth, consent, notifications, user-management) from prisma-client-js to the new prisma-client generator. Clinical-api stays on the default prisma-client-js + default output path (already idiomatic, not affected by the pre-existing import-path bug).

Why now

  1. prisma-client-js is deprecated in 7.x and expected to be removed in 8.x. Doing the generator migration during the 6 → 7 bump is materially cheaper than doing it separately under time pressure later.
  2. The 4 services' current import { PrismaClient } from '../../node_modules/.prisma/<name>-client' pattern is known-broken under nest start --watch (fails with "Cannot find module" when running from dist/). Integration tests work because ts-jest loads directly from src/. The new prisma-client generator emits into src/generated/prisma/, which nest build copies into dist/generated/prisma/, resolving the bug as a side effect.

In scope

  • Bump prisma + @prisma/client to ^7.8.0 in all 7 package.jsons (5 services + 2 shared packages).
  • Update 4 schema.prisma files: provider = "prisma-client-js"provider = "prisma-client", output../src/generated/prisma.
  • Update 4 prisma.service.ts files to import from the new generated path.
  • Add one root-level .gitignore rule for the generated output.
  • Remove any lingering references to the old node_modules/.prisma/<name>-client path (docs, env examples, CI scripts, comments).
  • Update pnpm-lock.yaml.
  • Verify: monorepo typecheck, full unit-test suite, full integration-test suite — all green.
  • Per-service smoke prisma migrate dev --create-only — no drift.
  • Smoke-boot the notifications service via pnpm start:dev and hit /health/ready — expect HTTP 200. This is the new verification enabled by the generator migration.

Out of scope (explicitly deferred)

  • Adopt omit config for PHI-sensitive fields (belongs in a dedicated PHI-at-rest PR that also addresses the notifications TODOs for Notification.variables and resolvedAddress encryption).
  • TypedSQL, Client Extensions (we don't use $use middleware anyway — confirmed during 5 → 6 audit).
  • Any schema or migration changes.
  • Touching clinical-api's generator config. It uses the default prisma-client-js + default output path. Leaving it alone is YAGNI-correct; moving it for "uniformity" is not justified by any concrete problem.

Success criteria

  • All CI jobs green on the upgrade PR.
  • Per-service smoke prisma migrate dev --create-only reports no drift across all 5 services.
  • pnpm --filter @sa-platform/notifications start:dev boots cleanly and /health/ready returns HTTP 200 — the pre-existing bug from PR 22 is demonstrably fixed.
  • No production behaviour change — the full test suite (unit + integration) encodes current behaviour and must remain passing.

2. The Actual Changes

This is the complete list of files the PR will touch.

Package manifests (7 files)

Version bump only — both prisma (devDependencies) and @prisma/client (dependencies):

File Before After
services/auth/package.json ^6.19.3 ^7.8.0
services/clinical-api/package.json ^6.19.3 ^7.8.0
services/consent/package.json ^6.19.3 ^7.8.0
services/notifications/package.json ^6.19.3 ^7.8.0
services/user-management/package.json ^6.19.3 ^7.8.0
packages/common/package.json ^6.19.3 ^7.8.0
packages/auth-client/package.json ^6.19.3 ^7.8.0

Exact 7.x minor is resolved at install time; caret pinning matches existing convention.

Schema generator changes (4 files)

For each of services/auth/prisma/schema.prisma, services/consent/prisma/schema.prisma, services/notifications/prisma/schema.prisma, services/user-management/prisma/schema.prisma:

// Before
generator client {
  provider = "prisma-client-js"
  output   = "../node_modules/.prisma/<name>-client"
}

// After
generator client {
  provider = "prisma-client"
  output   = "../src/generated/prisma"
}

services/clinical-api/prisma/schema.prismano change. Stays on default prisma-client-js + default output.

Prisma service import updates (4 files)

For each of services/auth/src/prisma/prisma.service.ts, services/consent/src/prisma/prisma.service.ts, services/notifications/src/prisma/prisma.service.ts, services/user-management/src/prisma/prisma.service.ts:

// Before
import { PrismaClient } from '../../node_modules/.prisma/<name>-client';

// After
import { PrismaClient } from '../generated/prisma/client';

The exact import entry-point path depends on what prisma-client emits as its index. Most likely '../generated/prisma/client', but confirmed empirically by running prisma generate once and inspecting src/generated/prisma/ structure. Plan step: run, inspect, then lock in the import line.

.gitignore addition

Add one root-level rule:

# Prisma-generated clients (prisma-client generator, 7.x+)
services/*/src/generated/

One rule covers all affected services without per-service .gitignore churn.

Lockfile

  • pnpm-lock.yaml — regenerated via pnpm install.

Files NOT touched

  • services/clinical-api/** — uses @prisma/client default import, no migration needed.
  • packages/common/src/prisma/prisma.service.ts — already uses @prisma/client (generic shared wrapper), works unchanged against 7.x.
  • packages/auth-client/src/** — uses @prisma/client for types only, no migration needed.
  • Seed scripts (services/clinical-api/prisma/seed/*.ts, services/clinical-api/scripts/encrypt-existing-data.ts) — use @prisma/client default, no change.
  • Any schema.prisma in services/clinical-api/ — default generator, no change.
  • Any migration SQL — forward-compatible across 6 → 7.
  • CI workflow (.github/workflows/ci.yml) — existing prisma:generate commands pick up 7.x automatically and emit to the new locations. Re-verify empirically during implementation.

3. Verification Plan

Local verification sequence

All steps must pass before pushing.

  1. From repo root: pnpm install — clean. Expect substantial lockfile churn as 7.x transitive deps update.
  2. pnpm --filter "./services/*" prisma:generate — all 5 services generate cleanly. The 4 migrated services emit into src/generated/prisma/; clinical-api continues emitting into node_modules/@prisma/client.
  3. Inspect one migrated service's src/generated/prisma/ contents to confirm the exact entry-point path. Lock in the import statement across all 4 prisma.service.ts files based on the observed structure. If the path differs from ./client (e.g. if it's just the directory index), adjust all four imports accordingly.
  4. pnpm --filter @sa-platform/common build && pnpm --filter @sa-platform/auth-client build — shared packages rebuild against 7.x.
  5. pnpm turbo run typecheck — all 9 workspace packages clean.
  6. pnpm turbo run test — full unit suite green across all services (~500+ tests).
  7. Verify the shadow-DB grant from PR 22 is still present:
    docker exec cdm-mysql mysql -usa_platform -psa_platform -e "SHOW GRANTS FOR 'sa_platform'@'%';" | grep "CREATE, DROP"
    
  8. Per-service smoke migrations in sequence (not parallel):
  9. services/auth, services/clinical-api, services/consent, services/notifications, services/user-management — each gets one prisma migrate dev --create-only --name smoke_prisma7_upgrade with the appropriate DATABASE_URL.
  10. Expected: each reports no diff. Delete the resulting smoke-migration directories before moving on (same cleanup as PR 22).
  11. Integration-test suites — all 5 services: notifications, consent, user-management, auth, clinical-api. All green. Clinical-api's Minio-dependent tests remain skipped unless Minio is running.
  12. Smoke the notifications service boot — the key new verification:
    cd services/notifications
    nohup pnpm start:dev > /tmp/notif-smoke.log 2>&1 &
    echo $! > /tmp/notif-smoke.pid
    sleep 8
    curl -sS http://localhost:3004/health/ready
    
    Expect HTTP 200 with "status":"ok". This is the verification we couldn't do in PR 22.
  13. Stop the service: kill $(cat /tmp/notif-smoke.pid).

CI verification

  • Lint / typecheck / unit-tests job across all packages — green.
  • Integration-tests job for each service — green.

Rollback plan

Same as PR 22: revert the single PR. No persisted data-layer changes, no schema changes, nothing to unwind.


4. Risks & Open Questions

Risks

Risk Likelihood Mitigation
New generator emits a slightly different type surface than prisma-client-js Medium Typecheck + full test suite catches drift. Keep fixes narrow. Mock-shape drift is acceptable; logic drift means STOP and investigate.
PrismaClient constructor signature changes between generators Low 7.x docs confirm the class is re-exported with a compatible constructor. Our NotificationsPrismaService extends PrismaClient {} pattern should survive unchanged.
nest build fails to copy src/generated/ contents into dist/ Low NestJS uses TypeScript's emit, which includes everything under the rootDir (src/). src/generated/prisma/ emits to dist/generated/prisma/. Step 10 of verification confirms.
CI's build step doesn't include prisma generate before TypeScript compile Low CI already runs pnpm --filter ... prisma:generate before typecheck (ci.yml lines 33-39). Same sequence works for the new generator.
Jest / ts-jest resolves the new relative import path differently than tsc Low ts-jest uses the same tsconfig module resolution. Existing integration tests from PR 22 already use ts-jest with relative paths successfully. If it fails, fallback is a moduleNameMapper in jest.config.ts.
$use middleware fully removed in 7.x — any use would break None Confirmed zero $use usage in the 5 → 6 audit.
rejectOnNotFound option fully removed in 7.x None Confirmed zero usage.
start:dev smoke reveals a different, unrelated bug Low-Medium Treat as newly discovered. Not blocking unless it's a regression from 6.x — 6.x already had the (different) import-path bug.
Smoke-migration output files contain an unwanted drop-tables diff Low Seen in PR 22 for clinical-api (local DB had stale tables from pre-extraction state). Mitigation is the same: delete the smoke-migration directories, do not commit them. Local DB drift is a local-dev concern, not a Prisma upgrade concern.

Open questions resolved during implementation

  • Exact import path from src/generated/prisma/ — determined by inspecting the generated output after the first prisma generate. Plan's default assumption is '../generated/prisma/client', but will be confirmed and adjusted before editing any import line.
  • Whether clinical-api's node_modules/@prisma/client output needs any intervention under 7.x — the default generator behaviour may have changed. If so, resolve during implementation without widening scope.

Known issue this PR fixes

  • The start:dev module-resolution bug flagged in PR 22 — the 4 services that use custom generator output paths should boot cleanly via pnpm start:dev after this migration.

Known issue this PR does NOT address

  • Clinical-api's Minio-dependent integration tests (unrelated; needs local Minio container).

5. Out of scope — follow-up items

Captured so they're not forgotten once 7.x is in place:

  • PHI-at-rest encryption for Notification.variables and resolvedAddress. TODO comments in place. Aligns with adopting Prisma 7's omit config to keep PHI fields out of default query results. Dedicated PR.
  • Migrate clinical-api's generator from prisma-client-js to prisma-client. Optional cleanup for uniformity if the project wants it; no functional driver right now.
  • Replace seed scripts' @prisma/client imports if clinical-api ever moves to the new generator.
  • Periodic re-enqueue sweep for stuck-queued notifications (platform concern, not Prisma).
  • slackUserId field on user-management schema (needed before Slack DMs to users work end-to-end).