js/realm

js/realm/index.ts

fino:realm - Realm construction and management.

A Realm is an isolated V8 Context with its own global object, module graph, microtask queue, and event loop. The parent's import rule list governs every module resolution in the child; the child can layer overrides on top.

Ordinary Realms in the current process run as movable isolates on the shared reactor thread pool. Linux sandbox Realms instead own a fixed thread so cgroup v2 threaded controls, Landlock, and seccomp can govern that workload. Both local modes provide JavaScript/module isolation rather than a hostile operating-system boundary: they share the process address space and file-descriptor table. Process realms add hard crash isolation but do not automatically restrict the child's access to the host. Remote realms run on another node's reactor pool over the current trusted fino:cluster WebTransport transport and require an active cluster before construction. Cluster authentication, hostile-peer handling, and remote watch / repl modes are outside this baseline.

The import rule list uses last-match-wins semantics. Declare a wildcard first as the baseline and more specific patterns afterwards as overrides. CLI OpenTelemetry bootstrap metadata follows realm construction separately from user data: children inherit a fino run --otlp-endpoint endpoint by default, otlpEndpoint overrides it for one subtree, and false disables it.

import { Realm, ImportMap } from 'fino:realm';

const realm = new Realm({
  entry: './worker.ts',
  overrides: ImportMap.inherit([
    { pattern: 'fino:process', directive: 'block' },
  ]),
});
const result = await realm.call('job-1');
await realm.terminate();

Types

type ImportDirectiveSer = | 'inherit' | 'block' | { type: 'inherit' } | { type: 'block' } | { type: 'remap'; target: string } | { type: 'source'; code: string; source_map: string } | { type: 'facade'; specifier: string; exports: string[]; streams?: string[]; sinks?: string[]; source?: string; module?: string; }

Wire-format representation of an import directive.

Directives control how a child realm resolves an import that matches an ImportRule. String forms are accepted for convenience and normalized to the Rust serde layout before crossing the native bridge. source injects an in-memory module, remap redirects to another specifier, and facade exposes parent-side RPC handlers.

import type { ImportDirectiveSer } from 'fino:realm';

const directive: ImportDirectiveSer = {
  type: 'remap',
  target: './sandboxed-logger.ts',
};

type RealmSandboxOptions = Omit< ProcessSandboxOptions, 'mode' | 'resources' | 'network' | 'process' > & { mode: 'strict'; resources?: Omit<ProcessSandboxResources, 'memoryBytes'> & { memoryBytes?: never; cpus?: string }; network?: { outbound?: Array<Pick<ProcessSandboxNetworkRule, 'action'>>; inbound?: Array<Pick<ProcessSandboxNetworkRule, 'action'>>; }; process?: { allowFork?: false; allowExec?: false; allowedBinaries?: [] }; }

Strict Linux policy installed on a dedicated sandbox Realm thread.

This uses the same policy vocabulary as strict process sandboxing, with thread-specific limits enforced by RealmOptions.sandbox: CPU quota, cpuset affinity, and pids use cgroup v2 threaded controllers; filesystem rules use Landlock; syscall/process/network rules use seccomp. Per-thread memory limits, exec, fork, best-effort mode, and async FFI are unavailable.

import type { RealmSandboxOptions } from 'fino:realm';

const sandbox: RealmSandboxOptions = {
  mode: 'strict',
  resources: { cpu: 0.5, cpus: '0-1', pids: 32 },
  network: { outbound: [{ action: 'deny' }] },
};

type RealmFn = (...args: any[]) => any

Function shape used by Realm.call().

A child entry module should default-export a function compatible with this type when the parent intends to call it. Arguments and return values must be supported by the active transport serializer.

import type { RealmFn } from 'fino:realm';

const worker: RealmFn = (name: string) => `hello ${name}`;
export default worker;

Interfaces

interface ImportRule {

Import rule applied to module resolution inside a child realm.

Rules are evaluated with last-match-wins semantics after the parent realm's inherited rules. A matching rule may inherit, block, remap, inject source, or expose a facade. Invalid patterns are rejected by the native loader when the child realm is created.

import type { ImportRule } from 'fino:realm';

const rule: ImportRule = {
  pattern: 'fino:process',
  directive: 'block',
};

Properties

from?: string

Optional pattern matching the importing module's specifier.

When omitted, the rule can apply regardless of which module performs the import. Use this for broad allow/deny lists, and provide from when only a specific importer should receive an override.

import type { ImportRule } from 'fino:realm';

const rule: ImportRule = {
  from: './plugin-host.ts',
  pattern: './plugin-api.ts',
  directive: 'inherit',
};
pattern: string

Pattern matching the specifier being imported.

This field is required. * is commonly used as a baseline rule, with more specific patterns appended later as overrides.

import type { ImportRule } from 'fino:realm';

const blockAll: ImportRule = { pattern: '*', directive: 'block' };
directive: ImportDirectiveSer | Facade

Resolution action to apply when the rule matches.

inherit falls back to the parent rule set, block rejects resolution, and object forms can remap, inject source, or expose a facade. Facade instances are accepted and normalized during realm construction.

import type { ImportRule } from 'fino:realm';

const rule: ImportRule = {
  pattern: 'virtual:config',
  directive: { type: 'source', code: 'export const port = 8080;', source_map: '' },
};

interface RealmOptions {

Options for constructing and running a child realm.

Realms normally run as movable isolates on the process-wide reactor pool. sandbox selects a fixed Linux thread with strict OS governance, process: true selects a separate process, and remote: true routes the realm to another cluster node.

import { Realm, type RealmOptions } from 'fino:realm';

const options: RealmOptions = { entry: './worker.ts' };
const realm = new Realm(options);

Properties

entry: string

Path to the entry module to evaluate in the child realm.

The path is resolved by the runtime loader using the realm root and import rules. The module may default-export a function for Realm.call().

import type { RealmOptions } from 'fino:realm';

const options: RealmOptions = { entry: './worker.ts' };
root?: string

Filesystem root for module resolution.

When omitted, the child inherits the parent's root. The root affects module lookup; service access remains entirely controlled by import rules.

import type { RealmOptions } from 'fino:realm';

const options: RealmOptions = { entry: './worker.ts', root: '/srv/app' };
overrides?: ImportMap | ImportRule[]

Import rules for this Realm. Appended after the parent's rules; last-match-wins. Use ImportMap.deny([...]) or ImportMap.inherit([...]).

import { ImportMap, type RealmOptions } from 'fino:realm';

const options: RealmOptions = {
  entry: './worker.ts',
  overrides: ImportMap.deny([{ pattern: './api.ts', directive: 'inherit' }]),
};
deterministic?: DeterministicRealmOptions

Make ambient time and randomness reproducible inside this Realm.

The child starts at startTime, timers advance virtual time when the Realm is otherwise idle, and Math.random() plus runtime random bytes draw from seed. The default start is 2023-11-14T22:13:20Z. An optional responseLatency range charges Facade results to the same virtual clock without waiting in real time.

This option does not simulate or deny I/O. Use import-map policy to control external effects. It is not inherited by nested Realms and is not supported with remote: true.

import { Realm } from 'fino:realm';

const realm = new Realm({
  entry: './worker.ts',
  deterministic: {
    seed: 'checkout-flow',
    startTime: 0,
    responseLatency: [10, 100],
  },
});
sandbox?: RealmSandboxOptions

Run this Realm on a dedicated Linux thread and install the requested cgroup v2 threaded controls, Landlock filesystem policy, and seccomp syscall policy before importing its entry module.

Sandbox Realms are strict-only and mutually exclusive with process and remote. They share the host process address space and inherited file descriptors, so use a process Realm for hostile-code isolation.

resources.memoryBytes, process exec/fork permission, watch, and repl are rejected because those controls cannot be safely scoped to this fixed thread lifecycle.

import { Realm } from 'fino:realm';

const realm = new Realm({
  entry: './worker.ts',
  sandbox: {
    mode: 'strict',
    resources: { cpu: 0.5, cpus: '0-1', pids: 32 },
    filesystem: { readonly: ['/srv/app'], writable: ['/tmp/work'] },
    network: { outbound: [{ action: 'deny' }] },
  },
});
process?: boolean

If true, spawn the child Realm as a separate OS process for hard crash isolation. Messaging uses framed binary over a Unix socketpair. Mutually exclusive with remote.

import type { RealmOptions } from 'fino:realm';

const options: RealmOptions = { entry: './worker.ts', process: true };
remote?: boolean

If true, spawn the child Realm on a remote cluster node. Requires a prior call to startCluster() or joinCluster() from fino:cluster. Messaging uses the cluster PORT_MSG protocol over WebTransport. Mutually exclusive with process.

import type { RealmOptions } from 'fino:realm';

const options: RealmOptions = { entry: './worker.ts', remote: true };
watch?: boolean

If true, automatically restart the child Realm whenever any file it imported changes on disk. The JS Realm instance is stable across reloads; only the underlying V8 isolate or process is replaced. Not supported with remote: true.

import type { RealmOptions } from 'fino:realm';

const options: RealmOptions = { entry: './worker.ts', watch: true };
repl?: boolean

If true, run this child Realm in REPL mode. The child listens for { __eval, id, code } messages on its port and responds with { __eval_result } or { __eval_error }. Not compatible with process, remote, or watch.

import type { RealmOptions } from 'fino:realm';

const options: RealmOptions = { entry: './repl-host.ts', repl: true };
data?: unknown

Arbitrary JSON-serializable configuration delivered to the child realm. The child reads it via internal:realm-bridge.getRealmData() before the entry module is imported, so it can shape application-specific worker configuration. Runtime bootstrap metadata such as otlpEndpoint is stored separately and does not appear here. Not supported with remote: true.

import type { RealmOptions } from 'fino:realm';

const options: RealmOptions = { entry: './worker.ts', data: { role: 'ingest' } };
otlpEndpoint?: string | false

OTLP/HTTP collector endpoint for CLI OpenTelemetry bootstrap in this realm.

When omitted, the child inherits the current realm's CLI endpoint, if one was seeded by fino run --otlp-endpoint or OTEL_EXPORTER_OTLP_ENDPOINT. Passing a non-empty string overrides that endpoint for this realm. Passing false disables CLI OpenTelemetry bootstrap for this realm even when the parent has an endpoint. The endpoint is runtime bootstrap metadata and does not appear in RealmOptions.data or getRealmData().

import { Realm, type RealmOptions } from 'fino:realm';

const options: RealmOptions = {
  entry: './worker.ts',
  otlpEndpoint: 'http://127.0.0.1:4318',
};

interface DeterministicRealmOptions {

Settings for deterministic ambient effects in one Realm.

Properties

seed: number | string

Seed for Math.random(), Web Crypto random bytes, and runtime entropy.

startTime?: number

Initial virtual Unix time in milliseconds. Defaults to 1700000000000.

responseLatency?: [number, number]

Inclusive virtual-millisecond range charged to each Facade response.

Scalar results and sink results draw once. Read-stream chunks draw independently and remain ordered; the terminal frame adds no extra delay. Delays use a seed-derived stream independent of guest randomness.

interface RealmSourceOptions extends Omit<RealmOptions, 'entry' | 'watch'> {

Options for creating a Realm from in-memory entrypoint source.

Source realms run the provided text as a normal ESM entry module, so static imports, type imports, and top-level await behave the same as file-backed entries. watch is intentionally unavailable because there is no entry file to monitor; use a file-backed Realm when entrypoint reloads are required.

import { Realm } from 'fino:realm';

const realm = Realm.fromSource(`
  import { basename } from 'fino:file/path';

  if (basename('/tmp/example.ts') !== 'example.ts') {
    throw new Error('unexpected basename');
  }
`);
await realm.run();

Properties

specifier?: string

Module specifier assigned to the source entry.

When omitted, Fino generates a unique fino:realm/source/... specifier. Provide an absolute file path or file:// URL when relative imports inside the source should resolve from a specific directory.

sourceMap?: string

Optional source map JSON for the provided source text.

Invalid or empty source maps are ignored by the runtime. The value defaults to an empty string, matching other source import directives.

Classes

class ImportMap {

An ordered list of import rules to apply to a child Realm.

Rules are last-match-wins. Use ImportMap.deny([...overrides]) to start with a block-all baseline and punch specific exceptions, or ImportMap.inherit([...overrides]) to inherit all and restrict specifics.

The rules in this object are the child-specific overrides that are appended after the parent's rules. The parent's rules always form the baseline.

const documentedClass = 'ImportMap';
console.log(documentedClass);

Constructors

constructor(rules: ImportRule[])

Create an ordered import map from explicit rules.

The rules are stored as child-specific overrides and later appended after parent rules. The constructor does not add a wildcard baseline; use ImportMap.deny() or ImportMap.inherit() when you want that default.

import { ImportMap } from 'fino:realm';

const map = new ImportMap([{ pattern: 'fino:process', directive: 'block' }]);

Static Methods

static deny(overrides: ImportRule[]): ImportMap

Deny everything by default; allow/remap/facade specific specifiers.

The wildcard { pattern: '*', directive: 'block' } is prepended, then the caller's overrides follow (each overrides the wildcard for its pattern).

import { ImportMap, Realm } from 'fino:realm';

const overrides = ImportMap.deny([
  { pattern: './worker-api.ts', directive: 'inherit' },
]);
new Realm({ entry: './worker.ts', overrides });
static inherit(overrides: ImportRule[]): ImportMap

Inherit everything from the parent by default; restrict specific specifiers.

The wildcard { pattern: '*', directive: 'inherit' } is prepended; the caller's overrides follow. Effectively a no-op wildcard (Inherit is dropped on the Rust side), but makes the intent explicit in code.

import { ImportMap, Realm } from 'fino:realm';

const overrides = ImportMap.inherit([
  { pattern: 'fino:process', directive: 'block' },
]);
new Realm({ entry: './worker.ts', overrides });

class FacadeHandle {

A stateful object handle returned from a Facade handler.

When a scalar handler returns a FacadeHandle, the parent registers its methods under a unique ID and sends { __handle: id, streams?: [...] } to the child. The child receives a Proxy that routes subsequent method calls back through internal:parent-rpc using the handle ID as the specifier.

facade.handle('open', async (path) => {
  const fh = await realFs.open(path, 'r');
  return new FacadeHandle(
    { stat: () => fh.stat(), close: () => fh.close() },
    { read: (_size) => fh.reader() },   // streaming method
  );
});

Constructors

constructor( scalar: Record<string, (...args: unknown[]) => unknown> = {}, streams: Record<string, (...args: unknown[]) => AsyncIterable<unknown>> = {}, sinks: Record< string, (args: unknown[], source: AsyncIterable<unknown>) => Promise<unknown> > = {}, )

Create a stateful handle with scalar, read-stream, and write-stream methods.

Scalar methods return one response, stream methods return an AsyncIterable, and sink methods receive chunks from the child as an AsyncIterable. Empty maps are allowed.

import { FacadeHandle } from 'fino:realm';

const handle = new FacadeHandle(
  { stat: () => ({ size: 10 }) },
  { read: async function* () { yield new Uint8Array([1, 2, 3]); } },
);

class Facade {

Public facade exposed to a child realm as a synthetic module.

A facade declares the names available to child imports and binds parent-side handlers for those names. Calls cross the realm boundary through RPC, so arguments and results must be serializable by the active realm transport unless they are represented as streams or FacadeHandle proxies.

import { Facade, ImportMap, Realm } from 'fino:realm';

const api = new Facade('app:api', ['version'])
  .handle('version', async () => '1.0.0');

new Realm({
  entry: './worker.ts',
  overrides: ImportMap.inherit([{ pattern: 'app:api', directive: api }]),
});

Constructors

constructor(specifier: string, exports: string[])

Create a facade for a synthetic module specifier.

exports may predeclare scalar method names visible in the child module. handle() also declares its method automatically, so callers building a facade entirely through handlers may pass an empty array. Stream and sink names are declared by stream() and sendStream().

import { Facade } from 'fino:realm';

const facade = new Facade('app:math', ['double'])
  .handle('double', async (value) => Number(value) * 2);

Methods

module(source: string): this

Define the child-side shape of this facade module.

The source may export any valid module shape, including classes and constants. Import call, callStream, or callSink from internal:parent-rpc to reach handlers bound on this facade, passing import.meta.url as the facade specifier. This is useful for APIs whose local shape cannot be represented by the default async function exports.

import { Facade } from 'fino:realm';

const clock = new Facade('app:clock', [])
  .module(`import { call } from 'internal:parent-rpc';
  export class Clock {
    now() { return call(import.meta.url, 'now', []); }
  }`)
  .handle('now', async () => Date.now());

The source is trusted parent configuration, not guest input. Syntax and module-linking errors surface when the child imports the facade.

moduleFrom(specifier: string): this

Use a registered builtin module as this facade's child-side implementation.

The runtime loads the builtin's compiled source under the facade specifier, so import.meta.url still identifies the facade for calls through internal:parent-rpc. This keeps nontrivial module implementations in ordinary TypeScript files while the Facade continues to own parent-side handlers.

import { Facade } from 'fino:realm';

const files = new Facade('app:files', [])
  .moduleFrom('internal:app/files-facade')
  .handle('read', async (path) => loadFile(String(path)));

Calling module() afterwards replaces this setting, and calling moduleFrom() replaces custom inline source.

handle(method: string, fn: (...args: unknown[]) => Promise<unknown>): this

Register a scalar handler.

The handler receives the child call arguments and returns one result. A thrown error or rejected promise is sent back as an RPC error. Returning a FacadeHandle creates a stateful child-side proxy.

import { Facade } from 'fino:realm';

const facade = new Facade('app:math', ['add']);
facade.handle('add', async (a, b) => Number(a) + Number(b));
stream(method: string, fn: (...args: unknown[]) => AsyncIterable<unknown>): this

Register a read-stream handler - the AsyncIterable it returns is pumped as __rpc_chunk / __rpc_end / __rpc_err envelopes (parent->child).

The method name is added to the facade's stream export list if it is not already present. Errors thrown while creating or consuming the iterable are delivered to the child as stream errors.

import { Facade } from 'fino:realm';

const facade = new Facade('app:logs', []);
facade.stream('tail', async function* () {
  yield 'line one';
});
sendStream( method: string, fn: (args: unknown[], source: AsyncIterable<unknown>) => Promise<unknown>, ): this

Register a write-stream (sink) handler - the child sends chunks to the parent via __rpc_send_chunk envelopes (child->parent, no per-chunk ack).

The handler receives (args, source: AsyncIterable<unknown>) and should drain source to completion before returning the final result.

Maps directly onto a QUIC client-initiated unidirectional stream when the cluster transport is later upgraded to QUIC.

import { Facade } from 'fino:realm';

const facade = new Facade('app:upload', []);
facade.sendStream('write', async (_args, source) => {
  let total = 0;
  for await (const chunk of source) total += (chunk as Uint8Array).byteLength;
  return { bytesWritten: total };
});

Static Methods

static from(obj: object, opts: { specifier: string }): Facade

Create a facade from callable properties on an object or class instance.

Own functions and prototype methods are exported. Non-function properties are ignored. Each generated handler calls the original method with the original object as the receiver expression.

import { Facade } from 'fino:realm';

const service = { ping: async () => 'pong' };
const facade = Facade.from(service, { specifier: 'app:service' });
static proxy(resolve: () => object, opts: { specifier: string; methods: string[] }): Facade

Create a capability-scoped proxy for a service resolved at call time.

Only methods are visible to the child, even when the resolved service has a larger API. The resolver runs for every call, which supports lazily initialized or replaceable services without a hand-written forwarding handler per method. The original service remains the receiver expression.

import { Facade } from 'fino:realm';

let service: { ping(): Promise<string> } | undefined;
const facade = Facade.proxy(
  () => service ??= { async ping() { return 'pong'; } },
  { specifier: 'app:service', methods: ['ping'] },
);

class ProcessPort extends RealmPort {

MessagePort-compatible endpoint for a realm running in a separate process.

Process ports are created by new Realm({ process: true }); application code reaches one through realm.port rather than constructing it directly.

import { Realm } from 'fino:realm';

const realm = new Realm({ entry: './worker.ts', process: true });
realm.port.postMessage({ job: 'start' });

Constructors

constructor(wakeReadFd: number, handle: number)

class Realm<F extends RealmFn = RealmFn> {

Isolated child realm with its own module graph and communication port.

A realm runs as a movable isolate on the shared reactor pool, in a separate process, or on a remote cluster node. Use run() for entry modules with side effects and call() for entry modules that default-export a function.

import { Realm } from 'fino:realm';

const realm = new Realm<(name: string) => string>({ entry: './worker.ts' });
const message = await realm.call('Ana');
realm.terminate();

Properties

port: MessagePort | RealmPort | ProcessPort | ClusterPort

Parent-side port for general communication with the child realm.

Reactor-pooled realms expose a RealmPort, process realms expose a ProcessPort, and remote realms expose a ClusterPort. Start the port before listening for messages.

import { Realm } from 'fino:realm';

const realm = new Realm({ entry: './worker.ts' });
realm.port.addEventListener('message', (event) => console.log(event.data));
realm.port.start();

Static Methods

static fromSource<F extends RealmFn = RealmFn>( source: string, options: RealmSourceOptions = {}, ): Realm<F>

Create a Realm whose entrypoint is in-memory module source.

The source is installed as an import-rule-backed entry module and then evaluated by the same Realm machinery used for file entries. Caller import rules and execution mode options are preserved. Source entries cannot use watch because there is no entry file to monitor for changes.

import { Realm } from 'fino:realm';

const realm = Realm.fromSource(`
  await Promise.resolve();
  globalThis.value = 42;
`);
await realm.run();

Constructors

constructor(opts: RealmOptions)

Create a child realm and its parent-side communication port.

The constructor builds import rules, creates the selected execution mode, and binds facade RPC dispatchers. It throws for invalid mode combinations, remote realms without an active cluster, or native creation failures.

import { ImportMap, Realm } from 'fino:realm';

const realm = new Realm({
  entry: './worker.ts',
  overrides: ImportMap.inherit([]),
});

Methods

run(): Promise<void>

Run the child realm to completion.

The returned promise resolves when the child exits cleanly and rejects when the child reports a runtime error. Watch-mode realms keep the promise pending across reloads until terminate() stops watching.

import { Realm } from 'fino:realm';

const realm = new Realm({ entry: './worker.ts' });
await realm.run();
call(...args: Parameters<F>): Promise<Awaited<ReturnType<F>>>

Call the child Realm's default-exported function with args.

The call starts the realm, sends a Call envelope, and resolves with the returned value. It rejects if the realm exits before returning, if the child serializes a call error, or if the remote cluster reports exit. Local ports remain open through child shutdown so final runtime control frames can drain after the call result.

import { Realm } from 'fino:realm';

const realm = new Realm<(a: number, b: number) => number>({ entry: './add.ts' });
const sum = await realm.call(2, 3);
terminate(options: { force?: boolean } = {}): void

Signal the child realm to stop.

Process and sandbox Realms normally receive a cooperative termination message. Pass { force: true } to send SIGKILL to a process Realm or terminate V8 execution on a sandbox Realm when synchronous code cannot service that message. Other realm kinds ignore force.

Embedded realms are terminated through the native child handle. Scheduled, process, and remote realms receive a __terminate message. The method is synchronous and does not wait for run() to settle.

import { Realm } from 'fino:realm';

const realm = new Realm({ entry: './worker.ts' });
realm.terminate();