Daydreams Logo
Advanced

Services

Dependency Injection & Lifecycle Management.

Daydreams utilizes a Dependency Injection (DI) container and a Service Provider pattern to manage dependencies and component lifecycles effectively.

Dependency Injection (container.ts)

At the heart of the framework's modularity is a simple Dependency Injection container, created using createContainer(). The container is responsible for instantiating and providing access to various services and components throughout the agent's lifecycle.

Purpose:

  • Decouples components by removing the need for them to know how to create their dependencies.
  • Manages the lifecycle of services (e.g., ensuring only one instance of a database client exists).
  • Makes components like loggers, clients, or configuration easily accessible.

Core Methods:

  • container.register(token, factory): Registers a factory function. A new instance is created every time resolve is called for the token.
  • container.singleton(token, factory): Registers a factory function, but the instance is created only once on the first resolve call. Subsequent calls return the same instance.
  • container.instance(token, value): Registers a pre-existing object instance directly.
  • container.resolve<Type>(token): Retrieves the instance associated with the token. Throws an error if the token is not registered.
  • container.alias(aliasToken, originalToken): Creates an alternative name (aliasToken) to resolve an existing originalToken.
import { createContainer, Logger, LogLevel } from "@daydreamsai/core"; // Assuming Logger/LogLevel are exported
 
// Assume DatabaseClient exists
declare class DatabaseClient {
  constructor(uri: string | undefined);
}
 
const container = createContainer();
 
// Register a singleton database client
container.singleton("dbClient", () => new DatabaseClient(process.env.DB_URI));
 
// Register a pre-created config object
const config = { apiKey: "123" };
container.instance("appConfig", config);
 
// Register a transient logger (new instance each time)
container.register(
  "requestLogger",
  () => new Logger({ level: LogLevel.DEBUG })
);
 
// Resolve dependencies
const db = container.resolve<DatabaseClient>("dbClient");
const cfg = container.resolve<typeof config>("appConfig");
const logger1 = container.resolve<Logger>("requestLogger");
const logger2 = container.resolve<Logger>("requestLogger"); // Different instance from logger1

The main Agent instance, Logger, TaskRunner, and other core components are typically registered within the container when createDreams is called.

Service Providers (serviceProvider.ts)

While you could register everything directly with the container, Daydreams uses a Service Provider pattern to organize the registration and initialization (booting) of related services, especially within extensions.

Purpose:

  • Groups related service registrations.
  • Provides a dedicated boot phase for initialization logic that might depend on other services already being registered (e.g., connecting a client after its configuration is registered).

Defining a Service Provider:

Use the service helper function:

import { service, type Container } from "@daydreamsai/core";
 
// Assume MyApiClient exists and has a connect method
declare class MyApiClient {
  constructor(config: { baseUrl: string | undefined });
  connect(): Promise<void>;
}
 
const myApiService = service({
  // Optional: Register dependencies into the container.
  // Runs before the boot phase.
  register(container: Container) {
    container.singleton("apiConfig", () => ({ baseUrl: process.env.API_URL }));
    container.singleton(
      "apiClient",
      (c) => new MyApiClient(c.resolve("apiConfig"))
    );
  },
 
  // Optional: Perform initialization logic after registration.
  // Runs during agent.start() after all services are registered.
  async boot(container: Container) {
    const apiClient = container.resolve<MyApiClient>("apiClient");
    await apiClient.connect(); // Example: Connect the client
    console.log("My API Client Connected!");
  },
});

Lifecycle:

  1. Registration: When a service provider is added to the agent (usually via an extension), its register method is called immediately by the ServiceManager (created internally in createDreams).
  2. Booting: When agent.start() is called, the ServiceManager iterates through all registered service providers and calls their boot methods after all register methods have completed.

Services and the DI container form the backbone for managing dependencies and initializing components within Daydreams agents and extensions.

On this page