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();