Containers
Containers hold your registered services and resolve them on demand. They can inherit from parent containers, creating a hierarchy that enables scoped service resolution.
Creating a Container
import { createContainer } from "@asimojs/asimo";
const app = createContainer({ name: "app", // required, must not contain "/" services: [...], // optional: factories and bundles extends: [...], // optional: parent containers config: {...}, // optional: global retry/lifecycle settings});Name Constraints
Container names must not contain forward slashes (/). The path property is auto-generated by concatenating the first parent’s path with the container’s name:
const root = createContainer({ name: "root" });root.path; // "/root"
const child = createContainer({ name: "child", extends: [root] });child.path; // "/root/child"Retrieving Services
get() — Synchronous
// Single serviceconst logger = container.get(LOGGER); // type: Logger
// Multiple services (tuple return)const [logger, db] = container.get(LOGGER, DB); // type: [Logger, Database]Use getOptional() to handle optional services gracefully (i.e. dependencies not defined as mandatory).
fetch() — Asynchronous
Works with both sync and async IIDs. Returns a Promise:
// Single serviceconst config = await container.fetch(CONFIG); // type: Config
// Multiple servicesconst [config, db] = await container.fetch(CONFIG, DB); // type: [Config, Database]Use fetchOptional() for optional services.
Optional Retrieval
// Returns T | null (sync) or Promise<T> | null (async)const logger = container.getOptional(LOGGER); // Logger | nullconst config = container.getOptional(CONFIG); // Promise<Config> | nullconst config2 = await container.fetchOptional(CONFIG); // Config | nullContainer Hierarchy
Child containers inherit services from their parents. When a service is requested, asimo walks up the parent chain depth-first:
const core = createContainer({ name: "core", services: [ factory({ provide: CONFIG, load: async () => loadConfig() }), ],});
const feature = createContainer({ name: "feature", extends: [core], // inherits CONFIG services: [ factory({ provide: FEATURE_SVC, load: () => new FeatureSvc() }), ],});
// FEATURE_SVC is resolved from `feature`, CONFIG from `core`const svc = feature.get(FEATURE_SVC);const cfg = await feature.fetch(CONFIG);Multiple Parents
A container can extend multiple parent containers. Services are resolved from parents in order — the first match wins:
const app = createContainer({ name: "app", extends: [coreServices, authServices, loggingServices],});Important: the path property is always computed from the first parent:
app.path; // computed from coreServices.path + "/app"Introspection
has() — Check if a service exists
container.has(LOGGER); // booleaninspect() — Get service status
// Single serviceconst info = container.inspect(LOGGER);// { ns: "app.services.Logger", path: "/app", status: "loaded" }
// All servicesconst all = container.inspect();// ServiceInspectInfo[]Status values:
"loaded"— factory has been called, value is cached"registered"— factory is registered but hasn’t been called yet"pending"— part of a bundle that hasn’t been loaded yet
entries() — Iterate direct services
for (const entry of container.entries()) { console.log(`${entry.ns}: ${entry.status}`);}dump() — Debug output
console.log(container.dump());// Asimo IoC Container: /app// /app: app.services.Logger - loaded// /app: app.services.Config - pendingContainer Identity
Every container is identified by two properties:
name— the local name, must be unique among siblings but can repeat across branchespath— the full hierarchical identifier, unique across the entire application
container.name; // "child"container.path; // "/root/child"The path is useful for debugging and error tracing — all AsimoError messages include the container path where the error occurred.