Skip to content
Closed
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
1 change: 1 addition & 0 deletions .changeset/ste-vec-query-support.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
"@cipherstash/protect": minor
"@cipherstash/schema": minor
"@cipherstash/stack": minor
---

Add encrypted JSONB query support with `searchableJson()` (recommended).
Expand Down
18 changes: 9 additions & 9 deletions .cursor/commands/create-example-app.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Cursor Super-Prompt: Protect.js Example Apps (Framework/ORM-Agnostic, No-Cheese)
# Cursor Super-Prompt: Stash Encryption Example Apps (Framework/ORM-Agnostic, No-Cheese)

ROLE
You are a senior systems engineer focused on developer experience and a core maintainer of `@cipherstash/protect`. Your mission: create a polished set of runnable **Protect.js example apps** across multiple stacks. Each example must be minimal, factual, and runnable in minutes.
You are a senior systems engineer focused on developer experience and a core maintainer of `@cipherstash/stack`. Your mission: create a polished set of runnable **Stash Encryption example apps** across multiple stacks. Each example must be minimal, factual, and runnable in minutes.

GROUNDING & SOURCES (use @ref; do not guess)
- Protect.js APIs: the Protect.js main README is the single source of truth. If not accessible, STOP and ask for the exact snippet/repo path. Do not invent APIs.
- Stash Encryption APIs: the Stash Encryption main README is the single source of truth. If not accessible, STOP and ask for the exact snippet/repo path. Do not invent APIs.
- ORM/DB docs (pick per stack):
@ref https://www.prisma.io/docs
@ref https://typeorm.io
Expand All @@ -26,7 +26,7 @@ STACK MATRIX (generate now)
Optional (time-permitting): nextjs-prisma (App Router), fastify-knex.

NO-CHEESE RULES (hard requirements)
Goal: smallest possible working example that clearly demonstrates Protect.js. Clarity > patterns > abstractions.
Goal: smallest possible working example that clearly demonstrates Stash Encryption. Clarity > patterns > abstractions.
DON'TS
- No Singletons/Factories/Service-Locators/DI frameworks.
- No ports & adapters/custom repo abstractions for tiny demos—call the ORM/client directly.
Expand All @@ -41,7 +41,7 @@ README tone
---
Simplicity budget (per example)
- ≤ 8 TS source files (excluding migrations).
- Deps: ORM/client + `@cipherstash/protect` + dev tooling (ts-node or tsx). Nothing else unless required by the stack.
- Deps: ORM/client + `@cipherstash/stack` + dev tooling (ts-node or tsx). Nothing else unless required by the stack.
- One `.env.example`; use `dotenv`. No layered config.

CODE STYLE
Expand All @@ -61,7 +61,7 @@ REQUIRED DX & SCRIPTS (per example)
- `seed` → seed sensible data
- `demo` → prints proof of encryption & queries
- `typecheck` → `tsc --noEmit`
- `.env.example` includes DB vars and **exact** Protect.js env names from the Protect README (do not invent):
- `.env.example` includes DB vars and **exact** Stash Encryption env names from the Stack README (do not invent):
{{PROTECT_ENV_VARS := "e.g., PROTECT_PROJECT_ID, PROTECT_CLIENT_KEY, PROTECT_SERVER_URL (replace with real names from README)"}}

PROJECT LAYOUT (monorepo, minimal)
Expand All @@ -80,10 +80,10 @@ README REQUIREMENTS (every example)
- AI banner (exact text) at the very top with {{BOOK_CHAT_URL}}.
- 90-second Quickstart (copy/paste only).
- "What this shows" checklist (encrypted fields, CRUD, query).
- "How encryption works here" (short, accurate, tied to Protect.js).
- "How encryption works here" (short, accurate, tied to Stash Encryption).
- Config notes for the stack (e.g., why CJS for TypeORM).
- Troubleshooting (ESM/CJS, ts-node/tsx, migration pitfalls).
- `@ref` links to stack docs + Protect README.
- `@ref` links to stack docs + Stack README.

