The CipherStash extension pack adds six encrypted column types and 17 query operators to Prisma Next, backed by the CipherStash EQL bundle. Columns are encrypted at the application layer before they reach the database, yet remain fully queryable — equality, range, substring search, and JSON path operators all work against ciphertext through EQL’s searchable encryption indices.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/prisma/prisma-next/llms.txt
Use this file to discover all available pages before exploring further.
What the extension provides
- Six encrypted column types:
EncryptedString,EncryptedDouble,EncryptedBigInt,EncryptedDate,EncryptedBoolean,EncryptedJson. Each maps a plain JS type to an EQL ciphertext stored in a singleeql_v2_encryptedPostgres column. - Per-codec search-mode flags (
equality,freeTextSearch,orderAndRange,searchableJson) that drive which EQL search-config indices are emitted at migration time. All flags default totrue. - 13 predicate operators surfaced as column methods (e.g.
m.email.cipherstashEq(...),m.salary.cipherstashGt(...)). - 4 free-standing helpers for sort and JSON projection:
cipherstashAsc,cipherstashDesc,cipherstashJsonbPathQueryFirst,cipherstashJsonbGet. bulkEncryptMiddleware(sdk)— coalesces cipherstash parameters across rows into onebulkEncryptSDK round-trip per(table, column)group before wire encoding.decryptAll(rows)— reads a result set and coalesces every cipherstash envelope into onebulkDecryptSDK round-trip per(table, column)group.- EQL contract space — the extension ships the
eql_v2_configurationtable,eql_v2_encryptedcomposite types,eql_v2domains, and the EQL bundle SQL as a Prisma Next contract space so the framework manages it alongside your own schema.
The six encrypted column types
| TS factory / PSL constructor | JS plaintext | EQL cast_as | Search-mode flags |
|---|---|---|---|
encryptedString / cipherstash.EncryptedString | string | text | equality, freeTextSearch, orderAndRange |
encryptedDouble / cipherstash.EncryptedDouble | number (IEEE-754) | double | equality, orderAndRange |
encryptedBigInt / cipherstash.EncryptedBigInt | bigint | big_int | equality, orderAndRange |
encryptedDate / cipherstash.EncryptedDate | Date (calendar date) | date | equality, orderAndRange |
encryptedBoolean / cipherstash.EncryptedBoolean | boolean | boolean | equality |
encryptedJson / cipherstash.EncryptedJson | JSON-serialisable unknown | jsonb | searchableJson |
eql_v2.add_search_config(...) operations at field-added events and eql_v2.remove_search_config(...) operations when a flag is turned off between contract versions.
Setup
Declare encrypted columns in your schema
true. To opt out of a specific index, set the flag to false explicitly — for example, encryptedString({ equality: false, freeTextSearch: false, orderAndRange: false }) for storage-only encryption with no queryable indices.Emit the contract and apply migrations
db init applies the bundled EQL baseline migration, which installs the eql_v2_configuration table, eql_v2_encrypted type, eql_v2 domains, and the EQL bundle SQL before your application migrations run.Writing and reading encrypted data
Operator reference
Predicate operators (column methods)
Predicate operators return boolean and are available in.where() clauses. Each operator requires a specific search-mode flag to be enabled on the column codec.
| Operator | Required flag | Lowering | Applies to |
|---|---|---|---|
cipherstashEq(plaintext) | equality | eql_v2.eq(self, $N) | all codecs |
cipherstashNe(plaintext) | equality | NOT eql_v2.eq(self, $N) | all codecs |
cipherstashInArray([...]) | equality | eql_v2.eq(self, $1) OR eql_v2.eq(self, $2) OR ... | all codecs |
cipherstashNotInArray([...]) | equality | NOT (eql_v2.eq(self, $1) OR ...) | all codecs |
cipherstashIlike(pattern) | freeTextSearch | eql_v2.ilike(self, $N) | EncryptedString |
cipherstashNotIlike(pattern) | freeTextSearch | NOT eql_v2.ilike(self, $N) | EncryptedString |
cipherstashGt(plaintext) | orderAndRange | eql_v2.gt(self, $N) | EncryptedString, EncryptedDouble, EncryptedBigInt, EncryptedDate |
cipherstashGte(plaintext) | orderAndRange | eql_v2.gte(self, $N) | as above |
cipherstashLt(plaintext) | orderAndRange | eql_v2.lt(self, $N) | as above |
cipherstashLte(plaintext) | orderAndRange | eql_v2.lte(self, $N) | as above |
cipherstashBetween(lo, hi) | orderAndRange | eql_v2.gte(self, $1) AND eql_v2.lte(self, $2) | as above |
cipherstashNotBetween(lo, hi) | orderAndRange | NOT (eql_v2.gte(...) AND eql_v2.lte(...)) | as above |
cipherstashJsonbPathExists(path) | searchableJson | eql_v2.jsonb_path_exists(self, $N) | EncryptedJson |
The framework’s built-in operators (
eq, gt, ilike, etc.) are not available on cipherstash columns and will produce a compile-time error. EQL ciphertexts contain randomized nonces — SQL = or < against raw eql_v2_encrypted values always returns false. The cipherstash-namespaced operators route through the correct EQL search-config indices.Free-standing helpers (non-predicate)
These helpers take a column expression as input and return a sort item or a typed expression — they are not assignable to the column-method predicate surface.| Helper | Required flag | Returns | Applies to |
|---|---|---|---|
cipherstashAsc(col) | orderAndRange | OrderByItem | EncryptedString, EncryptedDouble, EncryptedBigInt, EncryptedDate |
cipherstashDesc(col) | orderAndRange | OrderByItem | as above |
cipherstashJsonbPathQueryFirst(col, path) | searchableJson | Expression<cipherstash/json@1> | EncryptedJson |
cipherstashJsonbGet(col, path) | searchableJson | Expression<cipherstash/json@1> | EncryptedJson |
EQL search-config index types
Each search-mode flag maps to one EQL index family:| EQL index | Triggered by | What it enables |
|---|---|---|
unique | equality | Deterministic lookup via eql_v2.eq / eql_v2.in_array. |
match | freeTextSearch | Bloom-filter substring search via eql_v2.ilike. Probabilistic — false positives possible, false negatives not. |
ore | orderAndRange | Order-revealing encryption for eql_v2.gt / gte / lt / lte / between and bare-column ORDER BY. |
ste_vec | searchableJson | Searchable tree encoding for eql_v2.jsonb_path_query_first and eql_v2."->". |
Choosing the right codec
| You want to … | Pick |
|---|---|
| Searchable email or arbitrary string with substring search | encryptedString({ equality: true, freeTextSearch: true }) |
| Numeric range queries on salary, price, or score | encryptedDouble({ equality: true, orderAndRange: true }) |
| Account or ID number with exact-match and range | encryptedBigInt({ equality: true, orderAndRange: true }) |
| Calendar-date range queries | encryptedDate({ equality: true, orderAndRange: true }) |
Boolean flag with WHERE col = true predicates | encryptedBoolean({ equality: true }) |
| Searchable JSON document | encryptedJson({ searchableJson: true }) |
| Storage-only encryption (no queryable indices) | Any factory with every flag set to false |
Subpath exports
| Subpath | Purpose |
|---|---|
./control | SqlControlExtensionDescriptor (contract space, pack metadata, codec lifecycle hooks) |
./runtime | Envelope classes, CipherstashSdk, codec runtime, decryptAll, free-standing helpers |
./middleware | bulkEncryptMiddleware(sdk) |
./pack | cipherstashPackMeta for TypeScript contract authoring |
./column-types | Six TS factories: encryptedString, encryptedDouble, encryptedBigInt, encryptedDate, encryptedBoolean, encryptedJson |
./control, ./runtime, and ./middleware planes are tree-shakable — a runtime consumer never pulls the EQL bundle SQL or codec lifecycle hooks, and a control-plane consumer never pulls envelope classes, the SDK interface, or the middleware.
Security model
- Plaintext lifetime — the write-side envelope retains its plaintext slot post-encrypt. Treat envelope objects as plaintext-equivalent for the lifetime of the variable.
- Ciphertext routing — every read-side envelope carries the
(table, column)it was decoded from.decryptanddecryptAllroute bulk SDK calls by that key so the SDK can select the correct key material per column. - Plaintext redaction —
toJSON,toString,valueOf,Symbol.toPrimitive, andSymbol.for('nodejs.util.inspect.custom')all return[REDACTED]. Accidentalconsole.log,JSON.stringify, template-literal interpolation, andutil.inspectcannot leak plaintext. Explicit access is viaenvelope.expose(). - Cancellation — every internal SDK call accepts an
AbortSignal.
Known limitations
The following are explicitly deferred from the current implementation.
cipherstashJsonbPathExistsagainst the live EQL bundle — the framework currently binds the JSONpath as a plain textParamRefrather than the hashed selector the EQL bundle expects. Predicate queries return zero rows against rows that should match. The non-predicate helpers (cipherstashJsonbPathQueryFirst,cipherstashJsonbGet) work correctly. Workaround: project all rows with the SELECT-expression helpers and filter client-side.EncryptedBigIntcapped atNumber.MAX_SAFE_INTEGER— the CipherStash SDK’sJsPlaintextunion does not includebigint. Values beyondNumber.MAX_SAFE_INTEGERcannot be encrypted and will throw on overflow.- No encrypted timestamp / datetime — CipherStash offers only calendar-date encryption (
EncryptedDate). Encrypted timestamp support is deferred. - No re-encryption migration primitive — adopting cipherstash for an existing populated column requires re-encrypting row data. The codec lifecycle hook emits the correct search-config DDL but does not touch row data. Work around it with a hand-authored
dataTransformmigration.