dsteem release 0.12.2 on npmjs @blazeapps/dsteem
Follow-up to yesterday's Browser Testing harness post: the modernized
dsteemlibrary is published on npm as@blazeapps/[email protected]. This is the wrap-up post for the entire eleven-day series — Phase 0's planning doc to today'snpm publish. Twelve posts, eight phases, three milestone updates, one published package, and two same-day patches after a real Expo consumer surfaced bugs the harness couldn't catch. The honest version of how shipping software actually goes.

Live Now
npm install @blazeapps/dsteem
| Surface | Link |
|---|---|
| npm package | https://www.npmjs.com/package/@blazeapps/dsteem |
| GitHub source | https://github.com/blazeapps007/dsteem |
| API docs | https://blazeapps007.github.io/dsteem/ |
| Browser test harness | https://blazeapps007.github.io/dsteem/harness/ |
| Browser bundle (CDN) | https://unpkg.com/@blazeapps/dsteem@^0.12/dist/dsteem.browser.global.js |
Migration in one line: change every from 'dsteem' to from '@blazeapps/dsteem'. The public API is byte-identical to v0.11.3 — same classes, same methods, same return types. The legacy [email protected] on npm is left untouched; this is a clean parallel publish under a new scope.
Supported targets: Node.js 22+, modern browsers (via bundler or direct <script>), and React Native / Expo SDK 50–55 (Metro-ready as of v0.12.2 — Buffer + Node built-ins inlined inside the package; no consumer-side global.Buffer shim required).
v0.12.0 → v0.12.1 → v0.12.2 — Two Patches, One Real Bug
The first publish hit an Expo consumer within hours. The error:
_blazeappsDsteem.Client is not a constructor
Root cause: our package.json "browser" condition pointed at the IIFE bundle, which has no module-system exports — it assigns to var dsteem = (function(){...})() and is meant for <script src=""> consumption. Metro picked the IIFE thinking it was a module, then exposed undefined instead of Client.
v0.12.1 rerouted the browser/react-native conditions away from the IIFE and onto the Node CJS build (dist/index.cjs). That fixed the "not a constructor" error — but exposed a deeper bug:
Requiring unknown module "buffer"
The Node CJS build externalizes Node built-ins via esbuild's dynamic __require() shim. __require("buffer") works in Node (Node has buffer built in). It does not work in Metro, because Metro can't statically analyze indirect requires — it never bundles buffer, and the runtime blows up. Verified diagnosis:
$ grep -oE '__require[0-9]?\("(buffer|stream|util|assert)"\)' dist/index.cjs
__require("assert")
__require("buffer")
__require("stream")
__require("util")
Same for dist/index.mjs. Four ticking time bombs in the package's RN entry. The IIFE bundle had zero such leaks (the same grep returns empty against it — it's fully polyfilled via esbuild-plugin-polyfill-node), but it's not module-consumable. So we had a polyfilled bundle that bundlers couldn't use, and a module bundle that wasn't polyfilled. Neither one worked for RN.
v0.12.2 is the real fix. We added a third tsup build target — same source as the IIFE (src/index-browser.ts), same polyfillNode plugin, same noExternal: [/.*/] — but emitted as ESM + CJS modules instead of an IIFE. The new files dist/index.browser.{mjs,cjs} have:
- All Node built-ins (
buffer,stream,util,assert,process,crypto, …) inlined directly into the bundle (verified:grep -oE '__require\("(buffer|stream|util|assert)"\)' dist/index.browser.*returns zero matches) - Real named exports:
export { Client, PrivateKey, … }in the .mjs;exports.Client = Client;in the .cjs - Same 22 named exports as the Node build, so the public surface is identical regardless of which entry the consumer's bundler picks
The exports map now routes react-native and browser conditions to dist/index.browser.mjs — the polyfilled module build. The Node entries (dist/index.{cjs,mjs}) stay unchanged for Node consumers (small, externalized, ~230 KB each). The IIFE bundle stays in dist/ for direct unpkg / <script> consumption. Three target classes, three correct entry points:
| Consumer | Resolves to | Why |
|---|---|---|
Node import | dist/index.mjs | small (229 KB), uses Node's native buffer/stream/util/assert |
Node require | dist/index.cjs | same, for CJS callers |
| React Native (Metro) | dist/index.browser.mjs | all Node built-ins inlined — no Metro "unknown module buffer" |
| Browser bundlers (webpack/vite/rollup) | dist/index.browser.mjs | same — works in any browser bundle target |
Direct <script> / unpkg | dist/dsteem.browser.global.js | IIFE, unchanged |
The lesson: paste-and-test on a real RN app within the first hour caught what eight days of internal testing and the Browser Testing harness didn't catch. The harness validates op semantics in a normal browser — it doesn't exercise Metro's module-resolution path or its handling of indirect __require() calls. Two failure modes, one day apart, one real consumer made the difference. v0.12.1 was a partial fix shipped in good faith that exposed the deeper bug v0.12.2 actually patches.
The Whole Journey, in One Place
| # | Day | Phase | What landed |
|---|---|---|---|
| 1 | 2026-05-30 | Phase 0 | The plan + locked technical decisions + Phase 0 golden crypto fixtures captured |
| 2 | 2026-05-31 | Phase 1 | GitHub Actions CI workflow (replaces CircleCI + Travis) |
| 3 | 2026-06-01 | Phase 2 | tslint → ESLint 9 flat config + typescript-eslint 8 |
| 4 | 2026-06-02 | Phase 3 | TypeScript 3.1 → 5.6 + strict mode |
| 5 | 2026-06-03 | Phase 4 | Native [email protected] (HIGH-severity CVE) → @noble/curves + @noble/hashes |
| 6 | 2026-06-04 | Phase 5 | browserify+tsify+babelify+uglifyjs → tsup; dual ESM/CJS; 606 packages deleted |
| 7 | 2026-06-05 | Phase 6 | mocha 5 → 11; nyc → c8 (70% gate); Karma+Sauce → Playwright headless |
| 8 | 2026-06-06 | Phase 7 | typedoc 0.13 → 0.28; docs regenerated |
| 9 | 2026-06-07 | Milestone | All eight phases pushed as bef8443 to BlazeDevelopment; CI green on Node 22 + 24 + Playwright |
| 10 | 2026-06-07 | Update | typedoc site deployed to GitHub Pages (db9440e) |
| 11 | 2026-06-08 | Update | Browser Testing harness deployed alongside docs (fa27358) — 47 ops × 10 tabs |
| 12 | 2026-06-08 | Release | @blazeapps/[email protected] → same-day patches 0.12.1 (RN exports rerouting, incomplete) → 0.12.2 (polyfilled browser/RN module build — the real RN fix) |
Twelve posts. One publish. Two same-day patches. Plan locked in Phase 0, executed in order, only the patches were reactive.
What Actually Shipped to npm
$ npm pack --dry-run
@blazeapps/[email protected]
1.7 kB LICENSE
9.1 kB README.md
2.3 kB package.json
359.4 kB dist/dsteem.browser.global.js (IIFE — for <script>/unpkg)
737.4 kB dist/index.browser.cjs (polyfilled CJS — browser/RN CJS bundlers)
736.9 kB dist/index.browser.mjs (polyfilled ESM — browser/RN/Metro modern path)
229.9 kB dist/index.cjs (Node CJS — externalized built-ins)
228.9 kB dist/index.mjs (Node ESM — externalized built-ins)
92.0 kB dist/index.d.ts (types)
92.0 kB dist/index.d.mts (types, dual-package)
+ source maps for each
─────────────────────────
15 files, 1.8 MB packed, 9.2 MB unpacked
Nothing else — no src/, no test/, no node_modules, no SteemitPosts. The "files": ["dist"] field in package.json is doing exactly what it says.
Why so big now? v0.12.1 packed at 795.7 kB. v0.12.2 packs at 1.8 MB. The extra ~1 MB is the inlined polyfill graph (buffer + stream + util + assert + process + events + …) baked into the two new index.browser.* files. The tradeoff is intentional: bundler consumers get a self-contained module that works on first install without any host-side shims. Node consumers still get the lean 230 KB build.
Headline Numbers
What changed underneath, in raw figures:
| Metric | v0.11.3 (Nov 2019) | v0.12.2 (Jun 2026) | Change |
|---|---|---|---|
| Node floor | Node 8 era | Node 22 LTS | +14 majors |
| TypeScript | 3.1.6 | 5.6.3 | +4 majors |
| Browser bundle | 782 KB | 351 KB (IIFE) + 737 KB (polyfilled module) | IIFE −55%, module new |
| Crypto backend | native [email protected] (HIGH-severity CVE) | @noble/curves + @noble/hashes (audited pure-JS) | CVE eliminated, no native build |
| Lint | tslint (deprecated 2019) | ESLint 9 flat config | 0 warnings, 0 errors |
| Test runner | mocha 5 | mocha 11 | +6 majors |
| Coverage | nyc | c8, 70% line gate enforced in CI | replaced |
| Browser tests | Karma + Sauce Labs | Playwright headless Chromium/Firefox/WebKit | replaced |
| CI | CircleCI + Travis | GitHub Actions matrix Node 22 + 24 + Playwright | replaced |
| Build toolchain | browserify + tsify + babelify + uglifyjs + dts-generator + Makefile | tsup (esbuild) | unified |
| node_modules entries | (baseline) | baseline −606 packages | massively reduced |
| Dead polyfills purged | core-js@2 + regenerator-runtime + whatwg-fetch + node-fetch | all removed | dead since 2019 |
| Module format | CJS only | dual ESM + CJS + polyfilled-ESM/CJS + IIFE via exports map | additive |
| React Native / Expo | unsupported (or required Metro hacks) | supported out of the box via polyfilled module build | new in 0.12.2 |
Production npm audit | 1 HIGH (secp256k1) | 0 vulnerabilities | clean |
| Public API | (baseline) | identical — same classes, same methods, same return types | preserved |
| Tests passing | (baseline) | 50 in 422 ms (offline slice) | parity |
| Manual op coverage | none | 47 ops × 10 tabs in the browser harness | added |
The single decision that shaped everything else was preserve the public API exactly. That made each phase independently shippable, made the migration a one-line import rename, and made the v0.11.3 golden crypto fixtures load-bearing as the regression gate.
How a Consumer Migrates
Node, browsers (bundled), browsers (direct <script>)
Step 1: Update package.json:
- "dsteem": "^0.11.3"
+ "@blazeapps/dsteem": "^0.12.2"
Step 2: Update every import:
- import {Client, PrivateKey} from 'dsteem'
+ import {Client, PrivateKey} from '@blazeapps/dsteem'
- const {Client} = require('dsteem')
+ const {Client} = require('@blazeapps/dsteem')
Step 3: Ensure your runtime is Node 22 LTS or newer.
That's it. Same Client.database.*, same client.broadcast.*, same PrivateKey.fromLogin, same cryptoUtils.
React Native / Expo
RN's JS runtime (Hermes/JSC) doesn't ship Web Crypto's getRandomValues, which @noble/curves needs for ECDSA entropy. As of v0.12.2, Buffer and all Node built-ins are inlined inside the package itself — no consumer-side global.Buffer = Buffer shim required. Only one polyfill is still needed:
npm install @blazeapps/dsteem react-native-get-random-values
At the very top of your app entry (index.js / App.tsx), before any @blazeapps/dsteem import:
import 'react-native-get-random-values'
Then everywhere else:
import {Client, PrivateKey} from '@blazeapps/dsteem'
const client = new Client('https://api.steemit.com')
Clear Metro's cache once after the upgrade:
npx expo start --clear
No metro.config.js tweaks needed. Verified on Expo SDK 50–55 / RN 0.83. If you were on v0.12.1: you can now delete the import {Buffer} from 'buffer' + global.Buffer = Buffer lines from your app entry — both are unnecessary in v0.12.2.
Security Outcome
The single most important reason for this work:
- Eliminates the
[email protected]high-severity advisory — the only known CVE in the v0.11.x production dependency tree. Replaced by@noble/curves(same library powering ethers / viem) and@noble/hashes— both audited, pure-JS, no native bindings. - Eliminates native-build attack surface. No more
node-gyp, no prebuild binaries to verify, no platform-specific install failures. - Removes deprecated/unmaintained packages: tslint, core-js@2, dts-generator, the entire browserify toolchain.
- All remaining production deps (
bs58,bytebuffer,verror,@noble/*) are either current-maintenance or known-stable.
npm audit on the production tree of @blazeapps/[email protected]: 0 vulnerabilities.
The Three Surfaces
GitHub Pages serves the same workflow that publishes the npm package — typedoc docs at / and the manual op harness at /harness/. So as a consumer you have three independent ways to verify the package before trusting it with your account:
npm installthe actual artifact, run your test suite against it- API docs — every public type, class, method documented at https://blazeapps007.github.io/dsteem/. New v0.12.0 additive exports (BroadcastAPI, CreateAccountOptions, the RC interfaces) are all in the sidebar
- Browser harness at https://blazeapps007.github.io/dsteem/harness/ — fill in a form for any of the 47 operations, sign with a pasted key (use a test account!), inspect the signed transaction, optionally broadcast. Build-only mode is the default; broadcast is per-op opt-in.
Full Series
- Day 1 — Phase 0: dsteem Modernization Plan
- Day 2 — Phase 1: CI Plumbing on GitHub Actions
- Day 3 — Phase 2: Retiring tslint, Adopting ESLint 9
- Day 4 — Phase 3: TypeScript 3.1 → 5.6 + Strict Mode
- Day 5 — Phase 4: The @noble Crypto Swap
- Day 6 — Phase 5: tsup, Dual ESM/CJS, and 606 Packages Deleted
- Day 7 — Phase 6: Mocha 11, c8 Coverage, and Playwright Browser Tests
- Day 8 — Phase 7: typedoc 0.13 → 0.28, and What's Left for Release
- Day 9 — Milestone: Code Pushed to BlazeDevelopment Branch
- Day 9 — Update: Docs Live on GitHub Pages
- Day 10 — Update: Browser Testing Harness Live
- Day 11 — Release:
@blazeapps/[email protected]Live on npm (you are here)
Twelve posts, three same-day publishes (0.12.0 / 0.12.1 / 0.12.2), zero breaking changes for downstream consumers.
What's Next
This wraps the v0.12 release cycle. The repo and the package are stable at v0.12.2. The next things, when they happen, will be issue-driven rather than scheduled:
- More bug reports — the same-day v0.12.1 + v0.12.2 patches are the model. Hit the package with your real app; if anything diverges from v0.11.x behaviour, that's a P0 release blocker, and it'll get patched fast.
- v0.12.x patch releases — bug fixes only, additive-only changes. Same
^0.12range stays compatible. - v0.13.0 — only when a meaningful set of new Steem-side features warrants it (hardforks, new operation types, etc.). Nothing planned right now.
- Long-running CI — the Playwright browser smoke runs on every push, so regressions in the IIFE bundle are caught immediately. The Pages workflow republishes docs + harness on every push too, so the live surfaces never go stale. Open question: should CI also build the Metro target and try to resolve
@blazeapps/dsteemfrom a minimal Expo fixture? That's the missing layer that would have caught the v0.12.0 → v0.12.1 → v0.12.2 chain on the first publish, not the first consumer. Likely a v0.12.3 follow-up.
The legacy [email protected] on npm stays where it is. Anyone with an existing "dsteem": "^0.11.x" keeps getting 2019-vintage behaviour until they explicitly opt into @blazeapps/dsteem.
How You Can Help
- 🏗️ Adopt it:
npm install @blazeapps/dsteemin your real app, swap your imports, run your tests - 📱 Try it on Expo / React Native: install + the single polyfill (
react-native-get-random-values), drop your dsteem code in — and tell me if anything in the 47-op API misbehaves on the RN substrate. The v0.12.1/v0.12.2 patches only happened because a real consumer surfaced real bugs; that's the loop we need to keep running. - 🔬 Stress the harness: visit https://blazeapps007.github.io/dsteem/harness/, click through every tab, sign a few ops, broadcast a tiny
custom_jsonfrom a test account - 🐛 File issues: https://github.com/blazeapps007/dsteem/issues — anything that diverges from v0.11.x is a release blocker
- ⭐ Star the repo — it makes the modernization more findable for the next person looking up "dsteem typescript"
- 💬 Comment with your migration experience — what worked, what didn't, especially on React Native
🙏 Credits
Original dsteem by Johan Nordberg (2017–2019) — @blazeapps/dsteem is a modernization, not a replacement. Every architectural decision he made — the discriminated-union Operation type, the byte-exact FC serialization, the helper-class layering on Client, the canonical-signature retry loop — is preserved exactly. The work over the last eleven days was about replacing the substrate, never the design.
This work was developed with Claude AI assistance. All technical decisions reflect Steem ecosystem needs and the hard requirement of zero breaking changes.
Support Secure Steem Development
If you value proactive engineering, UX polish, and performance optimizations for the STEEM ecosystem, please consider supporting my witness: blaze.apps
🗳️ Vote Here:
Vote for blaze.apps Witness
Disclaimer: This post announces @blazeapps/[email protected] on the npm registry. Source: https://github.com/blazeapps007/dsteem. The public API matches [email protected] — migration is a one-line import rename. React Native consumers need react-native-get-random-values for ECDSA entropy (see "How a Consumer Migrates" above); everything else is inlined inside the package.