DELIVERABLES (return in one message/PR)
- Root `README.md` + `docker-compose.yml`.
Expand Down Expand Up @@ -118,4 +118,4 @@ OUTPUT FORMAT
VARIABLES TO FILL BEFORE RUN
- {{BOOK_CHAT_URL}} = your booking link
- {{STACKS}} = list of stacks to generate
- {{PROTECT_ENV_VARS}} = exact names from Protect.js README
- {{PROTECT_ENV_VARS}} = exact names from Stash Encryption README
6 changes: 3 additions & 3 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
## Protect.js Cursor Rules
## Stash Encryption Cursor Rules

These rules guide agents when creating or updating example apps under `examples/*` in this repository.

### Example App Prompt (for agents)

- **Goals**
- Show end-to-end usage of Protect.js with clear, minimal code.
- Show end-to-end usage of Stash Encryption with clear, minimal code.
- Demonstrate schema, encrypt/decrypt, and (when relevant) searchable encryption on PostgreSQL.

- **Hard guardrails (do not violate)**
Expand Down Expand Up @@ -33,7 +33,7 @@ These rules guide agents when creating or updating example apps under `examples/
- `docs/concepts/searchable-encryption.md`

- **Deliverables checklist for a new example**
- A `protect.ts` (or equivalent) that initializes `protect({ schemas })` using `csTable`/`csColumn`.
- A `protect.ts` (or equivalent) that initializes `Encryption({ schemas })` using `encryptedTable`/`encryptedColumn`.
- If targeting Postgres searchable encryption, include `.freeTextSearch().equality().orderAndRange()` on appropriate columns.
- A minimal script or route/handler that encrypts and decrypts at least one value.
- A README covering:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rebuild-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Rebuild Docs
on:
push:
tags:
- '@cipherstash/protect@*'
- '@cipherstash/stack@*'
- '@cipherstash/drizzle@*'

jobs:
Expand Down
34 changes: 17 additions & 17 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@ jobs:

- name: Create .env file in ./packages/protect/
run: |
touch ./packages/protect/.env
echo "CS_WORKSPACE_CRN=${{ secrets.CS_WORKSPACE_CRN }}" >> ./packages/protect/.env
echo "CS_CLIENT_ID=${{ secrets.CS_CLIENT_ID }}" >> ./packages/protect/.env
echo "CS_CLIENT_KEY=${{ secrets.CS_CLIENT_KEY }}" >> ./packages/protect/.env
echo "CS_CLIENT_ACCESS_KEY=${{ secrets.CS_CLIENT_ACCESS_KEY }}" >> ./packages/protect/.env
echo "SUPABASE_URL=${{ secrets.SUPABASE_URL }}" >> ./packages/protect/.env
echo "SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }}" >> ./packages/protect/.env
echo "CS_ZEROKMS_HOST=https://ap-southeast-2.aws.zerokms.cipherstashmanaged.net" >> ./packages/protect/.env
echo "CS_CTS_HOST=https://ap-southeast-2.aws.cts.cipherstashmanaged.net" >> ./packages/protect/.env
touch ./packages/stack/.env
echo "CS_WORKSPACE_CRN=${{ secrets.CS_WORKSPACE_CRN }}" >> ./packages/stack/.env
echo "CS_CLIENT_ID=${{ secrets.CS_CLIENT_ID }}" >> ./packages/stack/.env
echo "CS_CLIENT_KEY=${{ secrets.CS_CLIENT_KEY }}" >> ./packages/stack/.env
echo "CS_CLIENT_ACCESS_KEY=${{ secrets.CS_CLIENT_ACCESS_KEY }}" >> ./packages/stack/.env
echo "SUPABASE_URL=${{ secrets.SUPABASE_URL }}" >> ./packages/stack/.env
echo "SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }}" >> ./packages/stack/.env
echo "CS_ZEROKMS_HOST=https://ap-southeast-2.aws.zerokms.cipherstashmanaged.net" >> ./packages/stack/.env
echo "CS_CTS_HOST=https://ap-southeast-2.aws.cts.cipherstashmanaged.net" >> ./packages/stack/.env

- name: Create .env file in ./packages/protect-dynamodb/
- name: Create .env file in ./packages/dynamodb/
run: |
touch ./packages/protect-dynamodb/.env
echo "CS_WORKSPACE_CRN=${{ secrets.CS_WORKSPACE_CRN }}" >> ./packages/protect-dynamodb/.env
echo "CS_CLIENT_ID=${{ secrets.CS_CLIENT_ID }}" >> ./packages/protect-dynamodb/.env
echo "CS_CLIENT_KEY=${{ secrets.CS_CLIENT_KEY }}" >> ./packages/protect-dynamodb/.env
echo "CS_CLIENT_ACCESS_KEY=${{ secrets.CS_CLIENT_ACCESS_KEY }}" >> ./packages/protect-dynamodb/.env
echo "CS_ZEROKMS_HOST=https://ap-southeast-2.aws.zerokms.cipherstashmanaged.net" >> ./packages/protect/.env
echo "CS_CTS_HOST=https://ap-southeast-2.aws.cts.cipherstashmanaged.net" >> ./packages/protect/.env
touch ./packages/dynamodb/.env
echo "CS_WORKSPACE_CRN=${{ secrets.CS_WORKSPACE_CRN }}" >> ./packages/dynamodb/.env
echo "CS_CLIENT_ID=${{ secrets.CS_CLIENT_ID }}" >> ./packages/dynamodb/.env
echo "CS_CLIENT_KEY=${{ secrets.CS_CLIENT_KEY }}" >> ./packages/dynamodb/.env
echo "CS_CLIENT_ACCESS_KEY=${{ secrets.CS_CLIENT_ACCESS_KEY }}" >> ./packages/dynamodb/.env
echo "CS_ZEROKMS_HOST=https://ap-southeast-2.aws.zerokms.cipherstashmanaged.net" >> ./packages/dynamodb/.env
echo "CS_CTS_HOST=https://ap-southeast-2.aws.cts.cipherstashmanaged.net" >> ./packages/dynamodb/.env

- name: Create .env file in ./packages/drizzle/
run: |
Expand Down
31 changes: 16 additions & 15 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
This is the Protect.js repository - End-to-end, per-value encryption for JavaScript/TypeScript with zero‑knowledge key management (via CipherStash ZeroKMS). Encrypted data is stored as EQL JSON payloads; searchable encryption is currently supported for PostgreSQL.
This is the Stash Encryption repository (protectjs) - End-to-end, per-value encryption for JavaScript/TypeScript with zero‑knowledge key management (via CipherStash ZeroKMS). Encrypted data is stored as EQL JSON payloads; searchable encryption is currently supported for PostgreSQL.

## Prerequisites

Expand Down Expand Up @@ -43,7 +43,7 @@ pnpm test
- Filter to a single package (recommended for fast iteration):

```bash
pnpm --filter @cipherstash/protect test
pnpm --filter @cipherstash/stack test
pnpm --filter @cipherstash/nextjs test
```

Expand All @@ -64,47 +64,48 @@ USER_JWT=
USER_2_JWT=

# Logging (plaintext is never logged by design)
PROTECT_LOG_LEVEL=debug|info|error
CS_LOG_LEVEL=debug|info|error
```

