diff --git a/.changeset/remove-eql-v2-scaffold-examples-meta.md b/.changeset/remove-eql-v2-scaffold-examples-meta.md new file mode 100644 index 000000000..38714d679 --- /dev/null +++ b/.changeset/remove-eql-v2-scaffold-examples-meta.md @@ -0,0 +1,22 @@ +--- +'stash': patch +'@cipherstash/stack': patch +--- + +De-suffix the v3 client name in generated code and shipped guidance. + +`stash init` scaffolded `import { EncryptionV3 } from '@cipherstash/stack/v3'` +into the client file it writes. `EncryptionV3` is a deprecated alias of +`Encryption`, so new projects were started on the deprecated name. The +scaffold now emits `Encryption`. + +`@cipherstash/stack/v3` now re-exports `Encryption` alongside the deprecated +`EncryptionV3` alias, so a v3 schema and its client come from one import +specifier — the deprecation notice already documented this import, but it did +not resolve. + +Corrects the bundled agent skills and package docs, which described +`encryptedSupabase` as the legacy EQL v2 wrapper. It is the EQL v3 factory; +the v2 wrapper was removed. Also drops the stale "DynamoDB still requires v2" +note from the `@cipherstash/stack` README — DynamoDB writes EQL v3 and reads +existing v2 items. diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1b96cbb2f..849feafbf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -152,6 +152,14 @@ jobs: - name: Typecheck (wizard) run: pnpm --filter @cipherstash/wizard run typecheck + # `examples/*` are standalone apps outside the `./packages/*` filter that + # root `build`/`test` use, so nothing in CI compiled them. `examples/basic` + # had been broken since the v2 removal deleted `encryptedType` and the v2 + # `encryptedSupabase` — on a fully green board. Gate it through turbo so + # `^build` builds stack/stack-drizzle/stash first. + - name: Typecheck (examples/basic — guards the v3 stack/stack-drizzle importers) + run: pnpm exec turbo run typecheck --filter @cipherstash/basic-example + - name: Lint — no hardcoded package-manager runners run: pnpm run lint:runners diff --git a/AGENTS.md b/AGENTS.md index 5fcc10440..943dbb981 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -78,7 +78,7 @@ If these variables are missing, tests that require live encryption will fail or - `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. **EQL v3 only**: per-domain constructors (`cipherstash.TextSearch()` / `text()` / `bigIntOrd()` / …) and `cipherstashFromStack` (the `./v3` and `./stack` entries). The EQL v2 surface was removed — the adapter's baseline migration installs the EQL v3 bundle only (works on Supabase as a non-superuser) - `packages/stack-drizzle`: Drizzle ORM integration (`@cipherstash/stack-drizzle`), depends on `@cipherstash/stack` — **EQL v3 only**, on the package root (the v2 surface was removed and the old `./v3` subpath collapsed into `.`). 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/stack-supabase`: Supabase integration (`@cipherstash/stack-supabase`), depends on `@cipherstash/stack` — **EQL v3 only**: `encryptedSupabase` is the v3 factory (`encryptedSupabaseV3` remains as a `@deprecated` alias). Split out of `@cipherstash/stack`. - `packages/nextjs`: Next.js helpers and Clerk integration (`./clerk` export) - `packages/utils`: Shared config (`utils/config`) and logger (`utils/logger`) - `packages/bench`: Performance / index-engagement benchmarks (private, not published) @@ -155,7 +155,7 @@ Three rules to remember when editing CI or pnpm config: - **Identity-aware encryption**: Authenticate the client as the end user with `OidcFederationStrategy` (`config.authStrategy`, 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**: `types.*` column factories, `extractEncryptionSchema`, `createEncryptionOperators` from `@cipherstash/stack-drizzle` - - **Supabase**: `encryptedSupabase` (v2) / `encryptedSupabaseV3` (v3) from `@cipherstash/stack-supabase` + - **Supabase**: `encryptedSupabase` from `@cipherstash/stack-supabase` (EQL v3; `encryptedSupabaseV3` is a `@deprecated` alias) - **DynamoDB**: `encryptedDynamoDB` from `@cipherstash/stack/dynamodb` ## Critical Gotchas (read before coding) diff --git a/README.md b/README.md index 0753d24e1..489c7f83a 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,11 @@ **Encryption** ```typescript -import { Encryption, encryptedTable, encryptedColumn } from "@cipherstash/stack"; +import { Encryption, encryptedTable, types } from "@cipherstash/stack/v3"; -// 1. Define your schema +// 1. Define your schema — the column type fixes its query capabilities const users = encryptedTable("users", { - email: encryptedColumn("email").equality().freeTextSearch(), + email: types.TextSearch("email"), // equality + order/range + free-text search }); // 2. Initialize the client @@ -67,7 +67,7 @@ bun add @cipherstash/stack ## Features - **[Searchable encryption](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption)**: query encrypted data with equality, free text search, range, and [JSONB queries](https://cipherstash.com/docs/stack/cipherstash/encryption/searchable-encryption#jsonb-queries-with-searchablejson). -- **[Type-safe schema](https://cipherstash.com/docs/stack/cipherstash/encryption/schema)**: define encrypted tables and columns with `encryptedTable` / `encryptedColumn` +- **[Type-safe schema](https://cipherstash.com/docs/stack/cipherstash/encryption/schema)**: define encrypted tables and columns with `encryptedTable` and the `types.*` concrete-domain factories - **[Model & bulk operations](https://cipherstash.com/docs/stack/cipherstash/encryption/encrypt-decrypt#model-operations)**: encrypt and decrypt entire objects or batches with `encryptModel` / `bulkEncryptModels`. - **[Identity-aware encryption](https://cipherstash.com/docs/stack/cipherstash/encryption/identity)**: authenticate as the end user with `OidcFederationStrategy` and bind the data key to their identity with `.withLockContext({ identityClaim })` for policy-based access control. diff --git a/SECURITY.md b/SECURITY.md index 47b725910..ba3996d97 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -14,8 +14,8 @@ This repository is the CipherStash Stack monorepo for JavaScript/TypeScript. It | `@cipherstash/nextjs` | Next.js 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/stack-drizzle` | Drizzle ORM integration for `@cipherstash/stack` (EQL v3) | +| `@cipherstash/stack-supabase` | Supabase integration for `@cipherstash/stack` (EQL 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. diff --git a/docs/reference/supabase-sdk.md b/docs/reference/supabase-sdk.md index 6561f255e..7294a8eb9 100644 --- a/docs/reference/supabase-sdk.md +++ b/docs/reference/supabase-sdk.md @@ -4,14 +4,18 @@ are transparently encrypted on mutations, `::jsonb`-cast on selects, encrypted in filter terms, and decrypted in results. -Two entry points, one query mechanism: +One entry point, EQL v3 only: | Entry point | Schema DSL | Column storage | |---|---|---| -| `encryptedSupabase` | `@cipherstash/stack/schema` (EQL v2) | `eql_v2_encrypted` composite | -| `encryptedSupabaseV3` | `@cipherstash/stack/eql/v3` (EQL v3) | native `public.eql_v3_*` domains | +| `encryptedSupabase` | `@cipherstash/stack/eql/v3` (EQL v3) | native `public.eql_v3_*` domains | -Both filter via **direct EQL operators over PostgREST**: the wrapper encrypts +`encryptedSupabaseV3` remains as a `@deprecated`, type-identical alias. The old +EQL v2 authoring wrapper — `encryptedSupabase({ encryptionClient, +supabaseClient })` — has been removed; the name now binds to the v3 factory +below. + +It filters via **direct EQL operators over PostgREST**: the wrapper encrypts the filter term and emits an ordinary `col term` filter, which resolves to the custom operator defined on the encrypted type (equality by HMAC, range by the ordering term — CLLW-OPE on `_ord` domains, block-ORE on `_ord_ore` — @@ -19,7 +23,7 @@ free-text by bloom-filter containment). ## Quick start (EQL v3) -`encryptedSupabaseV3` is an async factory that **introspects the database at +`encryptedSupabase` is an async factory that **introspects the database at connect time**: it detects EQL v3 columns by their Postgres domain, derives each column's encryption config from the domain, and builds the encryption client internally. Introspection needs a direct Postgres connection @@ -27,14 +31,14 @@ client internally. Introspection needs a direct Postgres connection run in a Worker or the browser. ```typescript -import { encryptedSupabaseV3 } from '@cipherstash/stack-supabase' +import { encryptedSupabase } from '@cipherstash/stack-supabase' // Introspects the database via options.databaseUrl or DATABASE_URL -const es = await encryptedSupabaseV3( +const es = await encryptedSupabase( process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, ) -// or wrap an existing client: await encryptedSupabaseV3(supabaseClient, options) +// or wrap an existing client: await encryptedSupabase(supabaseClient, options) await es.from('users').insert({ email: 'a@b.com', amount: 30 }) @@ -49,16 +53,14 @@ await es.from('users').select('id, amount').gte('amount', 10).lte('amount', 100) `from(tableName)` takes only the table name — no schema argument; column capabilities come from the introspected domains. -The builder surface is shared across v2 and v3: -`.select/.insert/.update/.upsert/.delete`, +The builder surface is `.select/.insert/.update/.upsert/.delete`, `.eq/.neq/.in/.is/.gt/.gte/.lt/.lte/.match/.or/.not/.filter`, transforms (`.order/.limit/.range/.single/.maybeSingle/.csv/.abortSignal/.throwOnError`), -plus `.withLockContext(lockContext)` and `.audit(config)` — with one fork: -free-text search. v2 exposes `.like/.ilike` (SQL wildcard matching); v3 -exposes `.matches()` (fuzzy bloom token search) on encrypted columns, keeps -`.contains()` for native (exact) containment on plaintext columns, and treats -`like`/`ilike` on an encrypted column as an approximate shim that delegates to -`.matches()` (see "v3 encoding details" below). +plus `.withLockContext(lockContext)` and `.audit(config)`. For free-text +search it exposes `.matches()` (fuzzy bloom token search) on encrypted +columns, keeps `.contains()` for native (exact) containment on plaintext +columns, and treats `like`/`ilike` on an encrypted column as an approximate +shim that delegates to `.matches()` (see "v3 encoding details" below). ### Typing (v3) @@ -68,14 +70,14 @@ tables against the database at construction: ```typescript import { encryptedTable, types } from '@cipherstash/stack/eql/v3' -import { encryptedSupabaseV3 } from '@cipherstash/stack-supabase' +import { encryptedSupabase } from '@cipherstash/stack-supabase' const users = encryptedTable('users', { email: types.TextSearch('email'), // public.eql_v3_text_search amount: types.IntegerOrd('amount'), // public.eql_v3_integer_ord }) -const es = await encryptedSupabaseV3( +const es = await encryptedSupabase( process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, { schemas: { users } }, diff --git a/examples/basic/encrypt.ts b/examples/basic/encrypt.ts index 140e22605..17f869fd5 100644 --- a/examples/basic/encrypt.ts +++ b/examples/basic/encrypt.ts @@ -1,9 +1,13 @@ import 'dotenv/config' -import { Encryption, encryptedColumn, encryptedTable } from '@cipherstash/stack' +import { Encryption, encryptedTable, types } from '@cipherstash/stack/v3' +// EQL v3: a column's query capabilities are fixed by the domain you pick — +// there are no chainable capability tuners. `types.Text` is storage-only +// (encrypt/decrypt, no queries), which is all this demo needs. Reach for +// `types.TextEq` / `types.TextSearch` when you need to query the column. export const users = encryptedTable('users', { - name: encryptedColumn('name'), + name: types.Text('name'), }) export const client = await Encryption({ diff --git a/examples/basic/index.ts b/examples/basic/index.ts index 63bc282a6..b00ad13fa 100644 --- a/examples/basic/index.ts +++ b/examples/basic/index.ts @@ -1,7 +1,6 @@ import 'dotenv/config' import readline from 'node:readline' import { client, users } from './encrypt' -import { createContact, getAllContacts } from './src/queries/contacts' const rl = readline.createInterface({ input: process.stdin, @@ -69,32 +68,6 @@ async function main() { console.log('Bulk encrypted data:', bulkEncryptResult.data) - // Demonstrate Supabase integration with CipherStash encryption - console.log('\n--- Supabase Integration Demo ---') - - try { - // Example: Create a new contact (would insert into encrypted Supabase table) - console.log('Creating encrypted contact...') - const newContact = { - name: 'John Doe', - email: 'john@example.com', - role: 'Developer', // This field will be encrypted using CipherStash - } - - // Note: This would fail in this basic example since we don't have actual Supabase setup - // but shows the pattern for encrypted Supabase usage - console.log('Contact data to encrypt:', newContact) - - // Example: Fetch contacts (would decrypt results from Supabase) - console.log('Fetching encrypted contacts...') - // const contacts = await getAllContacts() - // console.log('Decrypted contacts:', contacts.data) - } catch (error) { - console.log( - 'Supabase demo skipped (no actual Supabase connection in this basic example)', - ) - } - rl.close() } diff --git a/examples/basic/package.json b/examples/basic/package.json index 1c1de60b3..4a62550ad 100644 --- a/examples/basic/package.json +++ b/examples/basic/package.json @@ -4,7 +4,8 @@ "version": "1.2.14-rc.4", "type": "module", "scripts": { - "start": "tsx index.ts" + "start": "tsx index.ts", + "typecheck": "tsc --project tsconfig.json --noEmit" }, "keywords": [], "author": "", @@ -13,7 +14,7 @@ "dependencies": { "@cipherstash/stack": "workspace:*", "@cipherstash/stack-drizzle": "workspace:*", - "@cipherstash/stack-supabase": "workspace:*", + "drizzle-orm": "^0.45.2", "dotenv": "^17.4.2", "pg": "8.22.0" }, diff --git a/examples/basic/src/encryption/index.ts b/examples/basic/src/encryption/index.ts index 4f78be08e..fca33a2bf 100644 --- a/examples/basic/src/encryption/index.ts +++ b/examples/basic/src/encryption/index.ts @@ -1,20 +1,15 @@ -import { Encryption } from '@cipherstash/stack' -import { - encryptedType, - extractEncryptionSchema, -} from '@cipherstash/stack-drizzle' +import { Encryption } from '@cipherstash/stack/v3' +import { extractEncryptionSchema, types } from '@cipherstash/stack-drizzle' import { integer, pgTable, timestamp } from 'drizzle-orm/pg-core' +// EQL v3 encrypted columns are concrete Postgres domains built with the +// `types.*` factories. The domain fixes the query capabilities: `TextSearch` +// is equality + order/range + free-text, the v3 equivalent of what the old v2 +// builder spelled `.equality().freeTextSearch()`. export const usersTable = pgTable('users', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), - email: encryptedType('email', { - equality: true, - freeTextSearch: true, - }), - name: encryptedType('name', { - equality: true, - freeTextSearch: true, - }), + email: types.TextSearch('email'), + name: types.TextSearch('name'), createdAt: timestamp('created_at').defaultNow(), }) diff --git a/examples/basic/src/lib/supabase/encrypted.ts b/examples/basic/src/lib/supabase/encrypted.ts deleted file mode 100644 index 147930987..000000000 --- a/examples/basic/src/lib/supabase/encrypted.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { encryptedSupabase } from '@cipherstash/stack-supabase' -import { contactsTable, encryptionClient } from '../../encryption/index' -import { createServerClient } from './server' - -const supabase = await createServerClient() -export const eSupabase = encryptedSupabase({ - encryptionClient, - supabaseClient: supabase, -}) diff --git a/examples/basic/src/lib/supabase/server.ts b/examples/basic/src/lib/supabase/server.ts deleted file mode 100644 index 967949176..000000000 --- a/examples/basic/src/lib/supabase/server.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createClient } from '@supabase/supabase-js' - -export async function createServerClient() { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL! - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! - - return createClient(supabaseUrl, supabaseKey) -} diff --git a/examples/basic/src/queries/contacts.ts b/examples/basic/src/queries/contacts.ts deleted file mode 100644 index ad8979b39..000000000 --- a/examples/basic/src/queries/contacts.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { contactsTable } from '../encryption/index' -import { eSupabase } from '../lib/supabase/encrypted' - -// Example queries using encrypted Supabase wrapper - -export async function getAllContacts() { - const { data, error } = await eSupabase - .from('contacts', contactsTable) - .select('id, name, email, role') // explicit columns, no * - .order('created_at', { ascending: false }) - - return { data, error } -} - -export async function getContactsByRole(role: string) { - const { data, error } = await eSupabase - .from('contacts', contactsTable) - .select('id, name, email, role') - .eq('role', role) // auto-encrypted - - return { data, error } -} - -export async function searchContactsByName(searchTerm: string) { - const { data, error } = await eSupabase - .from('contacts', contactsTable) - .select('id, name, email, role') - .ilike('name', `%${searchTerm}%`) // auto-encrypted - - return { data, error } -} - -export async function createContact(contact: { - name: string - email: string - role: string -}) { - const { data, error } = await eSupabase - .from('contacts', contactsTable) - .insert(contact) // auto-encrypted - .select('id, name, email, role') - .single() - - return { data, error } -} - -export async function updateContact( - id: string, - updates: Partial<{ name: string; email: string; role: string }>, -) { - const { data, error } = await eSupabase - .from('contacts', contactsTable) - .update(updates) // auto-encrypted - .eq('id', id) - .select('id, name, email, role') - .single() - - return { data, error } -} - -export async function deleteContact(id: string) { - const { error } = await eSupabase - .from('contacts', contactsTable) - .delete() - .eq('id', id) - - return { error } -} diff --git a/packages/bench/README.md b/packages/bench/README.md index af5c3e38b..cd5409815 100644 --- a/packages/bench/README.md +++ b/packages/bench/README.md @@ -3,8 +3,9 @@ Performance / index-engagement benchmarks for stack integrations. This package validates that each integration emits SQL that engages the canonical -EQL functional indexes (`eql_v2.hmac_256`, `eql_v2.bloom_filter`, `eql_v2.ste_vec`) -on a Supabase-shaped install (no operator classes). It runs in two layers: +EQL functional indexes (`eql_v3.eq_term`, `eql_v3.match_term`, +`eql_v3.to_ste_vec_query`) on a Supabase-shaped install (no operator classes). +It runs in two layers: 1. **EXPLAIN-shape tests** (`__tests__/`) — vitest tests that assert on `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)` output. Pass/fail. Cheap. diff --git a/packages/cli/src/commands/init/__tests__/utils-codegen.test.ts b/packages/cli/src/commands/init/__tests__/utils-codegen.test.ts index 5aabc73d4..59067ee14 100644 --- a/packages/cli/src/commands/init/__tests__/utils-codegen.test.ts +++ b/packages/cli/src/commands/init/__tests__/utils-codegen.test.ts @@ -20,7 +20,12 @@ describe('generateClientFromSchemas', () => { expect(out).toContain("age: types.IntegerOrd('age'),") expect(out).toContain("verified: types.Boolean('verified'),") expect(out).toContain("from '@cipherstash/stack/v3'") - expect(out).toContain('EncryptionV3(') + // `Encryption` is the current name; `EncryptionV3` is a deprecated alias. + // Same reasoning as the drizzle negatives below — this is a template + // literal written into the user's repo as real source, so nothing but an + // assertion here catches a scaffold that teaches the deprecated name. + expect(out).toContain('Encryption(') + expect(out).not.toContain('EncryptionV3') }) it('emits the chosen v3 domain factory per column (drizzle)', () => { @@ -54,6 +59,7 @@ describe('generateClientFromSchemas', () => { const out = generateClientFromSchemas('supabase', schemas) expect(out).toContain("email: types.TextSearch('email'),") expect(out).toContain("from '@cipherstash/stack/v3'") + expect(out).not.toContain('EncryptionV3') expect(out).not.toContain('@cipherstash/stack-drizzle') }) diff --git a/packages/cli/src/commands/init/steps/install-eql.ts b/packages/cli/src/commands/init/steps/install-eql.ts index 9ee6bd880..f6c912466 100644 --- a/packages/cli/src/commands/init/steps/install-eql.ts +++ b/packages/cli/src/commands/init/steps/install-eql.ts @@ -108,8 +108,8 @@ export const installEqlStep: InitStep = { // at all. That pin made `stash init --drizzle` the one flow that provisions // a v2 database while every other integration (and a bare `stash eql // install`) gets v3, and it contradicted the stash-drizzle skill we install - // into the very same project — that skill documents the `/v3` surface - // (`types.*` domains, `EncryptionV3`) and would have the user's agent + // into the very same project — that skill documents the v3 surface + // (`types.*` domains, `Encryption`) and would have the user's agent // author v3 code against a v2 database. // // `stash eql migration --drizzle` (added in #691) closes that gap: v3 SQL, diff --git a/packages/cli/src/commands/init/utils.ts b/packages/cli/src/commands/init/utils.ts index 68526460e..fc0e6aec1 100644 --- a/packages/cli/src/commands/init/utils.ts +++ b/packages/cli/src/commands/init/utils.ts @@ -286,11 +286,11 @@ const ${schemaVarName} = extractEncryptionSchema(${varName})` return `import { pgTable, integer, timestamp } from 'drizzle-orm/pg-core' import { types, extractEncryptionSchema } from '@cipherstash/stack-drizzle' -import { EncryptionV3 } from '@cipherstash/stack/v3' +import { Encryption } from '@cipherstash/stack/v3' ${tableDefs.join('\n\n')} -export const encryptionClient = await EncryptionV3({ +export const encryptionClient = await Encryption({ schemas: [${schemaVarNames.join(', ')}], }) ` @@ -311,11 +311,11 @@ ${columnDefs.join('\n')} const tableVarNames = schemas.map((s) => `${toCamelCase(s.tableName)}Table`) - return `import { EncryptionV3, encryptedTable, types } from '@cipherstash/stack/v3' + return `import { Encryption, encryptedTable, types } from '@cipherstash/stack/v3' ${tableDefs.join('\n\n')} -export const encryptionClient = await EncryptionV3({ +export const encryptionClient = await Encryption({ schemas: [${tableVarNames.join(', ')}], }) ` @@ -366,7 +366,7 @@ export function generateClientFromSchemas( * schemas yet, and explicitly tells the agent that the user's existing * schema files remain authoritative. The agent's job during the handoff * is to declare encrypted columns directly in those files and update the - * `EncryptionV3({ schemas: [...] })` call below to reference them. + * `Encryption({ schemas: [...] })` call below to reference them. */ export function generatePlaceholderClient(integration: Integration): string { if (integration === 'drizzle') { @@ -381,7 +381,7 @@ const DRIZZLE_PLACEHOLDER = `/** * \`stash init\` wrote this file. It is intentionally NOT a real Drizzle * schema. Your existing schema files (typically under \`src/db/\`) remain * authoritative — your agent will edit those directly when you encrypt a - * column, then update the \`EncryptionV3({ schemas: [...] })\` call below + * column, then update the \`Encryption({ schemas: [...] })\` call below * to reference the encrypted tables you declared there. * * Until that happens, the encryption client is initialised with no @@ -419,19 +419,19 @@ const DRIZZLE_PLACEHOLDER = `/** * billing_address: types.TextEq('billing_address'), * }) * - * Once you have encrypted tables declared, harvest them and pass to EncryptionV3(): + * Once you have encrypted tables declared, harvest them and pass to Encryption(): * * import { extractEncryptionSchema } from '@cipherstash/stack-drizzle' - * import { EncryptionV3 } from '@cipherstash/stack/v3' + * import { Encryption } from '@cipherstash/stack/v3' * import { users, orders } from './db/schema' * - * export const encryptionClient = await EncryptionV3({ + * export const encryptionClient = await Encryption({ * schemas: [extractEncryptionSchema(users), extractEncryptionSchema(orders)], * }) */ -import { EncryptionV3 } from '@cipherstash/stack/v3' +import { Encryption } from '@cipherstash/stack/v3' -export const encryptionClient = await EncryptionV3({ schemas: [] }) +export const encryptionClient = await Encryption({ schemas: [] }) ` const GENERIC_PLACEHOLDER = `/** @@ -440,7 +440,7 @@ const GENERIC_PLACEHOLDER = `/** * \`stash init\` wrote this file. It is intentionally NOT a real schema * definition. Your existing schema files remain authoritative — your * agent will declare encrypted columns there and update the - * \`EncryptionV3({ schemas: [...] })\` call below to reference them. + * \`Encryption({ schemas: [...] })\` call below to reference them. * * Until that happens, the encryption client is initialised with no * schemas, and \`stash encrypt\` commands will surface a clear error @@ -474,16 +474,16 @@ const GENERIC_PLACEHOLDER = `/** * billing_address: types.TextEq('billing_address'), * }) * - * Once you have encrypted tables declared, pass them to EncryptionV3(): + * Once you have encrypted tables declared, pass them to Encryption(): * - * import { EncryptionV3 } from '@cipherstash/stack/v3' + * import { Encryption } from '@cipherstash/stack/v3' * import { users, orders } from './db/schema' * - * export const encryptionClient = await EncryptionV3({ + * export const encryptionClient = await Encryption({ * schemas: [users, orders], * }) */ -import { EncryptionV3 } from '@cipherstash/stack/v3' +import { Encryption } from '@cipherstash/stack/v3' -export const encryptionClient = await EncryptionV3({ schemas: [] }) +export const encryptionClient = await Encryption({ schemas: [] }) ` diff --git a/packages/migrate/README.md b/packages/migrate/README.md index ea1fb6549..30d03a961 100644 --- a/packages/migrate/README.md +++ b/packages/migrate/README.md @@ -43,7 +43,7 @@ Creates `cipherstash.cs_migrations` idempotently. Normally called by `stash eql Chunked, resumable, idempotent backfill of plaintext → encrypted. Per chunk, in a single transaction: select next page → encrypt via `client.bulkEncryptModels` → `UPDATE … FROM (VALUES …)` → `INSERT` a `backfill_checkpoint` event. Guards with `encrypted IS NULL` so re-runs never double-write. - `db`: a `pg.PoolClient` (the runner drives transactions on it). -- `encryptionClient`: your initialised `@cipherstash/stack` client (or anything that exposes `bulkEncryptModels(models, table)` returning `{ data } | { failure }`). For an EQL v3 column pass an `EncryptionV3` client (from `@cipherstash/stack/v3`) — it pins the v3 wire format; the engine itself is version-agnostic and writes whatever envelope the client produces. +- `encryptionClient`: your initialised `@cipherstash/stack` client (or anything that exposes `bulkEncryptModels(models, table)` returning `{ data } | { failure }`). For an EQL v3 column pass an `Encryption` client (from `@cipherstash/stack/v3`) — it pins the v3 wire format; the engine itself is version-agnostic and writes whatever envelope the client produces. - `tableSchema`: the `EncryptedTable` for the target table from your encryption client file. - `signal`: optional `AbortSignal`. If aborted between chunks, the backfill exits cleanly and leaves a resumable checkpoint. diff --git a/packages/stack-drizzle/README.md b/packages/stack-drizzle/README.md index c3596884d..db8a1b45a 100644 --- a/packages/stack-drizzle/README.md +++ b/packages/stack-drizzle/README.md @@ -17,7 +17,7 @@ object. Install the domains once with `stash eql install --eql-version 3`. ```ts import { pgTable, integer } from 'drizzle-orm/pg-core' -import { EncryptionV3 } from '@cipherstash/stack/v3' +import { Encryption } from '@cipherstash/stack/v3' import { types as encryptedTypes, extractEncryptionSchema, @@ -31,7 +31,7 @@ const users = pgTable('users', { }) const schema = extractEncryptionSchema(users) -const client = await EncryptionV3({ schemas: [schema] }) +const client = await Encryption({ schemas: [schema] }) const ops = createEncryptionOperators(client) // Insert — encrypt models first diff --git a/packages/stack/README.md b/packages/stack/README.md index 8ca24e116..942f65fc9 100644 --- a/packages/stack/README.md +++ b/packages/stack/README.md @@ -57,7 +57,7 @@ The wizard will authenticate you, walk you through choosing a database connectio Define a table with concrete EQL v3 column types, build the typed client, and encrypt: ```typescript -import { EncryptionV3 } from "@cipherstash/stack/v3" +import { Encryption } from "@cipherstash/stack/v3" import { encryptedTable, types } from "@cipherstash/stack/eql/v3" // Define a schema — the column type fixes its query capabilities @@ -66,7 +66,7 @@ const users = encryptedTable("users", { }) // Create a typed client -const client = await EncryptionV3({ schemas: [users] }) +const client = await Encryption({ schemas: [users] }) // Encrypt a value const encrypted = await client.encrypt("hello@example.com", { @@ -374,7 +374,7 @@ import { createEncryptionOperators, extractEncryptionSchema, } from "@cipherstash/stack-drizzle" -import { EncryptionV3 } from "@cipherstash/stack/v3" +import { Encryption } from "@cipherstash/stack/v3" // Capabilities come from the concrete type — no flags to configure. const users = pgTable("users", { @@ -390,7 +390,7 @@ Derive the v3 schema from the table, build the typed client, and create the oper ```ts const usersSchema = extractEncryptionSchema(users) -const client = await EncryptionV3({ schemas: [usersSchema] }) +const client = await Encryption({ schemas: [usersSchema] }) const ops = createEncryptionOperators(client) const db = drizzle({ client: sqlClient }) @@ -478,7 +478,7 @@ explicit strategies cover the other cases: ```typescript import { OidcFederationStrategy } from "@cipherstash/stack" -import { EncryptionV3 } from "@cipherstash/stack/v3" +import { Encryption } from "@cipherstash/stack/v3" // The callback is re-invoked on every (re-)federation and must return the // CURRENT third-party OIDC JWT. @@ -488,7 +488,7 @@ const strategy = OidcFederationStrategy.create( ) if (strategy.failure) throw new Error(strategy.failure.error.message) -const client = await EncryptionV3({ +const client = await Encryption({ schemas: [users], config: { authStrategy: strategy.data }, }) @@ -598,10 +598,10 @@ See the [Going to Production](https://cipherstash.com/docs/stack/deploy/going-to Pass config directly when initializing the client: ```typescript -import { EncryptionV3 } from "@cipherstash/stack/v3" +import { Encryption } from "@cipherstash/stack/v3" import { users } from "./schema" -const client = await EncryptionV3({ +const client = await Encryption({ schemas: [users], config: { workspaceCrn: "crn:ap-southeast-2.aws:your-workspace-id", @@ -618,7 +618,7 @@ const client = await EncryptionV3({ Isolate encryption keys per tenant using keysets: ```typescript -const client = await EncryptionV3({ +const client = await Encryption({ schemas: [users], config: { keyset: { id: "123e4567-e89b-12d3-a456-426614174000" }, @@ -626,7 +626,7 @@ const client = await EncryptionV3({ }) // or by name -const client2 = await EncryptionV3({ +const client2 = await Encryption({ schemas: [users], config: { keyset: { name: "Company A" }, @@ -683,10 +683,10 @@ if (result.failure) { ## API Reference -### `EncryptionV3(config)` - Initialize the typed client +### `Encryption(config)` - Initialize the typed client ```typescript -function EncryptionV3(config: { +function Encryption(config: { schemas: AnyV3Table[] config?: ClientConfig }): Promise @@ -749,9 +749,9 @@ type UserEncrypted = InferEncrypted | Import Path | Provides | |-------|-----| -| `@cipherstash/stack/v3` | `EncryptionV3` typed client factory, `typedClient`, plus re-exports of the EQL v3 authoring DSL | +| `@cipherstash/stack/v3` | `Encryption` typed client factory (`EncryptionV3` is a `@deprecated` alias), `typedClient`, plus re-exports of the EQL v3 authoring DSL | | `@cipherstash/stack/eql/v3` | EQL v3 authoring DSL: `encryptedTable`, the `types` namespace, `buildEncryptConfig`, inference types (`InferPlaintext`, `InferEncrypted`, ...) | -| `@cipherstash/stack` | `Encryption` function (legacy v2 entry point), auth strategies | +| `@cipherstash/stack` | `Encryption` client factory, auth strategies | | `@cipherstash/stack/schema` | Legacy v2 schema builders (see [Legacy: EQL v2](#legacy-eql-v2)) | | `@cipherstash/stack/identity` | `LockContext` class and identity types | | `@cipherstash/stack/client` | Client-safe exports (schema builders and types only - no native FFI) | @@ -763,31 +763,33 @@ depend on `@cipherstash/stack` (they are no longer subpaths of it): | Package | Provides | |-------|-----| | `@cipherstash/stack-drizzle` | EQL v3 Drizzle integration (package root, v3 only): `types` column factories, `createEncryptionOperators`, `extractEncryptionSchema`, `makeEqlV3Column`, `EncryptionOperatorError` | -| `@cipherstash/stack-supabase` | Supabase integration: `encryptedSupabaseV3` (and the legacy v2 `encryptedSupabase`) | +| `@cipherstash/stack-supabase` | Supabase integration (v3 only): `encryptedSupabase` (`encryptedSupabaseV3` is a `@deprecated` alias) | ## Legacy: EQL v2 Before the concrete-domain types above, encrypted columns were declared with chainable capability builders and stored in a single `eql_v2_encrypted` -composite column type. The scalar surface remains supported for existing -deployments, but new work should use EQL v3. Legacy searchable JSON cannot be -emitted by protect-ffi 0.30 and must migrate to v3 `types.Json`: +composite column type. **v2 is now a read path, not an authoring surface**: +`decrypt` / `decryptModel` still read stored v2 payloads, so existing +deployments keep working, but new work must use EQL v3. Legacy searchable JSON +cannot be emitted by protect-ffi 0.30 and must migrate to v3 `types.Json`: - **Client and schema**: `Encryption` from `@cipherstash/stack` with `encryptedColumn("email").equality().freeTextSearch().orderAndRange()` and - the builders from `@cipherstash/stack/schema`. v2 and v3 tables cannot be - mixed in one client. + the builders from `@cipherstash/stack/schema`. These are still exported but + `@deprecated` — do not author new schemas with them. v2 and v3 tables cannot + be mixed in one client. - **Query formatting**: v2 query terms can be rendered as strings with `returnType: 'composite-literal'` / `'escaped-composite-literal'` for string-based APIs. -- **Integrations**: the v2 Supabase surface is `encryptedSupabase`. There is no - v2 Drizzle surface any more — `@cipherstash/stack-drizzle` dropped - `encryptedType` and the v2 operators, so existing v2 Drizzle columns are - read-only: decrypt them through `@cipherstash/stack` and migrate to a v3 - domain. -- **DynamoDB still requires v2**: `encryptedDynamoDB` from - `@cipherstash/stack/dynamodb` works with the v2 API only — v3 support is - tracked in [#657](https://github.com/cipherstash/stack/issues/657). +- **Integrations are v3 only.** `encryptedSupabase` is the EQL v3 factory — + there is no v2 Supabase wrapper any more. Likewise + `@cipherstash/stack-drizzle` dropped `encryptedType` and the v2 operators. + Existing v2 columns reached through either adapter are read-only: decrypt + them through `@cipherstash/stack` and migrate to a v3 domain. +- **DynamoDB writes EQL v3 only.** `encryptedDynamoDB` from + `@cipherstash/stack/dynamodb` encrypts with `types.*` v3 tables; its decrypt + methods still accept a v2 table so previously stored items remain readable. Full v2 documentation lives at [cipherstash.com/docs](https://cipherstash.com/docs). diff --git a/packages/stack/src/encryption/v3.ts b/packages/stack/src/encryption/v3.ts index e36c90731..070bce612 100644 --- a/packages/stack/src/encryption/v3.ts +++ b/packages/stack/src/encryption/v3.ts @@ -336,3 +336,7 @@ export const EncryptionV3 = Encryption // helpers so `@cipherstash/stack/v3` provides everything needed to author and // use a schema. export * from '@/eql/v3' +// `Encryption` comes along for the same reason — it is the current name for +// what `EncryptionV3` aliases, so authoring a v3 schema and building its +// client should not need a second import specifier. +export { Encryption } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46f4b28e7..a2d261a15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,12 +117,12 @@ importers: '@cipherstash/stack-drizzle': specifier: workspace:* version: link:../../packages/stack-drizzle - '@cipherstash/stack-supabase': - specifier: workspace:* - version: link:../../packages/stack-supabase dotenv: specifier: ^17.4.2 version: 17.4.2 + drizzle-orm: + specifier: ^0.45.2 + version: 0.45.2(@types/pg@8.20.0)(gel@2.2.0)(mysql2@3.16.0)(pg@8.22.0)(postgres@3.4.9) pg: specifier: 8.22.0 version: 8.22.0 diff --git a/skills/stash-cli/SKILL.md b/skills/stash-cli/SKILL.md index 430b41952..7cf99621a 100644 --- a/skills/stash-cli/SKILL.md +++ b/skills/stash-cli/SKILL.md @@ -475,7 +475,7 @@ stash encrypt cutover --table users --column email In one transaction it renames `` → `_plaintext` and `_encrypted` → ``, advances the pending config to `encrypting`, activates it, and appends a `cut_over` event. With a Proxy URL configured (`--proxy-url` or `CIPHERSTASH_PROXY_URL`) it then calls `eql_v2.reload_config()` so Proxy picks up the new shape. -> **After cutover, `` holds ciphertext — the read path is not automatic.** Wire reads through the encryption client (`decryptModel(row, table)` for Drizzle, the `encryptedSupabaseV3` wrapper for Supabase — `encryptedSupabase` on the legacy v2 surface, otherwise `decrypt` / `bulkDecryptModels`) before returning values to callers. Skip this and your read paths hand raw EQL payloads to end users. The integration skill has the exact API. **CipherStash Proxy is the one exception** — it decrypts on the wire, so Proxy users need no application change. The cutover plan written by `stash plan` includes this read-path switch as an explicit step. +> **After cutover, `` holds ciphertext — the read path is not automatic.** Wire reads through the encryption client (`decryptModel(row, table)` for Drizzle, the `encryptedSupabase` wrapper for Supabase, otherwise `decrypt` / `bulkDecryptModels`) before returning values to callers. Skip this and your read paths hand raw EQL payloads to end users. The integration skill has the exact API. **CipherStash Proxy is the one exception** — it decrypts on the wire, so Proxy users need no application change. The cutover plan written by `stash plan` includes this read-path switch as an explicit step. > > **Known gap (v2).** The pending-configuration precondition is satisfied by `stash db push`. SDK-only users (who otherwise never need `db push`) must therefore run it once before `encrypt cutover`. This gap is specific to the legacy v2 path and is not being decoupled — EQL v3 columns sidestep it entirely (no configuration table, no cut-over; see above), which is how [issue #585](https://github.com/cipherstash/stack/issues/585) was resolved when v3 became the default. diff --git a/skills/stash-drizzle/SKILL.md b/skills/stash-drizzle/SKILL.md index 027deb6ab..057280571 100644 --- a/skills/stash-drizzle/SKILL.md +++ b/skills/stash-drizzle/SKILL.md @@ -1,6 +1,6 @@ --- name: stash-drizzle -description: Integrate CipherStash encryption with Drizzle ORM using @cipherstash/stack-drizzle (EQL v3). Covers the types.* encrypted column factories (concrete Postgres domains), auto-encrypting query operators (eq, ne, gt/gte/lt/lte, between, inArray, matches, contains, JSON selector, asc/desc), schema extraction, the EncryptionV3 typed client, database setup with stash eql install, and migrating existing plaintext columns to encrypted. Use when adding encryption to a Drizzle ORM project, defining encrypted Drizzle schemas, or querying encrypted columns with Drizzle. +description: Integrate CipherStash encryption with Drizzle ORM using @cipherstash/stack-drizzle (EQL v3). Covers the types.* encrypted column factories (concrete Postgres domains), auto-encrypting query operators (eq, ne, gt/gte/lt/lte, between, inArray, matches, contains, JSON selector, asc/desc), schema extraction, the Encryption typed client, database setup with stash eql install, and migrating existing plaintext columns to encrypted. Use when adding encryption to a Drizzle ORM project, defining encrypted Drizzle schemas, or querying encrypted columns with Drizzle. --- # CipherStash Stack - Drizzle ORM Integration @@ -125,7 +125,7 @@ Value families: `Integer`/`Smallint`/`Numeric`/`Real`/`Double` (`number`), `Bigi ```typescript import { extractEncryptionSchema, createEncryptionOperators } from "@cipherstash/stack-drizzle" -import { EncryptionV3 } from "@cipherstash/stack/v3" +import { Encryption } from "@cipherstash/stack/v3" // Convert the Drizzle table definition to a CipherStash v3 schema const usersSchema = extractEncryptionSchema(usersTable) @@ -134,12 +134,12 @@ const usersSchema = extractEncryptionSchema(usersTable) ### 2. Initialize the Encryption Client ```typescript -const encryptionClient = await EncryptionV3({ +const encryptionClient = await Encryption({ schemas: [usersSchema], }) ``` -`EncryptionV3` returns a strongly-typed client: plaintext types are pinned to each column's domain, and query methods only accept queryable columns. +`Encryption` returns a strongly-typed client: plaintext types are pinned to each column's domain, and query methods only accept queryable columns. ### 3. Create Query Operators @@ -464,13 +464,13 @@ Update the encryption client to harvest the encrypted columns from the table: ```typescript // src/encryption/index.ts -import { EncryptionV3 } from '@cipherstash/stack/v3' +import { Encryption } from '@cipherstash/stack/v3' import { extractEncryptionSchema } from '@cipherstash/stack-drizzle' import { users } from '../db/schema' const usersEncryptionSchema = extractEncryptionSchema(users) -export const encryptionClient = await EncryptionV3({ schemas: [usersEncryptionSchema] }) +export const encryptionClient = await Encryption({ schemas: [usersEncryptionSchema] }) ``` Generate the migration with `drizzle-kit generate`. The generated SQL should be a single `ALTER TABLE ... ADD COLUMN email_encrypted public.eql_v3_text_search;`. Apply with `drizzle-kit migrate`. (This requires the EQL v3 SQL to be installed first — see Database Setup.) diff --git a/skills/stash-encryption/SKILL.md b/skills/stash-encryption/SKILL.md index 749c38f22..31f939907 100644 --- a/skills/stash-encryption/SKILL.md +++ b/skills/stash-encryption/SKILL.md @@ -189,7 +189,7 @@ The SDK never logs plaintext data. | `@cipherstash/stack/errors` | `EncryptionErrorTypes`, `StackError`, error subtypes, `getErrorMessage` | | `@cipherstash/stack/types` | All TypeScript types | | `@cipherstash/stack-drizzle` | Drizzle ORM integration for EQL v3 schemas — the package root, EQL v3 only (see the `stash-drizzle` skill) | -| `@cipherstash/stack-supabase` | `encryptedSupabaseV3` wrapper for Supabase (see the `stash-supabase` skill) | +| `@cipherstash/stack-supabase` | `encryptedSupabase` wrapper for Supabase — EQL v3 only (see the `stash-supabase` skill) | | `@cipherstash/stack/wasm-inline` | The **edge** entry — Deno, Bun, Cloudflare Workers, Supabase Edge Functions. Its own `Encryption` factory plus the v3 authoring surface, `EncryptionErrorTypes`, and the WASM build of protect-ffi inlined into the bundle. No native binding, so no bundler externalisation needed. | | `@cipherstash/stack/dynamodb` | `encryptedDynamoDB` — encrypt/write is **EQL v3 only** (`types.*`); decrypt still reads existing v2 items. See the `stash-dynamodb` skill | | `@cipherstash/stack/schema`, `@cipherstash/stack/client`, `@cipherstash/stack/encryption` | Legacy v2 schema builders and client surface — see "Legacy: EQL v2" below | @@ -1006,7 +1006,7 @@ Useful when the backfill needs to run in a worker, on a schedule, or alongside a | Target | Package / entry point | Skill | |---|---|---| | Drizzle ORM | `@cipherstash/stack-drizzle` — v3 column factories (each `types.*` factory emits its domain as the column's SQL type for `drizzle-kit generate`), schema extraction, auto-encrypting operators (`ops.eq`, `ops.matches`, `ops.contains`, `ops.selector`, `ops.asc`, ...) | `stash-drizzle` | -| Supabase | `encryptedSupabaseV3` from `@cipherstash/stack-supabase` — schema-aware query builder (`eq`, `matches`, `contains`, `selectorEq`/`selectorNe`, ...) that works through PostgREST, including as `anon` | `stash-supabase` | +| Supabase | `encryptedSupabase` from `@cipherstash/stack-supabase` — schema-aware query builder (`eq`, `matches`, `contains`, `selectorEq`/`selectorNe`, ...) that works through PostgREST, including as `anon` | `stash-supabase` | | Prisma | `@cipherstash/prisma-next` — searchable field-level encryption for Postgres | — | | DynamoDB | `encryptedDynamoDB` from `@cipherstash/stack/dynamodb` — encrypt is **EQL v3 only**; decrypt still reads existing v2 items | `stash-dynamodb` | @@ -1054,6 +1054,6 @@ const users = encryptedTable("users", { const client = await Encryption({ schemas: [users] }) ``` -The scalar v2 API — `Encryption` plus the `@cipherstash/stack/schema` builders, the v2 Supabase integration (`encryptedSupabase`), and `stash eql install --eql-version 2` — still exists for existing deployments, with two carve-outs. **Drizzle has no v2 surface at all**: `@cipherstash/stack-drizzle` dropped `encryptedType` and the `like`/`ilike` operators, so a v2 Drizzle column is read-only — decrypt it through `@cipherstash/stack` rather than through the adapter. **Legacy v2 `searchableJson()`** cannot be emitted by protect-ffi 0.30 (the selector envelope was removed), so migrate those columns to v3 `types.Json`. Full v2 documentation lives at [cipherstash.com/docs](https://cipherstash.com/docs). Remember: v2 and v3 tables cannot be mixed in one client. (If you are migrating code from the old `@cipherstash/protect` package, its `protect`/`csTable`/`csColumn` names map onto this v2 surface.) +**v2 is a read path now, not an authoring surface.** `decrypt` / `decryptModel` still read stored v2 payloads, and `stash eql install --eql-version 2` still installs the v2 SQL, so existing deployments keep working. What is gone: the **Supabase** and **Drizzle** adapters are EQL v3 only — `encryptedSupabase` is the v3 factory (there is no v2 form), and `@cipherstash/stack-drizzle` dropped `encryptedType` and the `like`/`ilike` operators. A v2 column reached through either adapter is read-only; decrypt it through `@cipherstash/stack` instead. The `@cipherstash/stack/schema` builders (`encryptedColumn(...).equality()`, …) remain exported but are `@deprecated` — do not author new schemas with them. **Legacy v2 `searchableJson()`** cannot be emitted by protect-ffi 0.30 (the selector envelope was removed), so migrate those columns to v3 `types.Json`. Full v2 documentation lives at [cipherstash.com/docs](https://cipherstash.com/docs). Remember: v2 and v3 tables cannot be mixed in one client. (If you are migrating code from the old `@cipherstash/protect` package, its `protect`/`csTable`/`csColumn` names map onto this v2 surface.) > **DynamoDB.** The DynamoDB integration (`encryptedDynamoDB` from `@cipherstash/stack/dynamodb`) now **encrypts EQL v3 only** — author tables with `types.*` from `@cipherstash/stack/eql/v3`. Its decrypt methods still accept a v2 table so previously stored v2 items remain readable. See the `stash-dynamodb` skill. diff --git a/skills/stash-prisma-next/SKILL.md b/skills/stash-prisma-next/SKILL.md index a19bae603..632af947c 100644 --- a/skills/stash-prisma-next/SKILL.md +++ b/skills/stash-prisma-next/SKILL.md @@ -117,7 +117,7 @@ export const db = postgres({ `cipherstashFromStack({ contractJson })` derives the v3 encryption schemas from the contract (one `public.eql_v3_*` domain per column), constructs the -`@cipherstash/stack` `EncryptionV3` client from your `CS_*` env vars or local +`@cipherstash/stack` `Encryption` client from your `CS_*` env vars or local profile, builds the SDK adapter, and returns ready-to-spread `extensions` and `middleware`. diff --git a/turbo.json b/turbo.json index 4ef8a864b..6ed7b4a76 100644 --- a/turbo.json +++ b/turbo.json @@ -19,6 +19,10 @@ "inputs": ["$TURBO_DEFAULT$", ".env*"], "cache": false }, + "typecheck": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$"] + }, "test:e2e": { "dependsOn": ["^build", "build"], "inputs": ["$TURBO_DEFAULT$", ".env*"],