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

bindings.mddocs/

Binding Registration

Multiple binding types for registering dependencies including direct values, factory functions, singletons, and contextual bindings. The binding system allows flexible dependency registration patterns to suit different use cases.

Capabilities

Direct Value Bindings

Register pre-constructed instances or primitive values directly in the container.

/**
 * Registers a binding as a direct value
 * @param binding - The binding key (string, symbol, or class constructor)
 * @param value - The actual value to bind
 */
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 } from "@adonisjs/fold";

const container = new Container();

// Bind primitive values
container.bindValue("app_name", "My Application");
container.bindValue("port", 3000);
container.bindValue("debug", true);

// Bind pre-constructed instances
const logger = new Logger({ level: "info" });
container.bindValue("logger", logger);
container.bindValue(Logger, logger);

// Bind configuration objects
container.bindValue("database_config", {
  host: "localhost",
  port: 5432,
  database: "myapp"
});

// Use the bound values
const appName = await container.make("app_name");
const dbConfig = await container.make("database_config");

Factory Function Bindings

Register factory functions that create instances each time they're resolved.

/**
 * Registers a binding with a factory function
 * @param binding - The binding key (string, symbol, or class constructor)
 * @param resolver - Factory function to construct the dependency
 */
bind<Binding extends keyof KnownBindings>(
  binding: Binding extends string | symbol ? Binding : never,
  resolver: BindingResolver<KnownBindings, KnownBindings[Binding]>
): void;
bind<Binding extends AbstractConstructor<any>>(
  binding: Binding,
  resolver: BindingResolver<KnownBindings, InstanceType<Binding>>
): void;

Usage Examples:

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

const container = new Container();

// Simple factory function
container.bind("random_id", () => {
  return Math.random().toString(36).substring(7);
});

// Factory with dependencies
container.bind("database", (resolver) => {
  const config = resolver.make("database_config");
  return new Database(config);
});

// Async factory function
container.bind("api_client", async (resolver) => {
  const token = await resolver.make("auth_token");
  return new ApiClient({ token });
});

// Factory with runtime values
container.bind("user_service", (resolver, runtimeValues) => {
  const database = resolver.make("database");
  const logger = resolver.make("logger");
  const options = runtimeValues?.[0] || {};
  return new UserService(database, logger, options);
});

// Use the bindings - new instance each time
const id1 = await container.make("random_id");
const id2 = await container.make("random_id"); // Different ID
const userService = await container.make("user_service", [{ cache: true }]);

Singleton Bindings

Register factory functions that create instances only once and cache the result.

/**
 * Registers a binding as a singleton
 * @param binding - The binding key (string, symbol, or class constructor)
 * @param resolver - Factory function to construct the dependency (called only once)
 */
singleton<Binding extends keyof KnownBindings>(
  binding: Binding extends string | symbol ? Binding : never,
  resolver: BindingResolver<KnownBindings, KnownBindings[Binding]>
): void;
singleton<Binding extends AbstractConstructor<any>>(
  binding: Binding,
  resolver: BindingResolver<KnownBindings, InstanceType<Binding>>
): void;

Usage Examples:

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

const container = new Container();

// Singleton database connection
container.singleton("database", (resolver) => {
  const config = resolver.make("database_config");
  return new Database(config);
});

// Singleton logger
container.singleton("logger", () => {
  return new Logger({ level: "info" });
});

// Expensive singleton with async initialization
container.singleton("cache", async (resolver) => {
  const config = await resolver.make("cache_config");
  const cache = new RedisCache(config);
  await cache.connect();
  return cache;
});

// Use singletons - same instance returned
const db1 = await container.make("database");
const db2 = await container.make("database"); // Same instance
console.log(db1 === db2); // true

Alias Bindings

Create aliases that point to existing bindings or class constructors.

/**
 * Registers an alias for a binding
 * @param alias - The alias key (must be string or symbol)
 * @param value - Reference to existing binding or class constructor
 */
alias<Alias extends keyof KnownBindings>(
  alias: Alias extends string | symbol ? Alias : never,
  value: keyof KnownBindings | AbstractConstructor<any>
): void;

Usage Examples:

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

const container = new Container();

// Register original bindings
container.singleton("primary_database", () => new Database("primary"));
container.bind(Logger, () => new Logger());

// Create aliases
container.alias("db", "primary_database");
container.alias("log", Logger);

// Use aliases
const database = await container.make("db"); // Same as "primary_database"
const logger = await container.make("log"); // Same as Logger