If these variables are missing, tests that require live encryption will fail or be skipped; prefer filtering to specific packages and tests while developing.

## Repository Layout

- `packages/protect`: Core library
- `src/index.ts`: Public API (`protect`, exports)
- `src/ffi/index.ts`: `ProtectClient` implementation, bridges to `@cipherstash/protect-ffi`
- `packages/stack`: Core library (published as `@cipherstash/stack`)
- `src/index.ts`: Public API (`Encryption`, exports)
- `src/ffi/index.ts`: `EncryptionClient` implementation, bridges to `@cipherstash/protect-ffi`
- `src/ffi/operations/*`: Encrypt/decrypt/model/bulk/search-terms operations (thenable pattern with optional `.withLockContext()`)
- `__tests__/*`: End-to-end and API contract tests (Vitest)
- `packages/schema`: Schema builder utilities and types (`csTable`, `csColumn`, `buildEncryptConfig`)
- `packages/protect`: Deprecated — re-exports from `stack` for backward compatibility
- `packages/schema`: Schema builder utilities and types (`encryptedTable`, `encryptedColumn`, `buildEncryptConfig`)
- `packages/nextjs`: Next.js helpers and Clerk integration (`./clerk` export)
- `packages/protect-dynamodb`: DynamoDB helpers for Protect.js
- `packages/dynamodb`: DynamoDB helpers (published as `@cipherstash/protect-dynamodb`)
- `packages/utils`: Shared config (`utils/config`) and logger (`utils/logger`)
- `examples/*`: Working apps (basic, drizzle, nextjs-clerk, next-drizzle-mysql, dynamo, hono-supabase)
- `docs/*`: Concepts, how-to guides (Next.js bundling, SST, npm lockfile v3), reference

