Skip to content

Latest commit

 

History

2,542 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rifty

Browser-based, Node-compatible runtime + WASI runner — a WebContainers-like system built from scratch. Run Express, npm install, a dev server, even WASI binaries, inside a browser tab. Pet project; goal is deep understanding of how these systems work, plus a practical "Express + npm install in the browser".

Status: active milestone M11 (Consumer Ready). APIs are 0.x and may move. See docs/ROADMAP.md, docs/adr/, docs/public/compat/.

Position: open, self-hostable, browser-local runtime infrastructure. See docs/public/open-runtime-position.md and the trust model; compatibility claims live in docs/public/compat/.

Live site: rifty.dev. Public sandbox: play.rifty.dev.

Packages

npm i @riftydev/sdk is the umbrella front door (packages/rifty): framework-free createSandbox() plus twelve runtime layers on subpaths (@riftydev/sdk/vfs, @riftydev/sdk/runtime, @riftydev/sdk/net, …). Each layer is also its own package; agent, workbench, shadow-registry and eddy ship standalone. Seventeen names, all ESM, ship .d.ts, released in lockstep under the @riftydev scope (publishing).

Package What it is Runs in
@riftydev/sdk Umbrella: one-install front door + createSandbox() browser + Worker
@riftydev/io EventEmitter, Buffer, node-compatible streams anywhere
@riftydev/vfs Virtual FS: in-memory + OPFS, with a sync mirror anywhere
@riftydev/kernel Processes / scheduling / IPC (Worker-as-process, SAB) browser + Worker
@riftydev/net node:net/http/https/ws + node:sqlite (sql.js) anywhere
@riftydev/runtime-js Node-compatible JS runtime: CJS/ESM loader + builtins browser + Worker
@riftydev/runtime-wasi WASI (preview1) runner for .wasm guests browser + Worker
@riftydev/npm-client In-browser npm: semver, registry, unpack, link, install anywhere
@riftydev/shell Tiny bash-flavoured shell over @riftydev/vfs anywhere
@riftydev/terminal xterm.js terminal wrapper browser
@riftydev/service-worker Service Worker preview/HMR routing bridge browser
@riftydev/git git over the VFS (isomorphic-git); smart-HTTP via a CORS proxy anywhere
@riftydev/ts-language-service In-browser ts.LanguageService over the VFS anywhere
@riftydev/agent Headless Pi coding-agent loop over public project hosts browser
@riftydev/workbench Framework-free project / session / run / preview API (owner Worker) browser + Worker
@riftydev/shadow-registry Data tables of in-browser npm substitutions anywhere
@riftydev/eddy Opt-in fast-install resolver service (ADR-0182) Node service
npm install @riftydev/sdk                 # everything + createSandbox() (the front door)
npm install @riftydev/vfs            # just the VFS
npm install @riftydev/npm-client     # just the npm resolver/installer
# …or any combination — they share singletons when installed at the same version

Quick start (use a part)

import { MemoryVfs, joinPath } from '@riftydev/vfs';

const vfs = new MemoryVfs();
await vfs.mkdir('/proj', { recursive: true });
await vfs.writeFile('/proj/hello.txt', 'hi from rifty');
console.log(await vfs.readFileText(joinPath('/proj', 'hello.txt'))); // "hi from rifty"
import { parse, matchesRange, pickBestVersion } from '@riftydev/npm-client';

matchesRange('1.4.2', '^1.2.0');                              // true
pickBestVersion(['1.0.0', '1.4.2', '2.0.0'], '^1.2.0');       // "1.4.2"

More runnable examples in examples/standalone-usage (pnpm --filter @rifty-examples/standalone start). Full product demo: the public sandbox or the local playground (pnpm dev).

Consuming rifty in your own app — read this first

