Implicit services

Simple services, which don't require any special options and whose dependencies can be resolved automatically by the compiler, need only to be discoverable by the compiler as exports from one of the resource files:

// this will register the ServiceOne class as a service and it will also
// automatically include any interfaces ServiceOne implements, as well as
// its ancestors and their interfaces, as aliases:
export class ServiceOne {
  // ...
}

// this will register a dynamic service of the type 'ServiceTwo'; any other
// interfaces which ServiceTwo extends will be registered as its aliases:
export interface ServiceTwo {
  // ...
}

// aside from classes and interfaces you can also export factory functions:
export function createServiceThree(): ServiceThree {
  // ...
}
typescript

Services registered this way will not have a public service ID. They will have autogenerated string identifiers beginning with a # character, but you're strongly discouraged from using these, because they're a product of the compilation process and can change at any time, even between compilations in some cases. Instead, you should rely on injection to get instances of these services.

Static factory methods

There is a special case for classes which don't have any public constructors, but which have a static create() method. If such a class is found, the create() method will be used as its factory:

export class ServiceFour {
  static create(): ServiceFour {
    // ...
  }

  private constructor() {
    // ...
  }
}
typescript

Service factories, whether they're functions or static create() methods, can be async:

export async function loadConfig(): Promise<ApplicationConfig> {
  return JSON.parse(await readFile('config.json', 'utf-8'));
}
typescript

Factories can also return undefined if a service cannot be instantiated at runtime, allowing services to be optional:

interface LogWriter {
  write(message: string): void;
}

export function fileLogWriter(): LogWriter {
  return process.env.LOG_FILE ? new FileLogWriter(process.env.LOG_FILE) : undefined;
}

export function elasticLogWriter(): LogWriter {
  return process.env.ELASTIC_DSN ? new ElasticLogWriter(process.env.ELASTIC_DSN) : undefined;
}
typescript

A service can also be a list or an iterable:

export function * logWriterFactory(): Iterable<LogWriter> {
  if (process.env.LOG_FILE) {
    yield new FileLogWriter(process.env.LOG_FILE);
  }

  if (process.env.ELASTIC_DSN) {
    yield new ElasticLogWriter(process.env.ELASTIC_DSN);
  }
}
typescript

When you need to do some more complex logic to create a service instance, or when you want to provide other service configuration such as a scope or one or more service hooks, you can export an explicit service definition from a resource file.