## Key Concepts and APIs

- **Initialization**: `protect({ schemas })` returns an initialized `ProtectClient`. Provide at least one `csTable`.
- **Schema**: Define tables/columns with `csTable` and `csColumn`. Add `.freeTextSearch().equality().orderAndRange()` to enable searchable encryption on PostgreSQL.
- **Initialization**: `Encryption({ schemas })` returns an initialized `EncryptionClient`. Provide at least one `encryptedTable`.
- **Schema**: Define tables/columns with `encryptedTable` and `encryptedColumn`. Add `.freeTextSearch().equality().orderAndRange()` to enable searchable encryption on PostgreSQL.
- **Operations** (all return Result-like objects and support chaining `.withLockContext(lockContext)` when applicable):
- `encrypt(plaintext, { table, column })`
- `decrypt(encryptedPayload)`
- `encryptModel(model, table)` / `decryptModel(model)`
- `bulkEncrypt(plaintexts[], { table, column })` / `bulkDecrypt(encrypted[])`
- `bulkEncryptModels(models[], table)` / `bulkDecryptModels(models[])`
- `createSearchTerms(terms)` for searchable queries
- **Identity-aware encryption**: Use `LockContext` from `@cipherstash/protect/identify` and chain `.withLockContext()` on operations. Same context must be used for both encrypt and decrypt.
- **Identity-aware encryption**: Use `LockContext` from `@cipherstash/stack/identity` and chain `.withLockContext()` on operations. Same context must be used for both encrypt and decrypt.

## Critical Gotchas (read before coding)

- **Native Node.js module**: Protect.js relies on `@cipherstash/protect-ffi` (Node-API). It must be loaded via native Node.js `require`. Do NOT bundle this module; configure bundlers to externalize it.
- **Native Node.js module**: Stash Encryption relies on `@cipherstash/protect-ffi` (Node-API). It must be loaded via native Node.js `require`. Do NOT bundle this module; configure bundlers to externalize it.
- Next.js: see `docs/how-to/nextjs-external-packages.md`
- SST/Serverless: see `docs/how-to/sst-external-packages.md`
- npm lockfile v3 on Linux: see `docs/how-to/npm-lockfile-v3.md`
- **Bun is not supported**: Due to Node-API compatibility gaps. Use Node.js.
- **Do not log plaintext**: The library never logs plaintext by design. Don’t add logs that risk leaking sensitive data.
- **Result shape is contract**: Operations return `{ data }` or `{ failure }`. Preserve this shape and error `type` values in `ProtectErrorTypes`.
- **Result shape is contract**: Operations return `{ data }` or `{ failure }`. Preserve this shape and error `type` values in `EncryptionErrorTypes`.
- **Encrypted payload shape is contract**: Keys like `c` in the EQL payload are validated by tests and downstream tools. Don’t change them.
- **Exports must support ESM and CJS**: Each package’s `exports` maps must keep both `import` and `require` fields. Don’t remove CJS.

Expand Down Expand Up @@ -142,11 +143,11 @@ pnpm changeset:publish
## Adding Features Safely (LLM checklist)

