refactor(session): narrow batch publication metadata and validation

This commit is contained in:
liukx0205 authored and Kexin Liu committed 2026-11-07 17:02:19 +08:00
1 parent e767bef144
commit 7f6b7ba4b6
6 files changed
+12 -14

No files matched your search

+2 -2
View File
@@ -584,8 +584,8 @@ declare class Session {
isOwnSeq(seq: SessionSeq): boolean;
/** The next event's sequence number — always the log length (the `seq = log.length` contiguity contract). */
get seq(): SessionLogOffset;
/** Current member and final sequence while synchronously notifying a batch's observers. */
get currentBatchPublication(): { readonly event: SessionEvent; readonly endSeq: SessionSeq } | undefined;
/** Current event while synchronously notifying a batch's observers. */
get currentBatchPublication(): SessionEvent | undefined;
/**
* Append consecutive events together after validating every candidate and dispatch.
* Observers read the complete committed log and final message surface.
+2 -2
View File
@@ -586,8 +586,8 @@ declare class Session {
isOwnSeq(seq: SessionSeq): boolean;
/** The next event's sequence number — always the log length (the `seq = log.length` contiguity contract). */
get seq(): SessionLogOffset;
/** Current member and final sequence while synchronously notifying a batch's observers. */
get currentBatchPublication(): { readonly event: SessionEvent; readonly endSeq: SessionSeq } | undefined;
/** Current event while synchronously notifying a batch's observers. */
get currentBatchPublication(): SessionEvent | undefined;
/**
* Append consecutive events together after validating every candidate and dispatch.
* Observers read the complete committed log and final message surface.
+4 -5
View File
@@ -554,7 +554,7 @@ function createAppendEvent(snapshot: ReturnType<typeof snapshotAppend>, seq: Ses
*/
export class Session {
private log: SessionEvent[] = []
private batchPublication: { readonly event: SessionEvent; readonly endSeq: SessionSeq } | undefined
private batchPublication: SessionEvent | undefined
/** Single incremental owner of surface acceptance and projection state. */
private readonly surfaceManager: SurfaceManager
@@ -792,8 +792,8 @@ export class Session {
return SessionLogOffset(this.log.length)
}
/** Current member and final sequence while synchronously notifying a batch's observers. */
get currentBatchPublication(): { readonly event: SessionEvent; readonly endSeq: SessionSeq } | undefined {
/** Current event while synchronously notifying a batch's observers. */
get currentBatchPublication(): SessionEvent | undefined {
return this.batchPublication
}
@@ -901,9 +901,8 @@ export class Session {
this.eventsSnapshot = undefined
acceptSurface()
if (entry !== undefined) {
const endSeq = SessionSeq(this.log.length - 1)
for (const { event, args, callbacks } of publications) {
this.batchPublication = { event, endSeq }
this.batchPublication = event
invokeContainedSessionObservers(entry.emitCtx, 'session/event', entry.id, args, callbacks)
}
}
+2 -3
View File
@@ -660,11 +660,10 @@ export class SurfaceManager implements SessionSurface {
this._assertProjections()
if (this._lastProcessedSeq < this.baseSeq + this.log.length - 1) this._processDelta()
const lastProcessedSeq = SessionSeq(this.baseSeq + this.log.length + events.length - 1)
if (events.every(event => event.type !== 'developer/message'
&& (event.surfaceOp === undefined || event.surfaceOp === 'append')
if (events.every(event => (event.surfaceOp === undefined || event.surfaceOp === 'append')
&& !this.projections.some(projection => projection.type === event.type))) {
const plans = events.map((event, index) => planSurfaceEvent(
this._state, event, SessionSeq(this.baseSeq + this.log.length + index), this.log, this.baseSeq, this.projections,
this._state, event, SessionSeq(this.baseSeq + this.log.length + index), this.log, this.baseSeq, this.projections, events,
))
return () => {
for (const plan of plans) applySurfacePlan(this._state, plan)
+1 -1
View File
@@ -54,7 +54,7 @@ describe('multiple Session appends', () => {
const observed: number[] = []
ctx.on('session/event', (current, event) => {
if (current !== session) return
expect(current.currentBatchPublication).toEqual({ event, endSeq: 1 })
expect(current.currentBatchPublication).toBe(event)
expect(current.seq).toBe(2)
expect(current.surface.nodes).toEqual([1])
expect(current.deriveMessages()).toEqual([messageValue])
@@ -8539,7 +8539,7 @@ export const TYPE_API: readonly TypeApiEntry[] = [
},
{
name: 'Session',
declaration: 'export class Session {\n get surface(): SessionSurface;\n readonly header: SessionHeader;\n readonly inheritedEventCount: SessionLogOffset;\n get id(): SessionId;\n readonly firstLiveSeq: SessionLogOffset;\n readonly firstLifecycleSeq: SessionLogOffset;\n static create(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader, inheritedEventCount?: SessionLogOffset, projections?: readonly SessionMessageProjection[]): Session;\n static fromRestore(id: SessionId, seed: readonly SessionEvent[], header: SessionHeader, inheritedEventCount: SessionLogOffset, eventState: SessionSeedEventState, projections?: readonly SessionMessageProjection[]): Session;\n eventAt(seq: SessionSeq): SessionEvent | undefined;\n snapshotEvents(fromSeq: SessionLogOffset = SessionLogOffset(0), toSeqExclusive: SessionLogOffset = this.seq): readonly SessionEvent[];\n ownEvents(): readonly SessionEvent[];\n isOwnSeq(seq: SessionSeq): boolean;\n get seq(): SessionLogOffset;\n get currentBatchPublication(): {\n readonly event: SessionEvent;\n readonly endSeq: SessionSeq;\n } | undefined;\n append(inputs: readonly SessionEventInput[]): readonly SessionEvent[];\n append<T extends SessionEventType>(type: T, data: SessionEventMap[T], ...opts: T extends SurfaceEventType ? [\n opts: SurfaceIntent<T>\n ] : [\n ]): SessionEvent<T>;\n requestHeader(): EpochHeader | undefined;\n requestContext(): RequestContext | undefined;\n toolHistory(): ToolHi /* …truncated — full shape in source */',
declaration: 'export class Session {\n get surface(): SessionSurface;\n readonly header: SessionHeader;\n readonly inheritedEventCount: SessionLogOffset;\n get id(): SessionId;\n readonly firstLiveSeq: SessionLogOffset;\n readonly firstLifecycleSeq: SessionLogOffset;\n static create(id: SessionId, seed?: readonly SessionEvent[], header?: SessionHeader, inheritedEventCount?: SessionLogOffset, projections?: readonly SessionMessageProjection[]): Session;\n static fromRestore(id: SessionId, seed: readonly SessionEvent[], header: SessionHeader, inheritedEventCount: SessionLogOffset, eventState: SessionSeedEventState, projections?: readonly SessionMessageProjection[]): Session;\n eventAt(seq: SessionSeq): SessionEvent | undefined;\n snapshotEvents(fromSeq: SessionLogOffset = SessionLogOffset(0), toSeqExclusive: SessionLogOffset = this.seq): readonly SessionEvent[];\n ownEvents(): readonly SessionEvent[];\n isOwnSeq(seq: SessionSeq): boolean;\n get seq(): SessionLogOffset;\n get currentBatchPublication(): SessionEvent | undefined;\n append(inputs: readonly SessionEventInput[]): readonly SessionEvent[];\n append<T extends SessionEventType>(type: T, data: SessionEventMap[T], ...opts: T extends SurfaceEventType ? [\n opts: SurfaceIntent<T>\n ] : [\n ]): SessionEvent<T>;\n requestHeader(): EpochHeader | undefined;\n requestContext(): RequestContext | undefined;\n toolHistory(): ToolHistory;\n deriveMessages(): Message[];\n deriveEventMessage(event: /* …truncated — full shape in source */',
},
{
name: 'SessionAccess',