Better Drizzle
almeidazs/better-drizzle
Write, review, and debug code that uses better-drizzle, the typed repository layer over Drizzle ORM 1.x (better(db), client.users.findMany, paginate, cursor, upsertMany, relation include/connect…
Integrate CipherStash searchable field-level encryption with Prisma Next using @cipherstash/stack-prisma (EQL v3).
$ npx skills add cipherstash/stack --skill stash-prisma -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install cipherstash/stack stash-prisma --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/stash-prisma .claude/skills/stash-prisma && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "stash-prisma" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-prisma into .claude/skills/stash-prisma/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-prisma", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/cipherstash/stack/tree/main/skills/stash-prismaType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add cipherstash/stack --skill stash-prisma -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install cipherstash/stack stash-prisma --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/stash-prisma .agents/skills/stash-prisma && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "stash-prisma" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-prisma into .agents/skills/stash-prisma/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-prisma", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add cipherstash/stack --skill stash-prisma -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install cipherstash/stack stash-prisma --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/stash-prisma .cursor/skills/stash-prisma && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "stash-prisma" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-prisma into .cursor/skills/stash-prisma/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-prisma", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/cipherstash/stack.git --path skills/stash-prisma--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add cipherstash/stack --skill stash-prisma -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install cipherstash/stack stash-prisma --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/stash-prisma .gemini/skills/stash-prisma && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "stash-prisma" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-prisma into .gemini/skills/stash-prisma/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-prisma", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install cipherstash/stack stash-prismaInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add cipherstash/stack --skill stash-prisma -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/stash-prisma .github/skills/stash-prisma && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "stash-prisma" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-prisma into .github/skills/stash-prisma/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-prisma", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add cipherstash/stack --skill stash-prisma -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install cipherstash/stack stash-prisma --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/cipherstash/stack.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/stash-prisma .opencode/skills/stash-prisma && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "stash-prisma" agent skill from https://github.com/cipherstash/stack/tree/main/skills/stash-prisma into .opencode/skills/stash-prisma/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "stash-prisma", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
stash-prismaIntegrate CipherStash searchable field-level encryption with Prisma Next using @cipherstash/stack-prisma (EQL v3).
Stash Prisma is an agent skill from cipherstash/stack. Integrate CipherStash searchable field-level encryption with Prisma Next using @cipherstash/stack-prisma (EQL v3). Covers the full 31-constructor catalog of domain-named encrypted column types in schema.prisma (per plaintext type × capability tier — Text/TextEq/TextOrd/TextMatch/TextSearch, Integer/Smallint/BigInt/Numeric/Real/Double × Eq/Ord, Date/Timestamp × Eq/Ord, Boolean, Json), the one-call cipherstashFromStack wiring, the runtime value envelopes (EncryptedString/Number/BigInt/Date/Boolean/Json) and…
Its SKILL.md is about 5.9k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Databases, covering ORMs and data access. It works with Prisma and PostgreSQL. The repository describes itself as: Searchable, application-level encryption for building privacy-first apps. The licence is MIT.
3 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 415b62c. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npxnpmFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use npx and npm, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
CS_CLIENT_KEYCS_CLIENT_ACCESS_KEYFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Stash Prisma loads about 5.9k tokens when it runs. Until then it costs about 221 tokens; SKILL.md has 2,278 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
Never log or read `~/.cipherstash`** or `.env*` credential files (see `stash-cli`).Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from cipherstash/stack at commit 415b62c, republished under its MIT licence (© cipherstash). 2,278 words, ~5,884 tokens.
.claude/skills/stash-prisma/SKILL.md (or your agent's skills folder).Guide for searchable field-level encryption in a Prisma Next app with
@cipherstash/stack-prisma (EQL v3), powered by @cipherstash/stack. You declare
encrypted columns directly in schema.prisma; Prisma Next's migration system
installs the EQL bundle in the same sweep that creates your tables — there is no
separate stash eql install step.
@cipherstash/stack-prismais EQL v3 only — there is no EQL v2 surface. Everything below is v3.
In EQL v3 every encrypted column is a concrete Postgres domain
(public.eql_v3_text_search, public.eql_v3_double_ord, …) whose query
capabilities are fixed by the column type you choose — there is no capability
config object. See the stash-encryption skill for the domain catalog and
capability semantics; this skill covers the Prisma-Next-specific surface.
schema.prismaeql* operatorscipherstashFromStacknpm install @cipherstash/stack @cipherstash/stack-prismaOr run npx stash init --prisma, which detects Prisma Next, installs both
packages pinned to the CLI release, and authenticates. It does not scaffold
the wiring files — Prisma Next derives its schema from contract.json, so there
is no encryption-client file to generate; init prints the next steps (declare
encrypted columns, emit the contract, run the migration) instead.
schema.prismaThe column types are domain-named — the name encodes the query capability
(matching the @cipherstash/stack types.* catalog), not a generic primitive:
model User {
id String @id
email cipherstash.TextSearch() // eq + range + free-text + ORDER BY
salary cipherstash.DoubleOrd() // eq + range + ORDER BY
accountId cipherstash.BigIntOrd() // eq + range + ORDER BY
birthday cipherstash.DateOrd() // eq + range + ORDER BY
emailVerified cipherstash.Boolean() // storage-only (no operators)
preferences cipherstash.Json() // containment (@>)
}The example shows six types; the full catalog is 31 constructors — one
per exposed public.eql_v3_* domain, derived mechanically from the domain
registry. Pick by plaintext TypeScript type first, then by the queries
you need:
| Plaintext (TS type) | Storage-only | Equality | Order + range | Free-text | Everything |
|---|---|---|---|---|---|
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() — searchable JSON: containment + JSONPath equality/range/ORDER BY |
Reading the table:
IntegerOrd() → eql_v3_integer_ord, Text() → eql_v3_text, and so on
(Json() → eql_v3_json_search).*Ord domain includes equality (equality + range + ORDER BY);
every *Eq domain is equality only; the bare family name is storage-only
(encrypt/decrypt, no operators). TextMatch is free-text only — no
equality. TextSearch carries all three text capabilities.IntegerOrd() (JS number) — not DoubleOrd()
(float semantics) and not BigIntOrd(), whose plaintext is a JS bigint
and rejects number values.*OrdOre variants exist in the database bundle but are deliberately
not exposed as constructors (their btree opclass is superuser-gated — see
stash-indexing).The type is fixed at the column — there is no capability tuner. A value you
only store and decrypt can use a storage-only domain; a value you filter or
sort needs the matching *Eq / *Ord / text-search domain.
prisma-next.config.tsSince Prisma Next 0.17 an application depends on exactly one database facade
(@prisma/orm-postgres; the retired @prisma-next/* scope no longer
publishes), and the facade's defineConfig wires the family, target, adapter,
driver, and PSL provider internally:
import cipherstash from '@cipherstash/stack-prisma/control'
import { defineConfig } from '@prisma/orm-postgres/config'
export default defineConfig({
contract: './prisma/schema.prisma',
output: 'src/prisma',
extensions: [cipherstash],
db: { connection: process.env['DATABASE_URL']! },
})The config key is extensions (0.17 renamed extensionPacks; the old key
fails loudly).
cipherstashFromStack in src/db.tsimport 'dotenv/config'
import { cipherstashFromStack } from '@cipherstash/stack-prisma/v3'
import postgres from '@prisma/orm-postgres/runtime'
import type { Contract } from './prisma/contract.d'
import contractJson from './prisma/contract.json' with { type: 'json' }
const cipherstash = await cipherstashFromStack({ contractJson })
export const db = postgres<Contract>({
contractJson,
extensions: cipherstash.extensions,
middleware: cipherstash.middleware,
})cipherstashFromStack({ contractJson }) derives the v3 encryption schemas from
the contract (one public.eql_v3_* domain per column), constructs the
@cipherstash/stack Encryption client from your CS_* env vars or local
profile, builds the SDK adapter, and returns ready-to-spread extensions and
middleware.
The extension pack contributes its own contract space at
migrations/cipherstash/, so the EQL bundle installs alongside your application
schema:
npx stash auth login # one-time, per developer
npx prisma-next contract emit
npx prisma-next migration plan --name initial
npx prisma-next migrate # installs EQL bundle + your schemaThe apply command is the top-level prisma-next migrate (add --yes to skip the
confirmation prompt in CI). There is no prisma-next migration apply subcommand.
Do not run stash eql install for a Prisma Next project — prisma-next migrate owns EQL installation, and stash init --prisma skips the
standalone installer for exactly this reason. The CLI enforces this: stash eql install detects a Prisma Next project and refuses (pointing you at prisma-next migrate) unless you pass --force.
@cipherstash/stack-prisma, re-plan before anything elseOnly prisma-next migration plan copies migration packages into your repo, and
the seed phase never rewrites a directory that already exists. A
migrations/cipherstash/ generated against an older version therefore keeps that
version's EQL bundle forever — it is old, not corrupt, so it passes every
integrity check and nothing reports a problem.
After upgrading the package, delete the vendored directory and regenerate it:
rm -rf migrations/cipherstash
npx prisma-next migration planThe database is untouched by this: markers are keyed by invariant, so already-applied invariants do not re-run and the only new work is the upgrade edges.
Skipping it is not always fatal, which is what makes it easy to miss. 1.0.0
and 1.1.x shipped the baseline at eql-3.0.4, and 1.2.x shipped it at
eql-3.0.6; later versions bake eql-3.1.0 into the same baseline directory
(20260601T0100_install_eql_v3_bundle), so its bytes and its migrationHash
changed:
| You run | With a stale migrations/cipherstash/ |
|---|---|
prisma-next migration plan | Succeeds, silently keeping the stale baseline — no hash mismatch, because it is intact, just old. |
prisma-next migrate (existing database) | Correct: applies only the upgrade edges the database has not walked. A 1.2.x database walks the 3.1.0 edge; a 1.0.0 or 1.1.x one walks 3.0.5, 3.0.6 and 3.1.0 (the bundle is re-installed three times). |
prisma-next migrate (fresh database) | Correct end state, but installs the stale baseline's bundle and then immediately re-installs each newer one over it. |
prisma-next db init (fresh database) | Fails. From a 1.0.0 or 1.1.x space: Operation cipherstash.upgrade-eql-v3-bundle-3.0.5 has class "data" which is not allowed by policy. From a 1.2.x space: Operation cipherstash.upgrade-eql-v3-bundle-3.1.0 has class "data" which is not allowed by policy. db init is additive-only and the stale baseline does not carry the newer invariants, so the planner has to reach for the data-classed upgrade edges. The message does not say any of that — the remedy is the rm -rf above. |
Upgrading and then running migrate or db init without planning first
leaves the newer bundle off disk entirely, so it is silently skipped and the
database stays on the older one.
stash encrypt)Declaring an encrypted column only covers new writes. To encrypt rows already in
a plaintext column, use the CLI's rollout lifecycle — stash encrypt backfill,
then switch reads, then stash encrypt drop (stash-cli and stash-encryption
are canonical for the sequence and its dual-write precondition).
Two things are Prisma-Next-specific:
stash.config.ts's client option
points at a file this integration deliberately doesn't have. stash encrypt backfill — the only command that loads it — detects a Prisma Next project,
reads the emitted contract.json (src/prisma/, prisma/, or the project
root), and derives the schemas the same way the runtime does. So run
prisma-next contract emit before stash encrypt backfill, and don't
hand-author a bridge client file.cipherstash.cs_migrations is
normally created by stash eql install, which this integration never runs.
stash encrypt backfill bootstraps it itself (idempotently), so the backfill
user needs CREATE on the database the first time.The adapter emits the encrypted query operators, but no index DDL — without
functional indexes over the eql_v3.* extractors, every encrypted predicate
sequential-scans. Since Prisma Next 0.17, @@index takes an expression
argument, so the indexes are declared in schema.prisma next to the columns
they serve and ride the same prisma-next migration plan / prisma-next migrate flow as everything else. Never run index DDL out-of-band.
One index per capability the column's domain carries:
model User {
// ... fields, including the encrypted columns ...
// cipherstash.TextEq / TextSearch: equality
@@index(expression: "eql_v3.eq_term(email)", name: "users_email_eq", type: "btree")
// cipherstash.*Ord / TextSearch: ordering + range (on numeric/date/timestamp
// _ord domains this one index serves = too; TextOrd needs the eq_term index
// above as well)
@@index(expression: "eql_v3.ord_term(created_at)", name: "users_created_at_ord", type: "btree")
// cipherstash.TextMatch / TextSearch: free-text match
@@index(expression: "eql_v3.match_term(bio)", name: "users_bio_match", type: "gin")
// cipherstash.Json: containment
@@index(expression: "(eql_v3.to_ste_vec_query(profile)::jsonb) jsonb_path_ops", name: "users_profile_json", type: "gin")
}Three rules the interpreter enforces: an @@index takes exactly one of a
fields list or an expression; an expression index requires name or
map (no default name can be derived from an expression); and an options
argument requires type. The expression string is the entire element list
between the parens of CREATE INDEX, inserted verbatim — which is why the
Json recipe carries its own parens and the jsonb_path_ops opclass. TS-authored
contracts have the same surface: index({ expression, name }) alongside the
column factories — and there, passing type makes options required (use
options: {} when you have none).
name: is a logical name, not the physical one: the index is created as
<name>_<8-hex content hash> (users_email_eq lands as e.g.
users_email_eq_1a2b3c4d), so when verifying with EXPLAIN or querying
pg_indexes, match on the prefix rather than the exact string. map: pins
the exact physical name instead — but the planner emits a drift warning
whenever map: is combined with an expression body, so prefer name: and
prefix-matching unless you must adopt an index that already exists under a
bare name.
ANALYZE is still part of the recipe — an expression index has no statistics
until it runs, and PSL cannot express it — so it rides a raw-SQL operation
(rawSql from @prisma/orm-postgres/migration) in the migration that
introduces the indexes:
rawSql({
id: 'analyze.users',
label: 'Refresh statistics for the new expression indexes',
operationClass: 'additive',
target: {
id: 'postgres',
details: { schema: 'public', objectType: 'table', name: 'users' },
},
precheck: [],
execute: [{ description: 'refresh statistics', sql: 'ANALYZE "public"."users"' }],
postcheck: [],
})rawSql also remains the fallback for index DDL PSL doesn't carry — with one
hard exception: CREATE INDEX CONCURRENTLY cannot run through the migration
flow at all. The runner wraps every apply in a single transaction, and
Postgres rejects CONCURRENTLY inside a transaction block (error 25001), so
a rawSql operation carrying it fails deterministically. This rarely matters
here: under the rollout timing in stash-indexing, these indexes are built
while the encrypted column is new or freshly backfilled, where a plain
CREATE INDEX is correct. If a table is genuinely too hot for that, the
concurrent build has to happen outside the migration runner.
Everything above works as a non-superuser role (Supabase included); only the
ORE-flavour (_ord_ore) ordering opclass is superuser-gated. For the full
model — which domains take which index, engagement rules, EXPLAIN
verification, rollout timing — see the stash-indexing skill. For encrypted
predicates written as raw SQL rather than through the cipherstash:*
operators — operand casts to eql_v3.query_*, per-driver parameter binding —
see the stash-postgres skill.
An EQL upgrade drops every index above, and prisma-next migrate will not
put them back. Installing a new bundle begins with DROP SCHEMA IF EXISTS eql_v3 CASCADE, which cascade-drops every functional index over an eql_v3.*
extractor — the PSL expression indexes and any rawSql index DDL alike.
Encrypted columns and their data survive and queries keep working — they just
silently sequential-scan again, so nothing errors and nothing warns.
Recovery is a new migration, because an applied one is never replayed: a
PSL expression index has to change its name: (the physical name carries a
content hash of the expression, so re-declaring the same index under the same
logical name plans no work), and a rawSql recovery operation needs a new op
id re-issuing the CREATE INDEX statements with its own ANALYZE. See
stash-indexing § "When to Create Indexes During an Encryption Rollout" for
the mechanism and the EXPLAIN check that confirms the indexes are back.
Capturing and restoring them automatically is tracked in
cipherstash/stack#918.
At the value boundary you wrap plaintext in a runtime envelope (primitive-named,
distinct from the domain-named column type) and unwrap with decryptAll +
.decrypt():
import {
decryptAll,
EncryptedString, EncryptedNumber, EncryptedBigInt,
EncryptedDate, EncryptedBoolean, EncryptedJson,
} from '@cipherstash/stack-prisma/runtime'
await db.orm.public.User.create({
id: 'user-0',
email: EncryptedString.from('alice@example.com'),
salary: EncryptedNumber.from(100_000), // DoubleOrd column
accountId: EncryptedBigInt.from(100_000_000_001n),
birthday: EncryptedDate.from(new Date('1990-01-01')),
emailVerified: EncryptedBoolean.from(true),
preferences: EncryptedJson.from({ theme: 'dark' }),
})
const rows = await db.orm.public.User.where((u) => u.email.eqlEq('alice@example.com')).all()
await decryptAll(rows) // batches one SDK round-trip per (table,column)
console.log(await rows[0]?.email.decrypt()) // 'alice@example.com'Envelopes pair by plaintext type, not by column name — one envelope
covers every domain of its family: EncryptedString ↔ all Text* columns,
EncryptedNumber ↔ all number families (Integer*, Smallint*,
Numeric*, Real*, Double*), EncryptedBigInt ↔ BigInt*,
EncryptedDate ↔ Date* and Timestamp*, EncryptedBoolean ↔ Boolean,
EncryptedJson ↔ Json.
eql*)Operators live on the encrypted column inside .where((u) => …) and encrypt the
search term for you — Prisma Next never sees plaintext in a query. EQL v3 uses the
EQL-derived eql* vocabulary:
| Operator | Meaning | Requires |
|---|---|---|
eqlEq(v) / eqlNeq(v) | equality / inequality | any searchable domain |
eqlIn(vs) / eqlNotIn(vs) | membership | any searchable domain |
eqlMatch(term) | free-text token match (eql_v3.matches) | TextSearch |
eqlGt/eqlGte/eqlLt/eqlLte(v) | range comparison | an *Ord domain |
eqlBetween(lo,hi) / eqlNotBetween(lo,hi) | range window | an *Ord domain |
eqlAsc(col) / eqlDesc(col) | ORDER BY (free functions, take the column) | an *Ord or TextSearch domain |
eqlJsonContains(obj) | encrypted JSON containment (@>) | EncryptedJson |
eqlJsonPathEq/Neq(path,v) | exact value equality/inequality at a JSONPath | EncryptedJson |
eqlJsonPathGt/Gte/Lt/Lte(path,v) | string/number ordering at a JSONPath | EncryptedJson |
eqlJsonPathAsc(col,path) / eqlJsonPathDesc(col,path) | ORDER BY a scalar JSONPath leaf (free functions) | EncryptedJson |
// range
await db.orm.public.User.where((u) => u.salary.eqlGt(100_000)).all()
// free-text
await db.orm.public.User.where((u) => u.email.eqlMatch('example.com')).all()
// between
await db.orm.public.User.where((u) => u.birthday.eqlBetween(lo, hi)).all()
// bigint membership
await db.orm.public.User.where((u) => u.accountId.eqlIn([100_000_000_001n])).all()
// encrypted JSON containment
await db.orm.public.User.where((u) => u.preferences.eqlJsonContains({ theme: 'dark' })).all()
// exact JSONPath equality (value-selector containment; GIN-indexable)
await db.orm.public.User.where((u) => u.preferences.eqlJsonPathEq('$.theme', 'dark')).all()
// JSONPath ordering (ciphertext-free selector + scalar term)
await db.orm.public.User.where((u) => u.preferences.eqlJsonPathGte('$.score', 80)).all()
// ordering
import { eqlAsc } from '@cipherstash/stack-prisma/runtime'
await db.orm.public.User.orderBy((u) => eqlAsc(u.salary)).all()
// ordering by a JSONPath leaf; missing paths follow PostgreSQL NULL ordering
import { eqlJsonPathAsc } from '@cipherstash/stack-prisma/runtime'
await db.orm.public.User.orderBy((u) => eqlJsonPathAsc(u.preferences, '$.score')).all()Applying an operator its domain doesn't support (e.g. eqlGt on a
storage-only EncryptedBoolean, or eqlMatch on a non-text domain) is a typed
error at build time, not a runtime surprise.
Same credential model as the rest of Stack:
npx stash auth login (device-code flow; token in ~/.cipherstash).CS_* env vars (CS_WORKSPACE_CRN, CS_CLIENT_ID,
CS_CLIENT_KEY, CS_CLIENT_ACCESS_KEY), minted with stash env. The
stash-auth skill is canonical for credentials and auth strategies;
stash-zerokms for keysets and what the credentials can reach.cipherstashFromStack resolves CS_* when present, else the local profile.
@cipherstash/stack wraps a native FFI module and must be excluded from bundling
(serverExternalPackages, esbuild external, etc.) — see the stash-encryption
skill's bundling section. The Prisma Next adapter is native-only:
cipherstashFromStack constructs the native @cipherstash/stack client, and
there is no wasm-inline variant of this adapter — the WASM entry is a
different client for non-Prisma edge paths (stash-edge), not a drop-in here.
Run Prisma Next apps on a Node runtime where the native module loads.
| Subpath | Purpose |
|---|---|
@cipherstash/stack-prisma/v3 | The v3 surface: cipherstashFromStack, the SDK adapter, envelopes/middleware |
@cipherstash/stack-prisma/control | The extension pack for extensions: [...] |
@cipherstash/stack-prisma/runtime | Envelope classes, decryptAll, eql* operators, EncryptedString.from()… |
@cipherstash/stack-prisma/stack | One-call setup against @cipherstash/stack: cipherstashFromStack |
@cipherstash/stack-prisma/column-types | camelCase factories (textSearch, bigIntOrd, …) for TS-authored contracts — emits byte-identical contract.json to the PSL constructors |
prisma-next migrate (top-level, not migration apply), never stash eql install.DoubleOrd column ↔ EncryptedNumber.from(...) value.prisma-next contract emit) after changing a
column's encrypted type, so cipherstashFromStack and the migrations agree.@cipherstash/stack-prisma — rm -rf migrations/cipherstash && npx prisma-next migration plan. Only migration plan vendors new migration packages; skip it and a fresh db init fails with
... has class "data" which is not allowed by policy.~/.cipherstash or .env* credential files (see stash-cli).© cipherstash, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/stash-prisma of cipherstash/stack.
Open the folder on GitHubat commit 415b62c
Stash Prisma next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Stash Prisma this skillcipherstash/stack | 157 | — | ~5.9k | Automated safety check: Notes | MIT | |
| Better Drizzlealmeidazs/better-drizzle | 347 | — | ~1.7k | Automated safety check: Pass | Apache-2.0 | |
| Prisma Database Setupcurvenote/curvenote | 169 | 3 repos | ~1.4k | Automated safety check: Pass | MIT | |
| Add Backendahpxex/open-dashboard | 146 | — | ~3.6k | Automated safety check: Pass | MIT | |
| Database FundamentalsDanielPodolsky/ownyourcode | 290 | 1 repos | ~1.6k | Automated safety check: Pass | MIT | |
| Database Expertcin12211/orca-q | 223 | — | ~2.8k | Automated safety check: Pass | MIT |
almeidazs/better-drizzle
Write, review, and debug code that uses better-drizzle, the typed repository layer over Drizzle ORM 1.x (better(db), client.users.findMany, paginate, cursor, upsertMany, relation include/connect…
curvenote/curvenote
Guides for configuring Prisma with different database providers (PostgreSQL, MySQL, SQLite, MongoDB, etc.).
ahpxex/open-dashboard
Everything about the data layer — pick one of six ready-to-run backend templates (TanStack Start + Drizzle + better-auth, Hono + Drizzle + better-auth, Hono + Prisma + better-auth, Hono + Drizzle +…
DanielPodolsky/ownyourcode
Reviews schema design, SQL queries, ORM patterns. An agent skill from DanielPodolsky/ownyourcode.
cin12211/orca-q
Database performance optimization, schema design, query analysis, and connection management across PostgreSQL, MySQL, MongoDB, and SQLite with ORM integration.
curvenote/curvenote
Prisma Postgres setup and operations guidance across Console, create-db CLI, Management API, and Management API SDK.
cipherstash/stack
How an agent files a GitHub issue on cipherstash repos — required structure (Background / Problem / Proposal), dumbed-down wording rules, and pre-filing checks.
cipherstash/stack
How an agent authors branches, commits, and pull requests on cipherstash/stack — naming, signed commits, the changeset/skills/meta-file checklist, and PR body structure with dumbed-down wording.
cipherstash/stack
The ZeroKMS key model — keysets, clients, client keys, and the grant/revoke lifecycle.
cipherstash/stack
Deploy a CipherStash encryption rollout to a live environment without losing data — the multi-deploy ladder (schema-add + dual-write → backfill → read cutover → stop dual-writes → drop plaintext)…
cipherstash/stack
Supply-chain security controls for the @cipherstash/stack monorepo.
cipherstash/stack
Integrate CipherStash encryption with Drizzle ORM using @cipherstash/stack-drizzle (EQL v3).
Works with
Categories
Integrate CipherStash searchable field-level encryption with Prisma Next using @cipherstash/stack-prisma (EQL v3). Stash Prisma is an agent skill from cipherstash/stack. Integrate CipherStash searchable field-level encryption with Prisma Next using @cipherstash/stack-prisma (EQL v3).
Stash Prisma fits situations like: adding encryption to a Prisma Next project; upgrading @cipherstash/stack-prisma; choosing a column type; querying encrypted columns.
Run `npx skills add cipherstash/stack --skill stash-prisma -a claude-code`. Or copy the skill folder (skills/stash-prisma in cipherstash/stack) into .claude/skills/stash-prisma in your project. Claude Code loads it when a task matches its description.
Run `npx skills add cipherstash/stack --skill stash-prisma -a codex`. Or copy the skill folder (skills/stash-prisma in cipherstash/stack) into .agents/skills/stash-prisma in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add cipherstash/stack --skill stash-prisma -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/stash-prisma, .gemini/skills/stash-prisma, .github/skills/stash-prisma and .opencode/skills/stash-prisma in your project.
Going by SKILL.md and its folder, Stash Prisma needs the command-line tools its instructions call (npx and npm) and credentials named CS_CLIENT_KEY and CS_CLIENT_ACCESS_KEY. Our summary lists: Node.js.
SKILL.md contains no URLs. Its commands use npx and npm, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (mentions a .env file), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Stash Prisma is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.9k tokens (SKILL.md is roughly 24k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Stash Prisma: Better Drizzle (almeidazs/better-drizzle, 347 stars), Prisma Database Setup (curvenote/curvenote, 169 stars), Add Backend (ahpxex/open-dashboard, 146 stars) and Database Fundamentals (DanielPodolsky/ownyourcode, 290 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
cipherstash (a GitHub organization) maintains it in cipherstash/stack, which has 157 GitHub stars. The repository holds 14 skills in this directory. The repository was last updated on October 7, 2026.
Source: cipherstash/stack on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.