Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
bd58f13
feat(stack): add @cipherstash/stack/adapter-kit internal support surface
coderdan Jul 12, 2026
5411a13
docs(plan): adapter package split execution plan + adapter-kit change…
coderdan Jul 12, 2026
c10e7a8
fix(lint): bump biome.json $schema to 2.5.2; sort adapter-kit exports
coderdan Jul 13, 2026
8e5e2b9
Merge remote-tracking branch 'origin/feat/eql-v3-text-search-schema' …
coderdan Jul 13, 2026
ed91460
refactor(supabase): extract src into @cipherstash/stack-supabase (#627)
coderdan Jul 13, 2026
ebbb90d
test(supabase): move Supabase unit tests into stack-supabase (#627)
coderdan Jul 13, 2026
c434cc1
refactor(drizzle): extract into @cipherstash/stack-drizzle (v2 + v3) …
coderdan Jul 13, 2026
bd91e93
fix(test-kit): keep needle-for out of the encryption/helpers typechec…
coderdan Jul 13, 2026
41d9210
test(adapters): relocate integration suites + shared harness (#627)
coderdan Jul 13, 2026
ca9d576
fix(stack-drizzle): import OidcFederationStrategy from @cipherstash/s…
coderdan Jul 13, 2026
46717a6
fix: repoint clerk-provider unit test + order the integration-clerk a…
coderdan Jul 13, 2026
1fc94da
style: sort clerk-provider.test imports (biome)
coderdan Jul 13, 2026
bff9a2e
ci(fta): extend the complexity gate to the split adapter packages (#627)
coderdan Jul 13, 2026
31ca318
docs(627): update skills, docs, meta-files, and changesets for the ad…
coderdan Jul 13, 2026
5e5adc9
fix(627): update CLI scaffolding, benches, and example for the adapte…
coderdan Jul 13, 2026
67e90b0
fix(627): address code-review findings on the adapter split
coderdan Jul 13, 2026
83d5c8a
docs(627): address review feedback on adapter package metadata + README
coderdan Jul 13, 2026
053041c
docs(627): alias v3 types as encryptedTypes in the drizzle README exa…
coderdan Jul 13, 2026
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
28 changes: 28 additions & 0 deletions .changeset/adapter-package-split.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
'@cipherstash/stack': minor
'@cipherstash/stack-drizzle': minor
'@cipherstash/stack-supabase': minor
---

Split the Drizzle and Supabase integrations into their own packages.

The adapters now ship as first-party packages that depend on `@cipherstash/stack`,
following the `@cipherstash/prisma-next` precedent:

- **`@cipherstash/stack-drizzle`** — Drizzle ORM integration. EQL v2 on the package
root (`@cipherstash/stack-drizzle`: `encryptedType`, `extractEncryptionSchema`,
`createEncryptionOperators`) and EQL v3 on `@cipherstash/stack-drizzle/v3`
(`types` factories, `createEncryptionOperatorsV3`, `extractEncryptionSchemaV3`, …).
- **`@cipherstash/stack-supabase`** — Supabase integration: `encryptedSupabase` (v2)
and `encryptedSupabaseV3` (v3, connect-time introspection).

**Breaking (`@cipherstash/stack`):** the `./drizzle`, `./supabase`, and
`./eql/v3/drizzle` subpath exports are removed. Migrate imports:

- `@cipherstash/stack/drizzle` → `@cipherstash/stack-drizzle`
- `@cipherstash/stack/eql/v3/drizzle` → `@cipherstash/stack-drizzle/v3`
- `@cipherstash/stack/supabase` → `@cipherstash/stack-supabase`

Add the relevant package to your dependencies alongside `@cipherstash/stack`. A new
`@cipherstash/stack/adapter-kit` subpath exposes the narrow core internals the
first-party adapters consume; it is the core↔adapter seam, not general-purpose API.
11 changes: 11 additions & 0 deletions .changeset/adapter-split-skills.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'stash': patch
---

Update the bundled `stash-drizzle`, `stash-supabase`, and `stash-encryption` agent
skills (and the stack README / Supabase reference doc) for the adapter package
split: the Drizzle and Supabase integrations import from `@cipherstash/stack-drizzle`
(+ `/v3`) and `@cipherstash/stack-supabase` respectively, installed alongside
`@cipherstash/stack`, rather than from `@cipherstash/stack/{drizzle,supabase,eql/v3/drizzle}`
subpaths. Skills ship inside the `stash` tarball, so the stale import paths would
otherwise become wrong guidance in a user's project.
12 changes: 12 additions & 0 deletions .changeset/stack-adapter-kit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@cipherstash/stack': minor
---

Add the `@cipherstash/stack/adapter-kit` subpath — a narrow support surface for
the first-party adapter packages (`@cipherstash/stack-supabase`,
`@cipherstash/stack-drizzle`) being split out of this package (#627). It
re-exports exactly the core internals the adapters consume (the logger,
`AuditConfig`, the v3 column model + `DATE_LIKE_CASTS`, the domain registry, the
match-index guard, and the model→composite helpers) so those imports resolve
across the package boundary without leaking six internal module paths. This is the
core↔adapter seam, not general-purpose public API.
25 changes: 21 additions & 4 deletions .github/workflows/fta-v3.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: "FTA Complexity (EQL v3)"
name: "FTA Complexity"

# Blocking complexity gate for the EQL v3 text-search schema. Runs the Fast
# TypeScript Analyzer (fta-cli) against the v3 source directory only and fails
# Blocking complexity gate for the EQL v3 core (stack) and the split adapter
# packages (stack-drizzle, stack-supabase). Runs the Fast TypeScript Analyzer
# (fta-cli) against each package's source and fails
# the check when any file exceeds the score cap (`--score-cap` in the
# `analyze:complexity` script). FTA is pure static source analysis, so this job
# needs no build step, database, or credentials.
Expand All @@ -12,6 +13,8 @@ on:
- 'main'
paths:
- 'packages/stack/src/eql/v3/**'
- 'packages/stack-drizzle/**'
- 'packages/stack-supabase/**'
# Shared match-index defaults live outside src/eql/v3 but shape every
# emitted v3 match block (load-bearing `k`/`m` ciphertext params), so edits
# here must trigger the v3 gate too.
Expand All @@ -23,6 +26,8 @@ on:
- "**"
paths:
- 'packages/stack/src/eql/v3/**'
- 'packages/stack-drizzle/**'
- 'packages/stack-supabase/**'
- 'packages/stack/src/schema/match-defaults.ts'
- 'packages/stack/package.json'
- '.github/workflows/fta-v3.yml'
Expand Down Expand Up @@ -61,5 +66,17 @@ jobs:

# Non-zero exit (score above the cap) fails the check — this is the
# blocking gate. No `continue-on-error`.
- name: Analyze v3 complexity
# One step per package so a failure names the offending package. Each caps
# at its current worst file (a ratchet, not an aspiration): stack src/eql/v3
# at 69, and the split adapter packages at their monolith maxima — drizzle
# 89 (operators.ts), supabase 91 (query-builder.ts). Lower a cap whenever a
# file is refactored below the next threshold; the v2 query-builder/operators
# monoliths are the debt to whittle down toward stack's 69.
- name: Analyze stack (eql/v3) complexity
run: pnpm --filter @cipherstash/stack run analyze:complexity

- name: Analyze stack-drizzle complexity
run: pnpm --filter @cipherstash/stack-drizzle run analyze:complexity

- name: Analyze stack-supabase complexity
run: pnpm --filter @cipherstash/stack-supabase run analyze:complexity
15 changes: 14 additions & 1 deletion .github/workflows/integration-drizzle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ on:
branches: [main]
paths:
- 'packages/stack/src/eql/v3/**'
- 'packages/stack-drizzle/**'
# Source layers the adapter's encoding/round-trip rests on: a break here
# (not just under src/eql/v3) can produce wrong rows, so trigger the live
# suite that would catch it.
Expand All @@ -37,6 +38,7 @@ on:
# Repeated verbatim: GitHub Actions does not support YAML anchors/aliases.
paths:
- 'packages/stack/src/eql/v3/**'
- 'packages/stack-drizzle/**'
# Source layers the adapter's encoding/round-trip rests on: a break here
# (not just under src/eql/v3) can produce wrong rows, so trigger the live
# suite that would catch it.
Expand Down Expand Up @@ -108,9 +110,13 @@ jobs:
# (they need CLERK_MACHINE_TOKEN + a workspace with the Clerk issuer
# registered); the Supabase adapter suites are not run here — they have
# their own job.
# The Drizzle adapter suites (incl. the Clerk-federated lock-context one)
# now live in @cipherstash/stack-drizzle (run below via its own
# test:integration). This glob scopes what still lives in @cipherstash/stack:
# the adapter-agnostic `shared/` core suites and the model-path
# `identity/matrix-identity` suite.
CS_IT_SUITE: >-
integration/shared/**/*.integration.test.ts,
integration/drizzle-v3/**/*.integration.test.ts,
integration/identity/**/*.integration.test.ts

steps:
Expand All @@ -135,6 +141,13 @@ jobs:
# `stash eql install --eql-version 3`, so an installer regression fails
# here rather than hiding behind a test-only SQL apply.
- name: Drizzle v3 integration suites
run: pnpm --filter @cipherstash/stack-drizzle run test:integration

# A second vitest invocation (stack's shared/ + identity suites live in a
# different package now). Its globalSetup calls the same EQL v3 install, but
# `isInstalled` short-circuits against the DB the first invocation already
# provisioned — a fast no-op check, not a second schema apply.
- name: Shared core + identity integration suites
run: pnpm --filter @cipherstash/stack run test:integration

- name: Stop ${{ matrix.db }}
Expand Down
18 changes: 14 additions & 4 deletions .github/workflows/integration-supabase.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
push:
branches: [main]
paths:
- 'packages/stack/src/supabase/**'
- 'packages/stack-supabase/**'
- 'packages/stack/src/eql/v3/**'
# Source layers the adapter's encoding/round-trip rests on: a break here
# (not just under src/supabase) can produce wrong wire output or rows, so
Expand All @@ -30,7 +30,7 @@ on:
pull_request:
branches: ['**']
paths:
- 'packages/stack/src/supabase/**'
- 'packages/stack-supabase/**'
- 'packages/stack/src/eql/v3/**'
# Source layers the adapter's encoding/round-trip rests on: a break here
# (not just under src/supabase) can produce wrong wire output or rows, so
Expand Down Expand Up @@ -84,9 +84,12 @@ jobs:
#
# `integration/identity/` runs on the Drizzle job (it needs Postgres +
# Drizzle, not PostgREST), so it is intentionally not listed here.
# The Supabase adapter suites now live in @cipherstash/stack-supabase (run
# below via its own test:integration). This glob scopes the adapter-agnostic
# `shared/` core suites that still live in @cipherstash/stack and run against
# this job's Postgres too.
CS_IT_SUITE: >-
integration/shared/**/*.integration.test.ts,
integration/supabase/**/*.integration.test.ts
integration/shared/**/*.integration.test.ts

steps:
- uses: actions/checkout@v6
Expand All @@ -110,6 +113,13 @@ jobs:
# `stash eql install --eql-version 3 --supabase --direct`, so an installer
# regression fails here rather than hiding behind a test-only SQL apply.
- name: Supabase v3 integration suites
run: pnpm --filter @cipherstash/stack-supabase run test:integration

# A second vitest invocation (stack's shared/ suites live in a different
# package now). Its globalSetup calls the same EQL v3 install, but
# `isInstalled` short-circuits against the DB the first invocation already
# provisioned — so this is a fast no-op check, not a second schema apply.
- name: Shared core integration suites (against Supabase Postgres)
run: pnpm --filter @cipherstash/stack run test:integration

- name: Stop ${{ matrix.db }}
Expand Down
12 changes: 7 additions & 5 deletions .github/workflows/tests-bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ jobs:
run: pnpm recursive install --frozen-lockfile

# `@cipherstash/stack` ships dist/-based `exports`; bench imports
# from it (and `@cipherstash/stack/drizzle`) so the package needs
# to be built before vitest can resolve it. Going through turbo
# also picks up `^build` deps if any are added later.
- name: Build @cipherstash/stack
run: pnpm exec turbo run build --filter @cipherstash/stack
# from `@cipherstash/stack` and `@cipherstash/stack-drizzle` (the Drizzle
# adapter split out into its own package), so both must be built before
# vitest can resolve them — the drizzle bench/test files import
# `@cipherstash/stack-drizzle`. Going through turbo also picks up `^build`
# deps (stack-drizzle depends on stack).
- name: Build stack + adapter packages
run: pnpm exec turbo run build --filter @cipherstash/stack --filter @cipherstash/stack-drizzle

# Starts the pinned postgres-eql container (PostgreSQL 17 + EQL
# pre-installed) via local/docker-compose.yml; waits for healthcheck.
Expand Down
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ If these variables are missing, tests that require live encryption will fail or
## Repository Layout

- `packages/stack`: Main package (`@cipherstash/stack`) containing the encryption client and all integrations
- Subpath exports: `@cipherstash/stack`, `@cipherstash/stack/client`, `@cipherstash/stack/identity`, `@cipherstash/stack/schema`, `@cipherstash/stack/eql/v3`, `@cipherstash/stack/eql/v3/drizzle`, `@cipherstash/stack/v3`, `@cipherstash/stack/types`, `@cipherstash/stack/drizzle`, `@cipherstash/stack/dynamodb`, `@cipherstash/stack/supabase`, `@cipherstash/stack/encryption`, `@cipherstash/stack/errors`, `@cipherstash/stack/wasm-inline`
- Subpath exports: `@cipherstash/stack`, `@cipherstash/stack/client`, `@cipherstash/stack/identity`, `@cipherstash/stack/schema`, `@cipherstash/stack/eql/v3`, `@cipherstash/stack/v3`, `@cipherstash/stack/types`, `@cipherstash/stack/dynamodb`, `@cipherstash/stack/encryption`, `@cipherstash/stack/errors`, `@cipherstash/stack/adapter-kit`, `@cipherstash/stack/wasm-inline` (the Drizzle and Supabase integrations moved to their own packages — see below)
- `packages/protect`: Core encryption library (internal, re-exported via `@cipherstash/stack`)
- `src/index.ts`: Public API (`Encryption`, exports)
- `src/ffi/index.ts`: `EncryptionClient` implementation, bridges to `@cipherstash/protect-ffi`
Expand All @@ -82,6 +82,8 @@ If these variables are missing, tests that require live encryption will fail or
- `packages/wizard`: AI-powered encryption setup (`@cipherstash/wizard`)
- `packages/migrate`: Plaintext-to-encrypted column migration (`@cipherstash/migrate`) — resumable backfill, per-column state
- `packages/prisma-next`: Prisma Next integration (`@cipherstash/prisma-next`) — searchable field-level encryption for Postgres
- `packages/stack-drizzle`: Drizzle ORM integration (`@cipherstash/stack-drizzle`), depends on `@cipherstash/stack` — EQL v2 (`.`) and EQL v3 (`./v3`). Split out of `@cipherstash/stack`.
- `packages/stack-supabase`: Supabase integration (`@cipherstash/stack-supabase`), depends on `@cipherstash/stack` — `encryptedSupabase` (v2) and `encryptedSupabaseV3` (v3). Split out of `@cipherstash/stack`.
- `packages/schema`: Schema builder utilities and types (`encryptedTable`, `encryptedColumn`, `encryptedField`)
- `packages/drizzle`: Drizzle ORM integration (`encryptedType`, `extractEncryptionSchema`, `createEncryptionOperators`)
- `packages/nextjs`: Next.js helpers and Clerk integration (`./clerk` export)
Expand Down Expand Up @@ -157,8 +159,8 @@ Three rules to remember when editing CI or pnpm config:
- `encryptQuery(terms[])` for batch query encryption
- **Identity-aware encryption**: Authenticate the client as the end user with `OidcFederationStrategy` (`config.strategy`, re-exported from `@cipherstash/stack`), then chain `.withLockContext({ identityClaim })` on operations to bind the data key to a claim. The same claim must be used for encrypt and decrypt. (`LockContext.identify()` from `@cipherstash/stack/identity` is deprecated — the strategy now handles token acquisition; `.withLockContext()` also accepts a `LockContext`.)
- **Integrations**:
- **Drizzle ORM**: `encryptedType`, `extractEncryptionSchema`, `createEncryptionOperators` from `@cipherstash/stack/drizzle`
- **Supabase**: `encryptedSupabase` from `@cipherstash/stack/supabase`
- **Drizzle ORM**: `encryptedType`, `extractEncryptionSchema`, `createEncryptionOperators` from `@cipherstash/stack-drizzle` (EQL v3 factories from `@cipherstash/stack-drizzle/v3`)
- **Supabase**: `encryptedSupabase` (v2) / `encryptedSupabaseV3` (v3) from `@cipherstash/stack-supabase`
- **DynamoDB**: `encryptedDynamoDB` from `@cipherstash/stack/dynamodb`

## Critical Gotchas (read before coding)
Expand Down
4 changes: 3 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This repository is the CipherStash Stack monorepo for JavaScript/TypeScript. It

| Package | Description |
| ------- | ----------- |
| `@cipherstash/stack` | Main package: encryption client and all integrations |
| `@cipherstash/stack` | Main package: encryption client, schema, EQL v3 typed client |
| `stash` | CipherStash CLI |
| `@cipherstash/protect` | Core encryption library (re-exported via `@cipherstash/stack`) |
| `@cipherstash/schema` | Schema builder utilities |
Expand All @@ -18,6 +18,8 @@ This repository is the CipherStash Stack monorepo for JavaScript/TypeScript. It
| `@cipherstash/protect-dynamodb` | DynamoDB helpers |
| `@cipherstash/migrate` | Plaintext-to-encrypted column migration tooling |
| `@cipherstash/prisma-next` | Prisma Next integration (searchable field-level encryption for Postgres) |
| `@cipherstash/stack-drizzle` | Drizzle ORM integration for `@cipherstash/stack` (EQL v2 + v3) |
| `@cipherstash/stack-supabase` | Supabase integration for `@cipherstash/stack` (EQL v2 + v3) |
| `@cipherstash/wizard` | AI-powered encryption setup |

**Security fixes are released for the latest release line of each package.** Security reports are welcome for any version, but fixes land in the latest release — if you are running an older major version, plan to upgrade to receive them.
Expand Down
Loading
Loading