-
Notifications
You must be signed in to change notification settings - Fork 6
feat(stack)!: align wasm-inline to the Result contract, and add bulkEncrypt/bulkDecrypt #741
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c70b823
feat(stack): bulkEncrypt/bulkDecrypt on the wasm-inline entry (#737)
coderdan 9e0b3a6
feat(stack)!: align the wasm-inline entry to the Result contract
coderdan f2c56b9
fix(stack): preserve non-Error rejection detail on the wasm-inline entry
coderdan 0020b56
fix(stack): address review — native FFI leak, Result plumbing, batch …
coderdan 1ffdc56
fix(stack): review round 2 — e2e, example, skill, and adapter bulk usage
coderdan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| --- | ||
| '@cipherstash/stack': minor | ||
| 'stash': patch | ||
| --- | ||
|
|
||
| **Breaking (`@cipherstash/stack/wasm-inline`):** every fallible method now returns a `Result` — `{ data } | { failure }` — instead of throwing. And `bulkEncrypt` / `bulkDecrypt` are added, so a list of encrypted rows costs **one** ZeroKMS round trip instead of one per row. | ||
|
|
||
| ### Result alignment | ||
|
|
||
| `encrypt`, `decrypt`, `encryptQuery` and `encryptQueryBulk` previously threw on failure, and returned bare values on success. They now return `{ data } | { failure }`, with `failure.type` drawn from `EncryptionErrorTypes` (`EncryptionError` for encrypt-side operations, `DecryptionError` for decrypt-side) and `failure.code` carrying the FFI error code where there is one. | ||
|
|
||
| ```typescript | ||
| // before | ||
| const encrypted = await client.encrypt(plaintext, { table: users, column: users.email }) | ||
|
|
||
| // after | ||
| const result = await client.encrypt(plaintext, { table: users, column: users.email }) | ||
| if (result.failure) throw new Error(result.failure.message) | ||
| const encrypted = result.data | ||
| ``` | ||
|
|
||
| This is the contract the native entry has always honoured, and the one `AGENTS.md` states outright: *"Operations return `{ data }` or `{ failure }`. Preserve this shape and error `type` values in `EncryptionErrorTypes`."* The WASM entry never followed it. That was drift rather than a design decision — nothing about WASM prevents it (`@byteslice/result` is already bundled into `dist/wasm-inline.js`), and it meant edge code had to be written in a different shape from every other surface, with failures that were easy to miss. | ||
|
|
||
| Fixed now because it is a breaking change and 1.0.0 has not shipped: `@cipherstash/stack@latest` is still `0.19.0`, so this surface has only ever been published under the `rc` tag. After GA it would have had to wait for a major. | ||
|
|
||
| `isEncrypted` is unchanged — a pure predicate with nothing to fail at, exactly as on the native entry. | ||
|
|
||
| ### Bulk operations | ||
|
|
||
| ```typescript | ||
| // Write: several columns across many rows, one round trip | ||
| const encrypted = await client.bulkEncrypt([ | ||
| { plaintext: "alice@example.com", table: users, column: users.email }, | ||
| { plaintext: "hello", table: users, column: users.bio }, | ||
| ]) | ||
|
|
||
| // Read: a whole page in one call | ||
| const emails = await client.bulkDecrypt(rows.map((r) => r.email)) | ||
| ``` | ||
|
|
||
| The WASM entry previously exposed no bulk operations at all, so rendering an N-row list on Deno, Cloudflare Workers, or Supabase Edge Functions meant N sequential ZeroKMS calls. Combined with the WASM cold start, that made list endpoints impractical on the edge. | ||
|
|
||
| Both are index-aligned with their input, and `null` / `undefined` entries yield `null` at the same index without reaching ZeroKMS (an all-null batch makes no call at all). Because each entry names its own table and column, a single `bulkEncrypt` can cover several columns across many rows — which is what makes the saving real, since a single-column batch would still cost one round trip per column. | ||
|
|
||
| `bulkDecrypt` builds on the fallible FFI primitive, so when items fail the `failure.message` names **every** failing index with its reason, rather than surfacing the first and discarding the rest. | ||
|
|
||
| The model helpers (`encryptModel` / `decryptModel` and their bulk forms) remain Node-only: the WASM entry has no single-model operation to build them on, so those need their own port. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4/15 — as committed, this example can't run against any published version.
npm:@cipherstash/stack@^0.18.0/wasm-inline(line 21) — a version whose entry returns bare values. Deployed as-is:encryptreturns the payload,encryptResult.failureisundefined(check passes),encryptResult.dataisundefined→decrypt(undefined)throws → 500 on every request.encryptedColumn('email').equality(), line 24), but the current entry exports only the v3 surface (export * from '@/eql/v3'— noencryptedColumn) and its factory rejects non-v3 tables outright.examples/supabase-worker/README.md:62still advertisesencryptedColumnon this surface too. The example needs the version bump and the v3 schema migration together.