1. Identify the target package(s) in `packages/*` and confirm whether changes affect public APIs or payload shapes.
2. If modifying `packages/protect` operations or `ProtectClient`, ensure:
2. If modifying `packages/stack` operations or `EncryptionClient`, ensure:
- The Result contract and error type strings remain stable.
- `.withLockContext()` remains available for affected operations.
- ESM/CJS exports continue to work (don’t break `require`).
3. If changing schema behavior (`packages/schema`), update type definitions and ensure `buildEncryptConfig` still validates with Zod in `ProtectClient.init`.
3. If changing schema behavior (`packages/schema`), update type definitions and ensure `buildEncryptConfig` still validates with Zod in `EncryptionClient.init`.
4. Add/extend tests in the same package. For features that require live credentials, guard with env checks or provide mock-friendly paths.
5. Run:
- `pnpm run code:fix`
Expand Down
69 changes: 69 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# CLAUDE.md — Stash Encryption (protectjs)

End-to-end, per-value encryption for JS/TS with zero-knowledge key management (CipherStash ZeroKMS). Encrypted data stored as EQL JSON payloads; searchable encryption supported for PostgreSQL.

## Quick reference

```bash
pnpm install # install (requires pnpm 10.x, Node >= 22)
pnpm run build # build all packages (Turborepo + tsup)
pnpm run dev # watch mode
pnpm test # run all package tests (Vitest)
pnpm --filter @cipherstash/stack test # test a single package
pnpm run code:fix # lint + format (Biome)
pnpm changeset # create a changeset for release
```

## Monorepo structure

pnpm workspaces with Turborepo orchestration. Packages under `packages/*`, examples under `examples/*`.

| Package | Purpose |
|---------|---------|
| `protect` | Deprecated — re-exports from `stack` for backward compatibility |
| `stack` | Core library — encrypt/decrypt, FFI bridge to `@cipherstash/protect-ffi` |
| `schema` | Schema builder (`encryptedTable`, `encryptedColumn`, `buildEncryptConfig`) |
| `nextjs` | Next.js helpers and Clerk integration |
| `dynamodb` | DynamoDB helpers |
| `drizzle` | Drizzle ORM integration |
| `utils` | Shared config and logger |

Build order is managed by Turborepo (`^build` dependency). Each package uses `tsup` for bundling.

## Critical constraints

1. **Native FFI** — `@cipherstash/protect-ffi` is a Node-API native module loaded via `require`. Bundlers **must** externalize it (Next.js: `serverExternalPackages`; SST/serverless: see `docs/how-to/`).
2. **No plaintext logging** — The library never logs plaintext by design. Never add logs that could leak sensitive data.
3. **Result contract** — All operations return `{ data }` or `{ failure }` with stable error `type` strings from `EncryptionErrorTypes`. Do not change this shape.
4. **EQL payload shape is contract** — Keys like `c` in encrypted payloads are validated by tests and downstream tools. Do not alter.
5. **ESM + CJS** — Every package's `exports` map must keep both `import` and `require` fields. Do not remove CJS support.
6. **Bun is not supported** — Node-API compatibility gaps. Use Node.js only.

## Environment variables (for tests/examples)

```bash
CS_WORKSPACE_CRN= # required for live encryption tests
CS_CLIENT_ID=
CS_CLIENT_KEY=
CS_CLIENT_ACCESS_KEY=
USER_JWT= # optional — identity-aware encryption tests
USER_2_JWT=
PROTECT_LOG_LEVEL=debug|info|error
```

Tests requiring credentials will fail or be skipped without these.

## Code style

- Biome for formatting and linting (single quotes, no semicolons, 2-space indent)
- `noThenProperty` lint rule is disabled (operations use thenable pattern)
- Tests use Vitest with `.test.ts` files under each package's `__tests__/`
- Test via public API; avoid reaching into private internals

## Making changes checklist

1. Identify target package(s); check if changes affect public APIs or payload shapes
2. Preserve Result contract, `.withLockContext()` chaining, and ESM/CJS exports
3. Add/extend tests in the same package
4. Run: `pnpm run code:fix && pnpm --filter <pkg> build && pnpm --filter <pkg> test`
5. Create a changeset (`pnpm changeset`) if the change affects published packages
Loading
Loading