js/sim

js/sim.ts

fino:sim — run code in a deterministic Realm against an explicit world.

A simulation is an ordinary child Realm with deterministic time and randomness, a deny-by-default import map, and parent-owned Facades for every external service. The harness observes the existing Realm transport to produce a journal; it does not introduce a second RPC or capability system.

The child owns its local values and execution state. The parent owns Facade implementations, journal copies, and Realm cleanup. Filesystem and network fakes are separate adapters and are not installed implicitly.

Example

import { simulate } from 'fino:sim';

const report = await simulate({
  entry: './worker.ts',
  seed: 'checkout-flow',
  world: {
    'app:inventory': {
      available: async (sku: string) => sku === 'blue-shirt',
    },
  },
});

console.log(report.result, report.journal.entries);

Types

type SimProvider = Facade | Record<string, unknown>

A parent-owned implementation exposed to the guest through one module specifier.

type FakeRoute = | FakeResponse | ((request: FakeRequest) => FakeResponse | undefined | Promise<FakeResponse | undefined>)

Static response or request-aware handler stored in a FakeNet route table.

type SimCassetteOptions = { mode: 'record' } | { mode: 'replay'; data: Cassette }

In-memory cassette behavior for one run. File storage policy is caller-owned.

Interfaces

interface SimMock {

An adapter that contributes one or more parent-owned modules to a simulation world.

Methods

world(): Record<string, SimProvider>

Return the world entries installed by this adapter, keyed by module specifier.

interface FakeRequest {

Copied HTTP request delivered to a FakeNet route handler.

Properties

url: string

Absolute request URL.

method: string

Normalized uppercase HTTP method.

headers: Record<string, string>

Normalized request headers keyed by lowercase name.

body: Uint8Array<ArrayBuffer> | null

Buffered request body, or null for requests without a body.

interface FakeResponse {

HTTP response returned by a FakeNet route.

Properties

status?: number

HTTP status code. Defaults to 200.

statusText?: string

HTTP reason phrase. Defaults to the empty string.

headers?: Record<string, string>

Response headers.

body?: string | Uint8Array

Buffered response body. Strings are UTF-8 encoded.

interface SimulateOptions {

Options for one deterministic simulation run.

Properties

entry: string

Entry module whose default export the harness calls.

args?: unknown[]

Arguments passed to the entry module's default export.

seed?: number | string

Seed for guest randomness. Defaults to 0.

startTime?: number

Initial virtual Unix time in milliseconds. Defaults to 1700000000000.

world?: Record<string, SimProvider>

Parent-owned modules available to the guest, keyed by import specifier.

overrides?: ImportRule[]

Additional rules appended after world entries. Last match wins.

cassette?: SimCassetteOptions

Record Realm RPC traffic or replay it from an in-memory cassette.

faults?: SimFaultOptions

Seeded failure and response-latency policy for Facade calls.

interface SimFaultOptions {

Deterministic behavior injected at the Realm RPC boundary.

Properties

errorRate?: number

Probability from zero through one that an eligible call fails. Defaults to 0.

only?: string[]

Restrict injected failures to these Facade specifiers.

message?: string

Error text returned for an injected failure.

latency?: [number, number]

Inclusive virtual-millisecond range charged to each Facade response.

interface SimJournal {

Read-only projection of completed Facade calls from one simulation.

Readonly Properties

readonly entries: readonly SimCall[]

Completed calls in guest invocation order.

Methods

calls(specifier?: string, method?: string): SimCall[]

Return completed calls matching an optional Facade specifier and method.

interface SimReport<Result = unknown> {

Observable outcome of one simulation run.

Properties

result: Result

Value returned by the entry module's default export.

journal: SimJournal

Completed Facade calls observed at the Realm transport boundary.

seed: number | string

Seed used for this run.

startTime: number

Initial virtual Unix time used for this run.

cassette?: Cassette

Recorded transport frames when cassette.mode is record.

interface SweepOutcome<Result = unknown> {

Result or failure produced by one seed in a simulation sweep.

Properties

seed: number | string

Seed used for this run.

report?: SimReport<Result>

Completed report when the run succeeded.

error?: unknown

Thrown value when the run failed.

Classes

class FakeNet implements SimMock {

Parent-owned HTTP route table for a simulated Realm's ambient fetch().

Method-specific keys such as POST https://api.example.com/orders take precedence over URL-only keys. A handler that returns undefined, or a URL with no matching route, produces a diagnostic 502 response without touching the operating-system network.

const net = new FakeNet().route('GET https://api.example.com/health', {
  body: 'ok',
});

const report = await simulate({
  entry: './worker.ts',
  world: net.world(),
});

Static Readonly Properties

static readonly specifier

Facade specifier used by the simulation's ambient Fetch adapter.

Constructors

constructor(routes: Record<string, FakeRoute> = {})

Create a route table from optional initial entries.

Methods

route(pattern: string, handler: FakeRoute): this

Add or replace pattern, returning this route table for chaining.

world(): Record<string, SimProvider>

Return the fetch Facade entry expected by simulate().

provider(): Record<string, unknown>

Return the parent-side provider independently for custom world composition.

class FakeFs implements SimMock {

Parent-owned in-memory filesystem for a simulated Realm.

The adapter replaces fino:file through the simulation import map, so guest code constructs DiskFileSystem normally while every operation crosses the existing Facade transport. The parent can seed state before a run and inspect a copied text snapshot afterwards.

import { FakeFs, simulate } from 'fino:sim';

const fs = new FakeFs({ '/etc/app.conf': 'debug=true' });
const report = await simulate({ entry: './worker.ts', world: fs.world() });
console.log(fs.snapshot(), report.journal.calls(FakeFs.specifier).length);

Static Readonly Properties

static readonly specifier

Facade specifier replaced by this adapter.

Constructors

constructor(files: Record<string, string | Uint8Array> = {})

Create an independent filesystem seeded with files.

Getters

get filesystem(): MemoryFileSystem

Parent-owned filesystem used for setup and direct assertions.

Methods

snapshot(): Record<string, string>

Return a copied text snapshot of every file currently in the tree.

world(): Record<string, SimProvider>

Return the fino:file Facade entry expected by simulate().

provider(): Facade

Return the filesystem Facade independently for custom world composition.

Functions

async function simulate<Result = unknown>( options: SimulateOptions, ): Promise<SimReport<Result>>

Run an entry module in a deterministic, deny-by-default Realm.

Plain objects in world become Facades; existing Facades retain their custom module and streaming shapes. overrides are applied last so callers can deliberately replace a world entry or inherit an additional pure module. The Realm and its transport observer are disposed whether the call returns or throws.

options defines the entry, arguments, deterministic inputs, import world, and optional in-memory recording, replay, or fault policy.

async function sweep<Result = unknown>( options: Omit<SimulateOptions, 'seed'>, seeds: number | Array<number | string>, ): Promise<Array<SweepOutcome<Result>>>

Run the same simulation sequentially for each seed.

Failures are retained beside their seed instead of stopping later runs. The sequential order keeps a reported seed sufficient to reproduce an outcome, without introducing cross-run scheduling as another input.

options is reused for each run. An array in seeds is used verbatim; a numeric seeds value runs integer seeds from zero up to that count.