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¶
prisma-client-jsis 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.- The 4 services' current
import { PrismaClient } from '../../node_modules/.prisma/<name>-client'pattern is known-broken undernest start --watch(fails with "Cannot find module" when running fromdist/). Integration tests work becausets-jestloads directly fromsrc/. The newprisma-clientgenerator emits intosrc/generated/prisma/, whichnest buildcopies intodist/generated/prisma/, resolving the bug as a side effect.
In scope¶
- Bump
prisma+@prisma/clientto^7.8.0in all 7 package.jsons (5 services + 2 shared packages). - Update 4
schema.prismafiles:provider = "prisma-client-js"→provider = "prisma-client",output→../src/generated/prisma. - Update 4
prisma.service.tsfiles to import from the new generated path. - Add one root-level
.gitignorerule for the generated output. - Remove any lingering references to the old
node_modules/.prisma/<name>-clientpath (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:devand hit/health/ready— expect HTTP 200. This is the new verification enabled by the generator migration.
Out of scope (explicitly deferred)¶
- Adopt
omitconfig for PHI-sensitive fields (belongs in a dedicated PHI-at-rest PR that also addresses the notifications TODOs forNotification.variablesandresolvedAddressencryption). - TypedSQL, Client Extensions (we don't use
$usemiddleware 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-onlyreports no drift across all 5 services. pnpm --filter @sa-platform/notifications start:devboots cleanly and/health/readyreturns 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.prisma — no 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 viapnpm install.
Files NOT touched¶
services/clinical-api/**— uses@prisma/clientdefault 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/clientfor types only, no migration needed.- Seed scripts (
services/clinical-api/prisma/seed/*.ts,services/clinical-api/scripts/encrypt-existing-data.ts) — use@prisma/clientdefault, no change. - Any
schema.prismainservices/clinical-api/— default generator, no change. - Any migration SQL — forward-compatible across 6 → 7.
- CI workflow (
.github/workflows/ci.yml) — existingprisma:generatecommands 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.
- From repo root:
pnpm install— clean. Expect substantial lockfile churn as 7.x transitive deps update. pnpm --filter "./services/*" prisma:generate— all 5 services generate cleanly. The 4 migrated services emit intosrc/generated/prisma/; clinical-api continues emitting intonode_modules/@prisma/client.- Inspect one migrated service's
src/generated/prisma/contents to confirm the exact entry-point path. Lock in the import statement across all 4prisma.service.tsfiles 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. pnpm --filter @sa-platform/common build && pnpm --filter @sa-platform/auth-client build— shared packages rebuild against 7.x.pnpm turbo run typecheck— all 9 workspace packages clean.pnpm turbo run test— full unit suite green across all services (~500+ tests).- 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" - Per-service smoke migrations in sequence (not parallel):
services/auth,services/clinical-api,services/consent,services/notifications,services/user-management— each gets oneprisma migrate dev --create-only --name smoke_prisma7_upgradewith the appropriateDATABASE_URL.- Expected: each reports no diff. Delete the resulting smoke-migration directories before moving on (same cleanup as PR 22).
- 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.
- Smoke the notifications service boot — the key new verification:
Expect HTTP 200 with
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"status":"ok". This is the verification we couldn't do in PR 22. - 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 firstprisma 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/clientoutput 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:devmodule-resolution bug flagged in PR 22 — the 4 services that use custom generator output paths should boot cleanly viapnpm start:devafter 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.variablesandresolvedAddress. TODO comments in place. Aligns with adopting Prisma 7'somitconfig to keep PHI fields out of default query results. Dedicated PR. - Migrate clinical-api's generator from
prisma-client-jstoprisma-client. Optional cleanup for uniformity if the project wants it; no functional driver right now. - Replace seed scripts'
@prisma/clientimports if clinical-api ever moves to the new generator. - Periodic re-enqueue sweep for stuck-queued notifications (platform concern, not Prisma).
slackUserIdfield on user-management schema (needed before Slack DMs to users work end-to-end).