Creating a Module
Initialize a Module
The CLI provides a scaffolding command that generates a module with the standard directory structure and configuration files.
ajs module init ./my-module
The command creates a new directory with a ready-to-use module template. The generated project includes TypeScript configuration, a package.json with Antelopejs metadata, and placeholder files for interfaces and implementations.
Module Structure
Every module follows a consistent directory layout that separates interface declarations from their implementations.
my-module/
├── package.json # Module metadata with antelopeJs config
├── tsconfig.json # TypeScript configuration
├── src/
│ ├── index.ts # Entry point with lifecycle functions
│ ├── interface/ # Interface contract
│ │ └── index.ts
│ └── implementations/ # Interface implementation
│ └── index.ts
└── dist/ # Compiled output
The interface/ directory holds the contract definition — types, proxy declarations, and wrapper functions that describe what the module offers. The implementations/ directory holds the concrete logic that fulfills the contract.
Package Configuration
The antelopeJs field in package.json defines how the core discovers and loads the module.
{
"name": "@your-org/my-module",
"version": "1.0.0",
"main": "dist/index.js",
"antelopeJs": {
"implements": ["@your-org/my-module"],
"baseUrl": "dist",
"moduleAliases": {},
"defaultConfig": {}
}
}
| Field | Description |
|---|---|
implements | Array of npm package names from which consumers import the interface |
baseUrl | Base directory for path resolution (typically dist) |
moduleAliases | Additional module import aliases |
defaultConfig | Default configuration values for this module |
test | Path to the test configuration file |
The implements array lists the npm package names that expose the interface contract. When the interface is embedded, the module lists its own package name because consumers import the interface from it (e.g., @your-org/my-module/interface). When the interface lives in a standalone package, the module lists that package name instead (e.g., @antelopejs/interface-api).
Entry Point
The module entry point exports lifecycle functions that the core calls in a specific order. These functions control initialization, startup, and teardown.
import { ImplementInterface } from "@antelopejs/interface-core";
export async function construct(config: any): Promise<void> {
// Called first — initialize resources
// Register interface implementations
ImplementInterface(
await import("./interface"),
await import("./implementations")
);
}
export function start(): void {
// Called after all modules are constructed
// All interfaces are now available
}
export async function stop(): Promise<void> {
// Gracefully stop operations
}
export async function destroy(): Promise<void> {
// Clean up resources (close connections, etc.)
}
The lifecycle follows four phases:
| Phase | When It Runs | Purpose |
|---|---|---|
construct | First, for each module | Initialize resources and register interface implementations |
start | After all modules are constructed | Begin operations — all interfaces are now resolved |
stop | When shutting down | Gracefully stop ongoing operations |
destroy | After all modules are stopped | Release resources, close connections |
construct function receives the module's configuration object. Use it to set up database connections, initialize state, or configure behavior based on user-provided settings.construct() or start(), the application boot fails. The core does not catch these errors — they propagate and prevent other modules from starting. Handle errors internally when you want graceful degradation, or let them propagate intentionally to signal a critical failure that should stop the entire application.TypeScript Configuration
A standard tsconfig.json for an Antelopejs module includes the compiler options needed for decorators and type declarations.
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
Importing Interfaces
To use interfaces provided by other modules, add the interface package as a dependency and import from it directly.
import { GetUser } from "@antelopejs/interface-user-service";
// Use the interface function
const user = await GetUser("user-123");
The import resolves to the interface declaration, not the implementation. At runtime, the core connects the call to whichever module implements that interface. Your code never depends on a specific implementation — only on the contract.
Adding the Module to a Project
Register the module in your project's antelope.config.ts to make it available at runtime.
import { defineConfig } from "@antelopejs/interface-core/config";
export default defineConfig({
name: "my-project",
modules: {
"my-module": {
source: { type: "local", path: "./my-module" }
}
}
});
The source field supports multiple types. Local paths point to a directory on disk, which is ideal during development. For production, modules can also come from npm packages or git repositories.
ajs project dev --watch to compile and reload automatically.See also
- Modules — Module states and lifecycle overview
- Testing — How to test your module
- Architecture — How the core orchestrates modules