# TypeScript and WebAssembly Performance

## Finding

The final TypeScript/Wasm facade is neutral or faster. Every shared median is
between 5.12% faster and 0.49% slower; every slower p95 is at most 2.19%.
Identity import/erase is 5.12% faster after removing non-Android registry
state.

The audit used three paired Node.js 22.23.1 processes on a GitHub-hosted Ubuntu
24.04 x86_64 runner. Values are nanoseconds per authored package call.
Negative deltas mean the current SDK is faster.

| Operation | Bytes | Old median ns | Current median ns | Delta | Current p95 ns | p95 delta |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Import and erase identity | 32 | 141,684 | 134,435 | -5.12% | 159,910 | -4.83% |
| Read cached public key | 0 | 10 | 10 | 0.00% | 10 | 0.00% |
| NIP-44 encrypt | 32 | 404,194 | 403,169 | -0.25% | 438,495 | -1.65% |
| NIP-44 decrypt | 32 | 379,507 | 376,219 | -0.87% | 383,524 | -7.21% |
| NIP-44 encrypt | 1,024 | 400,769 | 402,733 | +0.49% | 476,286 | -0.55% |
| NIP-44 decrypt | 1,024 | 385,368 | 386,885 | +0.39% | 422,006 | +1.20% |
| NIP-44 encrypt | 64,000 | 1,146,548 | 1,103,895 | -3.72% | 1,228,332 | +2.18% |
| NIP-44 decrypt | 64,000 | 1,177,319 | 1,153,489 | -2.02% | 1,170,370 | -1.96% |
| Sign NIP-01 event | 32 | 1,562,050 | 1,527,607 | -2.20% | 1,628,120 | -12.57% |
| Parse and verify event | 374 | 350,280 | 348,015 | -0.65% | 359,536 | -1.84% |

The 10-ns cached getter is an API-shape check, not an absolute performance
claim.

## Current-only baseline

| Operation | Bytes | Median ns | p95 ns |
| --- | ---: | ---: | ---: |
| Create NIP-59 rumor | 15 | 47,956 | 82,280 |
| Create NIP-17 text message | 15 | 113,490 | 119,251 |
| Create durable gift wrap | 15 | 3,204,829 | 3,498,655 |
| Authenticate and unwrap | 15 | 2,182,461 | 2,249,816 |
| Stream-encrypt attachment | 64,000 | 743,412 | 765,677 |

## Optimizations retained

- One package-level Wasm module is shared by CommonJS, Node ESM, browser, and
  Worker entry points. Consumers do not download or instantiate duplicate
  modules.
- `LocalIdentity` imports secret bytes once, caches the public key in the
  authored object, and does not allocate Android-only registry state.
- Public events, messages, metadata, and errors are frozen plain JavaScript
  values; generated `wasm-bindgen` classes stay internal.
- Complete encryption, signing, wrapping, relay, and media operations cross
  Wasm in coarse calls.
- Attachment APIs process bounded chunks instead of materializing an
  unbounded browser file.

## Footprint

| Artifact | Old bytes | Current bytes | Delta |
| --- | ---: | ---: | ---: |
| WebAssembly module | 1,636,562 | 2,860,071 | +74.76% |
| WebAssembly module, `gzip -n` | 1,291,087 | 1,675,061 | +29.74% |
| Packed npm artifact | n/a | 1,780,547 | Release-candidate baseline |

The Wasm growth reflects the complete relay, chat/account, Noise,
Negentropy, and media surface. The packed artifact still contains exactly one
Wasm binary; its CommonJS, ESM, browser, and Worker facades reference that
same file.

## Developer guidance

- Initialize the package once per JavaScript realm and reuse the resulting
  module. Do not instantiate Wasm for every account or message.
- Import one identity per authenticated session, clear the caller-owned secret
  buffer, and call `erase()` only for logout or early authority removal.
- Move bulk crypto, parsing, reconciliation, and attachment work to a dedicated
  Worker when it would block a browser event loop.
- Pass complete bounded strings, events, relay frames, and byte chunks. Avoid
  per-field Wasm calls.
- Retain immutable parsed values at trust boundaries rather than reparsing
  relay JSON for each projection.
- Stream files and enforce application cancellation/backpressure; do not read
  an arbitrary `Blob` into one buffer.

The packed external consumer verifies CommonJS, Node ESM, browser, and Worker
exports. Run performance through the manual workflow or the commands in
[Performance Benchmarks](../../benchmarks/README.md).

## Open work

Add real Chromium/WebKit/Firefox startup, memory, Worker-transfer, and mobile
browser measurements before promoting Node numbers to browser release gates.
Wasm size is the main JavaScript follow-up: dependency convergence or link
optimization must retain every vector and rerun the packed consumers.
