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

@specy/x86

v2.0.5

Published

X86 Emulator for JavaScript using Blink

Readme

@specy/x86

TypeScript wrapper around the blink x86-64 emulator, compiled to WebAssembly via Emscripten.

Usage

import { createX86Emulator } from '@specy/x86'

const emulator = await createX86Emulator({
  callbacks: {
    stdout: (charCode) => process.stdout.write(String.fromCharCode(charCode)),
    stderr: (charCode) => process.stderr.write(String.fromCharCode(charCode)),
  },
})

const result = await emulator.compile(`
global _start
section .text
_start:
  mov rax, 60
  xor rdi, rdi
  syscall
`)

if (result.ok) {
  await emulator.runUntilBlocked()
  console.log(emulator.stopReason)
}

createX86Emulator() initializes and awaits the wasm module internally; no wasm URL or byte buffer needs to be passed by the caller. NASM is the default assembler; pass mode: 'GNU_trunk' to use GNU as instead.

Compile and run

// Assemble and link source, returns { ok, errors }
const result = await emulator.compile(sourceCode)

// Run until a breakpoint, syscall block, or instruction limit is hit
await emulator.run(limit, breakpoints)

// Convenience wrapper — runs with no limit and no breakpoints
await emulator.runUntilBlocked()

run(limit, breakpoints) accepts an optional maximum instruction count and an array of 0-based source-line breakpoint indices. Breakpoints are resolved to native instruction addresses using the DWARF line information emitted by the assembler.

Check code without running

// Assembles and links but does not execute; updates the loaded program
const result = await emulator.checkCode(sourceCode)

Read registers and memory

const rax = emulator.getRegisterValue('rax')
const bytes = emulator.readMemoryBytes(address, length)

Undo history and call stack

Undo recording is opt-in. Call initialize(undoSize) after loading or compiling a program and before execution. undoSize is the maximum number of reversible instructions kept in history; internally this is a fixed-size circular buffer, so older entries are discarded when the buffer fills.

const result = await emulator.compile(sourceCode)
if (!result.ok) throw new Error(result.errors[0]?.error ?? 'Assembly failed')

// Keep the last 128 reversible instructions.
emulator.initialize(128)

await emulator.step()
await emulator.step()

const [latestStep] = emulator.getUndoHistory(1)
console.log(latestStep?.pc, latestStep?.mutations)

if (emulator.canUndo()) {
  emulator.undo()
}

History entries include register writes, memory writes, flag changes, and call-stack mutations. getUndoHistory(max) returns the newest entries first, as ExecutionStep[], which is useful for instruction-history UIs.

for (const step of emulator.getUndoHistory(10)) {
  console.log(step.pc, step.mutations)
}

The call stack is tracked while history recording is enabled. Calls push frames and returns pop them; undo restores the previous call-stack snapshot along with registers, flags, PC, SP, and captured memory bytes.

emulator.initialize(64)
await emulator.run(20)

const frames = emulator.getCallStack()
console.log(frames.map((frame) => frame.name))

When history is enabled, run() uses traced stepping so each executed instruction can be undone. If history is disabled with initialize(0) or by never calling initialize, normal fast execution remains available and canUndo() returns false.

Very large or truncated memory writes may not be reversible. In that case canUndo() returns false for the latest step, and calling undo() throws instead of restoring a partial state.

Events

emulator.on('stateChange', (state) => { /* ... */ })
emulator.on('stdout',      (charCode) => { /* ... */ })
emulator.on('stderr',      (charCode) => { /* ... */ })
emulator.on('signal',      (signal)   => { /* ... */ })
emulator.on('inputRequest', ()        => { /* ... */ })

Callbacks passed to createX86Emulator() and handlers registered with on() may return either void or Promise<void>. Async callbacks are observed but not awaited by the emulator, so UI work can be scheduled without blocking execution. stdin remains synchronous because it is called directly by Emscripten's filesystem; for non-blocking UI input, listen for inputRequest and call provideInput() when the user submits text.