Extending
Create a package or plugin
A Stone.js package bundles a capability so others can add it in one line. It is not a special artifact: it is a normal npm package that exports a blueprint (and usually some providers, decorators or services), plus the seams that let consumers wire it declaratively or imperatively.
#The anatomy
A well-formed extension exports, at minimum, a blueprint that registers everything it needs. Optionally it ships decorators and define* helpers so consumers get both paradigms, and a service provider that does the actual wiring.
| Export | Type | Description |
|---|---|---|
| xxxBlueprint | blueprint | The single entry point: registers providers, middleware, config defaults. |
| @Xxx() decorator | declarative | Optional: lets consumers enable the package with an annotation. |
| defineXxx(...) | imperative | Optional: the define* twin, for the imperative paradigm. |
| XxxServiceProvider | provider | Registers and boots the package’s services. |
#A minimal blueprint
import { CacheServiceProvider } from './CacheServiceProvider'
export const cacheBlueprint = {
stone: {
providers: [CacheServiceProvider],
cache: { driver: 'memory' } // config defaults consumers can override
}
}#The provider that wires it
import { Provider } from '@stone-js/core'
@Provider()
export class CacheServiceProvider {
constructor ({ container, blueprint }) { this.container = container; this.blueprint = blueprint }
register () {
const driver = this.blueprint.get('stone.cache.driver', 'memory')
this.container.singleton('cache', () => createCache(driver))
}
}#How consumers add it
import { cacheBlueprint } from '@acme/stone-cache'
// imperative:
export const App = defineStoneApp({ name: 'app' }, [routerBlueprint, cacheBlueprint])
// or declarative, if you ship a decorator:
@Cache({ driver: 'redis' })
@StoneApp({ name: 'app' })
export class Application {}#Test your extension
Test a package the way apps are tested: boot a real app with just your blueprint and assert the behaviour it adds. createTestApp takes your blueprint directly, so a test needs no host app.
import { createTestApp } from '@stone-js/testing'
import { cacheBlueprint } from '../src/blueprint'
it('registers the cache service', async () => {
const app = await createTestApp({ blueprint: cacheBlueprint })
const cache = app.container().make('cache')
cache.set('k', 1)
expect(cache.get('k')).toBe(1)
})#Publishing
- ESM only (
"type": "module"), TypeScript, ship types. - Declare
@stone-js/*peers you build on; inside a workspace, useworkspace:*. - Own a config namespace under
stone.*and document your keys. - Publish under your scope (or propose it for
@stone-js/*); it appears in the marketplace beside first-party packages, with no privileged access.