ProvenPin Install

inversify DI container: working versions (tested 2026-10-16)

Bind interfaces to implementations with symbols, decorate classes with injectable and inject, and resolve singleton vs transient scopes from an inversify Container offline.

passed4 passed · 0 failed2026-10-16 19:54 UTC
Status
passed
Last passed
2026-10-16 19:54 UTC
Tests
4 / 4
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
inversify@8.2.3reflect-metadata@0.2.2@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38958328921

The code

Exactly the files that passed. Your agent can pull them with get_recipe("inversify-di-container") (install), or see them on GitHub.

package.json
{
  "name": "inversify-di-container",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "inversify": "8.2.3",
    "reflect-metadata": "0.2.2"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "tsx": "4.23.15"
  }
}
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "strict": true,
    "types": ["node"],
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["src", "test"]
}
src/container.ts
import "reflect-metadata";
import { Container, inject, injectable } from "inversify";

export const TYPES = {
  Logger: Symbol.for("Logger"),
  UserRepository: Symbol.for("UserRepository"),
  UserService: Symbol.for("UserService"),
} as const;

export interface Logger {
  log(message: string): void;
}

export interface User {
  id: string;
  name: string;
}

export interface UserRepository {
  findById(id: string): User | undefined;
}

export interface UserService {
  greet(id: string): string;
}

@injectable()
export class ConsoleLogger implements Logger {
  readonly lines: string[] = [];

  log(message: string): void {
    this.lines.push(message);
  }
}

@injectable()
export class InMemoryUserRepository implements UserRepository {
  private readonly users = new Map<string, User>([
    ["1", { id: "1", name: "Ada" }],
  ]);

  findById(id: string): User | undefined {
    return this.users.get(id);
  }
}

@injectable()
export class DefaultUserService implements UserService {
  constructor(
    @inject(TYPES.Logger) private readonly logger: Logger,
    @inject(TYPES.UserRepository) private readonly repo: UserRepository,
  ) {}

  greet(id: string): string {
    const user = this.repo.findById(id);
    if (!user) {
      this.logger.log(`missing ${id}`);
      return "unknown";
    }
    this.logger.log(`greet ${id}`);
    return `Hello ${user.name}`;
  }
}

export function createContainer(): Container {
  const container = new Container();
  container.bind<Logger>(TYPES.Logger).to(ConsoleLogger).inSingletonScope();
  container
    .bind<UserRepository>(TYPES.UserRepository)
    .to(InMemoryUserRepository)
    .inSingletonScope();
  container
    .bind<UserService>(TYPES.UserService)
    .to(DefaultUserService)
    .inTransientScope();
  return container;
}
test/container.test.ts
import "reflect-metadata";
import { test } from "node:test";
import assert from "node:assert/strict";
import {
  createContainer,
  TYPES,
  ConsoleLogger,
  DefaultUserService,
  InMemoryUserRepository,
  type Logger,
  type UserRepository,
  type UserService,
} from "../src/container.ts";

test("singleton bindings resolve to the same instance", () => {
  const container = createContainer();
  const logger1 = container.get<Logger>(TYPES.Logger);
  const logger2 = container.get<Logger>(TYPES.Logger);
  assert.equal(logger1, logger2);
  const repo1 = container.get<UserRepository>(TYPES.UserRepository);
  const repo2 = container.get<UserRepository>(TYPES.UserRepository);
  assert.equal(repo1, repo2);
});

test("transient bindings resolve to new instances", () => {
  const container = createContainer();
  const service1 = container.get<UserService>(TYPES.UserService);
  const service2 = container.get<UserService>(TYPES.UserService);
  assert.notEqual(service1, service2);
});

test("injects dependencies into the resolved service", () => {
  const container = createContainer();
  const service = container.get<UserService>(TYPES.UserService);
  assert.equal(service.greet("1"), "Hello Ada");
  assert.equal(service.greet("nope"), "unknown");
  const logger = container.get<ConsoleLogger>(TYPES.Logger);
  assert.deepEqual(logger.lines, ["greet 1", "missing nope"]);
});

test("interfaces resolve to their bound implementations", () => {
  const container = createContainer();
  assert.ok(
    container.get<UserService>(TYPES.UserService) instanceof DefaultUserService,
  );
  assert.ok(
    container.get<UserRepository>(TYPES.UserRepository) instanceof
      InMemoryUserRepository,
  );
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.