--- a/dsh-session-persistence/lib/index.js +++ b/dsh-session-persistence/lib/index.js @@ -0,0 +0,0 @@ this.preparations.invalidate(id); } /** - * Prepare and reserve the exact unpublished Session used by resume. + * Discard `atSeq` and everything after (truncate). Flushes the live + * write-behind FIRST, then rewrites the artifact keeping only seq < atSeq + * and resets the durable cursor. + */ + async truncate(id, atSeq) { + if (!Number.isSafeInteger(atSeq) || atSeq < 0) throw new TypeError(`truncate atSeq must be a non-negative safe integer, got ${String(atSeq)}`); + const session = this.ctx.sessions.get(id); + if (session !== void 0 && this.live.has(session)) await this.flush(session); + return this.serialize(id, () => this.truncateCore(id, atSeq)); + } + + async truncateCore(id, atSeq) { + const stored = await this.backend.loadStored(id); + if (stored === void 0) throw new Error(`session "${id}" not found`); + const kept = stored.events.filter((event) => event.seq < atSeq); + if (this.backend.rewriteStored === void 0) throw new Error(`${this.backend.name} does not support durable truncate`); + await this.backend.rewriteStored(stored.meta, kept); + const session = this.ctx.sessions.get(id); + const live = session !== void 0 ? this.live.get(session) : void 0; + if (live !== void 0 && live.writes) { + live.writes.cancelAutomaticWait(); + live.writes.pending = live.writes.pending.filter((event) => event.seq < atSeq); + if (live.writes.pending.length > 0) live.writes.armTimer(); + } + const state = this.states.get(id) ?? { meta: stored.meta, cursor: 0, materialized: true }; + state.meta = structuredClone(stored.meta); + state.cursor = atSeq; + state.materialized = true; + this.states.set(id, state); + this.preparations.invalidate(id); + } +/** + * Prepare and reserve the exact unpublished Session used by resume. * Revision retries converge once the durable log remains unchanged for one * read/check round trip; continuous external writers may delay completion. * @param id - persisted session to prepare.