or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

bindings.mdcontainer.mddependency-injection.mdindex.mdmodules.mdparsing-utilities.mdresolver.mdtesting.md
tile.json

resolver.mddocs/

Container Resolver

Isolated resolver instances that can resolve dependencies and create class instances without modifying the parent container. ContainerResolver provides a scoped environment for dependency resolution with its own local bindings.

Capabilities

ContainerResolver Class

Isolated container resolver for dependency resolution without mutation capabilities.

/**
 * Container resolver exposes the APIs to resolve bindings. You can think
 * of resolver as an isolated container instance, with only the APIs
 * to resolve bindings.
 */
class ContainerResolver<KnownBindings extends Record<any, any>> {
  /** Checks if a binding exists in the resolver */
  hasBinding<Binding extends keyof KnownBindings>(binding: Binding): boolean;
  hasBinding(binding: BindingKey): boolean;
  
  /** Checks if all specified bindings exist in the resolver */
  hasAllBindings<Binding extends keyof KnownBindings>(bindings: Binding[]): boolean;
  hasAllBindings(bindings: BindingKey[]): boolean;
  
  /** Resolves a binding or constructs a class instance */
  make<Binding extends keyof KnownBindings>(
    binding: Binding,
    runtimeValues?: any[],
    createError?: ErrorCreator
  ): Promise<Binding extends string | symbol ? KnownBindings[Binding] : Make<Binding>>;
  make<Binding>(
    binding: Binding,
    runtimeValues?: any[],
    createError?: ErrorCreator
  ): Promise<Make<Binding>>;
  
  /** Resolves binding in context of a parent class for contextual bindings */
  resolveFor<Binding>(
    parent: unknown,
    binding: Binding,
    runtimeValues?: any[],
    createError?: ErrorCreator
  ): Promise<Make<Binding>>;
  
  /** Calls a method on an object by injecting its dependencies */
  call<Value extends Record<any, any>, Method extends ExtractFunctions<Value>>(
    value: Value,
    method: Method,
    runtimeValues?: any[],
    createError?: ErrorCreator
  ): Promise<ReturnType<Value[Method]>>;
  
  /** Registers a direct value in the resolver's local scope */
  bindValue<Binding extends keyof KnownBindings>(
    binding: Binding extends string | symbol ? Binding : never,
    value: KnownBindings[Binding]
  ): void;
  bindValue<Binding extends AbstractConstructor<any>>(
    binding: Binding,
    value: InstanceType<Binding>
  ): void;
}

Usage Examples:

import { Container, inject } from "@adonisjs/fold";

const container = new Container();

// Register container-level bindings
container.singleton("logger", () => new Logger());
container.bindValue("app_name", "My App");

// Create isolated resolver
const resolver = container.createResolver();

// Resolver has access to container bindings
const logger = await resolver.make("logger");
const appName = await resolver.make("app_name");

// Resolver can bind its own values
resolver.bindValue("request_id", "req_123");
const requestId = await resolver.make("request_id");

// Container doesn't have access to resolver-specific bindings
try {
  await container.make("request_id"); // Throws error
} catch (error) {
  console.log("request_id not found in container");
}

Resolver Creation

Create isolated resolver instances from the container.

/**
 * Create a container resolver to resolve bindings, or make classes.
 * Resolver values are isolated from the container.
 */
createResolver(): ContainerResolver<KnownBindings>;

Usage Examples:

import { Container } from "@adonisjs/fold";

const container = new Container();

// Create multiple isolated resolvers
const userResolver = container.createResolver();
const adminResolver = container.createResolver();

// Each resolver can have its own context
userResolver.bindValue("user_role", "user");
adminResolver.bindValue("user_role", "admin");

// Resolvers are isolated
const userRole = await userResolver.make("user_role"); // "user"
const adminRole = await adminResolver.make("user_role"); // "admin"

Scoped Value Binding

Register values that are specific to the resolver instance.

/**
 * Registers a binding as a direct value in the resolver's local binding values.
 * This method allows you to register pre-constructed instances or values that
 * can be resolved later. The binding key must be a string, symbol, or class constructor.
 */
bindValue<Binding extends keyof KnownBindings>(
  binding: Binding extends string | symbol ? Binding : never,
  value: KnownBindings[Binding]
): void;
bindValue<Binding extends AbstractConstructor<any>>(
  binding: Binding,
  value: InstanceType<Binding>
): void;

Usage Examples:

import { Container, inject } from "@adonisjs/fold";

