Schema and queries
CipherStash column constructors, runtime envelopes, and encrypted query operators for Prisma ORM 8.
Last updated
The CipherStash Prisma extension separates the encrypted column type from the runtime value envelope:
- In
schema.prisma, a domain-named constructor such asTextEq()fixes the Postgres plaintext family and searchable capabilities. - In application code, a primitive-named envelope such as
EncryptedStringwraps a plaintext value for encryption.
Column constructors
Choose the plaintext type first, then the narrowest query capability your application needs.
| Plaintext | Storage only | Equality | Order and range | Free-text only | All text queries |
|---|---|---|---|---|---|
string | Text() | TextEq() | TextOrd() | TextMatch() | TextSearch() |
number (int4) | Integer() | IntegerEq() | IntegerOrd() | — | — |
number (int2) | Smallint() | SmallintEq() | SmallintOrd() | — | — |
bigint (int8) | BigInt() | BigIntEq() | BigIntOrd() | — | — |
number (numeric) | Numeric() | NumericEq() | NumericOrd() | — | — |
number (float4) | Real() | RealEq() | RealOrd() | — | — |
number (float8) | Double() | DoubleEq() | DoubleOrd() | — | — |
Date (date) | Date() | DateEq() | DateOrd() | — | — |
Date (timestamp) | Timestamp() | TimestampEq() | TimestampOrd() | — | — |
boolean | Boolean() | — | — | — | — |
| JSON document | Json() supports containment and JSONPath queries | — | — | — | — |
Every *Ord() constructor includes equality. TextMatch() provides token matching without equality; TextSearch() combines equality, range, ordering, and token matching. Bare constructors such as Text() are storage-only.
Each constructor maps directly to an EQL v3 Postgres domain. For example, cipherstash.DoubleOrd() creates an eql_v3_double_ord column. The type cannot be broadened at query time.
Runtime envelopes
| Schema family | Write envelope |
|---|---|
Text* | EncryptedString.from(value) |
Integer*, Smallint*, Numeric*, Real*, Double* | EncryptedNumber.from(value) |
BigInt* | EncryptedBigInt.from(value) |
Date*, Timestamp* | EncryptedDate.from(value) |
Boolean | EncryptedBoolean.from(value) |
Json | EncryptedJson.from(value) |
Import envelopes and decryptAll from @cipherstash/stack-prisma/runtime.
import {
decryptAll,
EncryptedDate,
EncryptedNumber,
EncryptedString,
} from "@cipherstash/stack-prisma/runtime"
await db.orm.public.User.create({
id: "user-0",
email: EncryptedString.from("[email protected]"),
salary: EncryptedNumber.from(100_000),
birthday: EncryptedDate.from(new Date("1990-01-01")),
})
const rows = await db.orm.public.User.all()
await decryptAll(rows)decryptAll groups a result set by table and column to avoid one encryption-service round trip per value. Call .decrypt() on an envelope after the batch completes.
Query operators
Operators are generated only where the declared domain supports them.
| Operator | Meaning |
|---|---|
eqlEq(value), eqlNeq(value) | Equality and inequality |
eqlIn(values), eqlNotIn(values) | Set membership |
eqlMatch(term) | Free-text token match |
eqlGt/Gte/Lt/Lte(value) | Range comparison |
eqlBetween(low, high), eqlNotBetween(low, high) | Bounded range |
eqlAsc(column), eqlDesc(column) | Encrypted ordering |
eqlJsonContains(value) | JSON containment |
eqlJsonPathEq/Neq(path, value) | Equality at a JSONPath |
eqlJsonPathGt/Gte/Lt/Lte(path, value) | Range comparison at a JSONPath |
eqlJsonPathAsc/Desc(column, path) | Ordering by a JSONPath leaf |
import {
eqlAsc,
eqlJsonPathDesc,
} from "@cipherstash/stack-prisma/runtime"
const matches = await db.orm.public.User
.where((user) => user.email.eqlMatch("example.com"))
.all()
const earners = await db.orm.public.User
.where((user) => user.salary.eqlBetween(100_000, 150_000))
.orderBy((user) => eqlAsc(user.salary))
.all()
const preferences = await db.orm.public.User
.where((user) => user.profile.eqlJsonContains({ theme: "dark" }))
.orderBy((user) => eqlJsonPathDesc(user.profile, "$.score"))
.all()Free-text search is token matching, not SQL ILIKE. Pass a term such as example.com, not a wildcard pattern such as %example.com%.
Indexes
Encrypted predicates require functional Postgres indexes at meaningful scale. schema.prisma cannot express expression indexes, so put the index DDL in a Prisma raw-SQL migration operation and apply it with prisma-next migrate.
The domain-to-index matrix, SQL recipes, rollout timing, ANALYZE, and EXPLAIN verification are maintained in EQL indexes.
Subpath exports
| Import | Purpose |
|---|---|
@cipherstash/stack-prisma/control | Prisma extension pack |
@cipherstash/stack-prisma/v3 | cipherstashFromStack and the EQL v3 adapter |
@cipherstash/stack-prisma/runtime | Envelopes, decryptAll, and query helpers |
@cipherstash/stack-prisma/column-types | TypeScript-authored contract factories |