# Event Log Reference

**Unstable:** The event-log modules live under `effect/unstable/eventlog/*`. Unstable APIs
  may change between minor releases. Pin your version if you depend on them.

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/](https://effect.plants.sh/event-log/); for the server side of the
sync protocol see [/event-log/sync-server/](https://effect.plants.sh/event-log/sync-server/).

## 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.

```ts
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)
```
**Note:** Writing an event whose tag has no registered handler dies at runtime with
  `Event handler not found for: "<tag>"`. `EventLog.group`'s return type is checked
  so that every tag in the group is handled — an unhandled tag becomes the
  compile-time error `Event not handled: <tag>`.

---

## 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.

### make

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.

```ts
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 payload
```

### 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.

```ts
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

Guard that returns `true` when a value is an event definition.

```ts
Event.isEvent(UserRegistered) // => true
Event.isEvent({}) // => false
```

### TypeId

The runtime/type identifier `"~effect/eventlog/Event"` that marks event
definitions; used internally by `isEvent`.

### 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

`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

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

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.Type` of the payload schema).
- **`PayloadSchema<A>`** — the payload schema itself.
- **`PayloadWithTag<Events, Tag>`** — decoded payload value type for the event with `Tag` in a union.
- **`PayloadSchemaWithTag<Events, Tag>`** — payload schema for the event with `Tag` in a union.
- **`Success<A>`** — decoded success value type.
- **`SuccessSchema<A>`** — the success schema.
- **`SuccessWithTag<Events, Tag>`** — decoded success value type for the event with `Tag`.
- **`Error<A>`** — decoded error value type.
- **`ErrorSchema<A>`** — the error schema.
- **`ErrorWithTag<Events, Tag>`** — decoded error value type for the event with `Tag`.
- **`TaggedPayload<A>`** — `{ _tag: Tag; payload: Payload }` for an event (used by compaction).
- **`ToService<A>`** — derives the `EventHandler<Tag>` service marker for an event.
- **`AddError<A, Error>`** — event type with `Error` unioned into its error schema.
- **`WithTag<Events, Tag>`** — extracts the event with `Tag` from a union.
- **`ExcludeTag<Events, Tag>`** — removes the event with `Tag` from 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>`** — `ServicesClient` for the event with `Tag` in a union.

```ts
// Usage in context: the handler signature uses these helpers internally.
type Created = typeof UserRegistered
type Tag = Event.Tag<Created> // => "UserRegistered"
type Payload = Event.Payload<Created> // => { userId: string; email: string }
```

---

## 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).

### empty

The starting point: an `EventGroup<never>` with no events.

```ts
import * as EventGroup from "effect/unstable/eventlog/EventGroup"

const group = EventGroup.empty
```

### 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.

```ts
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

Guard that returns `true` when a value is an event group.

```ts
EventGroup.isEventGroup(Cart) // => true
```

### TypeId

The identifier `"~effect/eventlog/EventGroup"` marking groups; used by
`isEventGroup`.

### Type helpers

- **`Events<Group>`** — the union of `Event` definitions contained in a group.
- **`ToService<A>`** — the union of `EventHandler` service 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 the `events` record available structurally.

---

## EventLog

``

The high-level runtime that connects event definitions, handler layers, an
`EventJournal`, and optional remote replicas.

### schema

Combines one or more event groups into an `EventLogSchema`. This is the value you
pass to `makeClient`, `layer`, and `EventLog.write`.

```ts
const appSchema = EventLog.schema(Cart, Todos)
```

### EventLogSchema / isEventLogSchema / SchemaTypeId

`EventLogSchema<Groups>` carries a `groups` array and the `SchemaTypeId` brand
(`"~effect/eventlog/EventLog/Schema"`). `isEventLogSchema` guards values carrying
that brand.

```ts
EventLog.isEventLogSchema(appSchema) // => true
appSchema.groups // => [Cart, Todos]
```

### makeClient

Returns a typed write function over a schema, preserving each event's success and
error types. Requires the `EventLog` service.

```ts
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)

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 committed `Entry` values from the underlying journal.
- **`destroy`** — removes all journal data.

```ts
const dump = Effect.gen(function* () {
  const log = yield* EventLog.EventLog
  const entries = yield* log.entries
  return entries.length // => number of committed entries
})
```

### group

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 }`.

```ts
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

