Summary
Today a keyset can only be supplied at client initialization (Encryption({ config: { keyset } })), which binds the client to a single keyset for its lifetime. For multi-tenant applications this forces one Encryption() client per tenant. We should also allow passing a keyset to the individual encrypt / decrypt (and bulk / model) operations, so a single client can operate across keysets.
Motivation
- Multi-tenant services currently have to build and cache a client per tenant. A per-operation keyset override would let one client serve many tenants.
- It mirrors how
.withLockContext(...) already layers per-operation identity binding on top of a client — a per-operation keyset would be the analogous knob for keyspace selection.
Proposed shape (to be refined)
Allow an optional keyset in the encrypt/decrypt options, overriding the client-level default when present:
await client.encrypt("alice@example.com", {
column: users.email,
table: users,
keyset: { name: "tenant-a" }, // or { id: "<uuid>" }
})
await client.decrypt(ciphertext, { keyset: { name: "tenant-a" } })
Resolution rule: per-operation keyset wins over config.keyset, which in turn falls back to the workspace default.
Scope / things to work through
- Which operations get it:
encrypt, decrypt, encryptQuery, and the bulk / model variants.
- How it flows through to protect-ffi (init currently maps
KeysetIdentifier via toFfiKeysetIdentifier → { Name } | { Uuid }; confirm the FFI encrypt/decrypt calls accept a per-op keyset).
- UUID validation currently happens once in
Encryption(); per-op keysets would need validation at the operation boundary too.
- Interaction with lock context and
authStrategy (should be independent).
- Docs / TypeDoc updates once implemented.
Context
Came out of the strategy → authStrategy rename + keysets TypeDoc work. The Encryption() TypeDoc now documents keysets as an init-only, one-client-per-tenant concept (packages/stack/src/encryption/index.ts); this issue tracks lifting that restriction.
Summary
Today a keyset can only be supplied at client initialization (
Encryption({ config: { keyset } })), which binds the client to a single keyset for its lifetime. For multi-tenant applications this forces oneEncryption()client per tenant. We should also allow passing a keyset to the individualencrypt/decrypt(and bulk / model) operations, so a single client can operate across keysets.Motivation
.withLockContext(...)already layers per-operation identity binding on top of a client — a per-operationkeysetwould be the analogous knob for keyspace selection.Proposed shape (to be refined)
Allow an optional
keysetin the encrypt/decrypt options, overriding the client-level default when present:Resolution rule: per-operation
keysetwins overconfig.keyset, which in turn falls back to the workspace default.Scope / things to work through
encrypt,decrypt,encryptQuery, and the bulk / model variants.KeysetIdentifierviatoFfiKeysetIdentifier→{ Name } | { Uuid }; confirm the FFI encrypt/decrypt calls accept a per-op keyset).Encryption(); per-op keysets would need validation at the operation boundary too.authStrategy(should be independent).Context
Came out of the
strategy→authStrategyrename + keysets TypeDoc work. TheEncryption()TypeDoc now documents keysets as an init-only, one-client-per-tenant concept (packages/stack/src/encryption/index.ts); this issue tracks lifting that restriction.