@cipherstash/stack 0.16.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +225 -0
  2. package/README.md +2 -2
  3. package/dist/{chunk-CY5B4LIF.js → chunk-36AA7IBJ.js} +303 -204
  4. package/dist/chunk-36AA7IBJ.js.map +1 -0
  5. package/dist/{chunk-MD6742R6.js → chunk-4AVL4VZD.js} +1 -1
  6. package/dist/chunk-4AVL4VZD.js.map +1 -0
  7. package/dist/{chunk-4RNBI3UH.js → chunk-BNO32ZMV.js} +1 -1
  8. package/dist/chunk-BNO32ZMV.js.map +1 -0
  9. package/dist/{chunk-6FD7OBGM.js → chunk-HQANMV7R.js} +3 -3
  10. package/dist/chunk-HQANMV7R.js.map +1 -0
  11. package/dist/chunk-IADZCZEA.js +23 -0
  12. package/dist/chunk-IADZCZEA.js.map +1 -0
  13. package/dist/chunk-IBSK6P33.js +209 -0
  14. package/dist/chunk-IBSK6P33.js.map +1 -0
  15. package/dist/chunk-JSG2AMDI.js +17 -0
  16. package/dist/chunk-JSG2AMDI.js.map +1 -0
  17. package/dist/chunk-MP3SSDNN.js +4439 -0
  18. package/dist/chunk-MP3SSDNN.js.map +1 -0
  19. package/dist/chunk-OFQ555AX.js +191 -0
  20. package/dist/chunk-OFQ555AX.js.map +1 -0
  21. package/dist/chunk-PZ5AY32C.js +10 -0
  22. package/dist/chunk-PZ5AY32C.js.map +1 -0
  23. package/dist/chunk-U66S7VIF.js +435 -0
  24. package/dist/chunk-U66S7VIF.js.map +1 -0
  25. package/dist/{client-C_7TF8EL.d.cts → client-DSGHBN-g.d.cts} +209 -72
  26. package/dist/{client-BLbA6aFD.d.ts → client-DfCrlHXh.d.ts} +209 -72
  27. package/dist/client.cjs +4103 -43
  28. package/dist/client.cjs.map +1 -1
  29. package/dist/client.d.cts +5 -5
  30. package/dist/client.d.ts +5 -5
  31. package/dist/client.js +3 -1
  32. package/dist/drizzle/index.cjs +4139 -77
  33. package/dist/drizzle/index.cjs.map +1 -1
  34. package/dist/drizzle/index.d.cts +33 -33
  35. package/dist/drizzle/index.d.ts +33 -33
  36. package/dist/drizzle/index.js +39 -33
  37. package/dist/drizzle/index.js.map +1 -1
  38. package/dist/dynamodb/index.cjs +31 -14
  39. package/dist/dynamodb/index.cjs.map +1 -1
  40. package/dist/dynamodb/index.d.cts +12 -12
  41. package/dist/dynamodb/index.d.ts +12 -12
  42. package/dist/dynamodb/index.js +16 -12
  43. package/dist/dynamodb/index.js.map +1 -1
  44. package/dist/encryption/index.cjs +4611 -257
  45. package/dist/encryption/index.cjs.map +1 -1
  46. package/dist/encryption/index.d.cts +3 -3
  47. package/dist/encryption/index.d.ts +3 -3
  48. package/dist/encryption/index.js +35 -7
  49. package/dist/encryption/v3.cjs +7791 -0
  50. package/dist/encryption/v3.cjs.map +1 -0
  51. package/dist/encryption/v3.d.cts +100 -0
  52. package/dist/encryption/v3.d.ts +100 -0
  53. package/dist/encryption/v3.js +161 -0
  54. package/dist/encryption/v3.js.map +1 -0
  55. package/dist/eql/v3/index.cjs +612 -0
  56. package/dist/eql/v3/index.cjs.map +1 -0
  57. package/dist/eql/v3/index.d.cts +71 -0
  58. package/dist/eql/v3/index.d.ts +71 -0
  59. package/dist/eql/v3/index.js +89 -0
  60. package/dist/eql/v3/index.js.map +1 -0
  61. package/dist/errors/index.js +1 -0
  62. package/dist/identity/index.cjs +55 -35
  63. package/dist/identity/index.cjs.map +1 -1
  64. package/dist/identity/index.d.cts +54 -30
  65. package/dist/identity/index.d.ts +54 -30
  66. package/dist/identity/index.js +9 -177
  67. package/dist/identity/index.js.map +1 -1
  68. package/dist/index.cjs +4625 -273
  69. package/dist/index.cjs.map +1 -1
  70. package/dist/index.d.cts +18 -7
  71. package/dist/index.d.ts +18 -7
  72. package/dist/index.js +20 -5
  73. package/dist/index.js.map +1 -1
  74. package/dist/schema/index.cjs +4108 -44
  75. package/dist/schema/index.cjs.map +1 -1
  76. package/dist/schema/index.d.cts +1 -1
  77. package/dist/schema/index.d.ts +1 -1
  78. package/dist/schema/index.js +3 -1
  79. package/dist/supabase/index.cjs +4500 -75
  80. package/dist/supabase/index.cjs.map +1 -1
  81. package/dist/supabase/index.d.cts +113 -34
  82. package/dist/supabase/index.d.ts +113 -34
  83. package/dist/supabase/index.js +350 -32
  84. package/dist/supabase/index.js.map +1 -1
  85. package/dist/table-CIH7jZ2h.d.ts +603 -0
  86. package/dist/table-DihEAlxG.d.cts +603 -0
  87. package/dist/{types-public-Dv-r6Ik0.d.ts → types-public-CpS5KjwX.d.cts} +209 -36
  88. package/dist/{types-public-Dv-r6Ik0.d.cts → types-public-CpS5KjwX.d.ts} +209 -36
  89. package/dist/types-public.cjs.map +1 -1
  90. package/dist/types-public.d.cts +2 -2
  91. package/dist/types-public.d.ts +2 -2
  92. package/dist/types-public.js +2 -1
  93. package/dist/wasm-inline.d.ts +1269 -0
  94. package/dist/wasm-inline.js +4594 -0
  95. package/dist/wasm-inline.js.map +1 -0
  96. package/package.json +63 -13
  97. package/dist/chunk-4RNBI3UH.js.map +0 -1
  98. package/dist/chunk-6FD7OBGM.js.map +0 -1
  99. package/dist/chunk-CY5B4LIF.js.map +0 -1
  100. package/dist/chunk-JDZ2QUUF.js +0 -381
  101. package/dist/chunk-JDZ2QUUF.js.map +0 -1
  102. package/dist/chunk-MD6742R6.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,230 @@