Contextual Bindings

Register different implementations based on the requesting class context.

/**
 * Creates a contextual builder to define contextual bindings
 * @param parent - The parent class constructor
 * @returns ContextBindingsBuilder instance for fluent API
 */
when(parent: Constructor<any>): ContextBindingsBuilder<KnownBindings, AbstractConstructor<any>>;

/**
 * Adds a contextual binding for a given class constructor
 * @param parent - The parent class constructor
 * @param binding - The dependency class constructor
 * @param resolver - Factory function to resolve the dependency in this context
 */
contextualBinding<Binding extends AbstractConstructor<any>>(
  parent: Constructor<any>,
  binding: Binding,
  resolver: BindingResolver<KnownBindings, InstanceType<Binding>>
): void;

Usage Examples:

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

interface HashContract {
  hash(value: string): Promise<string>;
}

class Argon2Hash implements HashContract {
  async hash(value: string) { return "argon2_hash"; }
}

class BcryptHash implements HashContract {
  async hash(value: string) { return "bcrypt_hash"; }
}

@inject()
class UserService {
  constructor(private hasher: HashContract) {}
}

@inject()
class AdminService {
  constructor(private hasher: HashContract) {}
}

const container = new Container();

// Contextual bindings using fluent API
container
  .when(UserService)
  .give(HashContract)
  .use(() => new BcryptHash());

container
  .when(AdminService)
  .give(HashContract)
  .use(() => new Argon2Hash());

// Or using direct method
container.contextualBinding(
  UserService,
  HashContract,
  () => new BcryptHash()
);

// Different implementations injected based on context
const userService = await container.make(UserService); // Gets BcryptHash
const adminService = await container.make(AdminService); // Gets Argon2Hash

Contextual Bindings Builder

Fluent API for creating contextual bindings with a readable syntax.

class ContextBindingsBuilder<KnownBindings extends Record<any, any>, Binding> {
  /**
   * Specifies dependency to provide in the contextual binding
   * @param binding - The dependency class constructor to provide
   * @returns Builder instance for method chaining
   */
  give<GiveBinding extends AbstractConstructor<any>>(
    binding: GiveBinding
  ): ContextBindingsBuilder<KnownBindings, GiveBinding>;
  
  /**
   * Specifies resolver function for the contextual binding
   * @param resolver - Factory function to resolve the dependency
   */
  use(resolver: BindingResolver<KnownBindings, InstanceType<Binding>>): void;
}

Usage Examples:

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

interface StorageContract {
  store(file: Buffer): Promise<string>;
}

class LocalStorage implements StorageContract {
  async store(file: Buffer) { return "local_path"; }
}

class S3Storage implements StorageContract {
  async store(file: Buffer) { return "s3_url"; }
}

@inject()
class UserController {
  constructor(private storage: StorageContract) {}
}

@inject()
class AdminController {
  constructor(private storage: StorageContract) {}
}

const container = new Container();

// Fluent contextual binding
container
  .when(UserController)
  .give(StorageContract)
  .use((resolver) => {
    const config = resolver.make("storage_config");
    return new LocalStorage(config);
  });

container
  .when(AdminController)
  .give(StorageContract)
  .use(() => new S3Storage());

// Different storage implementations based on controller context
const userController = await container.make(UserController); // Gets LocalStorage
const adminController = await container.make(AdminController); // Gets S3Storage

Binding Resolver Function

The factory function signature used for all binding types.

/**
 * Shape of the binding resolver function
 */
type BindingResolver<KnownBindings extends Record<any, any>, Value> = (
  resolver: ContainerResolver<KnownBindings>,
  runtimeValues?: any[]
) => Value | Promise<Value>;

Usage Examples:

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

const container = new Container();

// Synchronous resolver
const syncResolver: BindingResolver<any, Logger> = (resolver) => {
  return new Logger();
};

// Asynchronous resolver
const asyncResolver: BindingResolver<any, Database> = async (resolver) => {
  const config = await resolver.make("config");
  const db = new Database(config);
  await db.connect();
  return db;
};

// Resolver with runtime values
const parameterizedResolver: BindingResolver<any, ApiClient> = (resolver, runtimeValues) => {
  const baseUrl = resolver.make("api_base_url");
  const options = runtimeValues?.[0] || {};
  return new ApiClient(baseUrl, options);
};

container.bind("logger", syncResolver);
container.singleton("database", asyncResolver);
container.bind("api_client", parameterizedResolver);