@cipherstash/protect-ffi 0.27.0 → 0.28.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.
package/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 CipherStash
3
+ Copyright (c) 2024-2026 CipherStash
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # Protect.js CipherStash Client FFI
1
+ # CipherStash Client FFI (`@cipherstash/protect-ffi`)
2
2
 
3
3
  > [!IMPORTANT]
4
- > If you are looking to implement this package into your application please use the official [protect package](https://github.com/cipherstash/protectjs).
4
+ > If you are looking to implement this package into your application please use the official [`@cipherstash/stack` package](https://github.com/cipherstash/stack).
5
5
 
6
6
  This project provides the JS bindings for the CipherStash Client Rust SDK and is bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon).
7
7
 
@@ -19,19 +19,18 @@ This command uses the [@neon-rs/cli](https://www.npmjs.com/package/@neon-rs/cli)
19
19
 
20
20
  ## Local setup
21
21
 
22
- You can use the `stash` CLI tool to set up your local environment.
23
-
24
- You will be prompted to sign in or create an account and follow steps to create a keyset and client key.
22
+ Authenticate the `stash` CLI to set up your local environment. It runs via `npx` — no separate install needed:
25
23
 
26
24
  ```sh
27
- brew install cipherstash/tap/stash
28
- stash setup
25
+ npx stash auth login
29
26
  ```
30
27
 
28
+ You will be prompted to sign in or create an account.
29
+
31
30
  ## Exploring
32
31
 
33
32
  After building `protect-ffi`, you can explore its exports at the Node console.
34
- `CS_CLIENT_ID` and `CS_CLIENT_KEY` must be set in your environment for the call to `newClient()` to succeed.
33
+ Credentials must be available for the call to `newClient()` to succeed resolved from the CipherStash profile store, from environment variables, or from an explicit `strategy` passed to `newClient()`. For local development the simplest option is `npx stash auth login`, which populates the profile store. Alternatively, set `CS_WORKSPACE_CRN`, `CS_ACCESS_KEY`, and the `CS_CLIENT_ID`/`CS_CLIENT_KEY` keypair in your environment.
35
34
 
36
35
  ```sh
37
36
  $ npm i
@@ -91,8 +90,8 @@ Notes:
91
90
  - v3 query encryption currently supports JSON containment only; scalar and
92
91
  selector queries throw `EQL_V3_QUERY_UNSUPPORTED` and need an
93
92
  `eqlVersion: 2` client.
94
- - `ope`-indexed columns map to `<family>_ord_ope` but cannot be produced
95
- end-to-end yet: the client does not emit the `op` term (CIP-3280).
93
+ - `ope`-indexed columns map to `<family>_ord_ope` and carry the `op`
94
+ (CLLW-OPE) term (emitted since cipherstash-client 0.38.1).
96
95
 
97
96
  > [!NOTE]
98
97
  > **Breaking TypeScript change:** `encrypt`/`encryptBulk` now return
@@ -104,6 +103,55 @@ Notes:
104
103
  > `payload.v === 3` to detect the v3 members. (A bare `payload.k === 'ct'`
105
104
  > does not compile against the union.)
106
105
 
106
+ ## BigInt plaintexts
107
+
108
+ Encrypted `cast_as: 'bigint'` columns store signed 64-bit integers
109
+ (PostgreSQL `bigint`). `encrypt` / `encryptBulk` / `encryptQuery` /
110
+ `encryptQueryBulk` accept the plaintext as either a JS `number` or a JS
111
+ `bigint`:
112
+
113
+ - `bigint` inputs are exact and bounds-checked against the full i64 range:
114
+ **-9223372036854775808 to 9223372036854775807** (-2^63 to 2^63 - 1).
115
+ Values outside that range throw a `RangeError` (a plain `RangeError`, not
116
+ a `ProtectError`) naming the bounds and the offending direction. Search
117
+ index terms (`hm`, `ob`, `op`) are derived from the same value, so the
118
+ boundary check covers index-term generation too.
119
+ - `number` inputs keep the existing exact-integer guard: fractional,
120
+ non-finite, or out-of-range values error instead of being silently
121
+ truncated.
122
+ - A `bigint` is only accepted as the top-level plaintext of a scalar
123
+ column. JSON has no bigint, so bigints nested inside `cast_as: 'json'`
124
+ documents (or JSON containment query terms) are rejected with a
125
+ `TypeError` on both Neon and wasm. More generally, plaintexts follow
126
+ `JSON.stringify` semantics on both platforms: `toJSON` is honored (a
127
+ `Date` becomes its ISO string), `undefined` properties are dropped, and
128
+ non-finite numbers become `null`.
129
+ - The internal wire form `{"__protect_ffi_bigint__": "<digits>"}` is
130
+ reserved: a `cast_as: 'json'` plaintext consisting of exactly that
131
+ single-key shape (with an in-range i64 decimal string) is read as a
132
+ bigint at the boundary and rejected for json columns. Nested
133
+ occurrences of the key inside a larger document are unaffected.
134
+
135
+ ```js
136
+ const ciphertext = await addon.encrypt(client, {
137
+ plaintext: 9007199254740993n, // beyond Number.MAX_SAFE_INTEGER — stays exact
138
+ column: 'score',
139
+ table: 'users',
140
+ })
141
+ const decrypted = await addon.decrypt(client, { ciphertext })
142
+ // decrypted === 9007199254740993n (a JS bigint)
143
+ ```
144
+
145
+ > [!WARNING]
146
+ > **Breaking change:** `decrypt` / `decryptBulk` / `decryptBulkFallible`
147
+ > now ALWAYS return a JS `bigint` for `cast_as: 'bigint'` columns — even
148
+ > for values that fit in a JS number. Previous releases returned a
149
+ > `number`, silently losing precision beyond `Number.MAX_SAFE_INTEGER`
150
+ > (2^53 - 1). Code comparing or doing arithmetic on decrypted bigint
151
+ > columns must be updated (e.g. `decrypted === 123n` instead of
152
+ > `decrypted === 123`, or `Number(decrypted)` when the value is known to
153
+ > be small).
154
+
107
155
  ## Errors
108
156
 
109
157
  Async API calls throw `ProtectError` with a stable `code` for programmatic handling.
@@ -151,8 +199,7 @@ Initiate a dry run of a patch release of this library via GitHub Actions. This p
151
199
 
152
200
  #### `npm test`
153
201
 
154
- Formats and lints Rust and TypeScript code.
155
- Also runs Rust tests.
202
+ Typechecks the TypeScript and runs the unit tests (tsc + vitest), formats and lints Rust and TypeScript code, and runs Rust tests.
156
203
 
157
204
  Note: `npm test` at project root does not run integration tests.
158
205
  For integration tests, see [below](#integration-tests).
@@ -175,6 +222,8 @@ protect-ffi/
175
222
  | └── src/
176
223
  | └── lib.rs
177
224
  ├── platforms/
225
+ ├── docs/
226
+ ├── scripts/
178
227
  ├── package.json
179
228
  └── target/
180
229
  ```
@@ -191,6 +240,8 @@ protect-ffi/
191
240
  | `crates/` | The directory tree containing the Rust source code for the project. |
192
241
  | `lib.rs` | Entry point for the Rust source code. |
193
242
  | `platforms/` | The directory containing distributions of the binary addon backend for each platform supported by this library. |
243
+ | `docs/` | JSONB integration docs and migration notes. |
244
+ | `scripts/` | Build helper scripts (e.g. WASM inlining). |
194
245
  | `package.json` | The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command. |
195
246
  | `target/` | Binary artifacts generated by the Rust build. |
196
247
 
@@ -208,9 +259,11 @@ Example environment variables:
208
259
  ```
209
260
  CS_CLIENT_ID=
210
261
  CS_CLIENT_KEY=
262
+ # The integration tests read CS_CLIENT_ACCESS_KEY; the library itself reads CS_ACCESS_KEY
211
263
  CS_CLIENT_ACCESS_KEY=
212
264
  CS_WORKSPACE_CRN=
213
- PGPORT=5432
265
+ # Must match the port used by the integration-test setup (mise.toml)
266
+ PGPORT=5436
214
267
  PGDATABASE=cipherstash
215
268
  PGUSER=cipherstash
216
269
  PGPASSWORD=password
@@ -249,7 +302,7 @@ The release workflow is responsible for:
249
302
 
250
303
  To perform a release:
251
304
 
252
- 1. Navigate to the ["Release" workflow page](https://github.com/cipherstash/protect-ffi/actions/workflows/release.yml) in GitHub.
305
+ 1. Navigate to the ["Release" workflow page](https://github.com/cipherstash/protectjs-ffi/actions/workflows/release.yml) in GitHub.
253
306
  1. Click on "Run workflow".
254
307
  1. Select the branch to release from.
255
308
  Use the default of `main` unless you want to do a pre-release version or dry run from a branch.
@@ -260,8 +313,11 @@ To perform a release:
260
313
  Select "custom" in the dropdown and fill in the "Custom version" text box if you want to use a semver string instead of the shorthand (patch, minor, major, etc.).
261
314
  1. Click "Run workflow".
262
315
 
263
- Note that we currently don't have any automation around release notes or a changelog.
264
- However, you can add release notes after running the workflow by editing the release on GitHub.
316
+ Release notes and the changelog are automated. Add notes for your changes under the
317
+ `[Unreleased]` heading in [`CHANGELOG.md`](./CHANGELOG.md). On release, the `version`
318
+ npm lifecycle hook promotes that section to a dated release entry
319
+ (`scripts/changelog-release.mjs`), and the workflow publishes the promoted section as
320
+ the GitHub release notes (`scripts/changelog-extract.mjs`).
265
321
 
266
322
  ## Learn More
267
323
 
@@ -247,6 +247,10 @@ export function newClient(opts) {
247
247
  const ret = wasm.newClient(opts);
248
248
  return ret;
249
249
  }
250
+ export function __wbg_BigInt_7882274c8d2ac74c() { return handleError(function (arg0) {
251
+ const ret = BigInt(arg0);
252
+ return ret;
253
+ }, arguments); }
250
254
  export function __wbg_Error_83742b46f01ce22d(arg0, arg1) {
251
255
  const ret = Error(getStringFromWasm0(arg0, arg1));
252
256
  return ret;
@@ -350,6 +354,10 @@ export function __wbg_arrayBuffer_eb8e9ca620af2a19() { return handleError(functi
350
354
  const ret = arg0.arrayBuffer();
351
355
  return ret;
352
356
  }, arguments); }
357
+ export function __wbg_assign_2c1c53ce7e57ea6e(arg0, arg1) {
358
+ const ret = Object.assign(arg0, arg1);
359
+ return ret;
360
+ }
353
361
  export function __wbg_buffer_60b8043cd926067d(arg0) {
354
362
  const ret = arg0.buffer;
355
363
  return ret;
@@ -473,6 +481,16 @@ export function __wbg_instanceof_Map_f194b366846aca0c(arg0) {
473
481
  const ret = result;
474
482
  return ret;
475
483
  }
484
+ export function __wbg_instanceof_Object_be1962063fcc0c9f(arg0) {
485
+ let result;
486
+ try {
487
+ result = arg0 instanceof Object;
488
+ } catch (_) {
489
+ result = false;
490
+ }
491
+ const ret = result;
492
+ return ret;
493
+ }
476
494
  export function __wbg_instanceof_Promise_7c3bdd7805c2c6e6(arg0) {
477
495
  let result;
478
496
  try {
@@ -551,6 +569,10 @@ export function __wbg_new_ab79df5bd7c26067() {
551
569
  const ret = new Object();
552
570
  return ret;
553
571
  }
572
+ export function __wbg_new_aff07d10ed2fdd7f(arg0, arg1) {
573
+ const ret = new RangeError(getStringFromWasm0(arg0, arg1));
574
+ return ret;
575
+ }
554
576
  export function __wbg_new_c518c60af666645b() { return handleError(function () {
555
577
  const ret = new AbortController();
556
578
  return ret;
@@ -605,6 +627,10 @@ export function __wbg_node_84ea875411254db1(arg0) {
605
627
  const ret = arg0.node;
606
628
  return ret;
607
629
  }
630
+ export function __wbg_parse_e9eddd2a82c706eb() { return handleError(function (arg0, arg1) {
631
+ const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
632
+ return ret;
633
+ }, arguments); }
608
634
  export function __wbg_process_44c7a14e11e9f69e(arg0) {
609
635
  const ret = arg0.process;
610
636
  return ret;
@@ -612,6 +638,10 @@ export function __wbg_process_44c7a14e11e9f69e(arg0) {
612
638
  export function __wbg_prototypesetcall_d62e5099504357e6(arg0, arg1, arg2) {
613
639
  Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
614
640
  }
641
+ export function __wbg_push_e87b0e732085a946(arg0, arg1) {
642
+ const ret = arg0.push(arg1);
643
+ return ret;
644
+ }
615
645
  export function __wbg_queueMicrotask_0c399741342fb10f(arg0) {
616
646
  const ret = arg0.queueMicrotask;
617
647
  return ret;
@@ -643,6 +673,10 @@ export function __wbg_set_282384002438957f(arg0, arg1, arg2) {
643
673
  export function __wbg_set_6be42768c690e380(arg0, arg1, arg2) {
644
674
  arg0[arg1] = arg2;
645
675
  }
676
+ export function __wbg_set_7eaa4f96924fd6b3() { return handleError(function (arg0, arg1, arg2) {
677
+ const ret = Reflect.set(arg0, arg1, arg2);
678
+ return ret;
679
+ }, arguments); }
646
680
  export function __wbg_set_8c0b3ffcf05d61c2(arg0, arg1, arg2) {
647
681
  arg0.set(getArrayU8FromWasm0(arg1, arg2));
648
682
  }
@@ -702,6 +736,10 @@ export function __wbg_status_318629ab93a22955(arg0) {
702
736
  const ret = arg0.status;
703
737
  return ret;
704
738
  }
739
+ export function __wbg_stringify_5ae93966a84901ac() { return handleError(function (arg0) {
740
+ const ret = JSON.stringify(arg0);
741
+ return ret;
742
+ }, arguments); }
705
743
  export function __wbg_subarray_a068d24e39478a8a(arg0, arg1, arg2) {
706
744
  const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0);
707
745
  return ret;
@@ -718,6 +756,10 @@ export function __wbg_then_9e335f6dd892bc11(arg0, arg1, arg2) {
718
756
  const ret = arg0.then(arg1, arg2);
719
757
  return ret;
720
758
  }
759
+ export function __wbg_toString_68d455d069f7169a() { return handleError(function (arg0, arg1) {
760
+ const ret = arg0.toString(arg1);
761
+ return ret;
762
+ }, arguments); }
721
763
  export function __wbg_url_7fefc1820fba4e0c(arg0, arg1) {
722
764
  const ret = arg1.url;
723
765
  const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -742,12 +784,12 @@ export function __wbg_wasmclient_new(arg0) {
742
784
  return ret;
743
785
  }
744
786
  export function __wbindgen_cast_0000000000000001(arg0, arg1) {
745
- // Cast intrinsic for `Closure(Closure { dtor_idx: 879, function: Function { arguments: [], shim_idx: 880, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
787
+ // Cast intrinsic for `Closure(Closure { dtor_idx: 893, function: Function { arguments: [], shim_idx: 894, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
746
788
  const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__hd9dedd770edb0aa1, wasm_bindgen__convert__closures_____invoke__h8b170b34f8b593df);
747
789
  return ret;
748
790
  }
749
791
  export function __wbindgen_cast_0000000000000002(arg0, arg1) {
750
- // Cast intrinsic for `Closure(Closure { dtor_idx: 932, function: Function { arguments: [Externref], shim_idx: 933, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
792
+ // Cast intrinsic for `Closure(Closure { dtor_idx: 946, function: Function { arguments: [Externref], shim_idx: 947, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
751
793
  const ret = makeMutClosure(arg0, arg1, wasm.wasm_bindgen__closure__destroy__h94971d029da3e81d, wasm_bindgen__convert__closures_____invoke__hacad64c6c6b78997);
752
794
  return ret;
753
795
  }
Binary file
@@ -30,11 +30,11 @@ export const wasm_bindgen__closure__destroy__h94971d029da3e81d: (a: number, b: n
30
30
  export const wasm_bindgen__convert__closures_____invoke__hacad64c6c6b78997: (a: number, b: number, c: any) => [number, number];
31
31
  export const wasm_bindgen__convert__closures_____invoke__h0cb80728b35f261e: (a: number, b: number, c: any, d: any) => void;
32
32
  export const wasm_bindgen__convert__closures_____invoke__h8b170b34f8b593df: (a: number, b: number) => void;
33
- export const __wbindgen_malloc: (a: number, b: number) => number;
34
- export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
35
33
  export const __wbindgen_exn_store: (a: number) => void;
36
34
  export const __externref_table_alloc: () => number;
37
35
  export const __wbindgen_externrefs: WebAssembly.Table;
36
+ export const __wbindgen_malloc: (a: number, b: number) => number;
37
+ export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
38
38
  export const __wbindgen_free: (a: number, b: number, c: number) => void;
39
39
  export const __externref_table_dealloc: (a: number) => void;
40
40
  export const __wbindgen_start: () => void;