/** * Runtime for writing typed events to an event journal. * * `EventLog` combines event groups, handlers, a journal, local identity, * optional remote replicas, and reactivity hooks. Writers send typed payloads * through a client; the matching handler runs first, and the journal entry is * committed only after the handler succeeds. This module also contains the * layers and helpers needed to assemble that runtime. * * @stability unstable * @since 4.0.0 */ import * as Arr from "../Array.ts"; import * as Context from "../Context.ts"; import * as Effect from "../Effect.ts"; import * as Layer from "../Layer.ts"; import type { Pipeable } from "../Pipeable.ts"; import { Reactivity } from "../reactivity/Reactivity.ts"; import * as ReactivityLayer from "../reactivity/Reactivity.ts"; import type * as Record from "../Record.ts"; import * as Redacted from "../Redacted.ts"; import * as Schema from "../Schema.ts"; import type * as Scope from "../Scope.ts"; import type { Covariant } from "../Types.ts"; import type * as Event from "./Event.ts"; import type * as EventGroup from "./EventGroup.ts"; import { Entry, EventJournal, type EventJournalError } from "./EventJournal.ts"; import * as EventLogEncryption from "./EventLogEncryption.ts"; import { StoreId } from "./EventLogMessage.ts"; import type { EventLogRemote } from "./EventLogRemote.ts"; declare const EventLog_base: Context.ServiceClass>>(options: { readonly schema: EventLogSchema; readonly event: Tag; readonly payload: Event.PayloadWithTag, Tag>; }) => Effect.Effect, Tag>, Event.ErrorWithTag, Tag> | EventJournalError>; /** * Write several events under one journal lock and one `HandlerTransaction`, * returning each handler's result in input order. If any handler fails, * none of the events are committed. */ readonly writeMany: >]: { readonly event: Tag; readonly payload: Event.PayloadWithTag, Tag>; }; }[Event.Tag>]>(options: { readonly schema: EventLogSchema; readonly events: Arr.NonEmptyReadonlyArray; }) => Effect.Effect, Item["event"]>>, Event.ErrorWithTag, Item["event"]> | EventJournalError>; readonly entries: Effect.Effect, EventJournalError>; readonly destroy: Effect.Effect; }>; /** * Service for writing typed event-log events through registered handlers. * * **Details** * * `write` encodes the event payload, runs the matching handler, commits the entry * only when the handler succeeds, and exposes access to the underlying journal * entries and destroy operation. * * @stability unstable * @category services * @since 4.0.0 */ export declare class EventLog extends EventLog_base { } declare const Registry_base: Context.ServiceClass; }) => void; readonly handlers: ReadonlyMap>; readonly registerCompaction: (options: { readonly events: ReadonlyArray; readonly effect: (options: { readonly entries: ReadonlyArray; readonly write: (entry: Entry, source?: Entry) => Effect.Effect; }) => Effect.Effect; }) => Effect.Effect; readonly compactors: ReadonlyMap; readonly effect: (options: { readonly entries: ReadonlyArray; readonly write: (entry: Entry, source?: Entry) => Effect.Effect; }) => Effect.Effect; }>; readonly registerRemote: (remote: EventLogRemote["Service"]) => Effect.Effect; readonly handleRemote: (handler: (remote: EventLogRemote["Service"]) => Effect.Effect) => Effect.Effect; readonly registerReactivity: (keys: Record>) => Effect.Effect; readonly reactivityKeys: Record>; }>; /** * Service that collects event handlers, compaction handlers, remote replicas, * and reactivity invalidation keys. * * @stability unstable * @category services * @since 4.0.0 */ export declare class Registry extends Registry_base { } /** * Provides an in-memory `Registry` for event handlers, compactors, remote * replicas, and reactivity keys. * * @stability unstable * @category layers * @since 4.0.0 */ export declare const layerRegistry: Layer.Layer; declare const Identity_base: Context.ServiceClass>; }>; /** * Context service for an event-log identity containing a public key and redacted * private key material. * * **Details** * * The identity is used by remote replication for authentication and by the * encryption service to derive signing and encryption keys. * * @stability unstable * @category services * @since 4.0.0 */ export declare class Identity extends Identity_base { } /** * Type-level identifier used to brand `EventLogSchema` values. * * @stability unstable * @category type IDs * @since 4.0.0 */ export type SchemaTypeId = "~effect/eventlog/EventLog/Schema"; /** * Runtime property key used to identify `EventLogSchema` values. * * @stability unstable * @category type IDs * @since 4.0.0 */ export declare const SchemaTypeId: SchemaTypeId; /** * Returns `true` when a value carries the `EventLogSchema` marker. * * @stability unstable * @category guards * @since 4.0.0 */ export declare const isEventLogSchema: (u: unknown) => u is EventLogSchema; /** * Schema describing the event groups that can be written through an `EventLog`. * * @stability unstable * @category schemas * @since 4.0.0 */ export interface EventLogSchema { readonly [SchemaTypeId]: SchemaTypeId; readonly groups: ReadonlyArray; } /** * Creates an `EventLogSchema` from one or more event groups. * * @stability unstable * @category schemas * @since 4.0.0 */ export declare const schema: >(...groups: Groups) => EventLogSchema; /** * Type-level identifier used to brand `Handlers` values. * * @stability unstable * @category type IDs * @since 4.0.0 */ export type HandlersTypeId = "~effect/eventlog/EventLog/Handlers"; /** * Runtime property key used to identify `Handlers` values. * * @stability unstable * @category type IDs * @since 4.0.0 */ export declare const HandlersTypeId: HandlersTypeId; /** * Builder for the handlers associated with an `EventGroup`. * * **Details** * * The `Events` type parameter tracks the event tags that still need handlers, and * each call to `handle` records a handler while accumulating any required * services. * * @stability unstable * @category handlers * @since 4.0.0 */ export interface Handlers extends Pipeable { readonly [HandlersTypeId]: { _Events: Covariant; }; readonly group: EventGroup.AnyWithProps; readonly handlers: Record.ReadonlyRecord>; readonly context: Context.Context; /** * Add the implementation for an `Event` to a `Handlers` group. */ handle, R1>(name: Tag, handler: (options: { readonly storeId: StoreId; readonly payload: Event.PayloadWithTag; readonly entry: Entry; readonly conflicts: ReadonlyArray<{ readonly entry: Entry; readonly payload: Event.PayloadWithTag; }>; }) => Effect.Effect, Event.ErrorWithTag, R1>): Handlers>; } /** * Namespace containing helper types for `Handlers` values and handler-producing * layers. * * @stability unstable * @since 4.0.0 */ export declare namespace Handlers { /** * Type that matches any `Handlers` value regardless of its services or remaining * events. * * @stability unstable * @category handlers * @since 4.0.0 */ interface Any { readonly [HandlersTypeId]: unknown; } /** * Runtime representation of one registered event handler, including its event * metadata, captured context, and handler function. * * @stability unstable * @category handlers * @since 4.0.0 */ type Item = { readonly event: Event.AnyWithProps; readonly context: Context.Context; readonly handler: (options: { readonly storeId: StoreId; readonly payload: unknown; readonly entry: Entry; readonly conflicts: ReadonlyArray<{ readonly entry: Entry; readonly payload: unknown; }>; }) => Effect.Effect; }; /** * Validates that a handler builder returned all required handlers. * * **Details** * * If any event tag remains unhandled, the type evaluates to an explanatory * compile-time error string. * * @stability unstable * @category handlers * @since 4.0.0 */ type ValidateReturn = A extends (Handlers | Effect.Effect, infer _EX, infer _RX>) ? [_Events] extends [never] ? A : `Event not handled: ${Event.Tag<_Events>}` : `Must return the implemented handlers`; /** * Extracts the error type from an effect that produces `Handlers`. * * @stability unstable * @category handlers * @since 4.0.0 */ type Error = A extends Effect.Effect, infer _EX, infer _RX> ? _EX : never; /** * Computes the services required by a `Handlers` value or by an effect that * produces one, including event schema services. * * @stability unstable * @category handlers * @since 4.0.0 */ type Services = A extends Handlers ? _R | Event.Services<_Events> : A extends Effect.Effect, infer _EX, infer _RX> ? _R | _RX | Event.Services<_Events> : never; } declare const CurrentStoreId_base: Context.Reference; /** * Context reference for the store id used by event-log writes and remote * replication. * * **Details** * * Defaults to the branded store id `"default"`. * * @stability unstable * @category services * @since 4.0.0 */ export declare class CurrentStoreId extends CurrentStoreId_base { } declare const HandlerTransaction_base: Context.Reference<(effect: Effect.Effect) => Effect.Effect>; /** * Context reference for a transaction that wraps the event handlers of each * batch replayed from a remote and of each local `write` or `writeMany`. * * **When to use** * * Use when handlers project into a store with transactions, such as * `sql.withTransaction` or an IndexedDB transaction over the projection * tables, so a batch of N events commits once instead of N times. * * **Details** * * The journal performs no I/O while the transaction is open: it reads replay * conflicts before the transaction starts and commits the batch after it * completes. Defaults to running the handlers without a transaction. * * If the transaction fails, the journal commits nothing from the batch. For a * replayed batch, the remote is subscribed again after a backoff of up to 10 * seconds and the batch is delivered again, so later batches from that remote * wait until it succeeds; local writes and other remotes are not blocked. A * local write fails with the error and is not retried. * * **Gotchas** * * - A replayed entry that fails to decode, or whose handler fails or dies, * fails the whole batch, which is retried as above. A failure that repeats * on every attempt, such as a payload written by a newer schema, stops sync * from that remote until the app is updated to handle it. * - In a local write, an error returned by a handler fails the transaction * and the write, and the journal commits none of its events. * - Failures that abort the underlying transaction fail the whole batch, even * when the handler recovers from them. In IndexedDB, any failed request that * is not prevented aborts the whole transaction, and in PostgreSQL so does a * failed statement outside a savepoint. * - Failing the batch does not undo side effects of handlers that already ran. * Only changes made inside a transaction that rolls back are undone. * - Replay handlers must tolerate running more than once for the same entry. * - Unless the transaction and the journal share a database connection, the * transaction commits before the journal does, and the two commits are not * atomic. A crash between them replays a remote batch on restart, but a * local write is lost from the journal while its projection changes remain. * - An IndexedDB transaction commits once it has no pending requests and * control returns to the event loop. A custom wrapper must stop the fiber * from yielding to the scheduler while it is open, by providing * `References.PreventSchedulerYield` as `withTransaction` from * `@effect/platform-browser` does. Otherwise it can commit partway through a * batch, and the remaining handlers fail. * - The transaction stays open for the whole batch, so other read-write * transactions on the same tables wait for it. * - The transaction must not change the error or success of the wrapped * effect. Convert its own failures into defects. * * @stability unstable * @category services * @since 4.0.6 */ export declare class HandlerTransaction extends HandlerTransaction_base { } /** * Schema for an event-log identity with a string public key and redacted * base64-encoded private key bytes. * * @stability unstable * @category schemas * @since 4.0.0 */ export declare const IdentitySchema: Schema.Struct<{ readonly publicKey: Schema.String; readonly privateKey: Schema.decodeTo, Schema.Uint8ArrayFromBase64, never, never>; }>; /** * Decodes a base64url identity string produced by `encodeIdentityString`. * * **Gotchas** * * Invalid input throws a schema decoding error. * * @stability unstable * @category constructors * @since 4.0.0 */ export declare const decodeIdentityString: (value: string) => Identity["Service"]; /** * Encodes an event-log identity as a base64url string containing the public key * and private key bytes. * * @stability unstable * @category constructors * @since 4.0.0 */ export declare const encodeIdentityString: (identity: Identity["Service"]) => string; /** * Generates a new event-log identity using the configured * `EventLogEncryption` service. * * @stability unstable * @category constructors * @since 4.0.0 */ export declare const makeIdentity: Effect.Effect; /** * Creates a layer that registers handlers for every event in an event group. * * **Details** * * The callback receives a `Handlers` builder; its return type is checked so every * event in the group is handled. * * @stability unstable * @category handlers * @since 4.0.0 */ export declare const group: (group: EventGroup.EventGroup, f: (handlers: Handlers) => Handlers.ValidateReturn) => Layer.Layer, Handlers.Error, Exclude, Scope.Scope | Identity> | Registry>; /** * Registers a batch compaction handler for an event group. * * **When to use** * * Use when an event under one primary key affects entries under other keys. * * **Details** * * The compaction effect receives every entry for the group in batch order, with * its decoded event or `event: undefined` if the payload cannot be decoded. * * Only entries that the effect outputs are kept: * * - `forward(entry)` keeps an input entry unchanged, with the same id, payload, * and primary key. * - `write(tag, payload)` encodes a replacement entry anchored to the first * entry in the batch, with a fresh id in that entry's millisecond. * * Entries that are neither forwarded nor replaced are dropped, including entries * that could not be decoded. During remote replay, kept entries are replayed in * the id order of their anchors, then in write order, and a replacement's * conflicts are the stored entries newer than its anchor. * * **Gotchas** * * Compaction runs before conflicts are read, so it never sees stored entries. * Collapsing several entries into one is only safe when no stored entry for * the same event and primary key has an id between theirs. Otherwise that * stored entry should have applied between the collapsed ones, and replicas * that received the entries in separate batches end up different. * * @see {@link groupCompaction} for compaction grouped by primary key * * @stability unstable * @category compaction * @since 4.0.0 */ export declare const groupBatchCompaction: (group: EventGroup.EventGroup, effect: (options: { readonly entries: ReadonlyArray<{ readonly entry: Entry; readonly event: Event.TaggedPayload | undefined; }>; readonly write: >(tag: Tag, payload: Event.PayloadWithTag) => Effect.Effect["EncodingServices"]>; readonly forward: (entry: Entry) => Effect.Effect; }) => Effect.Effect) => Layer.Layer["DecodingServices"] | Registry>; /** * Registers a compaction handler for an event group. * * **Details** * * During remote replay, decoded entries are grouped by primary key and passed to * the compaction effect, which outputs the entries to keep: * * - `forward(entry)` keeps an input entry unchanged, with the same id, payload, * and primary key. * - `write(tag, payload)` encodes a replacement entry anchored to the first * decoded entry for the primary key, with a fresh id in that entry's * millisecond. * * Entries that are neither forwarded nor replaced are dropped. Entries that * cannot be decoded are not passed to the effect and are kept unchanged. * During remote replay, kept entries are replayed in the id order of their * anchors, then in write order, and a replacement's conflicts are the stored * entries newer than its anchor. * * **Gotchas** * * Compaction runs before conflicts are read, so it never sees stored entries. * Collapsing several entries into one is only safe when no stored entry for * the same event and primary key has an id between theirs. Otherwise that * stored entry should have applied between the collapsed ones, and replicas * that received the entries in separate batches end up different. * * @see {@link groupBatchCompaction} for compaction across primary keys * * @stability unstable * @category compaction * @since 4.0.0 */ export declare const groupCompaction: (group: EventGroup.EventGroup, effect: (options: { readonly primaryKey: string; readonly entries: ReadonlyArray; readonly events: ReadonlyArray>; readonly write: >(tag: Tag, payload: Event.PayloadWithTag) => Effect.Effect["EncodingServices"]>; readonly forward: (entry: Entry) => Effect.Effect; }) => Effect.Effect) => Layer.Layer["DecodingServices"] | Registry>; type ReactivityKeyName = [keyof ReactivityLayer.Keys] extends [never] ? string : keyof ReactivityLayer.Keys; /** * Registers reactivity keys to invalidate when events from a group are written or * replayed. * * **Details** * * Pass a single key list for all events or a mapping from event tag to key list. * * @stability unstable * @category reactivity * @since 4.0.0 */ export declare const groupReactivity: (group: EventGroup.EventGroup, keys: { readonly [Tag in Event.Tag]?: ReadonlyArray; } | ReadonlyArray) => Layer.Layer; /** * Builds the effect used to replay entries received from a remote event log. * * **Details** * * The returned handler decodes the entry and conflicts with the registered event * schema, runs the matching handler with the supplied identity and store id, and * invalidates configured reactivity keys. Decode and handler failures are logged * and fail the effect. Invalidations are deferred until the end of an enclosing * `Reactivity.withBatch`. * * @stability unstable * @category handlers * @since 4.0.0 */ export declare const makeReplayFromRemote: (options: { readonly handlers: ReadonlyMap>; readonly storeId: StoreId; readonly identity: Identity["Service"]; readonly reactivity: Reactivity; readonly reactivityKeys: Record>; readonly logAnnotations: { readonly service: string; readonly effect: string; }; }) => (input: ReplayInput) => Effect.Effect; type ReplayInput = { readonly entry: Entry; readonly conflicts: ReadonlyArray; }; /** * Provides `EventLog` and `Registry` using the configured `EventJournal` and * `Identity`. * * @stability unstable * @category layers * @since 4.0.0 */ export declare const layerEventLog: Layer.Layer; /** * Combines event-group handler layers with the `EventLog` runtime for a schema. * * **When to use** * * Use when you need one layer that installs the shared `EventLog` runtime for * an `EventLogSchema` and registers an event-group handler layer for typed * writes. * * **Details** * * The supplied handler layer is provided with `layerEventLog`. The returned * layer provides `EventLog | Registry`, preserves the handler layer's error * type, and still requires its remaining services plus `EventJournal` and * `Identity`. * * **Gotchas** * * The schema argument does not register handlers by itself. Handler registration * comes from the supplied layer, and writing an event without a registered * handler dies with `Event handler not found for: ""`. * * @see {@link schema} for creating the schema argument from event groups * @see {@link group} for building the handler layer consumed by this layer * @see {@link layerEventLog} for installing the runtime and registry without combining a handler layer * * @stability unstable * @category layers * @since 4.0.0 */ export declare const layer: (_schema: EventLogSchema, layer: Layer.Layer, E, R>) => Layer.Layer | EventJournal | Identity>; /** * Creates a typed client function for writing events defined by an * `EventLogSchema`. * * **Details** * * The returned function takes one `{ event, payload }` object, typed so the * payload matches the event, as `writeMany` items are. It delegates to the * `EventLog` service and preserves each event's success and error types. * * **Example** (Writing events with a client) * * ```ts * import { Effect, Schema } from "effect" * import { EventGroup, EventLog } from "effect/eventlog" * * const Users = EventGroup.empty.add({ * tag: "UserCreated", * primaryKey: (payload) => payload.id, * payload: Schema.Struct({ id: Schema.String }) * }) * * export const createUser = Effect.gen(function*() { * const client = yield* EventLog.makeClient(EventLog.schema(Users)) * yield* client({ event: "UserCreated", payload: { id: "user-1" } }) * }) * ``` * * @stability unstable * @category constructors * @since 4.0.0 */ export declare const makeClient: (schema: EventLogSchema) => Effect.Effect<>]: { readonly event: Tag; readonly payload: Event.PayloadWithTag, Tag>; }; }[Event.Tag>]>(item: Item) => Effect.Effect, Item["event"]>, Event.ErrorWithTag, Item["event"]> | EventJournalError>, never, EventLog>; export {}; //# sourceMappingURL=EventLog.d.ts.map