feat(server): durable log reads, changes feed, and watermarked snapshots (#34962)
This commit is contained in:
parent
33705e632a
commit
bc2e270f82
28 changed files with 1402 additions and 1605 deletions
56
packages/schema/src/event-log.ts
Normal file
56
packages/schema/src/event-log.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
export * as EventLog from "./event-log.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { Event } from "./event.js"
|
||||
import { optional } from "./schema.js"
|
||||
|
||||
/**
|
||||
* Replay-to-live boundary marker for a durable log read. The reader now holds
|
||||
* every event committed at or below `seq`; `seq` is absent when the log holds
|
||||
* no events at or before the read position. May be re-emitted after the
|
||||
* reader internally re-attaches.
|
||||
*/
|
||||
export const CaughtUp = Schema.Struct({
|
||||
type: Schema.Literal("log.caught_up"),
|
||||
aggregateID: Schema.String,
|
||||
seq: optional(Event.Seq),
|
||||
}).annotate({
|
||||
identifier: "EventLog.CaughtUp",
|
||||
description:
|
||||
"Marker emitted when a log read transitions from replay to live (or completes a finite read). The reader holds every event committed at or below seq.",
|
||||
})
|
||||
export interface CaughtUp extends Schema.Schema.Type<typeof CaughtUp> {}
|
||||
|
||||
/**
|
||||
* A payload-free doorbell: the aggregate's log advanced to at least `seq`.
|
||||
* Hints are a latency optimization only; no consumer may derive correctness
|
||||
* from receiving one. Correctness always comes from a durable log read plus
|
||||
* the consumer's own checkpoint.
|
||||
*/
|
||||
export const Hint = Schema.Struct({
|
||||
type: Schema.Literal("log.hint"),
|
||||
aggregateID: Schema.String,
|
||||
seq: Event.Seq,
|
||||
}).annotate({
|
||||
identifier: "EventLog.Hint",
|
||||
description:
|
||||
"Payload-free change hint: the aggregate's durable log advanced to at least seq. Hints coalesce under backpressure (latest per aggregate) and are never a delivery guarantee.",
|
||||
})
|
||||
export interface Hint extends Schema.Schema.Type<typeof Hint> {}
|
||||
|
||||
/**
|
||||
* Hints may have been lost. Treat every aggregate as potentially dirty and
|
||||
* recover via bounded sweep plus durable log reads. Also emitted first on
|
||||
* every (re)subscribe, since hints during disconnection were never buffered.
|
||||
*/
|
||||
export const SweepRequired = Schema.Struct({
|
||||
type: Schema.Literal("log.sweep_required"),
|
||||
}).annotate({
|
||||
identifier: "EventLog.SweepRequired",
|
||||
description:
|
||||
"Hints may have been lost; treat every aggregate as potentially dirty and recover via bounded sweep plus durable log reads. Emitted first on every (re)subscribe.",
|
||||
})
|
||||
export interface SweepRequired extends Schema.Schema.Type<typeof SweepRequired> {}
|
||||
|
||||
export const Change = Schema.Union([Hint, SweepRequired]).annotate({ identifier: "EventLog.Change" })
|
||||
export type Change = typeof Change.Type
|
||||
|
|
@ -12,6 +12,18 @@ export const ID = Schema.String.check(Schema.isStartsWith("evt_")).pipe(
|
|||
)
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
/**
|
||||
* Position in one aggregate's durable log. Values originate from the durable
|
||||
* event envelope, caught-up markers, change hints, and snapshot watermarks;
|
||||
* `after` cursors accept only values that came from those sources.
|
||||
*/
|
||||
export const Seq = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)).pipe(Schema.brand("Event.Seq"))
|
||||
export type Seq = typeof Seq.Type
|
||||
|
||||
/** Durable schema version of one event type, from the event definition that committed it. */
|
||||
export const Version = Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)).pipe(Schema.brand("Event.Version"))
|
||||
export type Version = typeof Version.Type
|
||||
|
||||
export type Definition<
|
||||
Type extends string = string,
|
||||
DataSchema extends Schema.Codec<unknown, unknown> = Schema.Codec<unknown, unknown>,
|
||||
|
|
@ -32,8 +44,8 @@ export type Payload<D extends Definition = Definition> = {
|
|||
readonly data: Data<D>
|
||||
readonly durable?: {
|
||||
readonly aggregateID: string
|
||||
readonly seq: number
|
||||
readonly version: number
|
||||
readonly seq: Seq
|
||||
readonly version: Version
|
||||
}
|
||||
readonly location?: Location.Ref
|
||||
readonly metadata?: Record<string, unknown>
|
||||
|
|
@ -55,7 +67,7 @@ export function define<
|
|||
id: ID,
|
||||
metadata: optional(Schema.Record(Schema.String, Schema.Unknown)),
|
||||
type: Schema.Literal(input.type),
|
||||
durable: optional(Schema.Struct({ aggregateID: Schema.String, seq: Schema.Int, version: Schema.Int })),
|
||||
durable: optional(Schema.Struct({ aggregateID: Schema.String, seq: Seq, version: Version })),
|
||||
location: optional(Location.Ref),
|
||||
data,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue