Skip to content

Commit 10c434b

Browse files
calvinbrewerclaude
authored andcommitted
feat(prisma-example): convert the example app to EQL v3
Schema: every column is a concrete public.eql_v3_* domain (EncryptedTextSearch / EncryptedDoubleOrd / EncryptedBigIntOrd / EncryptedDateOrd / EncryptedBoolean / EncryptedJson); wiring via cipherstashFromStackV3. Migrations regenerated from the v3 contract — initial app migration carries zero add_search_config ops; the cipherstash space ships both bundle baselines. E2E suites adapted to the v3 surface and green from a wiped database (7 files / 40 tests, live PG + ZeroKMS): equality/range/free-text tokens, order-term sorting, JSON @> containment (positive, negative, multi-key, nested, empty-needle rejection), lossless bigint beyond MAX_SAFE_INTEGER, and eql_v3_boolean's storage-only operator refusal pinned as a feature. README walkthrough e2e (repo-level) also green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 37c31ae commit 10c434b

27 files changed

Lines changed: 1113 additions & 958 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cipherstash/prisma-next-example': minor
3+
---
4+
5+
Convert the example app to EQL v3. Every column is now a concrete `public.eql_v3_*` domain authored with the per-domain constructors (`EncryptedTextSearch`, `EncryptedDoubleOrd`, `EncryptedBigIntOrd`, `EncryptedDateOrd`, `EncryptedBoolean`, `EncryptedJson`), wired through `cipherstashFromStackV3({ contractJson })`. The e2e harness runs the full v3 surface against live Postgres + ZeroKMS with no skips: equality/range/free-text-token operators, `cipherstashV3Asc`/`Desc` order-term sorting, encrypted JSON containment (`cipherstashJsonContains` — the v2 `cipherstashJsonb*` helpers do not exist in v3), lossless `bigint` beyond `Number.MAX_SAFE_INTEGER`, and the storage-only `eql_v3_boolean` refusal (`EncryptionOperatorError`) pinned as a feature. Migrations regenerate from the v3 contract: the initial app migration creates the `users` table against the v3 domains with zero `add_search_config` ops, and the cipherstash space carries both bundle baselines (v2 + v3).

examples/prisma/README.md

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
# @cipherstash/prisma-next example
22

