npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

justerm-wasm-decode

v0.7.0

Published

WASM decoder for justerm's wire format: structure-of-arrays cell columns + format-owned colour/flag helpers. See ADR-0008.

Readme

justerm-wasm-decode

The canonical web decoder for justerm's binary wire format — the engine's native decode compiled to WASM, so a web consumer shares one decoder with the native backend instead of hand-writing (and re-syncing) a TypeScript mirror.

Typical data path: the native engine encodes a damage frame in your backend, the bytes cross your IPC (e.g. a Tauri Channel), and in the webview this package decodes them into renderer-ready columns.

The decoder owns the fixed formats and standards (the wire records, the colour-ref encoding, the flag bit positions, the xterm 16–255 colour formula). Theme values (your 16 ANSI colours + default fg/bg) and render policy (inverse/dim/bold→bright, the font atlas, the cursor) stay yours. See ADR-0008.

Version-locked to the justerm crate: this package's version equals the engine version, so pinning one justerm version gives you a matching encoder (native) + decoder (this). wireVersion() lets you assert agreement at load.

Install

npm install justerm-wasm-decode

Usage

import { decodeFrame, buildPalette, flags, wireVersion } from "justerm-wasm-decode";
import { resolveRgb, decodeColorRef, FG, BG } from "justerm-wasm-decode/colors.js";

// Bundler target (Vite/webpack): the above imports work directly.
// Web target (no bundler): `import init, { ... } from "justerm-wasm-decode"; await init();` first.

console.assert(wireVersion() === 2); // optional: assert the backend encoder agrees

// --- once at startup / on theme change ---
// buildPalette fills 0..15 from your scheme's ANSI colours and 16..255 from the
// fixed xterm cube/grayscale. Keep your default fg/bg alongside (they are the
// `Default` colour ref, resolved by role — not part of the 256).
const palette = {
  colors: buildPalette(Uint32Array.from(scheme.ansi16)), // 16 × 0xRRGGBB
  defaultFg: scheme.defaultFg, // 0xRRGGBB
  defaultBg: scheme.defaultBg,
};
const F = flags(); // bit constants, read once

// --- per frame (e.g. an IPC message) ---
const frame = decodeFrame(wireBytes); // throws on a malformed buffer
// Structure-of-arrays columns (zero-copy views) + the span directory.
const { codepoints, fg, bg, extra, link, spans } = frame;
const flagBits = frame.flags; // note: the column; `flags()` above is the constants

for (let s = 0; s < spans.length; s += 5) {
  const line = spans[s], left = spans[s + 1], offset = spans[s + 3], count = spans[s + 4];
  for (let k = 0; k < count; k++) {
    const i = offset + k;
    const col = left + k;

    const fgRgb = resolveRgb(fg[i], palette, FG); // 0xRRGGBB
    const bgRgb = resolveRgb(bg[i], palette, BG);
    const bold = (flagBits[i] & F.bold) !== 0;
    if (flagBits[i] & F.wide_char_spacer) continue; // trailing half of a wide glyph

    // your adapter: map codepoints[i] -> atlas glyph, apply bold/inverse/dim,
    // resolve extra[i]/link[i] via sideTable/linkTable, place at (line, col).
  }
}

frame.free(); // release the column views — or scope with `using frame = decodeFrame(...)`

Lifetime of the columns

codepoints / fg / bg / flags / extra / link / spans are zero-copy views into WASM linear memory — the bulk data reaches JS with no per-cell boundary crossing. A view is invalidated when WASM memory grows, which the next decodeFrame call can trigger. Read or copy what you need from one frame before decoding the next, and free() the frame (or scope it with using) when done. The palette from buildPalette is an owned copy, so it is safe to keep across frames.

What the columns hold

One entry per cell, in span order. spans is a flat directory: 5 u32s per span — line, left, right, cell_offset, cell_count — where cell k of a span is column index cell_offset + k.

| Column | Type | Meaning | |--------|------|---------| | codepoints | Uint32Array | base Unicode codepoint (not an atlas glyph id) | | fg / bg | Uint32Array | colour references — pass to resolveRgb | | flags | Uint16Array | attribute + layout bits — test with flags() constants | | extra | Uint16Array | 1-based sideTable index for a grapheme cluster (0 = none) | | link | Uint16Array | 1-based linkTable index for an OSC 8 hyperlink (0 = none) |

frame.sideTable (string[]) and frame.linkTable (string[]) carry the referenced clusters/URIs.

Cursor

The frame also carries the engine's cursor as scalar getters (screen coordinates, 0-based):

| Getter | Type | Meaning | |--------|------|---------| | cursorRow / cursorCol | number | cursor cell position | | cursorVisible | boolean | false when the engine hides the cursor (DECTCEM ?25l) |

justerm reports the cursor; drawing it is your adapter's job. beamterm has no cursor primitive, so draw the caret by inverting fg/bg on the cell at (cursorRow, cursorCol) (or an overlay quad). A pure cursor move is included in the frame's damage spans (the old and new cells), so an incremental cell-invert renderer clears the previous caret and inks the new one without ghosting.

Colour helpers

  • resolveRgb(ref, palette, role) → 0xRRGGBB — resolves a fg[i]/bg[i] ref: Default → the role's default (FG/BG), Indexedpalette.colors[i], Rgb → passthrough. Alloc-free; call it per cell. It does not apply inverse/dim/hidden/bold→bright — that is your render policy.
  • buildPalette(ansi16) → Uint32Array(256) — the 256-colour table (0..15 your ANSI, 16..255 the fixed xterm standard). Build once per scheme.
  • decodeColorRef(ref){ kind: "default" } | { kind: "indexed", index } | { kind: "rgb", r, g, b }. For inspection; allocates, so prefer resolveRgb in the hot loop.

Flag constants (flags())

bold, dim, italic, underline, blink, inverse, hidden, strikethrough, wide_char, wide_char_spacer, wrapline — each the bit to AND against a flags[i] value. How to act on them (skip the spacer, bold→bright, dim) is your render policy. wrapline is engine reflow/copy metadata, usually ignored by a renderer.

License

Dual-licensed under MIT or Apache-2.0, at your option — same as the justerm crate.