`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:

```ts
// (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)

Type-level helpers used by `group`:

- **`Handlers.Any`** — matches any `Handlers` value regardless of services or remaining events.
- **`Handlers.Item<R>`** — runtime representation of one registered handler (event metadata, captured `context`, and the handler function).
- **`Handlers.ValidateReturn<A>`** — checks the builder returned all handlers; an unhandled tag becomes `Event not handled: <tag>`, and a non-`Handlers` return becomes `Must return the implemented handlers`.
- **`Handlers.Error<A>`** — extracts the error type from an effect producing `Handlers`.
- **`Handlers.Services<A>`** — services required by a `Handlers` value (or effect producing one), including event schema services.

### 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`).

```ts
// 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 events
```

### 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.

```ts
// 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 Reactivity
```

### Identity (service)

`Context.Service` holding `{ publicKey: string; privateKey: Redacted<Uint8Array> }`.
Used for remote authentication and to derive encryption/signing keys.

```ts
import { Layer } from "effect"

const IdentityLive = Layer.effect(EventLog.Identity, EventLog.makeIdentity)
// requires EventLogEncryption to generate the identity
```

### IdentitySchema

Schema for an identity: `publicKey` as a string and `privateKey` as a redacted,
base64-encoded `Uint8Array`.

### makeIdentity

Effect that generates a fresh identity using the configured `EventLogEncryption`
service.

```ts
const eff = EventLog.makeIdentity // Effect<Identity, never, EventLogEncryption>
```

### 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.

```ts
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

`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.

```ts
import { Layer } from "effect"
import { StoreId } from "effect/unstable/eventlog/EventLogMessage"

const StoreLayer = Layer.succeed(
  EventLog.CurrentStoreId,
  StoreId.make("tenant-42")
)
```

### 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.

```ts
const program = Effect.gen(function* () {
  const registry = yield* EventLog.Registry
  registry.handlers // ReadonlyMap<string, Handlers.Item<any>>
  registry.reactivityKeys // Record<string, ReadonlyArray<string>>
})
```

### 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`.

```ts
// Layer<EventLog | Registry, never, EventJournal | Identity>
const runtime = EventLog.layerEventLog
```

### layer

The 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.

```ts
const Live = EventLog.layer(appSchema, CartHandlers).pipe(
  Layer.provide(layerMemory),
  Layer.provide(IdentityLive),
  Layer.provide(layerSubtle)
)
```

### 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

``

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)

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`** — // Layer<EventJournal>
const journal = layerMemory
```

### 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.

```ts
// Layer<EventJournal, EventJournalError>
const journal = layerIndexedDb({ database: "my_app_journal" })
```

### EventJournalError

`Data.TaggedError("EventJournalError")` recording the failing `method` and the
underlying `cause`.

```ts
new EventJournalError({ method: "write", cause: "disk full" })._tag
// => "EventJournalError"
```

### Entry

`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`.

```ts
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 id
entry.createdAt // => DateTime.Utc
```

### RemoteEntry

`Schema.Class` pairing a `remoteSequence` number with an `Entry`. Produced by remote
change streams and consumed by `writeFromRemote`.

```ts
const remote = new RemoteEntry({ remoteSequence: 7, entry })
remote.remoteSequence // => 7
```

### EntryId helpers

UUID v7 byte ids for entries; the embedded timestamp gives total ordering.

- **`EntryId`** (type) — `Uint8Array & Brand<...>`.
- **`EntryId`** (schema) — branded `Uint8Array` schema.
- **`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`** — `Order` over the raw id bytes.

```ts
const a = makeEntryIdUnsafe({ msecs: 1_000 })
const b = makeEntryIdUnsafe({ msecs: 2_000 })
entryIdMillis(a) // => 1000
EntryIdOrder(a, b) // => -1
```

