[data-reveal]{opacity:1!important;transform:none!important}
Embedded Runtime
Concepts

Embedded Runtime

Run AntelopeJS modules inside an application that owns the process, and compare the embedded mode with the standalone mode driven by the ajs CLI.

Overview

AntelopeJS runs in one of two modes:

  • Standalone — The ajs CLI owns the process. ajs project dev or ajs project start reads antelope.config.ts, loads the modules and runs until it receives a signal.
  • Embedded — Your application owns the process. It creates a runtime with createRuntime(), lists its modules in code, and starts and stops the runtime from its own lifecycle.

Both modes run the same modules with the same core: interface binding, module lifecycle and proxies behave identically. The embedded mode changes who owns the process and where the module list lives, and it lets host code that is not a module call interfaces through the runtime handle.

@antelopejs/core exports createRuntime and AntelopeRuntime since version 1.5.0.

host.ts
import { createRuntime } from "@antelopejs/core";

const runtime = createRuntime({
  modules: {
    "@antelopejs/mongodb": { config: { url: process.env.MONGODB_URL } },
  },
  uses: ["@antelopejs/interface-database"],
});

await runtime.start();
// ... the host application runs ...
await runtime.stop();
The embedded runtime is unrelated to embedded interfaces, which are interface contracts shipped inside the module that implements them. See Interfaces.

Standalone vs Embedded

AspectStandaloneEmbedded
Process ownerThe ajs CLI.Your application.
Signals and process handlersThe runtime claims SIGINT and SIGTERM and installs uncaughtException / unhandledRejection handlers.The host keeps them. The runtime installs nothing unless you opt in through policy.
LoggerThe runtime writes module logs using the logging section of antelope.config.ts.The runtime writes no log lines unless you pass logging. Passing it resets the global logger and takes over the log output.
Terminal outputProgress spinners on stdout.None, unless policy.terminal is on.
Where configuration livesantelope.config.ts, with environments, envOverrides and ${path} template substitution.The modules map passed to createRuntime(). Values are plain JavaScript: read environment variables in host code.
Module resolutionLocal, package and Git sources, installed into the .antelope/cache folder.Packages installed in the host's node_modules, or a path to a module folder. Nothing is downloaded and no cache is created.
ajs commandsAll ajs project commands.No ajs project command. ajs module commands still work on the module folders themselves.
Hot reloadajs project dev --watch reloads changed modules and restarts on configuration changes.None. The runtime starts in non-development mode with no file watcher; restart the host to pick up changes.
GetRuntimeInfo()dev is true under ajs project dev.dev is always false; env is the env option, default when unset.
How application code reaches interfacesModules import interface packages; the core binds each import to a provider.Modules work the same way. Host code calls runtime.use(), and implements interfaces with runtime.provide().

How the Runtime Joins the Host

createRuntime() returns an AntelopeRuntime that is not started. start() resolves each entry of modules from the host's node_modules (or from its path), then constructs and starts the modules exactly like a standalone launch.

The runtime also registers the host itself as a module with the reserved id __antelope_host__. The packages listed in uses and provides become that module's dependencies, so the host gets its own provider-bound copy of each interface. This is why host code must go through runtime.use(): a plain import of an interface package from host code loads a second copy of the package that no provider is attached to.

The host module carries no importOverrides, so runtime.use() returns a copy bound to the default provider of each interface. When several loaded modules implement the same interface, steer the default with exportPriority.

The host owns the outer lifecycle; the runtime owns the modules inside it:

Host application
  │ createRuntime({ modules, uses })
  ▼
Runtime handle (not started yet)
  │ await start()
  ▼
Modules constructed and started
  │ host calls runtime.use(contract)
  ▼
Provider-bound interface → provider
  │ host finishes serving requests
  ▼
await runtime.stop()
  └─ modules stopped and destroyed

This is an ownership and call-flow map, not another process. A plain host import is not the provider-bound interface shown here.

API Reference

createRuntime(options?)

Creates an AntelopeRuntime that runs as a guest of the calling process. The runtime is not started.

function createRuntime(options?: EmbeddedRuntimeOptions): AntelopeRuntime;

EmbeddedRuntimeOptions accepts the following fields, all optional:

OptionTypeDefaultDescription
projectFolderstringprocess.cwd()Folder that modules and relative path values resolve from.
envstring"default"Environment name reported to modules by GetRuntimeInfo().
modulesRecord<string, EmbeddedModuleConfig>{}Modules to load, keyed by package name.
usesstring[][]Interface packages the host consumes. start() fails if one does not resolve.
providesstring[][]Interface packages the host implements. The host becomes their default provider, and start() fails if one does not resolve.
loggingAntelopeLoggingunsetLog configuration, with the same shape as the logging section of antelope.config.ts. Setting it turns on the logging policy.
policyPartial<RuntimePolicy>all falseProcess responsibilities the runtime takes over from the host.

Each EmbeddedModuleConfig entry of modules accepts:

FieldTypeDescription
pathstringModule folder, absolute or relative to projectFolder. When unset, the package is resolved from the host's node_modules.
configunknownConfiguration passed to the module, as is.
importOverridesImportOverride[]Provider pins and connections, in the array form described in Interface Bindings.
disabledExportsstring[]Interfaces the module must not provide. See Disabled Exports.
exportPriorityRecord<string, number>Priority of the module as the default provider, keyed by interface. See Default provider.

RuntimePolicy lists the process responsibilities. All of them are off in embedded mode and on in standalone mode:

FieldEffect when true
processHandlersInstall uncaughtException and unhandledRejection handlers.
signalsClaim SIGINT and SIGTERM, and stop the runtime when they arrive.
terminalDraw progress spinners on stdout.
loggingReset the global logger and take over the log output. Passing the logging option turns this on.

AntelopeRuntime

MemberDescription
start(): Promise<void>Loads, constructs and starts the modules. Concurrent calls share one launch. If a module fails or a uses / provides package does not resolve, the runtime shuts down what it started and the promise rejects.
stop(): Promise<void>Stops and destroys the modules. Waits for an in-flight start() to settle first. Does nothing when the runtime is not running.
isRunning: booleantrue between a successful start() and the next stop().
use<T>(request: string): TReturns the runtime's provider-bound copy of an interface package or one of its subpaths, such as @antelopejs/interface-dms/client-base-url. Throws before start(), and when no loaded module provides the interface.
provide(packageName, implementation): ProvideHandleImplements an interface from host code. The package must be listed in provides. Call detach() on the returned handle to remove the implementation.
manager: ModuleManagerThe underlying module manager, for introspection such as runtime.manager.listModules(). The list includes the __antelope_host__ module. Throws before start().

start() logs a warning when a loaded module also implements an interface listed in provides: the host wins by default, and importOverrides on the consumers route them to the module instead.

Calls through an interface wait until an implementation is attached. After detach(), calls to the detached interface wait instead of failing.

See also