refactor(session): use one surface manager

This commit is contained in:
Tianyi Cui
2026-07-19 11:36:07 +08:00
parent 383a305bd4
commit 765dfb2174
20 changed files with 201 additions and 140 deletions

View File

@@ -16,13 +16,14 @@ import { SESSION_FORMAT_VERSION, SessionId } from './types.ts'
import type { ContextEnvelope, CreateSessionOptions, EpochHeader, SessionEvent, SessionEventMap, SessionEventType, SessionHeader, SurfaceIntent, SurfaceEventType } from './types.ts'
import { snapshotJsonValue } from './json.ts'
import { SurfaceManager } from './surface.ts'
import type { SessionSurface } from './surface.ts'
import { foldRequestHeader } from './request-header.ts'
export * from './types.ts'
export { isJsonValue, snapshotJsonValue } from './json.ts'
export type { JsonValue } from './json.ts'
export { interruptedTurnClosers } from './repair.ts'
export type { SurfaceFoldReplacement, SurfaceFoldResult } from './surface.ts'
export type { SessionSurface, SurfaceFoldReplacement, SurfaceFoldResult } from './surface.ts'
export { foldSurface, isSurfaceEvent, isSurfaceEligibleType } from './surface.ts'
export { canonicalHeader, foldRequestHeader, headerEquals } from './request-header.ts'
@@ -251,22 +252,12 @@ export function renderContextContent(
*/
export class Session {
private log: SessionEvent[] = []
/** Incremental acceptance state, kept separate from the public lazy view. */
private readonly surfaceValidator = new SurfaceManager(this.log)
/**
* Derived surface — a cached order of message-producing event sequences.
* Lazily rebuilt from `surfaceOp` markers in the log; processes only new
* events (delta) on each access — the log is append-only, so prior events
* never change.
* Undefined until first accessed (including after fork/seed).
*/
private _surface: SurfaceManager | undefined
/** Single incremental owner of surface acceptance and projection state. */
private readonly surfaceManager = new SurfaceManager(this.log)
/** The ordered surface over this session's event log. */
get surface(): SurfaceManager {
if (!this._surface) this._surface = new SurfaceManager(this.log)
return this._surface
get surface(): SessionSurface {
return this.surfaceManager
}
/**
@@ -309,7 +300,7 @@ export class Session {
// live append and a full-log fold. The candidate is planned before it
// enters `log`, so a failure cannot partially mutate the surface.
try {
this.surfaceValidator.validateNext(snapshot)
this.surfaceManager.validateNext(snapshot)
} catch (error: unknown) {
throw new Error(`invalid seed event at index ${index}: ${error instanceof Error ? error.message : 'invalid surface metadata'}`)
}
@@ -402,7 +393,7 @@ export class Session {
data: dataSnapshot,
...(surfaceMetadataSnapshot as { surfaceOp?: unknown; sourceEventSeqs?: unknown }),
} as unknown as SessionEvent<T>)
this.surfaceValidator.validateNext(event as SessionEvent)
this.surfaceManager.validateNext(event as SessionEvent)
if (entry !== undefined) entry.appending = true
try {
@@ -468,7 +459,7 @@ export class Session {
*
* CACHED: each surface node is projected exactly once, when first seen — a
* call costs O(new nodes), and a surface rewrite (a `replace`;
* {@link SurfaceManager.replaceGeneration}) rebuilds. The returned array is
* {@link SessionSurface.replaceGeneration}) rebuilds. The returned array is
* a fresh snapshot per call (later appends never grow an array a caller
* already holds); the `Message` objects in it are SHARED and **deep-frozen**.
* Their content reuses the already frozen durable event data, so the cache
@@ -476,8 +467,9 @@ export class Session {
* @returns a fresh array of the shared, frozen derived history.
*/
deriveMessages(): Message[] {
const nodes = this.surface.nodes
const generation = this.surface.replaceGeneration
const surface = this.surface
const nodes = surface.nodes
const generation = surface.replaceGeneration
if (generation !== this.derivedGeneration) {
this.derived = []
this.derivedNodes = 0

View File

@@ -55,6 +55,14 @@ export interface SurfaceFoldResult {
replacements: SurfaceFoldReplacement[]
}
/** Readonly live projection of the message-producing session events. */
export interface SessionSurface {
/** Current surface event sequences in model-visible order. */
readonly nodes: readonly number[]
/** Monotonic count of committed positional replacements. */
readonly replaceGeneration: number
}
/** Mutable state shared by complete and incremental folds. */
interface SurfaceFoldState {
nodes: number[]
@@ -244,7 +252,7 @@ export function foldSurface(events: readonly SessionEvent[]): SurfaceFoldResult
}
/** Incremental ordered surface view and append-boundary validator. */
export class SurfaceManager {
export class SurfaceManager implements SessionSurface {
/** Shared transition state; replacement history is not retained. */
private _state = createFoldState()
/** Last processed seq; -1 folds a seeded log on first access. */