Error handling
Handle errors with the Result pattern used across the SDK
Error handling
All async methods in the SDK return a Result object — a discriminated union with either a data key (success) or a failure key (error). You never get both. The SDK never throws exceptions.
The Result pattern
const result = await client.encrypt("hello@example.com", {
column: users.email,
table: users,
})
if (result.failure) {
// result.failure.type: string (e.g. "EncryptionError")
// result.failure.message: string (human-readable description)
console.error(result.failure.type, result.failure.message)
} else {
// result.data: Encrypted payload
console.log(result.data)
}This pattern applies to every async method: Encryption(), encrypt, decrypt, encryptModel, decryptModel, bulkEncrypt, bulkDecrypt, encryptQuery, and LockContext.identify().
Error types
| Type | When it occurs | Common causes |
|---|---|---|
ClientInitError | Encryption() initialization | Missing or invalid credentials, unreachable ZeroKMS endpoint, no schemas provided |
EncryptionError | encrypt, encryptModel, bulkEncrypt, encryptQuery | Invalid plaintext for the column's data type, NaN/Infinity for numeric columns, non-string value for freeTextSearch column |
DecryptionError | decrypt, decryptModel, bulkDecrypt | Corrupted ciphertext, wrong keyset, lock context mismatch (data was encrypted with a different identity) |
LockContextError | LockContext operations | Invalid JWT, missing required claims, CTS endpoint unreachable |
CtsTokenError | CTS token exchange | JWT rejected by CipherStash Token Service, expired token, CTS endpoint misconfigured |
Handling initialization errors
Client initialization is the most common place to encounter errors. Unlike other SDK operations, Encryption() throws on failure rather than returning a Result.
import { Encryption } from "@cipherstash/stack"
import { users } from "./schema"
try {
const client = await Encryption({ schemas: [users] })
} catch (error) {
// Check your CS_* environment variables
console.error("Failed to initialize:", error.message)
process.exit(1)
}Common initialization issues
| Symptom | Cause | Fix |
|---|---|---|
"Missing workspace CRN" | CS_WORKSPACE_CRN not set | Set the env var or pass workspaceCrn in config |
"Missing client ID" | CS_CLIENT_ID not set | Set the env var or pass clientId in config |
"Missing client key" | CS_CLIENT_KEY not set | Set the env var or pass clientKey in config |
"Missing access key" | CS_CLIENT_ACCESS_KEY not set | Set the env var or pass accessKey in config |
| Connection timeout | ZeroKMS endpoint unreachable | Check network connectivity and firewall rules |
Handling encrypt/decrypt errors
const encrypted = await client.encrypt(value, {
column: users.email,
table: users,
})
if (encrypted.failure) {
switch (encrypted.failure.type) {
case "EncryptionError":
// Log the error and decide whether to retry or fail
console.error("Encryption failed:", encrypted.failure.message)
break
}
}Common encrypt/decrypt issues
| Symptom | Cause | Fix |
|---|---|---|
"NaN is not a valid value" | Passed NaN to a numeric column | Validate inputs before encrypting |
"Infinity is not a valid value" | Passed Infinity to a numeric column | Validate inputs before encrypting |
"Expected string value" | Non-string value for a column with .freeTextSearch() | Convert to string or remove the freeTextSearch index |
| Decryption fails with lock context error | Data was encrypted with a different identity | Ensure the same user's JWT is used for both encrypt and decrypt |
| Decryption returns corrupted data error | Wrong keyset or tampered ciphertext | Verify the keyset matches the one used during encryption |
Handling bulk operation errors
Bulk operations (bulkDecrypt, bulkDecryptModels) support per-item error handling. The overall operation succeeds but individual items may fail.
const result = await client.bulkDecrypt(encryptedPayloads)
if (result.failure) {
// Entire operation failed (e.g., ZeroKMS unreachable)
console.error("Bulk decrypt failed:", result.failure.message)
} else {
// Per-item results
for (const item of result.data) {
if ("data" in item) {
console.log(`${item.id}: decrypted successfully`)
} else {
// Individual item failed
console.error(`${item.id}: ${item.error}`)
}
}
}Handling identity errors
When using identity-aware encryption, errors can occur during JWT identification or when using lock contexts.
import { LockContext } from "@cipherstash/stack/identity"
const lc = new LockContext()
const identifyResult = await lc.identify(userJwt)
if (identifyResult.failure) {
switch (identifyResult.failure.type) {
case "LockContextError":
// JWT is invalid or missing required claims
console.error("Identity error:", identifyResult.failure.message)
break
case "CtsTokenError":
// Token exchange with CTS failed
console.error("CTS error:", identifyResult.failure.message)
break
}
}Common identity issues
| Symptom | Cause | Fix |
|---|---|---|
LockContextError with missing claim | JWT doesn't contain the configured identity claim | Check your JWT contains the sub claim (or your custom claims) |
CtsTokenError with connection error | CTS endpoint unreachable | Verify CS_CTS_ENDPOINT is set correctly |
CtsTokenError with rejection | JWT is expired or not trusted by CTS | Ensure JWT is fresh and from a configured identity provider |
Wrapping errors in application code
A common pattern is to create a helper that converts Result into thrown errors for frameworks that expect exceptions (e.g., Express, Next.js API routes):
function unwrap<T>(result: { data?: T; failure?: { type: string; message: string } }): T {
if (result.failure) {
throw new Error(`[${result.failure.type}] ${result.failure.message}`)
}
return result.data as T
}
// Usage
const encrypted = unwrap(await client.encrypt("hello", { column: users.email, table: users }))Good to know: The
Resultpattern is intentionally designed to avoid thrown exceptions. Use theunwraphelper only at your application's boundaries where exceptions are expected.
Logging
Control SDK log verbosity with the STASH_STACK_LOG environment variable:
STASH_STACK_LOG=error # debug | info | error (default: error)| Value | What is logged |
|---|---|
error | Errors only (default) |
info | Info and errors |
debug | Debug, info, and errors |
The SDK never logs plaintext data or key material at any log level.