Compare commits

...

3 Commits

Author SHA1 Message Date
Kit Langton 603b334b7f fix(core): preserve event routing semantics 2026-06-25 12:41:44 -04:00
Kit Langton 45071a8d8e refactor(core): prove event publication branches 2026-06-25 12:04:05 -04:00
Kit Langton 95f264e04e refactor(schema): distinguish published event durability 2026-06-25 11:51:54 -04:00
13 changed files with 522 additions and 445 deletions
+1
View File
@@ -141,6 +141,7 @@ _Avoid_: Response envelope
- Promise streaming methods return a lazy `AsyncIterable` directly rather than a Promise-wrapped stream object. Iteration opens the connection, `AbortSignal` cancels it, and ending iteration closes the underlying request; the Effect emitter analogously returns `Stream` directly.
- Promise SSE connection establishment, declared HTTP failures, and infrastructure failures occur during `AsyncIterable` iteration, beginning with its first `next()` call, rather than during synchronous method construction.
- Neither generated streaming runtime automatically reconnects after disconnection. Promise `AsyncIterable` and Effect `Stream` fail explicitly; live consumers refresh and resubscribe, while durable sequence-based resume remains explicit composition above the generated client.
- Event definition durability is authoritative for published payloads. Durable definitions publish and decode only with commit metadata (`aggregateID`, `seq`, and `version`); live definitions forbid that metadata. Core's pre-commit payload without an assigned sequence is a separate internal type and never reaches subscribers or projectors.
- Promise client construction is synchronous and network-free. It requires `baseUrl`, defaults to `globalThis.fetch`, accepts client-level headers, and merges them with per-call header overrides.
- Effect client construction accepts an explicit `baseUrl` and obtains `HttpClient.HttpClient` from the Effect environment. It does not install fetch or duplicate per-call transport policy; callers transform/provide the client for headers, tracing, retries, recording, and tests, while fiber interruption owns cancellation.
- Promise and Effect emitters each own their generated public type modules. The **SDK Contract IR**, not a physically shared generated type package, is the common source; this permits zero-Effect wire types and rich decoded Effect types to evolve independently.
+67 -60
View File
@@ -2,7 +2,14 @@ export * as EventV2 from "./event"
import { Cause, Context, Effect, Layer, Option, PubSub, Schema, Stream } from "effect"
import { Event } from "@opencode-ai/schema/event"
import type { Data, Definition, Payload } from "@opencode-ai/schema/event"
import type {
Data,
Definition,
DurableDefinition,
LiveDefinition,
Payload,
UncommittedPayload,
} from "@opencode-ai/schema/event"
import { and, asc, eq, gt } from "drizzle-orm"
import { Database } from "./database/database"
import { EventSequenceTable, EventTable } from "./event/sql"
@@ -124,8 +131,25 @@ export const layerWith = (options?: LayerOptions) =>
)
function commitDurableEvent(
definition: Definition,
event: Payload,
definition: DurableDefinition,
event: UncommittedPayload<DurableDefinition>,
input: undefined,
commit?: (seq: number) => Effect.Effect<void>,
): Effect.Effect<Payload<DurableDefinition>>
function commitDurableEvent(
definition: DurableDefinition,
event: UncommittedPayload<DurableDefinition>,
input: {
readonly seq: number
readonly aggregateID: string
readonly ownerID?: string
readonly strictOwner?: boolean
},
commit?: (seq: number) => Effect.Effect<void>,
): Effect.Effect<Payload<DurableDefinition> | undefined>
function commitDurableEvent(
definition: DurableDefinition,
event: UncommittedPayload<DurableDefinition>,
input?: {
readonly seq: number
readonly aggregateID: string
@@ -135,7 +159,7 @@ export const layerWith = (options?: LayerOptions) =>
commit?: (seq: number) => Effect.Effect<void>,
) {
return Effect.gen(function* () {
const durable = definition?.durable
const durable = definition.durable
if (durable) {
const aggregateID = (event.data as Record<string, unknown>)[durable.aggregate]
if (typeof aggregateID !== "string") {
@@ -200,7 +224,7 @@ export const layerWith = (options?: LayerOptions) =>
.run()
.pipe(Effect.orDie)
}
return
return undefined
}
yield* Effect.die(
new InvalidDurableEventError({
@@ -210,7 +234,7 @@ export const layerWith = (options?: LayerOptions) =>
)
}
if (input && row?.ownerID && row.ownerID !== input.ownerID) {
return
return undefined
}
const seq = input?.seq ?? latest + 1
if (input && seq !== latest + 1) {
@@ -234,10 +258,10 @@ export const layerWith = (options?: LayerOptions) =>
message: `Event ${event.id} already exists at aggregate ${stored.aggregateID} sequence ${stored.seq}`,
}),
)
const committed = {
const committed: Payload<DurableDefinition> = {
...event,
durable: { aggregateID, seq, version: durable.version },
} as Payload
}
for (const projector of list) {
yield* projector(committed)
}
@@ -267,14 +291,14 @@ export const layerWith = (options?: LayerOptions) =>
])
.run()
.pipe(Effect.orDie)
return { aggregateID, seq }
return committed
}),
{ behavior: "immediate" },
)
.pipe(Effect.orDie)
if (committed) {
yield* Effect.forEach(
pubsub.durable.get(committed.aggregateID) ?? [],
pubsub.durable.get(committed.durable.aggregateID) ?? [],
(wake) => PubSub.publish(wake, undefined),
{ discard: true },
)
@@ -287,35 +311,6 @@ export const layerWith = (options?: LayerOptions) =>
})
}
function publishEvent<D extends Definition>(definition: D, event: Payload<D>, commit?: PublishOptions["commit"]) {
return Effect.gen(function* () {
if (!definition?.durable && commit)
return yield* Effect.die(
new InvalidDurableEventError({
type: event.type,
message: "Local commit hooks require a durable event",
}),
)
if (definition?.durable) {
const committed = yield* commitDurableEvent(definition, event as Payload, undefined, commit)
if (committed) {
event = {
...event,
durable: {
aggregateID: committed.aggregateID,
seq: committed.seq,
version: definition.durable.version,
},
}
yield* notify(event as Payload, true)
return event
}
}
yield* notify(event as Payload, false)
return event
})
}
const observe = (event: Payload, observer: (event: Payload) => Effect.Effect<void>) =>
Effect.suspend(() => observer(event)).pipe(
Effect.catchCauseIf(
@@ -337,7 +332,12 @@ export const layerWith = (options?: LayerOptions) =>
})
}
function publish<D extends Definition>(definition: D, data: Data<D>, options?: PublishOptions) {
function publish<D extends Definition>(
definition: D,
data: Data<D>,
options?: PublishOptions,
): Effect.Effect<Payload<D>>
function publish(definition: Definition, data: unknown, options?: PublishOptions): Effect.Effect<Payload> {
return Effect.gen(function* () {
const serviceLocation = Option.getOrUndefined(yield* Effect.serviceOption(Location.Service))
const location =
@@ -345,17 +345,34 @@ export const layerWith = (options?: LayerOptions) =>
(serviceLocation
? { directory: serviceLocation.directory, workspaceID: serviceLocation.workspaceID }
: undefined)
return yield* publishEvent(
definition,
{
if (definition.durable) {
const event: UncommittedPayload<DurableDefinition> = {
id: options?.id ?? ID.create(),
...(options?.metadata ? { metadata: options.metadata } : {}),
type: definition.type,
...(location ? { location } : {}),
data,
} as Payload<D>,
options?.commit,
)
}
const committed = yield* commitDurableEvent(definition, event, undefined, options?.commit)
yield* notify(committed, true)
return committed
}
if (options?.commit)
return yield* Effect.die(
new InvalidDurableEventError({
type: definition.type,
message: "Local commit hooks require a durable event",
}),
)
const event: Payload<LiveDefinition> = {
id: options?.id ?? ID.create(),
...(options?.metadata ? { metadata: options.metadata } : {}),
type: definition.type,
...(location ? { location } : {}),
data,
}
yield* notify(event, false)
return event
})
}
@@ -370,11 +387,11 @@ export const layerWith = (options?: LayerOptions) =>
new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` }),
)
} else {
const payload = {
const payload: UncommittedPayload<DurableDefinition> = {
id: event.id,
type: definition.type,
data: Schema.decodeUnknownSync(definition.data)(event.data),
} as Payload
}
const committed = yield* commitDurableEvent(definition, payload, {
seq: event.seq,
aggregateID: event.aggregateID,
@@ -382,17 +399,7 @@ export const layerWith = (options?: LayerOptions) =>
strictOwner: options?.strictOwner,
})
if (committed && options?.publish) {
yield* notify(
{
...payload,
durable: {
aggregateID: committed.aggregateID,
seq: committed.seq,
version: definition.durable.version,
},
},
true,
)
yield* notify(committed, true)
}
}
})
@@ -459,7 +466,7 @@ export const layerWith = (options?: LayerOptions) =>
const streamAll = (): Stream.Stream<Payload> => Stream.fromPubSub(pubsub.all)
const decodeSerializedEvent = (event: SerializedEvent) => {
const decodeSerializedEvent = (event: SerializedEvent): Payload<DurableDefinition> => {
const definition = Durable.get(event.type)
if (!definition?.durable) {
throw new InvalidDurableEventError({ type: event.type, message: `Unknown durable event type ${event.type}` })
+46 -2
View File
@@ -116,7 +116,7 @@ describe("EventV2", () => {
const event = yield* events.publish(VersionedMessage, { id: "one", text: "hello" })
expect(event.type).toBe("test.versioned")
expect(event.durable?.version).toBe(2)
expect(event.durable.version).toBe(2)
}),
)
@@ -138,6 +138,50 @@ describe("EventV2", () => {
}),
)
it.effect("preserves same-type projector routing across durable versions", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const historical = EventV2.define({
type: "test.projector-version",
durable: { version: 1, aggregate: "id" },
schema: { id: Schema.String },
})
const current = EventV2.define({
type: "test.projector-version",
durable: { version: 2, aggregate: "id" },
schema: { id: Schema.String },
})
const received = new Array<EventV2.Payload>()
yield* events.project(historical, (event) => Effect.sync(() => received.push(event)))
const published = yield* events.publish(current, { id: "aggregate" })
expect(received).toEqual([published])
}),
)
it.effect("preserves same-type subscription routing across durable versions", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
const historical = EventV2.define({
type: "test.subscription-version",
durable: { version: 1, aggregate: "id" },
schema: { id: Schema.String },
})
const current = EventV2.define({
type: "test.subscription-version",
durable: { version: 2, aggregate: "id" },
schema: { id: Schema.String },
})
const fiber = yield* events.subscribe(historical).pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
yield* Effect.yieldNow
const published = yield* events.publish(current, { id: "aggregate" })
expect(Array.from(yield* Fiber.join(fiber))).toEqual([published])
}),
)
it.effect("publishes to typed and wildcard subscriptions", () =>
Effect.gen(function* () {
const events = yield* EventV2.Service
@@ -764,7 +808,7 @@ describe("EventV2", () => {
const replayed = {
id: published.id,
type: EventV2.versionedType(DurableMessage.type, 1),
seq: published.durable!.seq,
seq: published.durable.seq,
aggregateID,
data: published.data,
}
@@ -17,7 +17,14 @@ const capture = () => {
const events = EventV2.Service.of({
publish: (definition, data) =>
Effect.sync(() => {
const event = { id: EventV2.ID.create(), type: definition.type, data } as EventV2.Payload<typeof definition>
const event = {
id: EventV2.ID.create(),
type: definition.type,
...(definition.durable
? { durable: { aggregateID: sessionID, seq: published.length, version: definition.durable.version } }
: {}),
data,
} as EventV2.Payload<typeof definition>
published.push({
type: definition.durable
? EventV2.versionedType(definition.type, definition.durable.version)
@@ -99,6 +99,17 @@ describe("PublicApi OpenAPI v2 errors", () => {
})
})
test("documents durable metadata only on durable events", () => {
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
const durable = spec.components.schemas.V2EventSessionCreated
const live = spec.components.schemas.V2EventSessionNextTextDelta
expect(durable?.required).toContain("durable")
expect(durable?.properties?.durable).toBeDefined()
expect(live?.required).not.toContain("durable")
expect(Reflect.get(live?.properties?.durable ?? {}, "not")).toEqual({})
})
test("preserves /api auth responses", () => {
const spec = OpenApi.fromApi(PublicApi) as OpenApiSpec
@@ -23,6 +23,7 @@ function request(route: string, directory: string, init: RequestInit = {}) {
const Event = Schema.Struct({
id: EventV2.ID,
type: Schema.String,
durable: Schema.optional(Schema.Struct({ aggregateID: Schema.String, seq: Schema.Int, version: Schema.Int })),
location: Schema.optional(Location.Ref),
data: Schema.Unknown,
})
@@ -81,12 +82,15 @@ describe("v2 location HttpApi", () => {
const reader = response.body!.getReader()
const connected = await readEvent(reader)
expect(connected.type).toBe("server.connected")
expect(connected).not.toHaveProperty("durable")
expect(connected.location).toBeUndefined()
const created = await request("/session", publisher.path, { method: "POST" })
expect(created.status).toBe(200)
const session = (await created.json()) as { id: string }
expect(await readEventType(reader, "session.created")).toMatchObject({
type: "session.created",
durable: { aggregateID: session.id, seq: 0, version: 1 },
location: { directory: publisher.path },
data: { sessionID: expect.any(String) },
})
@@ -9,6 +9,12 @@ import { SessionEvent } from "@opencode-ai/core/session/event"
import { SessionMessageUpdater } from "@opencode-ai/core/session/message-updater"
import { SessionMessage } from "@opencode-ai/core/session/message"
function durable(sessionID: SessionID, seq?: number): { aggregateID: SessionID; seq: number; version: 1 }
function durable(sessionID: SessionID, seq: number, version: 2): { aggregateID: SessionID; seq: number; version: 2 }
function durable(sessionID: SessionID, seq = 0, version: 1 | 2 = 1) {
return { aggregateID: sessionID, seq, version }
}
test.skip("step snapshots carry over to assistant messages", () => {
const state: SessionMessageUpdater.MemoryState = { messages: [] }
const sessionID = SessionID.make("session")
@@ -17,6 +23,7 @@ test.skip("step snapshots carry over to assistant messages", () => {
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID),
type: "session.next.step.started",
data: {
sessionID,
@@ -38,6 +45,7 @@ test.skip("step snapshots carry over to assistant messages", () => {
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID, 1, 2),
type: "session.next.step.ended",
data: {
sessionID,
@@ -70,6 +78,7 @@ test.skip("text ended populates assistant text content", () => {
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID),
type: "session.next.step.started",
data: {
sessionID,
@@ -88,6 +97,7 @@ test.skip("text ended populates assistant text content", () => {
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID, 1),
type: "session.next.text.started",
data: {
sessionID,
@@ -101,6 +111,7 @@ test.skip("text ended populates assistant text content", () => {
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID, 2),
type: "session.next.text.ended",
data: {
sessionID,
@@ -126,6 +137,7 @@ test.skip("tool completion stores completed timestamp", () => {
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID),
type: "session.next.step.started",
data: {
sessionID,
@@ -144,6 +156,7 @@ test.skip("tool completion stores completed timestamp", () => {
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID, 1),
type: "session.next.tool.input.started",
data: {
sessionID,
@@ -158,6 +171,7 @@ test.skip("tool completion stores completed timestamp", () => {
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID, 2),
type: "session.next.tool.called",
data: {
sessionID,
@@ -174,6 +188,7 @@ test.skip("tool completion stores completed timestamp", () => {
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID, 3),
type: "session.next.tool.success",
data: {
sessionID,
@@ -204,6 +219,7 @@ test("compaction events reduce to compaction message only when completed", () =>
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id,
durable: durable(sessionID),
type: "session.next.compaction.started",
data: {
sessionID,
@@ -245,6 +261,7 @@ test("compaction events reduce to compaction message only when completed", () =>
Effect.runSync(
SessionMessageUpdater.update(SessionMessageUpdater.memory(state), {
id: EventV2.ID.create(),
durable: durable(sessionID, 3),
type: "session.next.compaction.ended",
data: {
sessionID,
+18 -7
View File
@@ -8,24 +8,34 @@ import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
const fields = {
id: Event.ID,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
durable: Schema.optional(Schema.Struct({ aggregateID: Schema.String, seq: Schema.Int, version: Schema.Int })),
location: Schema.optional(Location.Ref),
}
const DurableEnvelope = Schema.Struct({ aggregateID: Schema.String, seq: Schema.Int, version: Schema.Int })
const schema = (definitions: ReadonlyArray<Definition>) =>
Schema.Union([
...definitions.map((definition) =>
Schema.Struct({
...fields,
type: Schema.Literal(definition.type),
data: definition.data,
}).annotate({ identifier: `V2Event.${definition.type}` }),
definition.durable
? Schema.Struct({
...fields,
durable: DurableEnvelope,
type: Schema.Literal(definition.type),
data: definition.data,
}).annotate({ identifier: `V2Event.${definition.type}` })
: Schema.Struct({
...fields,
durable: Schema.optional(Schema.Never),
type: Schema.Literal(definition.type),
data: definition.data,
}).annotate({ identifier: `V2Event.${definition.type}` }),
),
...(definitions.some((definition) => definition.type === "server.connected")
? []
: [
Schema.Struct({
...fields,
durable: Schema.optional(Schema.Never),
type: Schema.Literal("server.connected"),
data: Schema.Struct({}),
}).annotate({ identifier: "V2Event.server.connected" }),
@@ -56,4 +66,5 @@ export const makeEventGroup = (definitions: ReadonlyArray<Definition>) => make(d
const event = make(EventManifest.ServerDefinitions)
export const EventGroup = event.group
export type Event = typeof event.schema.Type
export const EventSchema = event.schema
export type Event = typeof EventSchema.Type
+27
View File
@@ -0,0 +1,27 @@
import { describe, expect, test } from "bun:test"
import { Event } from "@opencode-ai/schema/event"
import { Schema } from "effect"
import { EventSchema } from "../src/groups/event"
describe("EventSchema", () => {
test("requires durable metadata on durable events", () => {
expect(
Schema.is(EventSchema)({
id: Event.ID.create(),
type: "session.created",
data: { sessionID: "session" },
}),
).toBe(false)
})
test("rejects durable metadata on live events", () => {
expect(
Schema.is(EventSchema)({
id: Event.ID.create(),
type: "server.connected",
durable: { aggregateID: "aggregate", seq: 0, version: 1 },
data: {},
}),
).toBe(false)
})
})
+97 -40
View File
@@ -11,62 +11,119 @@ export const ID = Schema.String.check(Schema.isStartsWith("evt_")).pipe(
)
export type ID = typeof ID.Type
export type Definition<
export type DurableOptions = {
readonly version: number
readonly aggregate: string
}
export type DurableEnvelope = {
readonly aggregateID: string
readonly seq: number
readonly version: number
}
const PublishedDurableEnvelope = Schema.Struct({
aggregateID: Schema.String,
seq: Schema.Number,
version: Schema.Number,
})
const NoDurableEnvelope = Schema.optional(Schema.Never)
export type LiveDefinition<
Type extends string = string,
DataSchema extends Schema.Codec<unknown, unknown> = Schema.Codec<unknown, unknown>,
> = Schema.Top & {
readonly type: Type
readonly durable?: {
readonly version: number
readonly aggregate: string
}
readonly data: DataSchema
readonly durable?: never
}
export type DurableDefinition<
Type extends string = string,
DataSchema extends Schema.Codec<unknown, unknown> = Schema.Codec<unknown, unknown>,
Durability extends DurableOptions = DurableOptions,
> = Schema.Top & {
readonly type: Type
readonly data: DataSchema
readonly durable: Durability
}
export type Definition<
Type extends string = string,
DataSchema extends Schema.Codec<unknown, unknown> = Schema.Codec<unknown, unknown>,
> = LiveDefinition<Type, DataSchema> | DurableDefinition<Type, DataSchema>
export type Data<D extends Definition> = Schema.Schema.Type<D["data"]>
export type Payload<D extends Definition = Definition> = {
readonly id: ID
readonly type: D["type"]
readonly data: Data<D>
readonly durable?: {
readonly aggregateID: string
readonly seq: number
readonly version: number
}
readonly location?: Location.Ref
readonly metadata?: Record<string, unknown>
}
export type UncommittedPayload<D extends Definition = Definition> = D extends Definition
? {
readonly id: ID
readonly type: D["type"]
readonly data: Data<D>
readonly location?: Location.Ref
readonly metadata?: Record<string, unknown>
}
: never
export type PublishedPayload<D extends Definition = Definition> = D extends DurableDefinition
? UncommittedPayload<D> & { readonly durable: DurableEnvelope }
: D extends LiveDefinition
? UncommittedPayload<D> & { readonly durable?: never }
: never
export type Payload<D extends Definition = Definition> = PublishedPayload<D>
type LiveEventSchema<
Type extends string,
Fields extends Readonly<Record<PropertyKey, Schema.Codec<unknown, unknown>>>,
> = Schema.Schema<PublishedPayload<LiveDefinition<Type, Schema.Struct<Fields>>>> &
LiveDefinition<Type, Schema.Struct<Fields>>
type DurableEventSchema<
Type extends string,
Fields extends Readonly<Record<PropertyKey, Schema.Codec<unknown, unknown>>>,
Durability extends DurableOptions,
> = Schema.Schema<PublishedPayload<DurableDefinition<Type, Schema.Struct<Fields>, Durability>>> &
DurableDefinition<Type, Schema.Struct<Fields>, Durability>
export function define<
const Type extends string,
Fields extends Readonly<Record<PropertyKey, Schema.Codec<unknown, unknown>>>,
>(input: { readonly type: Type; readonly durable?: never; readonly schema: Fields }): LiveEventSchema<Type, Fields>
export function define<
const Type extends string,
Fields extends Readonly<Record<PropertyKey, Schema.Codec<unknown, unknown>>>,
const Durability extends DurableOptions,
>(input: {
readonly type: Type
readonly durable?: {
readonly version: number
readonly aggregate: string
}
readonly durable: Durability
readonly schema: Fields
}): Schema.Schema<Payload<Definition<Type, Schema.Struct<Fields>>>> & Definition<Type, Schema.Struct<Fields>> {
}): DurableEventSchema<Type, Fields, Durability>
export function define(input: {
readonly type: string
readonly durable?: DurableOptions
readonly schema: Readonly<Record<PropertyKey, Schema.Codec<unknown, unknown>>>
}): Schema.Top {
const data = Schema.Struct(input.schema)
return Object.assign(
Schema.Struct({
id: ID,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
type: Schema.Literal(input.type),
durable: Schema.optional(
Schema.Struct({ aggregateID: Schema.String, seq: Schema.Number, version: Schema.Number }),
),
location: Schema.optional(Location.Ref),
data,
}).annotate({ identifier: input.type }),
{
type: input.type,
...(input.durable === undefined ? {} : { durable: input.durable }),
data,
},
) as Schema.Schema<Payload<Definition<Type, Schema.Struct<Fields>>>> & Definition<Type, Schema.Struct<Fields>>
const fields = {
id: ID,
metadata: Schema.optional(Schema.Record(Schema.String, Schema.Unknown)),
type: Schema.Literal(input.type),
location: Schema.optional(Location.Ref),
data,
}
if (input.durable) {
return Object.assign(
Schema.Struct({ ...fields, durable: PublishedDurableEnvelope }).annotate({
identifier: input.type,
}),
{ type: input.type, durable: input.durable, data },
)
}
return Object.assign(Schema.Struct({ ...fields, durable: NoDurableEnvelope }).annotate({ identifier: input.type }), {
type: input.type,
data,
})
}
export function inventory<const Definitions extends ReadonlyArray<Definition>>(...definitions: Definitions) {
@@ -103,7 +160,7 @@ export function durable(definitions: ReadonlyArray<Definition>) {
if (result.has(key)) throw new Error(`Duplicate durable event definition for ${key}`)
result.set(key, definition)
return result
}, new Map<string, Definition>()),
}, new Map<string, DurableDefinition>()),
)
}
+92
View File
@@ -34,4 +34,96 @@ describe("public event schemas", () => {
expect(Event.durable([definition]).get("test.durable.1")).toBe(definition)
})
test("durable definitions require published commit metadata", () => {
const definition = Event.define({
type: "test.durable",
durable: { aggregate: "id", version: 1 },
schema: { id: Schema.String },
})
const payload: typeof definition.Type = {
id: Event.ID.create(),
type: definition.type,
durable: { aggregateID: "aggregate", seq: 0, version: 1 },
data: { id: "aggregate" },
}
expect(Schema.is(definition)(payload)).toBe(true)
expect(
Schema.is(definition)({
id: Event.ID.create(),
type: definition.type,
data: { id: "aggregate" },
}),
).toBe(false)
// @ts-expect-error Published durable payloads require commit metadata.
const missing: typeof definition.Type = { id: Event.ID.create(), type: definition.type, data: { id: "aggregate" } }
void missing
})
test("live definitions reject durable commit metadata", () => {
const definition = Event.define({
type: "test.live",
schema: { value: Schema.String },
})
const payload: typeof definition.Type = {
id: Event.ID.create(),
type: definition.type,
data: { value: "value" },
}
expect(Schema.is(definition)(payload)).toBe(true)
expect(
Schema.is(definition)({
...payload,
durable: { aggregateID: "aggregate", seq: 0, version: 1 },
}),
).toBe(false)
const invalid: typeof definition.Type = {
...payload,
// @ts-expect-error Live payloads cannot carry durable commit metadata.
durable: { aggregateID: "aggregate", seq: 0, version: 1 },
}
void invalid
})
test("mixed definition payloads preserve durability correlation", () => {
const durable = Event.define({
type: "test.mixed.durable",
durable: { aggregate: "id", version: 2 },
schema: { id: Schema.String },
})
const live = Event.define({
type: "test.mixed.live",
schema: { value: Schema.String },
})
type Mixed = Event.Payload<typeof durable | typeof live>
const committed: Mixed = {
id: Event.ID.create(),
type: durable.type,
durable: { aggregateID: "aggregate", seq: 0, version: 2 },
data: { id: "aggregate" },
}
const ephemeral: Mixed = {
id: Event.ID.create(),
type: live.type,
data: { value: "value" },
}
void committed
void ephemeral
// @ts-expect-error Durable union members require commit metadata.
const uncommitted: Mixed = { id: Event.ID.create(), type: durable.type, data: { id: "aggregate" } }
// @ts-expect-error Live union members cannot carry durable commit metadata.
const falselyCommitted: Mixed = {
id: Event.ID.create(),
type: live.type,
durable: { aggregateID: "aggregate", seq: 0, version: 2 },
data: { value: "value" },
}
void uncommitted
void falselyCommitted
})
})
+11
View File
@@ -58,6 +58,17 @@ if (sseTypesPatched === sseTypesSource) {
}
await Bun.write(sseTypesPath, sseTypesPatched)
// OpenAPI represents Schema.Never as `not: {}`, which @hey-api currently
// widens to unknown. Preserve impossible optional event fields as never.
const eventTypesPath = "./src/v2/gen/types.gen.ts"
const eventTypesFile = Bun.file(eventTypesPath)
const eventTypesSource = await eventTypesFile.text()
const eventTypesPatched = eventTypesSource.replaceAll(" durable?: unknown", " durable?: never")
if (eventTypesPatched === eventTypesSource) {
throw new Error(`Event never patch did not apply; @hey-api/openapi-ts output may have changed (${eventTypesPath})`)
}
await Bun.write(eventTypesPath, eventTypesPatched)
await $`bun prettier --write src/gen`
await $`bun prettier --write src/v2`
await $`rm -rf dist`
+123 -335
View File
@@ -4328,12 +4328,8 @@ export type V2EventModelsDevRefreshed = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "models-dev.refreshed"
data: {
[key: string]: unknown
@@ -4345,12 +4341,8 @@ export type V2EventIntegrationUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "integration.updated"
data: {
[key: string]: unknown
@@ -4362,12 +4354,8 @@ export type V2EventIntegrationConnectionUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "integration.connection.updated"
data: {
integrationID: string
@@ -4379,12 +4367,8 @@ export type V2EventCatalogUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "catalog.updated"
data: {
[key: string]: unknown
@@ -4396,12 +4380,12 @@ export type V2EventSessionCreated = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.created"
data: {
sessionID: string
@@ -4414,12 +4398,12 @@ export type V2EventSessionUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.updated"
data: {
sessionID: string
@@ -4432,12 +4416,12 @@ export type V2EventSessionDeleted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.deleted"
data: {
sessionID: string
@@ -4450,12 +4434,12 @@ export type V2EventMessageUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "message.updated"
data: {
sessionID: string
@@ -4468,12 +4452,12 @@ export type V2EventMessageRemoved = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "message.removed"
data: {
sessionID: string
@@ -4486,12 +4470,12 @@ export type V2EventMessagePartUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "message.part.updated"
data: {
sessionID: string
@@ -4505,12 +4489,12 @@ export type V2EventMessagePartRemoved = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "message.part.removed"
data: {
sessionID: string
@@ -4524,12 +4508,12 @@ export type V2EventSessionNextAgentSwitched = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.agent.switched"
data: {
timestamp: number
@@ -4544,12 +4528,12 @@ export type V2EventSessionNextModelSwitched = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.model.switched"
data: {
timestamp: number
@@ -4568,12 +4552,12 @@ export type V2EventSessionNextMoved = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.moved"
data: {
timestamp: number
@@ -4588,12 +4572,12 @@ export type V2EventSessionNextPrompted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.prompted"
data: {
timestamp: number
@@ -4609,12 +4593,12 @@ export type V2EventSessionNextPromptAdmitted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.prompt.admitted"
data: {
timestamp: number
@@ -4630,12 +4614,12 @@ export type V2EventSessionNextContextUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.context.updated"
data: {
timestamp: number
@@ -4650,12 +4634,12 @@ export type V2EventSessionNextSynthetic = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.synthetic"
data: {
timestamp: number
@@ -4670,12 +4654,12 @@ export type V2EventSessionNextShellStarted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.shell.started"
data: {
timestamp: number
@@ -4691,12 +4675,12 @@ export type V2EventSessionNextShellEnded = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.shell.ended"
data: {
timestamp: number
@@ -4711,12 +4695,12 @@ export type V2EventSessionNextStepStarted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.step.started"
data: {
timestamp: number
@@ -4737,12 +4721,12 @@ export type V2EventSessionNextStepEnded = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.step.ended"
data: {
timestamp: number
@@ -4769,12 +4753,12 @@ export type V2EventSessionNextStepFailed = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.step.failed"
data: {
timestamp: number
@@ -4789,12 +4773,12 @@ export type V2EventSessionNextTextStarted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.text.started"
data: {
timestamp: number
@@ -4809,12 +4793,8 @@ export type V2EventSessionNextTextDelta = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "session.next.text.delta"
data: {
timestamp: number
@@ -4830,12 +4810,12 @@ export type V2EventSessionNextTextEnded = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.text.ended"
data: {
timestamp: number
@@ -4851,12 +4831,12 @@ export type V2EventSessionNextReasoningStarted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.reasoning.started"
data: {
timestamp: number
@@ -4876,12 +4856,8 @@ export type V2EventSessionNextReasoningDelta = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "session.next.reasoning.delta"
data: {
timestamp: number
@@ -4897,12 +4873,12 @@ export type V2EventSessionNextReasoningEnded = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.reasoning.ended"
data: {
timestamp: number
@@ -4923,12 +4899,12 @@ export type V2EventSessionNextToolInputStarted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.input.started"
data: {
timestamp: number
@@ -4944,12 +4920,8 @@ export type V2EventSessionNextToolInputDelta = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "session.next.tool.input.delta"
data: {
timestamp: number
@@ -4965,12 +4937,12 @@ export type V2EventSessionNextToolInputEnded = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.input.ended"
data: {
timestamp: number
@@ -4986,12 +4958,12 @@ export type V2EventSessionNextToolCalled = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.called"
data: {
timestamp: number
@@ -5018,12 +4990,12 @@ export type V2EventSessionNextToolProgress = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.progress"
data: {
timestamp: number
@@ -5042,12 +5014,12 @@ export type V2EventSessionNextToolSuccess = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.success"
data: {
timestamp: number
@@ -5076,12 +5048,12 @@ export type V2EventSessionNextToolFailed = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.tool.failed"
data: {
timestamp: number
@@ -5106,12 +5078,12 @@ export type V2EventSessionNextRetried = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.retried"
data: {
timestamp: number
@@ -5126,12 +5098,12 @@ export type V2EventSessionNextCompactionStarted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.compaction.started"
data: {
timestamp: number
@@ -5146,12 +5118,8 @@ export type V2EventSessionNextCompactionDelta = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "session.next.compaction.delta"
data: {
timestamp: number
@@ -5166,12 +5134,12 @@ export type V2EventSessionNextCompactionEnded = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.compaction.ended"
data: {
timestamp: number
@@ -5188,12 +5156,12 @@ export type V2EventSessionNextRevertStaged = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.revert.staged"
data: {
timestamp: number
@@ -5213,12 +5181,12 @@ export type V2EventSessionNextRevertCleared = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.revert.cleared"
data: {
timestamp: number
@@ -5231,12 +5199,12 @@ export type V2EventSessionNextRevertCommitted = {
metadata?: {
[key: string]: unknown
}
durable?: {
location?: LocationRef
durable: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
type: "session.next.revert.committed"
data: {
timestamp: number
@@ -5250,12 +5218,8 @@ export type V2EventMessagePartDelta = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "message.part.delta"
data: {
sessionID: string
@@ -5271,12 +5235,8 @@ export type V2EventSessionDiff = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "session.diff"
data: {
sessionID: string
@@ -5289,12 +5249,8 @@ export type V2EventSessionError = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "session.error"
data: {
sessionID?: string
@@ -5315,12 +5271,8 @@ export type V2EventInstallationUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "installation.updated"
data: {
version: string
@@ -5332,12 +5284,8 @@ export type V2EventInstallationUpdateAvailable = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "installation.update-available"
data: {
version: string
@@ -5349,12 +5297,8 @@ export type V2EventFileEdited = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "file.edited"
data: {
file: string
@@ -5366,12 +5310,8 @@ export type V2EventReferenceUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "reference.updated"
data: {
[key: string]: unknown
@@ -5383,12 +5323,8 @@ export type V2EventPermissionV2Asked = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "permission.v2.asked"
data: {
id: string
@@ -5408,12 +5344,8 @@ export type V2EventPermissionV2Replied = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "permission.v2.replied"
data: {
sessionID: string
@@ -5427,12 +5359,8 @@ export type V2EventPluginAdded = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "plugin.added"
data: {
id: string
@@ -5444,12 +5372,8 @@ export type V2EventProjectDirectoriesUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "project.directories.updated"
data: {
projectID: string
@@ -5461,12 +5385,8 @@ export type V2EventFileWatcherUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "file.watcher.updated"
data: {
file: string
@@ -5479,12 +5399,8 @@ export type V2EventPtyCreated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "pty.created"
data: {
info: Pty
@@ -5496,12 +5412,8 @@ export type V2EventPtyUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "pty.updated"
data: {
info: Pty
@@ -5513,12 +5425,8 @@ export type V2EventPtyExited = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "pty.exited"
data: {
id: string
@@ -5531,12 +5439,8 @@ export type V2EventPtyDeleted = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "pty.deleted"
data: {
id: string
@@ -5548,12 +5452,8 @@ export type V2EventQuestionV2Asked = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "question.v2.asked"
data: {
id: string
@@ -5571,12 +5471,8 @@ export type V2EventQuestionV2Replied = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "question.v2.replied"
data: {
sessionID: string
@@ -5590,12 +5486,8 @@ export type V2EventQuestionV2Rejected = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "question.v2.rejected"
data: {
sessionID: string
@@ -5608,12 +5500,8 @@ export type V2EventTodoUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "todo.updated"
data: {
sessionID: string
@@ -5626,12 +5514,8 @@ export type V2EventLspUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "lsp.updated"
data: {
[key: string]: unknown
@@ -5643,12 +5527,8 @@ export type V2EventPermissionAsked = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "permission.asked"
data: {
id: string
@@ -5671,12 +5551,8 @@ export type V2EventPermissionReplied = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "permission.replied"
data: {
sessionID: string
@@ -5690,12 +5566,8 @@ export type V2EventTuiPromptAppend = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "tui.prompt.append"
data: {
text: string
@@ -5707,12 +5579,8 @@ export type V2EventTuiCommandExecute = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "tui.command.execute"
data: {
command:
@@ -5741,12 +5609,8 @@ export type V2EventTuiToastShow = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "tui.toast.show"
data: {
title?: string
@@ -5761,12 +5625,8 @@ export type V2EventTuiSessionSelect = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "tui.session.select"
data: {
/**
@@ -5781,12 +5641,8 @@ export type V2EventMcpToolsChanged = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "mcp.tools.changed"
data: {
server: string
@@ -5798,12 +5654,8 @@ export type V2EventMcpBrowserOpenFailed = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "mcp.browser.open.failed"
data: {
mcpName: string
@@ -5816,12 +5668,8 @@ export type V2EventCommandExecuted = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "command.executed"
data: {
name: string
@@ -5836,12 +5684,8 @@ export type V2EventProjectUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "project.updated"
data: {
id: string
@@ -5873,12 +5717,8 @@ export type V2EventSessionStatus = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "session.status"
data: {
sessionID: string
@@ -5891,12 +5731,8 @@ export type V2EventSessionIdle = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "session.idle"
data: {
sessionID: string
@@ -5908,12 +5744,8 @@ export type V2EventQuestionAsked = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "question.asked"
data: {
id: string
@@ -5931,12 +5763,8 @@ export type V2EventQuestionReplied = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "question.replied"
data: {
sessionID: string
@@ -5950,12 +5778,8 @@ export type V2EventQuestionRejected = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "question.rejected"
data: {
sessionID: string
@@ -5968,12 +5792,8 @@ export type V2EventSessionCompacted = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "session.compacted"
data: {
sessionID: string
@@ -5985,12 +5805,8 @@ export type V2EventVcsBranchUpdated = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "vcs.branch.updated"
data: {
branch?: string
@@ -6002,12 +5818,8 @@ export type V2EventWorkspaceReady = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "workspace.ready"
data: {
name: string
@@ -6019,12 +5831,8 @@ export type V2EventWorkspaceFailed = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "workspace.failed"
data: {
message: string
@@ -6036,12 +5844,8 @@ export type V2EventWorkspaceStatus = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "workspace.status"
data: {
workspaceID: string
@@ -6054,12 +5858,8 @@ export type V2EventWorktreeReady = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "worktree.ready"
data: {
name: string
@@ -6072,12 +5872,8 @@ export type V2EventWorktreeFailed = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "worktree.failed"
data: {
message: string
@@ -6089,12 +5885,8 @@ export type V2EventServerConnected = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "server.connected"
data: {
[key: string]: unknown
@@ -6106,12 +5898,8 @@ export type V2EventGlobalDisposed = {
metadata?: {
[key: string]: unknown
}
durable?: {
aggregateID: string
seq: number
version: number
}
location?: LocationRef
durable?: never
type: "global.disposed"
data: {
[key: string]: unknown