Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .changeset/prisma-next-drop-encrypted-prefix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
'@cipherstash/prisma-next': minor
'stash': patch
---

**Breaking (v3 authoring surface):** the EQL v3 PSL column constructors drop
the `Encrypted` prefix to line up with the stack / Drizzle `types.*` catalog —
the `cipherstash.` namespace already disambiguates. So
`cipherstash.EncryptedTextSearch()` → `cipherstash.TextSearch()`,
`cipherstash.EncryptedDoubleOrd()` → `cipherstash.DoubleOrd()`,
`cipherstash.EncryptedBoolean()` → `cipherstash.Boolean()`, etc.

The v3 one-call setup function is renamed `cipherstashFromStackV3` →
`cipherstashFromStack` (v3 is the default), and the existing v2 setup function
becomes `cipherstashFromStackV2`.

The camelCase TS-authoring factory exports move in lockstep:
`encryptedTextSearch` → `textSearch`, `encryptedDoubleOrd` → `doubleOrd`, etc.
(a property test enforces the PSL and TS names agree modulo first-letter case).

Unchanged: the runtime value envelopes (`EncryptedString`, `EncryptedNumber`,
`EncryptedBoolean`, …), the `cipherstash.*V2` legacy column constructors, the
generated `contract.json` / codec ids, and the `eql*` query operators.

The `stash-prisma-next` skill is updated to the new names (skills ship in the
`stash` tarball).
14 changes: 7 additions & 7 deletions examples/prisma/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ A single `User` model with one column per plaintext family, exercised end-to-end

| Column | Constructor | Domain | Query surface |
| --------------- | --------------------------------- | --------------------------- | -------------------------------------- |
| `email` | `cipherstash.EncryptedTextSearch()` | `public.eql_v3_text_search` | equality + order/range + free-text (`eqlMatch`) |
| `salary` | `cipherstash.EncryptedDoubleOrd()` | `public.eql_v3_double_ord` | equality + order/range |
| `accountId` | `cipherstash.EncryptedBigIntOrd()` | `public.eql_v3_bigint_ord` | equality + order/range (true `bigint`) |
| `birthday` | `cipherstash.EncryptedDateOrd()` | `public.eql_v3_date_ord` | equality + order/range |
| `emailVerified` | `cipherstash.EncryptedBoolean()` | `public.eql_v3_boolean` | storage-only (no operators) |
| `preferences` | `cipherstash.EncryptedJson()` | `public.eql_v3_json` | `eqlJsonContains` (`@>`) |
| `email` | `cipherstash.TextSearch()` | `public.eql_v3_text_search` | equality + order/range + free-text (`eqlMatch`) |
| `salary` | `cipherstash.DoubleOrd()` | `public.eql_v3_double_ord` | equality + order/range |
| `accountId` | `cipherstash.BigIntOrd()` | `public.eql_v3_bigint_ord` | equality + order/range (true `bigint`) |
| `birthday` | `cipherstash.DateOrd()` | `public.eql_v3_date_ord` | equality + order/range |
| `emailVerified` | `cipherstash.Boolean()` | `public.eql_v3_boolean` | storage-only (no operators) |
| `preferences` | `cipherstash.Json()` | `public.eql_v3_json` | `eqlJsonContains` (`@>`) |

📖 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.

Expand All @@ -22,7 +22,7 @@ A single `User` model with one column per plaintext family, exercised end-to-end
| `docker-compose.yml` | Local Postgres 16 on port 54338. |
| `prisma/schema.prisma` | Application schema (one `User` model exercising six cipherstash v3 domains). |
| `prisma-next.config.ts` | Wires `cipherstash` into `extensionPacks`. |
| `src/db.ts` | One-call setup via `cipherstashFromStackV3({ contractJson })`. |
| `src/db.ts` | One-call setup via `cipherstashFromStack({ contractJson })`. |
| `src/index.ts` | The demo flow. |
| `src/prisma/contract.*` | Emitted by `pnpm emit`. |
| `migrations/` | Emitted by `pnpm migration:plan` (app space + the cipherstash EQL bundle baselines). |
Expand Down
12 changes: 6 additions & 6 deletions examples/prisma/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@