1
1
  # @cipherstash/stack
2
2
 
3
+ ## 0.19.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cc62407: Add EQL v3 Supabase support, baselined on the `eql-3.0.0-alpha.2` release.
8
+
9
+ `@cipherstash/stack/supabase` gains `encryptedSupabaseV3` — the EQL v3
10
+ counterpart of `encryptedSupabase` for schemas authored with
11
+ `@cipherstash/stack/eql/v3`. The public surface and call shape are identical
12
+ to v2 (same filter methods, `withLockContext`, `audit`); only the schema type
13
+ and wire encoding differ.
14
+
15
+ **The v3 surface** is the `eql-3.0.0-alpha.2` release artifact: domains use
16
+ SQL-standard type names (`eql_v3.integer_ord`, `eql_v3.timestamp_ord`,
17
+ `eql_v3.boolean`, … mirrored by `types.IntegerOrd`, `types.TimestampOrd`,
18
+ `types.Boolean`, …), SEM internals live in a separate `eql_v3_internal`
19
+ schema (grant it roles, never expose it — only `eql_v3` goes in Supabase's
20
+ Exposed schemas), and envelopes are versioned `v: 3`. Envelope production
21
+ rides on `@cipherstash/protect-ffi` 0.27, which takes an `eqlVersion` so the
22
+ same client emits v2 or v3 payloads per schema.
23
+
24
+ **Adapter behaviour:**
25
+
26
+ - columns are stored in their native `eql_v3.*` domains (raw jsonb payloads,
27
+ no composite wrap), with JS property → DB column name resolution and `Date`
28
+ reconstruction from `cast_as` on decrypted rows;
29
+ - **INTERIM:** filter operands are full storage envelopes — every `eql_v3.*`
30
+ domain CHECK requires the storage keys, and the SQL operators coerce their
31
+ operand into the domain, so a term-only operand is rejected today. This is
32
+ a tracked workaround (Linear CIP-3402), not the design: a full-envelope
33
+ operand carries a real decryptable ciphertext plus all of the column's
34
+ index terms, and PostgREST filters travel in GET query strings, so operands
35
+ can land in URL logs, proxies, and Supabase request logs (query terms are
36
+ index-terms-only by design). The fix is an EQL-side term-only scalar query
37
+ envelope (the scalar analog of `eql_v3.jsonb_query`);
38
+ - `like`/`ilike` on encrypted columns are emitted as PostgREST `cs`
39
+ (bloom-filter `@>`) — the v3 domains define no LIKE operator. Substring
40
+ search currently also requires `include_original: false` on the match
41
+ index; that requirement is a symptom of the same interim full-envelope
42
+ operand and goes away with CIP-3402;
43
+ - filters on storage-only columns (e.g. `types.Boolean`) and null filter
44
+ values are rejected at the type level and at runtime.
45
+
46
+ The v3 builder's default row type is exactly the table's inferred plaintext
47
+ shape (no index-signature widening — widening would disable the storage-only
48
+ filter guard). Filtering or inserting plaintext passthrough columns requires
49
+ an explicit row type: `es.from<typeof users, UserRow>('users', users)`.
50
+
51
+ The CLI gains an EQL v3 path: `stash eql install --eql-version 3` installs the
52
+ vendored `eql-3.0.0-alpha.2` bundle (`--supabase` selects the opclass-stripped
53
+ variant and applies the role grants for both `eql_v3` and `eql_v3_internal`);
54
+ `stash db upgrade` also accepts `--eql-version`, and `stash db status` reports
55
+ v2 and v3 installs independently. The v2 `SUPABASE_PERMISSIONS_SQL` block is
56
+ now generated from a shared `supabasePermissionsSql(schemaName)` helper, with
57
+ `SUPABASE_PERMISSIONS_SQL_V3` covering the v3 schemas.
58
+
59
+ - 5e4f354: Add the EQL v3 `text_search` authoring DSL on a new `@cipherstash/stack/eql/v3`
60
+ subpath (`types.TextSearch`, v3 `encryptedTable` / `buildEncryptConfig`). The v3
61
+ builders emit the existing `EncryptConfig` shape, so encryption, payloads, and
62
+ query paths are unchanged at runtime.
63
+
64
+ Also widens the public client types (`EncryptionClientConfig.schemas`,
65
+ `EncryptOptions`, `SearchTerm`/`EncryptQueryOptions`) to a structural contract so
66
+ both v2 and v3 builders are accepted by `Encryption` / `encrypt` / `decrypt` /
67
+ `encryptQuery`. This is a backward-compatible widening — existing v2 usage is
68
+ unaffected. The structural contracts themselves (`BuildableColumn`,
69
+ `BuildableQueryColumn`, `BuildableV3QueryableColumn`, `BuildableTable`,
70
+ `BuildableTableColumns`) and the `encryptModel` return-type mapper
71
+ (`EncryptedFromBuildableTable`) are exported from `@cipherstash/stack/types` so
72
+ consumers can name them.
73
+
74
+ - 4ceefed: Add a strongly-typed EQL v3 client surface on a new `@cipherstash/stack/v3`
75
+ subpath (`EncryptionV3`, `typedClient`, `TypedEncryptionClient`). It re-exports
76
+ the v3 `types` namespace and table API (from `@cipherstash/stack/eql/v3`), so a
77
+ single import provides everything needed to author and use a v3 schema.
78
+
79
+ Every method derives its types from the concrete `table` / `column` builder
80
+ arguments:
81
+
82
+ - `encrypt` / `encryptQuery` pin the plaintext to the column's domain type
83
+ (`text → string`, `int8 → bigint`, `timestamptz → Date`, …).
84
+ - `encryptQuery` constrains `queryType` to the column's capabilities and rejects
85
+ storage-only columns at compile time.
86
+ - `encryptModel` / `bulkEncryptModels` validate schema-column fields against their
87
+ inferred plaintext type (passthrough fields are untouched) and return a precise
88
+ encrypted model.
89
+ - `decryptModel` / `bulkDecryptModels` return the precise plaintext model,
90
+ reconstructing `Date` / `bigint` values from the encrypt-config `cast_as`.
91
+
92
+ Because the typed methods bind to the concrete branded v3 classes, a hand-rolled
93
+ structural table/column is rejected — closing the soundness gap where a non-branded
94
+ table could be encrypted at runtime while typed as plaintext.
95
+
96
+ Runtime behaviour is unchanged: the encrypt/query paths return the same operations
97
+ as the base client; only the model-decrypt paths add a per-column `Date` / `bigint`
98
+ reconstruction step. The v2 client surface (`Encryption`) is untouched.
99
+
100
+ - cb34d71: Add EQL v3 schema builders for all generated SQL domains under `@cipherstash/stack/eql/v3`, exposed as the `types` namespace (one member per EQL v3 domain, e.g. `types.TextEq` / `types.Int4Ord` / `types.Timestamptz`), including explicit query capability metadata (`getQueryCapabilities()` / `isQueryable()`) and v3 table support in model encryption helpers (`encryptModel` / `bulkEncryptModels`).
101
+
102
+ Also widen the accepted plaintext input type for `encrypt` / `encryptQuery` to include `Date` and `bigint` (via the new `Plaintext` type), so v3 `date` / `timestamptz` / `int8` domains can be encrypted and queried with their natural JavaScript values.
103
+
104
+ - 90d19fb: Rename the encryption client's auth strategy config field from `config.strategy` to **`config.authStrategy`** to make its purpose clear, and expand the `Encryption()` TypeDoc with a full authentication and keysets walkthrough.
105
+
106
+ **`config.authStrategy`** is the new, documented field for supplying an auth strategy (`OidcFederationStrategy`, `AccessKeyStrategy`, or any `{ getToken() }` object). **`config.strategy` is retained as a deprecated alias** — passing it still works and forwards to the client, but logs a one-time runtime deprecation warning. When both are set, `authStrategy` wins (and the deprecation warning still fires so the leftover field gets cleaned up).
107
+
108
+ ```ts
109
+ import { Encryption, OidcFederationStrategy } from "@cipherstash/stack";
110
+
111
+ const client = await Encryption({
112
+ schemas: [users],
113
+ config: {
114
+ authStrategy: OidcFederationStrategy.create(workspaceCrn, () =>
115
+ getUserJwt()
116
+ ),
117
+ },
118
+ });
119
+ ```
120
+
121
+ **Migration:** rename `config.strategy` → `config.authStrategy`. No behavioural change beyond the deprecation warning; the field is forwarded to protect-ffi's `strategy` option exactly as before.
122
+
123
+ The `Encryption()` TypeDoc now documents the default `auto` strategy (env vars → local dev profile via `npx stash auth login`), the four `CS_*` production/CI variables, custom strategies (`AccessKeyStrategy`, `OidcFederationStrategy`), lock context, and keysets for multi-tenant isolation.
124
+
125
+ The `@cipherstash/stack/wasm-inline` entry (Deno / Edge / Workers / Bun) gets the same rename so the Node and WASM interfaces stay in sync: `WasmClientConfig.authStrategy` is the documented field, `strategy` is a deprecated alias that still works and warns at runtime.
126
+
127
+ - a5f5422: Bump `@cipherstash/auth` (and its per-platform native bindings) from `0.40.0` to `0.41.0`, and migrate to its new `Result`-returning API.
128
+
129
+ **What changed in `@cipherstash/auth` `0.41`.** Every fallible auth operation now returns a `@byteslice/result` `Result<T, AuthFailure>` (`{ data }` on success, `{ failure }` on error) instead of throwing. This covers strategy construction (`AccessKeyStrategy.create`, `OidcFederationStrategy.create`, `AutoStrategy.detect`, `DeviceSessionStrategy.fromProfile`), `getToken()`, and the device-code flow (`beginDeviceCodeFlow`, `pollForToken`, `openInBrowser`, `bindClientDevice`). Consumers now write `if (result.failure) …` and read `result.data` rather than `try/catch`. The `AuthError` type was renamed to **`AuthFailure`** — a discriminated union keyed by `type` (`"NOT_AUTHENTICATED"`, `"WORKSPACE_MISMATCH"`, …), replacing the old `error.code` string.
130
+
131
+ **`@cipherstash/stack` (breaking type surface).**
132
+
133
+ - **`AuthError` is renamed to `AuthFailure`** in the public re-exports from `@cipherstash/stack`. `AuthErrorCode` and `TokenResult` are unchanged. Anyone importing `AuthError` from `@cipherstash/stack` must switch to `AuthFailure`.
134
+ - The WASM-inline access-key path (`resolveStrategy`, used by `@cipherstash/stack/wasm-inline`'s `Encryption()`) now unwraps the `Result` from `AccessKeyStrategy.create`. A construction failure (e.g. an invalid CRN or access key) throws a descriptive `[encryption]` error naming the `AuthFailure.type` instead of surfacing the raw auth error.
135
+ - Bump `@cipherstash/protect-ffi` from `0.27.0` to `0.28.0`. auth `0.41`'s `getToken()` returns the token inside a `Result` envelope; protect-ffi `0.28` unwraps it (`.data.token`) inside its WASM `newClient`, whereas `0.27` read `.token` off the envelope and got `undefined` — which failed the WASM encrypt/decrypt round-trip with `token field is not a string`. `0.28` is the floor for the WASM path under auth `0.41`.
136
+
137
+ **`stash` (CLI) and `@cipherstash/wizard`.** Internal auth call sites (`stash auth login`, device binding, `init` auth check, and the wizard's token acquisition / prerequisite check) were updated to unwrap `Result` and branch on `failure.type`. Behaviour is preserved — auth failures still surface the same way to end users; no CLI/wizard API changed.
138
+
139
+ - 35b9ed6: Bump `@cipherstash/protect-ffi` to `0.26.0` and `@cipherstash/auth` to `0.40.0`, and replace the lock-context token ceremony with a strategy-based approach for identity-bound encryption.
140
+
141
+ **protect-ffi `0.26.0`** supersedes `0.25.0`. The public API is unchanged from `0.25` (internal fixes only). As in `0.25`, `serviceToken` is gone from the encrypt / decrypt / query option types; auth flows through the client's strategy / credentials, and lock contexts travel as `lockContext.identityClaim`. The WASM-inline path takes a single options object with the auth strategy nested under `strategy`, and `Encryption()` config uses **`workspaceCrn`** (`CS_WORKSPACE_CRN`) as the single source of truth — `CS_REGION` is no longer consulted. On that path `workspaceCrn` is required only alongside an `accessKey` (it derives the region); with a pre-built `strategy` it is **optional**, since the strategy already carries the CRN.
142
+
143
+ **Strategy-based, identity-bound encryption.** `OidcFederationStrategy` federates an end user's third-party OIDC JWT (Clerk, Supabase, Auth0, …) into a CTS service token. As of `@cipherstash/auth` `0.40` it takes a `workspaceCrn` (region derived from the CRN), matching `AccessKeyStrategy`. Pass it as `config.strategy` so every ZeroKMS request authenticates _as that user_, then bind the data key to a claim with `.withLockContext({ identityClaim })`:
144
+
145
+ ```ts
146
+ import { Encryption, OidcFederationStrategy } from "@cipherstash/stack";
147
+
148
+ const client = await Encryption({
149
+ schemas: [users],
150
+ config: {
151
+ strategy: OidcFederationStrategy.create(workspaceCrn, () => getUserJwt()),
152
+ },
153
+ });
154
+
155
+ await client
156
+ .encrypt("alice@example.com", { column: users.email, table: users })
157
+ .withLockContext({ identityClaim: ["sub"] });
158
+ ```
159
+
160
+ This replaces the old ceremony (`new LockContext()` → `await lc.identify(jwt)` → `.withLockContext(lc)`), which relied on a per-operation CTS token that protect-ffi removed in `0.25`.
161
+
162
+ - **`.withLockContext()`** now accepts a plain `{ identityClaim }` object (as well as a `LockContext`) and no longer requires a CTS token or an `identify()` call — it carries the identity claim only.
163
+ - **`LockContext.identify()` / `getLockContext()`** are **deprecated** (kept for backwards compatibility); the strategy handles token acquisition.
164
+ - **Strategies are re-exported** from `@cipherstash/stack` (`OidcFederationStrategy`, `AccessKeyStrategy`, `AutoStrategy`, `DeviceSessionStrategy`) and from `@cipherstash/stack/wasm-inline` (`OidcFederationStrategy`, `AccessKeyStrategy`) so integrators don't need a separate `@cipherstash/auth` install. `AuthStrategy` remains re-exported for the structural type.
165
+
166
+ **Migrating `region` → `workspaceCrn` (WASM-inline).** If you previously passed `region` (or relied on `CS_REGION`) to the WASM-inline `Encryption()` path, replace it with your workspace CRN: set `workspaceCrn` in config (or `CS_WORKSPACE_CRN` in the environment) to the value shown in the CipherStash dashboard (`crn:<region>.aws:<workspace-id>` — it embeds the region, which is now derived from it). `region` is ignored if passed.
167
+
168
+ **Lock-context enforcement is now server-side only.** Because the client no longer resolves a per-user CTS token at `withLockContext` time, it also cannot fail fast there: a wrong or missing identity claim surfaces as a ZeroKMS **decryption failure** (the data key simply doesn't unlock), not as a client-side error before the request. The cryptographic guarantee is unchanged — enforcement happens in ZeroKMS — but anyone relying on the old client-side throw for early feedback should assert on the operation's `failure` result instead.
169
+
170
+ Existing credential / env behaviour is preserved when `config.strategy` is omitted.
171
+
172
+ ### Patch Changes
173
+
174
+ - aa9c4b1: Documentation: refresh package READMEs after the protectjs → stack repository rename. Fixed repository and license links, replaced dead in-repo docs links with cipherstash.com/docs URLs, rewrote the incorrect @cipherstash/nextjs README, and added guidance pointing new projects to @cipherstash/stack.
175
+
176
+ ## 0.18.0
177
+
178
+ ### Minor Changes
179
+
180
+ - 6e7ae4e: Export the operation classes returned by the encryption and DynamoDB clients as public API.
181
+
182
+ The classes returned from public methods are now exported and documented in the API reference, so their types can be named and their TSDoc links resolve.
183
+
184
+ - From `@cipherstash/stack/encryption`: `EncryptOperation`, `EncryptQueryOperation`, `BatchEncryptQueryOperation`, `DecryptOperation`, `EncryptModelOperation`, `DecryptModelOperation`, `BulkEncryptOperation`, `BulkDecryptOperation`, `BulkEncryptModelsOperation`, `BulkDecryptModelsOperation`. `EncryptQueryOperation` and `BatchEncryptQueryOperation` were previously marked `@internal`; since they are returned from `EncryptionClient.encryptQuery`, they are now public for consistency with the other operations.
185
+ - From `@cipherstash/stack/dynamodb`: `EncryptModelOperation`, `DecryptModelOperation`, `BulkEncryptModelsOperation`, `BulkDecryptModelsOperation`.
186
+ - From `@cipherstash/stack/types`: `EncryptedQuery` and `EncryptedFromSchema`.
187
+
188
+ The `*WithLockContext` variants returned by `.withLockContext()` remain internal — they share the same awaitable shape and are not intended to be named directly.
189
+
190
+ No runtime behaviour changes; this only widens the exported surface and corrects TSDoc cross-references that previously failed to resolve.
191
+
192
+ - 712d7fa: Fix: restore runtime null short-circuits in the encryption operation classes.
193
+
194
+ A prior refactor (`feat(stack): remove null from Encrypted type`) tightened the type signatures to disallow `null` and, alongside that, deleted the `if (value === null) return null` guards from every operation in `packages/stack/src/encryption/operations/`. The type guard does not survive runtime: callers reaching the operation through a cast (e.g. `null as any`), dynamic model walking, or JS interop would then have their null silently encrypted by protect-ffi into a real SteVec ciphertext (`{ k: 'sv', v: 2, ... }`) — which is observable, surprising, and breaks symmetry with the model-helpers layer that does still treat null as "absent" at the field level.
195
+
196
+ Restored, mirroring the pattern in `@cipherstash/protect`:
197
+
198
+ - `encrypt` / `encryptWithLockContext`: `if (plaintext === null) return null`.
199
+ - `bulkEncrypt` / `bulkEncryptWithLockContext`: per-element null filter; nulls are preserved in position in the output.
200
+ - `decrypt` / `decryptWithLockContext`: `if (encryptedData === null) return null`.
201
+ - `bulkDecrypt` / `bulkDecryptWithLockContext`: per-element null filter, position-preserving merge.
202
+ - `encryptQuery` / `encryptQueryWithLockContext`: `if (plaintext === null || plaintext === undefined) return { data: null }`.
203
+ - `batchEncryptQuery` / `batchEncryptQueryWithLockContext`: per-element null/undefined filter; null slots in the input array stay null in the result array.
204
+
205
+ Type adjustments to support the runtime behavior honestly:
206
+
207
+ - `BulkEncryptPayload['plaintext']`, `BulkEncryptedData['data']`, `BulkDecryptPayload['data']`, and the `T` of `BulkDecryptedData` all widen to `... | null`. Bulk APIs now accept and return mixed nullable arrays without filtering ahead of time.
208
+ - `EncryptedQueryResult` widens to include `null` so the batch query path can return position-stable arrays with null slots.
209
+ - `Encryption.encrypt()` and `Encryption.decrypt()` public signatures are unchanged — still narrow (`JsPlaintext` / `Encrypted` input, `Encrypted` / `JsPlaintext` non-nullable output). The runtime null short-circuit in `EncryptOperation` / `DecryptOperation` is defense in depth for callers reaching the operation classes through casts, dynamic field walking, or JS interop. The narrow-return contract holds for any caller that respects the input contract.
210
+
211
+ ## 0.17.0
212
+
213
+ ### Minor Changes
214
+
215
+ - f743fcc: Upgrade `@cipherstash/protect-ffi` to `0.23.0` and the bundled CipherStash EQL extension to `eql-2.3.1`.
216
+
217
+ Breaking upstream changes adopted in this release:
218
+
219
+ - **Encrypt-config schema version**: `buildEncryptConfig` now emits `{ v: 1, ... }` (was `{ v: 2, ... }`). protect-ffi `0.22.0` started validating this field and rejects any value other than `1` with the new `UNSUPPORTED_CONFIG_VERSION` error code.
220
+ - **Storage and query payloads are now distinct types** (protect-ffi `0.23.0`): the previously-conflated `Encrypted` type splits into `Encrypted` (storage-only, `c` required) and a new `EncryptedQuery` (search terms — scalar `unique`/`match`/`ore` lookups and `ste_vec_selector` JSON path queries; no `c`). JSON containment queries (`ste_vec_term`) still return a storage-shaped `Encrypted` payload. `encryptQuery` / `encryptQueryBulk` now return `Encrypted | EncryptedQuery`, and the stack's `EncryptedSearchTerm` / `EncryptedQueryResult` unions widen to match. `decrypt` rejects query payloads at the type level. The DynamoDB `SearchTermsOperation` narrows via `'hm' in term` rather than `term.hm`.
221
+ - **SteVec encoding default flipped**: protect-ffi's default `mode` for `ste_vec` indexes changed from `compat` to `standard`. The two encodings are not cross-compatible. Existing JSON-searchable data that was indexed under `compat` will need to be re-encrypted to be queryable. The stack adopts the new `standard` default — there is no longer a way to pin `compat` from the SDK.
222
+ - **EQL extension bumped to `eql-2.3.1`**: the new SteVec `standard` encoding requires matching support in the database EQL extension. The CLI's bundled SQL (`packages/cli/src/sql/*.sql`) and the `@cipherstash/prisma-next` install bundle (`migrations/20260601T0000_install_eql_bundle/ops.json` + `eql-install.generated.ts`) are updated to `eql-2.3.1`. Databases installed with an older EQL extension must be reinstalled (`stash db install`) before containment / contained-by queries against SteVec columns will work. `eql-2.3.1` ships the `_encrypted_check_c` fix for SteVec storage payloads ([cipherstash/encrypt-query-language#232](https://github.com/cipherstash/encrypt-query-language/issues/232)).
223
+ - **New error codes**: `ProtectErrorCode` (re-exported from `@cipherstash/protect-ffi`) gains `MATCH_REQUIRES_TEXT` and `UNSUPPORTED_CONFIG_VERSION`. Exhaustive switches over `ProtectErrorCode` will need additional cases.
224
+ - **`match` index validation**: protect-ffi now rejects `match` indexes on columns whose `cast_as` is not text-family (`'text'` / `'string'`) with `MATCH_REQUIRES_TEXT`. The stack's `freeTextSearch()` builder is unaffected because it only targets string-typed columns.
225
+ - **`Encrypted` ciphertext shape**: protect-ffi's `Encrypted` type is now a discriminated union keyed on `k` (`'ct'` for scalars, `'sv'` for SteVec). SteVec storage payloads now place the root document ciphertext at `sv[0].c`. The stack's `isEncryptedPayload` runtime check continues to work because storage payloads still carry `c` (scalar) or `sv` (SteVec). The DynamoDB helpers (`toEncryptedDynamoItem`, `SearchTermsOperation`) now narrow on `k` before reading variant-only fields.
226
+ - **Config-validation error message wording**: error messages for config-validation failures now come from upstream `ConfigError`. `ProtectError.code` values are preserved; consumers that string-match on `err.message` for config-validation errors must update.
227
+
3
228
  ## 0.16.0
4
229
 
5
230
  ### Minor Changes
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  The all-in-one TypeScript SDK for the CipherStash data security stack.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@cipherstash/stack.svg?style=for-the-badge&labelColor=000000)](https://www.npmjs.com/package/@cipherstash/stack)
6
- [![License: MIT](https://img.shields.io/npm/l/@cipherstash/stack.svg?style=for-the-badge&labelColor=000000)](https://github.com/cipherstash/protectjs/blob/main/LICENSE.md)
6
+ [![License: MIT](https://img.shields.io/npm/l/@cipherstash/stack.svg?style=for-the-badge&labelColor=000000)](https://github.com/cipherstash/stack/blob/main/LICENSE.md)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-first-blue?style=for-the-badge&labelColor=000000)](https://www.typescriptlang.org/)
8
8
 
9
9
  --
@@ -687,4 +687,4 @@ All method signatures on the encryption client (`encrypt`, `decrypt`, `encryptMo
687
687
 
688
688
  ## License
689
689
 
690
- MIT - see [LICENSE.md](https://github.com/cipherstash/protectjs/blob/main/LICENSE.md).
690
+ MIT - see [LICENSE.md](https://github.com/cipherstash/stack/blob/main/LICENSE.md).