Foundations
Blueprint: config as a manifest
The Blueprint is how Stone.js incarnates the setup dimension: one manifest that describes the entire application, assembled once before the first event and then frozen. Everything the framework does at runtime, it reads from here.
#Configuration as a single source
The principle
When configuration is scattered and mutable, behaviour depends on the order things happened to be set. Collapse it into one manifest, computed once before anything runs, and behaviour depends only on what the manifest declares. Setup stops being a runtime concern.
In Stone.js
The Blueprint is a config store addressed by dotted stone.* keys. It is built by introspecting your decorators or by merging imperative meta-modules, then made immutable. Adapters, providers, middleware and routes all live here as data.
import { StoneApp } from '@stone-js/core'
@StoneApp({
name: 'tasks',
logger: { level: 'info' }
})
export class Application {}
// The decorator's options are read into the Blueprint under the stone.* namespace.import { defineStoneApp } from '@stone-js/core'
// A meta-module: an imperative description merged into the Blueprint.
export const appConfig = {
stone: { name: 'tasks', logger: { level: 'info' } }
}
export const App = defineStoneApp(appConfig, [/* blueprints */])#Two ways to fill it, one result
Declarative and imperative are just two syntaxes that produce the same Blueprint. A decorator writes keys by introspection; a define* meta-module writes the same keys explicitly. Downstream, nothing can tell which was used, because the manifest is identical.
Decorators and define* are two pens. The Blueprint is the one page they both write.
#Reading it at runtime
Inside the app, the config store is injected like any service and read by the same dotted keys. It is read-only; the manifest never changes mid-flight.
constructor ({ config }) {
this.pageSize = config.get('stone.tasks.pageSize', 20) // key, default
}