You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -10,7 +10,7 @@ Dev-time CLI and library for managing [CipherStash EQL](https://github.com/ciphe
10
10
11
11
## Why stack-forge?
12
12
13
-
`@cipherstash/stack` is the runtime encryption SDK — it should stay lean and free of heavy dependencies like `pg`. `@cipherstash/stack-forge` is a **devDependency** that handles database tooling: installing EQL extensions, checking permissions, and managing schema lifecycle.
13
+
`@cipherstash/stack` is the runtime encryption SDK — it should stay lean and free of heavy dependencies like `pg`. `@cipherstash/stack-forge` is a **devDependency** that handles database tooling: installing EQL extensions, checking permissions, validating schemas, and managing schema lifecycle.
14
14
15
15
Think of it like Prisma or Drizzle Kit — a companion CLI that sets up the database while the main SDK handles runtime operations.
16
16
@@ -30,9 +30,32 @@ bun add -D @cipherstash/stack-forge
30
30
31
31
## Quick Start
32
32
33
-
You can install EQL in two ways: **direct install** (connects to the DB and runs the SQL) or **Drizzle migration** (generates a migration file; you run `drizzle-kit migrate` yourself). The steps below use the direct install path.
33
+
The fastest way to get started is with the interactive `init` command:
34
34
35
-
### 1. Create a config file
35
+
```bash
36
+
npx stash-forge init
37
+
```
38
+
39
+
This will:
40
+
1. Check if `@cipherstash/stack` is installed and offer to install it
41
+
2. Ask for your database URL
42
+
3. Ask which integration you're using (Drizzle, Supabase, or plain PostgreSQL)
43
+
4. Let you build an encryption schema interactively or use a placeholder
44
+
5. Generate `stash.config.ts` and your encryption client file
45
+
46
+
Then install EQL in your database:
47
+
48
+
```bash
49
+
npx stash-forge install
50
+
```
51
+
52
+
That's it. EQL is now installed and your encryption schema is ready.
If your encryption client lives elsewhere, set `client` in `stash.config.ts` (e.g. `client: './lib/encryption.ts'`). That path is used by `stash-forge push`.
62
-
63
82
**Using Drizzle?** To install EQL via your migration pipeline instead, run `npx stash-forge install --drizzle`, then `npx drizzle-kit migrate`. See [install --drizzle](#install---drizzle) below.
64
83
65
84
---
@@ -76,21 +95,15 @@ export default defineConfig({
76
95
databaseUrl: process.env.DATABASE_URL!,
77
96
78
97
// Optional: path to your encryption client (default: './src/encryption/index.ts')
79
-
// Used by `stash-forge push` to load the encryption schema
98
+
// Used by `stash-forge push` and `stash-forge validate` to load the encryption schema
80
99
client: './src/encryption/index.ts',
81
-
82
-
// Optional: CipherStash workspace and credentials (for future schema sync)
|`client`| No | Path to encryption client file (default: `'./src/encryption/index.ts'`). Used by `push` to load the encryption schema. |
92
-
|`workspaceId`| No | CipherStash workspace ID |
93
-
|`clientAccessKey`| No | CipherStash client access key |
106
+
|`client`| No | Path to encryption client file (default: `'./src/encryption/index.ts'`). Used by `push` and `validate` to load the encryption schema. |
94
107
95
108
The CLI automatically loads `.env` files before evaluating the config, so `process.env` references work out of the box.
96
109
@@ -104,9 +117,29 @@ The config file is resolved by walking up from the current working directory, si
104
117
stash-forge <command> [options]
105
118
```
106
119
120
+
### `init`
121
+
122
+
Initialize CipherStash Forge in your project with an interactive wizard.
123
+
124
+
```bash
125
+
npx stash-forge init
126
+
```
127
+
128
+
The wizard will:
129
+
- Check if `@cipherstash/stack` is installed and prompt to install it (detects your package manager automatically)
130
+
- Ask for your database URL (pre-fills from `DATABASE_URL` env var)
131
+
- Ask which integration you're using (Drizzle ORM, Supabase, or plain PostgreSQL)
132
+
- Ask where to create the encryption client file
133
+
- If the client file already exists, ask whether to keep it or overwrite
134
+
- Let you choose between building a schema interactively or using a placeholder:
135
+
-**Build a schema:** asks for table name, column names, data types, and search operations for each column
136
+
-**Placeholder:** generates an example `users` table with `email` and `name` columns
137
+
- Generate `stash.config.ts` and the encryption client file
138
+
- Print next steps with links to the [CipherStash dashboard](https://dashboard.cipherstash.com/sign-in) for credentials
139
+
107
140
### `install`
108
141
109
-
Install the CipherStash EQL extensions into your database.
142
+
Install the CipherStash EQL extensions into your database. Uses bundled SQL by default for offline, deterministic installs.
-Downloads the Supabase-specific SQL variant (no `CREATE OPERATOR FAMILY`)
172
+
-Uses the Supabase-specific SQL variant (no `CREATE OPERATOR FAMILY`)
139
173
- Grants `USAGE`, table, routine, and sequence permissions on the `eql_v2` schema to `anon`, `authenticated`, and `service_role`
140
174
175
+
> **Note:** Without operator families, `ORDER BY` on encrypted columns is not currently supported — regardless of the client or ORM used. Sort application-side after decrypting the results as a workaround. Operator family support for Supabase is being developed with the Supabase and CipherStash teams. This limitation also applies when using `--exclude-operator-family` on any database.
176
+
141
177
**Preview changes first:**
142
178
143
179
```bash
144
180
npx stash-forge install --dry-run
145
181
```
146
182
183
+
**Fetch the latest EQL from GitHub instead of using the bundled version:**
184
+
185
+
```bash
186
+
npx stash-forge install --latest
187
+
```
188
+
147
189
#### `install --drizzle`
148
190
149
191
If you use [Drizzle ORM](https://orm.drizzle.team/) and want EQL installation as part of your migration history, use the `--drizzle` flag. It creates a Drizzle migration file containing the EQL install SQL, then you run your normal Drizzle migrations to apply it.
@@ -156,7 +198,7 @@ npx drizzle-kit migrate
156
198
**How it works:**
157
199
158
200
1. Runs `drizzle-kit generate --custom --name=<name>` to create an empty migration.
159
-
2.Downloads the EQL install script from the [EQL GitHub releases](https://github.com/cipherstash/encrypt-query-language/releases/latest).
201
+
2.Loads the bundled EQL install SQL (or downloads from GitHub with `--latest`).
160
202
3. Writes the EQL SQL into the generated migration file.
161
203
162
204
With a custom migration name or output directory:
@@ -168,9 +210,66 @@ npx drizzle-kit migrate
168
210
169
211
You need `drizzle-kit` installed in your project (`npm install -D drizzle-kit`). The `--out` directory must match your Drizzle config (e.g. `drizzle.config.ts`).
170
212
213
+
### `upgrade`
214
+
215
+
Upgrade an existing EQL installation to the version bundled with the package (or the latest from GitHub).
216
+
217
+
```bash
218
+
npx stash-forge upgrade [options]
219
+
```
220
+
221
+
| Option | Description |
222
+
|--------|-------------|
223
+
|`--dry-run`| Show what would happen without making changes |
224
+
|`--supabase`| Use Supabase-compatible upgrade |
225
+
|`--exclude-operator-family`| Skip operator family creation |
226
+
|`--latest`| Fetch the latest EQL from GitHub instead of using the bundled version |
227
+
228
+
The EQL install SQL is idempotent and safe to re-run. The upgrade command checks the current version, re-runs the install SQL, then reports the new version.
229
+
230
+
```bash
231
+
npx stash-forge upgrade
232
+
```
233
+
234
+
If EQL is not installed, the command suggests running `stash-forge install` instead.
235
+
236
+
### `validate`
237
+
238
+
Validate your encryption schema for common misconfigurations.
239
+
240
+
```bash
241
+
npx stash-forge validate [options]
242
+
```
243
+
244
+
| Option | Description |
245
+
|--------|-------------|
246
+
|`--supabase`| Check for Supabase-specific issues (e.g. ORDER BY without operator families) |
247
+
|`--exclude-operator-family`| Check for issues when operator families are excluded |
248
+
249
+
**Validation rules:**
250
+
251
+
| Rule | Severity | Description |
252
+
|------|----------|-------------|
253
+
|`freeTextSearch` on non-string column | Warning | Free-text search only works with string data |
254
+
|`orderAndRange` without operator families | Warning | ORDER BY won't work without operator families |
255
+
| No indexes on encrypted column | Info | Column is encrypted but not searchable |
256
+
|`searchableJson` without `json` data type | Error | searchableJson requires `dataType("json")`|
257
+
258
+
```bash
259
+
# Basic validation
260
+
npx stash-forge validate
261
+
262
+
# Validate with Supabase context
263
+
npx stash-forge validate --supabase
264
+
```
265
+
266
+
Validation is also automatically run before `push` — issues are logged as warnings but don't block the push.
267
+
268
+
The command exits with code 1 if there are errors (not for warnings or info).
269
+
171
270
### `push`
172
271
173
-
Load your encryption schema from the file specified by `client` in `stash.config.ts` and apply it to the database (or preview with `--dry-run`).
272
+
Push your encryption schema to the database. **This is only required when using CipherStash Proxy.** If you're using the SDK directly (Drizzle, Supabase, or plain PostgreSQL), this step is not needed — the schema lives in your application code.
|`--dry-run`| Load and validate the schema, then print it as JSON. No database changes. |
182
281
183
-
**Push schema to the database:**
282
+
When pushing, stash-forge:
283
+
1. Loads the encryption client from the path in `stash.config.ts`
284
+
2. Runs schema validation (warns but doesn't block)
285
+
3. Transforms SDK data types to EQL-compatible `cast_as` values (see table below)
286
+
4. Connects to Postgres and marks existing `eql_v2_configuration` rows as `inactive`
287
+
5. Inserts the new config as an `active` row
288
+
289
+
**SDK to EQL type mapping:**
290
+
291
+
The SDK uses developer-friendly type names (e.g. `'string'`, `'number'`), but EQL expects PostgreSQL-aligned types. The `push` command automatically maps these before writing to the database:
292
+
293
+
| SDK type (`dataType()`) | EQL `cast_as`|
294
+
|-------------------------|---------------|
295
+
|`string`|`text`|
296
+
|`text`|`text`|
297
+
|`number`|`double`|
298
+
|`bigint`|`big_int`|
299
+
|`boolean`|`boolean`|
300
+
|`date`|`date`|
301
+
|`json`|`jsonb`|
302
+
303
+
### `status`
304
+
305
+
Show the current state of EQL in your database.
184
306
185
307
```bash
186
-
npx stash-forge push
308
+
npx stash-forge status
187
309
```
188
310
189
-
This connects to Postgres, marks any existing rows in `eql_v2_configuration` as `inactive`, and inserts the current encrypt config as a new row with state `active`. Your runtime encryption (e.g. `@cipherstash/stack`) reads the active configuration from this table.
311
+
Reports:
312
+
- Whether EQL is installed and which version
313
+
- Database permission status
314
+
- Whether an active encrypt config exists in `eql_v2_configuration` (only relevant for CipherStash Proxy)
315
+
316
+
### `test-connection`
190
317
191
-
**Preview your encryption schema without writing to the database:**
318
+
Verify that the database URL in your config is valid and the database is reachable.
192
319
193
320
```bash
194
-
npx stash-forge push --dry-run
321
+
npx stash-forge test-connection
195
322
```
196
323
324
+
Reports the database name, connected user/role, and PostgreSQL server version. Useful for debugging connection issues before running `install` or `push`.
325
+
197
326
### Permission Pre-checks (install)
198
327
199
328
Before installing, `stash-forge` verifies that the connected database role has the required permissions:
@@ -206,13 +335,23 @@ If permissions are insufficient, the CLI exits with a clear message listing what
206
335
207
336
### Planned Commands
208
337
209
-
The following commands are defined but not yet implemented:
210
-
211
338
| Command | Description |
212
339
|---------|-------------|
213
-
|`init`| Initialize CipherStash Forge in your project |
214
340
|`migrate`| Run pending encrypt config migrations |
215
-
|`status`| Show EQL installation status |
341
+
342
+
---
343
+
344
+
## Bundled EQL SQL
345
+
346
+
The EQL install SQL is bundled with the package for offline, deterministic installs. Three variants are included:
347
+
348
+
| File | Used when |
349
+
|------|-----------|
350
+
|`cipherstash-encrypt.sql`| Default install |
351
+
|`cipherstash-encrypt-supabase.sql`|`--supabase` flag |
352
+
|`cipherstash-encrypt-no-operator-family.sql`|`--exclude-operator-family` flag |
353
+
354
+
The bundled SQL version is pinned to the package version. Use `--latest` to fetch the newest version from GitHub instead.
216
355
217
356
---
218
357
@@ -253,17 +392,41 @@ if (await installer.isInstalled()) {
253
392
|`checkPermissions()`|`Promise<PermissionCheckResult>`| Check if the database role has required permissions |
254
393
|`isInstalled()`|`Promise<boolean>`| Check if the `eql_v2` schema exists |
255
394
|`getInstalledVersion()`|`Promise<string \| null>`| Get the installed EQL version (or `null`) |
256
-
|`install(options?)`|`Promise<void>`|Download and execute the EQL install SQL in a transaction |
395
+
|`install(options?)`|`Promise<void>`|Execute the EQL install SQL in a transaction |
257
396
258
397
#### Install Options
259
398
260
399
```typescript
261
400
awaitinstaller.install({
262
401
excludeOperatorFamily: true, // Skip CREATE OPERATOR FAMILY
0 commit comments