Stone.jsDocs
Paradigm

Dependency injection

The container

The container is where dependency injection happens: for each event it holds the services your code asks for and hands them in. Most of the time you never touch it directly, you declare what you need and it arrives, but when you need control, this is the surface.

#Bindings and resolution

A binding maps a key (a class or an alias) to a way of producing a value. Resolution asks the container for a key and gets the value back, constructing it and its dependencies as needed. The container is created fresh per event, so bindings do not leak between requests.

app/providers/AppProvider.tsts
register () {
  // A singleton: built once per container (i.e. once per event), then cached.
  this.container.singleton('tasks', (c) => new TaskService(c.make('db')))

  // A fixed value.
  this.container.instance('clock', () => Date.now())

  // An alias: another name for an existing binding.
  this.container.alias('tasks', ['taskService'])
}

#Container methods

MethodTypeDescription
singleton(key, resolver)(key, (c) => V) => thisRegister a value built once per container and cached.
instance(key, value)(key, V) => thisRegister an already-built value.
alias(key, aliases)(key, string | string[]) => thisAdd one or more alternative names for a binding.
resolve(key, singleton?)<V>(key) => VResolve a value by key, constructing it if needed.
make(key)<V>(key) => VResolve, failing fast on an unknown key.
factory(key)<V>(key) => () => VGet a factory that resolves the key on each call.
bound(key)(key) => booleanWhether a key has a binding.
has(key)(key) => booleanWhether a key is known to the container.
singletonIf / instanceIf / bindingIfconditionalRegister only if the key is not already bound (see below).
getBindings() / getAliases()() => MapIntrospect what is registered.

#Conditional bindings (overridable defaults)

The *If variants register a binding only if nothing already claimed the key. This is how an extension ships a sensible default that an app can override just by binding its own first: last word to the app, a fallback from the package.

src/CacheProvider.tsts
register () {
  // The app may already have bound 'cache'; only fill in if it did not.
  this.container.singletonIf('cache', () => new MemoryCache())
}

#Prefer declaring over resolving

Reaching into the container by hand is the exception. The rule is to declare dependencies and let them be injected: register services with @Service, and destructure them in a constructor. Use the container API for dynamic or conditional wiring only.

app/Tasks.tsts
// Preferred: declare and receive.
constructor ({ tasks }: { tasks: TaskService }) { this.tasks = tasks }

// Exception: resolve explicitly when the dependency is dynamic.
constructor ({ container }) { this.driver = container.make(pickDriver()) }

Stone.js

Your app exists in every runtime. Until you run it.

An open-source project by Stone Foundation
Created by Mr. Stone (Evens Pierre)