What Is an Event in Magnitude's event-core? The Complete Technical Guide
In Magnitude's event-core, an event is the fundamental, timestamped unit of state change that flows through the EventBus, must satisfy the BaseEvent interface with a mandatory type discriminator, and can optionally be marked as ephemeral to prevent persistence.
An event in the context of the magnitudedev/magnitude repository represents the atomic payload that drives the entire reactive architecture. Every state transition, notification, or domain change is encapsulated as an object conforming to strict contracts defined in the core event bus implementation. Understanding this contract is essential for building projections, managing side effects, and ensuring durable state hydration across the framework.
The BaseEvent Contract
All events in Magnitude must satisfy the BaseEvent interface defined in packages/event-core/src/core/event-bus-core.ts. This contract serves as the foundation for type safety and runtime discrimination throughout the system.
The interface requires exactly one mandatory field and one optional flag:
export interface BaseEvent {
/** Discriminator used by the event bus and projections. */
readonly type: string
/**
* Ephemeral events flow through the EventBus and trigger projections/signals,
* but are **not** persisted to the EventSink and are **not** replayed during
* hydration.
*/
readonly ephemeral?: true
}
Source: [event-bus-core.ts](https://github.com/magnitudedev/magnitude/blob/main/packages/event-core/src/core/event-bus-core.ts#L20-L35)
The type property acts as a discriminator that allows the EventBusCore to route events to specific projections and subscribers. The optional ephemeral flag determines durability: when set to true, the event triggers real-time projections but bypasses the EventSink entirely, meaning it will never be persisted or replayed during hydration.
Use ephemeral events only when the notification originates outside a projection (where signals cannot be used) and when the data is transient or derived—persisting it would be incorrect because the triggering code re-runs naturally on replay.
Automatic Timestamp Enrichment
When an event is published, the EventBusCore automatically enriches it with a millisecond timestamp. This transformation yields the Timestamped<E> type, ensuring every consumer receives temporal context without manual instrumentation.
export type Timestamped<E extends BaseEvent> = E & { readonly timestamp: number }
Source: [event-bus-core.ts](https://github.com/magnitudedev/magnitude/blob/main/packages/event-core/src/core/event-bus-core.ts#L37-L39)
This automatic wrapping occurs inside the publish method before the event enters the processing queue. Consumers subscribing to the bus receive Stream<Timestamped<E>> values, allowing them to reason about event ordering and staleness.
Event Lifecycle in event-core
The journey of an event through Magnitude's architecture follows a strict three-phase pipeline implemented in event-bus-core.ts lines 92-151.
Publishing Events
Developers interact with the EventBusCoreService API (declared lines 40-65) to emit events. The publish method accepts a raw BaseEvent, wraps it in the Timestamped type, and enqueues it for processing. This operation returns an Effect that succeeds when the event is safely queued.
Internal Processing Pipeline
A single-consumer fiber dequeues each event sequentially to guarantee ordering. The pipeline executes:
- Projection Phase – Runs all registered projections against the event
- Persistence Phase – Forwards the event to the
EventSink(unless markedephemeral) - Broadcast Phase – Publishes the timestamped event via an internal
PubSubto all subscribers
This architecture ensures that projections see events before subscribers, and durable storage occurs before broadcast confirmation.
Subscribing to Events
Consumers obtain a Stream of Timestamped<E> values by subscribing to the bus. The API supports both broad subscriptions (all events) and filtered subscriptions (specific event types), enabling fine-grained reactive logic without performance penalties from filtering in user code.
Working with Events in Practice
Defining Custom Events
Extend BaseEvent to create domain-specific event types. Always use readonly modifiers for immutable event semantics:
// my-event.ts
import type { BaseEvent } from '@magnitudedev/event-core/src/core/event-bus-core'
export interface UserCreated extends BaseEvent {
readonly type: 'UserCreated'
readonly userId: string
readonly name: string
}
Publishing Events
Use the EventBusCoreTag to access the service via Effect's dependency injection context, then call publish with your event payload:
import { Effect } from 'effect'
import { EventBusCoreTag } from '@magnitudedev/event-core/src/core/event-bus-core'
import type { UserCreated } from './my-event'
const EventBus = EventBusCoreTag<UserCreated>()
const publishUser = (id: string, name: string) =>
Effect.flatMap(
EventBus,
bus => bus.publish({ type: 'UserCreated', userId: id, name })
)
Subscribing to Event Streams
Create reactive pipelines by subscribing to specific event types. The subscription returns a Stream that can be consumed with standard Effect operators:
import { Effect, Stream } from 'effect'
import { EventBusCoreTag } from '@magnitudedev/event-core/src/core/event-bus-core'
type AllEvents = UserCreated // | ...other events
const listenForUsers = Effect.flatMap(
EventBusCoreTag<AllEvents>(),
bus => bus.subscribeToTypes(['UserCreated'])
)
listenForUsers.pipe(
Effect.flatMap(stream =>
Stream.forEach(stream, ev =>
Effect.sync(() => console.log('New user at', ev.timestamp, ':', ev.userId))
)
)
)
Handling Ephemeral Events
For transient notifications that should not survive hydration, set the ephemeral flag to true:
import { Effect } from 'effect'
const transientAlert = Effect.flatMap(
EventBusCoreTag<AllEvents>(),
bus =>
bus.publish({
type: 'CacheMissAlert',
ephemeral: true,
message: 'Data not found in cache'
})
)
This event triggers projections and subscriptions immediately but is excluded from the EventSink persistence layer, ensuring it never replays during application hydration.
Summary
- An event in Magnitude is any object satisfying the
BaseEventinterface with a mandatorytypestring discriminator. - Timestamping is automatic:
EventBusCorewraps all events in theTimestamped<E>type before processing. - Ephemeral events bypass durable storage (the
EventSink) and hydration replays by settingephemeral: true. - Lifecycle follows strict ordering: publish → enqueue → projection → persistence (if not ephemeral) → broadcast.
- Source files defining this behavior reside primarily in
packages/event-core/src/core/event-bus-core.ts.
Frequently Asked Questions
What is the difference between an event and a signal in Magnitude?
A signal is used when a notification originates from within a projection and does not require persistence, while an event (including ephemeral ones) flows through the EventBus and can trigger projections across the system. Use events when the notification crosses projection boundaries; use signals for internal projection communication.
When should I use the ephemeral flag on an event?
Mark an event as ephemeral: true only when the notification is derived or transient and originates outside a projection (where signals cannot be used). If the event represents durable state change or could be triggered by replaying existing logic, omit the flag to allow persistence via the EventSink.
How does timestamping affect event processing?
The EventBusCore automatically enriches every event with a timestamp: number field (Unix milliseconds) during the publish phase. This ensures all subscribers and projections receive consistent temporal metadata without requiring manual date instantiation in business logic.
Where is the event persistence logic implemented?
Events are persisted through the EventSink interface, which accumulates non-ephemeral events awaiting durable storage. The routing decision occurs in the processing pipeline within event-bus-core.ts (lines 92-151), where the bus checks the ephemeral flag before forwarding to the sink.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →