Foundations
Service providers
A provider is how capability enters the application. It has two jobs: register bindings into the container, and then boot them once everything is registered. Every adapter and extension is, at heart, a provider, and so can your own modules be.
#Register, then boot
The principle
Wiring has two phases that must not be mixed: declaring what exists, and starting what has been declared. If a component boots before its dependencies are registered, order becomes fragile. Splitting the two makes assembly deterministic.
In Stone.js
A provider's register() only binds services; it must not resolve them. Once every provider has registered, the kernel calls each boot(), where resolving is safe because the whole graph now exists. An optional mustSkip() lets a provider opt out of a context it does not apply to.
import { Provider } from '@stone-js/core'
@Provider()
export class TasksProvider {
constructor ({ container, blueprint }) {
this.container = container
this.blueprint = blueprint
}
// Bind services into the container.
register () {
this.container.singleton('tasks', () => new TaskService(this.container))
}
// Run setup that needs other services to exist first.
async boot () {
await this.container.resolve('tasks').warmUp()
}
}import { defineServiceProvider } from '@stone-js/core'
const TasksProvider = ({ container }) => ({
register () {
container.singleton('tasks', () => new TaskService(container))
},
async boot () {
await container.resolve('tasks').warmUp()
}
})
export const providers = [defineServiceProvider(TasksProvider, {}, true)]register() says what exists. boot() starts it. Never resolve in register().
#Providers forbid the function form
A provider always needs the container, to register into it. The plain function form never receives the container, so providers must be a class or a factory. This is the one place the three forms are deliberately restricted, and the restriction follows directly from what a provider is for.