Stone.jsDocs
Paradigm

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.

ExportTypeDescription
xxxBlueprintblueprintThe single entry point: registers providers, middleware, config defaults.
@Xxx() decoratordeclarativeOptional: lets consumers enable the package with an annotation.
defineXxx(...)imperativeOptional: the define* twin, for the imperative paradigm.
XxxServiceProviderproviderRegisters and boots the package’s services.

#A minimal blueprint

src/blueprint.tsts
import { CacheServiceProvider } from './CacheServiceProvider'

export const cacheBlueprint = {
  stone: {
    providers: [CacheServiceProvider],
    cache: { driver: 'memory' }          // config defaults consumers can override
  }
}

#The provider that wires it

src/CacheServiceProvider.tsts
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

app/Application.tsts
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.

tests/cache.test.tsts
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, use workspace:*.
  • 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.

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)