module

js/module.ts

fino:module — runtime module registration utilities.

Registers in-memory modules whose exports come from a plain object rather than a file on disk. Once installed, a synthetic module resolves through the normal dynamic import() machinery, so any code in the runtime can import the specifier as if it were a real file. This is useful for test fixtures, plugins, and generated module graphs where writing source to disk would be awkward or impossible.

Installation mutates the runtime's module registry and therefore only affects imports that happen after install() returns. It does not rewrite namespace objects that were already imported, and it is scoped to the current runtime — a synthetic module installed in a parent Realm is not visible to a child Realm running in its own isolate. Always pair install() with a later uninstall() so fixtures do not leak into unrelated code.

Specifiers must be application-owned bare or path-like strings. Prefixed URI-like schemes such as fino:, internal:, or app: are rejected because they are reserved for builtins and runtime providers.

import { SyntheticModule } from 'fino:module';

const fixture = new SyntheticModule('fixture-config', {
  default: { port: 8080 },
  mode: 'test',
});
fixture.install();
const config = await import('fixture-config');
console.log(config.default.port, config.mode); // 8080 test
fixture.uninstall();

Classes

class SyntheticModule {

A module specifier backed by an object of named exports, resolvable through dynamic import() once installed.

Construction is inert: it merely records the specifier and export object. Nothing becomes importable until install() is called, and the module stays importable until uninstall() removes it. Each export key becomes a named export of the module; a default key becomes the default export. Because installation targets the current runtime's registry, a synthetic module is not shared across Realms that run in separate isolates.

Reinstalling the same specifier replaces the previous entry, which is a convenient way to swap a fixture's exports between tests. Prefer a bare or path-like specifier; scheme-prefixed specifiers are rejected by install().

import { SyntheticModule } from 'fino:module';
import { describe, it } from 'fino:test/test';

describe('feature flags', () => {
  it('reads the injected config', async (t) => {
    const flags = new SyntheticModule('app-flags', { betaSearch: true });
    flags.install();
    try {
      const { betaSearch } = await import('app-flags');
      t.equal(betaSearch, true);
    } finally {
      flags.uninstall();
    }
  });
});

Constructors

constructor(specifier: string, exports: Record<string, unknown>)

Records the specifier and export object without touching the module registry.

Construction has no side effects; call install() afterwards to make the module resolvable. The keys of exports become the module's named exports, and a default key becomes the default export — there is no implicit default otherwise.

import { SyntheticModule } from 'fino:module';

const fixture = new SyntheticModule('fixture-config', {
  default: { port: 8080 },
  mode: 'test',
});

Methods

install(): void

Install this synthetic module so future dynamic imports can resolve it.

Throws when the specifier has a URI-like scheme, because prefixed schemes are reserved for builtins and runtime providers. Reinstalling the same specifier replaces the direct registry entry used by future imports.

import { SyntheticModule } from 'fino:module';

const module = new SyntheticModule('fixture-config', { port: 8080 });
module.install();
console.log((await import('fixture-config')).port);
uninstall(): void

Remove this synthetic module from the runtime registry.

Uninstalling prevents future resolution of the specifier. It does not mutate namespace objects that were already imported while the module was installed.

import { SyntheticModule } from 'fino:module';

const module = new SyntheticModule('fixture-config', { port: 8080 });
module.install();
module.uninstall();