Core Utilities
Module Context Functions
Antelopejs provides several core utility functions that power its interface-based architecture. These utilities enable features like metadata handling, interface implementation, and module awareness that are essential for building modular applications.
GetResponsibleModule
The GetResponsibleModule
function is central to the module awareness capabilities of Antelopejs. It determines which module is responsible for the current code execution by analyzing the call stack.
Parameters
ignoreInterfaces
(default: true): Whether to ignore interfaces when determining the responsible modulestartFrame
(default: 0): The starting frame in the stack trace to analyze
Return Value
Returns the module ID as a string, or undefined
if no module is found.
Example
import { GetResponsibleModule } from '@ajs/core/beta';
// Get the ID of the module that's currently executing code
const currentModuleId = GetResponsibleModule();
// With custom parameters
const moduleId = GetResponsibleModule(false, 2); // includeInterfaces = false, startFrame = 2
// Common usage pattern for logging with module context
function logWithModuleContext(message: string) {
const moduleId = GetResponsibleModule();
console.log(`[${moduleId || 'unknown'}] ${message}`);
}
This function is primarily used internally by the proxy system to track which module registered callbacks or event handlers, but you can also use it directly when you need to obtain the current module context.
Interface Implementation Functions
InterfaceFunction
The InterfaceFunction
utility creates a function that routes calls through an AsyncProxy
, providing a more convenient way to work with asynchronous proxies. It's essentially a helper that wraps an AsyncProxy
to create a more intuitive function interface, removing the need to use .call()
when invoking the proxy.
Return Value
Returns a function that:
- Takes the same parameters as the expected implementation
- Returns a Promise resolving to the implementation's return value
- Has a
.proxy
property containing the underlying AsyncProxy object
Example
import { InterfaceFunction } from '@ajs/core/beta';
// In an interface definition module
export const GetUserById = InterfaceFunction<(userId: string) => User>();
export const CreateUser = InterfaceFunction<(userData: UserInput) => User>();
export const DeleteUser = InterfaceFunction<(userId: string) => boolean>();
// In an implementation module
CreateUser.proxy.onCall(async (userData) => {
// Implementation logic
return newUser;
});
// In a consumer module
const user = await GetUserById('user123');
// The function can be called directly like a regular function
// Equivalent to using AsyncProxy directly: await GetUserById.proxy.call('user123');
// Another example with more complex data
const newUser = await CreateUser({ name: 'John Doe', email: '[email protected]' });
For more details on how AsyncProxy works under the hood, refer to the Proxies documentation.
ImplementInterface
The ImplementInterface
function links a declared interface with its implementation, setting up the necessary proxies and event handlers to enable cross-module communication.
Parameters
declaration
: The interface declaration to implementimplementation
: The implementation of the interface (can be a Promise)
Return Value
Returns a Promise resolving to an object containing:
declaration
: The resolved declarationimplementation
: The resolved implementation
Example
import { ImplementInterface } from '@ajs/core/beta';
import * as userInterface from '@ajs.local/users/1';
// Implementation object matching the interface structure
const userImplementation = {
getUser: async (id: string) => {
// Implementation logic
return { id, username: 'johndoe', email: '[email protected]', created: new Date() };
},
createUser: async (data) => {
// Implementation logic
return { id: 'new-id', ...data, created: new Date() };
},
// Other implementations...
};
// Connect the interface declaration with the implementation
await ImplementInterface(userInterface, userImplementation);
// Alternatively, in a module's construct function:
export async function construct() {
// Import the interface declaration and implementation
// You can also pass the import promises directly to ImplementInterface
await ImplementInterface(
import('@ajs.local/interfaces/users/1'),
import('./implementations/users/1')
);
}
For a complete guide on how interfaces and implementations work together, see the Export Interfaces chapter.
Metadata Handling
GetMetadata
The GetMetadata
function retrieves or creates metadata associated with a target object. It's commonly used in decorator implementations for routing, parameter handling, and other reflection-based operations.
Parameters
target
: The target object to get metadata formeta
: The metadata class with a static symbol keyinherit
(default: true): Whether to inherit metadata from the prototype chain
Return Value
Returns an instance of the metadata class associated with the target.
Example
import { GetMetadata } from '@ajs/core/beta';
import { MakeMethodDecorator } from '@ajs/core/beta/decorators';
// Define a simple metadata class for controllers
class ControllerMeta {
// Symbol key used for storage
static key = Symbol('controller-metadata');
// Store the base path for the controller
path: string;
// Track routes defined on this controller
routes: Map<string, { method: string, path: string }> = new Map();
constructor(target: any) {
this.path = '';
}
}
// Create a Controller class decorator
function Controller(path: string) {
return function(target: any) {
// Get or create metadata for the controller class
const metadata = GetMetadata(target, ControllerMeta);
// Set the controller's base path
metadata.path = path;
return target;
};
}
// Create a GET method decorator
const Get = MakeMethodDecorator((target: any, key: string, descriptor: PropertyDescriptor, path?: string) => {
// Get the controller's metadata
const metadata = GetMetadata(target.constructor, ControllerMeta);
// Register this method as a route
metadata.routes.set(key, {
method: 'GET',
path: path || key
});
// Note: In a real implementation, we would need to register this route with a router or server
// This example only demonstrates how to use metadata to store routing information
return descriptor;
});
// Usage example
@Controller('/users')
class UserController {
@Get()
list() {
return { users: [] };
}
@Get('/:id')
getById(id: string) {
return { id, name: 'John Doe' };
}
}
// We can inspect the metadata later
const metadata = GetMetadata(UserController, ControllerMeta);
console.log(metadata.path); // '/users'
console.log(metadata.routes.get('list')); // { method: 'GET', path: 'list' }
console.log(metadata.routes.get('getById')); // { method: 'GET', path: '/:id' }
This pattern is commonly used in web frameworks to implement decorators. The metadata provides a way to store and retrieve information about classes and methods that can be processed at runtime.
Interface Discovery
Multiple modules can implement the same interface and version. In your application's antelope.json
configuration, you can specify distinct identifiers for each implementation:
{
"modules": {
"mongodb": {
"source": {
"type": "package",
"name": "@antelopejs/mongodb",
"version": "0.0.1"
},
"config": {
"url": "mongodb://localhost:27017"
},
"importOverrides": [
{
"interface": "database@beta",
"source": "mongodb",
"id": "main"
}
]
},
"mongodb-2": {
"source": {
"type": "package",
"name": "@antelopejs/mongodb",
"version": "0.0.1"
},
"config": {
"url": "mongodb://localhost:27018"
},
"importOverrides": [
{
"interface": "database@beta",
"source": "mongodb-2",
"id": "secondary"
}
]
}
}
}
In this example, two modules are using the MongoDB implementation, but each connects to a different database instance. By specifying the id
field in the importOverrides
, you can later target a specific implementation using the utility functions below.
GetInterfaceInstances
The GetInterfaceInstances
function retrieves all connections to implementations of a specified interface across the system. Note that the interfaceID
is composed of the interface name and version in the format name@version
.
Parameters
interfaceID
: The ID of the interface to get instances for, in the formatname@version
Return Value
Returns an array of interface connections, each containing:
id
: Optional unique identifier for the connectionpath
: The path to the interface implementation
Example
import { GetInterfaceInstances } from '@ajs/core/beta';
// Get all instances of the 'database@beta' interface
const databaseInterfaceInstances = GetInterfaceInstances('database@beta');
// Connect to each database
for (const provider of databaseInterfaceInstances) {
console.log(`Found database provider at: ${provider.path}`);
console.log(`Provider ID: ${provider.id || 'default'}`);
// Load and use the provider
}
GetInterfaceInstance
The GetInterfaceInstance
function retrieves a specific connection to an implementation of the specified interface by ID.
Parameters
interfaceID
: The ID of the interface to get an instance for, in the formatname@version
connectionID
: The ID of the specific connection to retrieve
Return Value
Returns the interface connection or undefined if not found, containing:
id
: Unique identifier for the connectionpath
: The path to the interface implementation
Example
import { GetInterfaceInstance } from '@ajs/core/beta';
// Get a specific instance of the 'database@beta' interface
const primaryDatabaseProvider = GetInterfaceInstance('database@beta', 'main');
// Based on the antelope.json configuration above:
// Get the 'main' database provider
const mainProvider = GetInterfaceInstance('database@beta', 'main');
if (mainProvider) {
// Load and use the specific provider connecting to localhost:27017
const mainDb = await import(mainProvider.path);
// Use the interface...
}
// Get the 'secondary' database provider
const secondaryProvider = GetInterfaceInstance('database@beta', 'secondary');
if (secondaryProvider) {
// Load and use the specific provider connecting to localhost:27018
const secondaryDb = await import(secondaryProvider.path);
// Use the interface...
}
This approach allows your application to work with multiple implementations of the same interface simultaneously, each configured for different purposes.