diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 220f09c..08b68f9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -42,13 +42,16 @@ jobs: - run: bun install --frozen-lockfile - # The TypeDoc reference under content/stack/reference/stack/latest/ is - # gitignored and built from a clone of cipherstash/stack. Roughly 18 - # links point into it, so without this step they all report as missing. + # The TypeDoc references under the V2 integration/reference sections are + # gitignored and built from a clone of cipherstash/stack. Links point + # into them, so without this step those targets report as missing. # `prebuild` generates before validating for the same reason. - name: Generate the SDK reference run: bun run generate-docs + - name: Generate the Supabase reference + run: bun run generate-docs:supabase + - name: Generate the Prisma reference run: bun run generate-docs:prisma diff --git a/.gitignore b/.gitignore index 7e2c4ba..641a8b8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,10 @@ content/stack/reference/drizzle/latest/ content/stack/reference/drizzle/v*/ content/docs/integrations/prisma/api-reference/* !content/docs/integrations/prisma/api-reference/meta.json +content/docs/integrations/supabase/api-reference/* +!content/docs/integrations/supabase/api-reference/meta.json +content/docs/reference/stack/api-reference/* +!content/docs/reference/stack/api-reference/meta.json .tmp-* .env.local diff --git a/IA.md b/IA.md index 1b7436f..38a9f69 100644 --- a/IA.md +++ b/IA.md @@ -415,13 +415,9 @@ and the old `/compare/*` paths redirect there (`v2-redirects.mjs`). rewritten for v3 upstream (v3 branch folded it into database-indexes.md; verify nothing from the v2 guide on main was lost) — see CIP-3351 - **Stack SDK:** -- [ ] `/reference/stack` — client + configuration (port encryption/* pages) -- [ ] `/reference/stack/schema` -- [ ] `/reference/stack/encrypt-decrypt` (+ bulk, models) -- [x] `/reference/stack/supabase` — THE canonical `encryptedSupabase` page, ONE signature (CIP-3328) -- [ ] `/reference/stack/drizzle-operators` -- [ ] `/reference/stack/errors` — port error-handling; miette catalog later (CIP-3338) -- [ ] `/reference/stack/upgrading-from-protect` (retitled package-rename guide) +- [x] `/reference/stack` — core package overview and usage +- [x] `/reference/stack/api-reference` — generated `@cipherstash/stack` API +- [x] `/integrations/supabase/api-reference` — generated `@cipherstash/stack-supabase` API (CIP-3328) - **Auth (CIP-3330):** - [ ] `/reference/auth/lock-contexts` - [ ] `/reference/auth/cts-tokens` diff --git a/content/docs/concepts/compare/index.mdx b/content/docs/concepts/compare/index.mdx index 5c34ec1..0615aa1 100644 --- a/content/docs/concepts/compare/index.mdx +++ b/content/docs/concepts/compare/index.mdx @@ -1,5 +1,6 @@ --- title: Comparisons +navTitle: Overview description: "How CipherStash compares to other approaches to protecting data." type: concept --- diff --git a/content/docs/concepts/compare/meta.json b/content/docs/concepts/compare/meta.json index 76e9696..3e374e5 100644 --- a/content/docs/concepts/compare/meta.json +++ b/content/docs/concepts/compare/meta.json @@ -1,5 +1,5 @@ { "title": "Comparisons", "icon": "Scale", - "pages": ["..."] + "pages": ["index", "..."] } diff --git a/content/docs/concepts/index.mdx b/content/docs/concepts/index.mdx index 3d36567..8fc5128 100644 --- a/content/docs/concepts/index.mdx +++ b/content/docs/concepts/index.mdx @@ -1,5 +1,6 @@ --- title: Concepts +navTitle: Overview description: "How CipherStash works and how to think about searchable encryption, keys, and identity." type: concept --- diff --git a/content/docs/concepts/meta.json b/content/docs/concepts/meta.json index 49d9122..a5e37c2 100644 --- a/content/docs/concepts/meta.json +++ b/content/docs/concepts/meta.json @@ -1,5 +1,5 @@ { "title": "Concepts", "icon": "Lightbulb", - "pages": ["...", "compare"] + "pages": ["index", "...", "compare"] } diff --git a/content/docs/guides/deployment/index.mdx b/content/docs/guides/deployment/index.mdx index e5ef18e..12b5ade 100644 --- a/content/docs/guides/deployment/index.mdx +++ b/content/docs/guides/deployment/index.mdx @@ -1,5 +1,6 @@ --- title: Deployment +navTitle: Overview description: "Deploy CipherStash changes safely across environments, with explicit credential, migration, rollout, monitoring, and rollback gates." type: guide components: [encryption, eql, cli] diff --git a/content/docs/guides/deployment/meta.json b/content/docs/guides/deployment/meta.json index e2ffe90..0bbd244 100644 --- a/content/docs/guides/deployment/meta.json +++ b/content/docs/guides/deployment/meta.json @@ -1,4 +1,4 @@ { "title": "Deployment", - "pages": ["..."] + "pages": ["index", "..."] } diff --git a/content/docs/guides/development/index.mdx b/content/docs/guides/development/index.mdx index 3ac286f..b1a735e 100644 --- a/content/docs/guides/development/index.mdx +++ b/content/docs/guides/development/index.mdx @@ -1,5 +1,6 @@ --- title: Development +navTitle: Overview description: "Development documentation — being built as part of the docs V2 overhaul." --- diff --git a/content/docs/guides/development/meta.json b/content/docs/guides/development/meta.json index 203f9c9..24f8878 100644 --- a/content/docs/guides/development/meta.json +++ b/content/docs/guides/development/meta.json @@ -1,4 +1,4 @@ { "title": "Development", - "pages": ["..."] + "pages": ["index", "..."] } diff --git a/content/docs/guides/index.mdx b/content/docs/guides/index.mdx index 8d6647e..32279bf 100644 --- a/content/docs/guides/index.mdx +++ b/content/docs/guides/index.mdx @@ -1,5 +1,6 @@ --- title: Guides +navTitle: Overview description: "Task-oriented guides: development workflow, data migration, deployment, and troubleshooting." type: guide --- diff --git a/content/docs/guides/meta.json b/content/docs/guides/meta.json index 498dd61..810cf95 100644 --- a/content/docs/guides/meta.json +++ b/content/docs/guides/meta.json @@ -1,5 +1,5 @@ { "title": "Guides", "icon": "Wrench", - "pages": ["..."] + "pages": ["index", "..."] } diff --git a/content/docs/guides/migration/index.mdx b/content/docs/guides/migration/index.mdx index 6741cf4..5820d97 100644 --- a/content/docs/guides/migration/index.mdx +++ b/content/docs/guides/migration/index.mdx @@ -1,5 +1,6 @@ --- title: Data migration +navTitle: Overview description: "Safely introduce CipherStash encryption to existing schemas and populated production data." type: guide components: [encryption, eql, cli] diff --git a/content/docs/guides/migration/meta.json b/content/docs/guides/migration/meta.json index 941c504..db4e68b 100644 --- a/content/docs/guides/migration/meta.json +++ b/content/docs/guides/migration/meta.json @@ -1,4 +1,4 @@ { "title": "Data migration", - "pages": ["..."] + "pages": ["index", "..."] } diff --git a/content/docs/guides/troubleshooting/index.mdx b/content/docs/guides/troubleshooting/index.mdx index d049ef4..b49feca 100644 --- a/content/docs/guides/troubleshooting/index.mdx +++ b/content/docs/guides/troubleshooting/index.mdx @@ -1,5 +1,6 @@ --- title: Troubleshooting +navTitle: Overview description: "Troubleshooting documentation — being built as part of the docs V2 overhaul." --- diff --git a/content/docs/guides/troubleshooting/meta.json b/content/docs/guides/troubleshooting/meta.json index 82c3c83..e5715ad 100644 --- a/content/docs/guides/troubleshooting/meta.json +++ b/content/docs/guides/troubleshooting/meta.json @@ -1,4 +1,4 @@ { "title": "Troubleshooting", - "pages": ["..."] + "pages": ["index", "..."] } diff --git a/content/docs/integrations/index.mdx b/content/docs/integrations/index.mdx index 050b058..04359af 100644 --- a/content/docs/integrations/index.mdx +++ b/content/docs/integrations/index.mdx @@ -1,5 +1,6 @@ --- title: Integrations +navTitle: Overview description: "Every CipherStash integration, by category: the Postgres platforms encrypted data lives in, and the query layers that read and write it." type: concept components: [encryption, eql, platform] diff --git a/content/docs/integrations/meta.json b/content/docs/integrations/meta.json index bfb94b1..15136e6 100644 --- a/content/docs/integrations/meta.json +++ b/content/docs/integrations/meta.json @@ -1,5 +1,5 @@ { "title": "Integrations", "icon": "Blocks", - "pages": ["!index", "supabase", "prisma", "..."] + "pages": ["index", "supabase", "prisma", "..."] } diff --git a/content/docs/integrations/supabase/api-reference/meta.json b/content/docs/integrations/supabase/api-reference/meta.json new file mode 100644 index 0000000..dd99951 --- /dev/null +++ b/content/docs/integrations/supabase/api-reference/meta.json @@ -0,0 +1,4 @@ +{ + "title": "API reference", + "pages": ["index"] +} diff --git a/content/docs/integrations/supabase/auth.mdx b/content/docs/integrations/supabase/auth.mdx index cd3ad17..f35e189 100644 --- a/content/docs/integrations/supabase/auth.mdx +++ b/content/docs/integrations/supabase/auth.mdx @@ -76,7 +76,7 @@ Federation authenticates *as* the user. **Lock context** goes further: it bakes Lock context requires the client to be authenticated with `OidcFederationStrategy` (above). It cannot be used with `AccessKeyStrategy`, which authenticates a *service*, not a user — there is no user `sub` claim to bind to. Lock context also requires a Business or Enterprise workspace plan. -Attach a lock context to any [`encryptedSupabase`](/reference/stack/supabase) query with `.withLockContext()`. `sub` is the Supabase user's UUID: +Attach a lock context to any [`encryptedSupabase`](/integrations/supabase/api-reference) query with `.withLockContext()`. `sub` is the Supabase user's UUID: ```typescript import { db } from "./lib/db" @@ -120,7 +120,7 @@ Both federation and lock context surface failures as values, not throws. | Provider not registered with the workspace | Federation fails — register the Supabase OIDC issuer first | | Expired or invalid Supabase session | `getJwt` throws / returns no session; federation cannot proceed | | CipherStash token expired mid-request | Strategy re-federates automatically via `getJwt` | -| Decrypting another user's locked value | `encryptionError` on the [query response](/reference/stack/supabase#responses-and-errors) | +| Decrypting another user's locked value | `encryptionError` on the [query response](/integrations/supabase/supabase-js#responses-and-errors) | The strategy's own factory and token errors follow the `@cipherstash/auth` `Result` shape (`failure.type`); see the [auth reference](/reference/auth). @@ -133,7 +133,7 @@ The strategy's own factory and token errors follow the `@cipherstash/auth` `Resu Run the federation strategy server-side in Supabase Edge Functions. - + `.withLockContext()` and `.audit()` on the query builder. diff --git a/content/docs/integrations/supabase/index.mdx b/content/docs/integrations/supabase/index.mdx index c26fce0..dba917b 100644 --- a/content/docs/integrations/supabase/index.mdx +++ b/content/docs/integrations/supabase/index.mdx @@ -22,11 +22,11 @@ Unlike ordinary application-level encryption, protected columns remain queryable CipherStash has two parts: 1. [Encrypt Query Language (EQL)](/reference/eql) adds encrypted column types, operators, and functions to your Supabase Postgres database. -2. [`encryptedSupabase`](/reference/stack/supabase) wraps the Supabase JavaScript client and performs encryption and decryption in your application. +2. [`encryptedSupabase`](/integrations/supabase/api-reference) wraps the Supabase JavaScript client and performs encryption and decryption in your application. The EQL type assigned to a column declares what can be queried. For example, an equality column supports `.eq()` and `.in()`, while an ordered column also supports ranges and `.order()`. Choose only the capabilities you need, because searchable encryption has defined leakage characteristics. See [EQL core concepts](/reference/eql/core-concepts) for the complete model. -With EQL 3.0.4, the Supabase wrapper supports encrypted equality, range, and ordering queries. Encrypted free-text and JSON queries require an adapter such as Drizzle or Prisma ORM 8 because PostgREST cannot express the typed operands those operations require. The [wrapper reference](/reference/stack/supabase#free-text-and-json-need-a-different-adapter) documents the limitation. +With EQL 3.0.4, the Supabase wrapper supports encrypted equality, range, and ordering queries. Encrypted free-text and JSON queries require an adapter such as Drizzle or Prisma ORM 8 because PostgREST cannot express the typed operands those operations require. The [supabase-js guide](/integrations/supabase/supabase-js#free-text-and-encrypted-json-queries) documents the limitation. ## Choose your path @@ -34,7 +34,7 @@ With EQL 3.0.4, the Supabase wrapper supports encrypted equality, range, and ord | --- | --- | | Encrypt and query your first Supabase column | [Supabase Quickstart](/integrations/supabase/quickstart) | | Use the Supabase JavaScript client | [supabase-js guide](/integrations/supabase/supabase-js) | -| Look up the complete wrapper API | [`encryptedSupabase` reference](/reference/stack/supabase) | +| Look up the complete wrapper API | [`encryptedSupabase` API reference](/integrations/supabase/api-reference) | | Bind decryption to the signed-in user | [Supabase Auth](/integrations/supabase/auth) | | Encrypt inside Supabase Edge Functions | [Edge Functions](/integrations/supabase/edge-functions) | | Choose local or deployment credentials | [Workspace credentials](/reference/workspace/configuration#credentials) | diff --git a/content/docs/integrations/supabase/meta.json b/content/docs/integrations/supabase/meta.json index f03d6df..a94eedf 100644 --- a/content/docs/integrations/supabase/meta.json +++ b/content/docs/integrations/supabase/meta.json @@ -1,5 +1,12 @@ { "title": "Supabase", "icon": "Supabase", - "pages": ["index", "quickstart", "supabase-js", "auth", "edge-functions"] + "pages": [ + "index", + "quickstart", + "supabase-js", + "auth", + "edge-functions", + "api-reference" + ] } diff --git a/content/docs/integrations/supabase/quickstart.mdx b/content/docs/integrations/supabase/quickstart.mdx index da64681..7480ee7 100644 --- a/content/docs/integrations/supabase/quickstart.mdx +++ b/content/docs/integrations/supabase/quickstart.mdx @@ -159,7 +159,7 @@ The Table Editor cannot decrypt or meaningfully filter the encrypted value becau ## Next steps - Learn the supported mutations, filters, ordering, response types, and errors in the [supabase-js guide](/integrations/supabase/supabase-js). -- Use the complete [`encryptedSupabase` API reference](/reference/stack/supabase) when you need method-level detail. +- Use the complete [`encryptedSupabase` API reference](/integrations/supabase/api-reference) when you need method-level detail. - Add functional indexes when the table reaches meaningful row counts by following [EQL indexes](/reference/eql/indexes). - For a populated table, follow the safe dual-write and backfill process in [Encrypt existing data](/guides/migration/encrypt-existing-data). - To use a direct Postgres ORM connection, continue with [Drizzle](/integrations/drizzle) or [Prisma ORM 8](/integrations/prisma). diff --git a/content/docs/integrations/supabase/supabase-js.mdx b/content/docs/integrations/supabase/supabase-js.mdx index e198479..b4408c2 100644 --- a/content/docs/integrations/supabase/supabase-js.mdx +++ b/content/docs/integrations/supabase/supabase-js.mdx @@ -229,7 +229,7 @@ Chain `.throwOnError()` when the surrounding code prefers exceptions. Chain `.au Run encryption in Deno with the WebAssembly build. - + The complete query-builder API, response types, and implementation details. diff --git a/content/docs/reference/auth/index.mdx b/content/docs/reference/auth/index.mdx index 9bea074..220fa84 100644 --- a/content/docs/reference/auth/index.mdx +++ b/content/docs/reference/auth/index.mdx @@ -1,5 +1,6 @@ --- title: Auth +navTitle: Overview description: "Auth documentation — being built as part of the docs V2 overhaul." --- diff --git a/content/docs/reference/auth/meta.json b/content/docs/reference/auth/meta.json index d801d12..943c560 100644 --- a/content/docs/reference/auth/meta.json +++ b/content/docs/reference/auth/meta.json @@ -1,4 +1,4 @@ { "title": "Auth", - "pages": ["..."] + "pages": ["index", "..."] } diff --git a/content/docs/reference/cli/index.mdx b/content/docs/reference/cli/index.mdx index 88764db..b8f6a0a 100644 --- a/content/docs/reference/cli/index.mdx +++ b/content/docs/reference/cli/index.mdx @@ -1,5 +1,6 @@ --- title: CLI +navTitle: Overview description: "Command reference for the stash CLI, generated from v0.17.0." type: reference components: [cli] diff --git a/content/docs/reference/cli/meta.json b/content/docs/reference/cli/meta.json index 6b90890..86e9f17 100644 --- a/content/docs/reference/cli/meta.json +++ b/content/docs/reference/cli/meta.json @@ -1,6 +1,7 @@ { "title": "CLI", "pages": [ + "index", "---Setup & workflow---", "init", "plan", diff --git a/content/docs/reference/eql/index.mdx b/content/docs/reference/eql/index.mdx index 92291db..d09cd07 100644 --- a/content/docs/reference/eql/index.mdx +++ b/content/docs/reference/eql/index.mdx @@ -1,5 +1,6 @@ --- title: EQL +navTitle: Overview description: "Encrypt Query Language (EQL) installs encrypted column types and operators into Postgres as plain SQL — encryption itself happens in your client." type: reference components: [eql] diff --git a/content/docs/reference/eql/meta.json b/content/docs/reference/eql/meta.json index 6d448c7..d432349 100644 --- a/content/docs/reference/eql/meta.json +++ b/content/docs/reference/eql/meta.json @@ -1,6 +1,7 @@ { "title": "EQL", "pages": [ + "index", "core-concepts", "---Types---", "numbers", diff --git a/content/docs/reference/eql/v2/index.mdx b/content/docs/reference/eql/v2/index.mdx index 9095659..228407e 100644 --- a/content/docs/reference/eql/v2/index.mdx +++ b/content/docs/reference/eql/v2/index.mdx @@ -1,5 +1,6 @@ --- title: EQL v2 +navTitle: Overview description: PostgreSQL types, operators, and functions for querying encrypted data with EQL v2 (v2.2) — the eql_v2_encrypted type and searchable index types. type: reference components: [eql] diff --git a/content/docs/reference/eql/v2/meta.json b/content/docs/reference/eql/v2/meta.json index 68333fd..c86899c 100644 --- a/content/docs/reference/eql/v2/meta.json +++ b/content/docs/reference/eql/v2/meta.json @@ -1,4 +1,4 @@ { "title": "EQL v2", - "pages": ["indexes", "queries", "payload"] + "pages": ["index", "indexes", "queries", "payload"] } diff --git a/content/docs/reference/index.mdx b/content/docs/reference/index.mdx index 997dff4..2f6f2d3 100644 --- a/content/docs/reference/index.mdx +++ b/content/docs/reference/index.mdx @@ -1,5 +1,6 @@ --- title: Reference +navTitle: Overview description: "Precise API documentation for EQL, the Stack SDK, Auth, the CLI, and Proxy." type: reference --- diff --git a/content/docs/reference/proxy/index.mdx b/content/docs/reference/proxy/index.mdx index f468666..446c501 100644 --- a/content/docs/reference/proxy/index.mdx +++ b/content/docs/reference/proxy/index.mdx @@ -1,5 +1,6 @@ --- title: Proxy +navTitle: Overview description: "Configuration, message flow, multitenant operation, and error reference for CipherStash Proxy." type: reference components: [proxy] diff --git a/content/docs/reference/stack/api-reference/meta.json b/content/docs/reference/stack/api-reference/meta.json new file mode 100644 index 0000000..45717c6 --- /dev/null +++ b/content/docs/reference/stack/api-reference/meta.json @@ -0,0 +1,17 @@ +{ + "title": "API reference", + "pages": [ + "index", + "Package-exports", + "adapter-kit", + "dynamodb", + "encryption", + "encryption.v3", + "eql.v3", + "errors", + "identity", + "schema", + "types-public", + "wasm-inline" + ] +} diff --git a/content/docs/reference/stack/index.mdx b/content/docs/reference/stack/index.mdx index edac1c3..4b60194 100644 --- a/content/docs/reference/stack/index.mdx +++ b/content/docs/reference/stack/index.mdx @@ -1,8 +1,33 @@ --- -title: Stack SDK -description: "Stack SDK documentation — being built as part of the docs V2 overhaul." +title: Overview +description: "The core @cipherstash/stack package for client-side encryption, searchable schemas, identity controls, and encrypted query operands." +type: reference +components: [encryption, eql] +audience: [developer] --- -This section is being built as part of the docs V2 overhaul ([CIP-3307](https://linear.app/cipherstash/issue/CIP-3307)). Track progress in [IA.md](https://github.com/cipherstash/docs/blob/v2/IA.md). +`@cipherstash/stack` is the core TypeScript SDK for CipherStash. It encrypts and decrypts values in your application, defines the searchable capabilities of encrypted columns, and creates encrypted operands for queries against [EQL](/reference/eql). -Until it lands, current documentation lives in the [existing docs](/stack). +Database and framework wrappers are separate packages. Use the [Supabase](/integrations/supabase), [Prisma ORM 8](/integrations/prisma), or [Drizzle](/integrations/drizzle) integration when you want encryption built into that library's query flow. + +## What the package provides + +| Area | Purpose | +| --- | --- | +| Encryption client | Encrypt and decrypt individual values, batches, and models. | +| Schema builders | Describe tables, columns, value types, and searchable capabilities. | +| Query encryption | Produce typed encrypted operands for equality, range, and search queries. | +| Identity controls | Bind encryption and decryption to federated identity claims. | +| DynamoDB helpers | Encrypt and decrypt DynamoDB attributes and expressions. | +| Error types | Inspect operation failures without parsing error messages. | + +## Start here + + + + Install the package, define an encrypted schema, and encrypt, query, and decrypt a value. + + + Browse the generated functions, classes, interfaces, and type aliases exported by the package. + + diff --git a/content/docs/reference/stack/meta.json b/content/docs/reference/stack/meta.json index d0f86af..1f08609 100644 --- a/content/docs/reference/stack/meta.json +++ b/content/docs/reference/stack/meta.json @@ -1,4 +1,4 @@ { "title": "Stack SDK", - "pages": ["..."] + "pages": ["index", "usage", "api-reference"] } diff --git a/content/docs/reference/stack/supabase.mdx b/content/docs/reference/stack/supabase.mdx deleted file mode 100644 index c05e347..0000000 --- a/content/docs/reference/stack/supabase.mdx +++ /dev/null @@ -1,291 +0,0 @@ ---- -title: Supabase wrapper -description: "The canonical encryptedSupabase reference: connect-time introspection, the full query-builder surface, and how each filter maps to an encrypted index term." -type: reference -components: [encryption, eql] -audience: [developer] -verifiedAgainst: - stack: "1.0.0" - eql: "3.0.4" ---- - -`encryptedSupabase` wraps a `@supabase/supabase-js` client so that queries on encrypted columns work like queries on plaintext ones. Mutations are encrypted before they leave your app, filter values are encrypted into the matching index term, and results are decrypted on the way back. You keep writing `.eq()`, `.gt()`, `.order()`, and the wrapper handles the encryption on both sides of every call. - -This page is the single source of truth for the wrapper's API. The [Supabase integration](/integrations/supabase) shows it in context; the [EQL reference](/reference/eql) covers the database side it queries against. - -The wrapper ships as its own package, `@cipherstash/stack-supabase`, which depends on `@cipherstash/stack`: - -```bash -npm install @cipherstash/stack @cipherstash/stack-supabase @supabase/supabase-js -``` - -## Setup - -`encryptedSupabase` **introspects your database when you call it**. It reads the column types, finds the ones typed as EQL v3 domains, and derives each column's encryption config from the domain. The database already records which columns are encrypted and what they can do, so you do not declare it a second time. - -That makes it async, and it makes `.from()` take a table name and nothing else. - -```typescript -import { encryptedSupabase } from "@cipherstash/stack-supabase" - -const supabase = await encryptedSupabase( - process.env.SUPABASE_URL!, - process.env.SUPABASE_ANON_KEY!, -) - -const { data, error } = await supabase - .from("users") - .select("id, email") - .eq("email", "alice@example.com") -``` - -Introspection needs a direct Postgres connection, which is a different credential from the Supabase URL and anon key. It reads `DATABASE_URL` from the environment, or `options.databaseUrl` if you pass it. - -```typescript -const supabase = await encryptedSupabase(supabaseUrl, supabaseKey, { - databaseUrl: process.env.PG_URL, -}) -``` - -Pass an existing client instead of a URL and key when you already build one, for example to attach a Supabase Auth session: - -```typescript -const supabase = await encryptedSupabase(supabaseClient, { - databaseUrl: process.env.PG_URL, -}) -``` - -### Options - -| Option | Type | Purpose | -| --- | --- | --- | -| `databaseUrl` | `string` | Postgres connection string used for introspection. Defaults to `process.env.DATABASE_URL`. | -| `config` | `ClientConfig` | Passed through to the encryption client. `eqlVersion` is forced to `3`. | -| `schemas` | `Record` | Optional declared tables. Adds compile-time types and startup verification. | - -### `.from(tableName)` - -Returns a query builder that mirrors the supabase-js builder. Columns that are not EQL domains pass through untouched, so mixed plaintext and encrypted tables are the normal case. - - -A table containing an EQL column that this SDK cannot model throws when you name it in `.from()`. The introspector identifies EQL domains by their Postgres comment, so an EQL column from a newer bundle than your SDK is detected, reported, and refused rather than silently read back as raw ciphertext. - - -## Declaring columns in the database - -The column's domain type is the schema. Declare the capability you query on and nothing more, because each capability stores an extra index term alongside the ciphertext. - -```sql -CREATE TABLE users ( - id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, - email public.eql_v3_text_search, -- equality, ordering, free-text match - age public.eql_v3_integer_ord, -- equality, ordering - name text -- plaintext, untouched -); -``` - -The capability model is the same one the database enforces through [EQL's domain variants](/reference/eql/core-concepts): - -| Domain | Enables | EQL term | -| --- | --- | --- | -| `public.eql_v3_` | Store and decrypt only | none | -| `public.eql_v3__eq` | `.eq()`, `.neq()`, `.in()` | `hm` | -| `public.eql_v3__ord` | `.gt()`, `.gte()`, `.lt()`, `.lte()`, `.order()`, plus equality | `op` | -| `public.eql_v3_text_match` | `.matches()` | `bf` | -| `public.eql_v3_text_search` | All of the above, on text | `hm`, `op`, `bf` | -| `public.eql_v3_json` | Encrypted JSON documents | `sv` | - -## Optional schemas: types and startup verification - -Without `schemas`, rows are `Record` and any table name is accepted. Declare a table and `.from()` returns a typed builder for it, with row types, filter-value types, and capability-narrowed methods. Declared tables are also verified against the live database before the client is handed back, so a column that was dropped or retyped fails at startup rather than at the first query. - -```typescript -import { encryptedSupabase } from "@cipherstash/stack-supabase" -import { encryptedTable, types } from "@cipherstash/stack/eql/v3" - -const users = encryptedTable("users", { - email: types.TextSearch("email"), - age: types.IntegerOrd("age"), -}) - -const supabase = await encryptedSupabase(supabaseUrl, supabaseKey, { - schemas: { users }, -}) - -// `.gt` is offered on `age`, and rejected on `email`, at compile time -await supabase.from("users").select("id, email").gt("age", 21) -``` - -Each key in `schemas` must equal its table's name. Declaring a table changes what TypeScript knows about it, never how its values are encrypted: a declared and an introspected column build byte-identically. - -## Writing data - -`insert`, `update`, and `upsert` encrypt every encrypted column before the request leaves your process. All values in a batch are encrypted in a single ZeroKMS round trip, so large inserts do not multiply key-service calls. - -```typescript -await supabase.from("users").insert({ - email: "alice@example.com", // encrypted - age: 30, // encrypted - name: "Alice Chen", // plaintext column, passes through -}) - -await supabase.from("users").update({ age: 31 }).eq("email", "alice@example.com") - -await supabase.from("users").delete().eq("email", "alice@example.com") -``` - -`insert` and `upsert` accept a single row or an array. `upsert` supports `onConflict` and `ignoreDuplicates` as in supabase-js. - -## Reading data - -Encrypted columns in the result are decrypted before `data` is returned to you. `select('*')` and a no-argument `select()` both work: the wrapper expands them from the column list it introspected. - -```typescript -const { data, error } = await supabase - .from("users") - .select("*") - .eq("email", "alice@example.com") -``` - -## Filters - -Every filter value on an encrypted column is encrypted client-side into the term that capability queries, then sent in place of the plaintext. The database only ever compares ciphertext. Filters on plaintext columns pass through to supabase-js unchanged. - -| Method | Requires | What the database compares | -| --- | --- | --- | -| `.eq()` / `.neq()` | `_eq`, `_ord`, or `text_search` | Equality terms | -| `.in(col, values)` | `_eq`, `_ord`, or `text_search` | Each element encrypted separately | -| `.gt()` `.gte()` `.lt()` `.lte()` | `_ord` or `text_search` | Ordering terms | -| `.matches(col, value)` | `text_match` or `text_search` | Bloom-filter token containment — **unavailable through PostgREST with EQL 3.0.4, [see below](#free-text-and-json-need-a-different-adapter)** | -| `.contains(col, value)` | `json`, or a plaintext array | Sub-document containment, not free-text. Encrypted JSON is **unavailable through PostgREST with EQL 3.0.4** | -| `.selectorEq(col, path, value)` / `.selectorNe()` | `json` | One JSON field's value, by path — **unavailable through PostgREST with EQL 3.0.4** | -| `.is(col, null)` | any column | Passed through. SQL `NULL` is never encrypted | -| `.match({ col: value })` | per column | Each pair applied as `.eq()` | -| `.or(filters)` | per column | Encrypted values inside the filter string are encrypted in place | -| `.not(col, op, value)` | per operator | Value is encrypted, operator passes through | -| `.filter(col, op, value)` | per operator | Escape hatch, same as `.not()` | - -```typescript -// Range on an _ord column -await supabase.from("users").select("id, age").gt("age", 21) - -// Encrypted and plaintext filters compose freely -await supabase - .from("users") - .select("id, email") - .eq("email", "alice@example.com") // encrypted comparison - .eq("name", "Alice Chen") // ordinary supabase-js filter -``` - -### Free-text and JSON need a different adapter [#free-text-and-json-need-a-different-adapter] - - -**With the current EQL 3.0.4 release, this wrapper cannot run encrypted free-text search or encrypted-JSON queries.** Every spelling raises: - -``` -[supabase v3]: matches() on encrypted column "email" is unavailable with EQL -3.0.2+: the SQL operator requires an eql_v3.query_* cast that PostgREST cannot -express. Use the Drizzle or Prisma ORM 8 adapter, or a scoped SQL/RPC function. -``` - -The error says `3.0.2+` because EQL 3.0.2 introduced this query-domain requirement. EQL 3.0.4 retains it. - -The limit is PostgREST's, not the encryption's. Those operators need a typed `eql_v3.query_*` operand, and PostgREST has no syntax for the cast — so the wrapper refuses rather than send an operand the database would compare wrongly. It fails closed on an unknown EQL version too. - -This applies to `.matches()`, `.contains()` on a `json` column, `.selectorEq()` / `.selectorNe()`, and the same terms reached through `.not()`, `.or()`, or a raw `.filter()`. **Equality, ordering, and range are unaffected** — `.eq()`, `.neq()`, `.in()`, `.gt()` / `.gte()` / `.lt()` / `.lte()`, and `.order()` all work, as do inserts, updates, and decryption on select. - -To search encrypted text with EQL 3.0.4, use the [Drizzle](/integrations/drizzle) or [Prisma ORM 8](/integrations/prisma) adapter for that query, or put it behind a scoped SQL or RPC function. Everything else on this page still applies. - - -On EQL 3.0.1 and earlier, `.matches()` tests whether the stored value's encrypted token set contains the search string's — case-insensitive, and false positives are possible while false negatives are not: - -```typescript -await supabase.from("users").select("*").matches("email", "alice@example.com") -``` - -It is **not** `.contains()`. On an encrypted text column `.contains()` raises regardless of EQL version — containment promises exactness, which bloom-filter matching cannot deliver, so the wrapper refuses rather than quietly answering a different question. `.contains()` is the right method for encrypted JSON documents and plaintext arrays. - -### `.like()` and `.ilike()` are approximate - -On a plaintext column they are ordinary SQL `LIKE`, unaffected by any of the above. On an **encrypted** column they are a compatibility shim that reduces the pattern to a needle and delegates to `.matches()` — so with EQL 3.0.4 they raise for the same reason it does. - -Where they do run, the result is *approximate*: matching becomes case-insensitive and one-sided (it may return false positives), and anchoring is lost. Leading and trailing `%` are stripped, since fuzzy matching subsumes them. A pattern that cannot be approximated — an internal `%`, or any `_` — raises instead of guessing. - -```typescript -// Reduced to matches("email", "@example.com") -await supabase.from("users").select("*").like("email", "%@example.com") - -// ❌ raises: an internal wildcard has no trigram equivalent -await supabase.from("users").select("*").like("email", "a%e@example.com") -``` - -Prefer `.matches()` in new code — it is the operation that actually runs, and it does not imply SQL semantics the database is not applying. - -## Ordering and pagination - -`.order()` on an `_ord` or `text_search` column sorts on the encrypted ordering terms, which is exactly what that capability exists for. See [ordering encrypted values](/reference/eql/sorting). `.limit()`, `.range()`, `.single()`, and `.maybeSingle()` behave as in supabase-js. - -```typescript -await supabase.from("users").select("id, email, age").order("age", { ascending: false }).limit(20) -``` - -Ordering a storage-only, equality-only, match-only, or `_ord_ore` encrypted column throws instead of sorting the raw stored payload. `.csv()` also throws because PostgREST serializes the rows before the wrapper can decrypt them; select rows normally and serialize the plaintext result in your application. - -## Identity and audit - -Two methods have no supabase-js equivalent: - -```typescript -await supabase - .from("users") - .select("id, email") - .eq("email", "alice@example.com") - .withLockContext(lockContext) - .audit({ metadata: { reason: "support-lookup" } }) -``` - -`.withLockContext(lockContext)` scopes every encrypt and decrypt in the query to an identity-locked context. See the [Supabase Auth integration](/integrations/supabase/auth) for wiring it to Supabase Auth sessions. `.audit(config)` attaches metadata to the query's decryption events in the [audit log](/security/audit-logging). - -`.abortSignal()`, `.throwOnError()`, and `.returns()` pass through as in supabase-js. - -## Responses and errors - -Awaiting the builder resolves to the supabase-js response shape, extended with encryption context: - -```typescript -type EncryptedSupabaseResponse = { - data: T | null - error: EncryptedSupabaseError | null - count: number | null - status: number - statusText: string -} - -type EncryptedSupabaseError = { - message: string - details?: string - hint?: string - code?: string - encryptionError?: EncryptionError // set when encrypt/decrypt failed, not the query -} -``` - -Check `error.encryptionError` to distinguish an encryption failure (bad workspace credentials, unreachable ZeroKMS, a payload that fails validation) from an ordinary PostgREST error. - -## How it works - -The builder is deferred: method calls record intent, and nothing executes until you `await`. At that point the wrapper, in order: - -1. Encrypts mutation payloads, all rows in one ZeroKMS call -2. Encrypts every filter value on an encrypted column into its index term -3. Adds `::jsonb` casts to encrypted columns in the `select` list -4. Replays the recorded calls onto the real supabase-js builder -5. Decrypts encrypted columns in the response - -Because the wrapped client is your own `createClient(...)` instance, Supabase Auth sessions and **Row Level Security policies apply unchanged**. RLS decides which rows come back; encryption decides whether what is in them can be read. Database-side setup (installing EQL, column types, grants, indexes) is covered in the [Supabase overview](/integrations/supabase). - -## Two names, one function - -`encryptedSupabase` and `encryptedSupabaseV3` are the **same function**, and either import works. The `V3` suffix dates from when this package also shipped an EQL v2 dialect; now that EQL v3 is the only generation the wrapper authors, the suffix is redundant, so `encryptedSupabase` is the preferred spelling and `encryptedSupabaseV3` is a deprecated alias kept for existing imports. - -The type names work the same way: `EncryptedSupabaseOptions`, `EncryptedSupabaseInstance`, and `TypedEncryptedSupabaseInstance`, with their `…V3…` forms as deprecated aliases. diff --git a/content/docs/reference/stack/usage.mdx b/content/docs/reference/stack/usage.mdx new file mode 100644 index 0000000..6e84cfc --- /dev/null +++ b/content/docs/reference/stack/usage.mdx @@ -0,0 +1,87 @@ +--- +title: Usage +description: "Install @cipherstash/stack and use its core encryption client with an EQL v3 encrypted column." +type: reference +components: [encryption, eql] +audience: [developer] +--- + +## Install + +```bash cta cta-type="install" example-id="install-stack" +npm install @cipherstash/stack +``` + +Authenticate locally with `stash auth login`, or configure the `CS_*` machine credentials described in [workspace configuration](/reference/workspace/configuration). + +## Define a schema and client + +The application schema tells Stack which columns are encrypted and which query capabilities they need. It must match the [EQL domain](/reference/eql/core-concepts) used by the database column. + +```typescript filename="src/encryption.ts" +import { Encryption, encryptedColumn, encryptedTable } from "@cipherstash/stack" + +export const users = encryptedTable("users", { + email: encryptedColumn("email").equality(), +}) + +export const encryption = await Encryption({ schemas: [users] }) +``` + +An equality-enabled text column uses the corresponding EQL type: + +```sql +CREATE TABLE users ( + id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + email public.eql_v3_text_eq +); +``` + +## Encrypt and store + +Stack operations return a `Result` with either `data` or `failure`: + +```typescript +const encrypted = await encryption.encrypt("alice@example.com", { + table: users, + column: users.email, +}) + +if (encrypted.failure) throw encrypted.failure + +await db.query("INSERT INTO users (email) VALUES ($1)", [encrypted.data]) +``` + +Encryption happens before the value leaves the application process. + +## Query an encrypted column + +Encrypt the operand with the same table and column definition, then cast it to the column's EQL domain in SQL: + +```typescript +const term = await encryption.encryptQuery("alice@example.com", { + table: users, + column: users.email, +}) + +if (term.failure) throw term.failure + +const result = await db.query( + "SELECT id, email FROM users WHERE email = $1::public.eql_v3_text_eq", + [term.data], +) +``` + +The database compares encrypted terms and never receives the plaintext operand. See [EQL filtering](/reference/eql/filtering) for the available query forms. + +## Decrypt a result + +```typescript +const decrypted = await encryption.decrypt(result.rows[0].email) + +if (decrypted.failure) throw decrypted.failure + +console.log(decrypted.data) +``` + +For every exported signature and option, use the [generated API reference](/reference/stack/api-reference). For an end-to-end project setup, follow the [quickstart](/get-started/quickstart). diff --git a/content/docs/reference/workspace/index.mdx b/content/docs/reference/workspace/index.mdx index d4414b5..e2b4693 100644 --- a/content/docs/reference/workspace/index.mdx +++ b/content/docs/reference/workspace/index.mdx @@ -1,5 +1,6 @@ --- title: Workspace & account +navTitle: Overview description: "Workspace configuration, organization and workspace members, and per-workspace billing." type: reference components: [platform] diff --git a/content/docs/security/compliance/index.mdx b/content/docs/security/compliance/index.mdx index 9af190d..8875fd7 100644 --- a/content/docs/security/compliance/index.mdx +++ b/content/docs/security/compliance/index.mdx @@ -1,5 +1,6 @@ --- title: Compliance +navTitle: Overview description: "Compliance documentation — being built as part of the docs V2 overhaul." --- diff --git a/content/docs/security/compliance/meta.json b/content/docs/security/compliance/meta.json index e7c6fa5..346ed94 100644 --- a/content/docs/security/compliance/meta.json +++ b/content/docs/security/compliance/meta.json @@ -1,4 +1,4 @@ { "title": "Compliance", - "pages": ["..."] + "pages": ["index", "..."] } diff --git a/content/docs/solutions/index.mdx b/content/docs/solutions/index.mdx index c04d308..df8c976 100644 --- a/content/docs/solutions/index.mdx +++ b/content/docs/solutions/index.mdx @@ -1,5 +1,6 @@ --- title: Solutions +navTitle: Overview description: "What CipherStash solves: protecting sensitive data, AI and RAG pipelines, data residency, and provable access." type: concept audience: [cto, ciso] diff --git a/content/stack/cipherstash/encryption/bulk-operations.mdx b/content/stack/cipherstash/encryption/bulk-operations.mdx index 5a6d823..550ce58 100644 --- a/content/stack/cipherstash/encryption/bulk-operations.mdx +++ b/content/stack/cipherstash/encryption/bulk-operations.mdx @@ -9,7 +9,7 @@ description: Encrypt and decrypt arrays of raw values in a single ZeroKMS round- This page covers the raw-value variants. If you want to encrypt whole objects (records with multiple fields), see [Model operations](/stack/cipherstash/encryption/models) instead. -For full method signatures, see the [`EncryptionClient` API reference](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient). +For full method signatures, see the [`EncryptionClient` API reference](/reference/stack/api-reference). ## Why bulk matters diff --git a/content/stack/cipherstash/encryption/models.mdx b/content/stack/cipherstash/encryption/models.mdx index 54d30f7..b7287ad 100644 --- a/content/stack/cipherstash/encryption/models.mdx +++ b/content/stack/cipherstash/encryption/models.mdx @@ -9,7 +9,7 @@ Model methods encrypt or decrypt an entire object in one call. The SDK inspects This is the recommended approach when working with database records: pass the object in, get the encrypted (or decrypted) version back, and write it to the database. -For full method signatures, see the [`EncryptionClient` API reference](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient). +For full method signatures, see the [`EncryptionClient` API reference](/reference/stack/api-reference). ## How schema-driven selection works diff --git a/content/stack/cipherstash/kms/oidc.mdx b/content/stack/cipherstash/kms/oidc.mdx index f4ffefc..581aaae 100644 --- a/content/stack/cipherstash/kms/oidc.mdx +++ b/content/stack/cipherstash/kms/oidc.mdx @@ -6,7 +6,7 @@ description: Register an identity provider so end users can authenticate cryptog OIDC Providers let you federate your existing identity provider (IdP) with CipherStash. Once registered, end users can authenticate cryptographic operations using the identity tokens your IdP issues, not just application credentials. -This pairs with [identity-aware encryption](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext) via `LockContext`, which binds encryption operations to a specific user identity. +This pairs with [identity-aware encryption](/reference/stack/api-reference) via `LockContext`, which binds encryption operations to a specific user identity. If you are only encrypting data at rest with application credentials, you do not need an OIDC provider. @@ -61,4 +61,4 @@ No client secret or shared key is stored. CipherStash validates tokens by fetchi ## Using it from code Once a provider is registered, use `LockContext` in the Encryption SDK to bind encryption operations to a user's identity token. -See the [`LockContext` API reference](/stack/reference/stack/latest/packages/stack/src/identity/classes/LockContext) for the full API. +See the [Stack API reference](/reference/stack/api-reference) for the full `LockContext` API. diff --git a/content/stack/reference/encryption-sdk.mdx b/content/stack/reference/encryption-sdk.mdx index 1abfb63..ef036e0 100644 --- a/content/stack/reference/encryption-sdk.mdx +++ b/content/stack/reference/encryption-sdk.mdx @@ -3,18 +3,18 @@ title: Encryption SDK reference description: Public entry points, supported data types, and configuration highlights for @cipherstash/stack field-level encryption. --- -`@cipherstash/stack` is CipherStash's field-level encryption SDK for TypeScript. It encrypts individual column values client-side using per-value keys derived from ZeroKMS (backed by AWS KMS), before data leaves the application. This page summarises the public surface, data type rules, and configuration options. Full type signatures live in the [auto-generated API reference](/stack/reference/stack/latest/packages/stack/src/encryption). +`@cipherstash/stack` is CipherStash's field-level encryption SDK for TypeScript. It encrypts individual column values client-side using per-value keys derived from ZeroKMS (backed by AWS KMS), before data leaves the application. This page summarises the public surface, data type rules, and configuration options. Full type signatures live in the [auto-generated API reference](/reference/stack/api-reference). ## Public entry points | Export | Import path | Purpose | |---|---|---| -| `Encryption(config)` | `@cipherstash/stack` | Factory function. Returns a `Promise`. [Reference](/stack/reference/stack/latest/packages/stack/src/encryption/functions/Encryption) | -| `EncryptionClient` | `@cipherstash/stack/encryption` | Class with all encrypt/decrypt methods. Obtain via `Encryption()`, not `new`. [Reference](/stack/reference/stack/latest/packages/stack/src/encryption/classes/EncryptionClient) | -| `encryptedTable` / `encryptedColumn` / `encryptedField` | `@cipherstash/stack/schema` | Schema builders. Define which tables and columns to encrypt, and which search indexes to create. [Reference](/stack/reference/stack/latest/packages/stack/src/schema) | -| `LockContext` | `@cipherstash/stack/identity` | Identity-aware encryption. Ties an encrypted value to a specific JWT identity. [Reference](/stack/reference/stack/latest/packages/stack/src/identity) | -| `Secrets` | `@cipherstash/stack/secrets` | End-to-end encrypted secret storage. Separate from field-level encryption. [Reference](/stack/reference/stack/latest/packages/stack/src/types-public) | -| Error types | `@cipherstash/stack/errors` | `StackError`, `EncryptionErrorTypes`, `getErrorMessage`. [Reference](/stack/reference/stack/latest/packages/stack/src/types-public) | +| `Encryption(config)` | `@cipherstash/stack` | Factory function. Returns a `Promise`. [Reference](/reference/stack/api-reference) | +| `EncryptionClient` | `@cipherstash/stack/encryption` | Class with all encrypt/decrypt methods. Obtain via `Encryption()`, not `new`. [Reference](/reference/stack/api-reference) | +| `encryptedTable` / `encryptedColumn` / `encryptedField` | `@cipherstash/stack/schema` | Schema builders. Define which tables and columns to encrypt, and which search indexes to create. [Reference](/reference/stack/api-reference) | +| `LockContext` | `@cipherstash/stack/identity` | Identity-aware encryption. Ties an encrypted value to a specific JWT identity. [Reference](/reference/stack/api-reference) | +| `Secrets` | `@cipherstash/stack/secrets` | End-to-end encrypted secret storage. Separate from field-level encryption. [Reference](/reference/stack/api-reference) | +| Error types | `@cipherstash/stack/errors` | `StackError`, `EncryptionErrorTypes`, `getErrorMessage`. [Reference](/reference/stack/api-reference) | ## Adapter packages @@ -75,7 +75,7 @@ Some numeric inputs are invalid for encryption. The following table covers the b | `clientKey` | `CS_CLIENT_KEY` | Client key material used for ZeroKMS encryption operations | | `keyset` | (none) | Multi-tenant isolation. Specify `{ name: "tenant-a" }` or `{ id: "" }` | -See the full type at [EncryptionClientConfig](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/EncryptionClientConfig) and [ClientConfig](/stack/reference/stack/latest/packages/stack/src/types-public/type-aliases/ClientConfig). +See [the generated API reference](/reference/stack/api-reference) for the complete `EncryptionClientConfig` and `ClientConfig` types. ```typescript filename="init.ts" import { Encryption } from "@cipherstash/stack" @@ -110,7 +110,4 @@ Set `STASH_STACK_LOG` to control log verbosity. The SDK never logs plaintext dat Everything else is in the auto-generated TypeDoc reference: -- [Encryption module](/stack/reference/stack/latest/packages/stack/src/encryption) — `Encryption()`, `EncryptionClient` class, all methods -- [Schema module](/stack/reference/stack/latest/packages/stack/src/schema) — `encryptedTable`, `encryptedColumn`, `encryptedField`, type inference helpers -- [Identity module](/stack/reference/stack/latest/packages/stack/src/identity) — `LockContext` -- [Types](/stack/reference/stack/latest/packages/stack/src/types-public) — `EncryptionClientConfig`, `ClientConfig`, `EncryptOptions`, `BulkEncryptPayload`, and more +- [Generated API reference](/reference/stack/api-reference) — encryption, schema, identity, configuration, and error exports diff --git a/content/stack/reference/index.mdx b/content/stack/reference/index.mdx index 5acc9a0..75fd1a7 100644 --- a/content/stack/reference/index.mdx +++ b/content/stack/reference/index.mdx @@ -11,7 +11,7 @@ Auto-generated TypeDoc reference for CipherStash packages. For hand-written guid The core CipherStash SDK. Includes Encryption (`Encryption()`, `EncryptionClient`, schema builders, type inference, Lock Context), Secrets API, and integrations (Drizzle, Supabase, DynamoDB, and more). -[Browse @cipherstash/stack reference](/stack/reference/stack/latest) +[Browse @cipherstash/stack reference](/reference/stack/api-reference) ### EQL API diff --git a/next.config.mjs b/next.config.mjs index efdadca..c526466 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -262,10 +262,10 @@ const config = { destination: "/stack/reference/proxy-reference", permanent: true, }, - // Reference section index → latest + // Legacy generated Stack reference → the V2 API reference { source: "/stack/reference/stack", - destination: "/stack/reference/stack/latest", + destination: "/reference/stack/api-reference", permanent: false, }, // === AI-cited URLs orphaned by the restructure === diff --git a/package.json b/package.json index db688e4..2b9a412 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { - "prebuild": "bun run generate-docs && bun run generate-docs:prisma && bun run generate-docs:eql && bun run generate-docs:eql-api && bun run generate-docs:cli && bun run validate-content && bun run validate-mermaid && bun run validate-links && bun run validate-redirects", + "prebuild": "bun run generate-docs && bun run generate-docs:supabase && bun run generate-docs:prisma && bun run generate-docs:eql && bun run generate-docs:eql-api && bun run generate-docs:cli && bun run validate-content && bun run validate-mermaid && bun run validate-links && bun run validate-redirects", "build": "next build", "dev": "next dev -p 3001", "start": "next start", @@ -12,6 +12,7 @@ "lint": "biome check", "format": "biome format --write", "generate-docs": "tsx scripts/generate-docs.ts", + "generate-docs:supabase": "tsx scripts/generate-supabase-docs.ts", "generate-docs:prisma": "tsx scripts/generate-prisma-docs.ts", "generate-docs:eql": "tsx scripts/generate-eql-docs.ts", "generate-docs:eql-api": "tsx scripts/generate-eql-api-docs.ts", diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index 84c7dcf..05bffbe 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -306,6 +306,7 @@ function renderIndex( const frontmatter = [ "---", "title: CLI", + "navTitle: Overview", `description: "Command reference for the ${CLI_NAME} CLI, generated from v${CLI_VERSION}."`, "type: reference", "components: [cli]", @@ -345,7 +346,7 @@ ${sections} } function renderMeta(manifest: Manifest, groups: Map): string { - const pages: string[] = []; + const pages: string[] = ["index"]; for (const g of manifest.groupOrder) { const groupPages = groups.get(g); if (!groupPages) continue; diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 697624c..98d2c45 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -2,7 +2,7 @@ /** * Main orchestrator for generating TypeDoc API reference documentation. * - * Generates docs for @cipherstash/stack (which includes all integrations). + * Generates docs for the core @cipherstash/stack package. * * Set PROTECT_WORKSPACE_PATH to point to a local protectjs checkout * for development (e.g., /Users/cj/Documents/CipherStash/Github/protectjs). @@ -14,36 +14,45 @@ import { type DocsConfig, generateDocs } from "./lib/docs-generator.js"; const stackConfig: DocsConfig = { packageName: "@cipherstash/stack", + projectName: "@cipherstash/stack", repoUrl: "https://github.com/cipherstash/stack.git", + sourceRef: "main", tempDirName: ".tmp-stack", - baseOutputDir: path.join(process.cwd(), "content/stack/reference/stack"), - // Stack 1.0 split the Drizzle and Supabase adapters into their own packages - // (@cipherstash/stack-drizzle, @cipherstash/stack-supabase) and removed the - // secrets module, so those entry points no longer exist under - // packages/stack/src. - // - // `stack-supabase` is documented from its new home. It is a separate npm - // package but the same reference surface to a reader, and without it nothing - // in the Supabase adapter — `encryptedSupabaseV3`, the query builder, the - // EQL-version constraints on its encrypted operators — reaches the generated - // reference at all. + baseOutputDir: path.join( + process.cwd(), + "content/docs/reference/stack/api-reference", + ), + publicPath: "/reference/stack/api-reference", + metaTitle: "API reference", + versionedOutput: false, + entryPointBasePath: "packages/stack/src", + router: "module", + flattenOutputFiles: true, + generatedSources: { + "packages/stack/src/package-exports.ts": + '/** Exports available from the `@cipherstash/stack` package root.\n * @module Package exports\n */\nexport * from "./index.js"\n', + }, entryPoints: [ + "./packages/stack/src/package-exports.ts", "./packages/stack/src/encryption/index.ts", "./packages/stack/src/schema/index.ts", + "./packages/stack/src/eql/v3/index.ts", + "./packages/stack/src/encryption/v3.ts", "./packages/stack/src/dynamodb/index.ts", "./packages/stack/src/identity/index.ts", "./packages/stack/src/types-public.ts", - "./packages/stack/src/client.ts", "./packages/stack/src/errors/index.ts", - "./packages/stack-supabase/src/index.ts", + "./packages/stack/src/adapter-kit.ts", + "./packages/stack/src/wasm-inline.ts", ], - tsconfigInclude: [ - "packages/stack/src/**/*", - "packages/stack-supabase/src/**/*", - ], - tagFilter: (tag: string) => - tag.includes("@cipherstash/stack@") && !tag.includes("stack-"), + tsconfigInclude: ["packages/stack/src/**/*"], + tagFilter: () => false, referencePathSegment: "stack", + frontmatterGlobals: { + type: "reference", + components: ["encryption", "eql"], + audience: ["developer"], + }, }; async function main() { diff --git a/scripts/generate-supabase-docs.ts b/scripts/generate-supabase-docs.ts new file mode 100644 index 0000000..a25c917 --- /dev/null +++ b/scripts/generate-supabase-docs.ts @@ -0,0 +1,37 @@ +#!/usr/bin/env tsx +/** Generate the Supabase integration's API reference from Stack main. */ +import path from "node:path"; +import { type DocsConfig, generateDocs } from "./lib/docs-generator.js"; + +const supabaseConfig: DocsConfig = { + packageName: "@cipherstash/stack-supabase", + projectName: "@cipherstash/stack-supabase", + repoUrl: "https://github.com/cipherstash/stack.git", + sourceRef: "main", + tempDirName: ".tmp-stack-supabase", + baseOutputDir: path.join( + process.cwd(), + "content/docs/integrations/supabase/api-reference", + ), + publicPath: "/integrations/supabase/api-reference", + metaTitle: "API reference", + versionedOutput: false, + entryPointBasePath: "packages/stack-supabase/src", + entryModule: "index", + router: "module", + flattenOutputFiles: true, + entryPoints: ["./packages/stack-supabase/src/index.ts"], + tsconfigInclude: ["packages/stack-supabase/src/**/*"], + tagFilter: () => false, + referencePathSegment: "supabase", + frontmatterGlobals: { + type: "reference", + components: ["encryption", "eql", "platform"], + audience: ["developer"], + }, +}; + +generateDocs(supabaseConfig).catch((error) => { + console.error("Failed to generate the Supabase API reference:", error); + process.exit(1); +}); diff --git a/scripts/lib/docs-generator.ts b/scripts/lib/docs-generator.ts index ef29cdc..ce45804 100644 --- a/scripts/lib/docs-generator.ts +++ b/scripts/lib/docs-generator.ts @@ -40,6 +40,10 @@ export interface DocsConfig { router?: "member" | "module"; /** Write generated module pages into one directory. */ flattenOutputFiles?: boolean; + /** Promote one TypeDoc entry module into the generated root page. */ + entryModule?: string; + /** Synthetic source entry points written into the temporary checkout. */ + generatedSources?: Record; } /** @@ -213,11 +217,16 @@ export async function generateMetaJson(dir: string): Promise { } const metaPath = path.join(dir, "meta.json"); - await fs.writeFile( - metaPath, - `${JSON.stringify({ pages }, null, 2)}\n`, - "utf8", + await fs.writeFile(metaPath, serializeMetaJson({ pages }), "utf8"); +} + +/** Keep generated metadata aligned with Biome's compact single-item arrays. */ +function serializeMetaJson(meta: { title?: string; pages: string[] }): string { + const json = JSON.stringify(meta, null, 2).replace( + /"pages": \[\n {4}"([^"]+)"\n {2}\]/, + '"pages": ["$1"]', ); + return `${json}\n`; } /** @@ -375,6 +384,17 @@ export async function generateDocsForTag( // Create output directory await fs.mkdir(outputDir, { recursive: true }); + // A package root named `index.ts` collides with TypeDoc's own `index.mdx`. + // Callers can add a synthetic, clearly named re-export module so the root + // package surface is documented without changing the source repository. + for (const [relativePath, source] of Object.entries( + config.generatedSources ?? {}, + )) { + const generatedPath = path.join(workingDir, relativePath); + await fs.mkdir(path.dirname(generatedPath), { recursive: true }); + await fs.writeFile(generatedPath, source, "utf8"); + } + // Create a custom tsconfig for TypeDoc const typedocTsConfig = { extends: "./tsconfig.json", @@ -510,6 +530,7 @@ export async function generateDocsForTag( entryFileName: "index", router: config.router, flattenOutputFiles: config.flattenOutputFiles, + entryModule: config.entryModule, // Type errors fail the build, deliberately. This was `true` to tolerate // "cross-package type references unresolved even though the source is // correct" — but that diagnosis was wrong, and tolerating it was not free: @@ -683,11 +704,7 @@ export async function generateDocs(config: DocsConfig): Promise { ...(config.metaTitle ? { title: config.metaTitle } : {}), pages: generatedVersions.map(({ dirName }) => dirName), }; - await fs.writeFile( - packageMetaPath, - `${JSON.stringify(packageMeta, null, 2)}\n`, - "utf8", - ); + await fs.writeFile(packageMetaPath, serializeMetaJson(packageMeta), "utf8"); // Clean up temp directory console.log("\nCleaning up..."); diff --git a/scripts/plugins/fumadocs-frontmatter.mjs b/scripts/plugins/fumadocs-frontmatter.mjs index 30dbc46..730c3ab 100644 --- a/scripts/plugins/fumadocs-frontmatter.mjs +++ b/scripts/plugins/fumadocs-frontmatter.mjs @@ -113,6 +113,7 @@ export function load(app) { page.frontmatter = { ...meta, + ...(isRoot ? { navTitle: "Overview" } : {}), // Author-provided frontmatter (e.g. @description comment tags, // frontmatterGlobals) still wins. ...page.frontmatter, diff --git a/scripts/validate-content-api.ts b/scripts/validate-content-api.ts index a421360..56fff7c 100644 --- a/scripts/validate-content-api.ts +++ b/scripts/validate-content-api.ts @@ -35,7 +35,8 @@ import path from "node:path"; /** Generated by scripts/*, or a version reference that documents old syntax on purpose. */ const SKIP_PATHS = [ "content/stack/reference/stack/", // TypeDoc output - "content/docs/reference/stack/", // TypeDoc output (post-repoint) + "content/docs/reference/stack/api-reference/", // TypeDoc output + "content/docs/integrations/supabase/api-reference/", // TypeDoc output "content/docs/integrations/prisma/api-reference/", // TypeDoc output "content/stack/reference/eql/index.mdx", // generated EQL API reference "content/docs/reference/eql/functions.mdx", // generated @@ -218,7 +219,7 @@ if (findings.length > 0) { // Say what was actually checked. This gate is a DENYLIST: it can only catch the // APIs listed in RULES, so an unqualified "no non-existent API found" overstates -// it — and did, while `content/docs/reference/stack/supabase.mdx` taught +// it — and did, while the Supabase wrapper reference taught // `.contains()` for encrypted free-text, which raises. Reporting the rule count // makes the coverage legible at a glance, so the next reader can tell the // difference between "verified" and "matched nothing on a short list". diff --git a/scripts/validate-links.ts b/scripts/validate-links.ts index c33b356..1ba7014 100644 --- a/scripts/validate-links.ts +++ b/scripts/validate-links.ts @@ -359,7 +359,8 @@ allBroken.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line); * spurious errors imply the docs are falling apart. */ const GENERATED = [ - "content/stack/reference/stack/latest", + "content/docs/reference/stack/api-reference/index.mdx", + "content/docs/integrations/supabase/api-reference/index.mdx", "content/docs/integrations/prisma/api-reference/index.mdx", ]; const missing = GENERATED.filter((d) => !fs.existsSync(path.join(ROOT, d))); diff --git a/v2-redirects.mjs b/v2-redirects.mjs index 5c93da1..34258f8 100644 --- a/v2-redirects.mjs +++ b/v2-redirects.mjs @@ -68,7 +68,7 @@ export const v2Redirects = [ }, { source: "/stack/cipherstash/encryption/supabase", - destination: "/reference/stack/supabase", + destination: "/integrations/supabase/api-reference", permanent: false, }, { @@ -377,7 +377,7 @@ export const v2Redirects = [ // Generated TypeDoc API reference (scripts/generate-docs.ts output) { source: "/stack/reference/stack/:path*", - destination: "/reference/stack/:path*", + destination: "/reference/stack/api-reference", permanent: false, }, ];