@inject()
class UserService {
  constructor(
    private logger: Logger,
    private currentUser: User
  ) {}
}

const container = new Container();
container.singleton("logger", () => new Logger());

// Create resolver for specific request context
const requestResolver = container.createResolver();

// Bind request-specific values
requestResolver.bindValue("current_user", new User({ id: 123, name: "Alice" }));
requestResolver.bindValue("request_id", "req_456");

// UserService gets request-specific current_user
const userService = await requestResolver.make(UserService);

Contextual Resolution

Resolve dependencies in the context of a specific parent class for contextual bindings.

/**
 * Resolves binding in context of a parent. The method is the same as the "make" method,
 * but takes a parent class constructor. This enables contextual binding resolution where
 * different implementations can be injected based on the requesting parent class.
 */
resolveFor<Binding>(
  parent: unknown,
  binding: Binding,
  runtimeValues?: any[],
  createError?: ErrorCreator
): Promise<Make<Binding>>;

Usage Examples:

import { Container, inject } from "@adonisjs/fold";

interface CacheContract {
  get(key: string): Promise<any>;
  set(key: string, value: any): Promise<void>;
}

class RedisCache implements CacheContract {
  async get(key: string) { return "redis_value"; }
  async set(key: string, value: any) {}
}

class MemoryCache implements CacheContract {
  async get(key: string) { return "memory_value"; }
  async set(key: string, value: any) {}
}

@inject()
class UserService {
  constructor(private cache: CacheContract) {}
}

@inject()
class AdminService {
  constructor(private cache: CacheContract) {}
}

const container = new Container();

// Set up contextual bindings
container
  .when(UserService)
  .give(CacheContract)
  .use(() => new MemoryCache());

container
  .when(AdminService)
  .give(CacheContract)
  .use(() => new RedisCache());

const resolver = container.createResolver();

// Resolve with specific parent context
const userCache = await resolver.resolveFor(UserService, CacheContract); // MemoryCache
const adminCache = await resolver.resolveFor(AdminService, CacheContract); // RedisCache

Method Injection

Call methods on objects with automatic dependency injection using the resolver.

/**
 * Calls a method on an object by injecting its dependencies. The method dependencies
 * are resolved in the same manner as class constructor dependencies, enabling
 * dependency injection for method calls.
 */
call<Value extends Record<any, any>, Method extends ExtractFunctions<Value>>(
  value: Value,
  method: Method,
  runtimeValues?: any[],
  createError?: ErrorCreator
): Promise<ReturnType<Value[Method]>>;

Usage Examples:

import { Container, inject } from "@adonisjs/fold";

class ApiController {
  @inject()
  async getUsers(userService: UserService, logger: Logger) {
    logger.info("Fetching users");
    return userService.getAll();
  }
  
  @inject()
  async createUser(userService: UserService, emailService: EmailService, userData: any) {
    const user = await userService.create(userData);
    await emailService.send(user.email, "Welcome!");
    return user;
  }
}

const container = new Container();
container.singleton("user_service", () => new UserService());
container.singleton("logger", () => new Logger());
container.singleton("email_service", () => new EmailService());

const resolver = container.createResolver();
const controller = new ApiController();

// Method injection with resolver
const users = await resolver.call(controller, "getUsers");
const newUser = await resolver.call(controller, "createUser", [{ name: "Alice" }]);

Binding Precedence

Understanding the order of binding resolution in resolvers.

Resolution Order:

  1. Resolver-specific values (set via resolver.bindValue())
  2. Container aliases
  3. Container binding values
  4. Container bindings (bind/singleton)
  5. Class constructor resolution (if binding is a class)

Usage Examples:

import { Container } from "@adonisjs/fold";

const container = new Container();

// Container bindings
container.bindValue("service", "container_service");
container.bind("api_url", () => "container_api_url");

const resolver = container.createResolver();

// Resolver override takes precedence
resolver.bindValue("service", "resolver_service");

const service = await resolver.make("service"); // "resolver_service"
const apiUrl = await resolver.make("api_url"); // "container_api_url"

Error Handling

Custom error handling for resolver operations.

Usage Examples:

import { Container } from "@adonisjs/fold";
import { RuntimeException } from "@poppinss/utils/exception";

const container = new Container();
const resolver = container.createResolver();

// Custom error creator
const createError = (message: string) => {
  return new RuntimeException(`Resolver Error: ${message}`);
};

try {
  const missing = await resolver.make("missing_binding", [], createError);
} catch (error) {
  console.error(error.message); // "Resolver Error: Cannot resolve binding..."
}