Stone.jsDocs
Paradigm

Essentials

Events & listeners

Not every consequence belongs in the handler that causes it. Emitting a domain event lets the rest of the app react, sending a mail, updating a projection, without the emitter knowing or caring who listens. Coupling drops; the code that matters stays readable.

#Emitting an event

The event emitter is injected like any service. Emit a named event with a payload from wherever the thing happens.

app/Tasks.tsdeclarativeimperative
constructor ({ eventEmitter }) { this.events = eventEmitter }

create (event: IncomingHttpEvent) {
  const task = this.tasks.add(event.get('title'))
  this.events.emit('task.created', { task })   // fire and forget
  return task
}
const create = ({ tasks, eventEmitter }) => (event) => {
  const task = tasks.add(event.get('title'))
  eventEmitter.emit('task.created', { task })
  return task
}
The handler states what happened. Listeners decide what to do about it.

#Listening

A listener reacts to one event. Mark a class with @Listener (or register a defineEventListener), and it runs whenever that event is emitted, with its dependencies injected.

app/NotifyOnCreate.tsdeclarativeimperative
import { Listener } from '@stone-js/core'

@Listener({ event: 'task.created' })
export class NotifyOnCreate {
  constructor ({ mailer }: { mailer: Mailer }) { this.mailer = mailer }

  handle (event: { task: Task }) {
    return this.mailer.send('a task was created', event.task)
  }
}
import { defineEventListener } from '@stone-js/core'

const NotifyOnCreate = ({ mailer }) => (event) =>
  mailer.send('a task was created', event.task)

export const listeners = [
  defineEventListener(NotifyOnCreate, { event: 'task.created' }, true)
]

#Subscribers

When one unit handles several related events, a subscriber groups them: mark it with @Subscriber (or defineEventSubscriber) and map each event to a method, with its dependencies injected once for the whole group.

app/TaskSubscriber.tsts
import { Subscriber } from '@stone-js/core'

@Subscriber()
export class TaskSubscriber {
  constructor ({ metrics }: { metrics: Metrics }) { this.metrics = metrics }

  subscribe (emitter) {
    emitter.on('task.created', (e) => this.metrics.inc('tasks.created'))
    emitter.on('task.deleted', (e) => this.metrics.inc('tasks.deleted'))
  }
}

#Wildcards

A wildcard name catches a whole family of events with one listener, useful for audit logs or metrics that should not enumerate every event by hand.

app/Audit.tsts
@Listener({ event: 'task.*' })       // task.created, task.updated, task.deleted, ...
export class AuditTaskEvents {
  handle (event, name) { this.audit.record(name, event) }
}

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)