Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Service Providers

A ServiceProvider is the unit of wiring — a class that encapsulates registration and boot for one feature area. Every service you want in the container gets its own provider.

Basic structure

import { ServiceProvider, type ContainerContract as Container } from "@artisansdk/architect"

export class AnalyticsProvider extends ServiceProvider {
  protected analytics?: AnalyticsService

  register(container: Container): void {
    container.singleton(AnalyticsService, AnalyticsService)
  }

  boot(container: Container): void {
    this.analytics = container.make(AnalyticsService)
    this.analytics.start()
  }

  destroy(): void {
    this.analytics?.stop()
  }
}

The register / boot contract

register() must only bind into the container — never resolve. boot() may safely resolve any binding because all providers’ register() calls have completed first.

// ✅ correct
register(container: Container) {
  container.singleton(MyService, MyService)
}

// ❌ wrong — resolving in register() risks getting undefined
//    if another provider hasn't registered yet
register(container: Container) {
  const config = container.make(ConfigRepository) // don't do this
}

Tearing down with destroy()

register() and boot() are void — there’s no return value to track. Tear-down work goes in destroy() instead: a separate method the Application calls once per provider, in reverse provider order, on shutdown. Whatever destroy() needs (a timer handle, an AbortController, a subscription) is tracked as an instance field, since it’s no longer passed back through a return value:

export class PollingProvider extends ServiceProvider {
  protected interval?: ReturnType<typeof setInterval>

  boot(container: Container): void {
    const poller = container.make(PollingService)
    this.interval = setInterval(() => poller.tick(), 5000)
  }

  destroy(): void {
    clearInterval(this.interval)
  }
}

This matches the same convention as React’s useEffect, Svelte’s onDestroy, and Vue’s onUnmounted — providers that don’t need cleanup simply don’t override destroy().

Provider ownership

Each ServiceProvider is the sole owner of registration, booting, and cleanup for its feature area. No other code should bind or unbind what a provider manages.

Passing providers

Pass provider instances to withProviders():

Application.configure()
  .withProviders([
    new DatabaseProvider(),
    new AuthProvider(),
    new ApiProvider(),
  ])
  .run()

Providers run in the order given.