model User {
id String @id
email cipherstash.EncryptedTextSearch()
salary cipherstash.EncryptedDoubleOrd()
accountId cipherstash.EncryptedBigIntOrd() @map("accountid")
birthday cipherstash.EncryptedDateOrd()
emailVerified cipherstash.EncryptedBoolean() @map("emailverified")
preferences cipherstash.EncryptedJson()
email cipherstash.TextSearch()
salary cipherstash.DoubleOrd()
accountId cipherstash.BigIntOrd() @map("accountid")
birthday cipherstash.DateOrd()
emailVerified cipherstash.Boolean() @map("emailverified")
preferences cipherstash.Json()

@@map("users")
}
8 changes: 4 additions & 4 deletions examples/prisma/src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Wire the Prisma Next Postgres runtime with the cipherstash EQL v3
* extension in one call.
*
* `cipherstashFromStackV3({ contractJson })` derives the v3 encryption
* `cipherstashFromStack({ contractJson })` derives the v3 encryption
* schemas from the contract (one `public.eql_v3_*` domain per column),
* constructs the `@cipherstash/stack` `EncryptionV3` client against
* your `CS_*` env vars or local profile, builds the SDK adapter, and
Expand All @@ -11,19 +11,19 @@
* does not model.
*
* A v3 client is v3-only: a contract carrying v2 cipherstash codec ids
* is rejected at setup (use `cipherstashFromStack` from
* is rejected at setup (use `cipherstashFromStackV2` from
* `@cipherstash/prisma-next/stack` for a v2 contract).
*/

import 'dotenv/config'

import { cipherstashFromStackV3 } from '@cipherstash/prisma-next/v3'
import { cipherstashFromStack } from '@cipherstash/prisma-next/v3'
import postgres from '@prisma-next/postgres/runtime'

import type { Contract } from './prisma/contract.d'
import contractJson from './prisma/contract.json' with { type: 'json' }

const cipherstash = await cipherstashFromStackV3({ contractJson })
const cipherstash = await cipherstashFromStack({ contractJson })

export const db = postgres<Contract>({
contractJson,
Expand Down
8 changes: 4 additions & 4 deletions examples/prisma/test/e2e/mixed.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* middleware controls and what this suite observes.)
*
* Crossing counts are observed by wrapping a fresh `CipherstashSdk`
* (built from `cipherstashFromStackV3({ contractJson }).encryptionClient`
* (built from `cipherstashFromStack({ contractJson }).encryptionClient`
* via `createCipherstashV3Sdk`) with a counting decorator and threading
* the wrapped instance into a private `db` runtime. Concretely:
*
Expand All @@ -40,7 +40,7 @@ import {
eqlAsc,
} from '@cipherstash/prisma-next/runtime'
import {
cipherstashFromStackV3,
cipherstashFromStack,
createCipherstashV3Sdk,
deriveStackSchemasV3,
} from '@cipherstash/prisma-next/v3'
Expand Down Expand Up @@ -141,12 +141,12 @@ describe('Mixed-domain e2e (live PG + EQL v3 + ZeroKMS)', () => {
let runtime: { close(): Promise<void> } | undefined

beforeAll(async () => {
// Reuse the encryption client from `cipherstashFromStackV3` so the
// Reuse the encryption client from `cipherstashFromStack` so the
// counting wrapper observes the same ZeroKMS workspace + schema
// surface the example app would in production. Re-derive the v3
// stack schemas from `contractJson` to satisfy
// `createCipherstashV3Sdk`'s `(client, schemas)` contract.
const { encryptionClient } = await cipherstashFromStackV3({ contractJson })
const { encryptionClient } = await cipherstashFromStack({ contractJson })
const schemas = deriveStackSchemasV3(contractJson)
const baseSdk = createCipherstashV3Sdk(encryptionClient, schemas)
counting = wrapWithCounting(baseSdk)
Expand Down
2 changes: 1 addition & 1 deletion examples/prisma/test/e2e/str-range.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* End-to-end coverage for the `eql_v3_text_search` domain against
* live Postgres + EQL v3 + ZeroKMS.
*
* `cipherstash.EncryptedTextSearch()` is the maximal text domain:
* `cipherstash.TextSearch()` is the maximal text domain:
* equality + order/range (OPE) + free-text search (bloom match) on one
* column. Pins:
* - Round-trip decrypt recovers the source strings.
Expand Down
18 changes: 9 additions & 9 deletions packages/prisma-next/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Declare encrypted columns directly in `schema.prisma`, and the framework's migra
- 🔍 Searchable encryption — equality, free-text search, range, order, JSON path and containment
- 🎯 Type-safe query operators — EQL v3 uses the EQL-derived `eql*` vocabulary (`eqlEq`, `eqlMatch`, `eqlGt`, `eqlAsc`, …); the legacy v2 surface keeps its `cipherstash*` names
- ⚡ Bulk encrypt / bulk decrypt coalescing — one SDK round-trip per `(table, column)` group per query
- 🧩 One-call setup via `cipherstashFromStackV3({ contractJson })` (v2: `cipherstashFromStack`) — no duplicate stack schema to maintain
- 🧩 One-call setup via `cipherstashFromStack({ contractJson })` (v2: `cipherstashFromStackV2`) — no duplicate stack schema to maintain
- 🛡️ Plaintext redaction on every implicit serialisation path (`toJSON`, `toString`, `util.inspect`, …)

## Installation
Expand All @@ -27,10 +27,10 @@ npm install @cipherstash/stack @cipherstash/prisma-next
// prisma/schema.prisma
model User {
id String @id
email cipherstash.EncryptedString()
salary cipherstash.EncryptedDouble()
birthday cipherstash.EncryptedDate()
preferences cipherstash.EncryptedJson()
email cipherstash.TextSearch()
salary cipherstash.DoubleOrd()
birthday cipherstash.DateOrd()
preferences cipherstash.Json()
}
```

Expand All @@ -47,12 +47,12 @@ export default defineConfig({
```typescript
// src/db.ts
import "dotenv/config"
import { cipherstashFromStack } from "@cipherstash/prisma-next/stack"
import { cipherstashFromStackV2 } from "@cipherstash/prisma-next/stack"
import postgres from "@prisma-next/postgres/runtime"
import type { Contract } from "./prisma/contract.d"
import contractJson from "./prisma/contract.json" with { type: "json" }

const cipherstash = await cipherstashFromStack({ contractJson })
const cipherstash = await cipherstashFromStackV2({ contractJson })

export const db = postgres<Contract>({
contractJson,
Expand Down Expand Up @@ -91,8 +91,8 @@ See the [full documentation](https://cipherstash.com/docs/stack/cipherstash/encr

| Subpath | Purpose |
| ---------------- | ------------------------------------------------------------------------------------------------------ |
| `./v3` | The complete EQL v3 surface: `cipherstashFromStackV3`, the `eql*` query operations, `eqlAsc`/`eqlDesc`, envelopes, middleware, SDK adapter |
| `./stack` | One-call setup against `@cipherstash/stack` (EQL v2): `cipherstashFromStack`, `deriveStackSchemas`, `createCipherstashSdk` |
| `./v3` | The complete EQL v3 surface: `cipherstashFromStack`, the `eql*` query operations, `eqlAsc`/`eqlDesc`, envelopes, middleware, SDK adapter |
| `./stack` | One-call setup against `@cipherstash/stack` (EQL v2): `cipherstashFromStackV2`, `deriveStackSchemas`, `createCipherstashSdk` |
| `./control` | `SqlControlExtensionDescriptor` (contract space + pack meta + codec lifecycle hooks) |
| `./runtime` | Six envelope classes + `CipherstashSdk` + codec runtime + `decryptAll` + four free-standing helpers |
| `./middleware` | `bulkEncryptMiddleware(sdk)` |
Expand Down
28 changes: 22 additions & 6 deletions packages/prisma-next/src/contract-authoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
* `*_ord_ore` variants) gets exactly one argument-less PSL constructor,
* derived 1:1 from `EXPOSED_DOMAIN_ENTRIES`:
*
* `cipherstash.EncryptedTextSearch` → codec
* `cipherstash.TextSearch` → codec
* `cipherstash/eql-v3/eql_v3_text_search@1`, native type
* `public.eql_v3_text_search`, static
* `typeParams: { castAs, capabilities }`.
*
* There are NO options: the constructor IS the capability set. The name is
* the mechanical `Encrypted<Stem><Suffix>` transform of the bare domain with
* its `eql_v3_` prefix stripped (`eql_v3_bigint_ord` → `EncryptedBigIntOrd`);
* its `eql_v3_` prefix stripped (`eql_v3_bigint_ord` → `BigIntOrd`);
* the codec id keeps the registry key VERBATIM (never name-transformed).
*
* ### Static typeParams — confirmed framework shape (Step 0)
Expand Down Expand Up @@ -107,13 +107,29 @@ function coreName(bareDomain: string): string {
return `${stemLabel}${suffixLabel}`
}

/** `eql_v3_text_search` → `EncryptedTextSearch`, `eql_v3_bigint_ord` → `EncryptedBigIntOrd`. */
/**
* `eql_v3_text_search` → `TextSearch`, `eql_v3_bigint_ord` → `BigIntOrd`.
*
* The `cipherstash.` PSL namespace already disambiguates, so the constructor
* names drop the `Encrypted` prefix to line up with the stack / Drizzle
* `types.*` catalog (`types.TextSearch`, `types.BigintOrd`, …). The v2
* constructors keep their `*V2` names; the runtime envelope classes
* (`EncryptedString`, `EncryptedBoolean`, …) are a separate surface and are
* unchanged.
*/
export function v3PascalName(bareDomain: string): string {
return `Encrypted${coreName(bareDomain)}`
return coreName(bareDomain)
}
/** `eql_v3_text_search` → `encryptedTextSearch`, `eql_v3_bigint_ord` → `encryptedBigIntOrd`. */
/**
* `eql_v3_text_search` → `textSearch`, `eql_v3_bigint_ord` → `bigIntOrd`.
*
* Kept in lockstep with {@link v3PascalName} — the camelCase TS-authoring
* factory name is the PSL constructor name with a lowercased first letter
* (a property test enforces the two agree modulo first-letter case).
*/
export function v3CamelName(bareDomain: string): string {
return `encrypted${coreName(bareDomain)}`
const core = coreName(bareDomain)
return `${core.charAt(0).toLowerCase()}${core.slice(1)}`
}

/**
Expand Down
66 changes: 33 additions & 33 deletions packages/prisma-next/src/exports/column-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* `{ castAs, capabilities }` typeParams block — identical to what its PSL
* counterpart lowers to, so PSL- and TS-authored contracts emit
* byte-identical `contract.json`. There are NO options: the factory IS the
* capability set (`encryptedTextSearch` vs `encryptedText`, and so on).
* capability set (`textSearch` vs `text`, and so on).
*
* ## v2: legacy `*V2` aliases
*
Expand Down Expand Up @@ -120,50 +120,50 @@ function v3Authored<C extends AnyEncryptedV3Column>(
}

// Each factory has a DISTINCT return type (V3ColumnDescriptor<EncryptedXColumn>);
// `encryptedBigIntOrd()` is not assignable to `encryptedText()` and vice-versa.
// `bigIntOrd()` is not assignable to `text()` and vice-versa.
// Note the stack factory names: bigint is `types.Bigint*` (not `BigInt*`).
// text family
export const encryptedText = v3Authored(types.Text)
export const encryptedTextEq = v3Authored(types.TextEq)
export const encryptedTextOrd = v3Authored(types.TextOrd)
export const encryptedTextMatch = v3Authored(types.TextMatch)
export const encryptedTextSearch = v3Authored(types.TextSearch)
export const text = v3Authored(types.Text)
export const textEq = v3Authored(types.TextEq)
export const textOrd = v3Authored(types.TextOrd)
export const textMatch = v3Authored(types.TextMatch)
export const textSearch = v3Authored(types.TextSearch)
// integer
export const encryptedInteger = v3Authored(types.Integer)
export const encryptedIntegerEq = v3Authored(types.IntegerEq)
export const encryptedIntegerOrd = v3Authored(types.IntegerOrd)
export const integer = v3Authored(types.Integer)
export const integerEq = v3Authored(types.IntegerEq)
export const integerOrd = v3Authored(types.IntegerOrd)
// smallint
export const encryptedSmallint = v3Authored(types.Smallint)
export const encryptedSmallintEq = v3Authored(types.SmallintEq)
export const encryptedSmallintOrd = v3Authored(types.SmallintOrd)
export const smallint = v3Authored(types.Smallint)
export const smallintEq = v3Authored(types.SmallintEq)
export const smallintOrd = v3Authored(types.SmallintOrd)
// bigint
export const encryptedBigInt = v3Authored(types.Bigint)
export const encryptedBigIntEq = v3Authored(types.BigintEq)
export const encryptedBigIntOrd = v3Authored(types.BigintOrd)
export const bigInt = v3Authored(types.Bigint)
export const bigIntEq = v3Authored(types.BigintEq)
export const bigIntOrd = v3Authored(types.BigintOrd)
// numeric
export const encryptedNumeric = v3Authored(types.Numeric)
export const encryptedNumericEq = v3Authored(types.NumericEq)
export const encryptedNumericOrd = v3Authored(types.NumericOrd)
export const numeric = v3Authored(types.Numeric)
export const numericEq = v3Authored(types.NumericEq)
export const numericOrd = v3Authored(types.NumericOrd)
// real
export const encryptedReal = v3Authored(types.Real)
export const encryptedRealEq = v3Authored(types.RealEq)
export const encryptedRealOrd = v3Authored(types.RealOrd)
export const real = v3Authored(types.Real)
export const realEq = v3Authored(types.RealEq)
export const realOrd = v3Authored(types.RealOrd)
// double
export const encryptedDouble = v3Authored(types.Double)
export const encryptedDoubleEq = v3Authored(types.DoubleEq)
export const encryptedDoubleOrd = v3Authored(types.DoubleOrd)
export const double = v3Authored(types.Double)
export const doubleEq = v3Authored(types.DoubleEq)
export const doubleOrd = v3Authored(types.DoubleOrd)
// date
export const encryptedDate = v3Authored(types.Date)
export const encryptedDateEq = v3Authored(types.DateEq)
export const encryptedDateOrd = v3Authored(types.DateOrd)
export const date = v3Authored(types.Date)
export const dateEq = v3Authored(types.DateEq)
export const dateOrd = v3Authored(types.DateOrd)
// timestamp
export const encryptedTimestamp = v3Authored(types.Timestamp)
export const encryptedTimestampEq = v3Authored(types.TimestampEq)
export const encryptedTimestampOrd = v3Authored(types.TimestampOrd)
export const timestamp = v3Authored(types.Timestamp)
export const timestampEq = v3Authored(types.TimestampEq)
export const timestampOrd = v3Authored(types.TimestampOrd)
// boolean (storage-only)
export const encryptedBoolean = v3Authored(types.Boolean)
export const boolean = v3Authored(types.Boolean)
// json (encrypted JSONB, ste_vec containment)
export const encryptedJson = v3Authored(types.Json)
export const json = v3Authored(types.Json)

// ---------------------------------------------------------------------------
// v2 legacy aliases (verbatim pre-rename bodies, now named *V2)
Expand Down
Loading
Loading