From 417 to 32 milliseconds: envsec on Effect 4 and Bun
envsec is a CLI: you run it, it does one thing, and it exits. You run it dozens of times a day, sometimes inside a script or a shell prompt. For a tool like that, startup time is the performance that matters, because it is the only one you actually feel.
The new beta (1.1.0-beta.1) wraps up a three-step journey: migrating from Effect 3 to Effect 4, trimming dependencies, and shipping a standalone binary built with bun build --compile. I measured every step on the same machine. The result: startup went from 417 ms to 32 ms (12.9× faster), and memory from 207 MB to 36 MB (5.8× less).
At a glance
| Effect 3 · Node (1.0.0-beta.18) | Effect 4 · Node (1.0.2) | Effect 4 · Node (1.1.0-beta.1) | Effect 4 · Bun binary (1.1.0-beta.1) | |
|---|---|---|---|---|
| envsec --version | 416.7 ms | 318.2 ms | 192.3 ms | 32.4 ms |
| envsec --help | 430.1 ms | 324.2 ms | 196.8 ms | 34.0 ms |
| envsec list | 443.4 ms | 319.2 ms | 199.4 ms | 33.7 ms |
| Memory (max RSS, list) | 207 MB | 154 MB | 97 MB | 36 MB |
| Installed packages | 42 | 19 | 10 | 0 (single file) |
| Disk footprint | 106 MB | 100 MB | 58 MB | 61 MB (runtime incl.) |
| Minified JS bundle | 516 KB | 371 KB | 371 KB | embedded |
The three steps
1. Effect 3 → Effect 4 (1.0.0-beta.18 → 1.0.2)
On Effect 3, a CLI like envsec pulled in a constellation of packages: effect, @effect/cli, @effect/platform, @effect/platform-node and, transitively, @effect/cluster, @effect/rpc, @effect/sql, @effect/workflow, @effect/printer, @effect/typeclass and more. 42 packages in total.
Effect 4 folds almost all of that into the effect package: the CLI module is now imported from effect/cli. The migration notes mention a fiber runtime rewritten for lower memory overhead and faster execution, and a core built for aggressive tree-shaking. For envsec, that meant:
- startup: −24% (417 → 318 ms)
- memory: −26% (207 → 154 MB)
- minified bundle: −28% (516 → 371 KB; gzipped 155 → 115 KB)
- packages: 42 → 19
None of the application logic changed. That gain comes from the library upgrade alone.
2. Leaner dependencies (1.0.2 → 1.1.0-beta.1 on Node)
Two targeted changes:
- Dropped
@effect/platform-node. The CLI only needsNodeRuntime.runMainandNodeServices.layer, which that package just re-exports from@effect/platform-node-shared. Its barrel import, however, also loadedundiciand theredisclient on every start. envsec now imports theplatform-node-sharedmodules directly. - Replaced
sql.jswithnode:sqlite. The metadata database used SQLite compiled to WebAssembly: a 23 MB package and a WASM module to instantiate on every start. It now uses the built-innode:sqlitemodule (Node ≥ 22.13, also supported by Bun). As a bonus, writes go straight to the file instead of re-exporting the whole in-memory database each time.
Result: startup 318 → 192 ms (−40%), memory 154 → 97 MB, packages 19 → 10, and the disk footprint nearly halved.
3. A standalone Bun binary
bun build src/main.ts --compile --minify --sourcemap --outfile envsec--compile produces a single executable with the Bun runtime embedded: no Node.js, no node_modules, no module resolution at startup. The CLI code ships pre-bundled and minified in one file.
Result: startup 192 → 32 ms (5.9× faster), memory 97 → 36 MB.
The gain has two sources:
- The runtime. On this machine an empty process takes 76.5 ms with Node (
node -e 0) and 4.5 ms with Bun (bun -e 0). That alone is worth about 72 ms. - Loading the code. Subtracting the runtime cost, envsec on Node spends about 116 ms loading and initialising modules; the binary needs about 28. Reading one pre-bundled file is far cheaper than resolving and evaluating hundreds of files spread across 10 packages.
Where the 384 milliseconds went
- Effect 3 · Node417 ms
- Effect 4 · Node318 ms−99 ms
- Effect 4 · leaner deps192 ms−126 ms
- Effect 4 · Bun binary32 ms−160 ms
Median of envsec --version, 40 runs. Each step builds on the previous one.
- Effect 3 · Node207 MB
- Effect 4 · Node154 MB
- Effect 4 · leaner deps97 MB
- Effect 4 · Bun binary36 MB
Median of 15 runs of envsec list, measured with /usr/bin/time -l.
Every step delivered a real gain, and each one enabled the next: without the move to node:sqlite the binary could not even be compiled, because the sql.js WASM file could not be located at run time.
Installation and distribution
| Effect 3 · npm | Effect 4 · npm 1.0.2 | Effect 4 · npm 1.1.0-beta.1 | Bun binary | |
|---|---|---|---|---|
| npm install, cold cache (median of 3) | 3.0 s | 2.0 s | 1.6 s | — |
| Download (compressed) | ~23 MB | ~21 MB | ~10 MB | 25 MB (tar.gz) |
| Needs Node.js | yes | yes | yes (≥ 22.13) | no |
The binary is not the lightest download: of its 61 MB, about 59 are the Bun runtime, so envsec and its dependencies weigh just over 1.5 MB. In exchange you don't need Node installed, there is no dependency tree to resolve, and startup is an order of magnitude faster. The npm package is still there for anyone who prefers Node, and it is 54% faster than the Effect 3 version too.
Methodology
- Machine: Apple M4 Pro, 24 GB RAM, macOS (Darwin 27.0)
- Runtimes: Node.js v26.10.0 for the npm versions; Bun 1.4.2 embedded in the binary
- Versions: installed from npm (
envsec@1.0.0-beta.18,@1.0.2,@1.1.0-beta.1). The binary isenvsec-darwin-arm64from thev1.1.0-beta.1GitHub release. - Tools:
hyperfine -N --warmup 5 --runs 40for timings;/usr/bin/time -l(median of 15 runs) for memory;bun build --minifyof the publisheddist/main.jsfor bundle size (sql.jsexcluded, since it loads as WASM). - Data: an empty database isolated via
ENVSEC_DB, so the measured commands never touch the system keychain.
Caveats
- This is one machine. Absolute numbers will differ on Linux and on Node 24 LTS: in earlier tests on Node 24, removing
@effect/platform-nodealone took--versionfrom ~241 to ~149 ms. - I measured startup, not long-running workloads. Commands that read secrets are dominated by keychain latency (Keychain, libsecret, Credential Manager), which is the same for every version.
- The binary doesn't use Bun's
--bytecodeyet, which could cut parsing time further.
Reproduce it
$export ENVSEC_DB=$PWD/db/store.sqlite && mkdir -p db$hyperfine -N --warmup 5 --runs 40 \$ -n "Effect 3" "effect3/node_modules/.bin/envsec list" \$ -n "Effect 4" "effect4/node_modules/.bin/envsec list" \$ -n "Bun" "./envsec list"Grab the binary from the v1.1.0-beta.1 release, or install it from the npm and Homebrew beta channels.
Conclusion
Effect 4 pulled its weight on its own: a quarter less startup time and memory just by upgrading the library. The real leap, though, came from three things together: a more compact Effect core, dependencies cut to the bone, and a runtime that starts in 4 ms. For a CLI you run all day, going from nearly half a second to 32 ms is the difference between a delay you notice and one you don't.