### RemoteId helpers

UUID v4 byte ids identifying a remote journal source.

- **`RemoteId`** (type) — `Uint8Array & Brand<...>`.
- **`RemoteId`** (schema) — branded `Uint8Array` schema.
- **`RemoteIdTypeId`** — the brand identifier.
- **`makeRemoteIdUnsafe()`** — generate a random `RemoteId` (unsafe cast).

```ts
const id = makeRemoteIdUnsafe() // => Uint8Array(16) branded RemoteId
```

---

## 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.

### layer

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.

```ts
// e.g. a SQLite client layer:
// const JournalLive = SqlEventJournal.layer({
  entryTable: "todo_journal",
  remotesTable: "todo_remotes"
}).pipe(Layer.provide(SqliteLive))
// Layer<EventJournal, SqlError, SqlClient>
```

### make

Constructs the SQL-backed `EventJournal` service directly (same options as `layer`).
Use when you assemble the service yourself.

```ts
const eff = SqlEventJournal.make()
// Effect<EventJournal["Service"], SqlError, SqlClient>
```

---

## 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)

The `Context.Service` representing one remote replica.

- **`id`** — the remote's `RemoteId`.
- **`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 current `Identity` has completed the handshake.

### 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/](https://effect.plants.sh/event-log/sync-server/) and RPC docs).

```ts
// // Layer<EventLogRemote, EventLogRemoteError, RpcClient.Protocol | Registry>
const RemoteLive = EventLogRemote.layerEncrypted.pipe(
  Layer.provide(ProtocolLive)
)
```

### 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.

```ts
// Effect<EventLogRemote, EventLogRemoteError, Scope | EventLogRemoteClient | EventLogEncryption | Registry>
const eff = EventLogRemote.makeEncrypted
```

### 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.

```ts
// Layer<EventLogRemoteClient, never, RpcClient.Protocol>
const ClientLive = EventLogRemote.EventLogRemoteClient.layer
```

### EventLogRemoteError

`Data.TaggedError("EventLogRemoteError")` recording the failing `method` and
`cause`.

```ts
new EventLogRemoteError({ method: "write", cause: "socket closed" })._tag
// => "EventLogRemoteError"
```

---

## 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)

The `Context.Service` with:

- **`encrypt(identity, entries)`** — encode and AES-GCM-encrypt entries, returning `{ iv, encryptedEntries }`.
- **`decrypt(identity, entries)`** — decrypt `EncryptedRemoteEntry[]` back into `RemoteEntry[]`.
- **`sha256(data)`** — SHA-256 hash as `Uint8Array`.
- **`sha256String(data)`** — SHA-256 hash as a lowercase hex string.
- **`generateIdentity`** — create a fresh `Identity` (random public key + 32 random private-key bytes).

```ts
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

`makeEncryptionSubtle(crypto)` builds the service from a Web Crypto `Crypto`
implementation (AES-GCM, SHA-256). `layerSubtle` provides it using
`globalThis.crypto`.

```ts
// Layer<EventLogEncryption>
const EncryptionLive = layerSubtle
```

### 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

`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/](https://effect.plants.sh/event-log/sync-server/) for server-side usage.

- **`StoreId`** — branded string (and schema) identifying a logical store; `StoreId.make("default")` is the default used by `CurrentStoreId`.
- **`EventLogRemoteRpcs`** — the `RpcGroup` with the hello, authenticate, write, and changes endpoints.
- **`EventLogAuthentication`** — `RpcMiddleware` that authenticates requests and provides the client `Identity` to handlers.
- **`EventLogProtocolError`** — `Schema.TaggedErrorClass` with a `code` of `"Unauthorized" | "Forbidden" | "NotFound" | "InvalidRequest" | "InternalServerError"`, plus `requestTag`, optional `publicKey`/`storeId`, and a `message`.

```ts
const store = StoreId.make("default") // => branded "default"
```