3-
End-to-end demo of [`@cipherstash/prisma-next`](../../packages/prisma-next/README.md): searchable application-layer encryption for Postgres with [Prisma Next](https://www.npmjs.com/package/@prisma-next/cli), using [`@cipherstash/stack`](../../packages/stack/README.md) as the encryption SDK.
3+
End-to-end demo of [`@cipherstash/prisma-next`](../../packages/prisma-next/README.md): searchable application-layer encryption for Postgres with [Prisma Next](https://www.npmjs.com/package/@prisma-next/cli), using [`@cipherstash/stack`](../../packages/stack/README.md) as the encryption SDK — on **EQL v3**, where every encrypted column is a concrete `public.eql_v3_*` Postgres domain and the constructor you pick *is* the capability set.
44

5-
A single `User` model with one column per cipherstash codec (string, double, bigint, date, boolean, JSON), exercised end-to-end: insert, equality, free-text search, range, between, in-array, sort, and `decryptAll`-amortised read.
5+
A single `User` model with one column per plaintext family, exercised end-to-end: insert, equality, free-text token search, range, between, in-array, encrypted-order-term sort, JSON containment, and `decryptAll`-amortised read.
6+
7+
| Column | Constructor | Domain | Query surface |
8+
| --------------- | --------------------------------- | --------------------- | -------------------------------------- |
9+
| `email` | `cipherstash.EncryptedTextSearch()` | `eql_v3_text_search` | equality + order/range + free-text |
10+
| `salary` | `cipherstash.EncryptedDoubleOrd()` | `eql_v3_double_ord` | equality + order/range |
11+
| `accountId` | `cipherstash.EncryptedBigIntOrd()` | `eql_v3_bigint_ord` | equality + order/range (true `bigint`) |
12+
| `birthday` | `cipherstash.EncryptedDateOrd()` | `eql_v3_date_ord` | equality + order/range |
13+
| `emailVerified` | `cipherstash.EncryptedBoolean()` | `eql_v3_boolean` | storage-only (no operators) |
14+
| `preferences` | `cipherstash.EncryptedJson()` | `eql_v3_json` | `cipherstashJsonContains` (`@>`) |
615

716
📖 See the [Prisma Next encryption docs](https://cipherstash.com/docs/stack/cipherstash/encryption/prisma-next) for the full operator reference, security model, and known limitations.
817

@@ -11,12 +20,12 @@ A single `User` model with one column per cipherstash codec (string, double, big
1120
| Path | Purpose |
1221
| -------------------------- | --------------------------------------------------------------------------------------------- |
1322
| `docker-compose.yml` | Local Postgres 16 on port 54338. |
14-
| `prisma/schema.prisma` | Application schema (one `User` model exercising all six cipherstash codecs). |
23+
| `prisma/schema.prisma` | Application schema (one `User` model exercising six cipherstash v3 domains). |
1524
| `prisma-next.config.ts` | Wires `cipherstash` into `extensionPacks`. |
16-
| `src/db.ts` | One-call setup via `cipherstashFromStack({ contractJson })`. |
25+
| `src/db.ts` | One-call setup via `cipherstashFromStackV3({ contractJson })`. |
1726
| `src/index.ts` | The demo flow. |
1827
| `src/prisma/contract.*` | Emitted by `pnpm emit`. |
19-
| `migrations/` | Emitted by `pnpm migration:plan`. |
28+
| `migrations/` | Emitted by `pnpm migration:plan` (app space + the cipherstash EQL bundle baselines). |
2029

2130
## Prerequisites
2231

@@ -33,7 +42,7 @@ docker compose up -d
3342
pnpm install
3443
pnpm emit # PSL → contract.{json,d.ts}
3544
pnpm migration:plan --name initial
36-
pnpm migration:apply # installs EQL bundle + your app schema in one sweep (runs `prisma-next migrate`)
45+
pnpm migration:apply # installs the EQL bundles + your app schema in one sweep (runs `prisma-next migrate`)
3746
pnpm start # runs the demo
3847
```
3948

@@ -52,40 +61,50 @@ pnpm install && pnpm emit && pnpm typecheck
5261
## Expected output
5362

5463
```text
55-
--- Insert (mixed-codec round-trip) ---
56-
Inserted 4 rows across six cipherstash codecs.
64+
--- Insert (mixed-domain round-trip) ---
65+
Inserted 4 rows across six cipherstash v3 domains.
5766
58-
--- cipherstashEq (string equality) ---
67+
--- cipherstashEq (text_search equality) ---
5968
Found 1 row(s) for alice@example.com.
6069
user-0: alice@example.com
6170
62-
--- cipherstashIlike (string free-text-search) ---
63-
Found 3 row(s) matching %@example.com.
71+
--- cipherstashIlike (text_search free-text tokens) ---
72+
Found 3 row(s) whose email contains example.com.
6473
user-0: alice@example.com
6574
user-1: bob@example.com
6675
user-2: carol@example.com
6776
68-
--- cipherstashGt (double order-and-range) ---
77+
--- cipherstashGt (double_ord order-and-range) ---
6978
Found 2 user(s) with salary > 100,000.
7079
user-1: salary=110000
7180
user-3: salary=145000
7281
73-
--- cipherstashBetween (date order-and-range) ---
82+
--- cipherstashBetween (date_ord order-and-range) ---
7483
Found 3 user(s) born between 1985 and 1995.
7584
76-
--- cipherstashInArray (bigint equality) ---
85+
--- cipherstashInArray (bigint_ord equality) ---
7786
Found 2 user(s) whose accountId is in the supplied array.
7887
79-
--- cipherstashInArray (boolean equality-only) ---
80-
Found 3 user(s) with emailVerified = true.
88+
--- eql_v3_boolean (storage-only round-trip) ---
89+
user-2: emailVerified=false
90+
91+
--- cipherstashJsonContains (encrypted jsonb @>) ---
92+
Found 2 user(s) with a dark-theme preference.
93+
user-0: {"theme":"dark","notifications":true}
94+
user-2: {"theme":"dark","notifications":true}
8195
82-
--- cipherstashAsc (bare-column ORDER BY) ---
96+
--- cipherstashV3Asc (encrypted order-term ORDER BY) ---
8397
user-0: email=alice@example.com
8498
user-1: email=bob@example.com
8599
user-2: email=carol@example.com
86100
user-3: email=dave@otherorg.test
87101
```
88102

103+
Two v3 behaviours worth noticing in that output:
104+
105+
- **`cipherstashIlike` is bloom-filter token containment** (`eql_v3.contains`), not SQL `ILIKE` — the needle's tokens must appear in the ciphertext's index. Plain substrings like `example.com` are the idiomatic needle; `%` wildcards are not part of the model.
106+
- **`eql_v3_boolean` is storage-only.** It round-trips `true`/`false` losslessly but surfaces no search operators — calling one throws `EncryptionOperatorError`. Filter on a searchable column and decrypt the boolean from the result set.
107+
89108
## References
90109

91110
- 📖 [Prisma Next encryption docs](https://cipherstash.com/docs/stack/cipherstash/encryption/prisma-next) — the canonical reference.

examples/prisma/migrations/app/20260709T1034_initial/migration.json

Lines changed: 0 additions & 19 deletions
This file was deleted.

examples/prisma/migrations/app/20260709T1034_initial/migration.ts

Lines changed: 0 additions & 144 deletions
This file was deleted.

0 commit comments

Comments
 (0)