stable

portability

Export half of `withPortability()`: a non-owner takes a portable, re-keyed .noydb copy of what their keyring can decrypt — non-destructive, always allowed.
Subpath@noy-db/hub/portability
FactorywithPortability()
Layerwith-audit
Two halves of one service. Export and withdrawal are the two workflows of the single withPortability() opt-in. This page covers export — taking a non-destructive portable copy of your data. To remove your data (one-party self-serve, or a two-party request → approve ceremony), see withdrawal.

What it does

A noy-db vault is an encrypted document store that several people can share: each member unlocks it with their own secret-protected keyring, which holds a role (owner, admin, operator, client, viewer, custodian) and the per-collection data-encryption keys (DEKs) that role was granted (see team).

The withPortability() service adds a data-sovereignty surface to vault.user.*: a non-owner can take a portable copy of their own accessible data (this page), or withdraw it entirely (the withdrawal page). "Their own" is enforced cryptographically, not by a runtime allowlist — the API operates only on collections whose DEKs the caller's keyring holds, so a record outside the caller's keys is undecryptable and can never enter the export.

Export — exportMyAccessibleData() — is the non-destructive half: it always succeeds (there is no policy gate a firm could use to deny a member a copy of data they can already decrypt) and is audited.

When you need it

  • GDPR-style data portability (Art. 20) for the clients of a firm-run vault — a member takes a re-keyed, independently-openable copy of their records with them.
  • Personal backups: a member snapshots the data they can decrypt, under their own secret, without touching the live vault.
  • The first, always-safe step of an offboarding flow — the destructive follow-through (erase / hand-over) lives on withdrawal.

Prerequisites

  • None for the read-only export surface — withPortability() alone enables vault.user.*.
  • In a multi-user vault: teamStrategy: withTeam() provides the roles and grants the export scoping reads.
  • For destructive self-serve withdrawal: additional gates apply — see withdrawal.

Opt in

Pass portabilityStrategy: withPortability() to createNoydb(). Without it, every vault.user.* portability method (export and withdrawal) throws PortabilityNotEnabledError, and the export/withdraw/request engines stay out of your bundle (they are loaded on demand behind the opt-in).

import { createNoydb } from '@noy-db/hub'
import { withTeam } from '@noy-db/hub/team'
import { withPortability } from '@noy-db/hub/portability'

const db = await createNoydb({
  user: 'client1',
  secret: 'client-pw-long-enough',
  teamStrategy: withTeam(),                 // multi-user vault: roles + grants
  portabilityStrategy: withPortability(),   // enables vault.user.* export + withdrawal
})
const vault = await db.openVault('acme')

This one opt-in enables both halves of the service. The multi-user scenarios assume a vault provisioned with withTeam(), where the firm (owner) has granted the caller a keyring.

Export a portable copy

Non-destructive and always allowed — there is no policy gate a firm could use to deny a member a copy of the data they can already decrypt. The call is audited.

const bytes = await vault.user.exportMyAccessibleData({
  reKey: { secret: 'my-portable-copy-secret' },
  scope: { collections: ['invoices'] },     // optional narrowing
})
// → Uint8Array — a standard `.noydb` bundle, independently openable
//   with the reKey secret (see the bundle service)
  • Omit reKey to keep the bundle under the source keyring (a personal backup).
  • Omit scope to export everything the caller can decrypt.

Who can do what

Export is available to every role, always scoped to what the caller can decrypt:

RoleexportMyAccessibleData scope
owner / adminall collections
operatorgranted collections
clientgranted collections
viewerall collections
custodianall collections

Export is non-destructive, so there is no role that is denied it — a member exporting a copy of data they can already read reveals nothing new. The destructive side (who may delete vs. who must request) is governed separately on withdrawal → Who can do what.

API reference

The @noy-db/hub/portability subpath exports withPortability(), the PortabilityStrategy type, the NO_PORTABILITY no-op default, PortabilityNotEnabledError, and every option/result type across both halves of the service.

vault.user.exportMyAccessibleData(opts?)

Re-keyed, access-scoped .noydb bundle of the caller's accessible data.

OptionTypeDefaultMeaning
reKey{ secret: string }inherit source keyringMake the bundle independently openable by a new owner
scope{ collections?: string[] }all accessibleNarrow to a subset of the caller's collections
compression'auto' | 'brotli' | 'gzip' | 'none''auto'Bundle compression

The five withdrawal methods (unilateralWithdrawal, requestWithdrawal, listWithdrawalRequests, approveWithdrawal, rejectWithdrawal) are documented on withdrawal → API reference.

Errors

ErrorThrown when
PortabilityNotEnabledErrorAny vault.user.* method called without portabilityStrategy: withPortability()

Export has no other failure modes — it cannot be policy-denied. The withdrawal-specific errors (ReadOnlyError, PolicyDeniedError, WithdrawalRequestError) are covered on withdrawal → Errors.

Audit

An export appends an op: 'lifecycle' entry to the tamper-evident ledger when the vault has history enabled (without it, auditing is a silent no-op). The entry participates in the ledger's hash chain but carries no data payload — only a structured reason:

Operationreason
Exportuser-export:<userId>

The withdrawal operations write their own ledger entries — see withdrawal → Audit.

Limits & gotchas

  • Export is scoped to the caller's keys: a record whose DEK the caller's keyring does not hold is undecryptable and never enters the bundle. There is no way to export beyond your own access.
  • Audit entries are only written when a history/ledger strategy is configured; enable withHistory() for the compliance trail.

See also

  • withdrawal — the destructive other half of withPortability(): one-party and two-party removal, source dispositions, and the policy gates.
  • bundle — the .noydb container format; readPod() to open an exported bundle.
  • team — roles, grants, and per-collection permissions.
  • history — the tamper-evident ledger the audit entry lands in.
  • Proven by showcase 120-client-portability-withdrawal.