Module Development

Creating a Module

Create an Antelopejs module from scratch — initialize the project, configure the entry point, and register it in your application.

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.

package.json
{
  "name": "@your-org/my-module",
  "version": "1.0.0",
  "main": "dist/index.js",
  "antelopeJs": {
    "implements": ["@your-org/my-module"],
    "baseUrl": "dist",
    "moduleAliases": {},
    "defaultConfig": {}
  }
}
FieldDescription
implementsArray of npm package names from which consumers import the interface
baseUrlBase directory for path resolution (typically dist)
moduleAliasesAdditional module import aliases
defaultConfigDefault configuration values for this module
testPath 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).

Interfaces start embedded inside the module and can later be extracted into standalone packages. See the Interfaces concept page and the Exporting Interfaces guide for details on this lifecycle.

Entry Point

The module entry point exports lifecycle functions that the core calls in a specific order. These functions control initialization, startup, and teardown.

src/index.ts
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:

PhaseWhen It RunsPurpose
constructFirst, for each moduleInitialize resources and register interface implementations
startAfter all modules are constructedBegin operations — all interfaces are now resolved
stopWhen shutting downGracefully stop ongoing operations
destroyAfter all modules are stoppedRelease resources, close connections
The 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.
If your module throws an error during 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.

tsconfig.json
{
  "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.

You do not need to know which module implements an interface. The core resolves implementations based on your project configuration. Swapping implementations requires only a configuration change, not a code change.

Adding the Module to a Project

Register the module in your project's antelope.config.ts to make it available at runtime.

antelope.config.ts
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.

The module must be compiled before the core can load it. Run your TypeScript build step before starting the project, or use 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