Show HN: Dipstick – A new way to DI in TS

4 months ago 6

Dipstick is a dependency injection framework for TypeScript. Instead of using @Decorators or unique strings to identify objects to the DI framework, Dipstick relies on the type system and code generation. The result is a DI framework that works with the strengths of typescript, instead of hacking around it's weaknesses.

No Decorators. No Names. Just Type-Safe DI.

Dipstick uses TypeScript's type system and code generation to create dependency injection containers. The framework is designed to be type-safe and easy to use, with a focus on maintainability and developer experience.

Modules are the core building blocks of Dipstick. They allow you to bind implementations to types that are used throughout your project. An instance of a module is akin to a "scope" in other DI frameworks -- the module instance will hold references to reusable bindings and static bindings. To create a module, export a type alias to dip.Module, and define its bindings:

import { dip } from 'dipstick'; interface IFoo {} class Foo implements IFoo {} ... export type MyModule = dip.Module<{ bindings: { foo: dip.Bind.Reusable<Foo, IFoo>; bar: dip.Bind.Transient<Bar, IBar>; baz: dip.Bind.Reusable<Baz, IBaz>; }; }>;

Dipstick supports three types of bindings:

Reusable bindings return the same instance every time they are called. This is useful for singletons or other objects that should only be created once per module:

export type MyModule = dip.Module<{ bindings: { // Returns the same Foo instance every time foo: dip.Bind.Reusable<Foo, IFoo>; }; }>;

Transient bindings return a new instance every time they are called. This is useful for objects that should be created fresh each time they are requested:

export type MyModule = dip.Module<{ bindings: { // Returns a new Bar instance every time bar: dip.Bind.Transient<Bar>; }; }>;

Static bindings are used to provide objects to a module when the module is instantiated. Use static bindings when you want to incorporate an object created outside of dipstick into a module so that it can be used as a dependency of other objects.

class RequestHandler { constructor(req: Request, res: Response) {} execute() { res.send(200, `hello ${req.path}`) } } export type RequestModule = dip.Module<{ bindings: { // Created outside of this module req: dip.Bind.Static<Request>; res: dip.Bind.Static<Request>; requestHandler: dip.Bind.Transient<RequestHandler> }; }>; app.use((req, res) => { const module = new MyModule({req, res}) const handler = module.requestHandler() handler.execute() })

Modules can depend on other modules. These dependencies are used to resolve types that the module cannot resolve itself:

class Foo { constructor(bar: Bar) {} } export type FooModule = dip.Module<{ bindings: { foo: dip.Bind.Reusable<Foo>; }; }>; export type BarModule = dip.Module<{ dependencies: [ FooModule ]; bindings: { bar: dip.Bind.Transient<Bar>; }; }>; const fooModule = new FooModule() const barModule = new BarModule([ fooModule ]) // if a module has both dependencies and static bindings, pass both: // const barModule = new BarModule({ baz: new Baz() }, [ fooModule ])
  1. Define your modules using type aliases to dip.Module
  2. Run the code generator:
    npm exec -- dipstick generate ./path/to/tsconfig.json --verbose
  3. Use the generated modules in your application:
    const myModule = new MyModuleImpl(); const service = myModule.myService(); ...

The code generator will:

  1. Scan your TypeScript files for exported module type aliases
  2. Generate implementation classes for each module
  3. Handle dependency injection and binding resolution
  4. Ensure type safety throughout the dependency graph

Contributions are welcome! Please see our CONTRIBUTORS.md guide for detailed information about:

  • Setting up the development environment
  • Running tests and code quality checks
  • Code style guidelines
  • Submitting pull requests

For quick contributions:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run npm run check && npm test
  5. Submit a Pull Request `
Read Entire Article