TL;DR: TypeWire is a small DI library for TypeScript — no decorators, no magic, just typed wires and explicit registration. Designed for clarity and composability as your system scales.

In recent posts, I’ve been writing about architectural clarity — how good boundaries come from assigning clear ownership and avoiding ambient behaviors. That doesn’t just apply to services or classes — it also applies to how we wire them together.
Constructors and factories are part of your system’s behavior. They may not drive core business logic directly, but they shape how that logic can be composed and reused.

When we manually stitch them together across files and modules, boundaries blur.
It’s not always obvious at first — but as the system grows, that blur often becomes expensive over time.

We already have solutions to this problem — DI frameworks and libraries. But many come with known challenges and friction in practice.

Given these known problems, TypeWire, is my attempt to experiment on
solution to the problem.

TypeWire is a small, transparent DI library for TypeScript — designed to formalize those boundaries.


🚀 TypeWire 0.1.0 is now live

📦 @typewirets/core
📦 @typewirets/inversify
📦 @typewirets/react
📝 Release Notes
📘 GitHub


Why Another DI library

Frankly, just for fun, but also to validate my hypothesis while having fun.

Most TypeScript DI systems borrow heavily from large enterprise-style frameworks. This often results in heavy decorator usage, with steep learning curve,
and difficulties in tracing actual implementations. These DI frameworks also creates very tight coupling to the framework itself.

None of them are usually the problem, if you're fully committed to the framework. For those who are looking for lightweight solution without wanting to tie oneself to a framework, or for those who are working with libraries that should not be tied into specific framework can be very challenging.

TypeWire attempts to address above issues:

  • Declarative TypeWire Definition:
    • Required dependencies - or could be delegated further.
    • A unique symbol representing the wire.
    • A factory or constructor on how to instantiate an instance of any type.
  • Each wire exposes two key methods - getInstance and apply - that abstract container interactions
    • Allows these TypeWire declarations to be used in compatible DI framework of choice (limited though)

These declarations shift ownership of construction and dependency resolution to each wire — rather than centralizing it in the container or framework.

Combined with explicit symbol registration, there’s no guessing, no tagging, and no inference via decorators. Just types and functions.

This makes DI feel like a natural modeling tool — not an abstract mechanism.

You get:

  • Explicit, self-contained registration
  • Clear boundaries between resolution, behavior, and configuration
  • Decoupling from framework-specific conventions

🛠 Example

Usual code example

import { typeWireOf, TypeWireContainer } from '@typewirets/core';

export interface UserService {
  getById(id: string): Promise<User | undefined>;
}

const userServiceWire = typeWireOf({
  token: 'UserService',
  async createWith() {
    const staticUsers: Record<string, User> = {};
    // or use class
    // the factory function clearly states what we are creating
    // We don't have to make this inline.
    return {
      async getById(id: string) {
        return staticUsers[id];
      },
    } satisfies UserService;
  },
});

const userControllerWire = typeWireOf({
  token: 'UserController',
  imports: {
    userService: userServiceWire,
  },
  createWith({ userService }) {
    return {
      async handle(req: express.Request, res: express.Response) {
        const user = await userService.getById(req.params.userId);
        if (!user) {
          res.status(404).end();
          return;
        }

        res.json(user);
      }
    }
  }
})

async function main() {
  const container = new TypeWireContainer();

  // loads all dependant wires as well
  await userControllerWire.apply(container);

  // more settings
  ...
  ///

  // Core business logic section
  const userService = await userService.getInstance(container);
  const user = await userService.getById('admin');

  const userController = await userControllerWire.getInstance(container);
  // ... etc
}

Override someone else's wire

import { typeWireOf, TypeWireContainer, typeWireGroupOf } from '@typewirets/core';

// from the example above

async function main2() {
  const container = new TypeWireContainer();

  const wireGroup = typeWireGroupOf([
    userControllerWire,
    // overrides while keeping the original type token
    userServiceWire.withCreator((ctx) => {
      // returned value must match type of the original wire.
      return {
        async getById(id: string): Promise<User | undefined> {
           throw Error("UserService is not in a good vibe");
        }
      };
    });
  ])

  // loads all dependant wires as well await wireGroup.apply(container);

  // more settings
  ...
  ///

  // Core business logic section
  const userService = await userService.getInstance(container);
  const user = await userService.getById('admin');

  const userController = await userControllerWire.getInstance(container);
  // ... etc
}

Testing

describe('Test Controller', () => {
   test("should use mock service", () => {
     const container = new TypeWireContainer();
     const wireGroup = typeWireGroupOf([
        userControllerWire,
        userServiceWire.withCreator((ctx) => {
          // explicit mocking!!!
          // no more dancing with jest.mock or vi.mock with complicated hoisting.
          return {
            async getById(id: string): Promise<User | undefined> {
              if (id === 'knownValue') {
                return someExpectedUser
              }

              throw Error("Not in a good test vibe")
            }
          };
        });
     ]);


      // loads all dependant wires as well
      await wireGroup.apply(container);

      // do the test
   });
});

You can use classes, factory functions, or async constructors — TypeWire doesn’t impose a pattern. It just helps formalize the glue once your system has enough moving parts that manual wiring becomes a bottleneck.

🔍 Related Articles

TypeWire builds on themes from my recent architecture series:

If you've ever struggled with messy construction logic, blurry service boundaries, or DI that makes your app harder to reason about — this might help.

⚠️ Still Early — But Stable

This is version 0.1.0. The container system and resolution APIs are stable, and real-world ready (I think 😉).

💬 Feedback Welcome

If you’re building something in TypeScript — especially in a monorepo or layered architecture — and want to keep things transparent as they scale, try it out.

Would love to hear thoughts, issues, or improvements from others thinking about these problems too.

http://github.com/typewirets/typewirets/