Events
SemitraEvents is the event subsystem in Semitra.
Use events when you want to publish that something happened, not necessarily perform the work itself.
What events do
Section titled “What events do”Events support:
- named listeners through
on() - one-shot listeners through
once() - synchronous emission through
emit() - asynchronous emission through
emitAsync() - queued emission through
enqueue() - draining queued events through
drain() - tenant propagation across event handling
That makes events useful for fan-out and cross-cutting reactions.
Event model
Section titled “Event model”Events are emitted as envelopes with:
- an event name
- a payload
- a queued timestamp
- a trace ID
- a tenant value
- optional scheduling metadata
The envelope format keeps observability and delayed processing consistent.
Example use case
Section titled “Example use case”The record subsystem emits record lifecycle events through SemitraEvents.
That lets you react to data changes without coupling the model layer directly
to every side effect.
Example reactions:
- log that a record was created
- increment an analytics counter
- invalidate a cache entry
- enqueue a job after a successful save
A common pattern is to publish one domain signal and let multiple listeners react independently:
import { InMemoryEventQueue, SemitraCache, SemitraEvents} from "@semitra/cli";
type PostPublishedEvent = { postId: string; authorId: string; tenant: string | null;};
const queue = new InMemoryEventQueue<PostPublishedEvent>();
export function registerPostListeners(cache: SemitraCache) { SemitraEvents.on<PostPublishedEvent>("posts:published", async ({ postId }) => { await cache.delete("posts:index", { namespace: "api" }); await cache.delete(`posts:${postId}`, { namespace: "api" }); });
SemitraEvents.on<PostPublishedEvent>( "posts:published", async ({ authorId, postId }) => { console.log("analytics", { type: "post_published", authorId, postId }); } );}
export async function publishPostEvent( postId: string, authorId: string, tenant: string | null) { await SemitraEvents.enqueue( "posts:published", { postId, authorId, tenant }, queue, { tenant } );}
export async function flushPostEvents() { await SemitraEvents.drain(queue);}That is a good fit for a publishing flow where the controller or record only needs to say “a post was published” and should not own cache invalidation, analytics, or notification fan-out itself.
Good use cases
Section titled “Good use cases”- invalidate or warm caches after a write
- record analytics events after a domain action
- fan out a signal to multiple internal listeners
- bridge record lifecycle hooks to downstream processes
- emit “something happened” notifications without hard-coding the consumer
When not to use events
Section titled “When not to use events”Do not use events when:
- the work must run exactly once and be durable
- the work belongs behind a queue consumer
- you need the side effect to be part of the controller response path
Events are the publishing layer. Jobs are the durable execution layer.