Migrating from 4.1.x to 5.0.0

Four breaking changes — all aliased and dev-warned in 4.1.0. If your 4.1.x dev console is clean, your 5.0 upgrade is a one-line bump.

TL;DR

5.0 closes the deprecation window opened by 4.1.0. Every removal here was already deprecated and dev-warned a minor release ago, so the upgrade path is:

  1. On 4.1.x, run your dev server. Any deprecated prop, callback, or function fires a one-shot [ndpr-toolkit/<module>] warning in the console.
  2. Fix each warning at its callsite.
  3. Bump to ^5.0.0.
pnpm up @tantainnovative/ndpr-toolkit@^5.0.0
# or
bun add @tantainnovative/ndpr-toolkit@^5.0.0
# or
npm install @tantainnovative/ndpr-toolkit@^5.0.0

If you skipped 4.1, read the 4.1 release notes first — every removal here had a one-minor window where both shapes worked, but a 4.0 → 5.0 jump means making the changes blind.

The breaking changes

1. Uniform list-manager callbacks: onAdd / onUpdate / onArchive

The three list-manager components and the useROPA hook each had module-specific callback names. 4.1 added uniform onAdd/onUpdate/onArchive names alongside the legacy ones. 5.0 drops the legacy names entirely.

Component / HookRemoved (4.x)Use instead (5.0)
LawfulBasisTrackeronAddActivity / onUpdateActivity / onArchiveActivityonAdd / onUpdate / onArchive
CrossBorderTransferManageronAddTransfer / onUpdateTransfer / onRemoveTransferonAdd / onUpdate / onArchive
ROPAManageronAddRecord / onUpdateRecord / onArchiveRecordonAdd / onUpdate / onArchive
useROPA optionsonRecordAdd / onRecordUpdate / onRecordArchiveonAdd / onUpdate / onArchive
// Before (4.x)
<LawfulBasisTracker
  activities={activities}
  onAddActivity={handleAdd}
  onUpdateActivity={handleUpdate}
  onArchiveActivity={handleArchive}
/>

// After (5.0)
<LawfulBasisTracker
  activities={activities}
  onAdd={handleAdd}
  onUpdate={handleUpdate}
  onArchive={handleArchive}
/>

Note: CrossBorderTransferManager's legacy callback was onRemoveTransfer but the canonical name is onArchive — the NDPA prefers soft-delete with audit trails over hard removal, and the new name matches what the component actually does.

2. NDPRDPIA: onComplete(answers)onResult(result)

The 4.x onComplete callback fired with just the raw answer map, throwing away the computed risk assessment. The new onResult receives the full DPIAResult — overall risk level, can-proceed verdict, conclusion, recommendations — so consumers can branch on outcome without re-running the assessment.

// Before (4.x)
<NDPRDPIA
  sections={sections}
  onComplete={(answers) => {
    // had to re-run assessDPIARisk() yourself to get risk score
    const result = assessDPIARisk(answers);
    if (result.overallRiskLevel === 'high') notifyDpo(result);
  }}
/>

// After (5.0)
<NDPRDPIA
  sections={sections}
  onResult={(result) => {
    if (result.overallRiskLevel === 'high') notifyDpo(result);
    // result.answers, result.overallRiskLevel, result.canProceed,
    // result.conclusion, result.recommendations all available
  }}
/>

3. ROPAManagerLite: recordsropa

ROPAManagerLite previously took a flat records: ProcessingRecord[]. The full ROPAManager takes a ropa: RecordOfProcessingActivities object (records + metadata). 5.0 unifies the API on the richer shape.

// Before (4.x)
<ROPAManagerLite records={records} />

// After (5.0)
<ROPAManagerLite
  ropa={{
    records,
    lastUpdated: Date.now(),
    version: '1.0',
    // ...other RecordOfProcessingActivities metadata
  }}
/>

4. Validators return structured errors, not English strings

Four validator functions were renamed to drop the Structured suffix and their legacy string-returning counterparts were removed. The new shape returns { valid; errors: Array<{ field, code, message }>; data? } — locale-independent and switch-able on stable codes.

Removed (4.x)Use instead (5.0)
validateConsentvalidateConsentStructured
validateConsentOptionsvalidateConsentOptionsStructured
validateDsrSubmissionvalidateDsrSubmissionStructured
formatDSRRequestformatDSRRequestStructured
// Before (4.x) — Next.js Route Handler
import { validateDsrSubmission } from '@tantainnovative/ndpr-toolkit/server';

export async function POST(req: Request) {
  const { valid, errors, data } = validateDsrSubmission(await req.json());
  if (!valid) {
    // errors is Record<string, string> of English messages
    return Response.json({ errors }, { status: 422 });
  }
  await dsrStore.create(data);
  return Response.json({ ok: true }, { status: 201 });
}

// After (5.0)
import { validateDsrSubmissionStructured } from '@tantainnovative/ndpr-toolkit/server';

export async function POST(req: Request) {
  const { valid, errors, data } = validateDsrSubmissionStructured(await req.json());
  if (!valid) {
    // errors is Array<{ field, code, message }> — switch on `code`
    return Response.json({ errors }, { status: 422 });
  }
  await dsrStore.create(data);
  return Response.json({ ok: true }, { status: 201 });
}

The error codevalues are stable and locale-independent — branch on them in client code instead of regex-matching English. Each validator's complete list of emitted codes is documented in its JSDoc.

What hasn't changed

  • Peer dependency range: still react ^18 || ^19.
  • Subpath exports: all 22 still resolve in ESM + CJS + TS.
  • Locales: same seven shipped (en, yo, ig, ha, pcm, ar, fr).
  • The NDPRProvider / NDPRThemeProvider APIs.
  • The cookie / localStorage / api / memory adapter contracts.
  • The Lite variant set (consent, lawful-basis, cross-border, ROPA).

Upgrade checklist

  1. Pin to ^4.1.0 if you're not already.
  2. Run your test suite and dev server. Watch the console for [ndpr-toolkit/...] deprecation warnings.
  3. For each warning, fix the callsite using the tables above.
  4. Re-run tests until the console is clean.
  5. Bump to ^5.0.0.
  6. Re-run tests one more time.

Stuck? Open an issue at github.com/mr-tanta/ndpr-toolkit/issues.