-
Notifications
You must be signed in to change notification settings - Fork 3
feat: release prisma next extension #444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| --- | ||
| "@cipherstash/prisma-next": minor | ||
| "stash": minor | ||
| --- | ||
|
|
||
| Add `@cipherstash/prisma-next` — searchable application-layer encryption for Postgres with Prisma Next. The framework's migration system installs the EQL bundle in the same `prisma-next migration apply` sweep that creates the application schema; no separate `stash db install` step. | ||
|
|
||
| **`@cipherstash/prisma-next` (new package, initial release)** | ||
|
|
||
| - **Six encrypted column types** — `EncryptedString`, `EncryptedDouble`, `EncryptedBigInt`, `EncryptedDate`, `EncryptedBoolean`, `EncryptedJson` — declared via PSL constructors (`cipherstash.Encrypted*()`) or TS factories (`encryptedString()`, etc.). | ||
| - **17 query operators** — 13 predicate operators surfaced as column methods (`cipherstashEq`, `cipherstashIlike`, `cipherstashGt`, `cipherstashBetween`, `cipherstashInArray`, `cipherstashJsonbPathExists`, …) and 4 free-standing helpers (`cipherstashAsc`, `cipherstashDesc`, `cipherstashJsonbPathQueryFirst`, `cipherstashJsonbGet`). | ||
| - **Per-codec search-mode flags** (`equality`, `freeTextSearch`, `orderAndRange`, `searchableJson`) drive the EQL search-config indices the codec lifecycle hook emits at migration time. Defaults to `true` across the board. | ||
| - **One-call setup** via `cipherstashFromStack({ contractJson })` from `@cipherstash/prisma-next/stack` — derives the stack `encryptedTable` / `encryptedColumn` schemas from `contract.json` (single source of truth, no duplicate hand-written declarations), constructs the `@cipherstash/stack` `EncryptionClient`, builds the framework-native `CipherstashSdk` adapter, and returns ready-to-spread `{ extensions, middleware, encryptionClient }` for `postgres<Contract>({...})`. | ||
| - **Layered API** — `deriveStackSchemas(contractJson)` and `createCipherstashSdk(client, schemas)` exposed as primitives for advanced users (custom keysets, multi-tenant routing, non-stack KMS). | ||
| - **Bulk-encrypt middleware** (`bulkEncryptMiddleware(sdk)`) coalesces every plaintext placeholder across a query into one `bulkEncrypt` SDK round-trip per `(table, column)` group. `decryptAll(rows)` does the symmetric coalescing on the read side. | ||
| - **Misconfig diagnostic** — if the user constructs the runtime descriptor but forgets to register `bulkEncryptMiddleware(sdk)` against the same SDK, the codec's encode throws a `RUNTIME.ENCODE_FAILED` envelope with a copy-pasteable wiring snippet at the first encrypted write. | ||
| - **Subpath exports** — `./stack`, `./control`, `./runtime`, `./middleware`, `./pack`, `./column-types`; tree-shakable along the control / runtime / middleware seams. | ||
| - **Contributes an EQL contract space** — installs the `eql_v2` schema, `eql_v2_encrypted` composite type, `ore_*` types, EQL functions / operators / casts via the cipherstash extension's baseline migration. Runs in the same control-plane sweep as the application schema. | ||
| - **Full docs**: https://cipherstash.com/docs/stack/cipherstash/encryption/prisma-next. | ||
|
|
||
| **`stash` (new feature)** | ||
|
|
||
| - **`stash init --prisma-next`** — new init provider for Prisma Next projects. Reuses `authenticate` + `resolve-database` + `install-deps` (additionally installs `@cipherstash/prisma-next`), skips `install-eql` (the framework handles it via `prisma-next migration apply`) and `build-schema` (`cipherstashFromStack` derives schemas from the contract — no hand-written encryption client file). Detected automatically when a `prisma-next.config.*` or `@cipherstash/prisma-next` dependency is present in the project. | ||
| - **`detectPrismaNext(cwd)`** — new export from `commands/db/detect.ts` mirroring the existing `detectDrizzle` / `detectSupabase` helpers. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| name: Prisma Next E2E | ||
|
|
||
| # End-to-end tests for `@cipherstash/prisma-next`: spins up a real | ||
| # Postgres container, applies the cipherstash baseline migration | ||
| # (EQL bundle install) + the example app's schema, then runs the | ||
| # suite at `examples/prisma/test/e2e/` against a live ZeroKMS | ||
| # workspace. | ||
| # | ||
| # Triggers only on changes that affect the package or the example | ||
| # (the unit-test suite in `tests.yml` covers everything that doesn't | ||
| # need a live workspace). | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| paths: | ||
| - 'packages/prisma-next/**' | ||
| - 'examples/prisma/**' | ||
| - '.github/workflows/prisma-next-e2e.yml' | ||
| pull_request: | ||
| branches: | ||
| - '**' | ||
| paths: | ||
| - 'packages/prisma-next/**' | ||
| - 'examples/prisma/**' | ||
| - '.github/workflows/prisma-next-e2e.yml' | ||
|
|
||
| jobs: | ||
| e2e: | ||
| name: Run Prisma Next E2E | ||
| runs-on: blacksmith-4vcpu-ubuntu-2404 | ||
|
|
||
| # Skip cleanly on fork PRs where secrets aren't available. The | ||
| # global-setup hook in the suite hard-errors when `CS_WORKSPACE_CRN` | ||
| # is unset; gating at the job level produces a clean "skipped" | ||
| # status instead of a noisy failure. | ||
| if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }} | ||
|
|
||
| env: | ||
| CS_WORKSPACE_CRN: ${{ secrets.CS_WORKSPACE_CRN }} | ||
| CS_CLIENT_ID: ${{ secrets.CS_CLIENT_ID }} | ||
| CS_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }} | ||
| CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }} | ||
|
|
||
| steps: | ||
| - name: Checkout Repo | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - uses: pnpm/action-setup@v6.0.3 | ||
| name: Install pnpm | ||
| with: | ||
| run_install: false | ||
|
|
||
| - name: Install Node.js | ||
| uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: 22 | ||
| cache: 'pnpm' | ||
|
|
||
| # node-pty's install hook falls back to `node-gyp rebuild` when no | ||
| # linux-x64 prebuild matches. pnpm/action-setup v6 no longer ships | ||
| # node-gyp on PATH, so install it explicitly. | ||
| - name: Install node-gyp | ||
| run: npm install -g node-gyp | ||
|
|
||
| - name: Install dependencies | ||
| run: pnpm install --frozen-lockfile | ||
|
|
||
| # Write the CS_* credentials and the harness DATABASE_URL into the | ||
| # example app's .env so the runtime + the `prisma-next migration | ||
| # apply` invocation in global-setup both pick them up. The harness | ||
| # also overrides DATABASE_URL inside the test process to point at | ||
| # the container, but the migration:apply subprocess relies on | ||
| # prisma-next.config.ts → process.env['DATABASE_URL'] being set | ||
| # before the test runner spawns it. | ||
| - name: Create .env file in examples/prisma | ||
| run: | | ||
| touch ./examples/prisma/.env | ||
| echo "DATABASE_URL=postgres://cipherstash:cipherstash@localhost:54329/cipherstash_e2e" >> ./examples/prisma/.env | ||
| echo "CS_WORKSPACE_CRN=${{ secrets.CS_WORKSPACE_CRN }}" >> ./examples/prisma/.env | ||
| echo "CS_CLIENT_ID=${{ secrets.CS_CLIENT_ID }}" >> ./examples/prisma/.env | ||
| echo "CS_CLIENT_KEY=${{ secrets.CS_CLIENT_KEY }}" >> ./examples/prisma/.env | ||
| echo "CS_CLIENT_ACCESS_KEY=${{ secrets.CS_CLIENT_ACCESS_KEY }}" >> ./examples/prisma/.env | ||
|
|
||
| # Build via turbo so the `^build` dependency on | ||
| # `@cipherstash/stack` (which `@cipherstash/prisma-next` imports | ||
| # `/schema` from) is honoured. A bare | ||
| # `pnpm --filter @cipherstash/prisma-next build` bypasses the | ||
| # task graph and leaves the upstream dist/ empty, surfacing as | ||
| # `Cannot find module '@cipherstash/stack/schema'` from tsc. | ||
| - name: Build @cipherstash/prisma-next | ||
| run: pnpm exec turbo run build --filter @cipherstash/prisma-next | ||
|
|
||
| - name: Emit example contract | ||
| run: pnpm --filter @cipherstash/prisma-next-example emit | ||
|
|
||
| - name: Start E2E Postgres container | ||
| working-directory: examples/prisma | ||
| run: | | ||
| docker compose -f test/e2e/docker-compose.yml up -d | ||
| # Wait for pg_isready before handing off to the suite — the | ||
| # global-setup hook expects the container to already be up. | ||
| for i in {1..60}; do | ||
| if docker exec cipherstash-e2e-postgres pg_isready -U cipherstash -d cipherstash_e2e >/dev/null 2>&1; then | ||
| echo "Postgres ready" | ||
| break | ||
| fi | ||
| sleep 1 | ||
| done | ||
|
|
||
| - name: Run E2E suite | ||
| run: pnpm --filter @cipherstash/prisma-next-example test:e2e | ||
|
|
||
| - name: Stop E2E Postgres container | ||
| if: always() | ||
| working-directory: examples/prisma | ||
| run: docker compose -f test/e2e/docker-compose.yml down -v | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,5 +66,6 @@ mise.local.toml | |
| cipherstash.toml | ||
| cipherstash.secret.toml | ||
| sql/cipherstash-*.sql | ||
| .cipherstash/ | ||
|
|
||
| notes/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # Postgres connection. The database must have the EQL bundle | ||
| # installed; `pnpm migration:apply` (after `pnpm migration:plan`) | ||
| # installs it for you alongside the application schema. | ||
| # | ||
| # Defaults match the bundled `docker-compose.yml`. Run | ||
| # `docker compose up -d` from this directory to start a Postgres on | ||
| # port 5544 with these credentials. | ||
| DATABASE_URL=postgres://postgres:postgres@localhost:5544/cipherstash_prisma_example | ||
|
|
||
| # CipherStash workspace credentials — **deployment only**. | ||
| # | ||
| # For local development, run `npx stash auth login` once. The PKCE flow | ||
| # stores per-developer credentials in your OS keychain, and the | ||
| # `@cipherstash/stack` `EncryptionClient` picks them up automatically. | ||
| # No CS_* env vars needed. | ||
| # | ||
| # Set the four values below only when you're deploying — production | ||
| # servers and CI runners are machine accounts with no human at the | ||
| # keyboard, so they use static credentials provisioned via the | ||
| # CipherStash dashboard (Settings → Access Keys). | ||
| # | ||
| # CS_WORKSPACE_CRN= | ||
| # CS_CLIENT_ID= | ||
| # CS_CLIENT_KEY= | ||
| # CS_CLIENT_ACCESS_KEY= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| # @cipherstash/prisma-next example | ||
|
|
||
| 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. | ||
|
|
||
| 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. | ||
|
|
||
| 📖 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. | ||
|
|
||
| ## Layout | ||
|
|
||
| | Path | Purpose | | ||
| | -------------------------- | --------------------------------------------------------------------------------------------- | | ||
| | `docker-compose.yml` | Local Postgres 16 on port 5544. | | ||
| | `prisma/schema.prisma` | Application schema (one `User` model exercising all six cipherstash codecs). | | ||
| | `prisma-next.config.ts` | Wires `cipherstash` into `extensionPacks`. | | ||
| | `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`. | | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| 1. **Docker** for the bundled Postgres on port 5544 (or any Postgres 16+). | ||
| 2. **A CipherStash workspace** — sign up at [cipherstash.com](https://cipherstash.com), then run `stash auth login` (PKCE; caches credentials in your OS keychain — no `CS_*` env vars needed in local dev). | ||
|
|
||
| ## Run it | ||
|
|
||
| ```bash | ||
| cp .env.example .env # DATABASE_URL points at the bundled Postgres | ||
| stash auth login # one-time, per developer | ||
|
|
||
| docker compose up -d | ||
| pnpm install | ||
| pnpm emit # PSL → contract.{json,d.ts} | ||
| pnpm migration:plan --name initial | ||
| pnpm migration:apply # installs EQL bundle + your app schema in one sweep | ||
| pnpm start # runs the demo | ||
| ``` | ||
|
|
||
| Teardown: | ||
|
|
||
| ```bash | ||
| docker compose down -v | ||
| ``` | ||
|
|
||
| Or, to just verify the example typechecks and emits a valid contract (no database, no workspace): | ||
|
|
||
| ```bash | ||
| pnpm install && pnpm emit && pnpm typecheck | ||
| ``` | ||
|
Comment on lines
+46
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add required README sections for native-module externalization and test execution. This README still misses two required items: a note that As per coding guidelines, "Each example app must include a README covering: setup (env vars, install, run commands), notes on native module externalization, and how to run tests". Also applies to: 89-93 🤖 Prompt for AI Agents |
||
|
|
||
| ## Expected output | ||
|
|
||
| ```text | ||
| --- Insert (mixed-codec round-trip) --- | ||
| Inserted 4 rows across six cipherstash codecs. | ||
|
|
||
| --- cipherstashEq (string equality) --- | ||
| Found 1 row(s) for alice@example.com. | ||
| user-0: alice@example.com | ||
|
|
||
| --- cipherstashIlike (string free-text-search) --- | ||
| Found 3 row(s) matching %@example.com. | ||
| user-0: alice@example.com | ||
| user-1: bob@example.com | ||
| user-2: carol@example.com | ||
|
|
||
| --- cipherstashGt (double order-and-range) --- | ||
| Found 2 user(s) with salary > 100,000. | ||
| user-1: salary=110000 | ||
| user-3: salary=145000 | ||
|
|
||
| --- cipherstashBetween (date order-and-range) --- | ||
| Found 3 user(s) born between 1985 and 1995. | ||
|
|
||
| --- cipherstashInArray (bigint equality) --- | ||
| Found 2 user(s) whose accountId is in the supplied array. | ||
|
|
||
| --- cipherstashInArray (boolean equality-only) --- | ||
| Found 3 user(s) with emailVerified = true. | ||
|
|
||
| --- cipherstashAsc (bare-column ORDER BY) --- | ||
| user-0: email=alice@example.com | ||
| user-1: email=bob@example.com | ||
| user-2: email=carol@example.com | ||
| user-3: email=dave@otherorg.test | ||
| ``` | ||
|
|
||
| ## References | ||
|
|
||
| - 📖 [Prisma Next encryption docs](https://cipherstash.com/docs/stack/cipherstash/encryption/prisma-next) — the canonical reference. | ||
| - [`@cipherstash/prisma-next` package README](../../packages/prisma-next/README.md) — install, subpath exports, quick start. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Local Postgres for the @cipherstash/prisma-next example. | ||
| # | ||
| # Usage: | ||
| # docker compose up -d # start | ||
| # docker compose down -v # stop + delete volume (fresh state) | ||
| # | ||
| # The DATABASE_URL in .env.example matches the values below: | ||
| # postgres://postgres:postgres@localhost:5544/cipherstash_prisma_example | ||
| # | ||
| # Port 5544 (not 5432) is used to avoid colliding with any host-side | ||
| # Postgres / other example containers. | ||
|
|
||
| services: | ||
| postgres: | ||
| image: postgres:16 | ||
| container_name: cipherstash-prisma-example-pg | ||
| restart: unless-stopped | ||
| environment: | ||
| POSTGRES_USER: postgres | ||
| POSTGRES_PASSWORD: postgres | ||
| POSTGRES_DB: cipherstash_prisma_example | ||
| ports: | ||
| - "5544:5432" | ||
| volumes: | ||
| - cipherstash-prisma-example-pg-data:/var/lib/postgresql/data | ||
| healthcheck: | ||
| test: ["CMD-SHELL", "pg_isready -U postgres -d cipherstash_prisma_example"] | ||
| interval: 2s | ||
| timeout: 5s | ||
| retries: 30 | ||
|
|
||
| volumes: | ||
| cipherstash-prisma-example-pg-data: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fail fast when Postgres never becomes ready.
The readiness loop can timeout silently and continue to the test step, which delays and obscures the root failure. Add an explicit timeout check and exit non-zero here.
Proposed fix
- name: Start E2E Postgres container working-directory: examples/prisma run: | docker compose -f test/e2e/docker-compose.yml up -d # Wait for pg_isready before handing off to the suite — the # global-setup hook expects the container to already be up. + ready=0 for i in {1..60}; do if docker exec cipherstash-e2e-postgres pg_isready -U cipherstash -d cipherstash_e2e >/dev/null 2>&1; then echo "Postgres ready" + ready=1 break fi sleep 1 done + if [ "$ready" -ne 1 ]; then + echo "Postgres did not become ready within 60s" + docker logs cipherstash-e2e-postgres || true + exit 1 + fi📝 Committable suggestion
🤖 Prompt for AI Agents