Modules
Module Structure
A module is a standard npm package with an antelopeJs field in its package.json. This field tells the core what the module implements, where to find its compiled output, and how to resolve its internal imports.
{
"name": "my-module",
"antelopeJs": {
"implements": ["@antelopejs/interface-example"],
"baseUrl": "dist",
"paths": { "@/*": ["*"] },
"moduleAliases": {},
"defaultConfig": {}
}
}
The implements array lists the interface packages this module provides. The baseUrl points to the compiled output directory. The paths field configures TypeScript-style path aliases, and defaultConfig provides fallback configuration values.
Entry Point
The module entry point (src/index.ts, compiled to dist/index.js) exports lifecycle functions. The core calls these functions in order as the module transitions through its lifecycle states.
import { ImplementInterface } from "@antelopejs/interface-core";
export async function construct(config: unknown): Promise<void> {
// Initialize resources, connect to databases
// Register interface implementations
}
export function start(): void {
// Module is fully active, all interfaces available
}
export async function stop(): Promise<void> {
// Gracefully stop operations
}
export async function destroy(): Promise<void> {
// Clean up resources
}
The construct function receives the module's configuration object. Use it to set up database connections, register interface implementations, and prepare internal state. The start function runs after all modules have been constructed and interfaces are resolved.
A module may also export an optional provide callback, which runs before any module constructs and returns the config variables it publishes — the values other modules reference from their own configuration with ${@<module>.<VAR_NAME>}. Only providers implement it, and it is the only place config variables are produced: a value returned from construct is ignored.
import type { ConfigVars } from "@antelopejs/interface-core/config";
export async function provide(config: unknown): Promise<ConfigVars> {
const port = await reservePort(config);
return { API_PORT: port };
}
No module has constructed while provide runs, so it cannot await another module's interface.
Lifecycle States
Every module passes through a defined sequence of states. The core manages these transitions and ensures they happen in the correct order.
| State | Description |
|---|---|
loaded | Module files have been resolved and loaded into memory. |
constructed | The construct() function has completed successfully. |
active | The start() function has completed and the module is serving its interfaces. |
The full lifecycle flows in one direction: loaded → constructed → active → (stop) → (destroy). The stop and destroy phases reverse the startup sequence, giving each module a chance to release resources gracefully.
A module that starts processes of its own (a sidecar, a dev server, a worker) stops them in stop() or destroy(). When the project stops, after every module was stopped and destroyed, the core also terminates the processes descending from it that are still running, with SIGTERM and then SIGKILL two seconds later, so a process that was not reaped does not outlive the project. This safety net only reaches child processes, on Linux: a process that is no longer a child of the project, such as a daemon started in an earlier run and reused, or a process started through a detached shell, must be stopped by the module that started or adopted it.
Module Events
Other modules can listen for lifecycle transitions. The core emits events whenever a module changes state, enabling coordination between modules that need to react to each other's availability.
import { Events } from "@antelopejs/interface-core/modules";
Events.ModuleConstructed.register((moduleId: string) => {
// Fires after a module completes construct()
});
Events.ModuleStarted.register((moduleId: string) => {
// Fires after a module completes start()
});
Events.ModuleStopped.register((moduleId: string) => {
// Fires after a module completes stop()
});
Events.ModuleDestroyed.register((moduleId: string) => {
// Fires after a module completes destroy()
});
Module Management Functions
The core provides functions to inspect and control modules at runtime. These functions are available from @antelopejs/interface-core/modules and operate on module IDs as defined in your project configuration.
Listing and Inspecting Modules
Retrieve the list of loaded modules or query detailed information about a specific module. Both functions return promises and accept module IDs as defined in your project configuration.
import { ListModules, GetModuleInfo } from "@antelopejs/interface-core/modules";
// List all loaded module IDs
const modules = await ListModules();
// Get detailed info about a module
const info = await GetModuleInfo("my-module");
// info.status: "loaded" | "constructed" | "active" | "unknown"
// info.source, info.config, info.importOverrides, info.disabledExports, info.localPath
Loading Modules Dynamically
Add new modules to a running application without restarting. The third argument controls whether the module starts immediately after loading.
import { LoadModule } from "@antelopejs/interface-core/modules";
await LoadModule(
"new-module",
{
source: { type: "package", package: "@scope/module", version: "1.0.0" },
config: {/* module config */},
},
true,
); // true = autostart
Lifecycle Control
Start, stop, destroy, or perform Hot Module Reloading (HMR) on individual modules. These functions give you fine-grained control over the module lifecycle at runtime.
import {
StartModule,
StopModule,
DestroyModule,
ReloadModule,
} from "@antelopejs/interface-core/modules";
await StartModule("my-module");
await StopModule("my-module");
await DestroyModule("my-module");
await ReloadModule("my-module"); // HMR
ReloadModule performs a stop-destroy-load-start cycle in a single call. During development, the --watch flag on ajs project dev triggers this automatically when source files change.When a watched module fails to reload, the error is logged, the module stays stopped, and its next source change reloads it again. The dev process keeps running: asynchronous work the destroyed generation left pending fails with ModuleContextInvalidatedError when it resumes, and that failure is logged instead of shutting the process down. Cancel such work in destroy() to keep the log clean.
Interfaces
Interfaces define the contracts that modules use to communicate — declaring functions, events, and registrations without tying them to any specific implementation.
Proxies
Proxies are the communication backbone of Antelopejs — AsyncProxy for function calls, EventProxy for broadcasting, and RegisteringProxy for registration systems.