feat(core): deterministic session log replay with synced watermark (#35040)

This commit is contained in:
Kit Langton 2026-07-02 21:38:20 -04:00 committed by GitHub
commit 57e9e9771d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 205 additions and 87 deletions

View file

@ -6,20 +6,19 @@ 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.
* every event committed at or below this watermark; `seq` is absent when the
* captured watermark is empty. Emitted once for the captured watermark.
*/
export const CaughtUp = Schema.Struct({
type: Schema.Literal("log.caught_up"),
export const Synced = Schema.Struct({
type: Schema.Literal("log.synced"),
aggregateID: Schema.String,
seq: optional(Event.Seq),
}).annotate({
identifier: "EventLog.CaughtUp",
identifier: "EventLog.Synced",
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.",
"Marker emitted once when a log read reaches its captured watermark. The reader holds every event committed at or below seq.",
})
export interface CaughtUp extends Schema.Schema.Type<typeof CaughtUp> {}
export interface Synced extends Schema.Schema.Type<typeof Synced> {}
/**
* A payload-free doorbell: the aggregate's log advanced to at least `seq`.

View file

@ -14,7 +14,7 @@ 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;
* event envelope, synced 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"))

View file

@ -1,6 +1,7 @@
import { describe, expect, test } from "bun:test"
import { Schema } from "effect"
import { Event } from "../src/event.js"
import { EventLog } from "../src/event-log.js"
describe("public event schemas", () => {
test("definition is pure", () => {
@ -34,4 +35,18 @@ describe("public event schemas", () => {
expect(Event.durable([definition]).get("test.durable.1")).toBe(definition)
})
test("synced marker encodes the captured watermark", () => {
expect(
Schema.encodeSync(EventLog.Synced)({
type: "log.synced",
aggregateID: "ses_test",
seq: Event.Seq.make(1),
}),
).toEqual({ type: "log.synced", aggregateID: "ses_test", seq: 1 })
expect(Schema.encodeSync(EventLog.Synced)({ type: "log.synced", aggregateID: "ses_test" })).toEqual({
type: "log.synced",
aggregateID: "ses_test",
})
})
})