BREAKING CHANGE: keyDir now defaults to null (ephemeral) instead of
'client_keys'. Clients that relied on keys persisting across restarts
must now pass keyDir explicitly.
The Python SDK defaults to key_dir=None: a key pair is generated in
memory for the session and never written to disk. The JS port defaulted
to 'client_keys' and always persisted, so merely constructing a client
wrote an RSA private key into the working directory. That is a weaker
default than the client it ports, and one users never asked for.
- keyDir?: string | null, defaulting to undefined. null and undefined
both mean ephemeral, matching Python's None.
- Persistent mode is unchanged when keyDir is set: load the existing
pair from that directory, otherwise generate and save one there.
- Browsers are always ephemeral; they have no filesystem.
Key rotation follows the same rule. It previously hardcoded
'client_keys' as its fallback directory, so an ephemeral client would
have started writing private keys to disk on the first rotation tick.
Rotated keys are now persisted only where keyDir or keyRotationDir is
explicitly configured.
Tests assert the intent (that saveKeys is never called) rather than
probing the filesystem, since a leftover client_keys/ from the old
default would otherwise make them pass or fail for the wrong reason.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three independent breakages, all present on main before this branch:
1. `npm ci` always exited non-zero. The root package.json carried
"install": "node-gyp-build", but binding.gyp lives in native/, so the
script ran in a directory with nothing to build:
gyp: binding.gyp not found (cwd: <repo root>)
native/package.json already declares that same install script in the
right place, alongside its binding.gyp and "gypfile": true, so the
root copy was a duplicate in the wrong package. Removing it also
clears the now-inaccurate hasInstallScript flag from the lockfile.
`npm ci` exits 0 again; the addon still builds from native/.
2. `npm run build` failed at the first step. @rollup/plugin-typescript
requires tslib as a peer, and nothing depended on it directly — it was
only present transitively as an optional dev dep, so a clean install
could omit it entirely. Declared explicitly.
3. rollup.config.js used ESM syntax while package.json has no
"type": "module", so Node parsed it as CommonJS and threw
"Cannot use import statement outside a module". Node 24 recovers by
reparsing (with a warning); Node 18 fails outright. Renamed to
rollup.config.mjs, which is unambiguous on both. Setting
"type": "module" instead would have broken jest.config.js, which is
CommonJS.
Verified on Node 18.19.1 and Node 24.18.0: npm ci exits 0, npm run build
produces all three bundles plus declarations, and 76/76 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dependency bumps on main left `npm test` and `npm run build:types`
broken independently of any feature work; both fail on a clean checkout
of origin/main.
TypeScript 6 no longer auto-includes @types packages the way node10
resolution did, so every test suite failed to compile with "Cannot find
name 'describe'/'expect'". Declare the needed @types explicitly instead:
- tsconfig.json: types: ["node"]
- jest.config.js: types: ["jest", "node"]
TypeScript 6 also errors on two settings this config relied on:
- moduleResolution "node" (node10) is deprecated -> "bundler", which
matches how the package is actually consumed (rollup-bundled, with
"module": "ESNext")
- an implicit rootDir is now an error when outDir/declarationDir are
set -> rootDir: "./src"
Drop three unused imports that noUnusedLocals turns into hard errors,
failing --emitDeclarationOnly.
Finally, NodeSecureMemory logged to stdout unconditionally on
construction. This was dormant while the native addon failed to load;
once it loads, it broke the "no console.log when debug=false" test. A
library must not write to stdout uninvited, and the client already
reports this via getProtectionInfo() behind its own debug flag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>