The leaf packages (@riftydev/io, @riftydev/vfs, @riftydev/npm-client, @riftydev/shell, @riftydev/shadow-registry) are plain isomorphic JS and need nothing special. The runtime (runtime-js, runtime-wasi, kernel, service-worker) has hard browser prerequisites — without them it will not boot:

  1. Cross-origin isolation is mandatory (for SharedArrayBuffer + Atomics.wait, used by synchronous IPC and sync fs). Serve with:

    Cross-Origin-Opener-Policy: same-origin
    Cross-Origin-Embedder-Policy: credentialless
    Cross-Origin-Resource-Policy: cross-origin
    

    Then globalThis.crossOriginIsolated === true. Header-less static hosts (e.g. GitHub Pages) do not serve this tier; Netlify / Cloudflare Pages / Vercel can work when configured with equivalent headers. The only thing that boots without isolation is the explicit shared-memory-free tier, createSandbox({ requireCrossOriginIsolation: false }): eval and files work, execSync throws a named NotImplementedError, child processes run same-realm (the no-COI install/build toolchain, ADR-0375, is on main and unreleased). Copy-paste configs: netlify.toml, apps/playground/public/_headers, and the dev-server headers in apps/playground/vite.config.ts. If you embed rifty in an iframe or app browser, the parent page must also be cross-origin isolated and the iframe must include allow="cross-origin-isolated"; otherwise open rifty as a top-level page.

  2. A bundler that emits module Worker entries. With Vite, import each entry with ?worker&url, pass the returned URL to rifty, and set worker: { format: 'es' }. An app that imports @riftydev/runtime-js/worker must list @riftydev/runtime-js as a direct dependency. Do not pass new URL('…', import.meta.url) indirectly to rifty: Vite cannot see the eventual new Worker(...), so a production build will not emit the Worker bundle.

  3. A service worker for preview/HMR routing — list @riftydev/service-worker as a direct dependency, build its @riftydev/service-worker/sw entry, and register it with registerServiceWorker(url). No prebuilt sw.js; it must be bundled (the playground does this in apps/playground/build/sw-plugin.ts).

  4. Same-origin WASM assets when used: node:sqlite needs sql.js/dist/sql-wasm.wasm reachable (inject a locateFile via initSqliteEngine({ locateFile }), awaited once before any DatabaseSync). Registry-backed esbuild owns and attests its own runtime asset.

Given those, the umbrella's createSandbox() does the boot wiring (capability probe → COI guard → VFS backend with memory fallback → service-worker registration → runtime worker) and hands you a live RuntimeController:

npm install @riftydev/sdk @riftydev/runtime-js @riftydev/service-worker
import runtimeWorkerUrl from '@riftydev/runtime-js/worker?worker&url';
import { checkCapabilities, createSandbox } from '@riftydev/sdk';

async function main(): Promise<void> {
  const caps = checkCapabilities();
  if (!caps.sufficient || !caps.capabilities.crossOriginIsolated) {
    throw new Error(caps.summary);
  }

  const sandbox = await createSandbox({
    workerUrl: runtimeWorkerUrl,
    serviceWorkerUrl: '/sw.js',
  });
  try {
    await sandbox.runtime.eval('console.log("hello from a Worker")');
  } finally {
    sandbox.dispose();
  }
}

void main();

Target es2022; Chrome-first (cross-browser e2e infra exists, see docs/public/compat/).

Develop (monorepo)

pnpm install
pnpm dev                  # playground at http://localhost:5273

pnpm typecheck            # workspace-wide
pnpm lint                 # biome
pnpm build:libs           # build all publishable packages to dist/
pnpm test:run             # unit
pnpm test:parity          # node parity runner
pnpm test:e2e             # playwright (chromium)

In-repo exports point at raw TypeScript src/ (dev/HMR needs no build); published packages point at built dist/ via publishConfig. See docs/public/publishing.md and ADR-0070.

Architecture

Five layers, top-down only — no reverse imports:

apps/playground          (UI: Monaco editor + xterm terminal — SolidJS, isolated)
shell · terminal · npm-client
runtime-js (Node API) · runtime-wasi (WASI)
kernel (processes, scheduling, IPC)
vfs · io · net   (+ service-worker, shadow-registry)

UI framework (SolidJS) is confined to apps/playground/** (D-002).

Contributing

Rules live in CLAUDE.md: TDD (tests/parity-case first), no any, no silent stubs (throw NotImplementedError), one delivered behavior per PR. Decisions are recorded as ADRs.

License

First-party rifty packages publish as MIT. See the open runtime position for the claim boundary; transitive dependency audit is tracked separately.

About

Browser-based Node-compatible runtime + WASI runner

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages