Event Log Reference
The event-log system is an append-only, offline-first store. You define events
(durable, tagged facts), group them, register handlers that fold each event
into your projections, and persist everything in an EventJournal. Local
writes are command-like (encode payload, run the handler, commit only on success);
remote replicas replay the same journal entries through the same handlers, with
optional compaction, reactivity invalidation, and encryption.
This page is the exhaustive per-module reference. For the conceptual overview and end-to-end walkthroughs see /event-log/; for the server side of the sync protocol see /event-log/sync-server/.
Common case
Section titled “Common case”Define events, group them, build a schema, register handlers, and obtain a typed client. The example uses an in-memory journal and a generated identity.
import { Effect, Layer, Schema } from "effect"import * as EventGroup from "effect/unstable/eventlog/EventGroup"import * as EventLog from "effect/unstable/eventlog/EventLog"import { layerMemory } from "effect/unstable/eventlog/EventJournal"import { layerSubtle } from "effect/unstable/eventlog/EventLogEncryption"
// 1. Define a group of events. Each event has a tag, a primaryKey derived from// the decoded payload, and optional payload/success/error schemas.const Todos = EventGroup.empty .add({ tag: "TodoCreated", primaryKey: (p) => p.id, payload: Schema.Struct({ id: Schema.String, title: Schema.String }), success: Schema.String }) .add({ tag: "TodoCompleted", primaryKey: (p) => p.id, payload: Schema.Struct({ id: Schema.String }) })
// 2. Combine groups into an EventLogSchema.const schema = EventLog.schema(Todos)
// 3. Register a handler for every event tag in the group.const TodosHandlers = EventLog.group(Todos, (handlers) => handlers .handle("TodoCreated", ({ payload }) => Effect.as(Effect.log(`created ${payload.title}`), payload.id) ) .handle("TodoCompleted", ({ payload }) => Effect.log(`completed ${payload.id}`) ))
// 4. Build the runtime layer: schema + handlers + journal + identity.const EventLogLive = EventLog.layer(schema, TodosHandlers).pipe( Layer.provide(layerMemory), Layer.provide(Layer.effect(EventLog.Identity, EventLog.makeIdentity)), Layer.provide(layerSubtle))
// 5. Use a typed client to write events.const program = Effect.gen(function* () { const write = yield* EventLog.makeClient(schema) const id = yield* write("TodoCreated", { id: "1", title: "Buy milk" }) // => "1" (the handler's success value) yield* write("TodoCompleted", { id })})
program.pipe(Effect.provide(EventLogLive), Effect.runFork)import * as Event from "effect/unstable/eventlog/Event"
An Event is the durable contract for one kind of fact: a stable tag, a
primaryKey derived from the decoded payload, and the payload/success/error
schemas. The payload schema also derives the MessagePack schema used to persist
entries and send them to remote replicas.
Creates a single event definition. Omitted payload/success default to
Schema.Void; omitted error defaults to Schema.Never. The MessagePack
payload schema (payloadMsgPack) is derived automatically.
import { Schema } from "effect"import * as Event from "effect/unstable/eventlog/Event"
const UserRegistered = Event.make({ tag: "UserRegistered", primaryKey: (p) => p.userId, payload: Schema.Struct({ userId: Schema.String, email: Schema.String }), success: Schema.Void, error: Schema.Never})
UserRegistered.tag // => "UserRegistered"UserRegistered.primaryKey({ userId: "u1", email: "a@b.c" }) // => "u1"UserRegistered.payloadMsgPack // Msgpack schema derived from the payloadaddError
Section titled “addError”Returns a new event definition whose error schema is a union of the existing error and the supplied one. Use it to attach a shared error to a single event.
import { Schema } from "effect"import * as Event from "effect/unstable/eventlog/Event"
class Forbidden extends Schema.TaggedErrorClass<Forbidden>("Forbidden")( "Forbidden", {}) {}
const Created = Event.make({ tag: "Created", primaryKey: () => "x" })const CreatedWithError = Event.addError(Created, Forbidden)// CreatedWithError.error is now `Schema.Union([Never, Forbidden])`isEvent
Section titled “isEvent”Guard that returns true when a value is an event definition.
Event.isEvent(UserRegistered) // => trueEvent.isEvent({}) // => falseTypeId
Section titled “TypeId”The runtime/type identifier "~effect/eventlog/Event" that marks event
definitions; used internally by isEvent.
Event (model)
Section titled “Event (model)”Event<Tag, Payload, Success, Error> is the interface produced by make. It
exposes tag, primaryKey, payload, payloadMsgPack, success, and error.
Payload/Success default to typeof Schema.Void and Error to
typeof Schema.Never.
Any / AnyWithProps
Section titled “Any / AnyWithProps”Event.Any is the type-erased event definition (runtime properties preserved, type
parameters dropped); Event.AnyWithProps is the same with its structural
properties available. Use these as constraints in generic event-handling code.
EventHandler
Section titled “EventHandler”Marker service interface (EventHandler<Tag>) associated with the handler for an
event tag. ToService derives it so handler layers can advertise which tags they
implement.
Type helpers
Section titled “Type helpers”These are type-level utilities — there is no runtime value to print. They derive
information from a single Event or from a union of events selected by tag.
Tag<A>— extracts the tag string literal from an event definition.Payload<A>— decoded payload value type (Schema.Typeof the payload schema).PayloadSchema<A>— the payload schema itself.PayloadWithTag<Events, Tag>— decoded payload value type for the event withTagin a union.PayloadSchemaWithTag<Events, Tag>— payload schema for the event withTagin a union.Success<A>— decoded success value type.SuccessSchema<A>— the success schema.SuccessWithTag<Events, Tag>— decoded success value type for the event withTag.Error<A>— decoded error value type.ErrorSchema<A>— the error schema.ErrorWithTag<Events, Tag>— decoded error value type for the event withTag.TaggedPayload<A>—{ _tag: Tag; payload: Payload }for an event (used by compaction).ToService<A>— derives theEventHandler<Tag>service marker for an event.AddError<A, Error>— event type withErrorunioned into its error schema.WithTag<Events, Tag>— extracts the event withTagfrom a union.ExcludeTag<Events, Tag>— removes the event withTagfrom a union (tracks remaining handlers).Services<A>— all schema encode/decode services for the payload, success, and error schemas.ServicesClient<A>— client-side schema services (payload encode, success/error decode).ServicesServer<A>— server-side schema services (payload decode, success/error encode).ServicesClientWithTag<Events, Tag>—ServicesClientfor the event withTagin a union.
// Usage in context: the handler signature uses these helpers internally.type Created = typeof UserRegisteredtype Tag = Event.Tag<Created> // => "UserRegistered"type Payload = Event.Payload<Created> // => { userId: string; email: string }EventGroup
Section titled “EventGroup”import * as EventGroup from "effect/unstable/eventlog/EventGroup"
An immutable catalog of event definitions for one domain. Start from empty, chain
.add(...), and pass the result to EventLog.schema (for clients) and
EventLog.group (for handlers).
The starting point: an EventGroup<never> with no events.
import * as EventGroup from "effect/unstable/eventlog/EventGroup"
const group = EventGroup.emptyEventGroup (.add / .addError)
Section titled “EventGroup (.add / .addError)”.add({ tag, primaryKey, payload?, success?, error? }) returns a new group with the
event added; .addError(error) returns a new group with the error schema unioned
into every event. Both are immutable and chainable.
import { Schema } from "effect"import * as EventGroup from "effect/unstable/eventlog/EventGroup"
class StoreFull extends Schema.TaggedErrorClass<StoreFull>("StoreFull")( "StoreFull", {}) {}
const Cart = EventGroup.empty .add({ tag: "ItemAdded", primaryKey: (p) => p.cartId, payload: Schema.Struct({ cartId: Schema.String, sku: Schema.String }) }) .add({ tag: "CartCleared", primaryKey: (p) => p.cartId, payload: Schema.Struct({ cartId: Schema.String }) }) // shared error applied to both events: .addError(StoreFull)isEventGroup
Section titled “isEventGroup”Guard that returns true when a value is an event group.
EventGroup.isEventGroup(Cart) // => trueTypeId
Section titled “TypeId”The identifier "~effect/eventlog/EventGroup" marking groups; used by
isEventGroup.
Type helpers
Section titled “Type helpers”Events<Group>— the union ofEventdefinitions contained in a group.ToService<A>— the union ofEventHandlerservice markers for all events (what a handler layer provides).ServicesClient<Group>— client-side schema services required by all events.ServicesServer<Group>— server-side schema services required by all events.Any— type-erased group marker.AnyWithProps—EventGroup<Event.Any>, with theeventsrecord available structurally.
EventLog
Section titled “EventLog”import * as EventLog from "effect/unstable/eventlog/EventLog"
The high-level runtime that connects event definitions, handler layers, an
EventJournal, and optional remote replicas.
schema
Section titled “schema”Combines one or more event groups into an EventLogSchema. This is the value you
pass to makeClient, layer, and EventLog.write.
const appSchema = EventLog.schema(Cart, Todos)EventLogSchema / isEventLogSchema / SchemaTypeId
Section titled “EventLogSchema / isEventLogSchema / SchemaTypeId”EventLogSchema<Groups> carries a groups array and the SchemaTypeId brand
("~effect/eventlog/EventLog/Schema"). isEventLogSchema guards values carrying
that brand.
EventLog.isEventLogSchema(appSchema) // => trueappSchema.groups // => [Cart, Todos]makeClient
Section titled “makeClient”Returns a typed write function over a schema, preserving each event’s success and
error types. Requires the EventLog service.
const program = Effect.gen(function* () { const write = yield* EventLog.makeClient(appSchema) yield* write("ItemAdded", { cartId: "c1", sku: "SKU-1" }) // success/error types are inferred per tag; failures include EventJournalError})EventLog (service)
Section titled “EventLog (service)”The Context.Service exposing journal-backed writes.
write— encodes the payload, derives the primary key, runs the matching handler, and commits the entry only when the handler succeeds.entries— reads all committedEntryvalues from the underlying journal.destroy— removes all journal data.
const dump = Effect.gen(function* () { const log = yield* EventLog.EventLog const entries = yield* log.entries return entries.length // => number of committed entries})Builds a Layer that registers handlers for every event in a group. The callback
receives a Handlers builder; each .handle(tag, fn) records a handler, and the
return type is validated so every tag is handled. Handler functions receive
{ storeId, payload, entry, conflicts }.
const CartHandlers = EventLog.group(Cart, (handlers) => handlers .handle("ItemAdded", ({ payload, entry }) => Effect.log(`add ${payload.sku} (entry ${entry.idString})`) ) .handle("CartCleared", ({ payload }) => Effect.log(`clear ${payload.cartId}`)))// Layer provides EventGroup.ToService<Cart>, requires Registry (+ any handler services)Handlers (builder) + HandlersTypeId
Section titled “Handlers (builder) + HandlersTypeId”Handlers<R, Events> tracks the events still needing handlers in its Events
parameter. .handle(tag, handler) returns a builder with that tag removed and any
handler service requirements accumulated into R. HandlersTypeId is the brand
"~effect/eventlog/EventLog/Handlers".
The handler receives:
// (options) => Effect<Success, Error, R1>{ storeId, // StoreId for this write/replay payload, // decoded payload for the tag entry, // the Entry being committed/replayed conflicts // ReadonlyArray<{ entry; payload }> — concurrent entries (remote replay)}Handlers (namespace)
Section titled “Handlers (namespace)”Type-level helpers used by group:
Handlers.Any— matches anyHandlersvalue regardless of services or remaining events.Handlers.Item<R>— runtime representation of one registered handler (event metadata, capturedcontext, and the handler function).Handlers.ValidateReturn<A>— checks the builder returned all handlers; an unhandled tag becomesEvent not handled: <tag>, and a non-Handlersreturn becomesMust return the implemented handlers.Handlers.Error<A>— extracts the error type from an effect producingHandlers.Handlers.Services<A>— services required by aHandlersvalue (or effect producing one), including event schema services.
groupCompaction
Section titled “groupCompaction”Registers a compaction handler. During remote replay, matching entries are decoded,
grouped by primary key, and passed to your effect, which may rewrite the history by
calling write(tag, payload). The effect receives
{ primaryKey, entries, events, write } where events is an array of
{ _tag, payload } (Event.TaggedPayload).
// Collapse a cart's history into a single "snapshot" event during replay.const CartCompaction = EventLog.groupCompaction(Cart, ({ events, write }) => { // events: ReadonlyArray<{ _tag; payload }> for this primaryKey, in order const last = events[events.length - 1] if (last._tag === "CartCleared") { return write("CartCleared", last.payload) } return Effect.void})// Layer requires Registry + payload DecodingServices for the group's eventsgroupReactivity
Section titled “groupReactivity”Registers reactivity keys to invalidate when a group’s events are written or replayed. Pass a single key array (applied to every tag) or a per-tag map.
// Same keys for all tags:const CartReactivity1 = EventLog.groupReactivity(Cart, ["carts"])
// Per-tag keys:const CartReactivity2 = EventLog.groupReactivity(Cart, { ItemAdded: ["carts", "cart-items"], CartCleared: ["carts"]})// On write/replay, keys are invalidated for the entry's primaryKey via ReactivityIdentity (service)
Section titled “Identity (service)”Context.Service holding { publicKey: string; privateKey: Redacted<Uint8Array> }.
Used for remote authentication and to derive encryption/signing keys.
import { Layer } from "effect"
const IdentityLive = Layer.effect(EventLog.Identity, EventLog.makeIdentity)// requires EventLogEncryption to generate the identityIdentitySchema
Section titled “IdentitySchema”Schema for an identity: publicKey as a string and privateKey as a redacted,
base64-encoded Uint8Array.
makeIdentity
Section titled “makeIdentity”Effect that generates a fresh identity using the configured EventLogEncryption
service.
const eff = EventLog.makeIdentity // Effect<Identity, never, EventLogEncryption>encodeIdentityString / decodeIdentityString
Section titled “encodeIdentityString / decodeIdentityString”Serialize an identity to/from a base64url string (handy for persisting an identity
in local storage). decodeIdentityString throws a schema error on invalid input.
const program = Effect.gen(function* () { const identity = yield* EventLog.makeIdentity const str = EventLog.encodeIdentityString(identity) // => base64url string containing publicKey + privateKey bytes const back = EventLog.decodeIdentityString(str) back.publicKey === identity.publicKey // => true})CurrentStoreId
Section titled “CurrentStoreId”Context.Reference<StoreId> selecting the logical store for writes and remote
replication. Defaults to StoreId.make("default"). Override it to scope a runtime
to a specific store.
import { Layer } from "effect"import { StoreId } from "effect/unstable/eventlog/EventLogMessage"
const StoreLayer = Layer.succeed( EventLog.CurrentStoreId, StoreId.make("tenant-42"))Registry (service) + layerRegistry
Section titled “Registry (service) + layerRegistry”Registry is the lower-level collector for handlers, compactors, remote replicas,
and reactivity keys. layerRegistry provides an in-memory implementation. You
usually get it transitively from layerEventLog/layer, but you can provide it
directly when assembling a custom runtime.
const program = Effect.gen(function* () { const registry = yield* EventLog.Registry registry.handlers // ReadonlyMap<string, Handlers.Item<any>> registry.reactivityKeys // Record<string, ReadonlyArray<string>>})layerEventLog
Section titled “layerEventLog”Provides EventLog | Registry from an EventJournal and Identity. Use this when
you register handlers separately (e.g. via standalone group/groupCompaction
layers) rather than through layer.
// Layer<EventLog | Registry, never, EventJournal | Identity>const runtime = EventLog.layerEventLogThe convenience layer: combines a schema, a handler layer, and the runtime into one
Layer<EventLog | Registry, E, ... | EventJournal | Identity>. The schema argument
does not register handlers by itself — handler registration comes from the supplied
layer.
const Live = EventLog.layer(appSchema, CartHandlers).pipe( Layer.provide(layerMemory), Layer.provide(IdentityLive), Layer.provide(layerSubtle))makeReplayFromRemote
Section titled “makeReplayFromRemote”Advanced. Builds the effect used to replay entries received from a remote: it decodes the entry and its conflicts, runs the matching handler with the supplied identity and store id, logs failures, and invalidates configured reactivity keys. Used internally by the runtime; you rarely call it directly.
EventJournal
Section titled “EventJournal”import * as EventJournal from "effect/unstable/eventlog/EventJournal"
The persistence boundary: stores committed entries, publishes local changes, and tracks remote replication metadata. Entry ids are UUID v7, so ordering is clock-derived.
EventJournal (service)
Section titled “EventJournal (service)”The Context.Service with these members:
entries—Effect<ReadonlyArray<Entry>, EventJournalError>; read all entries for replay.write— write one local entry, running a caller effect before committing; commits only if the effect succeeds.writeFromRemote— import a batch ofRemoteEntry; splits duplicates, optionally compacts, runs replay effects with conflicts, returns{ duplicateEntries }.withRemoteUncommited— run an effect with the entries a given remote has not yet acknowledged (used to push pending writes).nextRemoteSequence— the next sequence number to request from a remote.changes—Effect<PubSub.Subscription<Entry>, never, Scope>; subscribe to local writes.destroy— remove all data.withLock—(storeId) => (effect) => effect; serialize work for one store id.
const program = Effect.gen(function* () { const journal = yield* EventJournal.EventJournal const all = yield* journal.entries return all.length // => number of entries})makeMemory / layerMemory
Section titled “makeMemory / layerMemory”In-memory journal. makeMemory is the constructor effect; layerMemory is the
ready-made layer. Data lives only in process memory and is lost when discarded.
import { layerMemory } from "effect/unstable/eventlog/EventJournal"
// Layer<EventJournal>const journal = layerMemorymakeIndexedDb / layerIndexedDb
Section titled “makeIndexedDb / layerIndexedDb”Browser journal backed by IndexedDB. Takes optional { database } (default
"effect_event_journal"). makeIndexedDb requires Scope so the connection can
be closed; layerIndexedDb manages the scope for you.
import { layerIndexedDb } from "effect/unstable/eventlog/EventJournal"
// Layer<EventJournal, EventJournalError>const journal = layerIndexedDb({ database: "my_app_journal" })EventJournalError
Section titled “EventJournalError”Data.TaggedError("EventJournalError") recording the failing method and the
underlying cause.
import { EventJournalError } from "effect/unstable/eventlog/EventJournal"
new EventJournalError({ method: "write", cause: "disk full" })._tag// => "EventJournalError"Schema.Class for a committed entry: id (EntryId), event (tag string),
primaryKey, and payload (MessagePack bytes). Statics: arrayMsgpack,
encodeArray, decodeArray, and Order (orders by entry id). Getters: idString,
createdAtMillis, createdAt.
import { Entry, makeEntryIdUnsafe } from "effect/unstable/eventlog/EventJournal"
const entry = new Entry( { id: makeEntryIdUnsafe(), event: "ItemAdded", primaryKey: "c1", payload: new Uint8Array([1, 2, 3]) }, { disableChecks: true })
entry.event // => "ItemAdded"entry.idString // => "0190..." (UUID v7 string)entry.createdAtMillis // => epoch ms encoded in the UUID v7 identry.createdAt // => DateTime.UtcRemoteEntry
Section titled “RemoteEntry”Schema.Class pairing a remoteSequence number with an Entry. Produced by remote
change streams and consumed by writeFromRemote.
import { RemoteEntry } from "effect/unstable/eventlog/EventJournal"
const remote = new RemoteEntry({ remoteSequence: 7, entry })remote.remoteSequence // => 7EntryId helpers
Section titled “EntryId helpers”UUID v7 byte ids for entries; the embedded timestamp gives total ordering.
EntryId(type) —Uint8Array & Brand<...>.EntryId(schema) — brandedUint8Arrayschema.EntryIdTypeId— the brand identifier.makeEntryIdUnsafe({ msecs? })— generate a UUID v7 id, optionally at a given timestamp (unsafe: bytes are cast without validation).entryIdMillis(id)— extract the epoch-ms timestamp from a v7 id.EntryIdOrder—Orderover the raw id bytes.
import { makeEntryIdUnsafe, entryIdMillis, EntryIdOrder} from "effect/unstable/eventlog/EventJournal"
const a = makeEntryIdUnsafe({ msecs: 1_000 })const b = makeEntryIdUnsafe({ msecs: 2_000 })entryIdMillis(a) // => 1000EntryIdOrder(a, b) // => -1RemoteId helpers
Section titled “RemoteId helpers”UUID v4 byte ids identifying a remote journal source.
RemoteId(type) —Uint8Array & Brand<...>.RemoteId(schema) — brandedUint8Arrayschema.RemoteIdTypeId— the brand identifier.makeRemoteIdUnsafe()— generate a randomRemoteId(unsafe cast).
import { makeRemoteIdUnsafe } from "effect/unstable/eventlog/EventJournal"
const id = makeRemoteIdUnsafe() // => Uint8Array(16) branded RemoteIdSqlEventJournal
Section titled “SqlEventJournal”import * as SqlEventJournal from "effect/unstable/eventlog/SqlEventJournal"
A durable EventJournal backed by a SqlClient. Construction runs only minimal
CREATE TABLE IF NOT EXISTS statements — indexes, migrations, retention, and any
table-name strategy are your responsibility.
Provides EventJournal from a SqlClient. Optional { entryTable, remotesTable }
override the default table names (effect_event_journal, effect_event_remotes).
May fail with SqlError during table creation.
import { Layer } from "effect"import * as SqlEventJournal from "effect/unstable/eventlog/SqlEventJournal"// e.g. a SQLite client layer:// import { layer as SqliteLive } from "..."
const JournalLive = SqlEventJournal.layer({ entryTable: "todo_journal", remotesTable: "todo_remotes"}).pipe(Layer.provide(SqliteLive))// Layer<EventJournal, SqlError, SqlClient>Constructs the SQL-backed EventJournal service directly (same options as layer).
Use when you assemble the service yourself.
import * as SqlEventJournal from "effect/unstable/eventlog/SqlEventJournal"
const eff = SqlEventJournal.make()// Effect<EventJournal["Service"], SqlError, SqlClient>EventLogRemote
Section titled “EventLogRemote”import * as EventLogRemote from "effect/unstable/eventlog/EventLogRemote"
Client-side replica support: write local entries to a remote and stream remote
changes over the event-log RPC protocol. Sessions begin with a hello/authenticate
handshake, cache authentication per identity public key, and retry Forbidden
responses by re-authenticating.
EventLogRemote (service)
Section titled “EventLogRemote (service)”The Context.Service representing one remote replica.
id— the remote’sRemoteId.changes—({ identity, storeId, startSequence }) => Effect<Queue.Dequeue<RemoteEntry, EventLogRemoteError>, never, Scope>; subscribe to remote changes from a sequence number.write—({ identity, storeId, entries }) => Effect<void, EventLogRemoteError>; push local entries (chunked if large).whenAuthenticated— run an effect only after the currentIdentityhas completed the handshake.
layerUnencrypted / layerEncrypted
Section titled “layerUnencrypted / layerEncrypted”Layers providing EventLogRemote from an RpcClient.Protocol and Registry.
layerEncrypted additionally wires the Web Crypto encryption layer; it is the
default for untrusted transports. layerUnencrypted sends plaintext entries — use
only on trusted transports or in tests. Provide an RPC protocol layer (see the
/event-log/sync-server/ and RPC docs).
import { Layer } from "effect"import * as EventLogRemote from "effect/unstable/eventlog/EventLogRemote"// import an RpcClient.Protocol layer, e.g. over WebSocket/HTTP:// import { ProtocolLive } from "..."
// Layer<EventLogRemote, EventLogRemoteError, RpcClient.Protocol | Registry>const RemoteLive = EventLogRemote.layerEncrypted.pipe( Layer.provide(ProtocolLive))makeEncrypted / makeUnencrypted / makeWith
Section titled “makeEncrypted / makeUnencrypted / makeWith”Constructor effects behind the layers. makeEncrypted encrypts writes and decrypts
changes via EventLogEncryption; makeUnencrypted uses plaintext payloads;
makeWith({ encodeWrite, decodeChanges }) builds a remote from custom encode/decode
functions. All register the remote with the Registry for the current scope.
import * as EventLogRemote from "effect/unstable/eventlog/EventLogRemote"
// Effect<EventLogRemote, EventLogRemoteError, Scope | EventLogRemoteClient | EventLogEncryption | Registry>const eff = EventLogRemote.makeEncryptedEventLogRemoteClient
Section titled “EventLogRemoteClient”Context.Service providing the typed RPC client over EventLogRemoteRpcs. Its
static .layer builds the client from an RpcClient.Protocol. layerEncrypted/
layerUnencrypted provide this for you.
import * as EventLogRemote from "effect/unstable/eventlog/EventLogRemote"
// Layer<EventLogRemoteClient, never, RpcClient.Protocol>const ClientLive = EventLogRemote.EventLogRemoteClient.layerEventLogRemoteError
Section titled “EventLogRemoteError”Data.TaggedError("EventLogRemoteError") recording the failing method and
cause.
import { EventLogRemoteError } from "effect/unstable/eventlog/EventLogRemote"
new EventLogRemoteError({ method: "write", cause: "socket closed" })._tag// => "EventLogRemoteError"EventLogEncryption
Section titled “EventLogEncryption”import * as EventLogEncryption from "effect/unstable/eventlog/EventLogEncryption"
Cryptographic operations for encrypted remote replication. Keys are derived deterministically from the identity private key material, so the same stable identity is required to decrypt entries across sessions and devices.
EventLogEncryption (service)
Section titled “EventLogEncryption (service)”The Context.Service with:
encrypt(identity, entries)— encode and AES-GCM-encrypt entries, returning{ iv, encryptedEntries }.decrypt(identity, entries)— decryptEncryptedRemoteEntry[]back intoRemoteEntry[].sha256(data)— SHA-256 hash asUint8Array.sha256String(data)— SHA-256 hash as a lowercase hex string.generateIdentity— create a freshIdentity(random public key + 32 random private-key bytes).
const program = Effect.gen(function* () { const enc = yield* EventLogEncryption.EventLogEncryption const hex = yield* enc.sha256String(new Uint8Array([1, 2, 3])) // => 64-char lowercase hex string})makeEncryptionSubtle / layerSubtle
Section titled “makeEncryptionSubtle / layerSubtle”makeEncryptionSubtle(crypto) builds the service from a Web Crypto Crypto
implementation (AES-GCM, SHA-256). layerSubtle provides it using
globalThis.crypto.
import { layerSubtle } from "effect/unstable/eventlog/EventLogEncryption"
// Layer<EventLogEncryption>const EncryptionLive = layerSubtleEncryptedEntry / EncryptedRemoteEntry
Section titled “EncryptedEntry / EncryptedRemoteEntry”Schemas for encrypted payloads. EncryptedEntry pairs an entryId with the
encrypted bytes; EncryptedRemoteEntry adds the remote sequence and the AES-GCM
iv. Mostly used internally by the encrypted remote and the server protocol.
Protocol types
Section titled “Protocol types”import { ... } from "effect/unstable/eventlog/EventLogMessage"
These define the shared remote protocol. They are mostly internal to the sync
client/server; you typically only touch StoreId. See
/event-log/sync-server/ for server-side usage.
StoreId— branded string (and schema) identifying a logical store;StoreId.make("default")is the default used byCurrentStoreId.EventLogRemoteRpcs— theRpcGroupwith the hello, authenticate, write, and changes endpoints.EventLogAuthentication—RpcMiddlewarethat authenticates requests and provides the clientIdentityto handlers.EventLogProtocolError—Schema.TaggedErrorClasswith acodeof"Unauthorized" | "Forbidden" | "NotFound" | "InvalidRequest" | "InternalServerError", plusrequestTag, optionalpublicKey/storeId, and amessage.
import { StoreId } from "effect/unstable/eventlog/EventLogMessage"
const store = StoreId.make("default") // => branded "default"