# Glossary Glossary [#glossary] A [#a] ABAC (Attribute Based Access Control) [#abac-attribute-based-access-control] ABAC is a dynamic security model that makes access decisions based on a variety of attributes, including user characteristics, resource details, and environmental factors. Unlike [RBAC](#rbac-role-based-access-control), which relies solely on a user's assigned role, ABAC offers more granular and context-aware policies that adapt to changing conditions and diverse scenarios. Access key [#access-key] A persistent authentication credential used for communication with [ZeroKMS](#zerokms) or [CTS](#cts-cipherstash-token-service). See [Access keys](/docs/kms/access-keys) for details. Account management [#account-management] Activities to administer your CipherStash account, like billing, adding, and removing users. C [#c] Ciphertext [#ciphertext] An encrypted version of plaintext, produced by applying an encryption algorithm (a cipher). It is unreadable without a cipher to decrypt it. See also: [Plaintext](#plaintext) CipherStash CLI [#cipherstash-cli] The command line tool for interacting with CipherStash services. CipherStash Proxy [#cipherstash-proxy] A database proxy that sits between an application and a database, enhancing your existing database with encryption in use. CipherStash Proxy works in-tandem with your existing infrastructure and is fully contained within your environment. See [CipherStash Proxy](/docs/proxy) for details. Client [#client] A programmatic access point to a [keyset](#keyset). A client can have many keysets, and a keyset can also be shared by multiple clients. To access a keyset, you need a [client key](#client-key) and a [client ID](#client-id). Client ID [#client-id] A unique identifier of a [client](#client). Each [client key](#client-key) and client ID is unique to your app. Client key [#client-key] A unique identifier of a [client](#client). Each client key and [client ID](#client-id) is unique to your app. The client key is sensitive and should be kept secret. CTS (CipherStash Token Service) [#cts-cipherstash-token-service] CTS manages the trust relationships between a workspace and third-party or customer identity providers. It brokers secure access to CipherStash services like ZeroKMS, ensuring that only authenticated and authorized users gain entry. See [CTS](/docs/kms/cts) for details. D [#d] Dashboard [#dashboard] The web interface for configuring and using CipherStash services. Available at [dashboard.cipherstash.com](https://dashboard.cipherstash.com/). Data access event [#data-access-event] An event triggered by execution of SQL statements by CipherStash Proxy. Includes metadata of statements executed and records accessed. E [#e] EQL (Encrypt Query Language) [#eql-encrypt-query-language] Our [open-source library](https://github.com/cipherstash/encrypt-query-language) for PostgreSQL users. It simplifies the process of encrypting and querying sensitive data, giving you powerful tools to encrypt data transparently at the field level, query encrypted data directly using familiar SQL commands, and leverage encrypted indexes for secure and efficient searches. See [EQL](/docs/platform/eql) for details. H [#h] HMAC (Hash-based Message Authentication Code) [#hmac-hash-based-message-authentication-code] A cryptographic technique that combines a hash function with a secret key to verify both the integrity and authenticity of a message. Unlike raw hash functions (such as SHA-256), HMAC requires a secret key, which means only parties with the key can generate valid HMACs. This prevents attackers from pre-computing hash tables (rainbow tables) or guessing values. In searchable encryption, HMACs are used to create encrypted search tokens — the key stays client-side, so the server can match encrypted search tokens without ever learning the plaintext or being able to generate new tokens. I [#i] IdP (Identity Provider) [#idp-identity-provider] A third party identity provider, like Auth0, Okta, or Clerk. J [#j] JWT (JSON Web Token) [#jwt-json-web-token] JWT is a compact, URL-safe means of representing claims between two parties as a JSON object, typically used for authentication and authorization. They are digitally signed to ensure the integrity and authenticity of the information, allowing systems to verify user identity without maintaining server-side sessions. K [#k] Keyset [#keyset] A keyset is used to generate data keys, and is managed by [ZeroKMS](#zerokms). It includes configuration for encrypted columns and queryable indexes. Use keysets to group data for a specific purpose or project. A [client](#client) can have many keysets, and a keyset can also be shared by multiple clients. See [Keysets](/docs/kms/keysets) for details. O [#o] OIDC (OpenID Connect) [#oidc-openid-connect] OIDC is an identity layer built on top of the OAuth 2.0 protocol that enables clients to verify user identity through an [Identity Provider](#idp-identity-provider). It facilitates secure single sign-on (SSO) and simplifies the authentication process by allowing the Identity Provider to share standardized identity information using RESTful APIs and [JSON Web Tokens](#jwt-json-web-token) (JWTs). ORE (Order Revealing Encryption) [#ore-order-revealing-encryption] A searchable encryption technique allowing for search, comparison, and sorting of encrypted data without decryption. See [Range queries](/docs/platform/supported-queries#range--order) for details. P [#p] Plaintext [#plaintext] Unencrypted information, readable by humans and computers. R [#r] RBAC (Role Based Access Control) [#rbac-role-based-access-control] RBAC is a security model that assigns access permissions based on a user's role within an organization, streamlining the management of access rights by grouping permissions into predefined roles. Unlike [ABAC](#abac-attribute-based-access-control), which evaluates policies based on a range of attributes, RBAC relies solely on roles to determine access. S [#s] Searchable encrypted metadata [#searchable-encrypted-metadata] An encrypted data structure for finding records in encrypted columns. Essential for querying encrypted data, as it replaces the need for full table scans, improving performance. This is a core feature of CipherStash, supporting range, exact, and match queries. See [Supported queries](/docs/platform/supported-queries) for details. W [#w] Workspace [#workspace] CipherStash uses workspaces to keep things organized. A workspace contains [keysets](#keyset), clients, and configuration, and can: * Be used to separate environments (e.g. dev and prod) * Be shared with other users * Be associated with a custom identity provider See [Platform](/docs/platform) for details. Z [#z] ZeroKMS [#zerokms] CipherStash's specialized key management service. ZeroKMS provides high performance batch encryption and decryption, enabling a unique encryption key per field. See [ZeroKMS](/docs/kms) for details. # CipherStash Documentation CipherStash Documentation [#cipherstash-documentation] CipherStash is the new standard for data security that feels invisible. Encrypt, control, and audit access to sensitive data directly in your TypeScript applications. ```bash npm install @cipherstash/stack ``` Choose your integration [#choose-your-integration] CipherStash offers a few ways to leverage the data security stack: | | TypeScript SDK | CipherStash Proxy | | ---------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **How it works** | Encryption and Secrets SDK and CLI available as an NPM package | Postgres Encryption - Postgres wire protocol Proxy built on top of the Encryption SDK to transparently encrypt data in Postgres | | **Best for** | Teams moving quickly and building TypeScript apps | More complex app architectures or non TypeScript applications using Postgres | | **Language** | TypeScript / JavaScript (Node.js, Deno, Bun) | Any language (connects via PostgreSQL wire protocol) | | **Setup** | `npm install @cipherstash/stack` | Docker container or binary | Most teams start with the **SDK** for the best developer experience. Get started [#get-started] How it fits together [#how-it-fits-together] Every CipherStash product builds on [ZeroKMS](/docs/kms) and its core primitive, [Key Sets](/docs/kms/keysets): * **[Encryption](/docs/encryption)** uses Key Sets for [multi-tenant isolation](/docs/encryption/configuration#keysets) — each tenant gets its own cryptographic boundary. * **[Secrets](/docs/secrets)** uses Key Sets for [environment isolation](/docs/secrets/concepts#environments) — production, staging, and development secrets are cryptographically separated. * **[Proxy](/docs/proxy)** encrypts data transparently via PostgreSQL, backed by the same ZeroKMS key hierarchy. Understand the architecture [#understand-the-architecture] # Planning guide Planning guide [#planning-guide] This guide helps you plan a CipherStash integration. Use it to evaluate your security posture, choose the right integration path, and understand the architecture. Key discovery areas [#key-discovery-areas] Before starting, assess the following: * **Current data security posture** — How do you currently encrypt sensitive data? Are you relying solely on encryption-at-rest? * **Compliance requirements** — What regulations apply (GDPR, HIPAA, PCI-DSS, SOC2)? * **Architecture constraints** — What databases and application frameworks are you using? * **Security concerns** — What attack vectors are you most concerned about (breaches, insider threats, accidental exposure)? Understanding encryption types [#understanding-encryption-types] Encryption-at-rest [#encryption-at-rest] Data is encrypted when stored on disk but decrypted for any operation. A database breach that exposes memory or query results reveals plaintext data. Encryption-in-transit [#encryption-in-transit] Data is encrypted between services (TLS) but decrypted at each endpoint. Data is exposed in application memory and during processing. Encryption-in-use [#encryption-in-use] Data remains encrypted during search and processing. CipherStash provides encryption-in-use through searchable encryption — you can query encrypted data without decrypting it first. | Capability | At-rest | In-transit | In-use (CipherStash) | | ----------------------------- | ------- | ---------- | -------------------- | | Protects stored data | Yes | No | Yes | | Protects data in transit | No | Yes | Yes | | Protects data during queries | No | No | Yes | | Searchable without decryption | No | No | Yes | | Zero-trust architecture | No | No | Yes | Architecture considerations [#architecture-considerations] Zero-knowledge design [#zero-knowledge-design] CipherStash uses a split-key architecture. [ZeroKMS](/docs/kms) manages an authority key that is necessary but not sufficient to derive data keys. Your application holds an independent client key. Data keys are derived locally and never leave your infrastructure. This means: * Plaintext data never leaves your systems * Data keys are never transmitted over the network * Even CipherStash cannot access your data PostgreSQL integration [#postgresql-integration] CipherStash is designed around PostgreSQL — the most popular open-source database. Integration requires no architecture changes: * **[EQL](/docs/platform/eql)** provides native PostgreSQL types for encrypted data * Existing queries and applications work with minimal modification * Performance overhead is minimal (sub-millisecond for most operations) Integration paths [#integration-paths] | Path | Best for | Code changes | | ---------------------------------- | ---------------------------------------------------------- | --------------------------------------------------- | | [Encryption SDK](/docs/encryption) | Application-layer encryption with fine-grained control | Moderate — encrypt/decrypt calls in your data layer | | [CipherStash Proxy](/docs/proxy) | Existing PostgreSQL applications with minimal code changes | Minimal — point your connection at the proxy | ORM support [#orm-support] The Encryption SDK integrates with popular ORMs: * **[Drizzle ORM](/docs/encryption/drizzle)** — Custom encrypted column types with query operators * **[Supabase](/docs/encryption/supabase)** — Transparent encryption wrapper around the Supabase client * **[DynamoDB](/docs/encryption/dynamodb)** — Encrypted attributes with searchable capabilities ZeroKMS overview [#zerokms-overview] [ZeroKMS](/docs/kms) is CipherStash's zero-trust key management service: * **Key hierarchy** — Authority keys, client keys, and derived data keys provide defense in depth * **Regional deployment** — Available in [multiple regions](/docs/kms/regions) for data sovereignty * **Audit logging** — Complete visibility into key usage and access patterns * **Disaster recovery** — Multi-region key replication with [automated failover](/docs/kms/disaster-recovery) Enterprise features [#enterprise-features] Lock contexts [#lock-contexts] [Lock contexts](/docs/encryption/identity) provide identity-aware encryption. Bind encryption operations to authenticated users using JWTs for: * Temporal access control — time-limited access to sensitive data * Provable access boundaries — cryptographic proof of who accessed what * Audit trails — complete record of all data access events Multi-tenant encryption [#multi-tenant-encryption] Cryptographic tenant isolation ensures complete separation between tenants. Each tenant's data is encrypted with independent key material, meeting strict data residency and compliance requirements. Next steps [#next-steps] 1. **Choose your integration path** — [Encryption SDK](/docs/encryption/getting-started) or [CipherStash Proxy](/docs/proxy/getting-started) 2. **Set up credentials** — Create a workspace in the [CipherStash Dashboard](https://dashboard.cipherstash.com/) 3. **Define your schema** — Declare which columns to encrypt and what queries to support 4. **Deploy to production** — See [supported solutions](/docs/supported-solutions) for deployment options # Supported solutions Supported solutions [#supported-solutions] CipherStash offers multiple integration paths designed for developer productivity and production readiness. Integration options [#integration-options] Encryption SDK [#encryption-sdk] **Best for**: Teams who want fine-grained control over data encryption directly in their application. The `@cipherstash/stack` SDK provides application-layer encryption with full TypeScript support, schema-based configuration, and searchable encryption capabilities. * **Languages**: TypeScript / JavaScript (Node.js) * **ORMs**: [Drizzle](/docs/encryption/drizzle), [Supabase](/docs/encryption/supabase) * **Get started**: [Encryption SDK guide](/docs/encryption/getting-started) CipherStash Proxy [#cipherstash-proxy] **Best for**: DevOps teams who want to add encryption to existing PostgreSQL applications with little to no code changes. A drop-in SQL proxy for PostgreSQL that automatically handles encryption and decryption operations. * **Database**: PostgreSQL only * **Get started**: [Proxy guide](/docs/proxy/getting-started) Supported databases [#supported-databases] Database compatibility [#database-compatibility] | Database | Standard encryption | Searchable encryption | | ----------------------------------- | ------------------- | --------------------- | | PostgreSQL 15+ | Yes | Yes | | AWS RDS PostgreSQL | Yes | Yes | | AWS Aurora PostgreSQL | Yes | Yes | | GCP Cloud SQL for PostgreSQL | Yes | Yes | | Azure Database for PostgreSQL | Yes | Yes | | OCI Database Service for PostgreSQL | Yes | Yes | | DynamoDB | Yes | Yes | | Supabase | Yes | Coming soon | | Neon Postgres | Yes | — | | MySQL | Yes | — | | CockroachDB | Yes | — | **Standard encryption** works with any database that supports JSON or JSONB column types. Encrypted values are stored as JSON objects ([CipherCells](/docs/platform/cipher-cell)). **Searchable encryption** requires [EQL](/docs/platform/eql) (for PostgreSQL) or native integration (for DynamoDB). Searchable encryption enables equality lookups, range queries, ordering, and free-text search on encrypted data without decryption. Performance characteristics [#performance-characteristics] SDK integration [#sdk-integration] * **Latency**: Less than 5ms overhead for most operations * **Throughput**: Scales with your application * **Setup time**: Running in local dev in under 1 hour, production in under 3 days Proxy integration [#proxy-integration] * **Latency**: Less than 5ms overhead for most operations * **Throughput**: Horizontally scalable based on database throughput * **Setup time**: Operational in hours with existing PostgreSQL Getting started [#getting-started] 1. **Choose your integration path** based on your application type and requirements 2. **Review the getting started guide** for your selected solution — [Encryption SDK](/docs/encryption/getting-started) or [Proxy](/docs/proxy/getting-started) 3. **Set up your development environment** with the appropriate SDK or proxy 4. **Provision credentials** in the [CipherStash Dashboard](https://dashboard.cipherstash.com/) 5. **Deploy to production** with confidence in your security posture # API reference API reference [#api-reference] > For auto-generated TypeDoc reference with full type signatures, see the [generated API reference](/docs/reference). Encryption(config) [#encryptionconfig] Initialize the encryption client. Returns `Promise` and throws on error (e.g., bad credentials, missing config, invalid keyset UUID). ```typescript title="init.ts" import { Encryption } from "@cipherstash/stack" import { users } from "./schema" try { const client = await Encryption({ schemas: [users], config: { /* optional — reads CS_* env vars by default */ }, }) } catch (error) { console.error("Init failed:", error.message) } ``` Config options [#config-options] | Option | Type | Required | Description | | --------- | ------------------------ | -------- | ------------------------------------------------------- | | `schemas` | `EncryptedTable[]` | Yes | One or more encrypted table schemas | | `config` | `EncryptionClientConfig` | No | Explicit credentials. If omitted, reads `CS_*` env vars | EncryptionClientConfig [#encryptionclientconfig] ```typescript title="types.ts" type EncryptionClientConfig = { workspaceCrn: string // CRN format: "crn::" clientId: string // Client identifier clientKey: string // Client key material for ZeroKMS accessKey: string // API key for CipherStash API keyset?: { name?: string // Keyset name (multi-tenant isolation) id?: string // Keyset UUID (alternative to name) } } ``` Environment variables [#environment-variables] | Variable | Maps to | Description | | ---------------------- | -------------- | ---------------------------------------------------------- | | `CS_WORKSPACE_CRN` | `workspaceCrn` | Workspace identifier in CRN format | | `CS_CLIENT_ID` | `clientId` | Client identifier | | `CS_CLIENT_KEY` | `clientKey` | Client key material | | `CS_CLIENT_ACCESS_KEY` | `accessKey` | API key for authentication | | `STASH_STACK_LOG` | — | Log verbosity: `debug`, `info`, `error` (default: `error`) | The Result type [#the-result-type] All async methods return a `Result` — a discriminated union with either `data` (success) or `failure` (error), never both. ```typescript title="types.ts" type Result = | { data: T; failure?: never } | { failure: E; data?: never } type EncryptionError = { type: "ClientInitError" | "EncryptionError" | "DecryptionError" | "LockContextError" | "CtsTokenError" message: string } ``` Checking results [#checking-results] ```typescript title="result-check.ts" const result = await client.encrypt("hello", { column: users.email, table: users }) if (result.failure) { // result.failure.type: string // result.failure.message: string console.error(result.failure.type, result.failure.message) } else { // result.data: the encrypted payload console.log(result.data) } ``` Client methods [#client-methods] encrypt [#encrypt] Encrypt a single plaintext value. Null values are preserved (encrypting `null` returns `null`). ```typescript title="encrypt.ts" const result = await client.encrypt(plaintext, { column: users.email, table: users, }) // result.data: Encrypted payload ``` | Parameter | Type | Description | | ---------------- | ------------------------------------------------------- | --------------------------------------------- | | `plaintext` | `string \| number \| boolean \| Date \| bigint \| null` | The value to encrypt | | `options.column` | `ProtectColumn` | Column from your schema (e.g., `users.email`) | | `options.table` | `EncryptedTable` | Table from your schema (e.g., `users`) | Returns `EncryptOperation` — thenable, supports `.withLockContext()` and `.audit()`. decrypt [#decrypt] Decrypt a single encrypted payload. ```typescript title="decrypt.ts" const result = await client.decrypt(encryptedData) // result.data: plaintext value ``` | Parameter | Type | Description | | --------------- | ----------- | ---------------------------------------------------- | | `encryptedData` | `Encrypted` | The encrypted payload from a previous `encrypt` call | Returns `DecryptOperation` — thenable, supports `.withLockContext()`. encryptModel [#encryptmodel] Encrypt all schema-defined fields on an object. Fields not in the schema pass through unchanged. ```typescript title="encrypt-model.ts" type User = { id: string; email: string; createdAt: Date } const result = await client.encryptModel( { id: "user_123", email: "alice@example.com", createdAt: new Date() }, users ) // result.data: { id: "user_123", email: Encrypted, createdAt: Date } ``` | Parameter | Type | Description | | --------- | ---------------- | --------------------------------------------- | | `model` | `T` | The object to encrypt | | `table` | `EncryptedTable` | Table schema defining which fields to encrypt | Returns `EncryptModelOperation` — thenable, supports `.withLockContext()` and `.audit()`. decryptModel [#decryptmodel] Decrypt all encrypted fields on a model back to plaintext. ```typescript title="decrypt-model.ts" const result = await client.decryptModel(encryptedModel) // result.data: { id: "user_123", email: "alice@example.com", createdAt: Date } ``` Returns `DecryptModelOperation` — thenable, supports `.withLockContext()`. encryptQuery [#encryptquery] Encrypt a query term for searching encrypted data in PostgreSQL. ```typescript title="encrypt-query.ts" // Single query const result = await client.encryptQuery("alice@example.com", { column: users.email, table: users, queryType: "equality", // optional — auto-inferred from column indexes }) // Batch query (multiple terms in one ZeroKMS call) const results = await client.encryptQuery([ { value: "alice@example.com", column: users.email, table: users, queryType: "equality" as const }, { value: "bob", column: users.email, table: users, queryType: "freeTextSearch" as const }, ]) ``` | Parameter (single) | Type | Description | | -------------------- | ----------------------------------------------------------------------- | ------------------------------------ | | `plaintext` | `string \| number \| boolean \| object \| null` | The search term | | `options.column` | `ProtectColumn` | Column to search | | `options.table` | `EncryptedTable` | Table schema | | `options.queryType` | `'equality' \| 'freeTextSearch' \| 'orderAndRange' \| 'searchableJson'` | Index type. Auto-inferred if omitted | | `options.returnType` | `'eql' \| 'composite-literal' \| 'escaped-composite-literal'` | Output format. Default: `'eql'` | | `returnType` | Output | Use case | | ----------------------------- | ------------------ | --------------------------------------------- | | `'eql'` (default) | `Encrypted` object | Parameterized queries, ORMs accepting JSON | | `'composite-literal'` | `string` | Supabase `.eq()`, string-based APIs | | `'escaped-composite-literal'` | `string` | Embedding inside another string or JSON value | Returns `EncryptQueryOperation` or `BatchEncryptQueryOperation` — thenable, supports `.withLockContext()`. bulkEncrypt [#bulkencrypt] Encrypt multiple values in a single ZeroKMS call. Each value still gets a unique key. ```typescript title="bulk-encrypt.ts" const result = await client.bulkEncrypt( [ { id: "u1", plaintext: "alice@example.com" }, { id: "u2", plaintext: "bob@example.com" }, { id: "u3", plaintext: null }, ], { column: users.email, table: users } ) // result.data: [{ id: "u1", data: Encrypted }, ...] ``` Returns `BulkEncryptOperation` — thenable, supports `.withLockContext()` and `.audit()`. bulkDecrypt [#bulkdecrypt] Decrypt multiple encrypted payloads in a single ZeroKMS call. Supports per-item error handling. ```typescript title="bulk-decrypt.ts" const result = await client.bulkDecrypt(encryptedPayloads) for (const item of result.data) { if ("data" in item) { console.log(`${item.id}: ${item.data}`) } else { console.error(`${item.id} failed: ${item.error}`) } } ``` Returns `BulkDecryptOperation` — thenable, supports `.withLockContext()`. bulkEncryptModels [#bulkencryptmodels] Encrypt multiple model objects in a single ZeroKMS call. ```typescript title="bulk-encrypt-models.ts" const result = await client.bulkEncryptModels(userModels, users) // result.data: array of encrypted models ``` Returns `BulkEncryptModelsOperation` — thenable, supports `.withLockContext()` and `.audit()`. bulkDecryptModels [#bulkdecryptmodels] Decrypt multiple encrypted models in a single ZeroKMS call. ```typescript title="bulk-decrypt-models.ts" const result = await client.bulkDecryptModels(encryptedModels) // result.data: array of decrypted models ``` Returns `BulkDecryptModelsOperation` — thenable, supports `.withLockContext()`. Operation chaining [#operation-chaining] All operations return thenable objects that support chaining before awaiting: ```typescript title="chaining.ts" const result = await client .encrypt(plaintext, { column: users.email, table: users }) .withLockContext(lockContext) // identity-aware encryption .audit({ metadata: { action: "create" } }) // audit trail ``` | Method | Available on | Description | | ---------------------- | ------------------ | --------------------------------------------------------------------------- | | `.withLockContext(lc)` | All operations | Bind operation to an [identity lock context](/docs/encryption/identity) | | `.audit(options)` | Encrypt operations | Attach metadata to the [audit log](/docs/platform/compliance#audit-logging) | Schema builders [#schema-builders] ```typescript title="schema.ts" import { encryptedTable, encryptedColumn, encryptedField, } from "@cipherstash/stack/schema" ``` encryptedTable(name, columns) [#encryptedtablename-columns] Define an encrypted table schema. | Parameter | Type | Description | | --------- | ------------------------------- | --------------------------------------------------- | | `name` | `string` | Database table name | | `columns` | `Record` | Map of column names to encrypted column definitions | encryptedColumn(name) [#encryptedcolumnname] Define an encrypted column with chainable index methods. | Method | Description | | ------------------------ | ------------------------------------------------------------------------------------------------ | | `.equality()` | Enable exact-match queries (HMAC-SHA-256) | | `.freeTextSearch(opts?)` | Enable full-text / fuzzy search (Bloom filters) | | `.orderAndRange()` | Enable sorting and range queries (Block ORE) | | `.searchableJson()` | Enable encrypted JSONB queries (JSONPath + containment) | | `.dataType(cast)` | Set the plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'bigint'`, `'json'` | Methods are chainable — call as many as needed on a single column. encryptedField(name) [#encryptedfieldname] Define an encrypted field for nested objects. Not searchable. Supports `.dataType()` chaining. Type inference [#type-inference] ```typescript title="types.ts" import type { InferPlaintext, InferEncrypted } from "@cipherstash/stack/schema" type UserPlaintext = InferPlaintext // { email: string; age: string; address: string } type UserEncrypted = InferEncrypted // { email: Encrypted; age: Encrypted; address: Encrypted } ``` LockContext [#lockcontext] Identity-aware encryption using JWT-based lock contexts. ```typescript title="lock-context.ts" import { LockContext } from "@cipherstash/stack/identity" // Default: uses the "sub" claim const lc = new LockContext() // Custom claims const lc = new LockContext({ context: { identityClaim: ["sub", "org_id"] } }) // Identify user const result = await lc.identify(jwtToken) if (result.failure) { console.error(result.failure.message) } else { const lockContext = result.data // Use with .withLockContext(lockContext) } ``` | Constructor option | Type | Default | Description | | ----------------------- | -------------------- | ------- | ---------------------------------- | | `context.identityClaim` | `string \| string[]` | `"sub"` | JWT claim(s) to bind encryption to | Requires `CS_CTS_ENDPOINT` environment variable for the CipherStash Token Service. Subpath exports [#subpath-exports] | Import path | Provides | | ----------------------------- | --------------------------------------------------------------------------------------- | | `@cipherstash/stack` | `Encryption` function (main entry point) | | `@cipherstash/stack/schema` | `encryptedTable`, `encryptedColumn`, `encryptedField`, schema types | | `@cipherstash/stack/identity` | `LockContext` class and identity types | | `@cipherstash/stack/secrets` | `Secrets` class and secrets types | | `@cipherstash/stack/drizzle` | `encryptedType`, `extractEncryptionSchema`, `createEncryptionOperators` for Drizzle ORM | | `@cipherstash/stack/supabase` | `encryptedSupabase` wrapper for Supabase | | `@cipherstash/stack/dynamodb` | `encryptedDynamoDB` helper for DynamoDB | | `@cipherstash/stack/client` | Client-safe exports (schema builders and types only — no native FFI) | | `@cipherstash/stack/types` | All TypeScript types | Validation rules [#validation-rules] The SDK validates inputs before making ZeroKMS calls: | Rule | When it applies | | --------------------------------------------- | -------------------------------------- | | NaN and Infinity are rejected | Numeric values | | Only strings are accepted | Columns with `.freeTextSearch()` index | | At least one schema required | `Encryption()` initialization | | Valid UUID format required | Keyset `id` option | | JWT must be valid and contain required claims | `LockContext.identify()` | # Bundling Bundling [#bundling] `@cipherstash/stack` is a native Node.js module that relies on native `require` to load platform-specific binaries at runtime. It cannot be processed by bundlers like webpack or esbuild — you must exclude it from bundling. **You must exclude `@cipherstash/stack` from bundling.** `@cipherstash/stack` uses Node.js-specific features and requires the [native Node.js `require`](https://nodejs.org/api/modules.html#requireid). Bundlers that attempt to inline or transform it will break the native addon loading. Next.js [#nextjs] Next.js bundles Server Components by default. You need to tell Next.js to skip `@cipherstash/stack` and use native `require` instead. Next.js 15+ [#nextjs-15] Add `@cipherstash/stack` to `serverExternalPackages` in your `next.config.ts`: ```typescript title="next.config.ts" const nextConfig = { serverExternalPackages: ["@cipherstash/stack"], } export default nextConfig ``` See the [Next.js serverExternalPackages documentation](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages) for more details. Next.js 14 [#nextjs-14] Use the experimental configuration in `next.config.mjs`: ```javascript title="next.config.mjs" const nextConfig = { experimental: { serverComponentsExternalPackages: ["@cipherstash/stack"], }, } export default nextConfig ``` See the [Next.js 14 serverComponentsExternalPackages documentation](https://nextjs.org/docs/14/app/api-reference/next-config-js/serverComponentsExternalPackages) for more details. webpack [#webpack] Configure `externals` in your `webpack.config.js`: ```javascript title="webpack.config.js" module.exports = { externals: { "@cipherstash/stack": "commonjs @cipherstash/stack", }, } ``` This tells webpack to leave `require('@cipherstash/stack')` as-is in the output instead of trying to resolve and bundle the module. esbuild [#esbuild] Use the `--external` flag: ```bash esbuild app.js --bundle --external:@cipherstash/stack --platform=node ``` Or in your build configuration: ```javascript require("esbuild").build({ entryPoints: ["app.js"], bundle: true, external: ["@cipherstash/stack"], platform: "node", outfile: "out.js", }) ``` SST and AWS Lambda [#sst-and-aws-lambda] When deploying with [SST](https://sst.dev/), configure esbuild externals and install the package into the Lambda deployment bundle: ```typescript title="sst.config.ts" { nodejs: { esbuild: { external: ["@cipherstash/stack"], }, install: ["@cipherstash/stack"], }, } ``` The `external` option prevents esbuild from bundling the package. The `install` option ensures it gets installed into the Lambda deployment artifact so it's available at runtime. See the [SST Function documentation](https://sst.dev/docs/component/aws/function/#nodejs) for more details. Troubleshooting Linux deployments [#troubleshooting-linux-deployments] Some npm users experience deployment failures on Linux (e.g., AWS Lambda, Docker containers) when their `package-lock.json` was created on macOS or Windows. Who is affected [#who-is-affected] * You use `npm ci` in CI/CD * Your `package-lock.json` is version 3 and was generated on macOS/Windows * You deploy on Linux (Lambda, containers, EC2, etc.) Symptoms [#symptoms] Build succeeds, but the app fails to start on Linux with an error like: * `failed to load native addon` * `module not found` related to the native engine Why this happens [#why-this-happens] With `package-lock.json` version 3, npm only records optional native binaries for the platform that created the lockfile. Linux builds can miss the native engine that `@cipherstash/stack` needs at runtime. Solutions [#solutions] Option 1: Use pnpm (recommended) [#option-1-use-pnpm-recommended] pnpm installs the correct native binaries for each platform automatically: ```bash pnpm install --frozen-lockfile ``` Option 2: Generate lockfile on Linux in CI [#option-2-generate-lockfile-on-linux-in-ci] Ensure the Linux build records what Linux needs: ```bash rm -f package-lock.json npm install --package-lock-only --ignore-scripts \ --no-audit --no-fund --platform=linux --arch=x64 npm ci ``` Alternative using environment variables: ```bash npm_config_platform=linux npm_config_arch=x64 \ npm install --package-lock-only --ignore-scripts --no-audit --no-fund npm ci ``` Option 3: Pin lockfile to version 2 [#option-3-pin-lockfile-to-version-2] Keep using npm but pin lockfile v2 (npm 8): Locally: ```bash npm install --package-lock-only --lockfile-version=2 ``` In CI: ```bash npm i -g npm@8 npm ci ``` Quick verification [#quick-verification] Before deploying to Linux, verify the native engine loads by running your app inside a Linux container or CI job. # Configuration Configuration [#configuration] The SDK supports three configuration methods. Environment variables take precedence over config files. Environment variables [#environment-variables] The simplest approach. Set these in a `.env` file or your hosting platform: | Variable | Description | Required | | ---------------------- | --------------------------------------------------- | -------- | | `CS_WORKSPACE_CRN` | The workspace identifier (CRN format) | Yes | | `CS_CLIENT_ID` | The client identifier | Yes | | `CS_CLIENT_KEY` | Client key material used with ZeroKMS | Yes | | `CS_CLIENT_ACCESS_KEY` | API key for authenticating with the CipherStash API | Yes | | `CS_CONFIG_PATH` | Temporary path for client configuration | No | Sign up at [cipherstash.com/signup](https://cipherstash.com/signup) and follow the onboarding to generate credentials. TOML config files [#toml-config-files] For projects that prefer file-based configuration, use two files in your project root: cipherstash.toml [#cipherstashtoml] Non-sensitive configuration. Safe to commit to version control. ```toml [encrypt] client_id = "your-client-id" [auth] workspace_crn = "your-workspace-crn" ``` | Section | Key | Description | | ----------- | ------------------- | ---------------------------------------------------------- | | `[encrypt]` | `client_id` | Required. Client identifier (UUID). | | `[encrypt]` | `default_keyset_id` | Optional. Default keyset UUID. | | `[encrypt]` | `client_key` | **Not allowed.** Use `cipherstash.secret.toml` or env var. | | `[auth]` | `workspace_crn` | Required. Workspace CRN. | | `[auth]` | `access_key` | **Not allowed.** Use `cipherstash.secret.toml` or env var. | cipherstash.secret.toml [#cipherstashsecrettoml] Sensitive credentials. Add this file to `.gitignore`. ```toml [encrypt] client_key = "your-client-key" [auth] access_key = "your-access-key" ``` Programmatic config [#programmatic-config] Pass config directly when initializing the client. Useful when loading credentials from a secret manager: ```typescript title="protect/index.ts" import { Encryption, type EncryptionClientConfig } from "@cipherstash/stack" import { users } from "./schema" const config: EncryptionClientConfig = { schemas: [users], config: { workspaceCrn: "your-workspace-crn", accessKey: "your-access-key", clientId: "your-client-id", clientKey: "your-client-key", }, } const client = await Encryption(config) ``` Keysets [#keysets] [Key Sets](/docs/kms/keysets) are a ZeroKMS primitive for cryptographic isolation. Each Key Set maintains its own set of data encryption keys — data encrypted under one Key Set cannot be decrypted with another. This is the same primitive that powers [environment isolation in Secrets](/docs/secrets/concepts#environments). You can specify a Key Set by ID or by name: By ID [#by-id] ```typescript title="protect/index.ts" const client = await Encryption({ schemas: [users], config: { keyset: { id: "123e4567-e89b-12d3-a456-426614174000" }, }, }) ``` By name [#by-name] ```typescript title="protect/index.ts" const client = await Encryption({ schemas: [users], config: { keyset: { name: "Company A" }, }, }) ``` For full details on creating and managing Key Sets, see the [Key Sets reference](/docs/kms/keysets). Logging [#logging] Control log verbosity with the `STASH_STACK_LOG` environment variable: ```bash 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. Deploying to production [#deploying-to-production] File system write permissions [#file-system-write-permissions] In most hosting environments, set `CS_CONFIG_PATH` to a writable directory: ```bash CS_CONFIG_PATH=/tmp/.cipherstash ``` This has been tested on [Vercel](https://vercel.com/) and [AWS Lambda](https://aws.amazon.com/lambda/). Bundler configuration [#bundler-configuration] `@cipherstash/stack` includes a native FFI module that must be excluded from bundling: * **Next.js**: See [Bundling — Next.js](/docs/encryption/bundling#nextjs) * **SST / serverless**: See [SST setup](/docs/encryption/sst) * **Webpack / esbuild**: Add `@cipherstash/stack` to your externals configuration Platform requirements [#platform-requirements] * **Node.js** >= 18 * [Bun](https://bun.sh/) is not currently supported due to incomplete Node-API compatibility * See [Troubleshooting](/docs/encryption/troubleshooting) for npm lockfile issues on Linux # Drizzle ORM Drizzle ORM [#drizzle-orm] CipherStash provides first-class Drizzle ORM integration through `@cipherstash/stack/drizzle`. Define encrypted columns directly in your Drizzle table schema, and use auto-encrypting operators that make encrypted queries look like standard Drizzle code. Installation [#installation] ```bash npm install @cipherstash/stack drizzle-orm ``` The Drizzle integration is included in `@cipherstash/stack` and imports from `@cipherstash/stack/drizzle`. Database setup [#database-setup] Install the EQL extension [#install-the-eql-extension] The EQL (Encrypt Query Language) PostgreSQL extension enables searchable encryption functions. Generate a migration: ```bash npx generate-eql-migration ``` Options: | Flag | Default | Description | | ------------------- | ------------- | ---------------- | | `-n, --name ` | `install-eql` | Migration name | | `-o, --out ` | `drizzle` | Output directory | Then apply it: ```bash npx drizzle-kit migrate ``` Column storage [#column-storage] Encrypted columns use the `eql_v2_encrypted` PostgreSQL type (installed by EQL). If not using EQL directly, use JSONB: ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, email eql_v2_encrypted, -- with EQL extension name jsonb NOT NULL, -- or use jsonb age INTEGER -- non-encrypted columns are normal types ); ``` Schema definition [#schema-definition] Use `encryptedType()` to define encrypted columns directly in your Drizzle table schema: ```typescript import { pgTable, integer, timestamp, varchar } from "drizzle-orm/pg-core" import { encryptedType } from "@cipherstash/stack/drizzle" const usersTable = pgTable("users", { id: integer("id").primaryKey().generatedAlwaysAsIdentity(), // Encrypted string with search capabilities email: encryptedType("email", { equality: true, // enables: eq, ne, inArray freeTextSearch: true, // enables: like, ilike orderAndRange: true, // enables: gt, gte, lt, lte, between, asc, desc }), // Encrypted number age: encryptedType("age", { dataType: "number", equality: true, orderAndRange: true, }), // Encrypted JSON object with searchable JSONB queries profile: encryptedType<{ name: string; bio: string }>("profile", { dataType: "json", searchableJson: true, }), // Non-encrypted columns role: varchar("role", { length: 50 }), createdAt: timestamp("created_at").defaultNow(), }) ``` encryptedType(name, config?) [#encryptedtypetdataname-config] | Config Option | Type | Description | | ---------------- | ------------------------------------ | ------------------------------------------------------- | | `dataType` | `"string"` \| `"number"` \| `"json"` | Plaintext data type (default: `"string"`) | | `equality` | `boolean` \| `TokenFilter[]` | Enable equality index | | `freeTextSearch` | `boolean` \| `MatchIndexOpts` | Enable free-text search index | | `orderAndRange` | `boolean` | Enable ORE index for sorting and range queries | | `searchableJson` | `boolean` | Enable JSONB path queries (requires `dataType: "json"`) | The generic type parameter `` sets the TypeScript type for the decrypted value. Initialization [#initialization] 1. Extract schema from Drizzle table [#1-extract-schema-from-drizzle-table] ```typescript import { extractEncryptionSchema, createEncryptionOperators, } from "@cipherstash/stack/drizzle" import { Encryption } from "@cipherstash/stack" // Convert Drizzle table definition to CipherStash schema const usersSchema = extractEncryptionSchema(usersTable) ``` 2. Initialize encryption client [#2-initialize-encryption-client] ```typescript const encryptionClient = await Encryption({ schemas: [usersSchema], }) ``` 3. Create query operators [#3-create-query-operators] ```typescript const encryptionOps = createEncryptionOperators(encryptionClient) ``` 4. Create Drizzle instance [#4-create-drizzle-instance] ```typescript import { drizzle } from "drizzle-orm/postgres-js" import postgres from "postgres" const db = drizzle({ client: postgres(process.env.DATABASE_URL!) }) ``` Insert encrypted data [#insert-encrypted-data] Encrypt models before inserting: ```typescript // Single insert const encrypted = await encryptionClient.encryptModel( { email: "alice@example.com", age: 30, role: "admin" }, usersSchema, ) if (!encrypted.failure) { await db.insert(usersTable).values(encrypted.data) } // Bulk insert const encrypted = await encryptionClient.bulkEncryptModels( [ { email: "alice@example.com", age: 30, role: "admin" }, { email: "bob@example.com", age: 25, role: "user" }, ], usersSchema, ) if (!encrypted.failure) { await db.insert(usersTable).values(encrypted.data) } ``` Query encrypted data [#query-encrypted-data] Equality [#equality] ```typescript // Exact match — await the operator const results = await db .select() .from(usersTable) .where(await encryptionOps.eq(usersTable.email, "alice@example.com")) ``` Text search [#text-search] ```typescript // Case-insensitive search const results = await db .select() .from(usersTable) .where(await encryptionOps.ilike(usersTable.email, "%alice%")) // Case-sensitive search const results = await db .select() .from(usersTable) .where(await encryptionOps.like(usersTable.email, "%Smith%")) ``` Range queries [#range-queries] ```typescript // Greater than or equal const results = await db .select() .from(usersTable) .where(await encryptionOps.gte(usersTable.age, 18)) // Between (inclusive) const results = await db .select() .from(usersTable) .where(await encryptionOps.between(usersTable.age, 18, 65)) ``` Array operators [#array-operators] ```typescript const results = await db .select() .from(usersTable) .where(await encryptionOps.inArray(usersTable.email, [ "alice@example.com", "bob@example.com", ])) ``` Sorting [#sorting] Sort operators are synchronous — no `await` needed: ```typescript // Sort by encrypted column ascending const results = await db .select() .from(usersTable) .orderBy(encryptionOps.asc(usersTable.age)) // Sort descending const results = await db .select() .from(usersTable) .orderBy(encryptionOps.desc(usersTable.age)) ``` JSONB queries [#jsonb-queries] Query encrypted JSON columns using JSONB operators. These require `searchableJson: true` and `dataType: "json"` in the column's `encryptedType` config. Check path existence [#check-path-existence] ```typescript // Check if a JSONB path exists in an encrypted column const results = await db .select() .from(usersTable) .where(await encryptionOps.jsonbPathExists(usersTable.profile, "$.bio")) ``` Extract value at path [#extract-value-at-path] ```typescript // Extract the first matching value at a JSONB path const result = await encryptionOps.jsonbPathQueryFirst(usersTable.profile, "$.name") ``` Get value with -> operator [#get-value-with---operator] ```typescript // Get a value using the JSONB -> operator const result = await encryptionOps.jsonbGet(usersTable.profile, "$.name") ``` > `jsonbPathExists` returns a boolean and can be used in `WHERE` clauses. `jsonbPathQueryFirst` and `jsonbGet` return encrypted values — use them in `SELECT` expressions. Batched conditions (and / or) [#batched-conditions-and--or] Use `encryptionOps.and()` and `encryptionOps.or()` to batch multiple encrypted conditions into a single ZeroKMS call. This is more efficient than awaiting each operator individually. ```typescript // Batched AND — all encryptions happen in one call const results = await db .select() .from(usersTable) .where( await encryptionOps.and( encryptionOps.gte(usersTable.age, 18), // no await — lazy operator encryptionOps.lte(usersTable.age, 65), // no await — lazy operator encryptionOps.ilike(usersTable.email, "%example.com"), eq(usersTable.role, "admin"), // mix with regular Drizzle ops ), ) // Batched OR const results = await db .select() .from(usersTable) .where( await encryptionOps.or( encryptionOps.eq(usersTable.email, "alice@example.com"), encryptionOps.eq(usersTable.email, "bob@example.com"), ), ) ``` Pass lazy operators (no `await`) to `and()`/`or()`, then `await` the outer call. This batches all encryption into a single operation. Decrypt results [#decrypt-results] ```typescript // Single model const decrypted = await encryptionClient.decryptModel(results[0]) if (!decrypted.failure) { console.log(decrypted.data.email) // "alice@example.com" } // Bulk decrypt const decrypted = await encryptionClient.bulkDecryptModels(results) if (!decrypted.failure) { for (const user of decrypted.data) { console.log(user.email) } } ``` Non-encrypted column fallback [#non-encrypted-column-fallback] All operators automatically detect whether a column is encrypted. If the column is a regular Drizzle column, the operator falls back to the standard Drizzle operator: ```typescript // This works for both encrypted and non-encrypted columns await encryptionOps.eq(usersTable.email, "alice@example.com") // encrypted await encryptionOps.eq(usersTable.role, "admin") // falls back to drizzle eq() ``` Operator reference [#operator-reference] Encrypted operators (async) [#encrypted-operators-async] | Operator | Usage | Required Index | | ------------------------------------ | ----------------------------------- | ---------------- | | `eq(col, value)` | Equality | `equality` | | `ne(col, value)` | Not equal | `equality` | | `gt(col, value)` | Greater than | `orderAndRange` | | `gte(col, value)` | Greater than or equal | `orderAndRange` | | `lt(col, value)` | Less than | `orderAndRange` | | `lte(col, value)` | Less than or equal | `orderAndRange` | | `between(col, min, max)` | Between (inclusive) | `orderAndRange` | | `notBetween(col, min, max)` | Not between | `orderAndRange` | | `like(col, pattern)` | LIKE pattern match | `freeTextSearch` | | `ilike(col, pattern)` | ILIKE case-insensitive | `freeTextSearch` | | `notIlike(col, pattern)` | NOT ILIKE | `freeTextSearch` | | `inArray(col, values)` | IN array | `equality` | | `notInArray(col, values)` | NOT IN array | `equality` | | `jsonbPathQueryFirst(col, selector)` | Extract first value at JSONB path | `searchableJson` | | `jsonbGet(col, selector)` | Get value using JSONB `->` operator | `searchableJson` | | `jsonbPathExists(col, selector)` | Check if JSONB path exists | `searchableJson` | Sort operators (sync) [#sort-operators-sync] | Operator | Usage | Required Index | | ----------- | --------------- | --------------- | | `asc(col)` | Ascending sort | `orderAndRange` | | `desc(col)` | Descending sort | `orderAndRange` | Logical operators (async, batched) [#logical-operators-async-batched] | Operator | Usage | Description | | -------------------- | ---------------- | ------------------ | | `and(...conditions)` | Combine with AND | Batches encryption | | `or(...conditions)` | Combine with OR | Batches encryption | Passthrough operators (sync, no encryption) [#passthrough-operators-sync-no-encryption] `exists`, `notExists`, `isNull`, `isNotNull`, `not`, `arrayContains`, `arrayContained`, `arrayOverlaps` These are re-exported from Drizzle and work identically. Complete example: Express API [#complete-example-express-api] ```typescript import "dotenv/config" import express from "express" import { eq } from "drizzle-orm" import { drizzle } from "drizzle-orm/postgres-js" import postgres from "postgres" import { pgTable, integer, timestamp, varchar } from "drizzle-orm/pg-core" import { encryptedType, extractEncryptionSchema, createEncryptionOperators, } from "@cipherstash/stack/drizzle" import { Encryption } from "@cipherstash/stack" // Schema const usersTable = pgTable("users", { id: integer("id").primaryKey().generatedAlwaysAsIdentity(), email: encryptedType("email", { equality: true, freeTextSearch: true, }), age: encryptedType("age", { dataType: "number", orderAndRange: true, }), role: varchar("role", { length: 50 }), createdAt: timestamp("created_at").defaultNow(), }) // Init const usersSchema = extractEncryptionSchema(usersTable) const encryptionClient = await Encryption({ schemas: [usersSchema] }) const encryptionOps = createEncryptionOperators(encryptionClient) const db = drizzle({ client: postgres(process.env.DATABASE_URL!) }) const app = express() app.use(express.json()) // Create user app.post("/users", async (req, res) => { const encrypted = await encryptionClient.encryptModel(req.body, usersSchema) if (encrypted.failure) { return res.status(500).json({ error: encrypted.failure.message }) } const [user] = await db.insert(usersTable).values(encrypted.data).returning() res.json(user) }) // Search users app.get("/users", async (req, res) => { const conditions = [] if (req.query.email) { conditions.push( encryptionOps.ilike(usersTable.email, `%${req.query.email}%`), ) } if (req.query.minAge) { conditions.push( encryptionOps.gte(usersTable.age, Number(req.query.minAge)), ) } if (req.query.role) { conditions.push(eq(usersTable.role, req.query.role as string)) } let query = db.select().from(usersTable) if (conditions.length > 0) { query = query.where( await encryptionOps.and(...conditions), ) as typeof query } const results = await query const decrypted = await encryptionClient.bulkDecryptModels(results) if (decrypted.failure) { return res.status(500).json({ error: decrypted.failure.message }) } res.json(decrypted.data) }) app.listen(3000) ``` Error handling [#error-handling] `createEncryptionOperators` throws `EncryptionOperatorError` for configuration issues: ```typescript class EncryptionOperatorError extends Error { context?: { tableName?: string columnName?: string operator?: string } } ``` Encryption client operations return `Result` objects with `data` or `failure`. See [Error handling](/docs/encryption/error-handling) for details. # DynamoDB DynamoDB [#dynamodb] CipherStash provides a DynamoDB integration through `@cipherstash/stack/dynamodb`. The `encryptedDynamoDB` helper encrypts items before writing to DynamoDB and decrypts them after reading — it does not wrap the AWS SDK, so you keep full control of your DynamoDB operations. Installation [#installation] ```bash npm install @cipherstash/stack @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb ``` The DynamoDB integration is included in `@cipherstash/stack` and imports from `@cipherstash/stack/dynamodb`. How it works [#how-it-works] CipherStash encrypts each attribute into two DynamoDB attributes: | Original attribute | Stored as | Purpose | | ------------------ | --------------- | -------------------------------------------------------- | | `email` | `email__source` | Encrypted ciphertext | | `email` | `email__hmac` | HMAC for equality lookups (only if `.equality()` is set) | Non-encrypted attributes pass through unchanged. On decryption, the `__source` and `__hmac` attributes are recombined back into the original attribute name with the plaintext value. Setup [#setup] Define an encrypted schema [#define-an-encrypted-schema] Use `encryptedTable` and `encryptedColumn` from `@cipherstash/stack/schema` to declare which attributes to encrypt: ```typescript import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const users = encryptedTable("users", { email: encryptedColumn("email").equality(), // searchable via HMAC name: encryptedColumn("name"), // encrypt-only, no search phone: encryptedColumn("phone"), // encrypt-only metadata: encryptedColumn("metadata").dataType("json"), // encrypted JSON }) ``` Only attributes with `.equality()` get an `__hmac` attribute for querying. Attributes without it are encrypted but cannot be searched. See [Schema definition](/docs/encryption/schema) for full details on index types, data types, and nested objects. Initialize the clients [#initialize-the-clients] ```typescript import { DynamoDBClient } from "@aws-sdk/client-dynamodb" import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb" import { Encryption } from "@cipherstash/stack" import { encryptedDynamoDB } from "@cipherstash/stack/dynamodb" const dynamoClient = new DynamoDBClient({ region: "us-east-1" }) const docClient = DynamoDBDocumentClient.from(dynamoClient) const encryptionClient = await Encryption({ schemas: [users] }) const dynamo = encryptedDynamoDB({ encryptionClient }) ``` Optional: logger and error handler [#optional-logger-and-error-handler] ```typescript const dynamo = encryptedDynamoDB({ encryptionClient, options: { logger: { error: (message, error) => console.error(`[DynamoDB] ${message}`, error), }, errorHandler: (error) => { console.error(`[${error.code}] ${error.message}`) }, }, }) ``` Encrypt and write [#encrypt-and-write] Single item [#single-item] Encrypt a model, then pass the result to a standard DynamoDB `PutCommand`: ```typescript import { PutCommand } from "@aws-sdk/lib-dynamodb" const user = { pk: "user#1", email: "alice@example.com", // will be encrypted name: "Alice Smith", // will be encrypted role: "admin", // not in schema, passes through } const result = await dynamo.encryptModel(user, users) if (result.failure) { console.error("Encryption failed:", result.failure.message) } else { await docClient.send(new PutCommand({ TableName: "Users", Item: result.data, // result.data looks like: // { // pk: "user#1", // email__source: "", // email__hmac: "", // name__source: "", // role: "admin", // } })) } ``` Bulk items [#bulk-items] ```typescript import { BatchWriteCommand } from "@aws-sdk/lib-dynamodb" const items = [ { pk: "user#1", email: "alice@example.com", name: "Alice" }, { pk: "user#2", email: "bob@example.com", name: "Bob" }, ] const result = await dynamo.bulkEncryptModels(items, users) if (!result.failure) { await docClient.send(new BatchWriteCommand({ RequestItems: { Users: result.data.map(item => ({ PutRequest: { Item: item }, })), }, })) } ``` Read and decrypt [#read-and-decrypt] Single item [#single-item-1] ```typescript import { GetCommand } from "@aws-sdk/lib-dynamodb" const getResult = await docClient.send(new GetCommand({ TableName: "Users", Key: { pk: "user#1" }, })) const result = await dynamo.decryptModel(getResult.Item, users) if (!result.failure) { console.log(result.data) // { pk: "user#1", email: "alice@example.com", name: "Alice Smith", role: "admin" } } ``` Bulk items [#bulk-items-1] ```typescript import { BatchGetCommand } from "@aws-sdk/lib-dynamodb" const batchResult = await docClient.send(new BatchGetCommand({ RequestItems: { Users: { Keys: [{ pk: "user#1" }, { pk: "user#2" }], }, }, })) const result = await dynamo.bulkDecryptModels( batchResult.Responses?.Users ?? [], users, ) if (!result.failure) { for (const user of result.data) { console.log(user.email) // plaintext } } ``` Querying with encrypted keys [#querying-with-encrypted-keys] DynamoDB queries use key conditions, so you need to encrypt the search value into its HMAC form. Use `encryptionClient.encryptQuery()` to get the HMAC, then use it in your key condition. Encrypted partition key [#encrypted-partition-key] When an encrypted attribute is the partition key (e.g., `email__hmac`): ```typescript import { QueryCommand } from "@aws-sdk/lib-dynamodb" // 1. Encrypt the search value to get the HMAC const queryResult = await encryptionClient.encryptQuery([{ value: "alice@example.com", column: users.email, table: users, queryType: "equality", }]) if (queryResult.failure) { throw new Error(`Query encryption failed: ${queryResult.failure.message}`) } const emailHmac = queryResult.data[0]?.hm // 2. Use the HMAC in a DynamoDB query const result = await docClient.send(new QueryCommand({ TableName: "Users", KeyConditionExpression: "email__hmac = :email", ExpressionAttributeValues: { ":email": emailHmac, }, })) // 3. Decrypt the results const decrypted = await dynamo.bulkDecryptModels(result.Items ?? [], users) ``` Encrypted sort key [#encrypted-sort-key] When an encrypted attribute is used as a sort key: ```typescript const result = await docClient.send(new GetCommand({ TableName: "Users", Key: { pk: "org#1", // partition key (plain) email__hmac: emailHmac, // sort key (encrypted HMAC) }, })) const decrypted = await dynamo.decryptModel(result.Item, users) ``` Encrypted attribute in a GSI [#encrypted-attribute-in-a-gsi] When querying a Global Secondary Index where the GSI key is an encrypted HMAC: ```typescript const result = await docClient.send(new QueryCommand({ TableName: "Users", IndexName: "EmailIndex", KeyConditionExpression: "email__hmac = :email", ExpressionAttributeValues: { ":email": emailHmac, }, Limit: 1, })) if (result.Items?.length) { const decrypted = await dynamo.decryptModel(result.Items[0], users) } ``` Table design considerations [#table-design-considerations] Key schema patterns [#key-schema-patterns] | Pattern | Partition key | Sort key | Use case | | ------------ | ------------- | ------------- | ----------------------------------------------------------------- | | Plain PK | `pk` (plain) | — | Standard lookup by ID | | Encrypted PK | `email__hmac` | — | Lookup by encrypted attribute | | Encrypted SK | `pk` (plain) | `email__hmac` | Composite key with encrypted sort | | GSI on HMAC | `pk` (plain) | — | Query by encrypted attribute via GSI with `email__hmac` as GSI PK | What you can query [#what-you-can-query] * Equality on `__hmac` attributes (exact match only) * `attribute_exists(email__source)` / `attribute_not_exists(email__source)` in condition expressions What you cannot query [#what-you-cannot-query] * Range or comparison on encrypted attributes (no `BETWEEN`, `<`, `>` on `__source`) * Substring matching on encrypted attributes (no `begins_with`, `contains` on `__source`) * `__source` values are encrypted binary — only equality via `__hmac` is supported Audit logging [#audit-logging] All operations support `.audit()` chaining for audit metadata: ```typescript const result = await dynamo .encryptModel(user, users) .audit({ metadata: { sub: "user-id-123", action: "user_registration", timestamp: new Date().toISOString(), }, }) ``` Error handling [#error-handling] All operations return `Result` with either `data` or `failure`: ```typescript const result = await dynamo.encryptModel(user, users) if (result.failure) { console.error(result.failure.message) console.error(result.failure.code) // ProtectErrorCode | "DYNAMODB_ENCRYPTION_ERROR" console.error(result.failure.details) } ``` See [Error handling](/docs/encryption/error-handling) for details on error codes and handling patterns. API reference [#api-reference] encryptedDynamoDB(config) [#encrypteddynamodbconfig] ```typescript import { encryptedDynamoDB } from "@cipherstash/stack/dynamodb" const dynamo = encryptedDynamoDB({ encryptionClient: EncryptionClient, options?: { logger?: { error: (message: string, error: Error) => void } errorHandler?: (error: EncryptedDynamoDBError) => void } }) ``` Instance methods [#instance-methods] | Method | Signature | Returns | | ------------------- | --------------------- | ------------------------------------------------------- | | `encryptModel` | `(item: T, table)` | `EncryptModelOperation>` | | `bulkEncryptModels` | `(items: T[], table)` | `BulkEncryptModelsOperation>` | | `decryptModel` | `(item, table)` | `DecryptModelOperation` | | `bulkDecryptModels` | `(items[], table)` | `BulkDecryptModelsOperation` | All operations are thenable (awaitable) and support `.audit({ metadata })` chaining. Querying encrypted attributes [#querying-encrypted-attributes] Use the encryption client directly (not the DynamoDB helper): ```typescript const result = await encryptionClient.encryptQuery([{ value: "search-value", column: schema.fieldName, table: schema, queryType: "equality", }]) const hmac = result.data[0]?.hm // Use in DynamoDB key conditions ``` Complete example [#complete-example] ```typescript import { DynamoDBClient } from "@aws-sdk/client-dynamodb" import { DynamoDBDocumentClient, PutCommand, GetCommand, QueryCommand, } from "@aws-sdk/lib-dynamodb" import { Encryption } from "@cipherstash/stack" import { encryptedDynamoDB } from "@cipherstash/stack/dynamodb" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" // Schema const users = encryptedTable("users", { email: encryptedColumn("email").equality(), name: encryptedColumn("name"), }) // Clients const dynamoClient = new DynamoDBClient({ region: "us-east-1" }) const docClient = DynamoDBDocumentClient.from(dynamoClient) const encryptionClient = await Encryption({ schemas: [users] }) const dynamo = encryptedDynamoDB({ encryptionClient }) // Write const user = { pk: "user#1", email: "alice@example.com", name: "Alice" } const encResult = await dynamo.encryptModel(user, users) if (!encResult.failure) { await docClient.send(new PutCommand({ TableName: "Users", Item: encResult.data, })) } // Read by primary key const getResult = await docClient.send(new GetCommand({ TableName: "Users", Key: { pk: "user#1" }, })) const decResult = await dynamo.decryptModel(getResult.Item, users) if (!decResult.failure) { console.log(decResult.data.email) // "alice@example.com" } // Query by encrypted email (via HMAC) const queryEnc = await encryptionClient.encryptQuery([{ value: "alice@example.com", column: users.email, table: users, queryType: "equality", }]) const hmac = queryEnc.data[0]?.hm const queryResult = await docClient.send(new QueryCommand({ TableName: "Users", IndexName: "EmailIndex", KeyConditionExpression: "email__hmac = :e", ExpressionAttributeValues: { ":e": hmac }, })) const decrypted = await dynamo.bulkDecryptModels(queryResult.Items ?? [], users) ``` # Encrypt and decrypt Encrypt and decrypt [#encrypt-and-decrypt] All operations return a `Result` object with either a `data` key (success) or a `failure` key (error). See [Error handling](/docs/encryption/error-handling) for details. Single values [#single-values] Encrypt [#encrypt] ```typescript title="encrypt.ts" const encrypted = await client.encrypt("user@example.com", { column: users.email, table: users, }) if (encrypted.failure) { console.error(encrypted.failure.type, encrypted.failure.message) } else { console.log(encrypted.data) } ``` Decrypt [#decrypt] ```typescript title="decrypt.ts" const decrypted = await client.decrypt(encrypted.data) if (decrypted.failure) { console.error(decrypted.failure.message) } else { console.log(decrypted.data) // "user@example.com" } ``` Model operations [#model-operations] Encrypt or decrypt an entire object. Only fields matching your schema are encrypted — other fields pass through unchanged. Encrypt a model [#encrypt-a-model] ```typescript title="encrypt-model.ts" const user = { id: "1", email: "user@example.com", // defined in schema -> encrypted address: "123 Main St", // defined in schema -> encrypted createdAt: new Date(), // not in schema -> unchanged metadata: { role: "admin" }, // not in schema -> unchanged } const encryptedResult = await client.encryptModel(user, users) if (encryptedResult.failure) { console.error("Encryption failed:", encryptedResult.failure.message) return } const encryptedUser = encryptedResult.data // { // id: '1', // email: { c: 'encrypted_data...' }, // address: { c: 'encrypted_data...' }, // createdAt: Date, // metadata: { role: 'admin' } // } ``` Decrypt a model [#decrypt-a-model] `decryptModel` automatically detects and decrypts any encrypted fields: ```typescript title="decrypt-model.ts" const decryptedResult = await client.decryptModel(encryptedUser) if (decryptedResult.failure) { console.error("Decryption failed:", decryptedResult.failure.message) return } const decryptedUser = decryptedResult.data ``` Schema-aware return types [#schema-aware-return-types] The return type of `encryptModel` is schema-aware: fields matching the table schema are typed as `Encrypted`, while other fields retain their original types. For best results, let TypeScript infer the type parameters from the arguments: ```typescript title="encrypt-model.ts" const result = await client.encryptModel(user, users) // result.data.email is typed as Encrypted // result.data.id is typed as string // result.data.createdAt is typed as Date ``` Passing an explicit type parameter (e.g., `client.encryptModel(...)`) still works for backward compatibility — the return type degrades to `User` in that case: ```typescript title="encrypt-model.ts" type User = { id: string email: string | null address: string | null createdAt: Date } const result = await client.encryptModel(user, users) const back = await client.decryptModel(encryptedUser) ``` Bulk operations [#bulk-operations] All bulk methods make a single call to ZeroKMS regardless of the number of records, while still using a unique key per value. Bulk encrypt and decrypt values [#bulk-encrypt-and-decrypt-values] ```typescript title="bulk-encrypt.ts" const plaintexts = [ { id: "u1", plaintext: "alice@example.com" }, { id: "u2", plaintext: "bob@example.com" }, { id: "u3", plaintext: null }, // null values are preserved ] const encrypted = await client.bulkEncrypt(plaintexts, { column: users.email, table: users, }) ``` ```typescript title="bulk-decrypt.ts" const decrypted = await client.bulkDecrypt(encrypted.data) for (const item of decrypted.data) { if ("data" in item) { console.log(`${item.id}: ${item.data}`) } else { console.error(`${item.id} failed: ${item.error}`) } } ``` Bulk encrypt and decrypt models [#bulk-encrypt-and-decrypt-models] ```typescript title="bulk-encrypt-models.ts" const userModels = [ { id: "1", email: "alice@example.com", address: "123 Main St" }, { id: "2", email: "bob@example.com", address: "456 Oak Ave" }, ] const encrypted = await client.bulkEncryptModels(userModels, users) const decrypted = await client.bulkDecryptModels(encrypted.data) ``` Bulk model operations also support type parameters: ```typescript title="bulk-encrypt-models.ts" const result = await client.bulkEncryptModels(userModels, users) const back = await client.bulkDecryptModels(encrypted.data) ``` Identity-aware operations [#identity-aware-operations] Any encrypt or decrypt operation can be scoped to a specific user with a lock context. See [Identity-aware encryption](/docs/encryption/identity) for details. ```typescript title="identity-encrypt.ts" const encrypted = await client .encryptModel(user, users) .withLockContext(lockContext) const decrypted = await client .decryptModel(encryptedUser) .withLockContext(lockContext) // Also works with bulk operations const bulkEncrypted = await client .bulkEncryptModels(userModels, users) .withLockContext(lockContext) ``` # Error handling Error handling [#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 [#the-result-pattern] ```typescript title="result-check.ts" 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 [#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 [#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`. ```typescript title="init-error.ts" 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 [#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 [#handling-encryptdecrypt-errors] ```typescript title="encrypt-error.ts" 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 [#common-encryptdecrypt-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 [#handling-bulk-operation-errors] Bulk operations (`bulkDecrypt`, `bulkDecryptModels`) support **per-item error handling**. The overall operation succeeds but individual items may fail. ```typescript title="bulk-error.ts" 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 [#handling-identity-errors] When using [identity-aware encryption](/docs/encryption/identity), errors can occur during JWT identification or when using lock contexts. ```typescript title="identity-error.ts" 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 [#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](/docs/kms/cts) | Wrapping errors in application code [#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): ```typescript title="helpers.ts" function unwrap(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 `Result` pattern is intentionally designed to avoid thrown exceptions. Use the `unwrap` helper only at your application's boundaries where exceptions are expected. Logging [#logging] Control SDK log verbosity with the `STASH_STACK_LOG` environment variable: ```bash 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. # Getting started Getting started [#getting-started] This guide walks you through installing CipherStash Encryption, defining a schema, and encrypting your first value. Prerequisites [#prerequisites] * [Node.js](https://nodejs.org/) >= 18 * [TypeScript](https://www.typescriptlang.org/) * A CipherStash account — [sign up here](https://cipherstash.com/signup) Project structure [#project-structure] If you're starting from scratch, here's the recommended file structure: ``` project-root/ ├── src/ │ ├── protect/ │ │ ├── index.ts # Encryption client │ │ └── schema.ts # Table definitions │ └── index.ts # Your app ├── .env ├── package.json └── tsconfig.json ``` Create the structure: ```bash mkdir -p my-app/src/protect cd my-app npm init -y ``` Step 1: Install [#step-1-install] ```bash npm install @cipherstash/stack ``` Or with your preferred package manager: ```bash yarn add @cipherstash/stack pnpm add @cipherstash/stack ``` > `@cipherstash/stack` includes a native FFI module written in Rust. > You must opt out of bundling in tools like Webpack, esbuild, or Next.js. > See [Bundling](/docs/encryption/bundling) or [SST setup](/docs/encryption/sst) for details. Step 2: Set up credentials [#step-2-set-up-credentials] Sign up at [cipherstash.com/signup](https://cipherstash.com/signup) and follow the onboarding to get your credentials. Save them to a `.env` file: ```bash CS_WORKSPACE_CRN= # The workspace identifier CS_CLIENT_ID= # The client identifier CS_CLIENT_KEY= # Key material used with ZeroKMS CS_CLIENT_ACCESS_KEY= # API key for CipherStash API ``` See [Configuration](/docs/encryption/configuration) for all configuration options including TOML files and programmatic config. Step 3: Define your schema [#step-3-define-your-schema] Schemas declare which columns to encrypt and what queries to support. Create `src/protect/schema.ts`: ```typescript title="schema.ts" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" export const users = encryptedTable("users", { email: encryptedColumn("email"), }) export const orders = encryptedTable("orders", { address: encryptedColumn("address"), }) ``` To enable searchable encryption, chain index methods on your columns: ```typescript title="schema.ts" export const users = encryptedTable("users", { email: encryptedColumn("email") .equality() // exact match queries .freeTextSearch() // full-text search .orderAndRange(), // sorting and range queries }) ``` See [Schema definition](/docs/encryption/schema) for all index types. Step 4: Initialize the client [#step-4-initialize-the-client] Create `src/protect/index.ts`: ```typescript title="protect/index.ts" import { Encryption } from "@cipherstash/stack" import { users, orders } from "./schema" export const client = await Encryption({ schemas: [users, orders] }) ``` The client reads `CS_*` environment variables automatically. Step 5: Encrypt data [#step-5-encrypt-data] Create `src/index.ts`: ```typescript title="index.ts" import { users } from "./protect/schema" import { client } from "./protect" const encryptResult = await client.encrypt("secret@squirrel.example", { column: users.email, table: users, }) if (encryptResult.failure) { console.error(encryptResult.failure.type, encryptResult.failure.message) } else { console.log("Encrypted:", encryptResult.data) } ``` Run it: ```bash npx tsx src/index.ts ``` The result is a JSON payload ready for database storage: ```typescript // Success { data: { c: '\\x61202020202020472aaf602219d48c4a...' } } // Failure { failure: { type: 'EncryptionError', message: '...' } } ``` Step 6: Decrypt data [#step-6-decrypt-data] ```typescript title="index.ts" const decryptResult = await client.decrypt(encryptResult.data) if (decryptResult.failure) { console.error(decryptResult.failure.message) } else { console.log("Plaintext:", decryptResult.data) // "secret@squirrel.example" } ``` Step 7: Store in a database [#step-7-store-in-a-database] Encrypted data can be stored in any database that supports JSONB. For PostgreSQL, specify the column type as `jsonb`: ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, email jsonb NOT NULL ); ``` For searchable encryption in PostgreSQL, install the [EQL extension](https://github.com/cipherstash/encrypt-query-language) and use the `eql_v2_encrypted` column type instead. See [Searchable encryption](/docs/encryption/searchable-encryption) for details. Next steps [#next-steps] # Identity-aware encryption Identity-aware encryption [#identity-aware-encryption] Lock encryption to a specific user by requiring a valid JWT for decryption. When a value is encrypted with a lock context, it can only be decrypted by presenting the same user's identity token. How it works [#how-it-works] 1. Create a `LockContext` instance. 2. Identify the user with their JWT. 3. Pass the lock context to encrypt and decrypt operations. Basic usage [#basic-usage] ```typescript title="identity.ts" import { LockContext } from "@cipherstash/stack/identity" // 1. Create a lock context (defaults to the "sub" claim) const lc = new LockContext() // 2. Identify the user with their JWT const identifyResult = await lc.identify(userJwt) if (identifyResult.failure) { throw new Error(identifyResult.failure.message) } const lockContext = identifyResult.data // 3. Encrypt with lock context const encrypted = await client .encrypt("sensitive data", { column: users.email, table: users }) .withLockContext(lockContext) // 4. Decrypt with the same lock context const decrypted = await client .decrypt(encrypted.data) .withLockContext(lockContext) ``` Supported operations [#supported-operations] Lock contexts work with all encrypt and decrypt operations: ```typescript title="identity.ts" // Single operations const encrypted = await client .encryptModel(user, users) .withLockContext(lockContext) const decrypted = await client .decryptModel(encryptedUser) .withLockContext(lockContext) // Bulk operations const bulkEncrypted = await client .bulkEncryptModels(userModels, users) .withLockContext(lockContext) const bulkDecrypted = await client .bulkDecryptModels(encryptedUsers) .withLockContext(lockContext) ``` Custom identity claims [#custom-identity-claims] Override the default context by specifying which identity claims to use: ```typescript title="identity.ts" const lc = new LockContext({ context: { identityClaim: ["sub"], // this is the default }, }) ``` | Identity claim | Description | | -------------- | ---------------------------------------- | | `sub` | The user's subject identifier | | `scopes` | The user's scopes set by your IDP policy | Using with Clerk and Next.js [#using-with-clerk-and-nextjs] If you're using [Clerk](https://clerk.com/) as your identity provider, install the `@cipherstash/nextjs` package for automatic CTS token setup. ```bash npm install @cipherstash/nextjs ``` Set up middleware [#set-up-middleware] In your `middleware.ts`, use `protectClerkMiddleware` to automatically generate CTS tokens for every user session: ```typescript title="middleware.ts" import { clerkMiddleware } from "@clerk/nextjs/server" import { protectClerkMiddleware } from "@cipherstash/nextjs/clerk" export default clerkMiddleware(async (auth, req) => { return protectClerkMiddleware(auth, req) }) ``` Retrieve the CTS token [#retrieve-the-cts-token] Use `getCtsToken` to get the CTS token for the current user: ```typescript title="page.tsx" import { getCtsToken } from "@cipherstash/nextjs" export default async function Page() { const ctsToken = await getCtsToken() if (!ctsToken.success) { // handle error } // ctsToken is ready to use } ``` Create a LockContext with an existing CTS token [#create-a-lockcontext-with-an-existing-cts-token] Since the CTS token is already available from the middleware, construct the `LockContext` directly: ```typescript title="page.tsx" import { LockContext } from "@cipherstash/stack/identity" import { getCtsToken } from "@cipherstash/nextjs" export default async function Page() { const ctsToken = await getCtsToken() if (!ctsToken.success) { // handle error } const lockContext = new LockContext({ ctsToken }) // Use lockContext with encrypt/decrypt operations } ``` # Encryption Encryption [#encryption] CipherStash Encryption provides field-level encryption for your application data. Every value is encrypted with its own unique key via [ZeroKMS](/docs/kms), backed by AWS KMS — giving you strong data protection without sacrificing query capability. What you get [#what-you-get] * **Field-level encryption** — Each value encrypted with a unique key, not a shared table key. * **Searchable encryption** — Run exact match, free-text search, and range queries on encrypted data in PostgreSQL. * **Encrypted JSONB** — Query encrypted JSON fields using JSONPath selectors and containment operators. * **Bulk operations** — Encrypt or decrypt thousands of values in a single ZeroKMS call. * **Identity-aware encryption** — Tie encryption to a user's JWT so only that user can decrypt their data. * **Multi-tenant isolation** — Use [Key Sets](/docs/kms/keysets) from ZeroKMS to cryptographically isolate encryption keys per tenant, customer, or business unit. * **TypeScript-first** — Strongly typed schemas, results, and model operations. How it works [#how-it-works] 1. **Define a schema** — Declare which columns to encrypt and what queries to support. 2. **Initialize a client** — The SDK connects to ZeroKMS to manage encryption keys. 3. **Encrypt and store** — Encrypt values before writing to your database. 4. **Query encrypted data** — Encrypt query terms and run them against your encrypted columns. 5. **Decrypt on read** — Decrypt values when reading from the database. All key management — key generation, derivation, and isolation — is handled by [ZeroKMS](/docs/kms). Encryption keys are organized into [Key Sets](/docs/kms/keysets), the same primitive that powers [Secrets environment isolation](/docs/secrets/concepts#environments). Integration paths [#integration-paths] | | Encryption SDK | CipherStash Proxy | | ------------ | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | **Best for** | Teams who want fine-grained control over data encryption directly in their application | DevOps teams who want to add encryption to existing PostgreSQL apps with little to no code changes | | **Setup** | `npm install @cipherstash/stack`, define schemas, integrate into app | Docker container, configure environment variables | | **Database** | PostgreSQL (full searchable encryption) | PostgreSQL (transparent proxy) | Performance [#performance] * **Latency**: \< 5ms overhead for most operations ([benchmarks](https://app.artillery.io/share/sh_75edb9d22a060633bfdce04777ffd60d1bcfc88989393dc38d3a4924b83fc6cd)) * **Throughput**: Scales with your application performance * **Setup time**: Running in local dev in \< 1 hour, production in \< 3 days Next steps [#next-steps] # Migration guide Migration from @cipherstash/protect [#migration-from-cipherstashprotect] If you are migrating from `@cipherstash/protect`, use the following table to map the old API to the new one. Import changes [#import-changes] | `@cipherstash/protect` | `@cipherstash/stack` | Import path | | ------------------------------------------------------------- | ----------------------------------------------------------- | ----------------------------- | | `protect(config)` | `Encryption(config)` | `@cipherstash/stack` | | `csTable(name, cols)` | `encryptedTable(name, cols)` | `@cipherstash/stack/schema` | | `csColumn(name)` | `encryptedColumn(name)` | `@cipherstash/stack/schema` | | `import { LockContext } from "@cipherstash/protect/identify"` | `import { LockContext } from "@cipherstash/stack/identity"` | `@cipherstash/stack/identity` | New features in @cipherstash/stack [#new-features-in-cipherstashstack] | Feature | Import path | | --------------- | ---------------------------- | | `Secrets` class | `@cipherstash/stack/secrets` | | `stash` CLI | `npx stash` | What stays the same [#what-stays-the-same] All method signatures on the encryption client (`encrypt`, `decrypt`, `encryptModel`, `decryptModel`, `bulkEncrypt`, `bulkDecrypt`, `bulkEncryptModels`, `bulkDecryptModels`) remain the same. The `Result` pattern (`data` / `failure`) is unchanged. Step-by-step [#step-by-step] 1. Install the new package: ```bash npm install @cipherstash/stack npm uninstall @cipherstash/protect ``` 2. Update imports: ```typescript // Before import { protect } from "@cipherstash/protect" import { csTable, csColumn } from "@cipherstash/protect" // After import { Encryption } from "@cipherstash/stack" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" ``` 3. Rename function calls: ```typescript // Before const client = await protect({ schemas: [users] }) // After const client = await Encryption({ schemas: [users] }) ``` 4. Update schema definitions: ```typescript // Before const users = csTable("users", { email: csColumn("email").equality().freeTextSearch(), }) // After const users = encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch(), }) ``` No changes are needed to your encrypt/decrypt calls — they work the same way. # Schema definition Schema definition [#schema-definition] Schemas tell the SDK which database columns to encrypt and what types of queries to support on the encrypted data. Creating schema files [#creating-schema-files] Declare your encryption schema in TypeScript — either in a single file or split across multiple files: ``` src/protect/ └── schema.ts # single file ``` ``` src/protect/schemas/ ├── users.ts # per-table files └── posts.ts ``` Defining a schema [#defining-a-schema] A schema maps your database tables and columns using `encryptedTable` and `encryptedColumn`: ```typescript title="schema.ts" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" // TypeScript name Database table name // ↓ ↓ export const protectedUsers = encryptedTable("users", { // TypeScript name Database column name // ↓ ↓ email: encryptedColumn("email"), }) ``` Index types [#index-types] Index types determine what queries you can run on encrypted data. Methods are chainable — call as many as you need on a single column. ```typescript title="schema.ts" export const protectedUsers = encryptedTable("users", { email: encryptedColumn("email") .equality() // exact match queries .freeTextSearch() // full-text search .orderAndRange(), // sorting and range queries }) ``` | Method | Purpose | SQL equivalent | | ------------------- | -------------------------------------------- | -------------------------------------- | | `.equality()` | Exact match lookups | `WHERE email = 'user@example.com'` | | `.freeTextSearch()` | Full-text / fuzzy search | `WHERE description LIKE '%example%'` | | `.orderAndRange()` | Sorting, comparison, range queries | `ORDER BY price ASC` | | `.searchableJson()` | Encrypted JSONB path and containment queries | `WHERE metadata @> '{"role":"admin"}'` | Only enable the indexes you need — each additional index type has a performance cost. > For columns storing JSON data, `.searchableJson()` is the recommended index. It automatically configures the column for encrypted JSONB path and containment queries. See [Searchable encryption](/docs/encryption/searchable-encryption) for details. Data types [#data-types] Use `.dataType()` to specify the plaintext type for a column: ```typescript title="schema.ts" encryptedColumn("age").dataType("number").orderAndRange() ``` Supported data types: `'string'`, `'number'`, `'boolean'`, `'date'`, `'bigint'`, `'json'`. Free-text search options [#free-text-search-options] Customize the tokenizer and filter settings for `.freeTextSearch()`: ```typescript title="schema.ts" encryptedColumn("bio").freeTextSearch({ tokenizer: { kind: "ngram", token_length: 3 }, // or { kind: "standard" } token_filters: [{ kind: "downcase" }], k: 6, m: 2048, include_original: false, }) ``` Nested objects [#nested-objects] CipherStash Encryption supports nested objects in your schema, allowing you to encrypt nested properties. You can define nested objects up to 3 levels deep using `encryptedField`. > Searchable encryption is not supported on nested objects. This is most useful for NoSQL databases or less structured data. ```typescript title="schema.ts" import { encryptedTable, encryptedColumn, encryptedField } from "@cipherstash/stack/schema" export const protectedUsers = encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch(), profile: { name: encryptedField("profile.name"), address: { street: encryptedField("profile.address.street"), location: { coordinates: encryptedField("profile.address.location.coordinates"), }, }, }, }) ``` When working with nested objects: * Each level can have its own encrypted fields * The maximum nesting depth is 3 levels * Null and undefined values are supported at any level * Optional nested objects are supported Encrypted JSONB [#encrypted-jsonb] For columns that store JSON objects, use `.searchableJson()` to enable encrypted JSONB queries: ```typescript title="schema.ts" const documents = encryptedTable("documents", { metadata: encryptedColumn("metadata").searchableJson(), }) ``` This enables both JSONPath selector queries and containment queries on the encrypted data. Multiple tables [#multiple-tables] Pass multiple schemas when initializing the client: ```typescript title="protect/index.ts" import { Encryption } from "@cipherstash/stack" const client = await Encryption({ schemas: [protectedUsers, documents] }) ``` Type inference [#type-inference] Infer plaintext and encrypted types from your schema: ```typescript title="schema.ts" import type { InferPlaintext, InferEncrypted } from "@cipherstash/stack/schema" type UserPlaintext = InferPlaintext // { email: string; ... } type UserEncrypted = InferEncrypted // { email: Encrypted; ... } ``` Client-safe exports [#client-safe-exports] For client-side code where the native FFI module is not available, import schema builders from the `@cipherstash/stack/client` subpath: ```typescript title="schema.ts" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/client" ``` This exports schema builders and types only — no native module dependency. # Searchable encryption Searchable encryption [#searchable-encryption] CipherStash lets you run queries on encrypted data in PostgreSQL without decrypting it first — and it's [410,000x faster](https://github.com/cipherstash/tfhe-ore-bench) than homomorphic encryption. | Operation | Homomorphic | CipherStash | Speedup | | ----------- | ----------- | ----------- | ---------- | | **Encrypt** | 1.97 ms | 48 us | \~41x | | **a == b** | 111 ms | 238 ns | \~466,000x | | **a > b** | 192 ms | 238 ns | \~807,000x | Every decryption event is logged in [ZeroKMS](/docs/kms), giving you an audit trail for compliance with [SOC 2](https://cipherstash.com/compliance/soc2) and [BDSG](https://cipherstash.com/compliance/bdsg). Prerequisites [#prerequisites] 1. Install the [EQL custom types and functions](https://github.com/cipherstash/encrypt-query-language) in your PostgreSQL database 2. Define your encryption schema with the appropriate search indexes What is EQL? [#what-is-eql] EQL (Encrypt Query Language) is a set of PostgreSQL extensions that enable searching and sorting on encrypted data. It provides custom data types, comparison functions, and index support for encrypted values. Any encrypted column must use the `eql_v2_encrypted` type: ```sql CREATE TABLE users ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, email eql_v2_encrypted ); ``` The encryptQuery function [#the-encryptquery-function] Encrypt a query term so you can search encrypted data in PostgreSQL: ```typescript title="search.ts" const term = await client.encryptQuery("user@example.com", { column: schema.email, table: schema, }) if (term.failure) { // Handle the error } console.log(term.data) // encrypted query term ``` Batch queries [#batch-queries] Encrypt multiple query terms in a single call: ```typescript title="search.ts" const terms = await client.encryptQuery([ { value: "user@example.com", column: schema.email, table: schema }, { value: "18", column: schema.age, table: schema }, ]) ``` Query types [#query-types] Exact matching [#exact-matching] Use `.equality()` for exact match lookups: ```typescript title="search.ts" const term = await client.encryptQuery("user@example.com", { column: schema.email, table: schema, }) const result = await pgClient.query( "SELECT * FROM users WHERE email_encrypted = $1", [term.data] ) ``` Free-text search [#free-text-search] Use `.freeTextSearch()` for text-based searches: ```typescript title="search.ts" const term = await client.encryptQuery("example", { column: schema.email, table: schema, }) const result = await pgClient.query( "SELECT * FROM users WHERE email_encrypted LIKE $1", [term.data] ) ``` Sorting and range queries [#sorting-and-range-queries] Use `.orderAndRange()` for sorting and range operations: ```typescript title="search.ts" const result = await pgClient.query( "SELECT * FROM users ORDER BY eql_v2.ore_block_u64_8_256(age_encrypted) ASC" ) ``` JSONB queries with .searchableJson() [#jsonb-queries-with-searchablejson] For columns storing JSON data, `.searchableJson()` is the recommended approach. It automatically infers the correct query operation from the plaintext value type. ```typescript title="schema.ts" const documents = encryptedTable("documents", { metadata: encryptedColumn("metadata_encrypted").searchableJson(), }) ``` Auto-inference [#auto-inference] | Plaintext type | Inferred operation | Use case | | ----------------------------------- | ------------------ | ------------------------- | | `string` (e.g. `'$.user.email'`) | `steVecSelector` | JSONPath selector queries | | `object` (e.g. `{ role: 'admin' }`) | `steVecTerm` | Containment queries | | `array` (e.g. `['admin', 'user']`) | `steVecTerm` | Containment queries | | `null` | Returns `null` | Null handling | JSONPath selector queries [#jsonpath-selector-queries] Pass a string to query by JSON path: ```typescript title="search.ts" const pathTerm = await client.encryptQuery("$.user.email", { column: documents.metadata, table: documents, }) // Nested path const nestedTerm = await client.encryptQuery("$.user.profile.role", { column: documents.metadata, table: documents, }) // Array index const arrayTerm = await client.encryptQuery("$.items[0].name", { column: documents.metadata, table: documents, }) ``` Use the `toJsonPath` helper to convert dot-notation paths: ```typescript title="search.ts" import { toJsonPath } from "@cipherstash/stack" toJsonPath("user.email") // '$.user.email' toJsonPath("$.user.email") // '$.user.email' (unchanged) ``` Containment queries [#containment-queries] Pass an object or array to query by containment: ```typescript title="search.ts" // Key-value containment const roleTerm = await client.encryptQuery({ role: "admin" }, { column: documents.metadata, table: documents, }) // Nested object containment const nestedTerm = await client.encryptQuery( { user: { profile: { role: "admin" } } }, { column: documents.metadata, table: documents } ) // Array containment const tagsTerm = await client.encryptQuery(["admin", "user"], { column: documents.metadata, table: documents, }) ``` > Bare numbers and booleans are not supported as top-level `searchableJson` query values. Wrap them in an object or array. Use the `buildNestedObject` helper to construct nested containment queries: ```typescript title="search.ts" import { buildNestedObject } from "@cipherstash/stack" buildNestedObject("user.role", "admin") // Returns: { user: { role: 'admin' } } ``` Using JSONB queries in SQL [#using-jsonb-queries-in-sql] Specify `returnType: 'composite-literal'` for direct use in SQL: ```typescript title="search.ts" const term = await client.encryptQuery([{ value: "$.user.email", column: documents.metadata, table: documents, returnType: "composite-literal", }]) const result = await pgClient.query( "SELECT * FROM documents WHERE cs_ste_vec_v2(metadata_encrypted) @> $1", [term.data[0]] ) ``` Implementation example [#implementation-example] Using the pg client [#using-the-pg-client] ```typescript title="search.ts" import { Client } from "pg" import { Encryption } from "@cipherstash/stack" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const schema = encryptedTable("users", { email: encryptedColumn("email_encrypted") .equality() .freeTextSearch() .orderAndRange(), }) const pgClient = new Client({ /* connection details */ }) const client = await Encryption({ schemas: [schema] }) // Insert encrypted data const encryptedData = await client.encryptModel({ email: "user@example.com" }, schema) await pgClient.query( "INSERT INTO users (email_encrypted) VALUES ($1::jsonb)", [encryptedData.data.email_encrypted] ) // Search encrypted data const searchTerm = await client.encryptQuery("example.com", { column: schema.email, table: schema, }) const result = await pgClient.query( "SELECT * FROM users WHERE email_encrypted LIKE $1", [searchTerm.data] ) // Decrypt results const decryptedData = await client.bulkDecryptModels(result.rows) ``` Best practices [#best-practices] 1. **Choose the right indexes** — Use `.equality()` for exact matches (most efficient), `.freeTextSearch()` for text search, and `.orderAndRange()` for sorting (most expensive). Only enable what you need. 2. **Use parameterized queries** — Always use parameterized queries to prevent SQL injection. 3. **Use bulk operations** — Use `bulkEncryptModels` and `bulkDecryptModels` when working with multiple records. 4. **Handle errors** — Always check for failures with any `@cipherstash/stack` method. # SST SST [#sst] When deploying `@cipherstash/stack` in serverless functions with [SST](https://sst.dev/), you need to exclude the package from esbuild bundling and install it into the Lambda deployment artifact. Configure `nodejs.esbuild.external` and `nodejs.install` in your `sst.config.ts`: ```typescript title="sst.config.ts" { nodejs: { esbuild: { external: ["@cipherstash/stack"], }, install: ["@cipherstash/stack"], }, } ``` The `external` option prevents esbuild from bundling the package. The `install` option ensures it gets installed into the Lambda deployment artifact so it's available at runtime. See the [SST Function documentation](https://sst.dev/docs/component/aws/function/#nodejs) for more details. For other bundler configurations (webpack, esbuild, Next.js) and Linux deployment troubleshooting, see [Bundling](/docs/encryption/bundling). # Storing encrypted data Storing encrypted data [#storing-encrypted-data] This guide shows how to persist encrypted values in your database and retrieve them later using raw SQL. If you're using an ORM, see the [Drizzle integration](/docs/encryption/drizzle) or [Supabase integration](/docs/encryption/supabase) instead. Database compatibility [#database-compatibility] The Encryption SDK works with any database that supports JSON or JSONB column types. Searchable encryption is available in PostgreSQL (via [EQL](/docs/platform/eql)) and [DynamoDB](/docs/encryption/dynamodb). | Database | Standard encryption | Searchable encryption | | ----------------------------------- | ------------------- | --------------------- | | PostgreSQL 15+ | Yes | Yes | | AWS RDS PostgreSQL | Yes | Yes | | AWS Aurora PostgreSQL | Yes | Yes | | GCP Cloud SQL for PostgreSQL | Yes | Yes | | Azure Database for PostgreSQL | Yes | Yes | | OCI Database Service for PostgreSQL | Yes | Yes | | DynamoDB | Yes | Yes | | Supabase | Yes | Coming soon | | Neon Postgres | Yes | — | | MySQL | Yes | — | | CockroachDB | Yes | — | PostgreSQL guide [#postgresql-guide] Install EQL (optional) [#install-eql-optional] To enable searchable encryption in PostgreSQL, install [EQL](/docs/platform/eql) so you can use the `eql_v2_encrypted` data type. If you don't need searchable encryption, use `jsonb` instead (you can migrate to EQL later). ```bash # DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres curl -sL https://cphrstsh.link/eql | psql $DATABASE_URL ``` > **Choosing the column type:** > > * Use `eql_v2_encrypted` if you need searchable encryption (or want the option later) > * Use `jsonb` if you don't need searchable encryption or don't mind changing the data type later Create a table [#create-a-table] Encrypted values ([CipherCells](/docs/platform/cipher-cell)) are stored as either `jsonb` or `eql_v2_encrypted` in PostgreSQL. With EQL: ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, email eql_v2_encrypted NOT NULL ); ``` With JSONB: ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, email jsonb NOT NULL ); ``` Encrypt and insert data [#encrypt-and-insert-data] Encrypt the plaintext value and insert the resulting CipherCell into your database: ```typescript title="insert.ts" import { Encryption } from "@cipherstash/stack" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" import { Pool } from "pg" const users = encryptedTable("users", { email: encryptedColumn("email").equality(), }) const client = await Encryption({ schemas: [users] }) const pool = new Pool({ host: process.env.DB_HOST, database: process.env.DB_NAME, user: process.env.DB_USER, password: process.env.DB_PASSWORD, port: parseInt(process.env.DB_PORT || "5432"), }) async function insertUser(email: string) { const encryptResult = await client.encrypt(email, { column: users.email, table: users, }) if (encryptResult.failure) { throw new Error(`Encryption failed: ${encryptResult.failure.message}`) } // Use the ::jsonb cast to ensure Postgres treats the data as JSONB const result = await pool.query( "INSERT INTO users (email) VALUES ($1::jsonb) RETURNING id", [encryptResult.data] ) return result.rows[0].id } ``` > Always use the `::jsonb` cast when inserting encrypted values, even when using the `eql_v2_encrypted` column type. This ensures PostgreSQL correctly handles the CipherCell object. Retrieve and decrypt data [#retrieve-and-decrypt-data] Query the database and pass the CipherCell to the `decrypt` method: ```typescript title="retrieve.ts" async function getUserEmail(userId: number) { const fetchResult = await pool.query( "SELECT email FROM users WHERE id = $1", [userId] ) if (fetchResult.rows.length === 0) { throw new Error("User not found") } const decryptResult = await client.decrypt(fetchResult.rows[0].email) if (decryptResult.failure) { throw new Error(`Decryption failed: ${decryptResult.failure.message}`) } return decryptResult.data } ``` Bulk insert with UNNEST [#bulk-insert-with-unnest] When inserting many rows, use `bulkEncrypt` with PostgreSQL's `UNNEST` for efficient batch inserts: ```typescript title="bulk-insert.ts" async function insertUsers(emails: string[]) { const plaintexts = emails.map((email) => ({ plaintext: email })) const encryptedResult = await client.bulkEncrypt(plaintexts, { column: users.email, table: users, }) if (encryptedResult.failure) { throw new Error(`Bulk encryption failed: ${encryptedResult.failure.message}`) } const result = await pool.query( `INSERT INTO users (email) SELECT * FROM UNNEST($1::jsonb[])`, [encryptedResult.data.map((item) => item.data)] ) return result.rowCount } ``` Using `bulkEncrypt` instead of calling `encrypt` in a loop has a significant performance impact — it batches key derivation requests to ZeroKMS. Next steps [#next-steps] * [Searchable encryption](/docs/encryption/searchable-encryption) — query encrypted data without decrypting * [Drizzle ORM integration](/docs/encryption/drizzle) — encrypted queries with Drizzle * [Bulk operations](/docs/encryption/encrypt-decrypt) — encrypt and decrypt multiple values efficiently * [Identity-aware encryption](/docs/encryption/identity) — bind operations to authenticated identities # Supabase Supabase [#supabase] The `encryptedSupabase` wrapper makes encrypted queries look nearly identical to normal Supabase queries. It automatically handles encryption, decryption, `::jsonb` casts, and search term formatting. Prerequisites [#prerequisites] Install the [EQL v2 extension](https://github.com/cipherstash/encrypt-query-language/releases) in your Supabase project. The extension has a specific release for Supabase. Define your columns as `eql_v2_encrypted`: ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, name eql_v2_encrypted, email eql_v2_encrypted, age eql_v2_encrypted ); ``` Or store encrypted columns as JSONB if not using the EQL extension directly: ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, email jsonb NOT NULL, name jsonb NOT NULL, age jsonb, role VARCHAR(50), created_at TIMESTAMPTZ DEFAULT NOW() ); ``` Setup [#setup] ```typescript import { Encryption } from "@cipherstash/stack" import { encryptedSupabase } from "@cipherstash/stack/supabase" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" import { createClient } from "@supabase/supabase-js" const users = encryptedTable("users", { name: encryptedColumn("name") .equality() .freeTextSearch(), email: encryptedColumn("email") .equality() .freeTextSearch(), age: encryptedColumn("age") .dataType("number") .equality() .orderAndRange(), }) const client = await Encryption({ schemas: [users] }) const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!) const eSupabase = encryptedSupabase({ encryptionClient: client, supabaseClient: supabase, }) ``` Type-safe queries [#type-safe-queries] ```typescript type UserRow = { id: number; name: string; email: string; age: number; role: string } const { data } = await eSupabase .from("users", users) .select("id, name, email") ``` Inserting data [#inserting-data] Encrypted fields are automatically encrypted before insertion: ```typescript // Single insert const { data, error } = await eSupabase .from("users", users) .insert({ email: "alice@example.com", // encrypted automatically name: "Alice Smith", // encrypted automatically age: 30, // encrypted automatically role: "admin", // not in schema, passed through }) .select("id") // Bulk insert const { data, error } = await eSupabase .from("users", users) .insert([ { email: "alice@example.com", name: "Alice", age: 30, role: "admin" }, { email: "bob@example.com", name: "Bob", age: 25, role: "user" }, ]) .select("id") ``` Selecting data [#selecting-data] Results are automatically decrypted: ```typescript // List query const { data, error } = await eSupabase .from("users", users) .select("id, email, name, role") // data: [{ id: 1, email: "alice@example.com", name: "Alice Smith", role: "admin" }] // Single result const { data, error } = await eSupabase .from("users", users) .select("id, email, name") .eq("id", 1) .single() // data: { id: 1, email: "alice@example.com", name: "Alice Smith" } // Maybe single (returns null if no match) const { data, error } = await eSupabase .from("users", users) .select("id, email") .eq("email", "nobody@example.com") .maybeSingle() // data: null ``` > `encryptedSupabase` does not support `select('*')`. You must list columns explicitly so that encrypted columns can be cast with `::jsonb`. Query filters [#query-filters] All filter values for encrypted columns are automatically encrypted before the query executes. Multiple filters are batch-encrypted in a single ZeroKMS call for efficiency. Equality filters [#equality-filters] ```typescript // Exact match (requires .equality() on column) const { data } = await eSupabase .from("users", users) .select("id, email, name") .eq("email", "alice@example.com") // Not equal const { data } = await eSupabase .from("users", users) .select("id, email, name") .neq("email", "alice@example.com") // IN array (requires .equality()) const { data } = await eSupabase .from("users", users) .select("id, email, name") .in("name", ["Alice Smith", "Bob Jones"]) // NULL check (no encryption needed) const { data } = await eSupabase .from("users", users) .select("id, email, name") .is("email", null) ``` Text search filters [#text-search-filters] ```typescript // LIKE — case sensitive (requires .freeTextSearch()) const { data } = await eSupabase .from("users", users) .select("id, email, name") .like("name", "%alice%") // ILIKE — case insensitive (requires .freeTextSearch()) const { data } = await eSupabase .from("users", users) .select("id, email, name") .ilike("email", "%example.com%") ``` Range and comparison filters [#range-and-comparison-filters] ```typescript // Greater than (requires .orderAndRange()) const { data } = await eSupabase .from("users", users) .select("id, name, age") .gt("age", 21) // Greater than or equal const { data } = await eSupabase .from("users", users) .select("id, name, age") .gte("age", 18) // Less than const { data } = await eSupabase .from("users", users) .select("id, name, age") .lt("age", 65) // Less than or equal const { data } = await eSupabase .from("users", users) .select("id, name, age") .lte("age", 100) ``` Match (multi-column equality) [#match-multi-column-equality] ```typescript const { data } = await eSupabase .from("users", users) .select("id, email, name") .match({ email: "alice@example.com", name: "Alice Smith" }) ``` OR conditions [#or-conditions] ```typescript // String format const { data } = await eSupabase .from("users", users) .select("id, email, name") .or("email.eq.alice@example.com,email.eq.bob@example.com") // Structured format (more type-safe) const { data } = await eSupabase .from("users", users) .select("id, email, name") .or([ { column: "email", op: "eq", value: "alice@example.com" }, { column: "email", op: "eq", value: "bob@example.com" }, ]) ``` Both forms encrypt values for encrypted columns automatically. NOT filter [#not-filter] ```typescript const { data } = await eSupabase .from("users", users) .select("id, email, name") .not("email", "eq", "alice@example.com") ``` Raw filter [#raw-filter] ```typescript const { data } = await eSupabase .from("users", users) .select("id, email, name") .filter("email", "eq", "alice@example.com") ``` Combining encrypted and non-encrypted filters [#combining-encrypted-and-non-encrypted-filters] ```typescript const { data } = await eSupabase .from("users", users) .select("id, email, name") .eq("email", "alice@example.com") // encrypted .eq("role", "admin") // passed through as-is ``` Updating and deleting [#updating-and-deleting] ```typescript // Update const { data } = await eSupabase .from("users", users) .update({ name: "Alice Johnson" }) // encrypted automatically .eq("id", 1) .select("id, name") // Upsert const { data } = await eSupabase .from("users", users) .upsert( { id: 1, email: "alice@example.com", name: "Alice", role: "admin" }, { onConflict: "id" }, ) .select("id, email, name") // Delete const { error } = await eSupabase .from("users", users) .delete() .eq("id", 1) ``` Transforms [#transforms] These are passed through to Supabase directly: ```typescript const { data } = await eSupabase .from("users", users) .select("id, email, name") .eq("name", "Alice") .order("id", { ascending: false }) .limit(10) .range(0, 9) ``` Lock context and audit [#lock-context-and-audit] Chain `.withLockContext()` to tie encryption to a specific user's JWT: ```typescript import { LockContext } from "@cipherstash/stack/identity" const lc = new LockContext() const { data: lockContext } = await lc.identify(userJwt) const { data } = await eSupabase .from("users", users) .insert({ email: "alice@example.com", name: "Alice" }) .withLockContext(lockContext) .select("id") ``` Lock contexts work with all operations (insert, select, update, delete): ```typescript const { data } = await eSupabase .from("users", users) .select("id, email, name") .eq("email", "alice@example.com") .withLockContext(lockContext) .audit({ metadata: { userId: "user_123" } }) ``` Error handling [#error-handling] Encryption errors are surfaced with an additional `encryptionError` field: ```typescript const { data, error } = await eSupabase .from("users", users) .select("id, email") if (error) { if (error.encryptionError) { console.error("Encryption error:", error.encryptionError) } } ``` Response type [#response-type] ```typescript type EncryptedSupabaseResponse = { data: T | null // Decrypted rows error: EncryptedSupabaseError | null count: number | null status: number statusText: string } ``` Errors can come from Supabase (API errors) or from encryption operations. Check `error.encryptionError` for encryption-specific failures. Filter to index mapping [#filter-to-index-mapping] | Filter Method | Required Index | Query Type | | ------------------------ | ------------------- | ---------------------------------- | | `eq`, `neq`, `in` | `.equality()` | `'equality'` | | `like`, `ilike` | `.freeTextSearch()` | `'freeTextSearch'` | | `gt`, `gte`, `lt`, `lte` | `.orderAndRange()` | `'orderAndRange'` | | `is` | None | No encryption (NULL/boolean check) | Exposing EQL schema for Supabase [#exposing-eql-schema-for-supabase] 1. Go to [API settings](https://supabase.com/dashboard/project/_/settings/api) and add `eql_v2` to "Exposed schemas" 2. Run the following SQL: ```sql GRANT USAGE ON SCHEMA eql_v2 TO anon, authenticated, service_role; GRANT ALL ON ALL TABLES IN SCHEMA eql_v2 TO anon, authenticated, service_role; GRANT ALL ON ALL ROUTINES IN SCHEMA eql_v2 TO anon, authenticated, service_role; GRANT ALL ON ALL SEQUENCES IN SCHEMA eql_v2 TO anon, authenticated, service_role; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA eql_v2 GRANT ALL ON TABLES TO anon, authenticated, service_role; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA eql_v2 GRANT ALL ON ROUTINES TO anon, authenticated, service_role; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA eql_v2 GRANT ALL ON SEQUENCES TO anon, authenticated, service_role; ``` Complete example [#complete-example] ```typescript import { createClient } from "@supabase/supabase-js" import { Encryption } from "@cipherstash/stack" import { encryptedSupabase } from "@cipherstash/stack/supabase" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" // Schema const users = encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch(), name: encryptedColumn("name").equality().freeTextSearch(), age: encryptedColumn("age").dataType("number").equality().orderAndRange(), }) // Clients const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!) const encryptionClient = await Encryption({ schemas: [users] }) const eSupabase = encryptedSupabase({ encryptionClient, supabaseClient: supabase }) // Insert await eSupabase .from("users", users) .insert([ { email: "alice@example.com", name: "Alice", age: 30 }, { email: "bob@example.com", name: "Bob", age: 25 }, ]) // Query with multiple filters const { data } = await eSupabase .from("users", users) .select("id, email, name, age") .gte("age", 18) .lte("age", 35) .ilike("name", "%ali%") // data is fully decrypted: // [{ id: 1, email: "alice@example.com", name: "Alice", age: 30 }] ``` # Testing Testing [#testing] This guide covers strategies for testing applications that use CipherStash encryption. Test environment setup [#test-environment-setup] CipherStash encryption requires valid credentials to derive keys via ZeroKMS. For testing, you have two options: Option 1: Use a dedicated test workspace (recommended) [#option-1-use-a-dedicated-test-workspace-recommended] Create a separate CipherStash workspace for testing. This gives you real encryption behavior with isolated keys that don't affect production. ```bash title=".env.test" CS_WORKSPACE_CRN=crn:ap-southeast-2.aws:your-test-workspace-id CS_CLIENT_ID=your-test-client-id CS_CLIENT_KEY=your-test-client-key CS_CLIENT_ACCESS_KEY=your-test-access-key ``` This approach tests the full encryption path including ZeroKMS key derivation. Option 2: Mock the encryption client [#option-2-mock-the-encryption-client] For unit tests where you don't need real encryption, mock the client to return predictable values: ```typescript title="__mocks__/encryption.ts" import type { InferEncrypted } from "@cipherstash/stack/schema" export function createMockClient() { return { encrypt: async (plaintext: unknown) => ({ data: { __mock: true, plaintext }, }), decrypt: async (encrypted: unknown) => ({ data: (encrypted as any).plaintext, }), encryptModel: async (model: unknown) => ({ data: model, }), decryptModel: async (model: unknown) => ({ data: model, }), bulkEncrypt: async (items: any[]) => ({ data: items.map(i => ({ id: i.id, data: { __mock: true, plaintext: i.plaintext } })), }), bulkDecrypt: async (items: any[]) => ({ data: items.map(i => ({ id: i.id, data: i.plaintext })), }), } } ``` > **Good to know**: Mocking bypasses all encryption. Use this for testing business logic, not for validating that encryption works correctly. Always run integration tests with a real workspace. Integration tests with PostgreSQL [#integration-tests-with-postgresql] For integration tests that verify searchable encryption queries, you need: 1. A PostgreSQL database with the EQL extension 2. A test CipherStash workspace 3. Your schema definitions ```typescript title="test/setup.ts" import { Encryption } from "@cipherstash/stack" import { users } from "../src/schema" import { Pool } from "pg" let client: Awaited> let pool: Pool beforeAll(async () => { pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL }) // Install EQL extension await pool.query("CREATE EXTENSION IF NOT EXISTS eql_v2") // Initialize encryption client with test credentials // Encryption() throws on failure, so wrap in try/catch try { client = await Encryption({ schemas: [users] }) } catch (error) { throw new Error(`Test setup failed: ${(error as Error).message}`) } }) afterAll(async () => { await pool.end() }) ``` Testing encrypt and decrypt round-trips [#testing-encrypt-and-decrypt-round-trips] ```typescript title="test/encryption.test.ts" test("encrypt and decrypt returns original value", async () => { const original = "alice@example.com" const encrypted = await client.encrypt(original, { column: users.email, table: users, }) expect(encrypted.failure).toBeUndefined() const decrypted = await client.decrypt(encrypted.data) expect(decrypted.failure).toBeUndefined() expect(decrypted.data).toBe(original) }) ``` Testing searchable queries [#testing-searchable-queries] ```typescript title="test/search.test.ts" test("equality search finds encrypted record", async () => { // Encrypt and store a value const encrypted = await client.encrypt("alice@example.com", { column: users.email, table: users, }) await pool.query( "INSERT INTO users (email) VALUES ($1::jsonb)", [JSON.stringify(encrypted.data)] ) // Encrypt the search term const query = await client.encryptQuery("alice@example.com", { column: users.email, table: users, queryType: "equality", }) // Query the database const result = await pool.query( "SELECT * FROM users WHERE email @> $1::jsonb", [JSON.stringify(query.data)] ) expect(result.rows.length).toBe(1) }) ``` CI/CD setup [#cicd-setup] GitHub Actions [#github-actions] Store your test credentials as GitHub Actions secrets and expose them as environment variables: ```yaml title=".github/workflows/test.yml" name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:16 env: POSTGRES_DB: test POSTGRES_PASSWORD: postgres ports: - 5432:5432 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: pnpm install --frozen-lockfile - run: pnpm test env: CS_WORKSPACE_CRN: ${{ secrets.CS_TEST_WORKSPACE_CRN }} CS_CLIENT_ID: ${{ secrets.CS_TEST_CLIENT_ID }} CS_CLIENT_KEY: ${{ secrets.CS_TEST_CLIENT_KEY }} CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_TEST_ACCESS_KEY }} TEST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/test ``` > **Good to know**: Use a dedicated test workspace with limited access keys. Never use production credentials in CI. Docker-based tests [#docker-based-tests] If your CI uses Docker, ensure the native addon loads correctly by using a Linux-compatible lockfile. See [Bundling — Linux deployments](/docs/encryption/bundling#troubleshooting-linux-deployments) for details. Schema builders in test code [#schema-builders-in-test-code] The `@cipherstash/stack/client` subpath provides schema builders without the native FFI module. This is useful for importing schemas in client-side test code or test utilities that don't perform encryption: ```typescript title="test/utils.ts" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/client" // This import works without the native addon const users = encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch(), }) ``` # Troubleshooting Troubleshooting [#troubleshooting] Client initialization fails [#client-initialization-fails] Missing environment variables [#missing-environment-variables] The SDK reads `CS_*` environment variables by default. If any are missing, initialization fails with a `ClientInitError`. ``` ClientInitError: Missing workspace CRN ``` **Fix**: Set all required environment variables: ```bash CS_WORKSPACE_CRN=crn:ap-southeast-2.aws:your-workspace-id CS_CLIENT_ID=your-client-id CS_CLIENT_KEY=your-client-key CS_CLIENT_ACCESS_KEY=your-access-key ``` Or pass them explicitly in config: ```typescript title="explicit-config.ts" const client = await Encryption({ schemas: [users], config: { workspaceCrn: "crn:ap-southeast-2.aws:your-workspace-id", clientId: "your-client-id", clientKey: "your-client-key", accessKey: "your-access-key", }, }) ``` ZeroKMS connection timeout [#zerokms-connection-timeout] If the SDK cannot reach ZeroKMS, initialization will time out. **Check**: * Your application has outbound HTTPS access * No firewall or security group is blocking connections to `*.viturhosted.net` * The region in your `CS_WORKSPACE_CRN` matches your workspace's actual region No schemas provided [#no-schemas-provided] The `Encryption()` function requires at least one schema: ```typescript title="schema-required.ts" // This will fail const client = await Encryption({ schemas: [] }) // Provide at least one schema const client = await Encryption({ schemas: [users] }) ``` Decryption errors [#decryption-errors] Wrong keyset [#wrong-keyset] If data was encrypted with keyset A but you're decrypting with keyset B, decryption will fail. Each keyset provides cryptographic isolation. **Fix**: Ensure the client is configured with the same keyset that was used during encryption. Check the `keyset` option in your config or verify which keyset your client is associated with in the [dashboard](/docs/kms/clients). Lock context mismatch [#lock-context-mismatch] If data was encrypted with [identity-aware encryption](/docs/encryption/identity), it can only be decrypted with the same user's identity. ``` DecryptionError: Lock context mismatch ``` **Fix**: Ensure you're using the same user's JWT for decryption that was used during encryption. The lock context binds encryption to the identity claims in the JWT. Corrupted or tampered ciphertext [#corrupted-or-tampered-ciphertext] If the stored ciphertext has been modified, decryption will fail because AES-GCM-SIV provides authenticated encryption. **Fix**: Check that no database triggers, application code, or migrations have modified the encrypted column values. Search queries return no results [#search-queries-return-no-results] Missing EQL extension [#missing-eql-extension] Searchable encryption requires the [EQL PostgreSQL extension](/docs/platform/eql). Without it, queries against encrypted columns won't work. ```sql CREATE EXTENSION IF NOT EXISTS eql_v2; ``` Wrong query type [#wrong-query-type] If you're searching with the wrong query type for your column's indexes, queries may return no results. **Check**: * Using `equality` queries? The column needs `.equality()` in its schema. * Using `freeTextSearch` queries? The column needs `.freeTextSearch()` in its schema. * Using `orderAndRange` queries? The column needs `.orderAndRange()` in its schema. Schema mismatch [#schema-mismatch] The schema used to encrypt data must match the schema used to encrypt query terms. If you've changed your schema definition (added/removed indexes), existing encrypted data may not be searchable with the new schema. **Fix**: Re-encrypt existing data with the updated schema if you've changed index configurations. Native addon fails to load [#native-addon-fails-to-load] Symptoms [#symptoms] ``` Error: failed to load native addon Error: module not found: @cipherstash/protect-ffi ``` Bundler not configured [#bundler-not-configured] `@cipherstash/stack` uses a native Node.js addon and cannot be processed by bundlers. You must exclude it. **Fix**: See [Bundling](/docs/encryption/bundling) for configuration for Next.js, webpack, esbuild, and SST. Linux deployment with npm lockfile v3 [#linux-deployment-with-npm-lockfile-v3] Some npm users see deployment failures on Linux (e.g., AWS Lambda) when their `package-lock.json` was created on macOS or Windows. This happens with `package-lock.json` version 3, where npm only records native dependencies for the platform that created the lockfile. Linux builds can miss the native engine that `@cipherstash/stack` needs at runtime. **Who is affected**: * You use `npm ci` in CI/CD * Your `package-lock.json` is version 3 and was generated on macOS or Windows * You deploy on Linux (Lambda, containers, EC2, etc.) **Fix**: Use one of these solutions: **Option 1: Use pnpm (recommended)** ```bash pnpm install --frozen-lockfile ``` **Option 2: Generate lockfile on Linux in CI** ```bash rm -f package-lock.json npm install --package-lock-only --ignore-scripts --no-audit --no-fund --platform=linux --arch=x64 npm ci ``` **Option 3: Pin lockfile to version 2** ```bash npm install --package-lock-only --lockfile-version=2 ``` Quick verification [#quick-verification] Before deploying, verify the native engine loads by running your app inside a Linux container or CI job. Performance [#performance] Slow encrypt/decrypt operations [#slow-encryptdecrypt-operations] CipherStash encryption adds \< 5ms overhead per operation. If you're seeing higher latency: 1. **Check network latency to ZeroKMS.** Key derivation requires a round-trip to ZeroKMS. Deploy your application in the same AWS region as your workspace. 2. **Use bulk operations.** `bulkEncrypt` and `bulkDecrypt` make a single ZeroKMS call regardless of record count, which is significantly faster for multiple values. 3. **Check your connection pool.** If you're creating a new `Encryption` client per request, you're paying initialization cost each time. Initialize once and reuse the client. Optimizing bulk operations [#optimizing-bulk-operations] For best performance with bulk operations: ```typescript title="bulk-optimization.ts" // Slow: one ZeroKMS call per value for (const user of users) { await client.encrypt(user.email, { column: schema.email, table: schema }) } // Fast: one ZeroKMS call for all values const plaintexts = users.map(u => ({ id: u.id, plaintext: u.email })) await client.bulkEncrypt(plaintexts, { column: schema.email, table: schema }) ``` Debugging [#debugging] Enable debug logging to see detailed operation information: ```bash STASH_STACK_LOG=debug ``` This logs key derivation requests, operation timing, and connection details. The SDK never logs plaintext data or key material at any log level. # Access keys Access keys [#access-keys] Access keys are used to authenticate programmatic access to CipherStash [CTS](/docs/kms/cts) and [ZeroKMS](/docs/kms). Creating an access key [#creating-an-access-key] In the [CipherStash Dashboard](https://dashboard.cipherstash.com), you can create an access key by clicking the **Create access key** button in the [Access Keys](https://dashboard.cipherstash.com/workspaces/_/access-keys) section for your workspace. Roles [#roles] An access key is assigned a role which is associated with a set of permissions. You should use the role with the *least required privileges* and avoid higher privileges unless absolutely necessary. Member [#member] The member role is used for authenticating clients for cryptographic operations. These are the scopes that are available to the member role: ```text keyset:list data_key:generate data_key:retrieve ``` Control [#control] The control role is used for workspace automation tasks. It has access to the CipherStash API endpoints for creating, listing, enabling, disabling, granting, modifying, and revoking keysets and clients. These are the scopes that are available to the control role: ```text keyset:create keyset:list keyset:enable keyset:disable keyset:grant keyset:modify keyset:revoke client:list ``` Admin [#admin] In production environments, it is recommended to never use the admin role. Use the member role for authenticating clients, and the control role for workspace automation tasks. The admin role is "god" mode. It has access to all the CipherStash API endpoints and can authenticate clients for cryptographic operations. These are the scopes that are available to the admin role: ```text keyset:create keyset:list keyset:enable keyset:disable keyset:grant keyset:modify keyset:revoke data_key:generate data_key:retrieve client:create client:list client:delete access_key:create access_key:list access_key:delete ``` # Clients Clients [#clients] A Client is an instance of [CipherStash Proxy](/docs/proxy) or an application using an SDK like the [Encryption SDK](/docs/encryption) that you want to assign encryption or decryption privileges. A unique ID and key is generated for each client so access can be easily revoked at any time. Clients and keysets [#clients-and-keysets] When created, each client is associated with a [keyset](/docs/kms/keysets). This allows the client to perform encryption and decryption operations within the keyset. Granting access to additional keysets [#granting-access-to-additional-keysets] When creating a new client, you can associate it with one or more keysets to grant access to the encrypted data in those keysets. Revoking access to a client [#revoking-access-to-a-client] At any time, you can revoke access to a client by removing the association with the keysets which will prohibit that client from accessing the encrypted data in those keysets. Creating a client [#creating-a-client] In the [CipherStash Dashboard](https://dashboard.cipherstash.com), you can create a client by clicking the **Create a new client** button in the [Clients](https://dashboard.cipherstash.com/workspaces/_/clients) page. # Configuration KMS configuration [#kms-configuration] ZeroKMS is configured through the same credentials used by the Encryption SDK. The `CS_WORKSPACE_CRN` identifies your workspace in CRN format (e.g., `crn:ap-southeast-2.aws:your-workspace-id`). For the full configuration reference — environment variables, programmatic config, and logging — see [Encryption SDK configuration](/docs/encryption/configuration). Getting credentials [#getting-credentials] Sign up at [cipherstash.com/signup](https://cipherstash.com/signup) and follow the onboarding to generate your workspace credentials. You'll receive: * **Workspace CRN** — identifies your workspace and region * **Client ID** — identifies your application client * **Client key** — your half of the [dual-party key split](/docs/platform/security-architecture#key-hierarchy) * **Access key** — API key for authenticating with CipherStash Keysets [#keysets] To use a specific keyset for multi-tenant isolation, pass the `keyset` option: ```typescript title="keyset-config.ts" const client = await Encryption({ schemas: [users], config: { keyset: { name: "tenant-a" }, }, }) ``` See [Keysets](/docs/kms/keysets) for more details on multi-tenant key isolation. # CipherStash Token Service (CTS) CipherStash Token Service (CTS) [#cipherstash-token-service-cts] CipherStash Token Service (CTS) is an authentication and identity federation service. CTS issues tokens that grant clients access to [ZeroKMS](/docs/kms). That access is temporary, limited, and can be granularly scoped. If you are familiar with the [AWS Security Token Service](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html) (AWS STS), CTS fulfills a similar role to that. How it works [#how-it-works] At a high level: * Clients authenticate to CTS * CTS issues temporary tokens to those authenticated clients * Those temporary tokens can be used to make requests to ZeroKMS A client can be either [CipherStash Proxy](/docs/proxy) or an application using the [Encryption SDK](/docs/encryption). Tokens [#tokens] Clients can authenticate to CTS via either: * [Federating with an IDP](#federation) * [Access keys](#access-keys) The temporary tokens are, as the name suggests, temporary — they are valid for a maximum of 15 minutes. Federation [#federation] Federation allows you to use an existing source of identities to authenticate to CTS, and onwards to ZeroKMS. This allows you to rapidly grant and revoke people's access based on your product or organisation's onboarding and offboarding processes. By default, CTS federates with CipherStash Cloud's IDP. Bringing your own Identity Provider (IDP) [#bringing-your-own-identity-provider-idp] If you want to bring your own [IDP](/docs/glossary#idp-identity-provider) to CTS, you can configure your workspace to use either [Auth0](https://auth0.com/), [Okta](https://okta.com/), or [Clerk](https://clerk.com/). Reach out to [support@cipherstash.com](mailto:support@cipherstash.com) to get your workspace configured with your own IDP and discuss your use case. Access keys [#access-keys] [Access keys](/docs/kms/access-keys) are a persistent credential you can use for machine-to-machine access to CTS. You can create access keys in the [CipherStash Dashboard](https://dashboard.cipherstash.com). # Disaster recovery Disaster recovery [#disaster-recovery] ZeroKMS is designed to protect your encrypted data with robust disaster recovery capabilities. Your encrypted data remains recoverable even in the event of a complete service disruption. Separation of keys and data [#separation-of-keys-and-data] ZeroKMS uses a fundamental architectural principle: **your encrypted data stays in your database**. We only manage the key material needed to decrypt that data. This separation means: * Your encrypted data is never stored in CipherStash infrastructure * A CipherStash outage affects key access, not your data * Recovery doesn't require migrating or restoring potentially terabytes of encrypted data Key recovery process [#key-recovery-process] ZeroKMS uses a hierarchical key structure that enables you to regenerate data keys on-demand without storing every individual key. This means: * Key material is cryptographically reproducible * No database of individual data keys needs to be restored * Recovery time is measured in hours, not days Zero data loss [#zero-data-loss] In a disaster recovery scenario, no data loss occurs. All encrypted data remains intact and fully recoverable. The key material used to generate your data keys is preserved and can be regenerated. Fast recovery in case of regional failure [#fast-recovery-in-case-of-regional-failure] In the event of a complete regional failure, CipherStash can restore ZeroKMS service within a few hours. During this time: * Your encrypted data remains safely stored in your database * Applications cannot decrypt existing data or encrypt new data * Once service is restored, all operations resume normally What happens during a ZeroKMS outage? [#what-happens-during-a-zerokms-outage] Data access during an outage [#data-access-during-an-outage] **During a CipherStash outage, can we access our encrypted data?** No. While ZeroKMS is unavailable, your applications cannot perform cryptographic operations (encrypt or decrypt). This is an intentional security design — cryptographic operations require active key management infrastructure. **Is our encrypted data at risk?** No. Your encrypted data remains safely stored in your own database. A CipherStash outage does not expose, corrupt, or delete your data. After recovery [#after-recovery] Once service is restored to a new region or infrastructure: 1. Your applications reconnect to the restored ZeroKMS endpoint 2. Key material is cryptographically regenerated 3. All encrypted data becomes accessible again 4. No data migration or reindexing is required Architecture benefits [#architecture-benefits] Compared to traditional approaches [#compared-to-traditional-approaches] Many encryption and data protection solutions require storing both encrypted data and keys together in a "vault." During disaster recovery, these solutions must restore the entire vault — potentially terabytes of data, taking hours or days. **ZeroKMS is different:** * Only lightweight key material (megabytes) needs to be restored * Your encrypted data never moves — it stays in your database * Recovery is based on cryptographic regeneration, not data restoration Security during recovery [#security-during-recovery] * Encrypted data in your database remains protected * Key material is restored through cryptographically secure processes * Access controls and audit logging resume immediately when service is restored * No temporary "recovery windows" that might expose data Questions? [#questions] If you have specific disaster recovery requirements or need to discuss your resilience planning, please contact [support@cipherstash.com](mailto:support@cipherstash.com). # KMS ZeroKMS [#zerokms] ZeroKMS is the key management service that powers both [CipherStash Encryption](/docs/encryption) and [CipherStash Secrets](/docs/secrets). Every encrypted value gets its own unique key, derived via ZeroKMS and backed by AWS KMS — so you get strong key isolation without managing keys yourself. Zero Trust Key Management [#zero-trust-key-management] Zero Trust is a fundamental principle of secure-by-default design. However, applying Zero Trust to key management is unexpectedly difficult. Existing solutions reveal either data or keys to intermediaries which forces higher trust requirements for vendors and service providers. CipherStash's ZeroKMS uses **Zero Trust Key Management (ZTKM)**: key management for the connected digital landscape. How it works [#how-it-works] * **Unique key per value** — Each encrypted field uses a distinct data encryption key, not a shared table-level key. * **AWS KMS backed** — Root keys are stored in AWS KMS. ZeroKMS handles key derivation and wrapping. * **Zero-knowledge** — CipherStash never sees your plaintext data or unwrapped keys. When a data key is requested, ZeroKMS generates and returns key seeds to the client to create the data key locally. Data keys are never seen by third parties and are never sent across the network. * **Multi-tenant isolation** — Use keysets to isolate encryption keys per tenant, customer, or business unit. * **Bulk operations** — ZeroKMS does bulk encryption and decryption operations, enabling a unique data key per record without sacrificing performance. * **Multi-region** — ZeroKMS is highly available and deployed in [multiple cloud regions globally](/docs/kms/regions). It can also be deployed within your own cloud account or on-prem. What Key Sets power [#what-key-sets-power] [Key Sets](/docs/kms/keysets) are ZeroKMS's core primitive for cryptographic isolation. Every product in the CipherStash stack builds on them: * **Encryption — multi-tenant isolation.** Each tenant, customer, or business unit gets its own Key Set. Data encrypted under one Key Set cannot be decrypted with another, giving you per-tenant cryptographic boundaries with zero key management overhead. See [Encryption configuration](/docs/encryption/configuration#keysets) for setup details. * **Secrets — environment isolation.** Each Secrets environment (production, staging, development) maps to its own Key Set. This means a secret encrypted in staging can never be decrypted with production keys, and vice versa. See [Secrets concepts](/docs/secrets/concepts#environments) for more detail. Key Sets are a general-purpose primitive — you can use them to create any cryptographic boundary your application needs, whether that's per-tenant, per-environment, per-region, or any other isolation requirement. Read the whitepaper [#read-the-whitepaper] If you'd like to learn more about ZeroKMS, read the whitepaper on the [Trust Center](https://trust.cipherstash.com/resources). Next steps [#next-steps] # Keysets Keysets [#keysets] Keysets are used, in combination with ZeroKMS key material, to derive unique data keys per record. Each keyset maintains its own set of data encryption keys, so data encrypted under one keyset cannot be decrypted with another. Key Sets are the foundational primitive that powers [multi-tenant encryption](/docs/encryption/configuration#keysets) in the Encryption SDK and [environment isolation](/docs/secrets/concepts#environments) in Secrets. Default keyset [#default-keyset] Each workspace has a default keyset, which is used to derive data keys for all records in the workspace and meets the needs for most use cases. The default keyset is created automatically when you create a workspace. You can create additional keysets to meet your specific needs. Create a client with a keyset [#create-a-client-with-a-keyset] By ID [#by-id] ```typescript import { Encryption } from "@cipherstash/stack" import { users } from "./schema" const client = await Encryption({ schemas: [users], config: { keyset: { id: "123e4567-e89b-12d3-a456-426614174000" }, }, }) ``` By name [#by-name] ```typescript const client = await Encryption({ schemas: [users], config: { keyset: { name: "Company A" }, }, }) ``` In Secrets (via environments) [#in-secrets-via-environments] Secrets environments map directly to Key Sets. When you specify an `environment`, the Secrets SDK automatically uses the corresponding Key Set for encryption and decryption. See [Secrets concepts](/docs/secrets/concepts#environments) for more detail. ```typescript import { Secrets } from "@cipherstash/stack/secrets" const secrets = new Secrets({ // ...credentials environment: "production", // maps to a Key Set }) ``` How it works [#how-it-works] When you specify a keyset, ZeroKMS derives all data encryption keys from that keyset's root key. This means: * Data encrypted under keyset A cannot be decrypted with keyset B. * You can rotate or revoke keys at the keyset level. * Each tenant's data is cryptographically isolated from every other tenant. Use cases [#use-cases] * **Multi-tenant SaaS** — Give each customer their own keyset so their data is cryptographically separated. See [Encryption configuration](/docs/encryption/configuration#keysets). * **Environment isolation** — Use different keysets for production, staging, and development. See [Secrets concepts](/docs/secrets/concepts#environments). * **Compliance boundaries** — Isolate data by jurisdiction or regulatory requirement. * **Custom isolation boundaries** — Key Sets are a general-purpose primitive. Use them for any cryptographic boundary your application needs — per-region, per-department, or per-feature. Keysets and clients [#keysets-and-clients] Keysets can be associated with any number of clients to grant that client access to performing encryption and decryption operations on the data in that keyset. Granting access to a client [#granting-access-to-a-client] When creating a new keyset, you must choose which clients will be granted access to the keyset. You can manage this by clicking **Manage** on a particular keyset in the [Keysets](https://dashboard.cipherstash.com/workspaces/_/encryption/keysets) page of the [CipherStash Dashboard](https://dashboard.cipherstash.com). Revoking access to a keyset [#revoking-access-to-a-keyset] At any time, you can revoke access to a client by removing the association with the keysets which will prohibit that client from accessing the encrypted data in those keysets. You can manage this by clicking **Manage** on a particular client in the [Clients](https://dashboard.cipherstash.com/workspaces/_/clients) page of the [CipherStash Dashboard](https://dashboard.cipherstash.com). Managing keysets [#managing-keysets] Via the Dashboard [#via-the-dashboard] In the [CipherStash Dashboard](https://dashboard.cipherstash.com), you can create a keyset by clicking the **Create Keyset** button in the [Keysets](https://dashboard.cipherstash.com/workspaces/_/encryption/keysets) page. Via the API [#via-the-api] To create a keyset via the API, follow these steps: Step 1: Create an access key [#step-1-create-an-access-key] First, create an access key in the [CipherStash Dashboard](https://dashboard.cipherstash.com) with the role of "Control". Ensure you are using the access key with the "Control" role for workspace automation tasks. These access keys only have access to the CipherStash API CRUD operations and are not able to authenticate cryptographic operations. Step 2: Get a service token [#step-2-get-a-service-token] Use your access key to get a service token: ```bash curl https://ap-southeast-2.aws.auth.viturhosted.net/api/authorize \ -H "Content-Type: application/json" \ -d '{"accessKey": "your_access_key_set_with_control_role"}' ``` This will return a JSON response containing the access token and expiry information: ```json { "accessToken": "token_value", "expiry": 1757529498 } ``` Step 3: Export the token [#step-3-export-the-token] Export the access token as an environment variable: ```bash export CTS_TOKEN="the_accessToken_from_previous_step" ``` Step 4: Create the keyset [#step-4-create-the-keyset] Create a keyset using the service token (make sure your workspace is in ap-southeast-2 or update your endpoint): ```bash curl https://ap-southeast-2.aws.viturhosted.net/create-keyset \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CTS_TOKEN" \ -d '{"name":"my_automated_keyset","description":"automated"}' -v ``` This will return a JSON response containing the ID of the keyset created: ```json { "id": "key_set_id", "name": "my_key_set_name", "description": "my_key_set_description" } ``` Step 5: Grant access to a client [#step-5-grant-access-to-a-client] Grant access to a client by granting access to the keyset. You can find your client ID in the [CipherStash Dashboard](https://dashboard.cipherstash.com) on the [Clients](https://dashboard.cipherstash.com/workspaces/_/clients) page for your workspace. ```bash curl https://ap-southeast-2.aws.viturhosted.net/grant-keyset \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CTS_TOKEN" \ -d '{"keyset_id":"key_set_id","client_id":"client_id_to_grant_access_to"}' -v ``` This will return an HTTP 200 response if successful. # Regions Regions [#regions] Each workspace is tied to a specific region, and is deployed in that region's [ZeroKMS instance](/docs/kms). Supported ZeroKMS regions [#supported-zerokms-regions] * Asia Pacific (Sydney) * Europe (Frankfurt) * Europe (Ireland) * US East (N. Virginia) * US East (Ohio) * US West (N. California) * US West (Oregon) Requesting a new region [#requesting-a-new-region] If you need a region that is not listed above, please contact us at [support@cipherstash.com](mailto:support@cipherstash.com). # CipherStash vs AWS KMS CipherStash vs AWS KMS [#cipherstash-vs-aws-kms] Encrypting data shouldn't require managing binary buffers, base64 encoding, key ARNs, or building custom search solutions. CipherStash Encryption eliminates these complexities, giving you encryption that "just works" with a developer-friendly API. The simple truth: Encrypting a value [#the-simple-truth-encrypting-a-value] Let's start with the most basic operation — encrypting a single value. AWS KMS: Manual work required [#aws-kms-manual-work-required] ```typescript import { KMSClient, EncryptCommand } from '@aws-sdk/client-kms'; const client = new KMSClient({ region: 'us-west-2' }); const keyId = 'arn:aws:kms:us-west-2:123456789012:key/abcd1234-efgh-5678-ijkl-9012mnopqrst'; async function encryptWithKMS(plaintext: string): Promise { try { const command = new EncryptCommand({ KeyId: keyId, Plaintext: Buffer.from(plaintext), }); const response = await client.send(command); const ciphertext = response.CiphertextBlob; const base64Ciphertext = Buffer.from(ciphertext).toString('base64'); return base64Ciphertext; } catch (error) { console.error('Error encrypting data:', error); throw error; } } const encrypted = await encryptWithKMS('secret@squirrel.example'); ``` **What you're managing:** Key ARNs, binary buffer conversions, base64 encoding/decoding, manual error handling, region configuration, AWS credential setup. CipherStash Encryption: One simple call [#cipherstash-encryption-one-simple-call] ```typescript import { Encryption } from '@cipherstash/stack'; import { encryptedTable, encryptedColumn } from '@cipherstash/stack/schema'; const users = encryptedTable('users', { email: encryptedColumn('email'), }); const client = await Encryption({ schemas: [users], }); const encryptResult = await client.encrypt( 'secret@squirrel.example', { column: users.email, table: users } ); if (encryptResult.failure) { throw new Error(encryptResult.failure.message); } const ciphertext = encryptResult.data; ``` **What you get:** No key management (handled by ZeroKMS), no binary conversions, no base64 encoding, type-safe error handling, JSON payload ready for storage, zero-knowledge encryption by default. Decryption [#decryption] AWS KMS [#aws-kms] ```typescript async function decryptWithKMS(base64Ciphertext: string): Promise { try { const ciphertextBlob = Buffer.from(base64Ciphertext, 'base64'); const command = new DecryptCommand({ CiphertextBlob: ciphertextBlob }); const response = await client.send(command); return Buffer.from(response.Plaintext).toString('utf-8'); } catch (error) { console.error('Error decrypting data:', error); throw error; } } ``` CipherStash Encryption [#cipherstash-encryption] ```typescript const decryptResult = await client.decrypt(ciphertext); if (decryptResult.failure) { throw new Error(decryptResult.failure.message); } const plaintext = decryptResult.data; ``` Features that AWS KMS can't do without major custom work [#features-that-aws-kms-cant-do-without-major-custom-work] 1. Searchable encryption: Built-in vs impossible [#1-searchable-encryption-built-in-vs-impossible] **AWS KMS** requires decrypting everything and searching in memory, storing plaintext indexes, or building a custom searchable encryption solution. **CipherStash Encryption** has searchable encryption built-in: ```typescript const users = encryptedTable('users', { email: encryptedColumn('email') .freeTextSearch() .equality() .orderAndRange(), }); const searchTerms = await client.encryptQuery('secret', { column: users.email, table: users, }); ``` 2. Identity-aware encryption: Built-in vs custom implementation [#2-identity-aware-encryption-built-in-vs-custom-implementation] **AWS KMS** has no built-in support. You must implement custom logic with encryption context as a workaround. **CipherStash Encryption** has built-in identity-aware encryption: ```typescript import { LockContext } from '@cipherstash/stack/identity'; const lc = new LockContext(); const lockContext = await lc.identify(userJwt); const encryptResult = await client.encrypt( 'secret@squirrel.example', { column: users.email, table: users } ).withLockContext(lockContext); const decryptResult = await client.decrypt(ciphertext) .withLockContext(lockContext); ``` 3. Bulk operations: Native API vs manual batching [#3-bulk-operations-native-api-vs-manual-batching] **AWS KMS** has no bulk API — you must manually batch operations and handle rate limits. **CipherStash Encryption** has native bulk encryption: ```typescript const bulkPlaintexts = [ { id: '1', plaintext: 'Alice' }, { id: '2', plaintext: 'Bob' }, { id: '3', plaintext: 'Charlie' }, ]; const bulkResult = await client.bulkEncrypt(bulkPlaintexts, { column: users.name, table: users, }); const encryptedMap = bulkResult.data; ``` Feature comparison [#feature-comparison] | Feature | AWS KMS | CipherStash Encryption | | ----------------------------- | -------------------------------------- | -------------------------------- | | **Basic Encryption** | Requires manual buffer/base64 handling | One-line API, JSON payload | | **Key Management** | You manage key ARNs | Zero-knowledge, automatic | | **Searchable Encryption** | Not possible | Built-in for PostgreSQL | | **Identity-Aware Encryption** | Custom implementation | Built-in `LockContext` | | **Bulk Operations** | Manual batching | Native bulk API | | **Error Handling** | Try/catch, manual types | Type-safe Result pattern | | **Type Safety** | Manual typing | Full TypeScript inference | | **Storage Format** | Binary (needs encoding) | JSON (database-ready) | | **ORM Integration** | Manual integration | Built-in Drizzle support | | **Zero-Knowledge** | AWS has key access | True zero-knowledge | | **Setup Complexity** | Medium (AWS credentials, regions) | Low (just environment variables) | When to use each [#when-to-use-each] Use AWS KMS when: [#use-aws-kms-when] * You need encryption for AWS services (S3, EBS, etc.) * You're encrypting infrastructure-level resources * You don't need to search encrypted data * You're comfortable with manual buffer/base64 handling Use CipherStash Encryption when: [#use-cipherstash-encryption-when] * You're building applications with databases * You need to search encrypted data * You want a developer-friendly API * You need identity-aware encryption * You want zero-knowledge key management * You value type safety and developer experience References [#references] * [AWS KMS Documentation](https://docs.aws.amazon.com/kms/) * [CipherStash Encryption Getting Started](/docs/encryption/getting-started) * [CipherStash Schema Reference](/docs/encryption/schema) * [Searchable Encryption Concepts](/docs/platform/searchable-encryption) # Billing Billing [#billing] CipherStash uses a **per-workspace billing model**. Each workspace has its own plan and usage limits. Organizations and team members are always free and unlimited. Plans [#plans] | | Free | Pro ($99/mo) | Business ($870/mo) | Enterprise | | ----------------------- | -------- | ------------ | ------------------ | ---------- | | Protect ops/month | 10,000 | 50,000 | 500,000 | Unlimited | | Secrets | 100 | 500 | 2,000 | Unlimited | | Keysets | 5 | 10 | 25 | Unlimited | | Client apps | 1 | 5 | 15 | Unlimited | | OIDC providers | 1 | 2 | 5 | Unlimited | | Multi-tenant encryption | No | No | Yes | Yes | | Lock contexts | No | No | Yes | Yes | | Workspace type | Dev only | Dev + Prod | Dev + Prod | Dev + Prod | Free plan [#free-plan] The Free plan is designed for non-commercial personal projects and experimentation. Free workspaces are limited to development environments only. Pro plan [#pro-plan] The Pro plan is for teams and production applications. It includes development and production workspace support with higher limits. Business plan [#business-plan] The Business plan offers predictable pricing for demanding workloads with multi-tenant encryption and [Lock Contexts](/docs/encryption/identity) support. Enterprise plan [#enterprise-plan] Enterprise is an organization-level plan with custom pricing, unlimited limits across all workspaces, and dedicated support. Enterprise organizations bypass per-workspace billing entirely. Contact [sales@cipherstash.com](mailto:sales@cipherstash.com) for details. Managing workspace billing [#managing-workspace-billing] Each workspace has a dedicated billing page accessible from the workspace sidebar. From there you can: * View your current plan and usage metrics * Upgrade or downgrade your plan * Cancel your subscription * Access the Stripe billing portal for payment methods and invoices How billing works [#how-billing-works] * **One Stripe customer per organization**: All workspaces in your organization share the same Stripe customer for billing. * **One subscription per workspace**: Each workspace has its own Stripe subscription tied to its plan. * **Proration on upgrades**: When upgrading mid-cycle, you're charged a prorated amount for the remainder of the billing period. * **Downgrades at period end**: When downgrading, the change takes effect at the end of your current billing period. * **Cancellation**: Canceling a subscription keeps the plan active until the end of the billing period, then reverts to Free. Enterprise and AWS Marketplace [#enterprise-and-aws-marketplace] Organizations on Enterprise plans or subscribed through AWS Marketplace have billing managed at the organization level. All workspaces in these organizations have unlimited access to all features. Visit the Organization Profile to manage enterprise billing. Need help? [#need-help] Contact [support@cipherstash.com](mailto:support@cipherstash.com) for billing questions. # The CipherCell The CipherCell [#the-ciphercell] The **CipherCell** is CipherStash's standard format for storing encrypted data in a database. It is a JSON-based structure that combines **encrypted values**, **searchable encrypted metadata**, and **non-sensitive metadata** into a single, self-contained record. CipherCells are designed to make encrypted data practical to work with in real applications: they can be stored in existing databases (such as PostgreSQL jsonb columns), indexed, queried, and audited — without exposing plaintext. What a CipherCell contains [#what-a-ciphercell-contains] A CipherCell typically includes: * **Encrypted data** * The ciphertext for one or more sensitive values. * Each value is encrypted independently using strong authenticated encryption and unique per-value keys. * **Searchable Encrypted Metadata (SEM)** * Additional cryptographic material derived from the plaintext that enables secure querying using searchable encryption. * This metadata allows operations such as equality checks, range queries, or text search to be performed **without decrypting the data**. * The database can evaluate queries over this metadata, but cannot recover the original values. * **Non-sensitive metadata** * Plaintext fields that are safe to expose, such as schema identifiers, versioning information, timestamps, or application-level IDs. * Keeping this metadata unencrypted allows efficient filtering, indexing, and integration with existing tooling. How CipherCells are used [#how-ciphercells-are-used] CipherCells are stored directly in the database, usually in a JSON-compatible column. [Encrypt Query Language (EQL)](/docs/platform/eql) understands this structure and provides database functions and operators that work over CipherCells, enabling encrypted search and filtering while preserving strong security guarantees. From an application's perspective, a CipherCell behaves like a regular database value: * Applications write encrypted data as JSON * Databases store and index it * Queries operate on Searchable Encrypted Metadata (SEM) * Decryption happens only in trusted application code with the right keys and claims Why CipherCells exist [#why-ciphercells-exist] The CipherCell format solves a common problem with encryption at rest: traditional encryption makes data opaque and hard to query. CipherCells retain the benefits of encryption while enabling: * Fine-grained, **per-value** protection * Searchable encryption over structured data * Compatibility with existing databases and ORMs * Clear separation between sensitive and non-sensitive information In short, a CipherCell is the **unit of encrypted storage** in CipherStash — a portable, self-describing JSON record that makes encrypted data usable, searchable, and auditable by default. Structure [#structure] **Required fields**: Only `i` (identifier) and `v` (version) are required. **Payload requirement**: Either `c` (ciphertext) or `sv` (structured encryption vector) must be present, but never both. **Optional fields**: All searchable encrypted metadata (SEM) fields are optional and only included when the corresponding index types are configured. A CipherCell is stored as a JSON object with the following top-level structure: ```json { "i": { "t": "table_name", "c": "column_name" }, "v": 2, "c": "encrypted_data_in_messagepack_base85", "hm": "2e182f0c444d1d51f5f70f32d778b2eaa854f5921a4a2acaa4446c44055cb777", "ob": ["ore_block_1", "ore_block_2"], "bf": [1234, 5678, 9012] } ``` Top-level fields [#top-level-fields] i - Identifier [#i---identifier] The table and column identifier for this encrypted data. **Type**: Object with `t` (table) and `c` (column) properties **Required**: Yes ```json { "i": { "t": "users", "c": "email" } } ``` This field identifies which table and column the encrypted data belongs to, enabling proper decryption and index usage. v - Version [#v---version] The encryption version used for this CipherCell. **Type**: Integer **Required**: Yes ```json { "v": 2 } ``` The version field allows for cryptographic algorithm upgrades over time while maintaining backward compatibility. c - Ciphertext (required unless sv is present) [#c---ciphertext-required-unless-sv-is-present] The encrypted record containing the actual plaintext data. **Type**: String (MessagePack encoded and Base85 encoded) **Required**: Yes (unless `sv` field is present) ```json { "c": "Xk}0>Z*pVbW@%*8a%F0@" } ``` Either `c` or `sv` must be present in every CipherCell, but never both. Use `c` for standard encrypted values and `sv` for structured encryption vectors (arrays or JSON structures). a - Array item flag [#a---array-item-flag] Indicates whether this CipherCell represents an item within an array. **Type**: Boolean **Required**: No ```json { "a": true } ``` Searchable Encrypted Metadata (SEM) [#searchable-encrypted-metadata-sem] The CipherCell can contain various types of searchable encrypted metadata, each enabling different query capabilities. All SEM fields are optional and only included when the corresponding index type is configured. hm - HMAC-SHA256 [#hm---hmac-sha256] Enables exact match queries using HMAC-SHA256. **Type**: Hex-encoded string (64 characters) **Index Type**: [Exact](/docs/platform/supported-queries#exact-match) ```json { "hm": "2e182f0c444d1d51f5f70f32d778b2eaa854f5921a4a2acaa4446c44055cb777" } ``` ob - ORE Block [#ob---ore-block] Enables range queries and ordering using Order Revealing Encryption. **Type**: Array of strings **Index Type**: [Order / Range](/docs/platform/supported-queries#range--order) ```json { "ob": [ "01a2b3c4d5e6f7g8h9i0", "j1k2l3m4n5o6p7q8r9s0" ] } ``` bf - Bloom Filter [#bf---bloom-filter] Enables substring and pattern matching queries using encrypted Bloom filters with trigrams. **Type**: Array of integers **Index Type**: [Match](/docs/platform/supported-queries#match-pattern) ```json { "bf": [1234, 5678, 9012, 3456, 7890] } ``` b3 - Blake3 [#b3---blake3] Blake3 hash for exact matches in structured encryption vectors. **Type**: Hex-encoded string **Used in**: SteVec (Structured Encryption Vector) subfield s - Selector [#s---selector] Selector value for field selection in structured encryption vectors. **Type**: String **Used in**: SteVec (Structured Encryption Vector) subfield ocf - ORE CLWW Fixed-Width [#ocf---ore-clww-fixed-width] ORE CLWW (Chenette-Lewi-Weis-Wu) fixed-width scheme for 64-bit integer values in structured encryption vectors. **Type**: String **Used in**: SteVec (Structured Encryption Vector) subfield ocv - ORE CLWW Variable-Width [#ocv---ore-clww-variable-width] ORE CLWW variable-width scheme for string comparison in structured encryption vectors. **Type**: String **Used in**: SteVec (Structured Encryption Vector) subfield sv - Structured Encryption Vector (SteVec) (required unless c is present) [#sv---structured-encryption-vector-stevec-required-unless-c-is-present] Nested array of CipherCells for supporting containment queries and JSON-style operations. **Type**: Array of CipherCell objects **Required**: Yes (unless `c` attribute is present) ```json { "sv": [ { "c": "Xk}0>Z*pVbW@%*8a%F0@", "hm": "hash1...", "s": "selector1" }, { "c": "Yl~1?A+qWcX#&+9b&G1#", "hm": "hash2...", "s": "selector2" } ] } ``` SteVec enables queries on array elements and JSON document structures while maintaining encryption. Each element in the `sv` array is itself a CipherCell that can contain SEM fields like `b3`, `s`, `ocf`, and `ocv`. Complete example [#complete-example] Here's a complete CipherCell with multiple index types enabled: ```json { "i": { "t": "products", "c": "price" }, "v": 2, "c": "Xk}0>Z*pVbW@%*8a%F0@Yl~1?A+qWcX#&+9b&G1#", "hm": "2e182f0c444d1d51f5f70f32d778b2eaa854f5921a4a2acaa4446c44055cb777", "ob": [ "01a2b3c4d5e6f7g8h9i0", "j1k2l3m4n5o6p7q8r9s0", "t1u2v3w4x5y6z7a8b9c0" ], "bf": [1234, 5678, 9012, 3456, 7890, 2345, 6789] } ``` This CipherCell: * Belongs to the `price` column of the `products` table * Uses encryption version 2 * Contains the encrypted plaintext value * Supports exact match queries via `hm` * Supports range queries and ordering via `ob` * Supports pattern matching via `bf` Design principles [#design-principles] Minimal storage [#minimal-storage] Only the index types that are actually configured for a column are included in the CipherCell. This minimizes storage overhead and ensures optimal performance. Composable indexes [#composable-indexes] Multiple index types can be combined on a single column, enabling both exact matches and range queries, or exact matches and pattern matching, depending on application needs. Forward compatibility [#forward-compatibility] The version field (`v`) enables cryptographic algorithm upgrades without requiring full database re-encryption. Older versions can coexist with newer versions during migration. Standardized format [#standardized-format] The CipherCell format is consistent across all CipherStash SDKs and tools, ensuring interoperability and portability of encrypted data. Database storage [#database-storage] CipherCells can be stored as JSON in any database that supports JSON data types. However, for search to be supported using the [Encryption SDK](/docs/encryption) or [CipherStash Proxy](/docs/proxy), the `eql_v2.encrypted` database type must be used which is available when the Encrypt Query Language (EQL) helpers have been installed. # Compliance Compliance [#compliance] CipherStash helps organizations meet compliance requirements through field-level encryption, identity-bound access controls, and audit logging. This page summarizes how CipherStash maps to common compliance frameworks and what capabilities are available. Compliance frameworks [#compliance-frameworks] CipherStash's encryption and access control capabilities support the following compliance frameworks: | Framework | How CipherStash helps | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | **SOC 2 Type II** | Field-level encryption, access key scoping, audit logging of every decrypt operation, key rotation via keysets | | **HIPAA** | Encryption of PHI at the field level, identity-aware access controls (lock contexts), audit trails for data access | | **GDPR** | Encryption of personal data, data residency controls via [regional deployment](/docs/kms/regions), crypto-shredding by deleting keysets | | **PCI-DSS** | Encryption of cardholder data fields, key management via ZeroKMS, separation of key material from encrypted data | | **ISO 27001** | Cryptographic controls (A.10), access control (A.9), logging and monitoring (A.12) | | **CCPA** | Encryption of consumer personal information, access controls, audit trails | > **Good to know**: Contact [support@cipherstash.com](mailto:support@cipherstash.com) to request SOC 2 reports, sign a HIPAA Business Associate Agreement (BAA), or obtain a GDPR Data Processing Agreement (DPA). Data residency [#data-residency] CipherStash workspaces are deployed to specific AWS regions. Key material and encryption operations stay within the configured region. Available regions are listed on the [Regions](/docs/kms/regions) page. When you create a workspace, you select a region and all ZeroKMS key material is stored and processed in that region. Audit logging [#audit-logging] CipherStash logs every key derivation event — which means every encrypt and decrypt operation is recorded with context about who performed it, when, and for which keyset. Audit logs are available through the CipherStash dashboard. For programmatic access or SIEM integration, contact [support@cipherstash.com](mailto:support@cipherstash.com). What is logged [#what-is-logged] | Field | Description | | ---------------- | ------------------------------------------------------------------------ | | Timestamp | When the operation occurred | | Operation type | Encrypt or decrypt | | Client ID | Which client performed the operation | | Keyset | Which keyset was used | | Identity context | The identity claim (if using [lock contexts](/docs/encryption/identity)) | Audit with the SDK [#audit-with-the-sdk] The SDK supports attaching audit metadata to any operation: ```typescript title="audit-example.ts" const result = await client .encrypt(plaintext, { column: users.email, table: users }) .audit({ metadata: { action: "create", resource: "user" } }) ``` This metadata is included in the audit log alongside the standard fields. Crypto-shredding [#crypto-shredding] CipherStash supports crypto-shredding — permanently destroying data by deleting the encryption keys rather than the ciphertext. Because each [keyset](/docs/kms/keysets) provides cryptographic isolation, you can: 1. Delete a keyset to make all data encrypted under it permanently irrecoverable. 2. Use per-tenant keysets to support GDPR right-to-erasure (Article 17) by deleting a tenant's keyset. The encrypted data remains in your database but can never be decrypted. Key rotation [#key-rotation] CipherStash supports key rotation at the keyset level. Key rotation generates new key material without interrupting running applications. For rotation procedures, see [Keysets](/docs/kms/keysets). Security architecture [#security-architecture] For a complete description of the cryptographic design, key hierarchy, and trust model, see [Security architecture](/docs/platform/security-architecture). # Encrypt Query Language (EQL) Encrypt Query Language (EQL) [#encrypt-query-language-eql] **Encrypt Query Language (EQL)** is a set of PostgreSQL types, operators, and functions that enable queries on encrypted data without decryption. EQL works seamlessly with the [CipherCell](/docs/platform/cipher-cell) format to provide searchable encryption capabilities directly in PostgreSQL. What is EQL? [#what-is-eql] EQL provides the database-side components needed to query encrypted data. Unlike traditional PostgreSQL extensions, EQL is implemented as a collection of types, operators, and functions, making it compatible with managed database providers like AWS RDS that restrict extension installation. When combined with the [Encryption SDK](/docs/encryption) or [CipherStash Proxy](/docs/proxy), EQL enables: * **Exact match queries** using encrypted equality operators * **Range queries** with order-preserving encryption * **Pattern matching** using encrypted Bloom filters * **Unique constraints** on encrypted columns * **JSON/JSONB operations** on encrypted structured data Core components [#core-components] The eql_v2_encrypted type [#the-eql_v2_encrypted-type] The foundation of EQL is the `eql_v2_encrypted` data type, which stores [CipherCells](/docs/platform/cipher-cell) containing encrypted data and searchable encrypted metadata. ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, email eql_v2_encrypted, name eql_v2_encrypted ); ``` The `eql_v2_encrypted` type is required for searchable encryption in PostgreSQL. Regular `JSON` or `JSONB` types can store CipherCells but do not support encrypted queries. Operators [#operators] EQL provides PostgreSQL operators that work directly with encrypted data: ```sql -- Exact match SELECT * FROM users WHERE email = 'encrypted_search_value'::eql_v2_encrypted; -- Range queries SELECT * FROM products WHERE price > 'encrypted_value'::eql_v2_encrypted; -- Pattern matching SELECT * FROM documents WHERE content LIKE '%encrypted_pattern%'; ``` Functions [#functions] EQL includes functions for configuration, querying, and working with encrypted data: Configuration functions [#configuration-functions] Functions to set up and manage encrypted columns: * `eql_v2.add_column()` — Initialize a column for encryption * `eql_v2.add_search_config()` — Add searchable indexes to encrypted columns * `eql_v2.remove_column()` — Remove column configuration * `eql_v2.config()` — View current configuration Query functions [#query-functions] Functions for querying encrypted data (operator equivalents also available): * Equality checks: `eql_v2.encrypted_eq()` * Range comparisons: `eql_v2.encrypted_lt()`, `eql_v2.encrypted_gt()`, etc. * Pattern matching: `eql_v2.encrypted_like()`, `eql_v2.encrypted_ilike()` Index term extraction [#index-term-extraction] Functions to extract searchable terms from CipherCells: * `eql_v2.encrypted_get_hmac_256()` — Extract HMAC term for exact matches * `eql_v2.encrypted_get_bloom_filter()` — Extract Bloom filter for pattern matching * `eql_v2.encrypted_get_ore()` — Extract ORE term for range queries Index types [#index-types] EQL supports multiple searchable encryption index types. Each index type enables different query patterns: unique — Exact match [#unique--exact-match] Enables exact equality queries and unique constraints using HMAC-SHA256. [Learn more about exact indexes](/docs/platform/supported-queries#exact-match) ore — Range queries [#ore--range-queries] Enables range comparisons (`<`, `>`, `BETWEEN`) and ordering (`ORDER BY`) using Order Revealing Encryption. [Learn more about range indexes](/docs/platform/supported-queries#range--order) match — Pattern matching [#match--pattern-matching] Enables substring and full-text search (`LIKE`, `ILIKE`) using encrypted Bloom filters with trigrams. [Learn more about match indexes](/docs/platform/supported-queries#match-pattern) ste_vec — Structured data [#ste_vec--structured-data] Enables containment queries and JSON-style operations on encrypted arrays and JSONB data. How it works [#how-it-works] EQL leverages PostgreSQL's native indexing capabilities to enable efficient queries on encrypted data. The searchable encrypted metadata within [CipherCells](/docs/platform/cipher-cell) is indexed using standard PostgreSQL index types (B-tree for exact/range, GIN for pattern matching). When a query is executed: 1. **Client-side**: The application encrypts the search value using the same encryption scheme, producing a CipherCell with the appropriate searchable encrypted metadata 2. **Database-side**: EQL operators extract and compare the searchable encrypted metadata from both the stored CipherCells and the search CipherCell 3. **Result**: Matching rows are returned without ever decrypting the data in the database Compatibility [#compatibility] EQL is designed to work with: * **PostgreSQL 14+** — Full support for all EQL features * **Managed databases** — Works with AWS RDS, Azure Database, Google Cloud SQL, and other managed PostgreSQL providers * **CipherStash SDKs** — Integrates with all CipherStash SDKs as well as CipherStash Proxy Unlike PostgreSQL extensions that require `CREATE EXTENSION`, EQL types and functions are installed directly into your database schema, making it compatible with managed database environments that restrict extension installation. Related documentation [#related-documentation] * [CipherCell format](/docs/platform/cipher-cell) — The data structure used by EQL * [Supported queries](/docs/platform/supported-queries) — Available searchable encryption schemes # Platform Platform [#platform] The CipherStash platform consists of three core components that work together to provide continuous security for your applications and data. Core Components [#core-components] Workspaces [#workspaces] **What**: Isolated environments for your applications and configurations. **Contains**: Regional deployments, access keys, OIDC providers. **Purpose**: Environment isolation and configuration management. ZeroKMS [#zerokms] **What**: Zero-trust key management service. **Contains**: Client keys, key sets, cryptographic operations. **Purpose**: Secure key generation and management without key exposure. Organizations [#organizations] **What**: Multi-tenant management layer. **Contains**: Members, billing, cross-workspace policies. **Purpose**: Centralized governance and access control. Integration Paths [#integration-paths] For Applications [#for-applications] * **[Encryption SDK](/docs/encryption)**: Direct integration using workspace configuration * **[CipherStash Proxy](/docs/proxy)**: Transparent database encryption using platform authentication For Infrastructure [#for-infrastructure] * **Managed**: Use CipherStash Cloud with automatic scaling * **Self-hosted**: Deploy ZeroKMS in your own AWS environment (only available for Enterprise customers) Quick Start [#quick-start] Create an account and generate your credentials and keys in the [Dashboard](https://dashboard.cipherstash.com). Next steps [#next-steps] Concepts [#concepts] # Members Members [#members] There are two types of memberships in CipherStash: * Organization memberships * Workspace memberships Every user belongs to at least one organization, and can be invited to any number of workspaces inside that organization. Organization memberships [#organization-memberships] Organization memberships are used to manage which users in your organization have access to CipherStash in general. Once a user is added to an organization, they can be granted access to any number of workspaces inside that organization. Workspace memberships [#workspace-memberships] Workspace memberships are used to manage which users have access to a specific workspace. Once a user is added to a workspace, they have full access to managing the workspace, including creating and managing keysets, clients, and access keys. Adding a member to an organization [#adding-a-member-to-an-organization] To add a member to an organization, click the **Manage** button in the Dashboard navigation and select **Members**. Adding a member to a workspace [#adding-a-member-to-a-workspace] To add a member to a workspace, you can go to the [Workspace Settings](https://dashboard.cipherstash.com/workspaces/_/settings) page in the Dashboard and grant access to a user. # Searchable encryption Searchable encryption [#searchable-encryption] Traditional encryption (at-rest and in-transit) only protects data some of the time. To protect data fully, it must remain encrypted **in-use**. This ensures data remains protected even when other controls fail. Searchable encryption allows sensitive values to be encrypted but still queryable by trusted users. With searchable encryption: * Data can be encrypted, stored, and searched in your existing database * Encrypted data can be searched using equality, free text search, and range queries * Data remains encrypted, and is decrypted in your application * Queries are blazing fast, and won't slow down your application experience * Every decryption event is logged, giving you an audit trail of data access events Supported technologies [#supported-technologies] Searchable encryption is supported in PostgreSQL 14+ when used with: * [Encryption SDK](/docs/encryption) * [CipherStash Proxy](/docs/proxy) Vs field-level encryption [#vs-field-level-encryption] Often referred to as *field-level* or *row-level* encryption, encryption-in-use in the database has historically come with a number of challenges relating to key management and performance. Crucially, when values in a database table are encrypted using traditional approaches, queries over those records cease to function correctly. This is the core problem addressed by searchable encryption. | Capability | Field-Level Encryption | Searchable Encryption | | ----------------------------------------- | ---------------------- | --------------------- | | **Search over encrypted data** | Requires decryption | Native | | **Plaintext exposure during queries** | Yes | No | | **Where trust is required** | Application + database | Application only | | **Supports indexing** | No | Yes | | **Blast radius of credential compromise** | Large | Minimized | | **Provable access boundaries** | No | Yes | **Searchable encryption** protects sensitive data by ensuring that all search queries are encrypted before leaving the application. Comparisons are performed between encrypted query terms and stored encrypted records without needing to decrypt any values. Why make encryption searchable? [#why-make-encryption-searchable] Consider an everyday example. You have a table of users in your database, and you want to search for a user by their email address: ```sql -- Search by exact match SELECT * FROM users WHERE email = 'person@example.net'; ``` Whether you executed this query directly in the database, or through an application ORM, you'd expect the result to be the same. **But what if the email address is encrypted before it's stored in the database?** ```sql SELECT * FROM users; -- Results: -- | id | name | email -- |----+----------------+---------------------------- -- | 1 | Alice Johnson | mBbKmsMMkbKBSN... -- | 2 | Jane Doe | s1THy_NfQdN892... -- | 3 | Bob Smith | 892!dercydsd0s... ``` Now, what's the issue if you execute the equality query with this data set? ```sql SELECT * FROM users WHERE email = 'alice.johnson@example.com'; -- No results ``` There would be no results returned, because `alice.johnson@example.com` does not equal `mBbKmsMMkbKBSN...`! Another problem arises when *sorting* query results. Results would be ordered by the *encryption* of each value and not by the underlying plaintext data. Queries over encrypted data [#queries-over-encrypted-data] Searchable encryption solves these problems by encrypting query terms in a way that allows them to match encrypted database values *without revealing the underlying plaintext*. When you use CipherStash searchable encryption, your application encrypts the search term before sending it to the database. The encrypted query term can then be compared against the encrypted stored values, enabling correct query results while keeping everything encrypted. Supported query types [#supported-query-types] CipherStash searchable encryption supports several types of queries over encrypted data: * **Equality queries**: Exact matching (e.g., `WHERE email = `) * **Free text search**: Pattern matching and substring searches (e.g., `WHERE description LIKE `) * **Range queries**: Comparison operations (e.g., `WHERE age > `) * **Ordering**: Sorting encrypted data (e.g., `ORDER BY price DESC`) See [Supported Queries](/docs/platform/supported-queries) for a complete list, implementation details and limitations. Isn't this just hashing? [#isnt-this-just-hashing] At first glance, searchable encryption might seem similar to hashing — both allow you to compare values without revealing the original data. However, there are critical differences that make searchable encryption far more powerful and secure. **Hashing has significant limitations:** * **Public function**: Standard hashing algorithms (SHA-256, bcrypt, etc.) are public and unkeyed, meaning anyone can compute hashes and compare them against your database * **Rainbow table attacks**: Because hashes are deterministic and publicly computable, attackers can pre-compute hashes of common values and match them against your database * **Limited query types**: Hashing only supports exact equality checks — you cannot perform range queries (`>`, `<`), ordering (`ORDER BY`), or pattern matching (`LIKE`) * **Irreversible**: Once hashed, data cannot be decrypted for legitimate use in your application **Searchable encryption provides stronger protection:** * **Keyed functions**: CipherStash uses keyed cryptographic functions (like HMAC) for generating search terms, meaning attackers cannot generate valid search terms without access to your encryption keys * **Randomized encryption**: The actual encrypted data uses randomness, so the same plaintext produces different ciphertexts each time it's encrypted * **Rich queries**: Supports equality, range queries, ordering, and pattern matching while keeping data encrypted * **Decryptable**: Authorized applications can decrypt values for legitimate use, with every decryption logged for audit purposes * **Minimized information leakage**: While some information leakage is inherent to any searchable scheme (you need to match values to search them), the use of secret keys prevents direct attacks How it works [#how-it-works] Searchable encryption is fundamentally about the interaction between a trusted client and an untrusted (or semi-trusted) server. The client converts query terms (such as keywords or JSON paths) into cryptographic search tokens using keys that never leave the client or its trusted boundary. The server operates only on these opaque tokens, matching them against encrypted metadata to identify relevant records and return encrypted results. To query a set of encrypted values, a special query term is generated and encrypted using a key that only the client controls. A simple and secure way to do this is using a Hash-based Message Authentication Code (HMAC). Values are encrypted using standard randomized encryption (AES-GCM-SIV with 256-bit keys) and stored in the database along with their HMAC: ```sql id | name ----+--------------------------------------- 1 | {"e": "U9QKapIGP9rQZINK", "hm": "EDV6F1loviSFpbFOLhC2DPLT/jOYjt03Bv/DbqWiQdw"} 2 | {"e": "v0QmT6XcuMenl+Cy", "hm": "0uZMw7qI0ztSL6Ra1E8QkdzfJMHvHyiMxPJK373dV88"} 3 | {"e": "7aOcdrGXu9b+6h1u", "hm": "crMCp//4GMayU6ulBlGZRu2Gm/f+zIODBzCYdWcxbok"} ``` To query the data we generate the HMAC of the query term and use it to find records with matching HMAC values. For example, to find all users who have the name `Dan`, we'd generate the HMAC of the string `"Dan"`: ```sql SELECT name->e FROM users WHERE name->hm = '0uZMw7qI0ztSL6Ra1E8QkdzfJMHvHyiMxPJK373dV88'; ``` CipherStash uses a strict JSON schema called a [CipherCell](/docs/platform/cipher-cell) which is covered in the next section. **HMAC is a keyed hash** Unlike raw hash functions (such as SHA-256), HMAC requires a secret key. This means only parties with the key can generate valid HMACs, preventing attackers from pre-computing hash tables (rainbow tables) or guessing values. The key stays client-side, so the server can match encrypted search tokens without ever learning the plaintext or being able to generate new tokens. Architectural elements [#architectural-elements] CipherStash searchable encryption combines a number of different components: | Component | Description | | ---------------------------- | ----------------------------------------------------------------------------------------- | | CipherCell | Storage format for encrypted data + metadata | | Encrypt Query Language (EQL) | Types, operators, and functions for querying encrypted data | | ZeroKMS | High-performance key management service designed for searchable encryption | | Client | [CipherStash Proxy](/docs/proxy) or an app running the [Encryption SDK](/docs/encryption) | A query over encrypted data [#a-query-over-encrypted-data] To help explain the flow of a query using searchable encryption let's use an example. In this case the client will be a Node.js application using the Encryption SDK and the server will be a PostgreSQL database with EQL types installed. A query over encrypted data follows these steps: 1. Client encrypts the query via ZeroKMS [#1-client-encrypts-the-query-via-zerokms] The client application (or proxy) first encrypts the query terms using ZeroKMS. For example, to search for users with the name `"Dan"`, the client generates an HMAC of the search term using encryption keys managed by ZeroKMS. ```typescript title="encrypt.ts" import { encryption } from "./encryption"; import { users } from "./schema"; // The client encrypts the query term const encryptResult = await encryption.encrypt("Dan", { column: users.name, table: users, }); if (encryptResult.failure) { console.error("Encryption failed:", encryptResult.failure.message); } // The CipherCell contains the HMAC for searching const hmac = encryptResult.data.hm; // Returns: "0uZMw7qI0ztSL6Ra1E8QkdzfJMHvHyiMxPJK373dV88" ``` 2. Client generates SQL with encrypted query [#2-client-generates-sql-with-encrypted-query] The client uses the encrypted query token to construct an SQL statement. ```sql SELECT name->>'e' as encrypted_name FROM users WHERE name->>'hm' = '0uZMw7qI0ztSL6Ra1E8QkdzfJMHvHyiMxPJK373dV88'; ``` The database receives only the encrypted search token — it never sees the plaintext query `"Dan"`. 3. Database executes query and returns encrypted results [#3-database-executes-query-and-returns-encrypted-results] The database executes the query by matching the encrypted search token against the HMAC values stored in CipherCells. Matching records are identified and the database returns the encrypted ciphertexts (the `e` field) to the client. ```json { "encrypted_name": "v0QmT6XcuMenl+Cy" } ``` 4. Client decrypts data via ZeroKMS [#4-client-decrypts-data-via-zerokms] Finally, the client decrypts the returned CipherCells using ZeroKMS to reveal the plaintext data. ```typescript title="decrypt.ts" import { encryption } from "./encryption"; // Decrypt the CipherCell returned from the database const decryptResult = await encryption.decrypt({ c: "v0QmT6XcuMenl+Cy", // ... }); if (decryptResult.failure) { console.error("Decryption failed:", decryptResult.failure.message); } const plaintext = decryptResult.data; // Returns: "Dan" ``` Security model [#security-model] Under this security model: * The server can perform search operations, but cannot read data or queries * Keys remain entirely client-controlled, ensuring a strong trust boundary Leakage is tightly constrained to what is inherent to enabling search (e.g., whether two encrypted tokens are equal), and can be further minimised using advanced constructions such as structured encryption, per-record keys, or oblivious search patterns. This model allows applications to offer rich, real-time search over encrypted data while preserving strong privacy: the server executes the query, but only the client understands it. Performance [#performance] Based on some [benchmarks](https://github.com/cipherstash/tfhe-ore-bench?tab=readme-ov-file#results) CipherStash's approach is **410,000x faster** than homomorphic encryption: | Operation | Homomorphic | CipherStash | Speedup | | --------- | ----------- | ----------- | ---------- | | Encrypt | 1.97 ms | 48 µs | \~41x | | `a == b` | 111 ms | 238 ns | \~466,000x | | `a > b` | 192 ms | 238 ns | \~807,000x | | `a < b` | 190 ms | 240 ns | \~792,000x | | `a >= a` | 44 ms | 221 ns | \~199,000x | | `a <= a` | 44 ms | 226 ns | \~195,000x | Next steps [#next-steps] * Learn about the [CipherCell](/docs/platform/cipher-cell) storage format * Review [supported queries](/docs/platform/supported-queries) * Understand the [Encrypt Query Language (EQL)](/docs/platform/eql) # Security architecture Security architecture [#security-architecture] This page is the single reference for CipherStash's cryptographic design. It covers the key hierarchy, encryption primitives, trust model, and data flow so security teams can evaluate CipherStash without piecing together information from multiple sources. Cryptographic primitives [#cryptographic-primitives] | Purpose | Algorithm | Details | | ------------------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Data encryption | AES-GCM-SIV | 256-bit keys, authenticated encryption with nonce-misuse resistance | | Equality search tokens | HMAC-SHA-256 | Deterministic tokens for exact-match lookups | | Range and sorting indexes | Block ORE | Order-Revealing Encryption ([Lewi-Wu 2016](https://eprint.iacr.org/2016/612)) with security enhancements from [Bogatov et al 2018](https://arxiv.org/abs/1810.05135) | | Free-text search indexes | Encrypted Bloom filters | Trigram-based tokenization ([Nojima/Kadobayashi 2009](https://link.springer.com/chapter/10.1007/978-3-642-04474-8_17), [Chum/Zhang 2017](https://link.springer.com/chapter/10.1007/978-3-319-66399-9_15)) | | Ciphertext integrity | Blake3 | Fast cryptographic hash for CipherCell structure validation | | Ciphertext encoding | MessagePack + Base85 | Compact binary serialization stored as JSONB in PostgreSQL | All encryption happens client-side in the application process. CipherStash infrastructure never sees plaintext data. Key hierarchy [#key-hierarchy] CipherStash uses a **distributed trust model** with a dual-party key split. No single party — including CipherStash — can derive data encryption keys alone. Key types [#key-types] | Key | Where it lives | Purpose | | ----------------- | ----------------------------------- | ----------------------------------------------------------------- | | **Authority key** | ZeroKMS (server-side, HSM-friendly) | Server half of the key derivation input | | **Client key** | Your application / workload | Client half of the key derivation input (`CS_CLIENT_KEY` env var) | | **Data key** | Ephemeral — in-memory only | Per-value encryption key, derived from both halves, never stored | Key derivation [#key-derivation] ZeroKMS uses an **HMAC-based key derivation scheme**: 1. The SDK sends a key derivation request to ZeroKMS with the client key component. 2. ZeroKMS combines the authority key and client key to produce a **key seed**. 3. The SDK uses the key seed to derive a unique data encryption key locally. 4. The data key encrypts the value using AES-GCM-SIV, then is immediately discarded. Data keys are **ephemeral** — they are never stored, cached, or transmitted. This eliminates wrapped-key storage overhead and removes the risk of key cache compromise. Why this matters [#why-this-matters] | Traditional KMS | CipherStash ZeroKMS | | --------------------------------------- | --------------------------------------------------------------------------- | | Wrapped data keys stored alongside data | Data keys are ephemeral — never stored | | Key caching required for performance | No caching needed — HMAC derivation at 10,000+ ops/sec per node | | Single point of trust (KMS provider) | Distributed trust — both authority key and client key required | | Compromising KMS exposes all data keys | Compromising ZeroKMS alone is insufficient — attacker also needs client key | Trust model [#trust-model] CipherStash implements a **zero-knowledge architecture**: * **CipherStash never sees plaintext data.** All encryption and decryption happens in your application using the native Rust FFI module. * **CipherStash never sees unwrapped data keys.** Data keys are derived ephemerally and exist only in the client's memory. * **Both parties are required.** An attacker must compromise both the ZeroKMS authority key and the client key to derive data keys. Shared responsibility [#shared-responsibility] | Your responsibility | CipherStash responsibility | | ---------------------------------------------- | ----------------------------------------------------- | | Protect the client key (`CS_CLIENT_KEY`) | Protect authority keys in ZeroKMS (HSM-backed) | | Secure your application and database | Operate ZeroKMS infrastructure with high availability | | Manage access keys and keysets | Enforce access control policies on keysets | | Configure identity providers for lock contexts | Operate the CipherStash Token Service (CTS) | | Store encrypted data in your database | Never store, access, or log plaintext data | Blast radius [#blast-radius] CipherStash uses [keysets](/docs/kms/keysets) to scope encryption keys: * Each keyset provides **full cryptographic isolation**. Tenant A's keyset cannot decrypt Tenant B's data. * Compromising a single client key affects only the keysets that client has access to. * Revoking a client's access key immediately prevents further key derivation requests. Data flow [#data-flow] Encryption (write path) [#encryption-write-path] 1. Application calls `client.encrypt(plaintext, { column, table })`. 2. SDK sends a key derivation request to ZeroKMS (over TLS). 3. ZeroKMS combines authority key + client key → returns key seed. 4. SDK derives a unique data key from the seed locally. 5. SDK encrypts the plaintext using AES-GCM-SIV with the data key. 6. SDK generates search index tokens (HMAC, ORE, Bloom filter) if the column has indexes configured. 7. SDK packages ciphertext + index tokens into a [CipherCell](/docs/platform/cipher-cell) (JSON payload). 8. Data key is discarded from memory. 9. Application stores the CipherCell in PostgreSQL (as `eql_v2_encrypted` or `jsonb`). Decryption (read path) [#decryption-read-path] 1. Application reads the CipherCell from PostgreSQL. 2. Application calls `client.decrypt(encryptedData)`. 3. SDK sends a key derivation request to ZeroKMS. 4. ZeroKMS combines authority key + client key → returns key seed. 5. SDK derives the data key and decrypts the ciphertext. 6. Data key is discarded from memory. 7. Plaintext is returned to the application. Search (query path) [#search-query-path] 1. Application calls `client.encryptQuery(searchTerm, { column, table, queryType })`. 2. SDK generates search tokens (HMAC for equality, ORE for range, Bloom filter for free-text) using a key derived from ZeroKMS. 3. Application sends the encrypted tokens to PostgreSQL. 4. PostgreSQL's [EQL extension](/docs/platform/eql) compares encrypted tokens using the appropriate index — the database never sees plaintext. Searchable encryption security properties [#searchable-encryption-security-properties] Searchable encryption enables queries on encrypted data but involves inherent security tradeoffs. Each index type reveals specific, bounded information to the database server. | Index type | What is revealed to the database | Mitigation | | ---------------------------- | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | **HMAC (equality)** | Whether two encrypted values are identical (frequency information) | Per-column unique keys prevent cross-column correlation | | **ORE (range/sort)** | Relative ordering of encrypted values | Block ORE limits leakage to block-level ordering ([Lewi-Wu 2016](https://eprint.iacr.org/2016/612)) | | **Bloom filter (free-text)** | Probabilistic token membership with configurable false positive rate | Parameters `k` and `m` control the tradeoff between search accuracy and information leakage | When to use searchable encryption [#when-to-use-searchable-encryption] * **Use it** when you need to query encrypted data and the leakage profile is acceptable for your threat model. * **Don't use it** when the mere ordering or frequency of values is sensitive (e.g., exact salary rankings). In those cases, encrypt without indexes and decrypt application-side. For a deeper analysis of each index type, see [Searchable encryption](/docs/platform/searchable-encryption) and [Supported queries](/docs/platform/supported-queries). Network security [#network-security] All communication between the SDK and CipherStash services uses **TLS 1.2+** over HTTPS: * SDK → ZeroKMS: Key derivation requests over HTTPS * SDK → CTS: Token exchange requests over HTTPS (for identity-aware encryption) * SDK → your database: Your existing database connection (CipherStash does not proxy this) CipherStash services are deployed across [multiple AWS regions](/docs/kms/regions). Key material does not leave the region your workspace is configured in. Open-source components [#open-source-components] | Component | Repository | License | | ---------------------------- | ------------------------------------------------------------------------------------------------------ | ----------- | | EQL (Encrypt Query Language) | [github.com/cipherstash/encrypt-query-language](https://github.com/cipherstash/encrypt-query-language) | Open source | | ORE implementation | [github.com/cipherstash/ore.rs](https://github.com/cipherstash/ore.rs) | Open source | | CipherStash Proxy | [github.com/cipherstash/proxy](https://github.com/cipherstash/proxy) | Open source | The core cryptographic implementations (ORE, Bloom filter construction) are open source and independently auditable. ZeroKMS is a managed service operated by CipherStash. Further reading [#further-reading] * [What is CipherStash?](/docs/platform/what-is-cipherstash) — platform overview and threat model * [ZeroKMS](/docs/kms) — key management and keyset isolation * [CipherCell format](/docs/platform/cipher-cell) — encrypted payload structure * [Disaster recovery](/docs/kms/disaster-recovery) — key recovery and outage behavior # Supported queries Supported queries [#supported-queries] CipherStash provides three core index types, each optimized for specific use cases: | Index Type | Description | Example Operators | | ----------------------- | ------------------------------------------- | ------------------------------------------- | | [Exact](#exact-match) | Equality comparisons and unique constraints | `=`, `!=`, `IN`, `NOT IN` | | [Match](#match-pattern) | Substring and pattern matching | `LIKE`, `ILIKE`, `~~`, `~~*` | | [Range](#range--order) | Comparison operations and ordering | `<`, `<=`, `>`, `>=`, `BETWEEN`, `ORDER BY` | You can configure multiple index types on the same field to support different query patterns. For example, a field might have both `Exact` and `Match` indexes to support both equality checks and substring searches. Choosing index types [#choosing-index-types] When designing your schema, consider what queries you'll need to perform: | Query pattern | Required index type | | --------------------------------------- | ------------------- | | `WHERE email = 'user@example.com'` | Exact | | `WHERE name LIKE '%Smith%'` | Match | | `WHERE price < 100` | Range | | `WHERE status IN ('active', 'pending')` | Exact | | `ORDER BY created_at DESC` | Range | | `UNIQUE` constraint | Exact or Range | CipherCell storage [#ciphercell-storage] Each index type stores its encrypted search terms in a specific key within the [CipherCell](/docs/platform/cipher-cell) structure: * `hm`: Exact index terms (HMAC) * `bf`: Match index terms (Bloom filter) * `ob`: Range/Order index terms (ORE blocks) A single encrypted field can contain multiple index types in its CipherCell, allowing it to support multiple query patterns simultaneously. *** Exact match [#exact-match] Exact indexes use HMAC-SHA256 to enable equality comparisons on encrypted data. Supported operators [#supported-operators] = — Equality [#--equality] Returns true if both operands are equal. ```sql SELECT * FROM users WHERE email = $1::eql_v2_encrypted; ``` != — Negated equality [#--negated-equality] Returns true if operands are *not* equal. ```sql SELECT * FROM users WHERE email != $1::eql_v2_encrypted; ``` IN — Array comparison [#in--array-comparison] Returns true if the left-hand expression's result is equal to any of the right-hand expressions. ```sql SELECT * FROM users WHERE state IN ($1::eql_v2_encrypted, $2::eql_v2_encrypted); ``` `NOT IN`, `ANY` and `SOME` are also supported. JOIN — Table joins on encrypted columns [#join--table-joins-on-encrypted-columns] ```sql SELECT orders.id, orders.amount, users.name FROM orders JOIN users ON orders.user_email = users.email WHERE orders.amount > 100; ``` GROUP BY — Grouping by encrypted columns [#group-by--grouping-by-encrypted-columns] ```sql SELECT state, COUNT(*) as user_count FROM users GROUP BY state; ``` CipherCell key [#ciphercell-key] `Exact` indexes are stored in the `hm` key of a [CipherCell](/docs/platform/cipher-cell). Database indexes [#database-indexes] PostgreSQL B-tree indexes work with `Exact`. ```sql CREATE INDEX ON users ( encrypted_column eql_v2.encrypted_operator_class ); ``` Unique indexes [#unique-indexes] `UNIQUE` indexes require either `Exact` or `Range` searchable encrypted indexes to be available on the CipherCell. ```sql CREATE UNIQUE INDEX ON users ( encrypted_column eql_v2.encrypted_operator_class ); ``` Cryptography [#cryptography] `Exact` terms are generated using [HMAC](/docs/glossary#hmac-hash-based-message-authentication-code), specifically HMAC-SHA-256 which uses SHA with an output size of 32-bytes as the underlying hash function. *** Match (pattern) [#match-pattern] `Match` indexes use trigrams (three-character sequences) to enable substring matching on encrypted data. When a value is encrypted, trigrams are extracted and stored in an encrypted Bloom filter, allowing for efficient pattern matching without decrypting the data. Trigrams work best for matching patterns that are at least 3 characters long. Very short search patterns may produce more false positives. For example, the string "laptop" produces the following trigrams: * ` l` (note the leading spaces) * ` la` * `lap` * `apt` * `pto` * `top` * `op ` (note the trailing space) When searching for "apt" in an encrypted field, the system checks if the encrypted Bloom filter contains the `apt` trigram. Text processing pipeline [#text-processing-pipeline] Before text is indexed in an encrypted Bloom filter, it passes through a configurable processing pipeline on the client side. This pipeline determines how text is tokenized and normalized, which directly affects query behavior. Tokenizers [#tokenizers] Tokenizers break text into searchable units: **Standard** (Unicode whitespace): Splits text on whitespace boundaries. ``` "Hello World" → ["Hello", "World"] ``` **Ngram** (default: trigram): Generates character n-grams from text. ``` "laptop" → [" l", " la", "lap", "apt", "pto", "top", "op "] ``` **EdgeGram**: Generates n-grams from the beginning of text, useful for prefix matching. Accepts `min` and `max` gram size. ``` "search" with min=1, max=6 → ["s", "se", "sea", "sear", "searc", "search"] ``` Token filters [#token-filters] Token filters normalize tokens after tokenization: * **Upcase**: Converts all text to uppercase * **Downcase**: Converts all text to lowercase * **Stemmer**: Reduces words to their root form (`"running"` → `"run"`) * **Stop Word Removal**: Filters out common words like "a", "an", "the", "is", "it", etc. Pipeline impact on queries [#pipeline-impact-on-queries] If a **downcase** token filter is configured in the pipeline, both `LIKE` and `ILIKE` will perform case-insensitive searches. The distinction between `LIKE` (case-sensitive) and `ILIKE` (case-insensitive) only applies when no case-normalizing filters are used. Supported operators [#supported-operators-1] LIKE — Pattern matching [#like--pattern-matching] ```sql SELECT * FROM products WHERE name LIKE $1::eql_v2_encrypted; ``` ILIKE — Case-insensitive pattern matching [#ilike--case-insensitive-pattern-matching] ```sql SELECT * FROM products WHERE description ILIKE $1::eql_v2_encrypted; ``` ~~ — Equivalent to LIKE [#--equivalent-to-like] ```sql SELECT * FROM documents WHERE title ~~ $1::eql_v2_encrypted; ``` ~~* — Equivalent to ILIKE [#--equivalent-to-ilike] ```sql SELECT * FROM documents WHERE title ~~* $1::eql_v2_encrypted; ``` LIKE and ILIKE mapping [#like-and-ilike-mapping] Encrypted Bloom filters don't directly implement the SQL `LIKE` or `ILIKE` operators. Instead, they perform token-based matching using the configured text processing pipeline. CipherStash Proxy and the Encryption SDK automatically map `LIKE` and `ILIKE` queries to encrypted Bloom filter searches. This mapping works by: 1. Extracting the search pattern from the `LIKE` expression (ignoring `%` wildcards) 2. Processing the pattern through the same text pipeline (tokenizer + filters) 3. Checking if the resulting tokens exist in the encrypted Bloom filter Limitations [#limitations] * **Very short patterns (1-2 characters)**: Patterns shorter than 3 characters may produce different results because trigrams require at least 3 characters * **Wildcard-only patterns**: Patterns consisting only of wildcards cannot be mapped * **Character class patterns**: SQL `LIKE` supports `_` (single character) wildcards, but encrypted Bloom filters don't * **Position-specific matching**: While prefix and suffix searches generally work, the behavior is trigram-based rather than position-exact CipherCell key [#ciphercell-key-1] `Match` indexes are stored in the `bf` key of a [CipherCell](/docs/platform/cipher-cell). Database indexes [#database-indexes-1] PostgreSQL GIN (Generalized Inverted Index) indexes work with `Match`. ```sql CREATE INDEX products_encrypted_gin_index ON products USING GIN ( eql_v2.bloom_filter(encrypted_column) ); ``` `Match` indexes use GIN rather than B-tree indexes. GIN indexes are specifically designed for indexing composite values and are ideal for full-text and pattern matching operations. `UNIQUE` indexes are not supported for `Match` indexes, as Bloom filters are probabilistic data structures that cannot guarantee uniqueness. Use [Exact](#exact-match) or [Range](#range--order) indexes if you need unique constraints. Cryptography [#cryptography-1] `Match` terms are generated using encrypted Bloom filters, which enable boolean retrieval operations over encrypted data. The system extracts trigrams from the plaintext, encrypts each trigram using a keyed pseudorandom function, and stores them in a Bloom filter structure. Academic references [#academic-references] * [Cryptographically Secure Bloom-Filters](https://www.tdp.cat/issues/abs.a015a09.php) (Nojima, Kadobayashi 2009) * [A New Bloom Filter Structure for Searchable Encryption Schemes](https://www.cs.csi.cuny.edu/~zhangx/papers/P_2017_Codaspy_Chum_Zhang.pdf) (Chum, Zhang 2017) *** Range / Order [#range--order] Range indexes use Order Revealing Encryption (ORE), which allows comparison operations on encrypted data while preserving order relationships. Supported operators [#supported-operators-2] < — Less than [#--less-than] ```sql SELECT * FROM products WHERE price < $1::eql_v2_encrypted; ``` <= — Less than or equal [#--less-than-or-equal] ```sql SELECT * FROM products WHERE price <= $1::eql_v2_encrypted; ``` > — Greater than [#--greater-than] ```sql SELECT * FROM orders WHERE order_date > $1::eql_v2_encrypted; ``` >= — Greater than or equal [#--greater-than-or-equal] ```sql SELECT * FROM orders WHERE order_date >= $1::eql_v2_encrypted; ``` BETWEEN — Range comparison [#between--range-comparison] ```sql SELECT * FROM products WHERE price BETWEEN $1::eql_v2_encrypted AND $2::eql_v2_encrypted; ``` ORDER BY — Sort results [#order-by--sort-results] If applied to a string type, ordering is applied *lexicographically* (i.e. ordered by comparing elements left-to-right, like words in a dictionary). ```sql SELECT * FROM products ORDER BY price ASC; ``` CipherCell key [#ciphercell-key-2] `Order` / `Range` indexes are stored in the `ob` key of a [CipherCell](/docs/platform/cipher-cell). Database indexes [#database-indexes-2] PostgreSQL B-tree indexes work with `Order` / `Range`. ```sql CREATE INDEX ON products ( encrypted_column eql_v2.encrypted_operator_class ); ``` `UNIQUE` indexes require either `Exact` or `Range` searchable encrypted indexes to be available on the CipherCell. Cryptography [#cryptography-2] `Order` / `Range` terms are generated using [Order Revealing Encryption](/docs/glossary#ore-order-revealing-encryption) (ORE), which allows comparison operations on encrypted data while preserving order relationships. This enables efficient range queries and sorting on encrypted columns without decrypting the data. Academic references [#academic-references-1] The specific scheme used is based on Block ORE ([Lewi-Wu 2016](https://eprint.iacr.org/2016/612.pdf)) and incorporates security enhancements identified by [Bogatov et al 2018](https://eprint.iacr.org/2018/953.pdf). The implementation is available on [GitHub](https://github.com/cipherstash/ore.rs). # What is CipherStash? What is CipherStash? [#what-is-cipherstash] CipherStash is field-level encryption you can query without decryption, with cryptographically verifiable audit trails and key management up to 14x faster than AWS KMS. It lets you build secure features in days, not months. Why it matters [#why-it-matters] **The problem**: Organizations shouldn't have to choose between protecting their data or using it to drive growth. Traditional security tools are complex, slow down development, and create trade-offs between security and usability. **The solution**: CipherStash sees a better way to continuously secure data and unlock its value through developer-friendly tools for fine-grained, provable access control. How it works [#how-it-works] CipherStash implements a zero-knowledge architecture where the platform and vendors never see data keys. The system uses unique data keys per value for precise, per-item encryption and access control, with lock contexts that enforce attribute-based access control using cryptographic proofs. Core components [#core-components] **CipherStash ZeroKMS**: Fine-grained, identity-based control over data access, backed by real-time audit logs and scalable performance. **Integration options**: * **[Encryption SDK](/docs/encryption)**: JavaScript/TypeScript library for direct application integration * **[CipherStash Proxy](/docs/proxy)**: Drop-in SQL proxy with zero code changes required Key capabilities [#key-capabilities] * **Searchable encryption**: Run queries directly on encrypted data, enabling analysis and use of sensitive information without exposure * **Granular, identity-bound access controls**: Ensure only authorized individuals can access data — eliminating shared credentials and improving security posture * **Real-time auditability**: Log every data access with full context, helping meet SOC2, HIPAA, and GDPR standards while building customer trust * **Instant access revocation**: Revoke data access immediately without waiting on vendors — full control over data relationships and supply chains * **Flexible key management**: Support hybrid, cloud, and mobile deployments beyond traditional hardware security modules Threat model [#threat-model] CipherStash addresses several key security threats: **Data breach protection**: Even if database credentials are compromised, encrypted data remains protected through zero-knowledge architecture. **Insider threats**: Granular access controls prevent unauthorized access by internal users, with full audit trails for compliance. **Supply chain attacks**: Instant access revocation capabilities allow immediate response to compromised third-party vendors. **Compliance gaps**: Real-time auditability ensures continuous compliance with SOC2, HIPAA, and GDPR requirements. **Performance trade-offs**: Minimal latency impact (\< 5ms overhead) while maintaining 14x faster performance than AWS KMS for cryptographic operations. Comparisons [#comparisons] **vs. Traditional KMS**: CipherStash eliminates centralized key storage vulnerabilities while providing 14x better performance for high-throughput environments. **vs. Point-in-Time Compliance**: Unlike SOC2 audits that reflect security at a moment, CipherStash provides continuous assurance and real-time auditability. **vs. Database-Level Encryption**: CipherStash offers application-layer encryption with searchable capabilities, unlike transparent database encryption that limits query functionality. **vs. Homomorphic Encryption**: CipherStash provides practical searchable encryption with minimal performance overhead, suitable for production applications. Product interaction [#product-interaction] * **CipherStash ZeroKMS** serves as the foundation, providing fine-grained, identity-based control over data access with real-time audit logs. * **CipherStash Encryption SDK** enables direct integration for fast-moving engineering teams, with most teams up and running in days, not months. * **CipherStash Proxy** offers drop-in SQL proxy functionality for PostgreSQL, requiring zero code changes and available through AWS Marketplace. Commercial impact [#commercial-impact] By implementing CipherStash, organizations can: * **Expand into regulated markets**: Securely support financial transactions or healthcare records, opening doors to compliance-sensitive customers * **Build customer trust**: Provide enterprise clients with verifiable control over their data through granular access controls and audit logs * **Accelerate sales cycles**: Meet enterprise compliance requirements without months of security reviews * **Reduce compliance costs**: Automate audit processes and eliminate manual compliance reporting # Audit features Audit features [#audit-features] CipherStash Proxy provides comprehensive data access auditing for PostgreSQL. These features work transparently — no changes to your SQL or application code are required. Statement fingerprinting [#statement-fingerprinting] Statement fingerprints identify unique SQL statements by examining the raw parse tree using [pg\_query](https://pganalyze.com/blog/pg-query-2-0-postgres-query-parser#fingerprints-in-pg_query-a-better-way-to-check-if-two-queries-are-identical). Fingerprints ignore query differences when they result in the same query intent. They are unique across environments and time, providing a useful mechanism for identifying query patterns. **Example** | SQL | Fingerprint | | -------------------- | ------------------ | | `SELECT a, b FROM c` | `fb1f305bea85c2f6` | | `SELECT b, a FROM c` | `fb1f305bea85c2f6` | Both queries produce the same fingerprint because they access the same columns from the same table — the column order doesn't affect the intent. Statement redaction [#statement-redaction] The statement SQL is redacted before being included in an event payload. All static values in the SQL string are stripped. Table names, column names, and function names are retained. If parsing fails or another issue prevents redaction, the statement will not be transmitted. Most PostgreSQL libraries and frameworks default to using parameterized statements and the PostgreSQL Extended Protocol, in which case values will not be included in the SQL. **Example** | Statement SQL | Redacted SQL | | ----------------------------------- | ------------------------------------------ | | `SELECT a, b FROM c` | `SELECT a, b FROM c` | | `SELECT a, b FROM c WHERE id = '1'` | `SELECT a, b FROM c WHERE id = {REDACTED}` | Primary key injection [#primary-key-injection] Primary key injection connects SQL statements to record identifiers. It transparently ensures that any data access event includes the actual record identifiers. No need to instrument or change your SQL. CipherStash Proxy uses the database schema to identify SQL statements that do not reference a primary key. It injects the missing primary keys into the SQL before execution. Primary keys of accessed records can then be tracked with the data access event. The performance impact on the database is negligible as the primary key is by definition indexed, and the referenced tables are already present in the SQL statement. When combined with [identity-aware encryption](/docs/encryption/identity), the events are also linked to client identity, providing an end-to-end view of data access. Record reconciliation [#record-reconciliation] Record reconciliation extracts the record identifiers, maps them to the appropriate tables, and includes them in the data access event payload sent to Audit. Injected primary keys are always removed from the SQL results before being returned to the client. The process is internal to CipherStash Proxy — the format of the result set always matches the original query. How it works together [#how-it-works-together] These four features form a pipeline: 1. **Fingerprint** — identify the query pattern 2. **Redact** — strip sensitive values from the SQL statement 3. **Inject** — add primary key references to track which records are accessed 4. **Reconcile** — extract record identifiers and remove injected keys from results The result is a complete data access event containing: * **What query** was executed (fingerprint + redacted SQL) * **Which records** were accessed (reconciled primary keys) * **Who** executed it (when combined with identity-aware encryption) * **When** it happened (timestamp) All of this happens transparently within the proxy. Your application receives unmodified query results. # Deploying to AWS ECS Deploying CipherStash Proxy to AWS ECS [#deploying-cipherstash-proxy-to-aws-ecs] Prerequisites [#prerequisites] * **AWS Account**: An active [AWS account](https://aws.amazon.com/account/) * **AWS CLI**: Installed and configured with appropriate permissions ([install guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)) * **Docker**: [Installed](https://docs.docker.com/engine/install/) if you need to push the image to ECR * **CipherStash Proxy config**: Refer to the [Proxy config reference](https://github.com/cipherstash/proxy/blob/main/docs/reference/index.md) * **AWS RDS instance**: A [PostgreSQL RDS instance](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html) in the same VPC as your ECS cluster Sign up to CipherStash [#sign-up-to-cipherstash] Create a CipherStash account and generate credentials: ```bash # Install the CipherStash CLI ## macOS brew install cipherstash/tap/stash ## Linux ## Download from https://github.com/cipherstash/cli-releases/releases/latest # Setup your CipherStash configuration stash setup --proxy # Outputs credentials to .env.proxy.docker ``` Note down the credentials — you will need these in later steps. Prepare your Docker image [#prepare-your-docker-image] Docker images are available from: * [AWS Marketplace](https://aws.amazon.com/marketplace/pp/prodview-x4kffsfxcrreg) * [Docker Hub](https://hub.docker.com/r/cipherstash/proxy) If using Docker Hub, push the image to Amazon ECR: ```bash # Ensure you have set these environment variables: # export AWS_ACCOUNT_ID=111222333444 # export AWS_REGION=ap-southeast-2 set -u aws ecr create-repository --repository-name cipherstash-proxy aws ecr get-login-password | docker login \ --username AWS \ --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com docker tag cipherstash/proxy:latest \ $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/cipherstash-proxy:latest docker push \ $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/cipherstash-proxy:latest ``` Create secrets [#create-secrets] Using the credentials from Step 1, create `cipherstash-proxy-secrets.json`: ```json { "CS_WORKSPACE_ID": "...", "CS_CLIENT_ID": "...", "CS_DEFAULT_KEYSET_ID": "...", "CS_CLIENT_KEY": "...", "CS_CLIENT_ACCESS_KEY": "...", "CS_DATABASE__PASSWORD": "..." } ``` The value of `CS_DATABASE__PASSWORD` is the password of your PostgreSQL RDS instance. Create the secret in Secrets Manager: ```bash aws secretsmanager create-secret \ --name cipherstash-proxy \ --secret-string file://cipherstash-proxy-secrets.json ``` Note the ARN of the secret — you will need it for the task definition. Set up IAM roles and permissions [#set-up-iam-roles-and-permissions] Trust policy [#trust-policy] Create `ecs-tasks-trust-policy.json`: ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "ecs-tasks.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } ``` Create the IAM role: ```bash aws iam create-role \ --role-name ecsTaskExecutionRole \ --assume-role-policy-document file://ecs-tasks-trust-policy.json ``` Inline policy [#inline-policy] Create `cipherstash-proxy-ecs-policy.json`, substituting the ARN of your Secrets Manager secret: ```json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "secretsmanager:GetSecretValue", "kms:Decrypt" ], "Resource": [ "ARN_OF_SECRETSMANAGER_SECRET" ] } ] } ``` Attach the inline policy: ```bash aws iam put-role-policy \ --role-name ecsTaskExecutionRole \ --policy-name CipherStashProxyECSPolicy \ --policy-document file://cipherstash-proxy-ecs-policy.json ``` Managed policy [#managed-policy] Attach the ECS task execution managed policy: ```bash aws iam attach-role-policy \ --role-name ecsTaskExecutionRole \ --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy ``` Create an ECS task definition [#create-an-ecs-task-definition] Create `cipherstash-proxy-task-def.json`, replacing placeholders with values from previous steps: ```json { "family": "cipherstash-proxy", "networkMode": "awsvpc", "executionRoleArn": "ARN_OF_ROLE", "cpu": "256", "memory": "512", "containerDefinitions": [ { "name": "cipherstash-proxy", "image": "IMAGE_FROM_STEP_2", "essential": true, "portMappings": [ { "containerPort": 6432, "hostPort": 6432 }, { "containerPort": 9930, "hostPort": 9930 } ], "environment": [ { "name": "CS_DATABASE__USERNAME", "value": "RDS_USERNAME" }, { "name": "CS_DATABASE__NAME", "value": "RDS_DATABASE_NAME" }, { "name": "CS_DATABASE__HOST", "value": "RDS_HOSTNAME" }, { "name": "CS_DATABASE__PORT", "value": "RDS_PORT" }, { "name": "CS_PROMETHEUS__ENABLED", "value": "true" }, { "name": "CS_DATABASE__INSTALL_EQL", "value": "true" } ], "secrets": [ { "name": "CS_WORKSPACE_ID", "valueFrom": "SECRET_ARN:CS_WORKSPACE_ID::" }, { "name": "CS_CLIENT_ID", "valueFrom": "SECRET_ARN:CS_CLIENT_ID::" }, { "name": "CS_DEFAULT_KEYSET_ID", "valueFrom": "SECRET_ARN:CS_DEFAULT_KEYSET_ID::" }, { "name": "CS_CLIENT_KEY", "valueFrom": "SECRET_ARN:CS_CLIENT_KEY::" }, { "name": "CS_CLIENT_ACCESS_KEY", "valueFrom": "SECRET_ARN:CS_CLIENT_ACCESS_KEY::" }, { "name": "CS_DATABASE__PASSWORD", "valueFrom": "SECRET_ARN:CS_DATABASE__PASSWORD::" } ], "logConfiguration": { "logDriver": "awslogs", "options": { "awslogs-group": "cipherstash-proxy", "awslogs-region": "AWS_REGION", "awslogs-stream-prefix": "cipherstash-proxy" } } } ], "requiresCompatibilities": ["FARGATE"], "runtimePlatform": { "operatingSystemFamily": "LINUX", "cpuArchitecture": "ARM64" } } ``` Register the task definition: ```bash aws ecs register-task-definition \ --cli-input-json file://cipherstash-proxy-task-def.json ``` Create an ECS cluster and service [#create-an-ecs-cluster-and-service] Create the cluster and log group: ```bash aws ecs create-cluster --cluster-name ecs-app aws logs create-log-group --log-group-name cipherstash-proxy ``` Create an ECS service (ensure you have the subnet and security group of your RDS instance): ```bash # Ensure you have set these environment variables: # export SUBNETS=subnet-xxx # export SECURITY_GROUP=sg-xxx aws ecs create-service \ --cluster ecs-app \ --service-name CipherStashProxy \ --task-definition cipherstash-proxy \ --desired-count 1 \ --launch-type FARGATE \ --network-configuration "awsvpcConfiguration={subnets=[$SUBNETS],securityGroups=[$SECURITY_GROUP],assignPublicIp=ENABLED}" ``` Verify the deployment [#verify-the-deployment] Check the service and task status: ```bash # List services running in the cluster aws ecs list-services --cluster ecs-app # Show details of CipherStashProxy service aws ecs describe-services --cluster ecs-app --services CipherStashProxy # Tail the logs aws logs tail --since 6h --follow cipherstash-proxy ``` Notes and considerations [#notes-and-considerations] * **Security**: Make sure sensitive data such as keys and passwords are managed securely, preferably using AWS Secrets Manager or Parameter Store. * **Networking**: Configure network settings properly to allow your ECS tasks to communicate with other services. * **Scaling and management**: Monitor the service and adjust scaling as necessary. # Configuration Proxy configuration [#proxy-configuration] CipherStash Proxy is available as a [container image](https://hub.docker.com/r/cipherstash/proxy) on Docker Hub that can be deployed locally, in CI/CD, through to production. CipherStash Proxy is a Docker container that can be deployed locally, as a cloud sidecar, or as a standalone binary. It speaks the PostgreSQL protocol (based on [pgcat](https://github.com/pgcat/pgcat)), meaning your application connects to Proxy exactly the same way it connects to PostgreSQL. Installing Proxy [#installing-proxy] The easiest way to start using CipherStash Proxy with your application is by adding a container to your application's `docker-compose.yml`: ```yaml services: app: # Your app container config db: # Your Postgres container config proxy: image: cipherstash/proxy:latest container_name: proxy ports: - 6432:6432 - 9930:9930 environment: - CS_DATABASE__HOST=${CS_DATABASE__HOST} - CS_DATABASE__PORT=${CS_DATABASE__PORT} - CS_DATABASE__USERNAME=${CS_DATABASE__USERNAME} - CS_DATABASE__PASSWORD=${CS_DATABASE__PASSWORD} - CS_DATABASE__NAME=${CS_DATABASE__NAME} - CS_WORKSPACE_CRN=${CS_WORKSPACE_CRN} - CS_CLIENT_ACCESS_KEY=${CS_CLIENT_ACCESS_KEY} - CS_DEFAULT_KEYSET_ID=${CS_DEFAULT_KEYSET_ID} - CS_CLIENT_ID=${CS_CLIENT_ID} - CS_CLIENT_KEY=${CS_CLIENT_KEY} - CS_PROMETHEUS__ENABLED=${CS_PROMETHEUS__ENABLED:-true} ``` For a fully-working example, check out the [docker-compose.yml](https://github.com/cipherstash/proxy/blob/main/docker-compose.yml) in the Proxy repository. Start the Proxy container: ```bash docker compose up ``` Connect your PostgreSQL client to Proxy on TCP 6432. Prometheus metrics are exposed on port 9930. Read more about them in the [reference documentation](https://github.com/cipherstash/proxy/blob/main/docs/reference/index.md#prometheus-metrics). Configuring Proxy [#configuring-proxy] To run, CipherStash Proxy needs to know: * What port to run on * How to connect to the target PostgreSQL database * Secrets to authenticate to CipherStash There are two ways to configure Proxy: * **Environment variables** that Proxy looks up on startup * **TOML file** (`cipherstash-proxy.toml`) that Proxy reads on startup Configuration loading order: 1. If `cipherstash-proxy.toml` is present in the current working directory, Proxy reads its config from that file 2. If `cipherstash-proxy.toml` is not present, Proxy looks up environment variables 3. If **both** are present, Proxy uses the TOML file as the base and overrides with any environment variables that are set See the [Proxy config reference](https://github.com/cipherstash/proxy/blob/main/docs/reference/index.md#proxy-config-options) for all available options. Setting up the database schema [#setting-up-the-database-schema] Under the hood, Proxy uses [EQL](/docs/platform/eql) to index and search encrypted data. When you start the Proxy container, you can install EQL by setting the `CS_DATABASE__INSTALL_EQL` environment variable: ```bash CS_DATABASE__INSTALL_EQL=true ``` This installs the version of EQL bundled with the Proxy container. Using `CS_DATABASE__INSTALL_EQL` is only recommended for development environments. Install EQL by running [the installation script](https://github.com/cipherstash/encrypt-query-language/releases) as a database migration in production environments. Check the installed EQL version: ```sql SELECT eql_v2.version(); ``` Creating encrypted columns [#creating-encrypted-columns] When storing encrypted data in PostgreSQL with Proxy, you use the `eql_v2_encrypted` column type provided by EQL. Create a table with an encrypted column: ```sql CREATE TABLE users ( id SERIAL PRIMARY KEY, email eql_v2_encrypted ); ``` Adding encrypted indexes [#adding-encrypted-indexes] Encrypted columns cannot be searched without an encrypted index. The indexes you define determine what kind of searches you can perform. Add a `unique` index for exact match queries: ```sql SELECT eql_v2.add_search_config( 'users', 'email', 'unique', 'text' ); ``` Add `match` and `ore` indexes for pattern matching and ordering: ```sql SELECT eql_v2.add_search_config( 'users', 'email', 'match', 'text' ); SELECT eql_v2.add_search_config( 'users', 'email', 'ore', 'text' ); ``` Adding, updating, or deleting encrypted indexes on columns that already contain encrypted data will not re-index that data. To use the new indexes, you must `SELECT` the data out of the column and `UPDATE` it again. To learn about encrypted indexes for other data types (`text`, `int`, `boolean`, `date`, `jsonb`), see the [EQL documentation](https://github.com/cipherstash/encrypt-query-language/tree/main/docs). Encrypting existing data [#encrypting-existing-data] CipherStash Proxy includes an `encrypt` CLI tool to encrypt existing data, or to apply index changes after changes to the encryption configuration. See the [`encrypt` tool reference](https://github.com/cipherstash/proxy/blob/main/docs/reference/encrypt-tool.md) for usage details. Reference [#reference] See the [CipherStash Proxy reference](https://github.com/cipherstash/proxy/blob/main/docs/reference/index.md) for all available options. # Getting started with Proxy Getting started with Proxy [#getting-started-with-proxy] Clone the repo [#clone-the-repo] Start by cloning the [Proxy repo](https://github.com/cipherstash/proxy): ```bash git clone https://github.com/cipherstash/proxy cd proxy ``` Obtain a client key and credentials [#obtain-a-client-key-and-credentials] If you haven't already, sign up for a [CipherStash account](https://dashboard.cipherstash.com). Go to the [Dashboard onboarding](https://dashboard.cipherstash.com/workspaces/_/onboarding) and at **Step 2** click `Generate secrets`. The generated values will be copied to your clipboard. Store the client ID, key and credentials alongside the workspace CRN in `.env.proxy.docker`: ```ini CS_WORKSPACE_CRN= CS_CLIENT_ID= CS_CLIENT_KEY= CS_CLIENT_ACCESS_KEY= ``` Start the containers [#start-the-containers] ```bash docker compose up ``` This will start a PostgreSQL database on `localhost:5432`, and CipherStash Proxy on `localhost:6432`. There's an example table called `users` that you can use to start inserting and querying encrypted data with. In this example table we've chosen users' email, date of birth, and salary as examples of the kind of sensitive data that you might want to protect with encryption. Insert and read some data [#insert-and-read-some-data] Connect to the Proxy via `psql` and run some queries: ```bash docker compose exec proxy psql postgres://cipherstash:3ncryp7@localhost:6432/cipherstash ``` This establishes an interactive session with the database, via CipherStash Proxy. Insert and read some data via Proxy: ```sql INSERT INTO users (encrypted_email, encrypted_dob, encrypted_salary) VALUES ('alice@cipherstash.com', '1970-01-01', '100'); SELECT encrypted_email, encrypted_dob, encrypted_salary FROM users; ``` The `INSERT` inserts a record into the `users` table, and the `SELECT` reads the same record back. Notice that it looks like nothing happened: the data in the `INSERT` was unencrypted, and the data in the `SELECT` is also unencrypted. Now connect to the database directly via `psql` and see what the data actually looks like: ```bash docker compose exec proxy psql postgres://cipherstash:3ncryp7@postgres:5432/cipherstash ``` Query the database directly: ```sql SELECT encrypted_email, encrypted_dob, encrypted_salary FROM users; ``` You'll see the output is *much* larger, because the `SELECT` returns the raw encrypted data. The data is transparently encrypted and decrypted by Proxy. Update data with a WHERE clause [#update-data-with-a-where-clause] In your `psql` connection to Proxy, update the data and read it back: ```sql UPDATE users SET encrypted_dob = '1978-02-01' WHERE encrypted_email = 'alice@cipherstash.com'; SELECT encrypted_dob FROM users WHERE encrypted_email = 'alice@cipherstash.com'; ``` The `=` comparison operation in the `WHERE` clause is evaluated against **encrypted** data. The `SELECT` returns `1978-02-01`. Search encrypted data [#search-encrypted-data] Insert more records via Proxy and search them: ```sql INSERT INTO users (encrypted_email, encrypted_dob, encrypted_salary) VALUES ('bob@cipherstash.com', '1991-03-06', '10'); INSERT INTO users (encrypted_email, encrypted_dob, encrypted_salary) VALUES ('carol@cipherstash.com', '2005-12-30', '1000'); -- Range query on encrypted salary SELECT encrypted_email, encrypted_dob, encrypted_salary FROM users WHERE encrypted_salary <= 100; -- Pattern match on encrypted email SELECT encrypted_email, encrypted_dob, encrypted_salary FROM users WHERE encrypted_email LIKE 'alice'; -- Range query on encrypted date SELECT encrypted_email, encrypted_dob, encrypted_salary FROM users WHERE encrypted_dob > '2000-01-01'; ``` All comparison operations are evaluated against **encrypted** data — the literal values are transparently encrypted by Proxy before being compared in the database. This demonstrates the power of CipherStash Proxy: * Completely transparent encryption of sensitive data in PostgreSQL * All data remains searchable, while being protected with non-deterministic AES-256-GCM encryption * Zero changes required to your application's database queries # CipherStash Proxy CipherStash Proxy [#cipherstash-proxy] CipherStash Proxy provides transparent, *searchable* encryption for your existing PostgreSQL database. Features [#features] * **Automatic encryption and decryption** with zero changes to SQL — configure encryption for specific tables and columns * **Queries over encrypted values**: equality, comparison, ordering, grouping * **Built-in [Prometheus](https://prometheus.io/) support** for monitoring * **Written in Rust** for high performance and strongly-typed mapping of SQL statements * **Uses [ZeroKMS](/docs/kms)**, offering up to 14x the performance of AWS KMS * **Runs in a container** or as a standalone CLI tool Behind the scenes, CipherStash Proxy uses the [Encrypt Query Language (EQL)](/docs/platform/eql) to index and search encrypted data. When to use Proxy vs SDK [#when-to-use-proxy-vs-sdk] | | CipherStash Proxy | Encryption SDK | | ---------------- | ---------------------------------------------------------- | ----------------------------------------------------- | | **Best for** | DevOps teams adding encryption to existing PostgreSQL apps | Engineering teams building new applications | | **Code changes** | Zero — drop-in replacement for your database connection | Application-level integration with schema definitions | | **Setup** | Docker container, configure env vars | npm install, define schemas, integrate into app | | **Control** | Automatic, table/column configuration | Fine-grained, per-field control | Next steps [#next-steps] # API Reference API Reference [#api-reference] Auto-generated TypeDoc reference for CipherStash packages. For hand-written guides and examples, see the [Encryption SDK API reference](/docs/encryption/api-reference). Packages [#packages] @cipherstash/stack [#cipherstashstack] The core CipherStash SDK. Includes Encryption (`Encryption()`, `EncryptionClient`, schema builders, type inference, Lock Context), Secrets API, and integrations (Drizzle, Supabase, DynamoDB, and more). [Browse @cipherstash/stack reference](/docs/reference/stack/latest) EQL API [#eql-api] PostgreSQL types, operators, and functions for the Encrypt Query Language (EQL) extension. Enables searchable encryption and encrypted queries directly in the database. [Browse EQL API reference](/docs/reference/eql) # CLI reference CLI reference [#cli-reference] The `stash` CLI is bundled with `@cipherstash/stack` and available after install. It reads credentials from the same `CS_*` environment variables used by the SDK. Commands [#commands] Set a secret [#set-a-secret] Encrypt and store a secret: ```bash npx stash secrets set --name DATABASE_URL --value "postgres://..." --environment production npx stash secrets set -n DATABASE_URL -V "postgres://..." -e production ``` Get a secret [#get-a-secret] Retrieve and decrypt a secret: ```bash npx stash secrets get --name DATABASE_URL --environment production npx stash secrets get -n DATABASE_URL -e production ``` List secrets [#list-secrets] List all secret names in an environment: ```bash npx stash secrets list --environment production npx stash secrets list -e production ``` Delete a secret [#delete-a-secret] Delete a secret (prompts for confirmation): ```bash npx stash secrets delete --name DATABASE_URL --environment production ``` Pass `--yes` to skip the confirmation prompt: ```bash npx stash secrets delete --name DATABASE_URL --environment production --yes npx stash secrets delete -n DATABASE_URL -e production -y ``` Command reference [#command-reference] | Command | Flags | Aliases | Description | | ---------------------- | ------------------------------------ | ---------------- | --------------------------------------- | | `stash secrets set` | `--name`, `--value`, `--environment` | `-n`, `-V`, `-e` | Encrypt and store a secret | | `stash secrets get` | `--name`, `--environment` | `-n`, `-e` | Retrieve and decrypt a secret | | `stash secrets list` | `--environment` | `-e` | List all secret names in an environment | | `stash secrets delete` | `--name`, `--environment`, `--yes` | `-n`, `-e`, `-y` | Delete a secret | Configuration [#configuration] The CLI reads credentials from environment variables: | Variable | Description | | ---------------------- | --------------------------------------------------- | | `CS_WORKSPACE_CRN` | The workspace identifier (CRN format) | | `CS_CLIENT_ID` | The client identifier | | `CS_CLIENT_KEY` | Client key material used with ZeroKMS | | `CS_CLIENT_ACCESS_KEY` | API key for authenticating with the CipherStash API | # Concepts Concepts [#concepts] CipherStash Secrets is organized around four core resources: workspaces, environments, clients, and API keys. Understanding how they relate helps you design a secure secrets management strategy. Workspaces [#workspaces] A **workspace** is the top-level organizational unit. Each workspace gets its own isolated vault for storing encrypted secrets. * Separate resources by project or team. * Each workspace has a unique Cloud Resource Name (CRN) and region (e.g., `us-east-1`, `eu-west-1`). * Secrets stored in one workspace are completely isolated from other workspaces. Environments [#environments] **Environments** provide cryptographic isolation within a workspace. Each environment maps to its own [Key Set](/docs/kms/keysets) in ZeroKMS, so secrets in one environment cannot be decrypted with keys from another. This is the same Key Set primitive used for [multi-tenant encryption](/docs/encryption/configuration#keysets) in the Encryption SDK. Typical setups include: | Environment | Purpose | | ------------- | ---------------------- | | `production` | Live service secrets | | `staging` | Pre-production testing | | `development` | Local development | When you initialize the SDK, the `environment` parameter determines which keyset is used: ```typescript title="secrets.ts" import { Secrets } from "@cipherstash/stack/secrets" const secrets = new Secrets({ workspaceCRN: process.env.CS_WORKSPACE_CRN!, clientId: process.env.CS_CLIENT_ID!, clientKey: process.env.CS_CLIENT_KEY!, apiKey: process.env.CS_CLIENT_ACCESS_KEY!, environment: "production", }) ``` Clients [#clients] **Clients** (also called applications) represent the services that need access to secrets. Each client receives a unique client key used for authentication with ZeroKMS. * A client is associated with one or more environments. * The `CS_CLIENT_ID` and `CS_CLIENT_KEY` environment variables identify and authenticate a client. * Client keys are shown only once at creation — store them securely. API keys [#api-keys] **API keys** (also called access keys) authenticate requests to the CipherStash API. They are separate from client keys and control API-level access. * Set the `CS_CLIENT_ACCESS_KEY` environment variable with your API key. * Roles control permission scope: `admin` for full access, `member` for limited access. * Like client keys, API keys are shown only once at creation. Credential summary [#credential-summary] | Variable | Description | | ---------------------- | --------------------------------------------------- | | `CS_WORKSPACE_CRN` | Workspace identifier (CRN format) | | `CS_CLIENT_ID` | Client identifier | | `CS_CLIENT_KEY` | Client key material used with ZeroKMS | | `CS_CLIENT_ACCESS_KEY` | API key for authenticating with the CipherStash API | Security model [#security-model] * **End-to-end encryption** — Values are encrypted locally by the SDK before transmission. CipherStash never sees plaintext. * **Workspace isolation** — Each workspace has its own vault. No cross-workspace access is possible. * **Environment isolation** — Each environment uses a separate [Key Set](/docs/kms/keysets). A secret encrypted in staging cannot be decrypted with production keys. This is the same primitive used for [multi-tenant encryption](/docs/encryption/configuration#keysets). * **Zero-trust** — Every request is authenticated and authorized. There is no implicit trust between services. # Getting started Getting started with Secrets [#getting-started-with-secrets] Install [#install] Secrets is included in `@cipherstash/stack`: ```bash npm install @cipherstash/stack ``` Prerequisites [#prerequisites] You need a CipherStash account and workspace credentials. Sign up at [cipherstash.com/signup](https://cipherstash.com/signup) to get started. Set up your workspace [#set-up-your-workspace] Before using Secrets, create the following resources in the [CipherStash dashboard](https://dashboard.cipherstash.com): Create a workspace [#create-a-workspace] A workspace is the top-level container for all your resources. Each workspace gets its own isolated vault for storing secrets. Navigate to the dashboard and create a new workspace, selecting a region (e.g., `us-east-1`, `eu-west-1`). Create an environment [#create-an-environment] Environments provide cryptographic isolation between stages like production, staging, and development. Each environment uses its own encryption keyset. Navigate to your workspace's environments page and create a new environment (e.g., "production"). Create a client application [#create-a-client-application] Clients represent the services that access your secrets. Each client receives a unique client key for authentication. Navigate to your workspace's applications page, create a new client, and select the environment it should access. > **Important:** Save the client key displayed after creation. It is only shown once. Create an API key [#create-an-api-key] API keys authenticate requests to the CipherStash API. Navigate to your workspace's API keys page and create a new key with the appropriate role (`admin` or `member`). > **Important:** Save the API key displayed after creation. It is only shown once. Configure environment variables [#configure-environment-variables] Set the following environment variables with the credentials from the steps above: ```bash CS_WORKSPACE_CRN=your-workspace-crn CS_CLIENT_ID=your-client-id CS_CLIENT_KEY=your-client-key CS_CLIENT_ACCESS_KEY=your-access-key ``` Store a secret [#store-a-secret] ```typescript title="secrets.ts" import { Secrets } from "@cipherstash/stack/secrets" const secrets = new Secrets({ workspaceCRN: process.env.CS_WORKSPACE_CRN!, clientId: process.env.CS_CLIENT_ID!, clientKey: process.env.CS_CLIENT_KEY!, apiKey: process.env.CS_CLIENT_ACCESS_KEY!, environment: "production", }) await secrets.set("DATABASE_URL", "postgres://user:pass@host:5432/db") ``` The value is encrypted locally before being sent to the CipherStash API. Your plaintext secret never leaves your application. Retrieve a secret [#retrieve-a-secret] ```typescript title="secrets.ts" const result = await secrets.get("DATABASE_URL") if (!result.failure) { console.log(result.data) // "postgres://user:pass@host:5432/db" } ``` The encrypted value is fetched from the API and decrypted locally. Using the CLI [#using-the-cli] You can also manage secrets from the terminal without writing code: ```bash npx stash secrets set --name DATABASE_URL --value "postgres://..." --environment production npx stash secrets get --name DATABASE_URL --environment production ``` See the [CLI reference](/docs/secrets/cli) for all available commands. Next steps [#next-steps] * Learn about [core concepts](/docs/secrets/concepts) like workspaces and environments * See the full [SDK reference](/docs/secrets/sdk) for all operations * Use the [CLI reference](/docs/secrets/cli) for terminal-based management # Secrets Secrets [#secrets] CipherStash Secrets provides end-to-end encrypted secret storage. Values are encrypted locally before being sent to the CipherStash API — your plaintext secrets never leave your application. What you get [#what-you-get] * **End-to-end encryption** — Secrets are encrypted on the client before transmission. * **SDK and CLI** — Manage secrets programmatically or from the terminal. * **Environment scoping** — Organize secrets by environment (production, staging, development), each backed by its own [Key Set](/docs/kms/keysets) in ZeroKMS for cryptographic isolation. * **Bulk retrieval** — Fetch and decrypt multiple secrets in a single call. * **Workspace isolation** — Each workspace has its own isolated vault. * **Zero-trust architecture** — CipherStash never sees your plaintext secrets. How it works [#how-it-works] 1. The SDK encrypts secrets locally using `@cipherstash/stack`. 2. Encrypted values are sent to the CipherStash API. 3. CipherStash stores them in your workspace's isolated vault. 4. On retrieval, the encrypted value is fetched and decrypted locally. Only you can decrypt your secrets — CipherStash never has access to plaintext values. Each workspace has its own isolated vault, and each environment within a workspace uses its own [Key Set](/docs/kms/keysets) in ZeroKMS for cryptographic isolation. This is the same Key Set primitive that powers [multi-tenant encryption](/docs/encryption/configuration#keysets) — a secret encrypted in one environment can never be decrypted with keys from another. Next steps [#next-steps] # SDK reference Secrets SDK reference [#secrets-sdk-reference] Initialize [#initialize] ```typescript title="secrets.ts" import { Secrets } from "@cipherstash/stack/secrets" const secrets = new Secrets({ workspaceCRN: process.env.CS_WORKSPACE_CRN!, clientId: process.env.CS_CLIENT_ID!, clientKey: process.env.CS_CLIENT_KEY!, apiKey: process.env.CS_CLIENT_ACCESS_KEY!, environment: "production", }) ``` The `environment` parameter isolates secrets — each environment gets its own encryption keyset, providing cryptographic isolation. Store a secret [#store-a-secret] Encrypts the value locally, then sends the ciphertext to the API: ```typescript title="secrets.ts" const result = await secrets.set("DATABASE_URL", "postgres://user:pass@host:5432/db") if (result.failure) { console.error("Failed:", result.failure.message) // result.failure.type: "ApiError" | "NetworkError" | "ClientError" | "EncryptionError" } else { console.log(result.data.message) // success message } ``` Retrieve a single secret [#retrieve-a-single-secret] Fetches the encrypted value from the API, decrypts locally: ```typescript title="secrets.ts" const result = await secrets.get("DATABASE_URL") if (!result.failure) { console.log(result.data) // "postgres://user:pass@host:5432/db" } ``` Retrieve multiple secrets [#retrieve-multiple-secrets] Fetch and decrypt multiple secrets in a single call: ```typescript title="secrets.ts" const result = await secrets.getMany(["DATABASE_URL", "API_KEY", "JWT_SECRET"]) if (!result.failure) { console.log(result.data.DATABASE_URL) console.log(result.data.API_KEY) console.log(result.data.JWT_SECRET) } ``` `getMany` requires a minimum of 2 secret names and a maximum of 100 names per request. > **Use `getMany` over multiple `get` calls** — it's significantly more efficient because it batches the decryption into a single ZeroKMS operation. List secrets [#list-secrets] List all secret names in the current environment. Values stay encrypted — only metadata is returned: ```typescript title="secrets.ts" const result = await secrets.list() if (!result.failure) { for (const secret of result.data) { console.log(secret.name) // Also available: secret.createdAt, secret.updatedAt, secret.environment } } ``` Delete a secret [#delete-a-secret] ```typescript title="secrets.ts" const result = await secrets.delete("OLD_API_KEY") if (result.failure) { console.error("Failed:", result.failure.message) } ``` API summary [#api-summary] | Method | Signature | Returns | | --------- | ------------------------------------ | ------------------------------------------------------------------- | | `set` | `(name: string, value: string)` | `Promise>` | | `get` | `(name: string)` | `Promise>` | | `getMany` | `(names: string[])` (min 2, max 100) | `Promise, SecretsError>>` | | `list` | `()` | `Promise>` | | `delete` | `(name: string)` | `Promise>` | All operations return `Result` with either `data` or `failure`. Type reference [#type-reference] SecretsConfig [#secretsconfig] ```typescript interface SecretsConfig { workspaceCRN: string // Cloud Resource Name clientId: string // Client identifier clientKey: string // Client key material apiKey: string // API access key (CS_CLIENT_ACCESS_KEY) environment: string // Environment name accessKey?: string // Optional additional access key } ``` SecretMetadata [#secretmetadata] ```typescript interface SecretMetadata { id: string name: string environment: string createdAt: string updatedAt: string } ``` Error types [#error-types] ```typescript type SecretsErrorType = | "ApiError" // HTTP/API failures | "NetworkError" // Network connectivity issues | "ClientError" // Client initialization failures | "EncryptionError" // Encryption operation failed | "DecryptionError" // Decryption operation failed ``` Patterns [#patterns] Loading secrets at startup [#loading-secrets-at-startup] ```typescript title="config.ts" import { Secrets } from "@cipherstash/stack/secrets" const secrets = new Secrets({ workspaceCRN: process.env.CS_WORKSPACE_CRN!, clientId: process.env.CS_CLIENT_ID!, clientKey: process.env.CS_CLIENT_KEY!, apiKey: process.env.CS_CLIENT_ACCESS_KEY!, environment: process.env.NODE_ENV || "development", }) // Load all needed secrets in one efficient call const result = await secrets.getMany(["DATABASE_URL", "STRIPE_KEY", "SENDGRID_KEY"]) if (result.failure) { throw new Error(`Failed to load secrets: ${result.failure.message}`) } const config = result.data // Use config.DATABASE_URL, config.STRIPE_KEY, etc. ``` Environment isolation [#environment-isolation] Each environment has its own encryption keyset, providing cryptographic isolation: ```typescript title="secrets.ts" // Production secrets const prodSecrets = new Secrets({ ...creds, environment: "production" }) // Staging secrets (completely isolated keys) const stagingSecrets = new Secrets({ ...creds, environment: "staging" }) ``` A secret set in one environment cannot be decrypted with credentials from another environment. How it works [#how-it-works] 1. The SDK encrypts secrets locally using `@cipherstash/stack` before any network call. 2. Encrypted values are sent to the CipherStash API over HTTPS. 3. CipherStash stores them in your workspace's isolated vault. 4. On retrieval, the encrypted value is fetched and decrypted locally by the SDK. CipherStash never has access to your plaintext secrets. Each workspace has its own isolated vault, and each environment uses a separate encryption keyset managed by [ZeroKMS](/docs/kms). # Securing AI and RAG pipelines Securing AI and RAG pipelines [#securing-ai-and-rag-pipelines] Retrieval-Augmented Generation (RAG) pipelines commonly store sensitive documents alongside vector embeddings. Without encryption, this data is exposed at rest and during retrieval — creating a significant attack surface. CipherStash lets you encrypt sensitive content while preserving the ability to search and retrieve it. The problem [#the-problem] RAG architectures typically store: * **Document chunks** — the original text, often containing PII, financial data, or confidential business information * **Metadata** — source references, user associations, access tags * **Vector embeddings** — numeric representations used for similarity search If any of this data is exfiltrated from the database, the plaintext content is immediately exposed. Encryption-at-rest does not help — the data is decrypted as soon as it's queried. Encrypting RAG context data [#encrypting-rag-context-data] Use the Encryption SDK to encrypt sensitive fields before storing them alongside your embeddings. Define a schema for your documents [#define-a-schema-for-your-documents] ```typescript title="schema.ts" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" export const documents = encryptedTable("documents", { content: encryptedColumn("content") .freeTextSearch(), source: encryptedColumn("source") .equality(), userId: encryptedColumn("user_id") .equality(), }) ``` Encrypt before storage [#encrypt-before-storage] ```typescript title="ingest.ts" import { Encryption } from "@cipherstash/stack" import { documents } from "./schema" const client = await Encryption({ schemas: [documents] }) async function ingestDocument(doc: { content: string; source: string; userId: string; embedding: number[] }) { const encryptedContent = await client.encrypt(doc.content, { column: documents.content, table: documents, }) const encryptedSource = await client.encrypt(doc.source, { column: documents.source, table: documents, }) const encryptedUserId = await client.encrypt(doc.userId, { column: documents.userId, table: documents, }) if (encryptedContent.failure || encryptedSource.failure || encryptedUserId.failure) { throw new Error("Encryption failed") } // Store encrypted fields alongside the vector embedding await db.query( `INSERT INTO documents (content, source, user_id, embedding) VALUES ($1::jsonb, $2::jsonb, $3::jsonb, $4)`, [encryptedContent.data, encryptedSource.data, encryptedUserId.data, JSON.stringify(doc.embedding)] ) } ``` Decrypt retrieved context [#decrypt-retrieved-context] After vector similarity search retrieves relevant documents, decrypt the content before passing it to the LLM: ```typescript title="retrieve.ts" async function retrieveContext(queryEmbedding: number[], topK: number = 5) { // Vector similarity search returns encrypted rows const results = await db.query( `SELECT content, source FROM documents ORDER BY embedding <-> $1 LIMIT $2`, [JSON.stringify(queryEmbedding), topK] ) // Decrypt the content for each result const decryptedDocs = await Promise.all( results.rows.map(async (row) => { const content = await client.decrypt(row.content) const source = await client.decrypt(row.source) return { content: content.failure ? null : content.data, source: source.failure ? null : source.data, } }) ) return decryptedDocs.filter((doc) => doc.content !== null) } ``` Searchable encrypted retrieval [#searchable-encrypted-retrieval] When you need to filter documents by metadata before or alongside vector search, use [searchable encryption](/docs/encryption/searchable-encryption) with EQL: ```sql -- Find documents for a specific user using encrypted equality search SELECT content, source, embedding FROM documents WHERE eql_v2.eq(user_id, $1) ORDER BY embedding <-> $2 LIMIT 10; ``` This combines encrypted metadata filtering with vector similarity — without ever decrypting the metadata in the database. Benefits for AI pipelines [#benefits-for-ai-pipelines] * **Sensitive context stays encrypted** — document chunks containing PII or confidential data are never stored in plaintext * **Compliance-ready** — encrypted storage meets GDPR, HIPAA, and SOC2 requirements for data protection * **Selective decryption** — only decrypt what the LLM needs, reducing exposure surface * **Audit trail** — track who retrieved which documents and when using [identity-aware encryption](/docs/encryption/identity) # Regulatory compliance Regulatory compliance [#regulatory-compliance] CipherStash's searchable encryption provides the technical controls needed to satisfy regulatory requirements while maintaining full query capabilities. This page covers common compliance patterns using the Encryption SDK and EQL. Encrypted uniqueness constraints [#encrypted-uniqueness-constraints] Many compliance frameworks require that sensitive identifiers (email, SSN, tax ID) are unique across your dataset. With CipherStash, you can enforce uniqueness on encrypted data using HMAC-based indexes. Create a unique index on encrypted data [#create-a-unique-index-on-encrypted-data] When you define an `equality()` index on a column, CipherStash generates a deterministic HMAC that can be used for unique constraints: ```typescript title="schema.ts" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" export const patients = encryptedTable("patients", { ssn: encryptedColumn("ssn") .equality(), email: encryptedColumn("email") .equality(), }) ``` In PostgreSQL with EQL, create a unique index on the HMAC component: ```sql -- Create the table with encrypted columns CREATE TABLE patients ( id SERIAL PRIMARY KEY, ssn eql_v2_encrypted NOT NULL, email eql_v2_encrypted NOT NULL ); -- Add unique index on the HMAC (equality) index term CREATE UNIQUE INDEX patients_ssn_unique ON patients (eql_v2.hmac_256(ssn)); CREATE UNIQUE INDEX patients_email_unique ON patients (eql_v2.hmac_256(email)); ``` This ensures no two patients can have the same SSN or email — enforced at the database level — while the actual values remain encrypted. GDPR compliance patterns [#gdpr-compliance-patterns] Right to erasure (Article 17) [#right-to-erasure-article-17] Encrypted data with CipherStash supports crypto-shredding: revoke the keyset or client key, and all data encrypted under that key becomes permanently unreadable. Data minimization (Article 5) [#data-minimization-article-5] Encrypt all personal data fields and use [Lock Contexts](/docs/encryption/identity) to restrict which application components can decrypt specific records: ```typescript title="minimize-access.ts" // Only the billing service can decrypt payment data const result = await client .withLockContext({ identityToken: billingServiceJWT }) .decrypt(encryptedPaymentData) ``` Data portability (Article 20) [#data-portability-article-20] Encrypted values are stored as standard JSON objects ([CipherCells](/docs/platform/cipher-cell)). They can be exported, transferred between systems, and decrypted at the destination — provided the recipient has the appropriate key material. HIPAA compliance patterns [#hipaa-compliance-patterns] Access controls (§ 164.312(a)) [#access-controls--164312a] Use identity-aware encryption to bind decryption to authenticated healthcare providers: ```typescript title="hipaa-access.ts" // Encrypt patient records with identity binding const encrypted = await client .withLockContext({ identityToken: providerJWT }) .encrypt(patientRecord.diagnosis, { column: patients.diagnosis, table: patients, }) ``` Audit controls (§ 164.312(b)) [#audit-controls--164312b] Every encryption and decryption operation through ZeroKMS produces an audit event. Combine with [CipherStash Proxy audit features](/docs/proxy/audit) for comprehensive data access logging including statement fingerprints and record reconciliation. Integrity controls (§ 164.312(c)) [#integrity-controls--164312c] CipherStash's authenticated encryption (AES-256-GCM) ensures that any tampering with encrypted data is detected during decryption — the operation will fail if the ciphertext has been modified. PCI-DSS compliance patterns [#pci-dss-compliance-patterns] Requirement 3: Protect stored cardholder data [#requirement-3-protect-stored-cardholder-data] Encrypt cardholder data at the application layer before it reaches the database: ```typescript title="pci-store.ts" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" export const cards = encryptedTable("cards", { cardNumber: encryptedColumn("card_number") .equality(), cardholderName: encryptedColumn("cardholder_name"), }) ``` Requirement 10: Track and monitor access [#requirement-10-track-and-monitor-access] CipherStash's audit logging provides cryptographic proof of data access. Every decryption request is logged with: * **Who** — the authenticated identity (via Lock Context) * **What** — which encrypted column was accessed * **When** — timestamp of the operation Supported regulations [#supported-regulations] | Regulation | Key requirements | CipherStash features | | ------------- | ----------------------------------------------- | ---------------------------------------------------------- | | **GDPR** | Encryption, data minimization, right to erasure | Searchable encryption, Lock Contexts, crypto-shredding | | **HIPAA** | Access controls, audit trails, integrity | Identity-aware encryption, ZeroKMS audit logs, AES-256-GCM | | **PCI-DSS** | Protect cardholder data, access monitoring | Application-layer encryption, audit logging | | **SOC2** | Encryption at rest and in use, access controls | Encryption-in-use, role-based key access | | **ISO 27001** | Information security management | End-to-end encryption, key management, audit trails | | **CCPA** | Consumer data protection | Encrypted storage, access controls | # Data residency Data residency [#data-residency] CipherStash's architecture provides strong data residency guarantees through regional key management, zero-knowledge encryption, and cryptographic key splitting. This guide covers deployment patterns for organizations with cross-border data requirements. Regional ZeroKMS deployment [#regional-zerokms-deployment] ZeroKMS is available in [multiple regions](/docs/kms/regions) globally: * **Asia Pacific**: Sydney (ap-southeast-2) * **Europe**: Frankfurt (eu-central-1), Ireland (eu-west-1) * **US East**: N. Virginia (us-east-1) * **US West**: Oregon (us-west-2) By selecting a ZeroKMS region, you control where authority keys are managed. Combined with your application's deployment region, this gives you full control over where key material exists. Dual-party key split for sovereignty [#dual-party-key-split-for-sovereignty] CipherStash uses a dual-party key split architecture that provides a strong sovereignty guarantee: 1. **Authority key** — managed by ZeroKMS in your chosen region 2. **Client key** — managed by your application in your infrastructure Neither key alone is sufficient to derive data keys. Both must cooperate to encrypt or decrypt data. This means: * **ZeroKMS alone cannot access your data** — it only holds half of the key material * **Your application alone cannot access data** — it needs ZeroKMS to derive data keys * **Data keys are never transmitted** — they are derived locally in your infrastructure Deployment patterns [#deployment-patterns] Single-region deployment [#single-region-deployment] The simplest pattern: deploy your application and ZeroKMS in the same region. ``` ┌─────────────────────────────────┐ │ Region: eu-central-1 │ │ │ │ ┌───────────┐ ┌────────────┐ │ │ │ Your App │──│ ZeroKMS │ │ │ │ + Client │ │ + Authority│ │ │ │ Key │ │ Key │ │ │ └─────┬─────┘ └────────────┘ │ │ │ │ │ ┌─────┴─────┐ │ │ │ PostgreSQL │ │ │ │ (encrypted)│ │ │ └───────────┘ │ └─────────────────────────────────┘ ``` All key material and data remain within the single region. This satisfies most data residency requirements including GDPR and regional data protection laws. Multi-region with regional key isolation [#multi-region-with-regional-key-isolation] For organizations operating across regions with different data residency requirements, deploy separate workspaces per region: ``` ┌─────────────────────┐ ┌─────────────────────┐ │ Region: eu-central-1│ │ Region: ap-southeast-2│ │ │ │ │ │ App + Client Key │ │ App + Client Key │ │ ZeroKMS (EU) │ │ ZeroKMS (APAC) │ │ PostgreSQL (EU) │ │ PostgreSQL (APAC) │ └─────────────────────┘ └─────────────────────┘ ``` Each region has its own: * ZeroKMS workspace with independent authority keys * Client keys that never leave the region * Database with encrypted data Data encrypted in one region cannot be decrypted in another — providing cryptographic enforcement of data residency boundaries. Cross-border access with centralized control [#cross-border-access-with-centralized-control] When you need to access encrypted data across regions (e.g., a global support team), use the Encryption SDK with region-specific client keys: ```typescript title="regional-access.ts" import { Encryption } from "@cipherstash/stack" import { customers } from "./schema" // Configure client for the EU workspace const euClient = await Encryption({ schemas: [customers], workspaceCrn: process.env.CS_EU_WORKSPACE_CRN, clientId: process.env.CS_EU_CLIENT_ID, clientKey: process.env.CS_EU_CLIENT_KEY, accessKey: process.env.CS_EU_ACCESS_KEY, }) // Decrypt EU customer data (requires EU credentials) const result = await euClient.decrypt(encryptedEuRecord) ``` Access to each region's data requires that region's credentials. This provides an auditable, revocable access model — if a team member's access to a region needs to be revoked, delete their client credentials for that region's workspace. Compliance alignment [#compliance-alignment] | Requirement | How CipherStash addresses it | | --------------------------------------- | ---------------------------------------------------------------------------------------- | | **Data must not leave the region** | Encryption and decryption happen locally; plaintext never leaves your infrastructure | | **Key material must stay in-region** | ZeroKMS authority keys are region-bound; client keys deploy with your app | | **Audit trail for cross-border access** | ZeroKMS logs all key derivation requests with identity context | | **Ability to revoke access** | Delete client credentials or revoke [Lock Context](/docs/encryption/identity) identities | | **Cryptographic enforcement** | Dual-party key split makes unauthorized access mathematically impossible | Next steps [#next-steps] * [Configure ZeroKMS regions](/docs/kms/regions) for your workspaces * [Set up identity-aware encryption](/docs/encryption/identity) for access control * [Review disaster recovery](/docs/kms/disaster-recovery) for multi-region key backup # Use cases Use cases [#use-cases] CipherStash vs data vaults [#cipherstash-vs-data-vaults] Compare CipherStash's proof-based security architecture with traditional data vault solutions like Skyflow and VGS. Security architecture [#security-architecture] CipherStash: Proof-based security [#cipherstash-proof-based-security] Access is cryptographically provable. You do not need to fully trust CipherStash because we never see the data. CipherStash is actually multiple separate components: a cloud-based key service (ZeroKMS) and encryption services (either Proxy or the Encryption SDK) that always run in your infrastructure. Neither plaintext nor data keys are ever visible to the cloud service. Even the encrypted values always remain within your systems. CipherStash ZeroKMS employs an encryption key hierarchy to provide true zero-trust capabilities. For a given keyset, ZeroKMS manages an "authority key" which is not sufficient to generate data encryption keys. The client (Proxy or Encryption SDK) manages an independent client key. Data keys can only be generated by the client and so are never visible to ZeroKMS. Skyflow and VGS: Trust-based security [#skyflow-and-vgs-trust-based-security] There is no way to prove if data was accessed by an unauthorized user. You must trust that the vendor does the right thing. In contrast, Skyflow and VGS both process and store plaintext data. There is no strict trust boundary between your systems and the data vault which means you must trust that the company's processes, practices and personnel are appropriate. Searchable encryption [#searchable-encryption] CipherStash encryption is searchable and supports simple lookups, range queries, ordering and even free-text search. Data does not need to be decrypted to operate on and can therefore remain encrypted in a broader range of systems. CipherStash searchable encryption is incredibly fast — even the slowest of the mechanisms used takes less than 1ms to filter records in a PostgreSQL table containing 120 million rows. Skyflow supports only exact lookups. VGS does not support searchable encryption at all. Protection beyond the vault [#protection-beyond-the-vault] If any encrypted data unexpectedly leaves your PostgreSQL database it remains safe. This provides strong protection against accidental leaks as well as insider threats. Encrypted data can also be transferred between systems without ever needing to decrypt it. A vault offers no or limited protection for data outside the vault. If original values are retrieved and stored then no protection is provided. Example: Query customer records [#example-query-customer-records] **Scenario**: You have customer data including SSN, date of birth, email address and home address. You want to retrieve all records where the customer was born in 1990. | Vendor | How DOB is stored | Query capability | Avoids N+1 | Performance | | --------------- | --------------------------------- | -------------------------------- | ---------- | ----------------------------------------------- | | **CipherStash** | Encrypted per-value in your DB | Range queries via ORE | Yes | Efficient — runs in PostgreSQL | | **Skyflow** | Plaintext in vault, token outside | Exact match only; range limited | No | Extra 50–200 ms per vault query | | **VGS** | Plaintext in vault, alias outside | Equality only (no range queries) | No | Requires fetching all and filtering client-side | *** Data sovereignty [#data-sovereignty] CipherStash provides comprehensive solutions for data sovereignty requirements. The architecture gives you complete control over where your data and key material reside. Zero-knowledge architecture [#zero-knowledge-architecture] * **Data keys are never transmitted across networks** — keys are derived locally in your infrastructure * **Plaintext data never leaves your systems** — encryption and decryption occur entirely within your environment * **Even encrypted values remain in your infrastructure** — CipherStash Proxy and Encryption SDK run in your systems * **No single point of compromise** — cryptographic proofs ensure unauthorized access is mathematically impossible Cryptographic control [#cryptographic-control] CipherStash uses a **dual-party key split** architecture: * ZeroKMS manages an "authority key" (required but not sufficient) * Your client (Proxy or Encryption SDK) manages an independent "client key" * Data keys can only be generated when both components cooperate * Data keys are never visible to ZeroKMS or transmitted over networks Regional deployment [#regional-deployment] ZeroKMS is available in [multiple regions globally](/docs/kms/regions), including Asia Pacific (Sydney), Europe (Frankfurt, Ireland), US East, and US West. Compliance alignment [#compliance-alignment] CipherStash's data sovereignty architecture supports compliance with GDPR, regional data protection laws, government regulations, and industry-specific requirements for healthcare, financial services, and other regulated sectors. *** What data should I protect? [#what-data-should-i-protect] When improving your data security practices, it can be difficult to know what data is important enough to protect. Here are the common types of sensitive data you should encrypt. Personally Identifiable Information (PII) [#personally-identifiable-information-pii] Any data that could identify a specific individual: names, phone numbers, dates of birth, addresses, emails, IP addresses, social security numbers, license numbers, passport numbers. **Applicable regulations**: GDPR, CCPA, HIPAA, APP (Australia), PCI-DSS, SOC2, ISO27001 Protected Health Information (PHI) [#protected-health-information-phi] Any information about health status, provision of health care, or payment for health care that can be linked to an individual: medical record numbers, health insurance beneficiary numbers, biometric identifiers, medical conditions, medication histories, payment histories. **Applicable regulations**: HIPAA, CMIA, GDPR, APP Financial Information [#financial-information] Data about money, accounts, and transactions: account numbers and balances, transaction data, TFNs/ITINs, saved payees. **Applicable regulations**: GDPR, CCPA, PCI-DSS, CDR (Australia), SOC2, ISO27001 Authentication Information [#authentication-information] Credentials used to gain access to accounts and services: usernames, passwords (plaintext and hashed), OAuth tokens, session cookies. **Applicable regulations**: SOC2, ISO27001, PCI-DSS # Provable access control Provable access control [#provable-access-control] Traditional access control relies on application logic — if a bug or misconfiguration exposes data, there's no way to prove access was unauthorized. CipherStash provides **cryptographic proof-based access control** through Lock Contexts, where access to data is mathematically bound to authenticated identities. How it works [#how-it-works] CipherStash [Lock Contexts](/docs/encryption/identity) bind encryption and decryption operations to an authenticated identity (typically a JWT from your identity provider). The identity token becomes part of the key derivation process — without a valid token, the data keys cannot be derived and the data cannot be decrypted. This creates a provable access boundary: * If data was decrypted, the identity token **must** have been present * ZeroKMS logs the identity associated with every key derivation * The audit trail is cryptographically verifiable — it cannot be falsified Identity-aware encryption [#identity-aware-encryption] Bind encryption to an identity [#bind-encryption-to-an-identity] ```typescript title="identity-encrypt.ts" import { Encryption } from "@cipherstash/stack" import { patients } from "./schema" const client = await Encryption({ schemas: [patients] }) // The JWT identifies the healthcare provider performing the action async function encryptPatientRecord( record: { diagnosis: string }, providerJWT: string ) { const result = await client .withLockContext({ identityToken: providerJWT }) .encrypt(record.diagnosis, { column: patients.diagnosis, table: patients, }) if (result.failure) { throw new Error(`Encryption failed: ${result.failure.message}`) } return result.data } ``` Decrypt with identity verification [#decrypt-with-identity-verification] ```typescript title="identity-decrypt.ts" async function decryptPatientRecord( encryptedDiagnosis: unknown, providerJWT: string ) { const result = await client .withLockContext({ identityToken: providerJWT }) .decrypt(encryptedDiagnosis) if (result.failure) { // Decryption fails if the identity doesn't have access throw new Error(`Access denied: ${result.failure.message}`) } return result.data } ``` Audit logging [#audit-logging] Every encryption and decryption operation through ZeroKMS produces an audit event containing: | Field | Description | | ------------- | ------------------------------------------------------------- | | **Identity** | The authenticated user or service (from the Lock Context JWT) | | **Operation** | Encrypt or decrypt | | **Timestamp** | When the operation occurred | | **Keyset** | Which keyset was used | | **Client** | Which client application performed the operation | Combining with Proxy audit [#combining-with-proxy-audit] When using [CipherStash Proxy](/docs/proxy), additional audit capabilities are available: * **[Statement fingerprinting](/docs/proxy/audit)** — identify unique SQL query patterns accessing encrypted data * **[SQL redaction](/docs/proxy/audit)** — strip sensitive values from logged queries * **[Primary key injection](/docs/proxy/audit)** — track which specific records were accessed * **[Record reconciliation](/docs/proxy/audit)** — map data access events to specific table records Together, these provide an end-to-end audit trail: **who** accessed **what data**, **when**, using **which query**. Use cases [#use-cases] Healthcare: HIPAA audit requirements [#healthcare-hipaa-audit-requirements] HIPAA requires audit controls that record who accessed Protected Health Information (PHI). With Lock Contexts, every access to patient data is cryptographically tied to the authenticated provider: ```typescript title="hipaa-audit.ts" // Each access is provably tied to the authenticated provider const diagnosis = await client .withLockContext({ identityToken: drSmithJWT }) .decrypt(patient.encryptedDiagnosis) // ZeroKMS audit log shows: // - Identity: dr.smith@hospital.example (from JWT) // - Operation: decrypt // - Keyset: patients-production // - Timestamp: 2025-01-15T09:30:00Z ``` Financial services: Segregation of duties [#financial-services-segregation-of-duties] Ensure that only authorized roles can access specific data categories: ```typescript title="segregation.ts" // Compliance team can decrypt audit records const auditData = await client .withLockContext({ identityToken: complianceTeamJWT }) .decrypt(encryptedAuditRecord) // Trading team uses a different Lock Context — cannot decrypt audit records // The key derivation will fail because the identity doesn't match ``` Multi-tenant SaaS: Tenant isolation [#multi-tenant-saas-tenant-isolation] Bind encryption to tenant identity to provide cryptographic tenant isolation: ```typescript title="tenant-isolation.ts" // Encrypt data with tenant-scoped identity const encrypted = await client .withLockContext({ identityToken: tenantAJWT }) .encrypt(sensitiveData, { column: tenants.data, table: tenants, }) // Only Tenant A's identity can decrypt this data // Tenant B's JWT will fail key derivation ``` Benefits over traditional access control [#benefits-over-traditional-access-control] | Aspect | Traditional (application logic) | Provable (CipherStash Lock Contexts) | | ------------------------ | ---------------------------------------- | -------------------------------------------------------- | | **Enforcement** | Software checks that can be bypassed | Cryptographic — mathematically impossible to bypass | | **Audit trail** | Application logs that can be modified | ZeroKMS audit logs that are cryptographically verifiable | | **Proof of access** | Circumstantial (log entries) | Deterministic (key derivation requires identity) | | **Blast radius of bugs** | Data exposed if access check is bypassed | Data remains encrypted even if application logic fails | Next steps [#next-steps] * [Set up identity-aware encryption](/docs/encryption/identity) with Lock Contexts * [Configure audit logging](/docs/proxy/audit) with CipherStash Proxy * [Review compliance patterns](/docs/use-cases/compliance) for GDPR, HIPAA, and PCI-DSS # EQL API Reference > **Latest Version:** 2.2.1 Complete API reference for the Encrypt Query Language (EQL) PostgreSQL extension. Functions [#functions] * [`->(eql_v2_encrypted, eql_v2_encrypted)`](#eql-v2-encrypted-eql-v2-encrypted) - -> operator with encrypted selector * [`->>(eql_v2_encrypted, eql_v2_encrypted)`](#eql-v2-encrypted-eql-v2-encrypted) - ->> operator with encrypted selector * [`>(eql_v2_encrypted, eql_v2_encrypted)`](#eql-v2-encrypted-eql-v2-encrypted) - > operator for encrypted value and JSONB * [`blake3(eql_v2_encrypted)`](#blake3eql-v2-encrypted) - Extract Blake3 hash index term from encrypted column value. * [`bloom_filter(eql_v2_encrypted)`](#bloom-filtereql-v2-encrypted) - Extract Bloom filter index term from encrypted column value. * [`check_encrypted(eql_v2_encrypted)`](#check-encryptedeql-v2-encrypted) - Validate encrypted composite type structure. * [`compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#compare-ore-block-u64-8-256-termseql-v2ore-block-u64-8-256-eql-v2ore-block-u64-8-256) - Compare ORE block composite types. * [`compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#compare-ore-block-u64-8-256-termseql-v2ore-block-u64-8-256-eql-v2ore-block-u64-8-256) - Compare ORE block composite types. * [`compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256_term, eql_v2.ore_block_u64_8_256_term)`](#compare-ore-block-u64-8-256-termseql-v2ore-block-u64-8-256-term-eql-v2ore-block-u64-8-256-term) - Compare arrays of ORE block terms recursively. * [`compare_ore_cllw_term_bytes(bytea, bytea)`](#compare-ore-cllw-term-bytesbytea-bytea) - Compare CLLW ORE ciphertext bytes. * [`compare_ore_cllw_var_8_term(eql_v2.ore_cllw_var_8, eql_v2.ore_cllw_var_8)`](#compare-ore-cllw-var-8-termeql-v2ore-cllw-var-8-eql-v2ore-cllw-var-8) - Compare variable-width CLLW ORE ciphertext terms. * [`config_add_cast(text, text, text, jsonb)`](#config-add-casttext-text-text-jsonb) - Set cast type for column in configuration. * [`config_add_column(text, text, jsonb)`](#config-add-columntext-text-jsonb) - Add column to table configuration if not present. * [`config_add_index(text, text, text, jsonb, jsonb)`](#config-add-indextext-text-text-jsonb-jsonb) - Add search index to column configuration. * [`config_add_table(text, jsonb)`](#config-add-tabletext-jsonb) - Add table to configuration if not present. * [`config_check_cast(jsonb)`](#config-check-castjsonb) - Validate cast types in configuration. * [`config_check_indexes(jsonb)`](#config-check-indexesjsonb) - Validate index types in configuration. * [`config_check_tables(jsonb)`](#config-check-tablesjsonb) - Validate tables field presence. * [`config_check_version(jsonb)`](#config-check-versionjsonb) - Validate version field presence. * [`config_match_default()`](#config-match-default) - Generate default options for match index. * [`count_encrypted_with_active_config(TEXT, TEXT)`](#count-encrypted-with-active-configtext-text) - Count rows encrypted with active configuration. * [`create_encrypted_columns()`](#create-encrypted-columns) - Create encrypted columns for initial encryption. * [`diff_config(JSONB, JSONB)`](#diff-configjsonb-jsonb) - Compare two configurations and find differences. * [`eql_v2_configuration()`](#eql-v2-configuration) - Unique pending configuration constraint. * [`has_blake3(jsonb)`](#has-blake3jsonb) - Check if JSONB payload contains Blake3 index term. * [`has_bloom_filter(jsonb)`](#has-bloom-filterjsonb) - Check if JSONB payload contains Bloom filter index term. * [`has_hmac_256(jsonb)`](#has-hmac-256jsonb) - Check if JSONB payload contains HMAC-SHA256 index term. * [`has_ore_block_u64_8_256(jsonb)`](#has-ore-block-u64-8-256jsonb) - Check if JSONB payload contains ORE block index term. * [`has_ore_cllw_u64_8(jsonb)`](#has-ore-cllw-u64-8jsonb) - Check if JSONB payload contains CLLW ORE index term. * [`has_ore_cllw_var_8(jsonb)`](#has-ore-cllw-var-8jsonb) - Check if JSONB payload contains variable-width CLLW ORE index term. * [`hmac_256(eql_v2_encrypted)`](#hmac-256eql-v2-encrypted) - Extract HMAC-SHA256 index term from encrypted column value. * [`ilike(eql_v2_encrypted, eql_v2_encrypted)`](#ilikeeql-v2-encrypted-eql-v2-encrypted) - Case-insensitive pattern matching helper. * [`is_ste_vec_array(jsonb)`](#is-ste-vec-arrayjsonb) - Check if JSONB payload is marked as an STE vector array. * [`is_ste_vec_array()`](#is-ste-vec-array) - Check if encrypted column value is marked as an STE vector array. * [`is_ste_vec_value(jsonb)`](#is-ste-vec-valuejsonb) - Check if JSONB payload is a single-element STE vector. * [`jsonb_array(jsonb)`](#jsonb-arrayjsonb) - Extract deterministic fields as array for GIN indexing. * [`jsonb_array_elements(jsonb)`](#jsonb-array-elementsjsonb) - Extract elements from encrypted JSONB array. * [`jsonb_array_elements_text(jsonb)`](#jsonb-array-elements-textjsonb) - Extract encrypted array elements as ciphertext. * [`jsonb_array_from_array_elements(jsonb)`](#jsonb-array-from-array-elementsjsonb) - Extract full encrypted JSONB elements as array. * [`jsonb_array_length(jsonb)`](#jsonb-array-lengthjsonb) - Get length of encrypted JSONB array. * [`jsonb_array_to_bytea_array(jsonb)`](#jsonb-array-to-bytea-arrayjsonb) - Convert JSONB hex array to bytea array. * [`jsonb_contained_by(eql_v2_encrypted, eql_v2_encrypted)`](#jsonb-contained-byeql-v2-encrypted-eql-v2-encrypted) - GIN-indexable JSONB "is contained by" check. * [`jsonb_contains(eql_v2_encrypted, jsonb)`](#jsonb-containseql-v2-encrypted-jsonb) - GIN-indexable JSONB containment check (encrypted, jsonb) * [`jsonb_path_exists(jsonb, text)`](#jsonb-path-existsjsonb-text) - Check if selector path exists in encrypted JSONB. * [`jsonb_path_query(eql_v2_encrypted, eql_v2_encrypted)`](#jsonb-path-queryeql-v2-encrypted-eql-v2-encrypted) - Query encrypted JSONB with encrypted selector. * [`jsonb_path_query_first(jsonb, text)`](#jsonb-path-query-firstjsonb-text) - Get first element matching selector. * [`log(text, text)`](#logtext-text) - Log message with context. * [`log(text)`](#logtext) - Log message for debugging. * [`ore_block_u64_8_256(jsonb)`](#ore-block-u64-8-256jsonb) - Extract ORE block index term from JSONB payload. * [`ore_block_u64_8_256_gt(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore-block-u64-8-256-gteql-v2ore-block-u64-8-256-eql-v2ore-block-u64-8-256) - Greater than operator for ORE block types. * [`ore_block_u64_8_256_gte(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore-block-u64-8-256-gteeql-v2ore-block-u64-8-256-eql-v2ore-block-u64-8-256) - Greater than or equal operator for ORE block types. * [`ore_block_u64_8_256_lt(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore-block-u64-8-256-lteql-v2ore-block-u64-8-256-eql-v2ore-block-u64-8-256) - Less than operator for ORE block types. * [`ore_block_u64_8_256_lte(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore-block-u64-8-256-lteeql-v2ore-block-u64-8-256-eql-v2ore-block-u64-8-256) - Less than or equal operator for ORE block types. * [`ore_block_u64_8_256_neq(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256)`](#ore-block-u64-8-256-neqeql-v2ore-block-u64-8-256-eql-v2ore-block-u64-8-256) - Not equal operator for ORE block types. * [`ore_cllw_u64_8(eql_v2_encrypted)`](#ore-cllw-u64-8eql-v2-encrypted) - Extract CLLW ORE index term from encrypted column value. * [`ore_cllw_var_8(eql_v2_encrypted)`](#ore-cllw-var-8eql-v2-encrypted) - Extract variable-width CLLW ORE index term from encrypted column value. * [`ready_for_encryption()`](#ready-for-encryption) - Check if database is ready for encryption. * [`reload_config()`](#reload-config) - Reload configuration from CipherStash Proxy. * [`rename_encrypted_columns()`](#rename-encrypted-columns) - Finalize initial encryption by renaming columns. * [`select_pending_columns()`](#select-pending-columns) - Get columns with pending configuration changes. * [`select_target_columns()`](#select-target-columns) - Map pending columns to their encrypted target columns. * [`selector(jsonb)`](#selectorjsonb) - Extract selector value from JSONB payload. * [`ste_vec(eql_v2_encrypted)`](#ste-veceql-v2-encrypted) - Extract STE vector index from encrypted column value. * [`ste_vec_contains(eql_v2_encrypted, eql_v2_encrypted)`](#ste-vec-containseql-v2-encrypted-eql-v2-encrypted) - Check if encrypted value 'a' contains all elements of encrypted value 'b'. * [`ste_vec_contains(public.eql_v2_encrypted, eql_v2_encrypted)`](#ste-vec-containspubliceql-v2-encrypted-eql-v2-encrypted) - Check if STE vector array contains a specific encrypted element. * [`to_encrypted(text)`](#to-encryptedtext) - Convert text to encrypted type. * [`to_jsonb(eql_v2_encrypted)`](#to-jsonbeql-v2-encrypted) - Convert encrypted type to JSONB. * [`to_ste_vec_value(jsonb)`](#to-ste-vec-valuejsonb) - Convert single-element STE vector to regular encrypted value. Private Functions [#private-functions] * [`_encrypted_check_c(jsonb)`](#encrypted-check-cjsonb) - Validate ciphertext field in encrypted payload. * [`_encrypted_check_i_ct(jsonb)`](#encrypted-check-i-ctjsonb) - Validate table and column fields in ident. * [`_encrypted_check_v(jsonb)`](#encrypted-check-vjsonb) - Validate version field in encrypted payload. * [`_first_grouped_value()`](#first-grouped-value) - State transition function for grouped\_value aggregate. *** Functions [#functions-1] ->(eql_v2_encrypted, eql_v2_encrypted) [#-eql_v2_encrypted-eql_v2_encrypted] -> operator with encrypted selector Parameters [#parameters] | Name | Type | Description | | ---------- | ------------------ | ------------------------ | | `e` | `eql_v2_encrypted` | Encrypted JSONB data | | `selector` | `eql_v2_encrypted` | Encrypted field selector | Returns [#returns] **Type:** `eql_v2_encrypted` eql\_v2\_encrypted Encrypted value at selector Variants [#variants] * `->(eql_v2_encrypted, text)` *** ->>(eql_v2_encrypted, eql_v2_encrypted) [#-eql_v2_encrypted-eql_v2_encrypted-1] ->> operator with encrypted selector Parameters [#parameters-1] | Name | Type | Description | | ---------- | ------------------ | ------------------------ | | `e` | `eql_v2_encrypted` | Encrypted JSONB data | | `selector` | `eql_v2_encrypted` | Encrypted field selector | Returns [#returns-1] **Type:** `text` text Encrypted value at selector, implicitly cast from eql\_v2\_encrypted Variants [#variants-1] * `->>(eql_v2_encrypted, text)` *** >(eql_v2_encrypted, eql_v2_encrypted) [#eql_v2_encrypted-eql_v2_encrypted] > operator for encrypted value and JSONB Parameters [#parameters-2] | Name | Type | Description | | ---- | ------------------ | ----------- | | `a` | `eql_v2_encrypted` | | | `b` | `eql_v2_encrypted` | | Variants [#variants-2] * [`>(eql_v2_encrypted, eql_v2_encrypted)`](#eql-v2-encrypted-eql-v2-encrypted) *** blake3(eql_v2_encrypted) [#blake3eql_v2_encrypted] Extract Blake3 hash index term from encrypted column value. Extracts the Blake3 hash from an encrypted column value by accessing its underlying JSONB data field. Parameters [#parameters-3] | Name | Type | Description | | ----- | ------------------ | ------------ | | `val` | `eql_v2_encrypted` | column value | Returns [#returns-2] **Type:** `eql_v2.blake3` eql\_v2.blake3 Blake3 hash value, or NULL if not present Variants [#variants-3] * `blake3(jsonb)` *** bloom_filter(eql_v2_encrypted) [#bloom_filtereql_v2_encrypted] Extract Bloom filter index term from encrypted column value. Extracts the Bloom filter from an encrypted column value by accessing its underlying JSONB data field. Parameters [#parameters-4] | Name | Type | Description | | ----- | ------------------ | ------------ | | `val` | `eql_v2_encrypted` | column value | Returns [#returns-3] **Type:** `eql_v2.bloom_filter` eql\_v2.bloom\_filter Bloom filter as smallint array Variants [#variants-4] * `bloom_filter(jsonb)` *** check_encrypted(eql_v2_encrypted) [#check_encryptedeql_v2_encrypted] Validate encrypted composite type structure. Validates an eql\_v2\_encrypted composite type by checking its underlying JSONB payload. Delegates to eql\_v2.check\_encrypted(jsonb). Parameters [#parameters-5] | Name | Type | Description | | ----- | ------------------ | ----------------- | | `val` | `eql_v2_encrypted` | value to validate | Returns [#returns-4] **Type:** `BOOLEAN` Boolean True if structure is valid Exceptions [#exceptions] * if any required field is missing or invalid Variants [#variants-5] * `check_encrypted(jsonb)` *** compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#compare_ore_block_u64_8_256_termseql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256] Compare ORE block composite types. Parameters [#parameters-6] | Name | Type | Description | | ---- | ---------------------------- | ----------- | | `a` | `eql_v2.ore_block_u64_8_256` | | | `b` | `eql_v2.ore_block_u64_8_256` | | *** compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#compare_ore_block_u64_8_256_termseql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256-1] Compare ORE block composite types. Parameters [#parameters-7] | Name | Type | Description | | ---- | ---------------------------- | ----------- | | `a` | `eql_v2.ore_block_u64_8_256` | | | `b` | `eql_v2.ore_block_u64_8_256` | | *** compare_ore_block_u64_8_256_terms(eql_v2.ore_block_u64_8_256_term, eql_v2.ore_block_u64_8_256_term) [#compare_ore_block_u64_8_256_termseql_v2ore_block_u64_8_256_term-eql_v2ore_block_u64_8_256_term] Compare arrays of ORE block terms recursively. Parameters [#parameters-8] | Name | Type | Description | | ---- | --------------------------------- | ----------- | | `a` | `eql_v2.ore_block_u64_8_256_term` | | | `b` | `eql_v2.ore_block_u64_8_256_term` | | *** compare_ore_cllw_term_bytes(bytea, bytea) [#compare_ore_cllw_term_bytesbytea-bytea] Compare CLLW ORE ciphertext bytes. Parameters [#parameters-9] | Name | Type | Description | | ---- | ------- | ----------- | | `a` | `bytea` | | | `b` | `bytea` | | *** compare_ore_cllw_var_8_term(eql_v2.ore_cllw_var_8, eql_v2.ore_cllw_var_8) [#compare_ore_cllw_var_8_termeql_v2ore_cllw_var_8-eql_v2ore_cllw_var_8] Compare variable-width CLLW ORE ciphertext terms. Parameters [#parameters-10] | Name | Type | Description | | ---- | ----------------------- | ----------- | | `a` | `eql_v2.ore_cllw_var_8` | | | `b` | `eql_v2.ore_cllw_var_8` | | *** config_add_cast(text, text, text, jsonb) [#config_add_casttext-text-text-jsonb] Set cast type for column in configuration. Parameters [#parameters-11] | Name | Type | Description | | ------------- | ------- | ----------- | | `table_name` | `text` | | | `column_name` | `text` | | | `cast_as` | `text` | | | `config` | `jsonb` | | *** config_add_column(text, text, jsonb) [#config_add_columntext-text-jsonb] Add column to table configuration if not present. Parameters [#parameters-12] | Name | Type | Description | | ------------- | ------- | ----------- | | `table_name` | `text` | | | `column_name` | `text` | | | `config` | `jsonb` | | *** config_add_index(text, text, text, jsonb, jsonb) [#config_add_indextext-text-text-jsonb-jsonb] Add search index to column configuration. Parameters [#parameters-13] | Name | Type | Description | | ------------- | ------- | ----------- | | `table_name` | `text` | | | `column_name` | `text` | | | `index_name` | `text` | | | `opts` | `jsonb` | | | `config` | `jsonb` | | *** config_add_table(text, jsonb) [#config_add_tabletext-jsonb] Add table to configuration if not present. Parameters [#parameters-14] | Name | Type | Description | | ------------ | ------- | ----------- | | `table_name` | `text` | | | `config` | `jsonb` | | *** config_check_cast(jsonb) [#config_check_castjsonb] Validate cast types in configuration. Parameters [#parameters-15] | Name | Type | Description | | ----- | ------- | ----------- | | `val` | `jsonb` | | *** config_check_indexes(jsonb) [#config_check_indexesjsonb] Validate index types in configuration. Parameters [#parameters-16] | Name | Type | Description | | ----- | ------- | ----------- | | `val` | `jsonb` | | *** config_check_tables(jsonb) [#config_check_tablesjsonb] Validate tables field presence. Parameters [#parameters-17] | Name | Type | Description | | ----- | ------- | ----------- | | `val` | `jsonb` | | *** config_check_version(jsonb) [#config_check_versionjsonb] Validate version field presence. Parameters [#parameters-18] | Name | Type | Description | | ----- | ------- | ----------- | | `val` | `jsonb` | | *** config_match_default() [#config_match_default] Generate default options for match index. *** count_encrypted_with_active_config(TEXT, TEXT) [#count_encrypted_with_active_configtext-text] Count rows encrypted with active configuration. Parameters [#parameters-19] | Name | Type | Description | | ------------- | ------ | ----------- | | `table_name` | `TEXT` | | | `column_name` | `TEXT` | | *** create_encrypted_columns() [#create_encrypted_columns] Create encrypted columns for initial encryption. For each plaintext column with pending configuration that lacks an encrypted target column, creates a new column '\{column\_name}\_encrypted' of type eql\_v2\_encrypted. This prepares the database schema for initial encryption. Returns [#returns-5] **Type:** `TABLE(table_name` TABLE(table\_name text, column\_name text) Created encrypted columns Note [#note] Only creates columns that don't already exist ⚠️ Warning [#️-warning] Executes dynamic DDL (ALTER TABLE ADD COLUMN) - modifies database schema Variants [#variants-6] * eql\_v2.rename\_encrypted\_columns *** diff_config(JSONB, JSONB) [#diff_configjsonb-jsonb] Compare two configurations and find differences. Parameters [#parameters-20] | Name | Type | Description | | ---- | ------- | ----------- | | `a` | `JSONB` | | | `b` | `JSONB` | | *** eql_v2_configuration() [#eql_v2_configuration] Unique pending configuration constraint. Unique encrypting configuration constraint. Parameters [#parameters-21] | Name | Type | Description | | ------- | ---- | ----------- | | `state` | | | Note [#note-1] Only one configuration can be 'encrypting' at once *** has_blake3(jsonb) [#has_blake3jsonb] Check if JSONB payload contains Blake3 index term. Check if encrypted column value contains Blake3 index term. Tests whether the encrypted data payload includes a 'b3' field, indicating a Blake3 hash is available for exact-match queries. Parameters [#parameters-22] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-6] **Type:** `boolean` Boolean True if Blake3 hash is present Variants [#variants-7] * [`has_blake3(jsonb)`](#has-blake3jsonb) *** has_bloom_filter(jsonb) [#has_bloom_filterjsonb] Check if JSONB payload contains Bloom filter index term. Check if encrypted column value contains Bloom filter index term. Tests whether the encrypted data payload includes a 'bf' field, indicating a Bloom filter is available for pattern-match queries. Parameters [#parameters-23] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-7] **Type:** `boolean` Boolean True if Bloom filter is present Variants [#variants-8] * [`has_bloom_filter(jsonb)`](#has-bloom-filterjsonb) *** has_hmac_256(jsonb) [#has_hmac_256jsonb] Check if JSONB payload contains HMAC-SHA256 index term. Check if encrypted column value contains HMAC-SHA256 index term. Tests whether the encrypted data payload includes an 'hm' field, indicating an HMAC-SHA256 hash is available for exact-match queries. Parameters [#parameters-24] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-8] **Type:** `boolean` Boolean True if HMAC-SHA256 hash is present Variants [#variants-9] * [`has_hmac_256(jsonb)`](#has-hmac-256jsonb) *** has_ore_block_u64_8_256(jsonb) [#has_ore_block_u64_8_256jsonb] Check if JSONB payload contains ORE block index term. Check if encrypted column value contains ORE block index term. Tests whether the encrypted data payload includes an 'ob' field, indicating an ORE block is available for range queries. Parameters [#parameters-25] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-9] **Type:** `boolean` Boolean True if ORE block is present Variants [#variants-10] * [`has_ore_block_u64_8_256(jsonb)`](#has-ore-block-u64-8-256jsonb) *** has_ore_cllw_u64_8(jsonb) [#has_ore_cllw_u64_8jsonb] Check if JSONB payload contains CLLW ORE index term. Check if encrypted column value contains CLLW ORE index term. Tests whether the encrypted data payload includes an 'ocf' field, indicating a CLLW ORE ciphertext is available for range queries. Parameters [#parameters-26] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-10] **Type:** `boolean` Boolean True if CLLW ORE ciphertext is present Variants [#variants-11] * [`has_ore_cllw_u64_8(jsonb)`](#has-ore-cllw-u64-8jsonb) *** has_ore_cllw_var_8(jsonb) [#has_ore_cllw_var_8jsonb] Check if JSONB payload contains variable-width CLLW ORE index term. Check if encrypted column value contains variable-width CLLW ORE index term. Tests whether the encrypted data payload includes an 'ocv' field, indicating a variable-width CLLW ORE ciphertext is available for range queries. Parameters [#parameters-27] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-11] **Type:** `boolean` Boolean True if variable-width CLLW ORE ciphertext is present Variants [#variants-12] * [`has_ore_cllw_var_8(jsonb)`](#has-ore-cllw-var-8jsonb) *** hmac_256(eql_v2_encrypted) [#hmac_256eql_v2_encrypted] Extract HMAC-SHA256 index term from encrypted column value. Extracts the HMAC-SHA256 hash from an encrypted column value by accessing its underlying JSONB data field. Parameters [#parameters-28] | Name | Type | Description | | ----- | ------------------ | ------------ | | `val` | `eql_v2_encrypted` | column value | Returns [#returns-12] **Type:** `eql_v2.hmac_256` eql\_v2.hmac\_256 HMAC-SHA256 hash value Variants [#variants-13] * `hmac_256(jsonb)` *** ilike(eql_v2_encrypted, eql_v2_encrypted) [#ilikeeql_v2_encrypted-eql_v2_encrypted] Case-insensitive pattern matching helper. Parameters [#parameters-29] | Name | Type | Description | | ---- | ------------------ | ----------- | | `a` | `eql_v2_encrypted` | | | `b` | `eql_v2_encrypted` | | *** is_ste_vec_array(jsonb) [#is_ste_vec_arrayjsonb] Check if JSONB payload is marked as an STE vector array. Check if encrypted column value is marked as an STE vector array. Tests whether the encrypted data payload has the 'a' (array) flag set to true, indicating it represents an array for STE vector operations. Parameters [#parameters-30] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-13] **Type:** `boolean` Boolean True if value is marked as an STE vector array Variants [#variants-14] * [`is_ste_vec_array(jsonb)`](#is-ste-vec-arrayjsonb) *** is_ste_vec_array() [#is_ste_vec_array] Check if encrypted column value is marked as an STE vector array. Tests whether an encrypted column value has the array flag set by checking its underlying JSONB data field. Parameters [#parameters-31] | Name | Type | Description | | ----- | ---- | ----------- | | `val` | | | Returns [#returns-14] **Type:** `BEGIN IF NOT eql_v2` Boolean True if value is marked as an STE vector array Variants [#variants-15] * [`is_ste_vec_array(jsonb)`](#is-ste-vec-arrayjsonb) *** is_ste_vec_value(jsonb) [#is_ste_vec_valuejsonb] Check if JSONB payload is a single-element STE vector. Check if encrypted column value is a single-element STE vector. Tests whether the encrypted data payload contains an 'sv' field with exactly one element. Single-element STE vectors can be treated as regular encrypted values. Parameters [#parameters-32] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-15] **Type:** `boolean` Boolean True if value is a single-element STE vector Variants [#variants-16] * [`is_ste_vec_value(jsonb)`](#is-ste-vec-valuejsonb) *** jsonb_array(jsonb) [#jsonb_arrayjsonb] Extract deterministic fields as array for GIN indexing. Extract deterministic fields as array from encrypted column. Extracts only deterministic search term fields (s, b3, hm, ocv, ocf) from each STE vector element. Excludes non-deterministic ciphertext for correct containment comparison using PostgreSQL's native > operator. Parameters [#parameters-33] | Name | Type | Description | | ----- | ------- | -------------------------------- | | `val` | `jsonb` | containing encrypted EQL payload | Returns [#returns-16] **Type:** `jsonb[]` jsonb\[] Array of JSONB elements with only deterministic fields Note [#note-2] Use this for GIN indexes and containment queries Variants [#variants-17] * [`jsonb_array(jsonb)`](#jsonb-arrayjsonb) *** jsonb_array_elements(jsonb) [#jsonb_array_elementsjsonb] Extract elements from encrypted JSONB array. Returns each element of an encrypted JSONB array as a separate row. Each element is returned as an eql\_v2\_encrypted value with metadata preserved from the parent array. Parameters [#parameters-34] | Name | Type | Description | | ----- | ------- | ----------------------------------- | | `val` | `jsonb` | JSONB payload representing an array | Returns [#returns-17] **Type:** `SETOF` SETOF eql\_v2\_encrypted One row per array element Note [#note-3] Each element inherits metadata (version, ident) from parent Exceptions [#exceptions-1] * if value is not an array (missing 'a' flag) Variants [#variants-18] * eql\_v2.jsonb\_array\_elements\_text *** jsonb_array_elements_text(jsonb) [#jsonb_array_elements_textjsonb] Extract encrypted array elements as ciphertext. Returns each element of an encrypted JSONB array as its raw ciphertext value (text representation). Unlike jsonb\_array\_elements, this returns only the ciphertext 'c' field without metadata. Parameters [#parameters-35] | Name | Type | Description | | ----- | ------- | ----------------------------------- | | `val` | `jsonb` | JSONB payload representing an array | Returns [#returns-18] **Type:** `SETOF` SETOF text One ciphertext string per array element Note [#note-4] Returns ciphertext only, not full encrypted structure Exceptions [#exceptions-2] * if value is not an array (missing 'a' flag) Variants [#variants-19] * eql\_v2.jsonb\_array\_elements *** jsonb_array_from_array_elements(jsonb) [#jsonb_array_from_array_elementsjsonb] Extract full encrypted JSONB elements as array. Extract full encrypted JSONB elements as array from encrypted column. Extracts all JSONB elements from the STE vector including non-deterministic fields. Use jsonb\_array() instead for GIN indexing and containment queries. Parameters [#parameters-36] | Name | Type | Description | | ----- | ------- | -------------------------------- | | `val` | `jsonb` | containing encrypted EQL payload | Returns [#returns-19] **Type:** `jsonb[]` jsonb\[] Array of full JSONB elements Variants [#variants-20] * [`jsonb_array_from_array_elements(jsonb)`](#jsonb-array-from-array-elementsjsonb) *** jsonb_array_length(jsonb) [#jsonb_array_lengthjsonb] Get length of encrypted JSONB array. Returns the number of elements in an encrypted JSONB array by counting elements in the STE vector ('sv'). The encrypted value must have the array flag ('a') set to true. Parameters [#parameters-37] | Name | Type | Description | | ----- | ------- | ----------------------------------- | | `val` | `jsonb` | JSONB payload representing an array | Returns [#returns-20] **Type:** `integer` integer Number of elements in the array Note [#note-5] Array flag 'a' must be present and set to true value Exceptions [#exceptions-3] * 'cannot get array length of a non-array' if 'a' flag is missing or not true Variants [#variants-21] * eql\_v2.jsonb\_array\_elements *** jsonb_array_to_bytea_array(jsonb) [#jsonb_array_to_bytea_arrayjsonb] Convert JSONB hex array to bytea array. Parameters [#parameters-38] | Name | Type | Description | | ----- | ------- | ----------- | | `val` | `jsonb` | | *** jsonb_contained_by(eql_v2_encrypted, eql_v2_encrypted) [#jsonb_contained_byeql_v2_encrypted-eql_v2_encrypted] GIN-indexable JSONB "is contained by" check. GIN-indexable JSONB "is contained by" check (jsonb, encrypted) GIN-indexable JSONB "is contained by" check (encrypted, jsonb) Checks if all JSONB elements from 'a' are contained in 'b'. Uses jsonb\[] arrays internally for native PostgreSQL GIN index support. Parameters [#parameters-39] | Name | Type | Description | | ---- | ------------------ | ----------------------------------------- | | `a` | `eql_v2_encrypted` | Value to check (typically a table column) | | `b` | `eql_v2_encrypted` | Container value | Returns [#returns-21] **Type:** `boolean` Boolean True if all elements of a are contained in b Variants [#variants-22] * [`jsonb_contained_by(eql_v2_encrypted, eql_v2_encrypted)`](#jsonb-contained-byeql-v2-encrypted-eql-v2-encrypted) *** jsonb_contains(eql_v2_encrypted, jsonb) [#jsonb_containseql_v2_encrypted-jsonb] GIN-indexable JSONB containment check (encrypted, jsonb) GIN-indexable JSONB containment check (jsonb, encrypted) Checks if encrypted value 'a' contains all JSONB elements from jsonb value 'b'. Uses jsonb\[] arrays internally for native PostgreSQL GIN index support. Parameters [#parameters-40] | Name | Type | Description | | ---- | ------------------ | ------------------------------------------ | | `a` | `eql_v2_encrypted` | Container value (typically a table column) | | `b` | `jsonb` | JSONB value to search for | Returns [#returns-22] **Type:** `boolean` Boolean True if a contains all elements of b Variants [#variants-23] * `jsonb_contains(eql_v2_encrypted, eql_v2_encrypted)` *** jsonb_path_exists(jsonb, text) [#jsonb_path_existsjsonb-text] Check if selector path exists in encrypted JSONB. Check existence with encrypted selector. Tests whether any encrypted elements match the given selector path. More efficient than jsonb\_path\_query when only existence check is needed. Parameters [#parameters-41] | Name | Type | Description | | ---------- | ------- | ------------------------------ | | `val` | `jsonb` | Encrypted JSONB value to check | | `selector` | `text` | Encrypted selector to test | Returns [#returns-23] **Type:** `boolean` boolean True if path exists Variants [#variants-24] * [`jsonb_path_exists(jsonb, text)`](#jsonb-path-existsjsonb-text) *** jsonb_path_query(eql_v2_encrypted, eql_v2_encrypted) [#jsonb_path_queryeql_v2_encrypted-eql_v2_encrypted] Query encrypted JSONB with encrypted selector. Overload that accepts encrypted selector and extracts its plaintext value before delegating to main jsonb\_path\_query implementation. Parameters [#parameters-42] | Name | Type | Description | | ---------- | ------------------ | ----------------------------------- | | `val` | `eql_v2_encrypted` | Encrypted JSONB value to query | | `selector` | `eql_v2_encrypted` | Encrypted selector to match against | Returns [#returns-24] **Type:** `SETOF` SETOF eql\_v2\_encrypted Matching encrypted elements Variants [#variants-25] * `jsonb_path_query(jsonb, text)` *** jsonb_path_query_first(jsonb, text) [#jsonb_path_query_firstjsonb-text] Get first element matching selector. Get first element with encrypted selector. Returns only the first encrypted element matching the selector path, or NULL if no match found. More efficient than jsonb\_path\_query when only one result is needed. Parameters [#parameters-43] | Name | Type | Description | | ---------- | ------- | ------------------------------ | | `val` | `jsonb` | Encrypted JSONB value to query | | `selector` | `text` | Encrypted selector to match | Returns [#returns-25] **Type:** `eql_v2_encrypted` eql\_v2\_encrypted First matching element or NULL Note [#note-6] Uses LIMIT 1 internally for efficiency Variants [#variants-26] * [`jsonb_path_query_first(jsonb, text)`](#jsonb-path-query-firstjsonb-text) *** log(text, text) [#logtext-text] Log message with context. Overload of log function that includes context label for better log organization during testing. Parameters [#parameters-44] | Name | Type | Description | | ----- | ------ | -------------------------------------------- | | `ctx` | `text` | Context label (e.g., test name, module name) | | `s` | `text` | Message to log | Note [#note-7] Format: "\[LOG] \{ctx} \{message}" Variants [#variants-27] * [`log(text)`](#logtext) *** log(text) [#logtext] Log message for debugging. Convenience function to emit log messages during testing and debugging. Uses RAISE NOTICE to output messages to PostgreSQL logs. Parameters [#parameters-45] | Name | Type | Description | | ---- | ------ | ----------- | | `s` | `text` | to log | Note [#note-8] Primarily used in tests and development Variants [#variants-28] * [`log(text, text)`](#logtext-text) *** ore_block_u64_8_256(jsonb) [#ore_block_u64_8_256jsonb] Extract ORE block index term from JSONB payload. Extract ORE block index term from encrypted column value. Extracts the ORE block array from the 'ob' field of an encrypted data payload. Used internally for range query comparisons. Parameters [#parameters-46] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-26] **Type:** `eql_v2.ore_block_u64_8_256` eql\_v2.ore\_block\_u64\_8\_256 ORE block index term Exceptions [#exceptions-4] * if 'ob' field is missing when ore index is expected Variants [#variants-29] * [`ore_block_u64_8_256(jsonb)`](#ore-block-u64-8-256jsonb) *** ore_block_u64_8_256_gt(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_gteql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256] Greater than operator for ORE block types. Parameters [#parameters-47] | Name | Type | Description | | ---- | ---------------------------- | ----------- | | `a` | `eql_v2.ore_block_u64_8_256` | | | `b` | `eql_v2.ore_block_u64_8_256` | | *** ore_block_u64_8_256_gte(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_gteeql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256] Greater than or equal operator for ORE block types. Parameters [#parameters-48] | Name | Type | Description | | ---- | ---------------------------- | ----------- | | `a` | `eql_v2.ore_block_u64_8_256` | | | `b` | `eql_v2.ore_block_u64_8_256` | | *** ore_block_u64_8_256_lt(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_lteql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256] Less than operator for ORE block types. Parameters [#parameters-49] | Name | Type | Description | | ---- | ---------------------------- | ----------- | | `a` | `eql_v2.ore_block_u64_8_256` | | | `b` | `eql_v2.ore_block_u64_8_256` | | *** ore_block_u64_8_256_lte(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_lteeql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256] Less than or equal operator for ORE block types. Parameters [#parameters-50] | Name | Type | Description | | ---- | ---------------------------- | ----------- | | `a` | `eql_v2.ore_block_u64_8_256` | | | `b` | `eql_v2.ore_block_u64_8_256` | | *** ore_block_u64_8_256_neq(eql_v2.ore_block_u64_8_256, eql_v2.ore_block_u64_8_256) [#ore_block_u64_8_256_neqeql_v2ore_block_u64_8_256-eql_v2ore_block_u64_8_256] Not equal operator for ORE block types. Parameters [#parameters-51] | Name | Type | Description | | ---- | ---------------------------- | ----------- | | `a` | `eql_v2.ore_block_u64_8_256` | | | `b` | `eql_v2.ore_block_u64_8_256` | | *** ore_cllw_u64_8(eql_v2_encrypted) [#ore_cllw_u64_8eql_v2_encrypted] Extract CLLW ORE index term from encrypted column value. Extracts the CLLW ORE ciphertext from an encrypted column value by accessing its underlying JSONB data field. Parameters [#parameters-52] | Name | Type | Description | | ----- | ------------------ | ------------ | | `val` | `eql_v2_encrypted` | column value | Returns [#returns-27] **Type:** `eql_v2.ore_cllw_u64_8` eql\_v2.ore\_cllw\_u64\_8 CLLW ORE ciphertext Variants [#variants-30] * `ore_cllw_u64_8(jsonb)` *** ore_cllw_var_8(eql_v2_encrypted) [#ore_cllw_var_8eql_v2_encrypted] Extract variable-width CLLW ORE index term from encrypted column value. Extracts the variable-width CLLW ORE ciphertext from an encrypted column value by accessing its underlying JSONB data field. Parameters [#parameters-53] | Name | Type | Description | | ----- | ------------------ | ------------ | | `val` | `eql_v2_encrypted` | column value | Returns [#returns-28] **Type:** `eql_v2.ore_cllw_var_8` eql\_v2.ore\_cllw\_var\_8 Variable-width CLLW ORE ciphertext Variants [#variants-31] * `ore_cllw_var_8(jsonb)` *** ready_for_encryption() [#ready_for_encryption] Check if database is ready for encryption. Verifies that all columns with pending configuration have corresponding encrypted target columns created. Returns true if encryption can proceed. Returns [#returns-29] **Type:** `BOOLEAN` boolean True if all pending columns have target encrypted columns Note [#note-9] Returns false if any pending column lacks encrypted column Variants [#variants-32] * eql\_v2.create\_encrypted\_columns *** reload_config() [#reload_config] Reload configuration from CipherStash Proxy. Placeholder function for reloading configuration from the CipherStash Proxy. Currently returns NULL without side effects. Returns [#returns-30] **Type:** `void` Void Note [#note-10] This function may be used for configuration synchronization in future versions *** rename_encrypted_columns() [#rename_encrypted_columns] Finalize initial encryption by renaming columns. After initial encryption completes, renames columns to complete the transition:Plaintext column '\{column\_name}' → '\{column\_name}\_plaintext'Encrypted column '\{column\_name}\_encrypted' → '\{column\_name}' This makes the encrypted column the primary column with the original name. Returns [#returns-31] **Type:** `TABLE(table_name` TABLE(table\_name text, column\_name text, target\_column text) Renamed columns Note [#note-11] Only renames columns where target is '\{column\_name}\_encrypted' ⚠️ Warning [#️-warning-1] Executes dynamic DDL (ALTER TABLE RENAME COLUMN) - modifies database schema Variants [#variants-33] * eql\_v2.create\_encrypted\_columns *** select_pending_columns() [#select_pending_columns] Get columns with pending configuration changes. Compares 'pending' and 'active' configurations to identify columns that need encryption or re-encryption. Returns columns where configuration differs. Returns [#returns-32] **Type:** `TABLE(table_name` TABLE(table\_name text, column\_name text) Columns needing encryption Note [#note-12] Treats missing active config as empty config Exceptions [#exceptions-5] * if no pending configuration exists Variants [#variants-34] * eql\_v2.select\_target\_columns *** select_target_columns() [#select_target_columns] Map pending columns to their encrypted target columns. For each column with pending configuration, identifies the corresponding encrypted column. During initial encryption, target is '\{column\_name}\_encrypted'. Returns NULL for target\_column if encrypted column doesn't exist yet. Returns [#returns-33] **Type:** `TABLE(table_name` TABLE(table\_name text, column\_name text, target\_column text) Column mappings Note [#note-13] The LEFT JOIN checks both original and '\_encrypted' suffix variations with type verification Variants [#variants-35] * eql\_v2.create\_encrypted\_columns *** selector(jsonb) [#selectorjsonb] Extract selector value from JSONB payload. Extract selector value from encrypted column value. Extracts the selector ('s') field from an encrypted data payload. Selectors are used to match STE vector elements during containment queries. Parameters [#parameters-54] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-34] **Type:** `text` Text The selector value Exceptions [#exceptions-6] * if 's' field is missing Variants [#variants-36] * [`selector(jsonb)`](#selectorjsonb) *** ste_vec(eql_v2_encrypted) [#ste_veceql_v2_encrypted] Extract STE vector index from encrypted column value. Extracts the STE vector from an encrypted column value by accessing its underlying JSONB data field. Used for containment query operations. Parameters [#parameters-55] | Name | Type | Description | | ----- | ------------------ | ------------ | | `val` | `eql_v2_encrypted` | column value | Returns [#returns-35] **Type:** `public.eql_v2_encrypted[]` eql\_v2\_encrypted\[] Array of encrypted STE vector elements Variants [#variants-37] * `ste_vec(jsonb)` *** ste_vec_contains(eql_v2_encrypted, eql_v2_encrypted) [#ste_vec_containseql_v2_encrypted-eql_v2_encrypted] Check if encrypted value 'a' contains all elements of encrypted value 'b'. Performs STE vector containment comparison between two encrypted values. Returns true if all elements in b's STE vector are found in a's STE vector. Used internally by the > containment operator for searchable encryption. Parameters [#parameters-56] | Name | Type | Description | | ---- | ------------------ | ----------------------------------------- | | `a` | `eql_v2_encrypted` | First encrypted value (container) | | `b` | `eql_v2_encrypted` | Second encrypted value (elements to find) | Returns [#returns-36] **Type:** `boolean` Boolean True if all elements of b are contained in a Note [#note-14] Each element of b must match both selector and value in a Variants [#variants-38] * eql\_v2."@>" *** ste_vec_contains(public.eql_v2_encrypted, eql_v2_encrypted) [#ste_vec_containspubliceql_v2_encrypted-eql_v2_encrypted] Check if STE vector array contains a specific encrypted element. Tests whether any element in the STE vector array 'a' contains the encrypted value 'b'. Matching requires both the selector and encrypted value to be equal. Used internally by ste\_vec\_contains(encrypted, encrypted) for array containment checks. Parameters [#parameters-57] | Name | Type | Description | | ---- | ------------------------- | --------------------- | | `a` | `public.eql_v2_encrypted` | | | `b` | `eql_v2_encrypted` | element to search for | Returns [#returns-37] **Type:** `boolean` Boolean True if b is found in any element of a Note [#note-15] Compares both selector and encrypted value for match Variants [#variants-39] * [`ste_vec_contains(eql_v2_encrypted, eql_v2_encrypted)`](#ste-vec-containseql-v2-encrypted-eql-v2-encrypted) *** to_encrypted(text) [#to_encryptedtext] Convert text to encrypted type. Parses a text representation of encrypted JSONB payload and wraps it in the eql\_v2\_encrypted composite type. Parameters [#parameters-58] | Name | Type | Description | | ------ | ------ | ----------------------------------------- | | `data` | `text` | representation of JSONB encrypted payload | Returns [#returns-38] **Type:** `public.eql_v2_encrypted` eql\_v2\_encrypted Encrypted value wrapped in composite type Note [#note-16] Delegates to eql\_v2.to\_encrypted(jsonb) after parsing text as JSON Variants [#variants-40] * `to_encrypted(jsonb)` *** to_jsonb(eql_v2_encrypted) [#to_jsonbeql_v2_encrypted] Convert encrypted type to JSONB. Extracts the underlying JSONB payload from an eql\_v2\_encrypted composite type. Useful for debugging or when raw encrypted payload access is needed. Parameters [#parameters-59] | Name | Type | Description | | ----------- | ------------------ | ----------- | | `e public.` | `eql_v2_encrypted` | | Returns [#returns-39] **Type:** `jsonb` jsonb Raw JSONB encrypted payload Note [#note-17] Returns the raw encrypted structure including ciphertext and index terms Variants [#variants-41] * `to_encrypted(jsonb)` *** to_ste_vec_value(jsonb) [#to_ste_vec_valuejsonb] Convert single-element STE vector to regular encrypted value. Convert single-element STE vector to regular encrypted value (encrypted type) Extracts the single element from a single-element STE vector and returns it as a regular encrypted value, preserving metadata. If the input is not a single-element STE vector, returns it unchanged. Parameters [#parameters-60] | Name | Type | Description | | ----- | ------- | --------------------- | | `val` | `jsonb` | encrypted EQL payload | Returns [#returns-40] **Type:** `eql_v2_encrypted` eql\_v2\_encrypted Regular encrypted value (unwrapped if single-element STE vector) Variants [#variants-42] * [`to_ste_vec_value(jsonb)`](#to-ste-vec-valuejsonb) *** Private Functions [#private-functions-1] _encrypted_check_c(jsonb) [#_encrypted_check_cjsonb] Validate ciphertext field in encrypted payload. Parameters [#parameters-61] | Name | Type | Description | | ----- | ------- | ----------- | | `val` | `jsonb` | | *** _encrypted_check_i_ct(jsonb) [#_encrypted_check_i_ctjsonb] Validate table and column fields in ident. Parameters [#parameters-62] | Name | Type | Description | | ----- | ------- | ----------- | | `val` | `jsonb` | | *** _encrypted_check_v(jsonb) [#_encrypted_check_vjsonb] Validate version field in encrypted payload. Parameters [#parameters-63] | Name | Type | Description | | ----- | ------- | ----------- | | `val` | `jsonb` | | *** _first_grouped_value() [#_first_grouped_value] State transition function for grouped\_value aggregate. Parameters [#parameters-64] | Name | Type | Description | | ------- | ---- | ----------- | | `jsonb` | | | | `jsonb` | | | *** # @cipherstash/stack **@cipherstash/stack** *** @cipherstash/stack [#cipherstashstack] Modules [#modules] * [client](/docs/reference/stack/latest/client/index) * [drizzle](/docs/reference/stack/latest/drizzle/index) * [dynamodb](/docs/reference/stack/latest/dynamodb/index) * [encryption](/docs/reference/stack/latest/encryption/index) * [identity](/docs/reference/stack/latest/identity/index) * [schema](/docs/reference/stack/latest/schema/index) * [secrets](/docs/reference/stack/latest/secrets/index) * [supabase](/docs/reference/stack/latest/supabase/index) * [types-public](/docs/reference/stack/latest/types-public/index) # client [**@cipherstash/stack**](../index) *** client [#client] References [#references] encryptedTable [#encryptedtable] Re-exports [encryptedTable](/docs/reference/stack/latest/schema/functions/encryptedTable) *** encryptedColumn [#encryptedcolumn] Re-exports [encryptedColumn](/docs/reference/stack/latest/schema/functions/encryptedColumn) *** encryptedField [#encryptedfield] Re-exports [encryptedField](/docs/reference/stack/latest/schema/functions/encryptedField) *** EncryptedColumn [#encryptedcolumn-1] Re-exports [EncryptedColumn](/docs/reference/stack/latest/schema/classes/EncryptedColumn) *** EncryptedTable [#encryptedtable-1] Re-exports [EncryptedTable](/docs/reference/stack/latest/schema/classes/EncryptedTable) *** EncryptedTableColumn [#encryptedtablecolumn] Re-exports [EncryptedTableColumn](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn) *** EncryptedField [#encryptedfield-1] Re-exports [EncryptedField](/docs/reference/stack/latest/schema/classes/EncryptedField) *** InferPlaintext [#inferplaintext] Re-exports [InferPlaintext](/docs/reference/stack/latest/schema/type-aliases/InferPlaintext) *** InferEncrypted [#inferencrypted] Re-exports [InferEncrypted](/docs/reference/stack/latest/schema/type-aliases/InferEncrypted) *** EncryptionClient [#encryptionclient] Re-exports [EncryptionClient](/docs/reference/stack/latest/encryption/classes/EncryptionClient) # drizzle [**@cipherstash/stack**](../index) *** drizzle [#drizzle] Classes [#classes] * [EncryptionOperatorError](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError) * [EncryptionConfigError](/docs/reference/stack/latest/drizzle/classes/EncryptionConfigError) Type Aliases [#type-aliases] * [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig) Functions [#functions] * [encryptedType](/docs/reference/stack/latest/drizzle/functions/encryptedType) * [createEncryptionOperators](/docs/reference/stack/latest/drizzle/functions/createEncryptionOperators) * [extractEncryptionSchema](/docs/reference/stack/latest/drizzle/functions/extractEncryptionSchema) References [#references] CastAs [#castas] Re-exports [CastAs](/docs/reference/stack/latest/schema/type-aliases/CastAs) *** MatchIndexOpts [#matchindexopts] Re-exports [MatchIndexOpts](/docs/reference/stack/latest/schema/type-aliases/MatchIndexOpts) *** TokenFilter [#tokenfilter] Re-exports [TokenFilter](/docs/reference/stack/latest/schema/type-aliases/TokenFilter) # dynamodb [**@cipherstash/stack**](../index) *** dynamodb [#dynamodb] Interfaces [#interfaces] * [EncryptedDynamoDBConfig](/docs/reference/stack/latest/dynamodb/interfaces/EncryptedDynamoDBConfig) * [EncryptedDynamoDBError](/docs/reference/stack/latest/dynamodb/interfaces/EncryptedDynamoDBError) * [EncryptedDynamoDBInstance](/docs/reference/stack/latest/dynamodb/interfaces/EncryptedDynamoDBInstance) Functions [#functions] * [encryptedDynamoDB](/docs/reference/stack/latest/dynamodb/functions/encryptedDynamoDB) # encryption [**@cipherstash/stack**](../index) *** encryption [#encryption] Classes [#classes] * [EncryptionClient](/docs/reference/stack/latest/encryption/classes/EncryptionClient) Functions [#functions] * [noClientError](/docs/reference/stack/latest/encryption/functions/noClientError) # identity [**@cipherstash/stack**](../index) *** identity [#identity] Classes [#classes] * [LockContext](/docs/reference/stack/latest/identity/classes/LockContext) Type Aliases [#type-aliases] * [CtsRegions](/docs/reference/stack/latest/identity/type-aliases/CtsRegions) * [IdentifyOptions](/docs/reference/stack/latest/identity/type-aliases/IdentifyOptions) * [CtsToken](/docs/reference/stack/latest/identity/type-aliases/CtsToken) * [Context](/docs/reference/stack/latest/identity/type-aliases/Context) * [LockContextOptions](/docs/reference/stack/latest/identity/type-aliases/LockContextOptions) * [GetLockContextResponse](/docs/reference/stack/latest/identity/type-aliases/GetLockContextResponse) # schema [**@cipherstash/stack**](../index) *** schema [#schema] Classes [#classes] * [EncryptedField](/docs/reference/stack/latest/schema/classes/EncryptedField) * [EncryptedColumn](/docs/reference/stack/latest/schema/classes/EncryptedColumn) * [EncryptedTable](/docs/reference/stack/latest/schema/classes/EncryptedTable) Type Aliases [#type-aliases] * [CastAs](/docs/reference/stack/latest/schema/type-aliases/CastAs) * [TokenFilter](/docs/reference/stack/latest/schema/type-aliases/TokenFilter) * [MatchIndexOpts](/docs/reference/stack/latest/schema/type-aliases/MatchIndexOpts) * [SteVecIndexOpts](/docs/reference/stack/latest/schema/type-aliases/SteVecIndexOpts) * [UniqueIndexOpts](/docs/reference/stack/latest/schema/type-aliases/UniqueIndexOpts) * [OreIndexOpts](/docs/reference/stack/latest/schema/type-aliases/OreIndexOpts) * [ColumnSchema](/docs/reference/stack/latest/schema/type-aliases/ColumnSchema) * [EncryptedTableColumn](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn) * [EncryptConfig](/docs/reference/stack/latest/schema/type-aliases/EncryptConfig) * [InferPlaintext](/docs/reference/stack/latest/schema/type-aliases/InferPlaintext) * [InferEncrypted](/docs/reference/stack/latest/schema/type-aliases/InferEncrypted) Functions [#functions] * [encryptedTable](/docs/reference/stack/latest/schema/functions/encryptedTable) * [encryptedColumn](/docs/reference/stack/latest/schema/functions/encryptedColumn) * [encryptedField](/docs/reference/stack/latest/schema/functions/encryptedField) * [buildEncryptConfig](/docs/reference/stack/latest/schema/functions/buildEncryptConfig) # secrets [**@cipherstash/stack**](../index) *** secrets [#secrets] Classes [#classes] * [Secrets](/docs/reference/stack/latest/secrets/classes/Secrets) Interfaces [#interfaces] * [SecretsError](/docs/reference/stack/latest/secrets/interfaces/SecretsError) * [SecretsConfig](/docs/reference/stack/latest/secrets/interfaces/SecretsConfig) * [SecretMetadata](/docs/reference/stack/latest/secrets/interfaces/SecretMetadata) * [ListSecretsResponse](/docs/reference/stack/latest/secrets/interfaces/ListSecretsResponse) * [GetSecretResponse](/docs/reference/stack/latest/secrets/interfaces/GetSecretResponse) * [SetSecretResponse](/docs/reference/stack/latest/secrets/interfaces/SetSecretResponse) * [SetSecretRequest](/docs/reference/stack/latest/secrets/interfaces/SetSecretRequest) * [DeleteSecretResponse](/docs/reference/stack/latest/secrets/interfaces/DeleteSecretResponse) * [DeleteSecretRequest](/docs/reference/stack/latest/secrets/interfaces/DeleteSecretRequest) * [PlanLimitError](/docs/reference/stack/latest/secrets/interfaces/PlanLimitError) * [DecryptedSecretResponse](/docs/reference/stack/latest/secrets/interfaces/DecryptedSecretResponse) Type Aliases [#type-aliases] * [SecretName](/docs/reference/stack/latest/secrets/type-aliases/SecretName) * [SecretValue](/docs/reference/stack/latest/secrets/type-aliases/SecretValue) * [SecretsErrorType](/docs/reference/stack/latest/secrets/type-aliases/SecretsErrorType) * [GetManySecretsResponse](/docs/reference/stack/latest/secrets/type-aliases/GetManySecretsResponse) # supabase [**@cipherstash/stack**](../index) *** supabase [#supabase] Interfaces [#interfaces] * [EncryptedSupabaseInstance](/docs/reference/stack/latest/supabase/interfaces/EncryptedSupabaseInstance) * [SupabaseClientLike](/docs/reference/stack/latest/supabase/interfaces/SupabaseClientLike) * [EncryptedQueryBuilder](/docs/reference/stack/latest/supabase/interfaces/EncryptedQueryBuilder) Type Aliases [#type-aliases] * [EncryptedSupabaseConfig](/docs/reference/stack/latest/supabase/type-aliases/EncryptedSupabaseConfig) * [EncryptedSupabaseResponse](/docs/reference/stack/latest/supabase/type-aliases/EncryptedSupabaseResponse) * [EncryptedSupabaseError](/docs/reference/stack/latest/supabase/type-aliases/EncryptedSupabaseError) * [PendingOrCondition](/docs/reference/stack/latest/supabase/type-aliases/PendingOrCondition) Functions [#functions] * [encryptedSupabase](/docs/reference/stack/latest/supabase/functions/encryptedSupabase) # types-public [**@cipherstash/stack**](../index) *** types-public [#types-public] Type Aliases [#type-aliases] * [Client](/docs/reference/stack/latest/types-public/type-aliases/Client) * [EncryptedValue](/docs/reference/stack/latest/types-public/type-aliases/EncryptedValue) * [Encrypted](/docs/reference/stack/latest/types-public/type-aliases/Encrypted) * [KeysetIdentifier](/docs/reference/stack/latest/types-public/type-aliases/KeysetIdentifier) * [ClientConfig](/docs/reference/stack/latest/types-public/type-aliases/ClientConfig) * [EncryptionClientConfig](/docs/reference/stack/latest/types-public/type-aliases/EncryptionClientConfig) * [EncryptOptions](/docs/reference/stack/latest/types-public/type-aliases/EncryptOptions) * [EncryptedReturnType](/docs/reference/stack/latest/types-public/type-aliases/EncryptedReturnType) * [SearchTerm](/docs/reference/stack/latest/types-public/type-aliases/SearchTerm) * [EncryptedSearchTerm](/docs/reference/stack/latest/types-public/type-aliases/EncryptedSearchTerm) * [EncryptedQueryResult](/docs/reference/stack/latest/types-public/type-aliases/EncryptedQueryResult) * [EncryptedFields](/docs/reference/stack/latest/types-public/type-aliases/EncryptedFields) * [OtherFields](/docs/reference/stack/latest/types-public/type-aliases/OtherFields) * [DecryptedFields](/docs/reference/stack/latest/types-public/type-aliases/DecryptedFields) * [Decrypted](/docs/reference/stack/latest/types-public/type-aliases/Decrypted) * [BulkEncryptPayload](/docs/reference/stack/latest/types-public/type-aliases/BulkEncryptPayload) * [BulkEncryptedData](/docs/reference/stack/latest/types-public/type-aliases/BulkEncryptedData) * [BulkDecryptPayload](/docs/reference/stack/latest/types-public/type-aliases/BulkDecryptPayload) * [BulkDecryptedData](/docs/reference/stack/latest/types-public/type-aliases/BulkDecryptedData) * [DecryptionResult](/docs/reference/stack/latest/types-public/type-aliases/DecryptionResult) * [QueryTypeName](/docs/reference/stack/latest/types-public/type-aliases/QueryTypeName) * [EncryptQueryOptions](/docs/reference/stack/latest/types-public/type-aliases/EncryptQueryOptions) * [ScalarQueryTerm](/docs/reference/stack/latest/types-public/type-aliases/ScalarQueryTerm) * [LoggingConfig](/docs/reference/stack/latest/types-public/type-aliases/LoggingConfig) Variables [#variables] * [queryTypes](/docs/reference/stack/latest/types-public/variables/queryTypes) # EncryptionConfigError [**@cipherstash/stack**](../../index) *** Class: EncryptionConfigError [#class-encryptionconfigerror] Defined in: [.tmp-stack/packages/stack/src/drizzle/operators.ts:95](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/operators.ts#L95) Create Drizzle query operators (`eq`, `lt`, `gt`, etc.) that work with encrypted columns. The returned operators encrypt query values before passing them to Drizzle, enabling searchable encryption in standard Drizzle queries. Extends [#extends] * [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError) Constructors [#constructors] Constructor [#constructor] ```ts new EncryptionConfigError(message, context?): EncryptionConfigError; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/operators.ts:96](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/operators.ts#L96) Parameters [#parameters] message [#message] `string` context? [#context] tableName? [#tablename] `string` columnName? [#columnname] `string` operator? [#operator] `string` Returns [#returns] `EncryptionConfigError` Overrides [#overrides] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`constructor`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#constructor) Properties [#properties] context? [#context-1] ```ts readonly optional context: { tableName?: string; columnName?: string; operator?: string; }; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/operators.ts:84](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/operators.ts#L84) tableName? [#tablename-1] ```ts optional tableName: string; ``` columnName? [#columnname-1] ```ts optional columnName: string; ``` operator? [#operator-1] ```ts optional operator: string; ``` Inherited from [#inherited-from] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`context`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#context) *** stackTraceLimit [#stacktracelimit] ```ts static stackTraceLimit: number; ``` Defined in: .tmp-stack/node\_modules/.pnpm/@types+node\@22.19.3/node\_modules/@types/node/globals.d.ts:68 The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. Inherited from [#inherited-from-1] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`stackTraceLimit`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#stacktracelimit) *** cause? [#cause] ```ts optional cause: unknown; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 Inherited from [#inherited-from-2] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`cause`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#cause) *** name [#name] ```ts name: string; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 Inherited from [#inherited-from-3] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`name`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#name) *** message [#message-1] ```ts message: string; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 Inherited from [#inherited-from-4] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`message`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#message) *** stack? [#stack] ```ts optional stack: string; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 Inherited from [#inherited-from-5] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`stack`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#stack) Methods [#methods] captureStackTrace() [#capturestacktrace] ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: .tmp-stack/node\_modules/.pnpm/@types+node\@22.19.3/node\_modules/@types/node/globals.d.ts:52 Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. ```js const myObject = {}; Error.captureStackTrace(myObject); myObject.stack; // Similar to `new Error().stack` ``` The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`. The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace. The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance: ```js function a() { b(); } function b() { c(); } function c() { // Create an error without stack trace to avoid calculating the stack trace twice. const { stackTraceLimit } = Error; Error.stackTraceLimit = 0; const error = new Error(); Error.stackTraceLimit = stackTraceLimit; // Capture the stack trace above function b Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace throw error; } a(); ``` Parameters [#parameters-1] targetObject [#targetobject] `object` constructorOpt? [#constructoropt] `Function` Returns [#returns-1] `void` Inherited from [#inherited-from-6] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`captureStackTrace`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#capturestacktrace) *** prepareStackTrace() [#preparestacktrace] ```ts static prepareStackTrace(err, stackTraces): any; ``` Defined in: .tmp-stack/node\_modules/.pnpm/@types+node\@22.19.3/node\_modules/@types/node/globals.d.ts:56 Parameters [#parameters-2] err [#err] `Error` stackTraces [#stacktraces] `CallSite`\[] Returns [#returns-2] `any` See [#see] [https://v8.dev/docs/stack-trace-api#customizing-stack-traces](https://v8.dev/docs/stack-trace-api#customizing-stack-traces) Inherited from [#inherited-from-7] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`prepareStackTrace`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#preparestacktrace) *** isError() [#iserror] ```ts static isError(error): error is Error; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:23 Indicates whether the argument provided is a built-in Error instance or not. Parameters [#parameters-3] error [#error] `unknown` Returns [#returns-3] `error is Error` Inherited from [#inherited-from-8] [`EncryptionOperatorError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError).[`isError`](/docs/reference/stack/latest/drizzle/classes/EncryptionOperatorError.mdx#iserror) # EncryptionOperatorError [**@cipherstash/stack**](../../index) *** Class: EncryptionOperatorError [#class-encryptionoperatorerror] Defined in: [.tmp-stack/packages/stack/src/drizzle/operators.ts:81](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/operators.ts#L81) Custom error types for better debugging Extends [#extends] * `Error` Extended by [#extended-by] * [`EncryptionConfigError`](/docs/reference/stack/latest/drizzle/classes/EncryptionConfigError) Constructors [#constructors] Constructor [#constructor] ```ts new EncryptionOperatorError(message, context?): EncryptionOperatorError; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/operators.ts:82](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/operators.ts#L82) Parameters [#parameters] message [#message] `string` context? [#context] tableName? [#tablename] `string` columnName? [#columnname] `string` operator? [#operator] `string` Returns [#returns] `EncryptionOperatorError` Overrides [#overrides] ```ts Error.constructor ``` Properties [#properties] context? [#context-1] ```ts readonly optional context: { tableName?: string; columnName?: string; operator?: string; }; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/operators.ts:84](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/operators.ts#L84) tableName? [#tablename-1] ```ts optional tableName: string; ``` columnName? [#columnname-1] ```ts optional columnName: string; ``` operator? [#operator-1] ```ts optional operator: string; ``` *** stackTraceLimit [#stacktracelimit] ```ts static stackTraceLimit: number; ``` Defined in: .tmp-stack/node\_modules/.pnpm/@types+node\@22.19.3/node\_modules/@types/node/globals.d.ts:68 The `Error.stackTraceLimit` property specifies the number of stack frames collected by a stack trace (whether generated by `new Error().stack` or `Error.captureStackTrace(obj)`). The default value is `10` but may be set to any valid JavaScript number. Changes will affect any stack trace captured *after* the value has been changed. If set to a non-number value, or set to a negative number, stack traces will not capture any frames. Inherited from [#inherited-from] ```ts Error.stackTraceLimit ``` *** cause? [#cause] ```ts optional cause: unknown; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 Inherited from [#inherited-from-1] ```ts Error.cause ``` *** name [#name] ```ts name: string; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 Inherited from [#inherited-from-2] ```ts Error.name ``` *** message [#message-1] ```ts message: string; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 Inherited from [#inherited-from-3] ```ts Error.message ``` *** stack? [#stack] ```ts optional stack: string; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 Inherited from [#inherited-from-4] ```ts Error.stack ``` Methods [#methods] captureStackTrace() [#capturestacktrace] ```ts static captureStackTrace(targetObject, constructorOpt?): void; ``` Defined in: .tmp-stack/node\_modules/.pnpm/@types+node\@22.19.3/node\_modules/@types/node/globals.d.ts:52 Creates a `.stack` property on `targetObject`, which when accessed returns a string representing the location in the code at which `Error.captureStackTrace()` was called. ```js const myObject = {}; Error.captureStackTrace(myObject); myObject.stack; // Similar to `new Error().stack` ``` The first line of the trace will be prefixed with `${myObject.name}: ${myObject.message}`. The optional `constructorOpt` argument accepts a function. If given, all frames above `constructorOpt`, including `constructorOpt`, will be omitted from the generated stack trace. The `constructorOpt` argument is useful for hiding implementation details of error generation from the user. For instance: ```js function a() { b(); } function b() { c(); } function c() { // Create an error without stack trace to avoid calculating the stack trace twice. const { stackTraceLimit } = Error; Error.stackTraceLimit = 0; const error = new Error(); Error.stackTraceLimit = stackTraceLimit; // Capture the stack trace above function b Error.captureStackTrace(error, b); // Neither function c, nor b is included in the stack trace throw error; } a(); ``` Parameters [#parameters-1] targetObject [#targetobject] `object` constructorOpt? [#constructoropt] `Function` Returns [#returns-1] `void` Inherited from [#inherited-from-5] ```ts Error.captureStackTrace ``` *** prepareStackTrace() [#preparestacktrace] ```ts static prepareStackTrace(err, stackTraces): any; ``` Defined in: .tmp-stack/node\_modules/.pnpm/@types+node\@22.19.3/node\_modules/@types/node/globals.d.ts:56 Parameters [#parameters-2] err [#err] `Error` stackTraces [#stacktraces] `CallSite`\[] Returns [#returns-2] `any` See [#see] [https://v8.dev/docs/stack-trace-api#customizing-stack-traces](https://v8.dev/docs/stack-trace-api#customizing-stack-traces) Inherited from [#inherited-from-6] ```ts Error.prepareStackTrace ``` *** isError() [#iserror] ```ts static isError(error): error is Error; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.esnext.error.d.ts:23 Indicates whether the argument provided is a built-in Error instance or not. Parameters [#parameters-3] error [#error] `unknown` Returns [#returns-3] `error is Error` Inherited from [#inherited-from-7] ```ts Error.isError ``` # createEncryptionOperators [**@cipherstash/stack**](../../index) *** Function: createEncryptionOperators() [#function-createencryptionoperators] ```ts function createEncryptionOperators(encryptionClient): { eq: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ne: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; gt: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; gte: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; lt: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; lte: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; between: (left, min, max) => SQL<unknown> | Promise<SQL<unknown>>; notBetween: (left, min, max) => SQL<unknown> | Promise<SQL<unknown>>; like: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ilike: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; notIlike: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; jsonbPathQueryFirst: (left, right) => Promise<SQL<unknown>>; jsonbGet: (left, right) => Promise<SQL<unknown>>; jsonbPathExists: (left, right) => Promise<SQL<unknown>>; inArray: (left, right) => Promise<SQL<unknown>>; notInArray: (left, right) => Promise<SQL<unknown>>; asc: (column) => SQL; desc: (column) => SQL; and: (...conditions) => Promise<SQL<unknown>>; or: (...conditions) => Promise<SQL<unknown>>; exists: (subquery) => SQL; notExists: (subquery) => SQL; isNull: (value) => SQL; isNotNull: (value) => SQL; not: (condition) => SQL; arrayContains: { <T> (column, values): SQL; <TColumn> (column, values): SQL; <T> (column, values): SQL; }; arrayContained: { <T> (column, values): SQL; <TColumn> (column, values): SQL; <T> (column, values): SQL; }; arrayOverlaps: { <T> (column, values): SQL; <TColumn> (column, values): SQL; <T> (column, values): SQL; }; }; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/operators.ts:975](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/operators.ts#L975) Creates a set of encryption-aware operators that automatically encrypt values for encrypted columns before using them with Drizzle operators. For equality and text search operators (eq, ne, like, ilike, inArray, etc.): Values are encrypted and then passed to regular Drizzle operators, which use PostgreSQL's built-in operators for eql\_v2\_encrypted types. For order and range operators (gt, gte, lt, lte, between, notBetween): Values are encrypted and then use eql\_v2.\* functions (eql\_v2.gt(), eql\_v2.gte(), etc.) which are required for ORE (Order-Revealing Encryption) comparisons. Parameters [#parameters] encryptionClient [#encryptionclient] [`EncryptionClient`](/docs/reference/stack/latest/encryption/classes/EncryptionClient) The EncryptionClient instance Returns [#returns] An object with all Drizzle operators wrapped for encrypted columns eq() [#eq] ```ts eq: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ``` Equality operator - encrypts value for encrypted columns. Requires either `equality` or `orderAndRange` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Parameters [#parameters-1] left [#left] `SQLWrapper` right [#right] `unknown` Returns [#returns-1] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example] Select users with a specific email address. ```ts const condition = await ops.eq(usersTable.email, 'user@example.com') const results = await db.select().from(usersTable).where(condition) ``` ne() [#ne] ```ts ne: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ``` Not equal operator - encrypts value for encrypted columns. Requires either `equality` or `orderAndRange` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Parameters [#parameters-2] left [#left-1] `SQLWrapper` right [#right-1] `unknown` Returns [#returns-2] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example-1] Select users whose email address is not a specific value. ```ts const condition = await ops.ne(usersTable.email, 'user@example.com') const results = await db.select().from(usersTable).where(condition) ``` gt() [#gt] ```ts gt: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ``` Greater than operator for encrypted columns with ORE index. Requires `orderAndRange` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Parameters [#parameters-3] left [#left-2] `SQLWrapper` right [#right-2] `unknown` Returns [#returns-3] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example-2] Select users older than a specific age. ```ts const condition = await ops.gt(usersTable.age, 30) const results = await db.select().from(usersTable).where(condition) ``` gte() [#gte] ```ts gte: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ``` Greater than or equal operator for encrypted columns with ORE index. Requires `orderAndRange` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Parameters [#parameters-4] left [#left-3] `SQLWrapper` right [#right-3] `unknown` Returns [#returns-4] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example-3] Select users older than or equal to a specific age. ```ts const condition = await ops.gte(usersTable.age, 30) const results = await db.select().from(usersTable).where(condition) ``` lt() [#lt] ```ts lt: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ``` Less than operator for encrypted columns with ORE index. Requires `orderAndRange` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Parameters [#parameters-5] left [#left-4] `SQLWrapper` right [#right-4] `unknown` Returns [#returns-5] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example-4] Select users younger than a specific age. ```ts const condition = await ops.lt(usersTable.age, 30) const results = await db.select().from(usersTable).where(condition) ``` lte() [#lte] ```ts lte: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ``` Less than or equal operator for encrypted columns with ORE index. Requires `orderAndRange` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Parameters [#parameters-6] left [#left-5] `SQLWrapper` right [#right-5] `unknown` Returns [#returns-6] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example-5] Select users younger than or equal to a specific age. ```ts const condition = await ops.lte(usersTable.age, 30) const results = await db.select().from(usersTable).where(condition) ``` between() [#between] ```ts between: (left, min, max) => SQL<unknown> | Promise<SQL<unknown>>; ``` Between operator for encrypted columns with ORE index. Requires `orderAndRange` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Parameters [#parameters-7] left [#left-6] `SQLWrapper` min [#min] `unknown` max [#max] `unknown` Returns [#returns-7] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example-6] Select users within a specific age range. ```ts const condition = await ops.between(usersTable.age, 20, 30) const results = await db.select().from(usersTable).where(condition) ``` notBetween() [#notbetween] ```ts notBetween: (left, min, max) => SQL<unknown> | Promise<SQL<unknown>>; ``` Not between operator for encrypted columns with ORE index. Requires `orderAndRange` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Parameters [#parameters-8] left [#left-7] `SQLWrapper` min [#min-1] `unknown` max [#max-1] `unknown` Returns [#returns-8] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example-7] Select users outside a specific age range. ```ts const condition = await ops.notBetween(usersTable.age, 20, 30) const results = await db.select().from(usersTable).where(condition) ``` like() [#like] ```ts like: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ``` Like operator for encrypted columns with free text search. Requires `freeTextSearch` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). > \[!IMPORTANT] > Case sensitivity on encrypted columns depends on the [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). > Ensure that the column is configured for case-insensitive search if needed. Parameters [#parameters-9] left [#left-8] `SQLWrapper` right [#right-6] `unknown` Returns [#returns-9] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example-8] Select users with email addresses matching a pattern. ```ts const condition = await ops.like(usersTable.email, '%@example.com') const results = await db.select().from(usersTable).where(condition) ``` ilike() [#ilike] ```ts ilike: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ``` ILike operator for encrypted columns with free text search. Requires `freeTextSearch` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). > \[!IMPORTANT] > Case sensitivity on encrypted columns depends on the [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). > Ensure that the column is configured for case-insensitive search if needed. Parameters [#parameters-10] left [#left-9] `SQLWrapper` right [#right-7] `unknown` Returns [#returns-10] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> Example [#example-9] Select users with email addresses matching a pattern (case-insensitive). ```ts const condition = await ops.ilike(usersTable.email, '%@example.com') const results = await db.select().from(usersTable).where(condition) ``` notIlike() [#notilike] ```ts notIlike: (left, right) => SQL<unknown> | Promise<SQL<unknown>>; ``` Parameters [#parameters-11] left [#left-10] `SQLWrapper` right [#right-8] `unknown` Returns [#returns-11] `SQL`\<`unknown`> | `Promise`\<`SQL`\<`unknown`>> jsonbPathQueryFirst() [#jsonbpathqueryfirst] ```ts jsonbPathQueryFirst: (left, right) => Promise<SQL<unknown>>; ``` JSONB path query first operator for encrypted columns with searchable JSON. Requires `searchableJson` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Encrypts the JSON path selector and calls `eql_v2.jsonb_path_query_first()`, casting the parameter to `eql_v2_encrypted`. Parameters [#parameters-12] left [#left-11] `SQLWrapper` right [#right-9] `unknown` Returns [#returns-12] `Promise`\<`SQL`\<`unknown`>> Throws [#throws] If the column does not have `searchableJson` enabled. jsonbGet() [#jsonbget] ```ts jsonbGet: (left, right) => Promise<SQL<unknown>>; ``` JSONB get operator for encrypted columns with searchable JSON. Requires `searchableJson` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Encrypts the JSON path selector and uses the `->` operator, casting the parameter to `eql_v2_encrypted`. Parameters [#parameters-13] left [#left-12] `SQLWrapper` right [#right-10] `unknown` Returns [#returns-13] `Promise`\<`SQL`\<`unknown`>> Throws [#throws-1] If the column does not have `searchableJson` enabled. jsonbPathExists() [#jsonbpathexists] ```ts jsonbPathExists: (left, right) => Promise<SQL<unknown>>; ``` JSONB path exists operator for encrypted columns with searchable JSON. Requires `searchableJson` to be set on [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Encrypts the JSON path selector and calls `eql_v2.jsonb_path_exists()`, casting the parameter to `eql_v2_encrypted`. Parameters [#parameters-14] left [#left-13] `SQLWrapper` right [#right-11] `unknown` Returns [#returns-14] `Promise`\<`SQL`\<`unknown`>> Throws [#throws-2] If the column does not have `searchableJson` enabled. inArray() [#inarray] ```ts inArray: (left, right) => Promise<SQL<unknown>>; ``` Parameters [#parameters-15] left [#left-14] `SQLWrapper` right [#right-12] `unknown`\[] | `SQLWrapper` Returns [#returns-15] `Promise`\<`SQL`\<`unknown`>> notInArray() [#notinarray] ```ts notInArray: (left, right) => Promise<SQL<unknown>>; ``` Parameters [#parameters-16] left [#left-15] `SQLWrapper` right [#right-13] `unknown`\[] | `SQLWrapper` Returns [#returns-16] `Promise`\<`SQL`\<`unknown`>> asc() [#asc] ```ts asc: (column) => SQL; ``` Parameters [#parameters-17] column [#column] `SQLWrapper` Returns [#returns-17] `SQL` desc() [#desc] ```ts desc: (column) => SQL; ``` Parameters [#parameters-18] column [#column-1] `SQLWrapper` Returns [#returns-18] `SQL` and() [#and] ```ts and: (...conditions) => Promise<SQL<unknown>>; ``` Parameters [#parameters-19] conditions [#conditions] ...( \| `SQLWrapper` \| `SQL`\<`unknown`> \| `Promise`\<`SQL`\<`unknown`>> \| `undefined`)\[] Returns [#returns-19] `Promise`\<`SQL`\<`unknown`>> or() [#or] ```ts or: (...conditions) => Promise<SQL<unknown>>; ``` Parameters [#parameters-20] conditions [#conditions-1] ...( \| `SQLWrapper` \| `SQL`\<`unknown`> \| `Promise`\<`SQL`\<`unknown`>> \| `undefined`)\[] Returns [#returns-20] `Promise`\<`SQL`\<`unknown`>> exists() [#exists] ```ts exists: (subquery) => SQL; ``` Test whether a subquery evaluates to have any rows. Examples [#examples] ```ts // Users whose `homeCity` column has a match in a cities // table. db .select() .from(users) .where( exists(db.select() .from(cities) .where(eq(users.homeCity, cities.id))), ); ``` Parameters [#parameters-21] subquery [#subquery] `SQLWrapper` Returns [#returns-21] `SQL` See [#see] notExists for the inverse of this test notExists() [#notexists] ```ts notExists: (subquery) => SQL; ``` Test whether a subquery doesn't include any result rows. Examples [#examples-1] ```ts // Users whose `homeCity` column doesn't match // a row in the cities table. db .select() .from(users) .where( notExists(db.select() .from(cities) .where(eq(users.homeCity, cities.id))), ); ``` Parameters [#parameters-22] subquery [#subquery-1] `SQLWrapper` Returns [#returns-22] `SQL` See [#see-1] exists for the inverse of this test isNull() [#isnull] ```ts isNull: (value) => SQL; ``` Test whether an expression is NULL. By the SQL standard, NULL is neither equal nor not equal to itself, so it's recommended to use `isNull` and `notIsNull` for comparisons to NULL. Examples [#examples-2] ```ts // Select cars that have no discontinuedAt date. db.select().from(cars) .where(isNull(cars.discontinuedAt)) ``` Parameters [#parameters-23] value [#value] `SQLWrapper` Returns [#returns-23] `SQL` See [#see-2] isNotNull for the inverse of this test isNotNull() [#isnotnull] ```ts isNotNull: (value) => SQL; ``` Test whether an expression is not NULL. By the SQL standard, NULL is neither equal nor not equal to itself, so it's recommended to use `isNull` and `notIsNull` for comparisons to NULL. Examples [#examples-3] ```ts // Select cars that have been discontinued. db.select().from(cars) .where(isNotNull(cars.discontinuedAt)) ``` Parameters [#parameters-24] value [#value-1] `SQLWrapper` Returns [#returns-24] `SQL` See [#see-3] isNull for the inverse of this test not() [#not] ```ts not: (condition) => SQL; ``` Negate the meaning of an expression using the `not` keyword. Examples [#examples-4] ```ts // Select cars _not_ made by GM or Ford. db.select().from(cars) .where(not(inArray(cars.make, ['GM', 'Ford']))) ``` Parameters [#parameters-25] condition [#condition] `SQLWrapper` Returns [#returns-25] `SQL` arrayContains() [#arraycontains] ```ts arrayContains: { <T> (column, values): SQL; <TColumn> (column, values): SQL; <T> (column, values): SQL; }; ``` Call Signature [#call-signature] ```ts <T>(column, values): SQL; ``` Test that a column or expression contains all elements of the list passed as the second argument. Throws [#throws-3] The argument passed in the second array can't be empty: if an empty is provided, this method will throw. Examples [#examples-5] ```ts // Select posts where its tags contain "Typescript" and "ORM". db.select().from(posts) .where(arrayContains(posts.tags, ['Typescript', 'ORM'])) ``` Type Parameters [#type-parameters] T [#t] `T` Parameters [#parameters-26] column [#column-2] `Aliased`\<`T`> values [#values] `SQLWrapper` | `Placeholder`\<`string`, `any`> | `T` Returns [#returns-26] `SQL` See [#see-4] * arrayContained to find if an array contains all elements of a column or expression * arrayOverlaps to find if a column or expression contains any elements of an array Call Signature [#call-signature-1] ```ts <TColumn>(column, values): SQL; ``` Test that a column or expression contains all elements of the list passed as the second argument. Throws [#throws-4] The argument passed in the second array can't be empty: if an empty is provided, this method will throw. Examples [#examples-6] ```ts // Select posts where its tags contain "Typescript" and "ORM". db.select().from(posts) .where(arrayContains(posts.tags, ['Typescript', 'ORM'])) ``` Type Parameters [#type-parameters-1] TColumn [#tcolumn] `TColumn` *extends* `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`> Parameters [#parameters-27] column [#column-3] `TColumn` values [#values-1] `SQLWrapper` | `Placeholder`\<`string`, `any`> | `TColumn`\[`"_"`]\[`"data"`] Returns [#returns-27] `SQL` See [#see-5] * arrayContained to find if an array contains all elements of a column or expression * arrayOverlaps to find if a column or expression contains any elements of an array Call Signature [#call-signature-2] ```ts <T>(column, values): SQL; ``` Test that a column or expression contains all elements of the list passed as the second argument. Throws [#throws-5] The argument passed in the second array can't be empty: if an empty is provided, this method will throw. Examples [#examples-7] ```ts // Select posts where its tags contain "Typescript" and "ORM". db.select().from(posts) .where(arrayContains(posts.tags, ['Typescript', 'ORM'])) ``` Type Parameters [#type-parameters-2] T [#t-1] `T` *extends* `SQLWrapper` Parameters [#parameters-28] column [#column-4] `Exclude`\<`T`, \| `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`> \| `Aliased`\<`unknown`>> values [#values-2] `unknown`\[] | `SQLWrapper` Returns [#returns-28] `SQL` See [#see-6] * arrayContained to find if an array contains all elements of a column or expression * arrayOverlaps to find if a column or expression contains any elements of an array arrayContained() [#arraycontained] ```ts arrayContained: { <T> (column, values): SQL; <TColumn> (column, values): SQL; <T> (column, values): SQL; }; ``` Call Signature [#call-signature-3] ```ts <T>(column, values): SQL; ``` Test that the list passed as the second argument contains all elements of a column or expression. Throws [#throws-6] The argument passed in the second array can't be empty: if an empty is provided, this method will throw. Examples [#examples-8] ```ts // Select posts where its tags contain "Typescript", "ORM" or both, // but filtering posts that have additional tags. db.select().from(posts) .where(arrayContained(posts.tags, ['Typescript', 'ORM'])) ``` Type Parameters [#type-parameters-3] T [#t-2] `T` Parameters [#parameters-29] column [#column-5] `Aliased`\<`T`> values [#values-3] `SQLWrapper` | `Placeholder`\<`string`, `any`> | `T` Returns [#returns-29] `SQL` See [#see-7] * arrayContains to find if a column or expression contains all elements of an array * arrayOverlaps to find if a column or expression contains any elements of an array Call Signature [#call-signature-4] ```ts <TColumn>(column, values): SQL; ``` Test that the list passed as the second argument contains all elements of a column or expression. Throws [#throws-7] The argument passed in the second array can't be empty: if an empty is provided, this method will throw. Examples [#examples-9] ```ts // Select posts where its tags contain "Typescript", "ORM" or both, // but filtering posts that have additional tags. db.select().from(posts) .where(arrayContained(posts.tags, ['Typescript', 'ORM'])) ``` Type Parameters [#type-parameters-4] TColumn [#tcolumn-1] `TColumn` *extends* `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`> Parameters [#parameters-30] column [#column-6] `TColumn` values [#values-4] `SQLWrapper` | `Placeholder`\<`string`, `any`> | `TColumn`\[`"_"`]\[`"data"`] Returns [#returns-30] `SQL` See [#see-8] * arrayContains to find if a column or expression contains all elements of an array * arrayOverlaps to find if a column or expression contains any elements of an array Call Signature [#call-signature-5] ```ts <T>(column, values): SQL; ``` Test that the list passed as the second argument contains all elements of a column or expression. Throws [#throws-8] The argument passed in the second array can't be empty: if an empty is provided, this method will throw. Examples [#examples-10] ```ts // Select posts where its tags contain "Typescript", "ORM" or both, // but filtering posts that have additional tags. db.select().from(posts) .where(arrayContained(posts.tags, ['Typescript', 'ORM'])) ``` Type Parameters [#type-parameters-5] T [#t-3] `T` *extends* `SQLWrapper` Parameters [#parameters-31] column [#column-7] `Exclude`\<`T`, \| `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`> \| `Aliased`\<`unknown`>> values [#values-5] `unknown`\[] | `SQLWrapper` Returns [#returns-31] `SQL` See [#see-9] * arrayContains to find if a column or expression contains all elements of an array * arrayOverlaps to find if a column or expression contains any elements of an array arrayOverlaps() [#arrayoverlaps] ```ts arrayOverlaps: { <T> (column, values): SQL; <TColumn> (column, values): SQL; <T> (column, values): SQL; }; ``` Call Signature [#call-signature-6] ```ts <T>(column, values): SQL; ``` Test that a column or expression contains any elements of the list passed as the second argument. Throws [#throws-9] The argument passed in the second array can't be empty: if an empty is provided, this method will throw. Examples [#examples-11] ```ts // Select posts where its tags contain "Typescript", "ORM" or both. db.select().from(posts) .where(arrayOverlaps(posts.tags, ['Typescript', 'ORM'])) ``` Type Parameters [#type-parameters-6] T [#t-4] `T` Parameters [#parameters-32] column [#column-8] `Aliased`\<`T`> values [#values-6] `SQLWrapper` | `Placeholder`\<`string`, `any`> | `T` Returns [#returns-32] `SQL` See [#see-10] * arrayContains to find if a column or expression contains all elements of an array * arrayContained to find if an array contains all elements of a column or expression Call Signature [#call-signature-7] ```ts <TColumn>(column, values): SQL; ``` Test that a column or expression contains any elements of the list passed as the second argument. Throws [#throws-10] The argument passed in the second array can't be empty: if an empty is provided, this method will throw. Examples [#examples-12] ```ts // Select posts where its tags contain "Typescript", "ORM" or both. db.select().from(posts) .where(arrayOverlaps(posts.tags, ['Typescript', 'ORM'])) ``` Type Parameters [#type-parameters-7] TColumn [#tcolumn-2] `TColumn` *extends* `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`> Parameters [#parameters-33] column [#column-9] `TColumn` values [#values-7] `SQLWrapper` | `Placeholder`\<`string`, `any`> | `TColumn`\[`"_"`]\[`"data"`] Returns [#returns-33] `SQL` See [#see-11] * arrayContains to find if a column or expression contains all elements of an array * arrayContained to find if an array contains all elements of a column or expression Call Signature [#call-signature-8] ```ts <T>(column, values): SQL; ``` Test that a column or expression contains any elements of the list passed as the second argument. Throws [#throws-11] The argument passed in the second array can't be empty: if an empty is provided, this method will throw. Examples [#examples-13] ```ts // Select posts where its tags contain "Typescript", "ORM" or both. db.select().from(posts) .where(arrayOverlaps(posts.tags, ['Typescript', 'ORM'])) ``` Type Parameters [#type-parameters-8] T [#t-5] `T` *extends* `SQLWrapper` Parameters [#parameters-34] column [#column-10] `Exclude`\<`T`, \| `Column`\<`ColumnBaseConfig`\<`ColumnDataType`, `string`>, `object`, `object`> \| `Aliased`\<`unknown`>> values [#values-8] `unknown`\[] | `SQLWrapper` Returns [#returns-34] `SQL` See [#see-12] * arrayContains to find if a column or expression contains all elements of an array * arrayContained to find if an array contains all elements of a column or expression Example [#example-10] ```ts // Initialize operators const ops = createEncryptionOperators(encryptionClient) // Equality search - automatically encrypts and uses PostgreSQL operators const results = await db .select() .from(usersTable) .where(await ops.eq(usersTable.email, 'user@example.com')) // Range query - automatically encrypts and uses eql_v2.gte() const olderUsers = await db .select() .from(usersTable) .where(await ops.gte(usersTable.age, 25)) ``` # encryptedType [**@cipherstash/stack**](../../index) *** Function: encryptedType() [#function-encryptedtype] ```ts function encryptedType<TData>(name, config?): PgCustomColumnBuilder<{ name: string; dataType: "custom"; columnType: "PgCustomColumn"; data: TData; driverParam: string; enumValues: undefined; }>; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/index.ts:89](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/index.ts#L89) Creates an encrypted column type for Drizzle ORM with configurable searchable encryption options. When data is encrypted, the actual stored value is an [EQL v2](/docs/reference/eql) encrypted composite type which includes any searchable encryption indexes defined for the column. Importantly, the original data type is not known until it is decrypted. Therefore, this function allows specifying the original data type via the `dataType` option in the configuration. This ensures that when data is decrypted, it can be correctly interpreted as the intended TypeScript type. Type Parameters [#type-parameters] TData [#tdata] `TData` The TypeScript type of the data stored in the column Parameters [#parameters] name [#name] `string` The column name in the database config? [#config] [`EncryptedColumnConfig`](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig) Optional configuration for data type and searchable encryption indexes Returns [#returns] `PgCustomColumnBuilder`\<\{ `name`: `string`; `dataType`: `"custom"`; `columnType`: `"PgCustomColumn"`; `data`: `TData`; `driverParam`: `string`; `enumValues`: `undefined`; }> A Drizzle column type that can be used in pgTable definitions Searchable Encryption Options [#searchable-encryption-options] * `dataType`: Specifies the original data type of the column (e.g., 'string', 'number', 'json'). Default is 'string'. * `freeTextSearch`: Enables free text search index. Can be a boolean for default options, or an object for custom configuration. * `equality`: Enables equality index. Can be a boolean for default options, or an array of token filters. * `orderAndRange`: Enables order and range index for sorting and range queries. * `searchableJson`: Enables searchable JSON index for JSONB path queries on encrypted JSON columns. See [EncryptedColumnConfig](/docs/reference/stack/latest/drizzle/type-aliases/EncryptedColumnConfig). Example [#example] Defining a drizzle table schema for postgres table with encrypted columns. ```typescript import { pgTable, integer, timestamp } from 'drizzle-orm/pg-core' import { encryptedType } from '@cipherstash/stack/drizzle' const users = pgTable('users', { email: encryptedType('email', { freeTextSearch: true, equality: true, orderAndRange: true, }), age: encryptedType('age', { dataType: 'number', equality: true, orderAndRange: true, }), profile: encryptedType('profile', { dataType: 'json', }), }) ``` # extractEncryptionSchema [**@cipherstash/stack**](../../index) *** Function: extractEncryptionSchema() [#function-extractencryptionschema] ```ts function extractEncryptionSchema<T>(table): EncryptedTable<Record<string, EncryptedColumn>> & Record<string, EncryptedColumn>; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/schema-extraction.ts:26](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/schema-extraction.ts#L26) Extracts an encryption schema from a Drizzle table definition. This function identifies columns created with `encryptedType` and builds a corresponding `EncryptedTable` with `encryptedColumn` definitions. Type Parameters [#type-parameters] T [#t] `T` *extends* `PgTable`\<`any`> Parameters [#parameters] table [#table] `T` The Drizzle table definition Returns [#returns] [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<`Record`\<`string`, [`EncryptedColumn`](/docs/reference/stack/latest/schema/classes/EncryptedColumn)>> & `Record`\<`string`, [`EncryptedColumn`](/docs/reference/stack/latest/schema/classes/EncryptedColumn)> A EncryptedTable that can be used with encryption client initialization Example [#example] ```ts const drizzleUsersTable = pgTable('users', { email: encryptedType('email', { freeTextSearch: true, equality: true }), age: encryptedType('age', { dataType: 'number', orderAndRange: true }), }) const encryptionSchema = extractEncryptionSchema(drizzleUsersTable) const client = await createEncryptionClient({ schemas: [encryptionSchema.build()] }) ``` # EncryptedColumnConfig [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedColumnConfig [#type-alias-encryptedcolumnconfig] ```ts type EncryptedColumnConfig = { dataType?: CastAs; freeTextSearch?: | boolean | MatchIndexOpts; equality?: | boolean | TokenFilter[]; orderAndRange?: boolean; searchableJson?: boolean; }; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/index.ts:9](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/index.ts#L9) Configuration for encrypted column indexes and data types Properties [#properties] dataType? [#datatype] ```ts optional dataType: CastAs; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/index.ts:13](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/index.ts#L13) Data type for the column (default: 'string') *** freeTextSearch? [#freetextsearch] ```ts optional freeTextSearch: | boolean | MatchIndexOpts; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/index.ts:17](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/index.ts#L17) Enable free text search. Can be a boolean for default options, or an object for custom configuration. *** equality? [#equality] ```ts optional equality: | boolean | TokenFilter[]; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/index.ts:21](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/index.ts#L21) Enable equality index. Can be a boolean for default options, or an array of token filters. *** orderAndRange? [#orderandrange] ```ts optional orderAndRange: boolean; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/index.ts:25](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/index.ts#L25) Enable order and range index for sorting and range queries. *** searchableJson? [#searchablejson] ```ts optional searchableJson: boolean; ``` Defined in: [.tmp-stack/packages/stack/src/drizzle/index.ts:30](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/drizzle/index.ts#L30) Enable searchable JSON index for JSONB path queries. Requires dataType: 'json'. # encryptedDynamoDB [**@cipherstash/stack**](../../index) *** Function: encryptedDynamoDB() [#function-encrypteddynamodb] ```ts function encryptedDynamoDB(config): EncryptedDynamoDBInstance; ``` Defined in: [.tmp-stack/packages/stack/src/dynamodb/index.ts:39](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/index.ts#L39) Create an encrypted DynamoDB helper bound to an `EncryptionClient`. Returns an object with `encryptModel`, `decryptModel`, `bulkEncryptModels`, and `bulkDecryptModels` methods that transparently encrypt/decrypt DynamoDB items according to the provided table schema. Parameters [#parameters] config [#config] [`EncryptedDynamoDBConfig`](/docs/reference/stack/latest/dynamodb/interfaces/EncryptedDynamoDBConfig) Configuration containing the `encryptionClient` and optional logging / error-handling callbacks. Returns [#returns] [`EncryptedDynamoDBInstance`](/docs/reference/stack/latest/dynamodb/interfaces/EncryptedDynamoDBInstance) An [EncryptedDynamoDBInstance](/docs/reference/stack/latest/dynamodb/interfaces/EncryptedDynamoDBInstance) with encrypt/decrypt operations. Example [#example] ```typescript import { Encryption } from "@cipherstash/stack" import { encryptedDynamoDB } from "@cipherstash/stack/dynamodb" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const users = encryptedTable("users", { email: encryptedColumn("email").equality(), }) const client = await Encryption({ schemas: [users] }) const dynamo = encryptedDynamoDB({ encryptionClient: client }) const encrypted = await dynamo.encryptModel({ email: "a@b.com" }, users) ``` # EncryptedDynamoDBConfig [**@cipherstash/stack**](../../index) *** Interface: EncryptedDynamoDBConfig [#interface-encrypteddynamodbconfig] Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:10](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L10) Properties [#properties] encryptionClient [#encryptionclient] ```ts encryptionClient: EncryptionClient; ``` Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:11](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L11) *** options? [#options] ```ts optional options: { logger?: { error: (message, error) => void; }; errorHandler?: (error) => void; }; ``` Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:12](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L12) logger? [#logger] ```ts optional logger: { error: (message, error) => void; }; ``` logger.error() [#loggererror] ```ts error: (message, error) => void; ``` Parameters [#parameters] message [#message] `string` error [#error] `Error` Returns [#returns] `void` errorHandler()? [#errorhandler] ```ts optional errorHandler: (error) => void; ``` Parameters [#parameters-1] error [#error-1] [`EncryptedDynamoDBError`](/docs/reference/stack/latest/dynamodb/interfaces/EncryptedDynamoDBError) Returns [#returns-1] `void` # EncryptedDynamoDBError [**@cipherstash/stack**](../../index) *** Interface: EncryptedDynamoDBError [#interface-encrypteddynamodberror] Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:20](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L20) Extends [#extends] * `Error` Properties [#properties] code [#code] ```ts code: ProtectErrorCode | "DYNAMODB_ENCRYPTION_ERROR"; ``` Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:21](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L21) *** details? [#details] ```ts optional details: Record<string, unknown>; ``` Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:22](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L22) *** cause? [#cause] ```ts optional cause: unknown; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es2022.error.d.ts:26 Inherited from [#inherited-from] ```ts Error.cause ``` *** name [#name] ```ts name: string; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1076 Inherited from [#inherited-from-1] ```ts Error.name ``` *** message [#message] ```ts message: string; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1077 Inherited from [#inherited-from-2] ```ts Error.message ``` *** stack? [#stack] ```ts optional stack: string; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1078 Inherited from [#inherited-from-3] ```ts Error.stack ``` # EncryptedDynamoDBInstance [**@cipherstash/stack**](../../index) *** Interface: EncryptedDynamoDBInstance [#interface-encrypteddynamodbinstance] Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:25](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L25) Methods [#methods] encryptModel() [#encryptmodel] ```ts encryptModel<T>(item, table): EncryptModelOperation<T>; ``` Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:26](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L26) Type Parameters [#type-parameters] T [#t] `T` *extends* `Record`\<`string`, `unknown`> Parameters [#parameters] item [#item] `T` table [#table] [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn)> Returns [#returns] `EncryptModelOperation`\<`T`> *** bulkEncryptModels() [#bulkencryptmodels] ```ts bulkEncryptModels<T>(items, table): BulkEncryptModelsOperation<T>; ``` Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:31](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L31) Type Parameters [#type-parameters-1] T [#t-1] `T` *extends* `Record`\<`string`, `unknown`> Parameters [#parameters-1] items [#items] `T`\[] table [#table-1] [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn)> Returns [#returns-1] `BulkEncryptModelsOperation`\<`T`> *** decryptModel() [#decryptmodel] ```ts decryptModel<T>(item, table): DecryptModelOperation<T>; ``` Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:36](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L36) Type Parameters [#type-parameters-2] T [#t-2] `T` *extends* `Record`\<`string`, `unknown`> Parameters [#parameters-2] item [#item-1] `Record`\<`string`, \| [`EncryptedValue`](/docs/reference/stack/latest/types-public/type-aliases/EncryptedValue) \| `unknown`> table [#table-2] [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn)> Returns [#returns-2] `DecryptModelOperation`\<`T`> *** bulkDecryptModels() [#bulkdecryptmodels] ```ts bulkDecryptModels<T>(items, table): BulkDecryptModelsOperation<T>; ``` Defined in: [.tmp-stack/packages/stack/src/dynamodb/types.ts:41](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/dynamodb/types.ts#L41) Type Parameters [#type-parameters-3] T [#t-3] `T` *extends* `Record`\<`string`, `unknown`> Parameters [#parameters-3] items [#items-1] `Record`\<`string`, `unknown`>\[] table [#table-3] [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn)> Returns [#returns-3] `BulkDecryptModelsOperation`\<`T`> # EncryptionClient [**@cipherstash/stack**](../../index) *** Class: EncryptionClient [#class-encryptionclient] Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:46](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L46) The EncryptionClient is the main entry point for interacting with the CipherStash Encryption library. It provides methods for encrypting and decrypting individual values, as well as models (objects) and bulk operations. The client must be initialized using the Encryption function before it can be used. Constructors [#constructors] Constructor [#constructor] ```ts new EncryptionClient(workspaceCrn?): EncryptionClient; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:51](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L51) Parameters [#parameters] workspaceCrn? [#workspacecrn] `string` Returns [#returns] `EncryptionClient` Methods [#methods] encrypt() [#encrypt] ```ts encrypt(plaintext, opts): EncryptOperation; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:181](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L181) Encrypt a value - returns a promise which resolves to an encrypted value. Parameters [#parameters-1] plaintext [#plaintext] `JsPlaintext` The plaintext value to be encrypted. opts [#opts] [`EncryptOptions`](/docs/reference/stack/latest/types-public/type-aliases/EncryptOptions) Options specifying the column (or nested field) and table for encryption. See [EncryptOptions](/docs/reference/stack/latest/types-public/type-aliases/EncryptOptions). Returns [#returns-1] `EncryptOperation` An EncryptOperation that can be awaited or chained with additional methods. Examples [#examples] The following example demonstrates how to encrypt a value using the Encryption client. It includes defining an encryption schema with encryptedTable and encryptedColumn, initializing the client with Encryption, and performing the encryption. `encrypt` returns an EncryptOperation which can be awaited to get a Result which can either be the encrypted value or an EncryptionError. ```typescript // Define encryption schema import { Encryption } from "@cipherstash/stack" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const userSchema = encryptedTable("users", { email: encryptedColumn("email"), }); // Initialize Encryption client const client = await Encryption({ schemas: [userSchema] }) // Encrypt a value const encryptedResult = await client.encrypt( "person@example.com", { column: userSchema.email, table: userSchema } ) // Handle encryption result if (encryptedResult.failure) { throw new Error(`Encryption failed: ${encryptedResult.failure.message}`); } console.log("Encrypted data:", encryptedResult.data); ``` When encrypting data, a LockContext can be provided to tie the encryption to a specific user or session. This ensures that the same lock context is required for decryption. The following example demonstrates how to create a lock context using a user's JWT token and use it during encryption. ```typescript // Define encryption schema and initialize client as above // Create a lock for the user's `sub` claim from their JWT const lc = new LockContext(); const lockContext = await lc.identify(userJwt); if (lockContext.failure) { // Handle the failure } // Encrypt a value with the lock context // Decryption will then require the same lock context const encryptedResult = await client.encrypt( "person@example.com", { column: userSchema.email, table: userSchema } ) .withLockContext(lockContext) ``` See [#see] * [EncryptOptions](/docs/reference/stack/latest/types-public/type-aliases/EncryptOptions) * Result * encryptedTable * encryptedColumn * encryptedField * LockContext * EncryptOperation *** encryptQuery() [#encryptquery] Call Signature [#call-signature] ```ts encryptQuery(plaintext, opts): EncryptQueryOperation; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:238](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L238) Encrypt a query value - returns a promise which resolves to an encrypted query value. Parameters [#parameters-2] plaintext [#plaintext-1] `JsPlaintext` The plaintext value to be encrypted for querying. opts [#opts-1] `QueryTermBase` Options specifying the column, table, and optional queryType for encryption. Returns [#returns-2] `EncryptQueryOperation` An EncryptQueryOperation that can be awaited or chained with additional methods. Examples [#examples-1] The following example demonstrates how to encrypt a query value using the Encryption client. ```typescript // Define encryption schema import { Encryption } from "@cipherstash/stack" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const userSchema = encryptedTable("users", { email: encryptedColumn("email").equality(), }); // Initialize Encryption client const client = await Encryption({ schemas: [userSchema] }) // Encrypt a query value const encryptedResult = await client.encryptQuery( "person@example.com", { column: userSchema.email, table: userSchema, queryType: 'equality' } ) // Handle encryption result if (encryptedResult.failure) { throw new Error(`Encryption failed: ${encryptedResult.failure.message}`); } console.log("Encrypted query:", encryptedResult.data); ``` The queryType can be auto-inferred from the column's configured indexes: ```typescript // When queryType is omitted, it will be inferred from the column's indexes const encryptedResult = await client.encryptQuery( "person@example.com", { column: userSchema.email, table: userSchema } ) ``` See [#see-1] EncryptQueryOperation **JSONB columns (searchableJson):** When `queryType` is omitted on a `searchableJson()` column, the query operation is inferred: * String plaintext → `steVecSelector` (JSONPath queries like `'$.user.email'`) * Object/Array plaintext → `steVecTerm` (containment queries like `{ role: 'admin' }`) Call Signature [#call-signature-1] ```ts encryptQuery(terms): BatchEncryptQueryOperation; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:247](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L247) Encrypt multiple values for use in queries (batch operation). Parameters [#parameters-3] terms [#terms] readonly [`ScalarQueryTerm`](/docs/reference/stack/latest/types-public/type-aliases/ScalarQueryTerm)\[] Array of query terms to encrypt Returns [#returns-3] `BatchEncryptQueryOperation` *** decrypt() [#decrypt] ```ts decrypt(encryptedData): DecryptOperation; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:316](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L316) Decryption - returns a promise which resolves to a decrypted value. Parameters [#parameters-4] encryptedData [#encrypteddata] `Encrypted` The encrypted data to be decrypted. Returns [#returns-4] `DecryptOperation` A DecryptOperation that can be awaited or chained with additional methods. Examples [#examples-2] The following example demonstrates how to decrypt a value that was previously encrypted using the [encrypt](/docs/reference/stack/latest/encryption/classes/EncryptionClient.mdx#encrypt) method. It includes encrypting a value first, then decrypting it, and handling the result. ```typescript const encryptedData = await client.encrypt( "person@example.com", { column: "email", table: "users" } ) const decryptResult = await client.decrypt(encryptedData) if (decryptResult.failure) { throw new Error(`Decryption failed: ${decryptResult.failure.message}`); } console.log("Decrypted data:", decryptResult.data); ``` Provide a lock context when decrypting: ```typescript await client.decrypt(encryptedData) .withLockContext(lockContext) ``` See [#see-2] * LockContext * DecryptOperation *** encryptModel() [#encryptmodel] ```ts encryptModel<T, S>(input, table): EncryptModelOperation<EncryptedFromSchema<T, S>>; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:366](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L366) Encrypt a model (object) based on the table schema. Only fields whose keys match columns defined in the table schema are encrypted. All other fields are passed through unchanged. Returns a thenable operation that supports `.withLockContext()` for identity-aware encryption. The return type is **schema-aware**: fields matching the table schema are typed as `Encrypted`, while other fields retain their original types. For best results, let TypeScript infer the type parameters from the arguments rather than providing an explicit type argument. Type Parameters [#type-parameters] T [#t] `T` *extends* `Record`\<`string`, `unknown`> S [#s] `S` *extends* [`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn) = [`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn) Parameters [#parameters-5] input [#input] `T` The model object with plaintext values to encrypt. table [#table] [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<`S`> The table schema defining which fields to encrypt. Returns [#returns-5] `EncryptModelOperation`\<`EncryptedFromSchema`\<`T`, `S`>> An `EncryptModelOperation` that can be awaited to get a `Result` containing the model with schema-defined fields typed as `Encrypted`, or an `EncryptionError`. Example [#example] ```typescript import { Encryption } from "@cipherstash/stack" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" type User = { id: string; email: string; createdAt: Date } const usersSchema = encryptedTable("users", { email: encryptedColumn("email").equality(), }) const client = await Encryption({ schemas: [usersSchema] }) // Let TypeScript infer the return type from the schema. // result.data.email is typed as `Encrypted`, result.data.id stays `string`. const result = await client.encryptModel( { id: "user_123", email: "alice@example.com", createdAt: new Date() }, usersSchema, ) if (result.failure) { console.error(result.failure.message) } else { console.log(result.data.id) // string console.log(result.data.email) // Encrypted } ``` *** decryptModel() [#decryptmodel] ```ts decryptModel<T>(input): DecryptModelOperation<T>; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:408](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L408) Decrypt a model (object) whose fields contain encrypted values. Identifies encrypted fields automatically and decrypts them, returning the model with plaintext values. Returns a thenable operation that supports `.withLockContext()` for identity-aware decryption. Type Parameters [#type-parameters-1] T [#t-1] `T` *extends* `Record`\<`string`, `unknown`> Parameters [#parameters-6] input [#input-1] `T` The model object with encrypted field values. Returns [#returns-6] `DecryptModelOperation`\<`T`> A `DecryptModelOperation<T>` that can be awaited to get a `Result` containing the model with decrypted plaintext fields, or an `EncryptionError`. Example [#example-1] ```typescript // Decrypt a previously encrypted model const decrypted = await client.decryptModel<User>(encryptedUser) if (decrypted.failure) { console.error(decrypted.failure.message) } else { console.log(decrypted.data.email) // "alice@example.com" } // With a lock context const decrypted = await client .decryptModel<User>(encryptedUser) .withLockContext(lockContext) ``` *** bulkEncryptModels() [#bulkencryptmodels] ```ts bulkEncryptModels<T, S>(input, table): BulkEncryptModelsOperation<EncryptedFromSchema<T, S>>; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:459](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L459) Encrypt multiple models (objects) in a single bulk operation. Performs a single call to ZeroKMS regardless of the number of models, while still using a unique key for each encrypted value. Only fields matching the table schema are encrypted; other fields pass through unchanged. The return type is **schema-aware**: fields matching the table schema are typed as `Encrypted`, while other fields retain their original types. For best results, let TypeScript infer the type parameters from the arguments. Type Parameters [#type-parameters-2] T [#t-2] `T` *extends* `Record`\<`string`, `unknown`> S [#s-1] `S` *extends* [`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn) = [`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn) Parameters [#parameters-7] input [#input-2] `T`\[] An array of model objects with plaintext values to encrypt. table [#table-1] [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<`S`> The table schema defining which fields to encrypt. Returns [#returns-7] `BulkEncryptModelsOperation`\<`EncryptedFromSchema`\<`T`, `S`>> A `BulkEncryptModelsOperation` that can be awaited to get a `Result` containing an array of models with schema-defined fields typed as `Encrypted`, or an `EncryptionError`. Example [#example-2] ```typescript import { Encryption } from "@cipherstash/stack" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" type User = { id: string; email: string } const usersSchema = encryptedTable("users", { email: encryptedColumn("email"), }) const client = await Encryption({ schemas: [usersSchema] }) // Let TypeScript infer the return type from the schema. // Each item's email is typed as `Encrypted`, id stays `string`. const result = await client.bulkEncryptModels( [ { id: "1", email: "alice@example.com" }, { id: "2", email: "bob@example.com" }, ], usersSchema, ) if (!result.failure) { console.log(result.data) // array of models with encrypted email fields } ``` *** bulkDecryptModels() [#bulkdecryptmodels] ```ts bulkDecryptModels<T>(input): BulkDecryptModelsOperation<T>; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:501](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L501) Decrypt multiple models (objects) in a single bulk operation. Performs a single call to ZeroKMS regardless of the number of models, restoring all encrypted fields to their original plaintext values. Type Parameters [#type-parameters-3] T [#t-3] `T` *extends* `Record`\<`string`, `unknown`> Parameters [#parameters-8] input [#input-3] `T`\[] An array of model objects with encrypted field values. Returns [#returns-8] `BulkDecryptModelsOperation`\<`T`> A `BulkDecryptModelsOperation<T>` that can be awaited to get a `Result` containing an array of models with decrypted plaintext fields, or an `EncryptionError`. Example [#example-3] ```typescript const encryptedUsers = encryptedResult.data // from bulkEncryptModels const result = await client.bulkDecryptModels<User>(encryptedUsers) if (!result.failure) { for (const user of result.data) { console.log(user.email) // plaintext email } } // With a lock context const result = await client .bulkDecryptModels<User>(encryptedUsers) .withLockContext(lockContext) ``` *** bulkEncrypt() [#bulkencrypt] ```ts bulkEncrypt(plaintexts, opts): BulkEncryptOperation; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:543](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L543) Encrypt multiple plaintext values in a single bulk operation. Each value is encrypted with its own unique key via a single call to ZeroKMS. Values can include optional `id` fields for correlating results back to your application data. Parameters [#parameters-9] plaintexts [#plaintexts] [`BulkEncryptPayload`](/docs/reference/stack/latest/types-public/type-aliases/BulkEncryptPayload) An array of objects with `plaintext` (and optional `id`) fields. opts [#opts-2] [`EncryptOptions`](/docs/reference/stack/latest/types-public/type-aliases/EncryptOptions) Options specifying the target column (or nested encryptedField) and table. See [EncryptOptions](/docs/reference/stack/latest/types-public/type-aliases/EncryptOptions). Returns [#returns-9] `BulkEncryptOperation` A `BulkEncryptOperation` that can be awaited to get a `Result` containing an array of `{ id?, data: Encrypted }` objects, or an `EncryptionError`. Example [#example-4] ```typescript import { Encryption } from "@cipherstash/stack" import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const users = encryptedTable("users", { email: encryptedColumn("email"), }) const client = await Encryption({ schemas: [users] }) const result = await client.bulkEncrypt( [ { id: "u1", plaintext: "alice@example.com" }, { id: "u2", plaintext: "bob@example.com" }, ], { column: users.email, table: users }, ) if (!result.failure) { // result.data = [{ id: "u1", data: Encrypted }, { id: "u2", data: Encrypted }, ...] console.log(result.data) } ``` *** bulkDecrypt() [#bulkdecrypt] ```ts bulkDecrypt(encryptedPayloads): BulkDecryptOperation; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:580](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L580) Decrypt multiple encrypted values in a single bulk operation. Performs a single call to ZeroKMS to decrypt all values. The result uses a multi-status pattern: each item in the returned array has either a `data` field (success) or an `error` field (failure), allowing graceful handling of partial failures. Parameters [#parameters-10] encryptedPayloads [#encryptedpayloads] [`BulkDecryptPayload`](/docs/reference/stack/latest/types-public/type-aliases/BulkDecryptPayload) An array of objects with `data` (encrypted payload) and optional `id` fields. Returns [#returns-10] `BulkDecryptOperation` A `BulkDecryptOperation` that can be awaited to get a `Result` containing an array of `{ id?, data: plaintext }` or `{ id?, error: string }` objects, or an `EncryptionError` if the entire operation fails. Example [#example-5] ```typescript const encrypted = await client.bulkEncrypt(plaintexts, { column: users.email, table: users }) const result = await client.bulkDecrypt(encrypted.data) if (!result.failure) { for (const item of result.data) { if ("data" in item) { console.log(`${item.id}: ${item.data}`) } else { console.error(`${item.id} failed: ${item.error}`) } } } ``` *** clientInfo() [#clientinfo] ```ts clientInfo(): { workspaceId: string | undefined; }; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:585](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L585) e.g., debugging or environment info Returns [#returns-11] ```ts { workspaceId: string | undefined; } ``` workspaceId [#workspaceid] ```ts workspaceId: string | undefined; ``` # noClientError [**@cipherstash/stack**](../../index) *** Function: noClientError() [#function-noclienterror] ```ts function noClientError(): Error; ``` Defined in: [.tmp-stack/packages/stack/src/encryption/index.ts:36](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/encryption/index.ts#L36) Returns [#returns] `Error` # LockContext [**@cipherstash/stack**](../../index) *** Class: LockContext [#class-lockcontext] Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:53](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L53) Manages CipherStash lock contexts for row-level access control. A `LockContext` ties encryption/decryption operations to an authenticated user identity via CTS (CipherStash Token Service). Call [identify](/docs/reference/stack/latest/identity/classes/LockContext.mdx#identify) with a user's JWT to obtain a CTS token, then pass the `LockContext` to `.withLockContext()` on any encrypt/decrypt operation. Example [#example] ```typescript import { LockContext } from "@cipherstash/stack/identity" const lc = new LockContext() const identified = await lc.identify(userJwt) if (identified.failure) throw new Error(identified.failure.message) const result = await client .encrypt(value, { column: users.email, table: users }) .withLockContext(identified.data) ``` Constructors [#constructors] Constructor [#constructor] ```ts new LockContext(__namedParameters?): LockContext; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:58](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L58) Parameters [#parameters] __namedParameters? [#__namedparameters] [`LockContextOptions`](/docs/reference/stack/latest/identity/type-aliases/LockContextOptions) = `{}` Returns [#returns] `LockContext` Methods [#methods] identify() [#identify] ```ts identify(jwtToken): Promise<Result<LockContext, EncryptionError>>; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:94](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L94) Exchange a user's JWT for a CTS token and bind it to this lock context. Parameters [#parameters-1] jwtToken [#jwttoken] `string` A valid OIDC / JWT token for the current user. Returns [#returns-1] `Promise`\<`Result`\<`LockContext`, `EncryptionError`>> A `Result` containing this `LockContext` (now authenticated) or an error. Example [#example-1] ```typescript const lc = new LockContext() const result = await lc.identify(userJwt) if (result.failure) { console.error("Auth failed:", result.failure.message) } ``` *** getLockContext() [#getlockcontext] ```ts getLockContext(): Promise<Result<GetLockContextResponse, EncryptionError>>; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:156](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L156) Retrieve the current CTS token and context for use with encryption operations. Must be called after [identify](/docs/reference/stack/latest/identity/classes/LockContext.mdx#identify). Returns the token/context pair that `.withLockContext()` expects. Returns [#returns-2] `Promise`\<`Result`\<[`GetLockContextResponse`](/docs/reference/stack/latest/identity/type-aliases/GetLockContextResponse), `EncryptionError`>> A `Result` containing the CTS token and identity context, or an error if [identify](/docs/reference/stack/latest/identity/classes/LockContext.mdx#identify) has not been called. # Context [**@cipherstash/stack**](../../index) *** Type Alias: Context [#type-alias-context] ```ts type Context = { identityClaim: string[]; }; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:17](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L17) Properties [#properties] identityClaim [#identityclaim] ```ts identityClaim: string[]; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:18](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L18) # CtsRegions [**@cipherstash/stack**](../../index) *** Type Alias: CtsRegions [#type-alias-ctsregions] ```ts type CtsRegions = "ap-southeast-2"; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:6](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L6) # CtsToken [**@cipherstash/stack**](../../index) *** Type Alias: CtsToken [#type-alias-ctstoken] ```ts type CtsToken = { accessToken: string; expiry: number; }; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:12](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L12) Properties [#properties] accessToken [#accesstoken] ```ts accessToken: string; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:13](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L13) *** expiry [#expiry] ```ts expiry: number; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:14](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L14) # GetLockContextResponse [**@cipherstash/stack**](../../index) *** Type Alias: GetLockContextResponse [#type-alias-getlockcontextresponse] ```ts type GetLockContextResponse = { ctsToken: CtsToken; context: Context; }; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:26](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L26) Properties [#properties] ctsToken [#ctstoken] ```ts ctsToken: CtsToken; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:27](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L27) *** context [#context] ```ts context: Context; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:28](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L28) # IdentifyOptions [**@cipherstash/stack**](../../index) *** Type Alias: IdentifyOptions [#type-alias-identifyoptions] ```ts type IdentifyOptions = { fetchFromCts?: boolean; }; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:8](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L8) Properties [#properties] fetchFromCts? [#fetchfromcts] ```ts optional fetchFromCts: boolean; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:9](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L9) # LockContextOptions [**@cipherstash/stack**](../../index) *** Type Alias: LockContextOptions [#type-alias-lockcontextoptions] ```ts type LockContextOptions = { context?: Context; ctsToken?: CtsToken; }; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:21](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L21) Properties [#properties] context? [#context] ```ts optional context: Context; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:22](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L22) *** ctsToken? [#ctstoken] ```ts optional ctsToken: CtsToken; ``` Defined in: [.tmp-stack/packages/stack/src/identity/index.ts:23](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/identity/index.ts#L23) # EncryptedColumn [**@cipherstash/stack**](../../index) *** Class: EncryptedColumn [#class-encryptedcolumn] Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:182](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L182) Constructors [#constructors] Constructor [#constructor] ```ts new EncryptedColumn(columnName): EncryptedColumn; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:192](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L192) Parameters [#parameters] columnName [#columnname] `string` Returns [#returns] `EncryptedColumn` Methods [#methods] dataType() [#datatype] ```ts dataType(castAs): EncryptedColumn; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:214](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L214) Set or override the plaintext data type for this column. By default all columns are treated as `'string'`. Use this method to specify a different type so the encryption layer knows how to encode the plaintext before encrypting. Parameters [#parameters-1] castAs [#castas] The plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'bigint'`, or `'json'`. `"string"` | `"number"` | `"bigint"` | `"boolean"` | `"date"` | `"json"` | `"text"` Returns [#returns-1] `EncryptedColumn` This `EncryptedColumn` instance for method chaining. Example [#example] ```typescript import { encryptedColumn } from "@cipherstash/stack/schema" const dateOfBirth = encryptedColumn("date_of_birth").dataType("date") ``` *** orderAndRange() [#orderandrange] ```ts orderAndRange(): EncryptedColumn; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:236](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L236) Enable Order-Revealing Encryption (ORE) indexing on this column. ORE allows sorting, comparison, and range queries on encrypted data. Use with `encryptQuery` and `queryType: 'orderAndRange'`. Returns [#returns-2] `EncryptedColumn` This `EncryptedColumn` instance for method chaining. Example [#example-1] ```typescript import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const users = encryptedTable("users", { email: encryptedColumn("email").orderAndRange(), }) ``` *** equality() [#equality] ```ts equality(tokenFilters?): EncryptedColumn; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:260](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L260) Enable an exact-match (unique) index on this column. Allows equality queries on encrypted data. Use with `encryptQuery` and `queryType: 'equality'`. Parameters [#parameters-2] tokenFilters? [#tokenfilters] \{ `kind`: `"downcase"`; }\[] Optional array of token filters (e.g. `[{ kind: 'downcase' }]`). When omitted, no token filters are applied. Returns [#returns-3] `EncryptedColumn` This `EncryptedColumn` instance for method chaining. Example [#example-2] ```typescript import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const users = encryptedTable("users", { email: encryptedColumn("email").equality(), }) ``` *** freeTextSearch() [#freetextsearch] ```ts freeTextSearch(opts?): EncryptedColumn; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:295](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L295) Enable a full-text / fuzzy search (match) index on this column. Uses n-gram tokenization by default for substring and fuzzy matching. Use with `encryptQuery` and `queryType: 'freeTextSearch'`. Parameters [#parameters-3] opts? [#opts] Optional match index configuration. Defaults to 3-character ngram tokenization with a downcase filter, `k=6`, `m=2048`, and `include_original=true`. tokenizer? [#tokenizer] \| \{ `kind`: `"standard"`; } \| \{ `kind`: `"ngram"`; `token_length`: `number`; } = `tokenizerSchema` token_filters? [#token_filters] \{ `kind`: `"downcase"`; }\[] = `...` k? [#k] `number` = `...` m? [#m] `number` = `...` include_original? [#include_original] `boolean` = `...` Returns [#returns-4] `EncryptedColumn` This `EncryptedColumn` instance for method chaining. Example [#example-3] ```typescript import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const users = encryptedTable("users", { email: encryptedColumn("email").freeTextSearch(), }) // With custom options const posts = encryptedTable("posts", { body: encryptedColumn("body").freeTextSearch({ tokenizer: { kind: "ngram", token_length: 4 }, k: 8, m: 4096, }), }) ``` *** searchableJson() [#searchablejson] ```ts searchableJson(): EncryptedColumn; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:333](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L333) Configure this column for searchable encrypted JSON (STE-Vec). Enables encrypted JSONPath selector queries (e.g. `'$.user.email'`) and containment queries (e.g. `{ role: 'admin' }`). Automatically sets the data type to `'json'`. When used with `encryptQuery`, the query operation is auto-inferred from the plaintext type: strings become selector queries, objects/arrays become containment queries. Returns [#returns-5] `EncryptedColumn` This `EncryptedColumn` instance for method chaining. Example [#example-4] ```typescript import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const documents = encryptedTable("documents", { metadata: encryptedColumn("metadata").searchableJson(), }) ``` *** build() [#build] ```ts build(): { cast_as: "number" | "bigint" | "boolean" | "date" | "json" | "text"; indexes: { ore?: { }; unique?: { token_filters?: { kind: "downcase"; }[]; }; match?: Required<{ tokenizer?: | { kind: "standard"; } | { kind: "ngram"; token_length: number; }; token_filters?: { kind: "downcase"; }[]; k?: number; m?: number; include_original?: boolean; }>; ste_vec?: { prefix: string; }; }; }; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:339](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L339) Returns [#returns-6] ```ts { cast_as: "number" | "bigint" | "boolean" | "date" | "json" | "text"; indexes: { ore?: { }; unique?: { token_filters?: { kind: "downcase"; }[]; }; match?: Required<{ tokenizer?: | { kind: "standard"; } | { kind: "ngram"; token_length: number; }; token_filters?: { kind: "downcase"; }[]; k?: number; m?: number; include_original?: boolean; }>; ste_vec?: { prefix: string; }; }; } ``` cast_as [#cast_as] ```ts cast_as: "number" | "bigint" | "boolean" | "date" | "json" | "text"; ``` indexes [#indexes] ```ts indexes: { ore?: { }; unique?: { token_filters?: { kind: "downcase"; }[]; }; match?: Required<{ tokenizer?: | { kind: "standard"; } | { kind: "ngram"; token_length: number; }; token_filters?: { kind: "downcase"; }[]; k?: number; m?: number; include_original?: boolean; }>; ste_vec?: { prefix: string; }; }; ``` indexes.ore? [#indexesore] ```ts optional ore: { }; ``` indexes.unique? [#indexesunique] ```ts optional unique: { token_filters?: { kind: "downcase"; }[]; }; ``` indexes.unique.token_filters? [#indexesuniquetoken_filters] ```ts optional token_filters: { kind: "downcase"; }[]; ``` indexes.match? [#indexesmatch] ```ts optional match: Required<{ tokenizer?: | { kind: "standard"; } | { kind: "ngram"; token_length: number; }; token_filters?: { kind: "downcase"; }[]; k?: number; m?: number; include_original?: boolean; }>; ``` indexes.ste_vec? [#indexesste_vec] ```ts optional ste_vec: { prefix: string; }; ``` indexes.ste_vec.prefix [#indexesste_vecprefix] ```ts prefix: string; ``` *** getName() [#getname] ```ts getName(): string; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:346](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L346) Returns [#returns-7] `string` # EncryptedField [**@cipherstash/stack**](../../index) *** Class: EncryptedField [#class-encryptedfield] Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:139](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L139) Builder for a nested encrypted field (encrypted but not searchable). Create with [encryptedField](/docs/reference/stack/latest/schema/functions/encryptedField). Use inside nested objects in [encryptedTable](/docs/reference/stack/latest/schema/functions/encryptedTable); supports `.dataType()` for plaintext type. No index methods (equality, orderAndRange, etc.). Constructors [#constructors] Constructor [#constructor] ```ts new EncryptedField(valueName): EncryptedField; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:143](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L143) Parameters [#parameters] valueName [#valuename] `string` Returns [#returns] `EncryptedField` Methods [#methods] dataType() [#datatype] ```ts dataType(castAs): EncryptedField; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:165](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L165) Set or override the plaintext data type for this field. By default all values are treated as `'string'`. Use this method to specify a different type so the encryption layer knows how to encode the plaintext before encrypting. Parameters [#parameters-1] castAs [#castas] The plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'text'`, `'bigint'`, or `'json'`. `"string"` | `"number"` | `"bigint"` | `"boolean"` | `"date"` | `"json"` | `"text"` Returns [#returns-1] `EncryptedField` This `EncryptedField` instance for method chaining. Example [#example] ```typescript import { encryptedField } from "@cipherstash/stack/schema" const age = encryptedField("age").dataType("number") ``` *** build() [#build] ```ts build(): { cast_as: "number" | "bigint" | "boolean" | "date" | "json" | "text"; indexes: { }; }; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:170](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L170) Returns [#returns-2] ```ts { cast_as: "number" | "bigint" | "boolean" | "date" | "json" | "text"; indexes: { }; } ``` cast_as [#cast_as] ```ts cast_as: "number" | "bigint" | "boolean" | "date" | "json" | "text"; ``` indexes [#indexes] ```ts indexes: { } = {}; ``` *** getName() [#getname] ```ts getName(): string; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:177](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L177) Returns [#returns-3] `string` # EncryptedTable [**@cipherstash/stack**](../../index) *** Class: EncryptedTable [#class-encryptedtablet] Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:356](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L356) Type Parameters [#type-parameters] T [#t] `T` *extends* [`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn) Constructors [#constructors] Constructor [#constructor] ```ts new EncryptedTable<T>(tableName, columnBuilders): EncryptedTable<T>; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:360](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L360) Parameters [#parameters] tableName [#tablename] `string` columnBuilders [#columnbuilders] `T` Returns [#returns] `EncryptedTable`\<`T`> Properties [#properties] tableName [#tablename-1] ```ts readonly tableName: string; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:361](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L361) Methods [#methods] build() [#build] ```ts build(): TableDefinition; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:384](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L384) Compile this table schema into a `TableDefinition` used internally by the encryption client. Iterates over all column builders, calls `.build()` on each, and assembles the final `{ tableName, columns }` structure. For `searchableJson()` columns, the STE-Vec prefix is automatically set to `"<tableName>/<columnName>"`. Returns [#returns-1] `TableDefinition` A `TableDefinition` containing the table name and built column configs. Example [#example] ```typescript const users = encryptedTable("users", { email: encryptedColumn("email").equality(), }) const definition = users.build() // { tableName: "users", columns: { email: { cast_as: "string", indexes: { unique: ... } } } } ``` # buildEncryptConfig [**@cipherstash/stack**](../../index) *** Function: buildEncryptConfig() [#function-buildencryptconfig] ```ts function buildEncryptConfig(...protectTables): { v: number; tables: Record<string, Record<string, { cast_as: "string" | "number" | "bigint" | "boolean" | "date" | "json" | "text"; indexes: { ore?: { }; unique?: { token_filters?: { kind: "downcase"; }[]; }; match?: { tokenizer?: | { kind: "standard"; } | { kind: "ngram"; token_length: number; }; token_filters?: { kind: "downcase"; }[]; k?: number; m?: number; include_original?: boolean; }; ste_vec?: { prefix: string; }; }; }>>; }; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:621](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L621) Build an encrypt config from a list of encrypted tables. Parameters [#parameters] protectTables [#protecttables] ...[`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn)>\[] Returns [#returns] ```ts { v: number; tables: Record<string, Record<string, { cast_as: "string" | "number" | "bigint" | "boolean" | "date" | "json" | "text"; indexes: { ore?: { }; unique?: { token_filters?: { kind: "downcase"; }[]; }; match?: { tokenizer?: | { kind: "standard"; } | { kind: "ngram"; token_length: number; }; token_filters?: { kind: "downcase"; }[]; k?: number; m?: number; include_original?: boolean; }; ste_vec?: { prefix: string; }; }; }>>; } ``` An encrypt config object. v [#v] ```ts v: number; ``` tables [#tables] ```ts tables: Record<string, Record<string, { cast_as: "string" | "number" | "bigint" | "boolean" | "date" | "json" | "text"; indexes: { ore?: { }; unique?: { token_filters?: { kind: "downcase"; }[]; }; match?: { tokenizer?: | { kind: "standard"; } | { kind: "ngram"; token_length: number; }; token_filters?: { kind: "downcase"; }[]; k?: number; m?: number; include_original?: boolean; }; ste_vec?: { prefix: string; }; }; }>> = tablesSchema; ``` Example [#example] ```typescript import { buildEncryptConfig } from "@cipherstash/stack/schema" const users = encryptedTable("users", { email: encryptedColumn("email").equality(), }) const orders = encryptedTable("orders", { amount: encryptedColumn("amount").dataType("number"), }) const config = buildEncryptConfig(users, orders) console.log(config) ``` # encryptedColumn [**@cipherstash/stack**](../../index) *** Function: encryptedColumn() [#function-encryptedcolumn] ```ts function encryptedColumn(columnName): EncryptedColumn; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:569](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L569) Define an encrypted column within a table schema. Creates a `EncryptedColumn` builder for the given column name. Chain index methods (`.equality()`, `.freeTextSearch()`, `.orderAndRange()`, `.searchableJson()`) and/or `.dataType()` to configure searchable encryption and the plaintext data type. Parameters [#parameters] columnName [#columnname] `string` The name of the database column to encrypt. Returns [#returns] [`EncryptedColumn`](/docs/reference/stack/latest/schema/classes/EncryptedColumn) A new `EncryptedColumn` builder. Example [#example] ```typescript import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const users = encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch().orderAndRange(), }) ``` # encryptedField [**@cipherstash/stack**](../../index) *** Function: encryptedField() [#function-encryptedfield] ```ts function encryptedField(valueName): EncryptedField; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:595](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L595) Define an encrypted field for use in nested or structured schemas. `encryptedField` is similar to [encryptedColumn](/docs/reference/stack/latest/schema/functions/encryptedColumn) but creates an [EncryptedField](/docs/reference/stack/latest/schema/classes/EncryptedField) for nested fields that are encrypted but not searchable (no indexes). Use `.dataType()` to specify the plaintext type. Parameters [#parameters] valueName [#valuename] `string` The name of the value field. Returns [#returns] [`EncryptedField`](/docs/reference/stack/latest/schema/classes/EncryptedField) A new `EncryptedField` builder. Example [#example] ```typescript import { encryptedTable, encryptedField } from "@cipherstash/stack/schema" const orders = encryptedTable("orders", { details: { amount: encryptedField("amount").dataType("number"), currency: encryptedField("currency"), }, }) ``` # encryptedTable [**@cipherstash/stack**](../../index) *** Function: encryptedTable() [#function-encryptedtable] ```ts function encryptedTable<T>(tableName, columns): EncryptedTable<T> & T; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:533](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L533) Define an encrypted table schema. Creates a `EncryptedTable` that maps a database table name to a set of encrypted column definitions. Pass the resulting object to `Encryption({ schemas: [...] })` when initializing the client. The returned object is also a proxy that exposes each column builder directly, so you can reference columns as `users.email` when calling `encrypt`, `decrypt`, and `encryptQuery`. Type Parameters [#type-parameters] T [#t] `T` *extends* [`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn) Parameters [#parameters] tableName [#tablename] `string` The name of the database table this schema represents. columns [#columns] `T` An object whose keys are logical column names and values are [EncryptedColumn](/docs/reference/stack/latest/schema/classes/EncryptedColumn) from [encryptedColumn](/docs/reference/stack/latest/schema/functions/encryptedColumn), or nested objects whose leaves are [EncryptedField](/docs/reference/stack/latest/schema/classes/EncryptedField) from [encryptedField](/docs/reference/stack/latest/schema/functions/encryptedField). Returns [#returns] [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<`T`> & `T` A `EncryptedTable<T> & T` that can be used as both a schema definition and a column accessor. Example [#example] ```typescript import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema" const users = encryptedTable("users", { email: encryptedColumn("email").equality().freeTextSearch(), address: encryptedColumn("address"), }) // Use as schema const client = await Encryption({ schemas: [users] }) // Use as column accessor await client.encrypt("hello@example.com", { column: users.email, table: users }) ``` # CastAs [**@cipherstash/stack**](../../index) *** Type Alias: CastAs [#type-alias-castas] ```ts type CastAs = z.infer<typeof castAsEnum>; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:101](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L101) Type-safe alias for castAsEnum used to specify the *unencrypted* data type of a column or value. This is important because once encrypted, all data is stored as binary blobs. See [#see] castAsEnum for possible values. # ColumnSchema [**@cipherstash/stack**](../../index) *** Type Alias: ColumnSchema [#type-alias-columnschema] ```ts type ColumnSchema = z.infer<typeof columnSchema>; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:107](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L107) # EncryptConfig [**@cipherstash/stack**](../../index) *** Type Alias: EncryptConfig [#type-alias-encryptconfig] ```ts type EncryptConfig = z.infer<typeof encryptConfigSchema>; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:128](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L128) # EncryptedTableColumn [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedTableColumn [#type-alias-encryptedtablecolumn] ```ts type EncryptedTableColumn = { [key: string]: | EncryptedColumn | { [key: string]: | EncryptedField | { [key: string]: | EncryptedField | { [key: string]: EncryptedField; }; }; }; }; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:113](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L113) Shape of table columns: either top-level [EncryptedColumn](/docs/reference/stack/latest/schema/classes/EncryptedColumn) or nested objects whose leaves are [EncryptedField](/docs/reference/stack/latest/schema/classes/EncryptedField). Used with [encryptedTable](/docs/reference/stack/latest/schema/functions/encryptedTable). Index Signature [#index-signature] ```ts [key: string]: | EncryptedColumn | { [key: string]: | EncryptedField | { [key: string]: | EncryptedField | { [key: string]: EncryptedField; }; }; } ``` # InferEncrypted [**@cipherstash/stack**](../../index) *** Type Alias: InferEncrypted [#type-alias-inferencryptedt] ```ts type InferEncrypted<T> = T extends EncryptedTable<infer C> ? { [K in keyof C as C[K] extends EncryptedColumn | EncryptedField ? K : never]: Encrypted } : never; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:486](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L486) Infer the encrypted type from a EncryptedTable schema. Type Parameters [#type-parameters] T [#t] `T` *extends* [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<`any`> Example [#example] ```typescript const users = encryptedTable("users", { email: encryptedColumn("email").equality(), }) type UserEncrypted = InferEncrypted<typeof users> // => { email: Encrypted } ``` # InferPlaintext [**@cipherstash/stack**](../../index) *** Type Alias: InferPlaintext [#type-alias-inferplaintextt] ```ts type InferPlaintext<T> = T extends EncryptedTable<infer C> ? { [K in keyof C as C[K] extends EncryptedColumn | EncryptedField ? K : never]: string } : never; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:464](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L464) Infer the plaintext (decrypted) type from a EncryptedTable schema. Type Parameters [#type-parameters] T [#t] `T` *extends* [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<`any`> Example [#example] ```typescript const users = encryptedTable("users", { email: encryptedColumn("email").equality(), name: encryptedColumn("name"), }) type UserPlaintext = InferPlaintext<typeof users> // => { email: string; name: string } ``` # MatchIndexOpts [**@cipherstash/stack**](../../index) *** Type Alias: MatchIndexOpts [#type-alias-matchindexopts] ```ts type MatchIndexOpts = z.infer<typeof matchIndexOptsSchema>; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:103](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L103) # OreIndexOpts [**@cipherstash/stack**](../../index) *** Type Alias: OreIndexOpts [#type-alias-oreindexopts] ```ts type OreIndexOpts = z.infer<typeof oreIndexOptsSchema>; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:106](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L106) # SteVecIndexOpts [**@cipherstash/stack**](../../index) *** Type Alias: SteVecIndexOpts [#type-alias-stevecindexopts] ```ts type SteVecIndexOpts = z.infer<typeof steVecIndexOptsSchema>; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:104](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L104) # TokenFilter [**@cipherstash/stack**](../../index) *** Type Alias: TokenFilter [#type-alias-tokenfilter] ```ts type TokenFilter = z.infer<typeof tokenFilterSchema>; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:102](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L102) # UniqueIndexOpts [**@cipherstash/stack**](../../index) *** Type Alias: UniqueIndexOpts [#type-alias-uniqueindexopts] ```ts type UniqueIndexOpts = z.infer<typeof uniqueIndexOptsSchema>; ``` Defined in: [.tmp-stack/packages/stack/src/schema/index.ts:105](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/schema/index.ts#L105) # Secrets [**@cipherstash/stack**](../../index) *** Class: Secrets [#class-secrets] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:169](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L169) The Secrets client provides a high-level API for managing encrypted secrets stored in CipherStash. Secrets are encrypted locally before being sent to the API, ensuring end-to-end encryption. Constructors [#constructors] Constructor [#constructor] ```ts new Secrets(config): Secrets; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:178](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L178) Parameters [#parameters] config [#config] [`SecretsConfig`](/docs/reference/stack/latest/secrets/interfaces/SecretsConfig) Returns [#returns] `Secrets` Methods [#methods] set() [#set] ```ts set(name, value): Promise<Result<SetSecretResponse, SecretsError>>; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:292](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L292) Store an encrypted secret in the vault. The value is encrypted locally before being sent to the API. API: POST /api/secrets/set Parameters [#parameters-1] name [#name] `string` The name of the secret value [#value] `string` The plaintext value to encrypt and store Returns [#returns-1] `Promise`\<`Result`\<[`SetSecretResponse`](/docs/reference/stack/latest/secrets/interfaces/SetSecretResponse), [`SecretsError`](/docs/reference/stack/latest/secrets/interfaces/SecretsError)>> A Result containing the API response or an error *** get() [#get] ```ts get(name): Promise<Result<string, SecretsError>>; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:345](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L345) Retrieve and decrypt a secret from the vault. The secret is decrypted locally after retrieval. API: GET /api/secrets/get?workspaceId=...\&environment=...\&name=... Parameters [#parameters-2] name [#name-1] `string` The name of the secret to retrieve Returns [#returns-2] `Promise`\<`Result`\<`string`, [`SecretsError`](/docs/reference/stack/latest/secrets/interfaces/SecretsError)>> A Result containing the decrypted value or an error *** getMany() [#getmany] ```ts getMany(names): Promise<Result<Record<string, string>, SecretsError>>; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:413](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L413) Retrieve and decrypt many secrets from the vault. The secrets are decrypted locally after retrieval. This method only triggers a single network request to the ZeroKMS. API: GET /api/secrets/get-many?workspaceId=...\&environment=...\&names=name1,name2,... Constraints: * Minimum 2 secret names required * Maximum 100 secret names per request Parameters [#parameters-3] names [#names] `string`\[] The names of the secrets to retrieve (min 2, max 100) Returns [#returns-3] `Promise`\<`Result`\<`Record`\<`string`, `string`>, [`SecretsError`](/docs/reference/stack/latest/secrets/interfaces/SecretsError)>> A Result containing an object mapping secret names to their decrypted values *** list() [#list] ```ts list(): Promise<Result<SecretMetadata[], SecretsError>>; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:504](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L504) List all secrets in the environment. Only names and metadata are returned; values remain encrypted. API: GET /api/secrets/list?workspaceId=...\&environment=... Returns [#returns-4] `Promise`\<`Result`\<[`SecretMetadata`](/docs/reference/stack/latest/secrets/interfaces/SecretMetadata)\[], [`SecretsError`](/docs/reference/stack/latest/secrets/interfaces/SecretsError)>> A Result containing the list of secrets or an error *** delete() [#delete] ```ts delete(name): Promise<Result<DeleteSecretResponse, SecretsError>>; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:534](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L534) Delete a secret from the vault. API: POST /api/secrets/delete Parameters [#parameters-4] name [#name-2] `string` The name of the secret to delete Returns [#returns-5] `Promise`\<`Result`\<[`DeleteSecretResponse`](/docs/reference/stack/latest/secrets/interfaces/DeleteSecretResponse), [`SecretsError`](/docs/reference/stack/latest/secrets/interfaces/SecretsError)>> A Result containing the API response or an error # DecryptedSecretResponse [**@cipherstash/stack**](../../index) *** Interface: DecryptedSecretResponse [#interface-decryptedsecretresponse] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:156](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L156) Properties [#properties] name [#name] ```ts name: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:157](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L157) *** environment [#environment] ```ts environment: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:158](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L158) *** value [#value] ```ts value: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:159](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L159) *** createdAt [#createdat] ```ts createdAt: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:160](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L160) *** updatedAt [#updatedat] ```ts updatedAt: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:161](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L161) # DeleteSecretRequest [**@cipherstash/stack**](../../index) *** Interface: DeleteSecretRequest [#interface-deletesecretrequest] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:141](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L141) API request body for deleting a secret. POST /api/secrets/delete Properties [#properties] workspaceId [#workspaceid] ```ts workspaceId: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:142](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L142) *** environment [#environment] ```ts environment: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:143](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L143) *** name [#name] ```ts name: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:144](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L144) # DeleteSecretResponse [**@cipherstash/stack**](../../index) *** Interface: DeleteSecretResponse [#interface-deletesecretresponse] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:132](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L132) API response for deleting a secret. POST /api/secrets/delete Properties [#properties] success [#success] ```ts success: true; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:133](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L133) *** message [#message] ```ts message: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:134](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L134) # GetSecretResponse [**@cipherstash/stack**](../../index) *** Interface: GetSecretResponse [#interface-getsecretresponse] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:85](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L85) API response for getting a single secret. GET /api/secrets/get?workspaceId=...\&environment=...\&name=... The `encryptedValue` is the raw value stored in the vault's `value` column, which is the `{ data: Encrypted }` object that was passed to the set endpoint. Properties [#properties] name [#name] ```ts name: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:86](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L86) *** environment [#environment] ```ts environment: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:87](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L87) *** encryptedValue [#encryptedvalue] ```ts encryptedValue: { data: Encrypted; }; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:88](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L88) data [#data] ```ts data: Encrypted; ``` *** createdAt [#createdat] ```ts createdAt: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:91](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L91) *** updatedAt [#updatedat] ```ts updatedAt: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:92](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L92) # ListSecretsResponse [**@cipherstash/stack**](../../index) *** Interface: ListSecretsResponse [#interface-listsecretsresponse] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:73](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L73) API response for listing secrets. GET /api/secrets/list?workspaceId=...\&environment=... Properties [#properties] environment [#environment] ```ts environment: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:74](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L74) *** secrets [#secrets] ```ts secrets: SecretMetadata[]; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:75](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L75) # PlanLimitError [**@cipherstash/stack**](../../index) *** Interface: PlanLimitError [#interface-planlimiterror] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:151](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L151) API error response for plan limit violations (403). Returned by POST /api/secrets/set when the workspace has reached its secret limit. Properties [#properties] error [#error] ```ts error: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:152](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L152) *** code [#code] ```ts code: "PLAN_LIMIT_REACHED"; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:153](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L153) # SecretMetadata [**@cipherstash/stack**](../../index) *** Interface: SecretMetadata [#interface-secretmetadata] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:61](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L61) Secret metadata returned from the API (list endpoint). All fields are always present in API responses. Properties [#properties] id [#id] ```ts id: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:62](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L62) *** name [#name] ```ts name: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:63](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L63) *** environment [#environment] ```ts environment: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:64](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L64) *** createdAt [#createdat] ```ts createdAt: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:65](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L65) *** updatedAt [#updatedat] ```ts updatedAt: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:66](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L66) # SecretsConfig [**@cipherstash/stack**](../../index) *** Interface: SecretsConfig [#interface-secretsconfig] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:48](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L48) Configuration options for initializing the Stash client Properties [#properties] workspaceCRN [#workspacecrn] ```ts workspaceCRN: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:49](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L49) *** clientId [#clientid] ```ts clientId: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:50](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L50) *** clientKey [#clientkey] ```ts clientKey: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:51](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L51) *** environment [#environment] ```ts environment: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:52](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L52) *** apiKey [#apikey] ```ts apiKey: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:53](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L53) *** accessKey? [#accesskey] ```ts optional accessKey: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:54](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L54) # SecretsError [**@cipherstash/stack**](../../index) *** Interface: SecretsError [#interface-secretserror] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:40](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L40) Error returned by secrets operations. Properties [#properties] type [#type] ```ts type: SecretsErrorType; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:41](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L41) *** message [#message] ```ts message: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:42](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L42) # SetSecretRequest [**@cipherstash/stack**](../../index) *** Interface: SetSecretRequest [#interface-setsecretrequest] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:119](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L119) API request body for setting a secret. POST /api/secrets/set Properties [#properties] workspaceId [#workspaceid] ```ts workspaceId: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:120](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L120) *** environment [#environment] ```ts environment: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:121](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L121) *** name [#name] ```ts name: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:122](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L122) *** encryptedValue [#encryptedvalue] ```ts encryptedValue: { data: Encrypted; }; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:123](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L123) data [#data] ```ts data: Encrypted; ``` # SetSecretResponse [**@cipherstash/stack**](../../index) *** Interface: SetSecretResponse [#interface-setsecretresponse] Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:110](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L110) API response for setting a secret. POST /api/secrets/set Properties [#properties] success [#success] ```ts success: true; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:111](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L111) *** message [#message] ```ts message: string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:112](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L112) # GetManySecretsResponse [**@cipherstash/stack**](../../index) *** Type Alias: GetManySecretsResponse [#type-alias-getmanysecretsresponse] ```ts type GetManySecretsResponse = GetSecretResponse[]; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:104](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L104) API response for getting multiple secrets. GET /api/secrets/get-many?workspaceId=...\&environment=...\&names=name1,name2,... Returns an array of GetSecretResponse objects. Constraints: * `names` must be comma-separated (minimum 2 names) * Maximum 100 names per request # SecretName [**@cipherstash/stack**](../../index) *** Type Alias: SecretName [#type-alias-secretname] ```ts type SecretName = string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:24](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L24) # SecretValue [**@cipherstash/stack**](../../index) *** Type Alias: SecretValue [#type-alias-secretvalue] ```ts type SecretValue = string; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:25](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L25) # SecretsErrorType [**@cipherstash/stack**](../../index) *** Type Alias: SecretsErrorType [#type-alias-secretserrortype] ```ts type SecretsErrorType = | "ApiError" | "NetworkError" | "ClientError" | "EncryptionError" | "DecryptionError"; ``` Defined in: [.tmp-stack/packages/stack/src/secrets/index.ts:30](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/secrets/index.ts#L30) Discriminated error type for secrets operations. # encryptedSupabase [**@cipherstash/stack**](../../index) *** Function: encryptedSupabase() [#function-encryptedsupabase] ```ts function encryptedSupabase(config): EncryptedSupabaseInstance; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/index.ts:41](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/index.ts#L41) Create an encrypted Supabase wrapper that transparently handles encryption and decryption for queries on encrypted columns. Parameters [#parameters] config [#config] [`EncryptedSupabaseConfig`](/docs/reference/stack/latest/supabase/type-aliases/EncryptedSupabaseConfig) Configuration containing the encryption client and Supabase client. Returns [#returns] [`EncryptedSupabaseInstance`](/docs/reference/stack/latest/supabase/interfaces/EncryptedSupabaseInstance) An object with a `from()` method that mirrors `supabase.from()` but auto-encrypts mutations, adds `::jsonb` casts, encrypts filter values, and decrypts results. Example [#example] ```typescript import { Encryption } from '@cipherstash/stack' import { encryptedSupabase } from '@cipherstash/stack/supabase' import { encryptedTable, encryptedColumn } from '@cipherstash/stack/schema' const users = encryptedTable('users', { name: encryptedColumn('name').freeTextSearch().equality(), email: encryptedColumn('email').freeTextSearch().equality(), }) const client = await Encryption({ schemas: [users] }) const eSupabase = encryptedSupabase({ encryptionClient: client, supabaseClient: supabase }) // INSERT - auto-encrypts, auto-converts to PG composite await eSupabase.from('users', users) .insert({ name: 'John', email: 'john@example.com', age: 30 }) // SELECT with filter - auto-casts ::jsonb, auto-encrypts search term, auto-decrypts const { data } = await eSupabase.from('users', users) .select('id, email, name') .eq('email', 'john@example.com') ``` # EncryptedQueryBuilder [**@cipherstash/stack**](../../index) *** Interface: EncryptedQueryBuilder [#interface-encryptedquerybuildert] Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:232](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L232) Extends [#extends] * `PromiseLike`\<[`EncryptedSupabaseResponse`](/docs/reference/stack/latest/supabase/type-aliases/EncryptedSupabaseResponse)\<`T`\[]>> Type Parameters [#type-parameters] T [#t] `T` *extends* `Record`\<`string`, `unknown`> = `Record`\<`string`, `unknown`> Methods [#methods] select() [#select] ```ts select(columns, options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:235](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L235) Parameters [#parameters] columns [#columns] `string` options? [#options] head? [#head] `boolean` count? [#count] `"exact"` | `"planned"` | `"estimated"` Returns [#returns] `EncryptedQueryBuilder`\<`T`> *** insert() [#insert] ```ts insert(data, options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:239](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L239) Parameters [#parameters-1] data [#data] `Partial`\<`T`> | `Partial`\<`T`>\[] options? [#options-1] count? [#count-1] `"exact"` | `"planned"` | `"estimated"` defaultToNull? [#defaulttonull] `boolean` onConflict? [#onconflict] `string` Returns [#returns-1] `EncryptedQueryBuilder`\<`T`> *** update() [#update] ```ts update(data, options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:247](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L247) Parameters [#parameters-2] data [#data-1] `Partial`\<`T`> options? [#options-2] count? [#count-2] `"exact"` | `"planned"` | `"estimated"` Returns [#returns-2] `EncryptedQueryBuilder`\<`T`> *** upsert() [#upsert] ```ts upsert(data, options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:251](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L251) Parameters [#parameters-3] data [#data-2] `Partial`\<`T`> | `Partial`\<`T`>\[] options? [#options-3] count? [#count-3] `"exact"` | `"planned"` | `"estimated"` onConflict? [#onconflict-1] `string` ignoreDuplicates? [#ignoreduplicates] `boolean` defaultToNull? [#defaulttonull-1] `boolean` Returns [#returns-3] `EncryptedQueryBuilder`\<`T`> *** delete() [#delete] ```ts delete(options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:260](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L260) Parameters [#parameters-4] options? [#options-4] count? [#count-4] `"exact"` | `"planned"` | `"estimated"` Returns [#returns-4] `EncryptedQueryBuilder`\<`T`> *** eq() [#eq] ```ts eq<K>(column, value): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:263](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L263) Type Parameters [#type-parameters-1] K [#k] `K` *extends* `string` Parameters [#parameters-5] column [#column] `K` value [#value] `T`\[`K`] Returns [#returns-5] `EncryptedQueryBuilder`\<`T`> *** neq() [#neq] ```ts neq<K>(column, value): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:264](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L264) Type Parameters [#type-parameters-2] K [#k-1] `K` *extends* `string` Parameters [#parameters-6] column [#column-1] `K` value [#value-1] `T`\[`K`] Returns [#returns-6] `EncryptedQueryBuilder`\<`T`> *** gt() [#gt] ```ts gt<K>(column, value): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:268](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L268) Type Parameters [#type-parameters-3] K [#k-2] `K` *extends* `string` Parameters [#parameters-7] column [#column-2] `K` value [#value-2] `T`\[`K`] Returns [#returns-7] `EncryptedQueryBuilder`\<`T`> *** gte() [#gte] ```ts gte<K>(column, value): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:269](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L269) Type Parameters [#type-parameters-4] K [#k-3] `K` *extends* `string` Parameters [#parameters-8] column [#column-3] `K` value [#value-3] `T`\[`K`] Returns [#returns-8] `EncryptedQueryBuilder`\<`T`> *** lt() [#lt] ```ts lt<K>(column, value): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:273](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L273) Type Parameters [#type-parameters-5] K [#k-4] `K` *extends* `string` Parameters [#parameters-9] column [#column-4] `K` value [#value-4] `T`\[`K`] Returns [#returns-9] `EncryptedQueryBuilder`\<`T`> *** lte() [#lte] ```ts lte<K>(column, value): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:274](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L274) Type Parameters [#type-parameters-6] K [#k-5] `K` *extends* `string` Parameters [#parameters-10] column [#column-5] `K` value [#value-5] `T`\[`K`] Returns [#returns-10] `EncryptedQueryBuilder`\<`T`> *** like() [#like] ```ts like<K>(column, pattern): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:278](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L278) Type Parameters [#type-parameters-7] K [#k-6] `K` *extends* `string` Parameters [#parameters-11] column [#column-6] `K` pattern [#pattern] `string` Returns [#returns-11] `EncryptedQueryBuilder`\<`T`> *** ilike() [#ilike] ```ts ilike<K>(column, pattern): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:282](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L282) Type Parameters [#type-parameters-8] K [#k-7] `K` *extends* `string` Parameters [#parameters-12] column [#column-7] `K` pattern [#pattern-1] `string` Returns [#returns-12] `EncryptedQueryBuilder`\<`T`> *** is() [#is] ```ts is<K>(column, value): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:286](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L286) Type Parameters [#type-parameters-9] K [#k-8] `K` *extends* `string` Parameters [#parameters-13] column [#column-8] `K` value [#value-6] `boolean` | `null` Returns [#returns-13] `EncryptedQueryBuilder`\<`T`> *** in() [#in] ```ts in<K>(column, values): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:290](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L290) Type Parameters [#type-parameters-10] K [#k-9] `K` *extends* `string` Parameters [#parameters-14] column [#column-9] `K` values [#values] `T`\[`K`]\[] Returns [#returns-14] `EncryptedQueryBuilder`\<`T`> *** filter() [#filter] ```ts filter<K>( column, operator, value): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:294](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L294) Type Parameters [#type-parameters-11] K [#k-10] `K` *extends* `string` Parameters [#parameters-15] column [#column-10] `K` operator [#operator] `string` value [#value-7] `T`\[`K`] Returns [#returns-15] `EncryptedQueryBuilder`\<`T`> *** not() [#not] ```ts not<K>( column, operator, value): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:299](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L299) Type Parameters [#type-parameters-12] K [#k-11] `K` *extends* `string` Parameters [#parameters-16] column [#column-11] `K` operator [#operator-1] `string` value [#value-8] `T`\[`K`] Returns [#returns-16] `EncryptedQueryBuilder`\<`T`> *** or() [#or] Call Signature [#call-signature] ```ts or(filters, options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:304](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L304) Parameters [#parameters-17] filters [#filters] `string` options? [#options-5] referencedTable? [#referencedtable] `string` foreignTable? [#foreigntable] `string` Returns [#returns-17] `EncryptedQueryBuilder`\<`T`> Call Signature [#call-signature-1] ```ts or(conditions, options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:308](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L308) Parameters [#parameters-18] conditions [#conditions] [`PendingOrCondition`](/docs/reference/stack/latest/supabase/type-aliases/PendingOrCondition)\[] options? [#options-6] referencedTable? [#referencedtable-1] `string` foreignTable? [#foreigntable-1] `string` Returns [#returns-18] `EncryptedQueryBuilder`\<`T`> *** match() [#match] ```ts match(query): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:312](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L312) Parameters [#parameters-19] query [#query] `Partial`\<`T`> Returns [#returns-19] `EncryptedQueryBuilder`\<`T`> *** order() [#order] ```ts order<K>(column, options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:313](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L313) Type Parameters [#type-parameters-13] K [#k-12] `K` *extends* `string` Parameters [#parameters-20] column [#column-12] `K` options? [#options-7] ascending? [#ascending] `boolean` nullsFirst? [#nullsfirst] `boolean` referencedTable? [#referencedtable-2] `string` foreignTable? [#foreigntable-2] `string` Returns [#returns-20] `EncryptedQueryBuilder`\<`T`> *** limit() [#limit] ```ts limit(count, options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:322](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L322) Parameters [#parameters-21] count [#count-5] `number` options? [#options-8] referencedTable? [#referencedtable-3] `string` foreignTable? [#foreigntable-3] `string` Returns [#returns-21] `EncryptedQueryBuilder`\<`T`> *** range() [#range] ```ts range( from, to, options?): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:326](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L326) Parameters [#parameters-22] from [#from] `number` to [#to] `number` options? [#options-9] referencedTable? [#referencedtable-4] `string` foreignTable? [#foreigntable-4] `string` Returns [#returns-22] `EncryptedQueryBuilder`\<`T`> *** single() [#single] ```ts single(): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:331](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L331) Returns [#returns-23] `EncryptedQueryBuilder`\<`T`> *** maybeSingle() [#maybesingle] ```ts maybeSingle(): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:332](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L332) Returns [#returns-24] `EncryptedQueryBuilder`\<`T`> *** csv() [#csv] ```ts csv(): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:333](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L333) Returns [#returns-25] `EncryptedQueryBuilder`\<`T`> *** abortSignal() [#abortsignal] ```ts abortSignal(signal): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:334](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L334) Parameters [#parameters-23] signal [#signal] `AbortSignal` Returns [#returns-26] `EncryptedQueryBuilder`\<`T`> *** throwOnError() [#throwonerror] ```ts throwOnError(): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:335](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L335) Returns [#returns-27] `EncryptedQueryBuilder`\<`T`> *** returns() [#returns-28] ```ts returns<U>(): EncryptedQueryBuilder<U>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:336](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L336) Type Parameters [#type-parameters-14] U [#u] `U` *extends* `Record`\<`string`, `unknown`> Returns [#returns-29] `EncryptedQueryBuilder`\<`U`> *** withLockContext() [#withlockcontext] ```ts withLockContext(lockContext): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:337](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L337) Parameters [#parameters-24] lockContext [#lockcontext] [`LockContext`](/docs/reference/stack/latest/identity/classes/LockContext) Returns [#returns-30] `EncryptedQueryBuilder`\<`T`> *** audit() [#audit] ```ts audit(config): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:338](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L338) Parameters [#parameters-25] config [#config] `AuditConfig` Returns [#returns-31] `EncryptedQueryBuilder`\<`T`> *** then() [#then] ```ts then<TResult1, TResult2>(onfulfilled?, onrejected?): PromiseLike<TResult1 | TResult2>; ``` Defined in: node\_modules/.pnpm/typescript\@5.9.3/node\_modules/typescript/lib/lib.es5.d.ts:1544 Attaches callbacks for the resolution and/or rejection of the Promise. Type Parameters [#type-parameters-15] TResult1 [#tresult1] `TResult1` = [`EncryptedSupabaseResponse`](/docs/reference/stack/latest/supabase/type-aliases/EncryptedSupabaseResponse)\<`T`\[]> TResult2 [#tresult2] `TResult2` = `never` Parameters [#parameters-26] onfulfilled? [#onfulfilled] The callback to execute when the Promise is resolved. (`value`) => `TResult1` | `PromiseLike`\<`TResult1`> | `null` onrejected? [#onrejected] The callback to execute when the Promise is rejected. (`reason`) => `TResult2` | `PromiseLike`\<`TResult2`> | `null` Returns [#returns-32] `PromiseLike`\<`TResult1` | `TResult2`> A Promise for the completion of which ever callback is executed. Inherited from [#inherited-from] ```ts PromiseLike.then ``` # EncryptedSupabaseInstance [**@cipherstash/stack**](../../index) *** Interface: EncryptedSupabaseInstance [#interface-encryptedsupabaseinstance] Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:16](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L16) Methods [#methods] from() [#from] ```ts from<T>(tableName, schema): EncryptedQueryBuilder<T>; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:17](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L17) Type Parameters [#type-parameters] T [#t] `T` *extends* `Record`\<`string`, `unknown`> = `Record`\<`string`, `unknown`> Parameters [#parameters] tableName [#tablename] `string` schema [#schema] [`EncryptedTable`](/docs/reference/stack/latest/schema/classes/EncryptedTable)\<[`EncryptedTableColumn`](/docs/reference/stack/latest/schema/type-aliases/EncryptedTableColumn)> Returns [#returns] [`EncryptedQueryBuilder`](/docs/reference/stack/latest/supabase/interfaces/EncryptedQueryBuilder)\<`T`> # SupabaseClientLike [**@cipherstash/stack**](../../index) *** Interface: SupabaseClientLike [#interface-supabaseclientlike] Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:208](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L208) Methods [#methods] from() [#from] ```ts from(table): any; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:209](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L209) Parameters [#parameters] table [#table] `string` Returns [#returns] `any` # EncryptedSupabaseConfig [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedSupabaseConfig [#type-alias-encryptedsupabaseconfig] ```ts type EncryptedSupabaseConfig = { encryptionClient: EncryptionClient; supabaseClient: SupabaseClientLike; }; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:11](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L11) Properties [#properties] encryptionClient [#encryptionclient] ```ts encryptionClient: EncryptionClient; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:12](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L12) *** supabaseClient [#supabaseclient] ```ts supabaseClient: SupabaseClientLike; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:13](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L13) # EncryptedSupabaseError [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedSupabaseError [#type-alias-encryptedsupabaseerror] ```ts type EncryptedSupabaseError = { message: string; details?: string; hint?: string; code?: string; encryptionError?: EncryptionError; }; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:35](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L35) Properties [#properties] message [#message] ```ts message: string; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:36](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L36) *** details? [#details] ```ts optional details: string; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:37](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L37) *** hint? [#hint] ```ts optional hint: string; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:38](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L38) *** code? [#code] ```ts optional code: string; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:39](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L39) *** encryptionError? [#encryptionerror] ```ts optional encryptionError: EncryptionError; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:40](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L40) # EncryptedSupabaseResponse [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedSupabaseResponse [#type-alias-encryptedsupabaseresponset] ```ts type EncryptedSupabaseResponse<T> = { data: T | null; error: | EncryptedSupabaseError | null; count: number | null; status: number; statusText: string; }; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:27](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L27) Type Parameters [#type-parameters] T [#t] `T` Properties [#properties] data [#data] ```ts data: T | null; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:28](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L28) *** error [#error] ```ts error: | EncryptedSupabaseError | null; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:29](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L29) *** count [#count] ```ts count: number | null; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:30](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L30) *** status [#status] ```ts status: number; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:31](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L31) *** statusText [#statustext] ```ts statusText: string; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:32](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L32) # PendingOrCondition [**@cipherstash/stack**](../../index) *** Type Alias: PendingOrCondition [#type-alias-pendingorcondition] ```ts type PendingOrCondition = { column: string; op: FilterOp; value: unknown; }; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:69](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L69) Properties [#properties] column [#column] ```ts column: string; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:70](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L70) *** op [#op] ```ts op: FilterOp; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:71](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L71) *** value [#value] ```ts value: unknown; ``` Defined in: [.tmp-stack/packages/stack/src/supabase/types.ts:72](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/supabase/types.ts#L72) # queryTypes [**@cipherstash/stack**](../../index) *** Variable: queryTypes [#variable-querytypes] ```ts const queryTypes: { orderAndRange: "orderAndRange"; freeTextSearch: "freeTextSearch"; equality: "equality"; steVecSelector: "steVecSelector"; steVecTerm: "steVecTerm"; searchableJson: "searchableJson"; }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:221](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L221) Type Declaration [#type-declaration] orderAndRange [#orderandrange] ```ts readonly orderAndRange: "orderAndRange" = 'orderAndRange'; ``` freeTextSearch [#freetextsearch] ```ts readonly freeTextSearch: "freeTextSearch" = 'freeTextSearch'; ``` equality [#equality] ```ts readonly equality: "equality" = 'equality'; ``` steVecSelector [#stevecselector] ```ts readonly steVecSelector: "steVecSelector" = 'steVecSelector'; ``` steVecTerm [#stevecterm] ```ts readonly steVecTerm: "steVecTerm" = 'steVecTerm'; ``` searchableJson [#searchablejson] ```ts readonly searchableJson: "searchableJson" = 'searchableJson'; ``` # BulkDecryptPayload [**@cipherstash/stack**](../../index) *** Type Alias: BulkDecryptPayload [#type-alias-bulkdecryptpayload] ```ts type BulkDecryptPayload = { id?: string; data: Encrypted; }[]; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:183](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L183) Type Declaration [#type-declaration] id? [#id] ```ts optional id: string; ``` data [#data] ```ts data: Encrypted; ``` # BulkDecryptedData [**@cipherstash/stack**](../../index) *** Type Alias: BulkDecryptedData [#type-alias-bulkdecrypteddata] ```ts type BulkDecryptedData = DecryptionResult<JsPlaintext>[]; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:184](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L184) # BulkEncryptPayload [**@cipherstash/stack**](../../index) *** Type Alias: BulkEncryptPayload [#type-alias-bulkencryptpayload] ```ts type BulkEncryptPayload = { id?: string; plaintext: JsPlaintext; }[]; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:177](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L177) Type Declaration [#type-declaration] id? [#id] ```ts optional id: string; ``` plaintext [#plaintext] ```ts plaintext: JsPlaintext; ``` # BulkEncryptedData [**@cipherstash/stack**](../../index) *** Type Alias: BulkEncryptedData [#type-alias-bulkencrypteddata] ```ts type BulkEncryptedData = { id?: string; data: Encrypted; }[]; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:182](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L182) Type Declaration [#type-declaration] id? [#id] ```ts optional id: string; ``` data [#data] ```ts data: Encrypted; ``` # Client [**@cipherstash/stack**](../../index) *** Type Alias: Client [#type-alias-client] ```ts type Client = Awaited<ReturnType<typeof newClient>> | undefined; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:29](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L29) Public type re-exports for `@cipherstash/stack/types`. This module exposes only the public types from the internal types module. Internal helpers (`queryTypeToFfi`, `queryTypeToQueryOp`, `FfiIndexTypeName`, `QueryTermBase`) are excluded. # ClientConfig [**@cipherstash/stack**](../../index) *** Type Alias: ClientConfig [#type-alias-clientconfig] ```ts type ClientConfig = { workspaceCrn?: string; accessKey?: string; clientId?: string; clientKey?: string; keyset?: KeysetIdentifier; }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:43](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L43) Properties [#properties] workspaceCrn? [#workspacecrn] ```ts optional workspaceCrn: string; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:50](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L50) The CipherStash workspace CRN (Cloud Resource Name). Format: `crn:<region>.aws:<workspace-id>`. Can also be set via the `CS_WORKSPACE_CRN` environment variable. If omitted, the SDK reads from the environment or TOML config files. *** accessKey? [#accesskey] ```ts optional accessKey: string; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:57](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L57) The API access key used for authenticating with the CipherStash API. Can also be set via the `CS_CLIENT_ACCESS_KEY` environment variable. Obtain this from the CipherStash dashboard after creating a workspace. *** clientId? [#clientid] ```ts optional clientId: string; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:64](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L64) The client identifier used to authenticate with CipherStash services. Can also be set via the `CS_CLIENT_ID` environment variable. Generated during workspace onboarding in the CipherStash dashboard. *** clientKey? [#clientkey] ```ts optional clientKey: string; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:71](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L71) The client key material used in combination with ZeroKMS for encryption operations. Can also be set via the `CS_CLIENT_KEY` environment variable. Generated during workspace onboarding in the CipherStash dashboard. *** keyset? [#keyset] ```ts optional keyset: KeysetIdentifier; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:79](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L79) An optional keyset identifier for multi-tenant encryption. Each keyset provides cryptographic isolation, giving each tenant its own keyspace. Specify by name (`{ name: "tenant-a" }`) or UUID (`{ id: "..." }`). Keysets are created and managed in the CipherStash dashboard. # Decrypted [**@cipherstash/stack**](../../index) *** Type Alias: Decrypted [#type-alias-decryptedt] ```ts type Decrypted<T> = OtherFields<T> & DecryptedFields<T>; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:142](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L142) Model with encrypted fields replaced by plaintext (decrypted) values Type Parameters [#type-parameters] T [#t] `T` # DecryptedFields [**@cipherstash/stack**](../../index) *** Type Alias: DecryptedFields [#type-alias-decryptedfieldst] ```ts type DecryptedFields<T> = { [K in keyof T as NonNullable<T[K]> extends Encrypted ? K : never]: null extends T[K] ? string | null : string }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:136](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L136) Type Parameters [#type-parameters] T [#t] `T` # DecryptionResult [**@cipherstash/stack**](../../index) *** Type Alias: DecryptionResult [#type-alias-decryptionresultt] ```ts type DecryptionResult<T> = DecryptionSuccess<T> | DecryptionError<T>; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:194](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L194) Result type for individual items in bulk decrypt operations. Uses `error`/`data` fields (not `failure`/`data`) since bulk operations can have per-item failures. Type Parameters [#type-parameters] T [#t] `T` # EncryptOptions [**@cipherstash/stack**](../../index) *** Type Alias: EncryptOptions [#type-alias-encryptoptions] ```ts type EncryptOptions = { column: | EncryptedColumn | EncryptedField; table: EncryptedTable<EncryptedTableColumn>; }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:99](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L99) Options for single-value encrypt operations. Use a column from your table schema (from encryptedColumn) or a nested field (from encryptedField) as the target for encryption. Properties [#properties] column [#column] ```ts column: | EncryptedColumn | EncryptedField; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:101](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L101) The column or nested field to encrypt into. From [EncryptedColumn](/docs/reference/stack/latest/schema/classes/EncryptedColumn) or [EncryptedField](/docs/reference/stack/latest/schema/classes/EncryptedField). *** table [#table] ```ts table: EncryptedTable<EncryptedTableColumn>; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:102](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L102) # EncryptQueryOptions [**@cipherstash/stack**](../../index) *** Type Alias: EncryptQueryOptions [#type-alias-encryptqueryoptions] ```ts type EncryptQueryOptions = QueryTermBase; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:254](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L254) # Encrypted [**@cipherstash/stack**](../../index) *** Type Alias: Encrypted [#type-alias-encrypted] ```ts type Encrypted = CipherStashEncrypted; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:35](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L35) Structural type representing encrypted data. See also `EncryptedValue` for branded nominal typing. # EncryptedFields [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedFields [#type-alias-encryptedfieldst] ```ts type EncryptedFields<T> = { [K in keyof T as NonNullable<T[K]> extends Encrypted ? K : never]: T[K] }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:128](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L128) Type Parameters [#type-parameters] T [#t] `T` # EncryptedQueryResult [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedQueryResult [#type-alias-encryptedqueryresult] ```ts type EncryptedQueryResult = | Encrypted | string; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:122](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L122) Result of encryptQuery (single or batch): EQL or composite literal string # EncryptedReturnType [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedReturnType [#type-alias-encryptedreturntype] ```ts type EncryptedReturnType = "eql" | "composite-literal" | "escaped-composite-literal"; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:106](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L106) Format for encrypted query/search term return values # EncryptedSearchTerm [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedSearchTerm [#type-alias-encryptedsearchterm] ```ts type EncryptedSearchTerm = | Encrypted | string; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:119](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L119) Encrypted search term result: EQL object or composite literal string # EncryptedValue [**@cipherstash/stack**](../../index) *** Type Alias: EncryptedValue [#type-alias-encryptedvalue] ```ts type EncryptedValue = Brand<CipherStashEncrypted, "encrypted">; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:32](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L32) A branded type representing encrypted data. Cannot be accidentally used as plaintext. # EncryptionClientConfig [**@cipherstash/stack**](../../index) *** Type Alias: EncryptionClientConfig [#type-alias-encryptionclientconfig] ```ts type EncryptionClientConfig = { schemas: AtLeastOneCsTable<EncryptedTable<EncryptedTableColumn>>; config?: ClientConfig; logging?: LoggingConfig; }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:84](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L84) Properties [#properties] schemas [#schemas] ```ts schemas: AtLeastOneCsTable<EncryptedTable<EncryptedTableColumn>>; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:85](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L85) *** config? [#config] ```ts optional config: ClientConfig; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:86](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L86) *** logging? [#logging] ```ts optional logging: LoggingConfig; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:87](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L87) # KeysetIdentifier [**@cipherstash/stack**](../../index) *** Type Alias: KeysetIdentifier [#type-alias-keysetidentifier] ```ts type KeysetIdentifier = | { name: string; } | { id: string; }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:41](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L41) # LoggingConfig [**@cipherstash/stack**](../../index) *** Type Alias: LoggingConfig [#type-alias-loggingconfig] ```ts type LoggingConfig = { enabled?: boolean; pretty?: boolean; drain?: LoggerConfig["drain"]; }; ``` Defined in: [.tmp-stack/packages/stack/src/utils/logger/index.ts:4](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/utils/logger/index.ts#L4) Properties [#properties] enabled? [#enabled] ```ts optional enabled: boolean; ``` Defined in: [.tmp-stack/packages/stack/src/utils/logger/index.ts:5](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/utils/logger/index.ts#L5) *** pretty? [#pretty] ```ts optional pretty: boolean; ``` Defined in: [.tmp-stack/packages/stack/src/utils/logger/index.ts:6](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/utils/logger/index.ts#L6) *** drain? [#drain] ```ts optional drain: LoggerConfig["drain"]; ``` Defined in: [.tmp-stack/packages/stack/src/utils/logger/index.ts:7](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/utils/logger/index.ts#L7) # OtherFields [**@cipherstash/stack**](../../index) *** Type Alias: OtherFields [#type-alias-otherfieldst] ```ts type OtherFields<T> = { [K in keyof T as NonNullable<T[K]> extends Encrypted ? never : K]: T[K] }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:132](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L132) Type Parameters [#type-parameters] T [#t] `T` # QueryTypeName [**@cipherstash/stack**](../../index) *** Type Alias: QueryTypeName [#type-alias-querytypename] ```ts type QueryTypeName = | "orderAndRange" | "freeTextSearch" | "equality" | "steVecSelector" | "steVecTerm" | "searchableJson"; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:210](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L210) User-facing query type names for encrypting query values. * `'equality'`: Exact match. [Exact Queries](https://cipherstash.com/docs/platform/searchable-encryption/supported-queries/exact) * `'freeTextSearch'`: Text search. [Match Queries](https://cipherstash.com/docs/platform/searchable-encryption/supported-queries/match) * `'orderAndRange'`: Comparison and range. [Range Queries](https://cipherstash.com/docs/platform/searchable-encryption/supported-queries/range) * `'steVecSelector'`: JSONPath selector (e.g. `'$.user.email'`) * `'steVecTerm'`: Containment (e.g. `{ role: 'admin' }`) * `'searchableJson'`: Auto-infers selector or term from plaintext type (recommended) # ScalarQueryTerm [**@cipherstash/stack**](../../index) *** Type Alias: ScalarQueryTerm [#type-alias-scalarqueryterm] ```ts type ScalarQueryTerm = QueryTermBase & { value: JsPlaintext; }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:256](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L256) Type Declaration [#type-declaration] value [#value] ```ts value: JsPlaintext; ``` # SearchTerm [**@cipherstash/stack**](../../index) *** Type Alias: SearchTerm [#type-alias-searchterm] ```ts type SearchTerm = { value: JsPlaintext; column: EncryptedColumn; table: EncryptedTable<EncryptedTableColumn>; returnType?: EncryptedReturnType; }; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:111](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L111) Properties [#properties] value [#value] ```ts value: JsPlaintext; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:112](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L112) *** column [#column] ```ts column: EncryptedColumn; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:113](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L113) *** table [#table] ```ts table: EncryptedTable<EncryptedTableColumn>; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:114](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L114) *** returnType? [#returntype] ```ts optional returnType: EncryptedReturnType; ``` Defined in: [.tmp-stack/packages/stack/src/types.ts:115](https://github.com/cipherstash/protectjs/blob/b5af0c3d694c936e750cba5e1e0f7c422cc1bf04/packages/stack/src/types.ts#L115)