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

@arrow-js/sandbox

v1.0.0

Published

QuickJS/WASM-backed sandbox runtime for executing Arrow templates outside the host window realm.

Readme

@arrow-js/sandbox

ArrowJS

ArrowJS sandbox executes user-authored Arrow JavaScript or TypeScript inside an async QuickJS/WASM VM while rendering through trusted host DOM code.

Docs · API Reference · Playground

What this package does

@arrow-js/sandbox lets you run untrusted Arrow code without executing that code in the page's window realm.

It provides:

  • an async QuickJS/WASM runtime for user-authored modules
  • AST-based preprocessing for implicit Arrow imports and template extraction
  • a sandbox-specific @arrow-js/core shim
  • a host DOM renderer and delegated event bridge

Install

pnpm add @arrow-js/sandbox

Basic usage

import { sandbox } from '@arrow-js/sandbox'

const view = sandbox({
  source: {
    'main.ts': `
      const state = reactive({ count: 0 })

      export default html\`<button @click="\${() => state.count++}">
        Clicked \${() => state.count}
      </button>\`
    `,
  },
})

view(document.getElementById('app')!)

Arrow identifiers such as html, reactive, component, pick, and props can be auto-injected when they are used as free identifiers. Explicit user imports are preserved.

Multi-file modules

const view = sandbox({
  source: {
    'main.ts': `
      import App from './App.ts'

      export default App
    `,
    'state.ts': `
      import { reactive } from '@arrow-js/core'
      export const state = reactive({ count: 0 })
    `,
    'App.ts': `
      import { html } from '@arrow-js/core'
      import { state } from './state.ts'

      export default html\`
        <button @click="\${() => state.count++}">
          Clicked \${() => state.count}
        </button>
      \`
    `,
  },
})

view(mountPoint)

Supported virtual imports:

  • relative imports between provided virtual files
  • .ts, .js, .mjs, and index.* fallback resolution
  • @arrow-js/core, resolved to the sandbox shim

Unsupported imports fail fast. There is no network fetch fallback.

API

export interface SandboxProps {
  source: Record<string, string>
  shadowDOM?: boolean
  onError?: (error: Error | string) => void
  debug?: boolean
}

export interface SandboxEvents {
  output?: (payload: unknown) => void
}

export function sandbox<T extends {
  source: object
  shadowDOM?: boolean
  onError?: (error: Error | string) => void
  debug?: boolean
}>(
  props: T,
  events?: SandboxEvents
): ArrowTemplate

sandbox() returns an Arrow template. You can mount it directly, or compose it inside a larger Arrow template:

html`<section>${sandbox({ source })}</section>`

The rendered host element is always <arrow-sandbox>.

Source requirements:

  • exactly one entry file: main.ts or main.js
  • optional main.css, injected into the sandbox host root
  • all other entries are virtual JS/TS/MJS modules

shadowDOM defaults to true. When enabled, the sandbox mounts into an open shadow root on <arrow-sandbox>. When disabled, it mounts into the element’s light DOM instead.

Sandbox output bridge

The optional events.output callback receives values emitted from inside QuickJS through the global output(payload) function:

const view = sandbox(
  {
    source: {
      'main.ts': `
        output({ status: 'ready' })
        export default html\`<div>Sandbox Ready</div>\`
      `,
    },
  },
  {
    output(payload) {
      console.log(payload)
    },
  }
)

output(payload) accepts one payload value. The payload is serialized to plain data before it crosses from the VM into the host.

Security model

  • User-authored logic runs inside QuickJS/WASM.
  • The host page mutates the real DOM through trusted renderer code only.
  • Event listeners on the real DOM forward sanitized payloads back to the VM.
  • The sandbox does not receive direct access to window, document, DOM nodes, storage, or arbitrary browser APIs.
  • html templates are preprocessed into descriptors. The host never evaluates user expressions.
  • DOM listeners in the host never attach raw user callbacks from sandbox code.
  • Synthetic sandbox events preserve common access patterns such as event.target.value and event.currentTarget.checked without exposing live host DOM nodes.

Explicitly bridged globals currently include setTimeout, clearTimeout, setInterval, clearInterval, and a restricted fetch() proxy. The host owns the real timers and networking, but the registered callbacks and response handling still execute inside QuickJS.

Event payloads are forwarded as plain data. The VM receives a narrow snapshot, not a live DOM event object.

The sandbox exposes event.target, event.currentTarget, and event.srcElement as plain data snapshots with a deliberately small surface: value, checked, id, and tagName. Compatibility shortcuts such as event.value and event.checked are still present, but event.target.value is the preferred shape.

Sandboxed fetch

The sandbox fetch() bridge is intentionally narrower than browser window.fetch:

  • only absolute https: URLs are allowed, plus http: for localhost addresses
  • no Request objects, no relative URLs, and no inherited browser request context
  • credentials are always forced to omit
  • referrerPolicy is always forced to no-referrer
  • mode is always forced to cors
  • request headers are user-supplied only and sensitive ambient headers such as authorization, cookie, origin, referer, and user-agent are blocked
  • responses are exposed as a small Response-like object with ok, status, statusText, url, redirected, headers, text(), json(), and arrayBuffer()
  • requests time out after 15 seconds and responses are capped at 1 MB

This bridge is designed to avoid ambient page credentials and host DOM access. It is still routed through the browser networking stack, so browser-controlled metadata such as Origin or User-Agent may still exist at the HTTP layer.

Supported subset

  • text interpolation
  • attribute interpolation
  • event bindings such as @click
  • nested elements
  • sync component() composition
  • component emits via component((props, emit) => ...) and parent listeners via Child(props, { eventName })
  • async component() composition with VM-owned fallback/render/error handling
  • pick() / props() narrowing for component props
  • global output(payload) host bridge
  • reactive updates inside the VM
  • restricted bridged fetch() requests and JSON/text response handling
  • bridged timer callbacks via setTimeout and setInterval
  • arrays and conditional child regions
  • multi-root templates without a wrapper element

Unsupported or partial

  • full @arrow-js/core parity
  • keyed list diffing
  • direct DOM refs or real DOM node access
  • arbitrary external imports
  • browser API access without an explicit bridge
  • hard CPU and memory isolation

Current limitations

  • This is not yet a hard boundary against CPU or memory exhaustion.
  • Memory limits are applied to the QuickJS runtime, but denial-of-service hardening still needs more work.
  • TypeScript support uses typescript.transpileModule, not full semantic type-checking.
  • Template support is intentionally narrower than the standard Arrow host runtime.

Development

pnpm --filter @arrow-js/sandbox sync:vm
pnpm --filter @arrow-js/sandbox demo
pnpm exec vitest run packages/sandbox/src/index.spec.ts
pnpm exec playwright test -c playwright.sandbox.config.ts

The demo includes a weather mini-app that fetches current conditions from the public Open-Meteo forecast API.