Skip to content

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 service
const 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 service
const config = await container.fetch(CONFIG); // type: Config
// Multiple services
const [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 | null
const config = container.getOptional(CONFIG); // Promise<Config> | null
const config2 = await container.fetchOptional(CONFIG); // Config | null

Container 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); // boolean

inspect() — Get service status

// Single service
const info = container.inspect(LOGGER);
// { ns: "app.services.Logger", path: "/app", status: "loaded" }
// All services
const 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 - pending

Container Identity

Every container is identified by two properties:

  • name — the local name, must be unique among siblings but can repeat across branches
  • path — 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.