@deepseek-ai/dsh-session-persistence 0.0.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@deepseek-ai/dsh-session-persistence`.
4
+ * @module @deepseek-ai/dsh-session-persistence/invariant
5
+ */
6
+ const PACKAGE_NAME = "@deepseek-ai/dsh-session-persistence";
7
+ /** Cordis companion plugin name. */
8
+ const name = "session-persistence-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: persistence correctness requires backend round-trip and crash-tail tests;
13
+ * this package exposes no continuously observable in-process relation.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Shared buffering, serialization, adoption, repair, and disposal orchestration
3
+ * for first-party backends. Third-party backends may implement the public
4
+ * persistence seam directly.
5
+ * @module @deepseek-ai/dsh-session-persistence/coordinator
6
+ */
7
+ import { Context } from '@deepseek-ai/cordis';
8
+ import { SessionPreparation } from '@deepseek-ai/dsh-session';
9
+ import type { SessionEvent, SessionId, SessionHeader } from '@deepseek-ai/dsh-session';
10
+ import type { SessionInspection } from './index.ts';
11
+ import type { SessionPersistenceRevision } from './revision.ts';
12
+ /** Default number of detached session preparations retained by a coordinator. */
13
+ export declare const DEFAULT_PREPARED_SESSION_CACHE_SIZE = 5;
14
+ /** Default maximum intentional wait before a live session batch starts writing. */
15
+ export declare const DEFAULT_WRITE_BATCH_MAX_DELAY_MS = 200;
16
+ /** Largest write batching delay accepted by Node's timer implementation. */
17
+ export declare const MAX_WRITE_BATCH_DELAY_MS = 2147483647;
18
+ /** Durable session contents failed validation after a successful backend read. */
19
+ export declare class SessionPersistenceCorruptionError extends Error {
20
+ /**
21
+ * @param message - stable corruption context.
22
+ * @param options - original validation failure.
23
+ */
24
+ constructor(message: string, options: ErrorOptions);
25
+ }
26
+ /** Coordinator policy supplied by a concrete persistence backend. */
27
+ export interface PersistenceCoordinatorOptions {
28
+ /** Maximum completed unpublished preparations retained for reuse. */
29
+ readonly preparedSessionCacheSize: number;
30
+ /** Maximum intentional batching wait after an idle live queue receives work. */
31
+ readonly writeBatchMaxDelayMs: number;
32
+ }
33
+ /**
34
+ * A stored session's header, valid contiguous event prefix, source-qualified
35
+ * revision, and optional opaque torn-tail marker. The revision identifies the
36
+ * exact detached prefix. The coordinator only checks marker presence and
37
+ * returns its value to {@link PersistenceBackend.commitRepair}; each backend
38
+ * owns the marker type.
39
+ */
40
+ export interface StoredPrefix<TornMarker = unknown> {
41
+ meta: SessionHeader;
42
+ events: SessionEvent[];
43
+ /** Revision observed for exactly this detached prefix. */
44
+ revision: SessionPersistenceRevision;
45
+ tornMarker?: TornMarker;
46
+ }
47
+ /**
48
+ * A stored session's header plus the events at or past a requested seq — the
49
+ * return shape of the optional seek-capable
50
+ * {@link PersistenceBackend.loadStoredFrom} hook. Non-mutating reads carry no
51
+ * torn marker: there is nothing to repair.
52
+ */
53
+ export interface StoredSuffix {
54
+ meta: SessionHeader;
55
+ events: SessionEvent[];
56
+ }
57
+ /**
58
+ * The storage contract between {@link PersistenceCoordinator} and a concrete
59
+ * backend: the minimal set of durable primitives the orchestration calls. A
60
+ * backend implements these (over files, rows, an object store, …); the
61
+ * coordinator supplies everything else (buffering, serialization, cursors,
62
+ * adoption, crash repair sequencing, dispose quiescence).
63
+ *
64
+ * @typeParam TornMarker - the backend's opaque torn-tail repair token (see
65
+ * {@link StoredPrefix}). The coordinator treats it as fully opaque.
66
+ */
67
+ export interface PersistenceBackend<TornMarker = unknown> {
68
+ /** Human-readable backend name, used in the dispose-failure AggregateError. */
69
+ readonly name: string;
70
+ /**
71
+ * Read a stored prefix by id, scanning every backend storage scope. Returns
72
+ * `undefined` if no stored artifact exists. Returned metadata must identify
73
+ * `id` before repair or state publication. Used by resume/load, live adoption,
74
+ * and — via `!== undefined` — the create-collision probe. The returned
75
+ * `tornMarker` is present iff there is a torn tail to truncate. Every header
76
+ * and event graph must be fresh, mutually unaliased, and unretained by the
77
+ * backend because preparation freezes and publishes them in place. The
78
+ * returned revision must identify exactly those values and use the same
79
+ * representation as {@link readStoredRevision}.
80
+ * @param id - persisted session id to resolve.
81
+ * @param signal - optional cancellation for backend read work.
82
+ */
83
+ loadStored(id: SessionId, signal?: AbortSignal): Promise<StoredPrefix<TornMarker> | undefined>;
84
+ /**
85
+ * Read the current source-qualified revision for one stored session without
86
+ * loading its event log. Returns `undefined` when the identity is absent.
87
+ * @param id - persisted session id to observe.
88
+ * @param signal - optional cancellation for backend read work.
89
+ */
90
+ readStoredRevision(id: SessionId, signal?: AbortSignal): Promise<SessionPersistenceRevision | undefined>;
91
+ /**
92
+ * Optional seek-capable suffix read behind the service's `readFrom`: return
93
+ * the header plus the stored events with `seq >= fromSeq` without reading
94
+ * the whole log. A backend whose medium can address events by seq (SQLite)
95
+ * implements this so `readFrom` scales with the suffix; sequential backends
96
+ * omit it and the coordinator falls back to {@link loadStored} plus a
97
+ * forward skip. Non-mutating (no truncation, no closers). Validation of the
98
+ * region strictly below `fromSeq` is limited to seq contiguity — the
99
+ * service contract scopes this read to the suffix — unless that suffix
100
+ * contains a supported legacy shape whose normalization needs earlier
101
+ * message-identity facts, in which case the coordinator falls back
102
+ * to the complete stored prefix.
103
+ * @param id - persisted session id to resolve.
104
+ * @param fromSeq - first event seq to include (non-negative safe integer,
105
+ * validated by the coordinator before this hook runs).
106
+ * @param signal - optional cancellation for backend read work.
107
+ */
108
+ loadStoredFrom?(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<StoredSuffix | undefined>;
109
+ /**
110
+ * Durably append a CONTIGUOUS batch, lazily materializing the session first
111
+ * when `!isMaterialized`. The materialize-write and the first event batch MUST
112
+ * commit ATOMICALLY (a crash between them must not leave a materialized-but-
113
+ * empty session). Returns once the batch is durable.
114
+ */
115
+ appendBatch(meta: SessionHeader, events: readonly SessionEvent[], isMaterialized: boolean): Promise<void>;
116
+ /**
117
+ * Make a crash repair durable: truncate the torn tail (iff
118
+ * `tornMarker !== undefined`) and append `closers` (iff any). NOT required to
119
+ * be atomic — a file backend may truncate-then-append in two fsync'd steps.
120
+ * Used by load (truncate + synthetic closers) and by live-adoption (truncate
121
+ * only, `closers = []`).
122
+ */
123
+ commitRepair(meta: SessionHeader, tornMarker: TornMarker | undefined, closers: readonly SessionEvent[]): Promise<void>;
124
+ /**
125
+ * List all stored (materialized) sessions' metadata.
126
+ * @param signal - optional cancellation for backend listing work.
127
+ */
128
+ list(signal?: AbortSignal): Promise<SessionHeader[]>;
129
+ /**
130
+ * Optional lifecycle teardown (e.g. close a database handle). Awaited by the
131
+ * coordinator's dispose effect AFTER the quiescence drain. A stateless file
132
+ * backend omits it.
133
+ */
134
+ close?(): Promise<void>;
135
+ }
136
+ /**
137
+ * Owns the backend-agnostic session write-path orchestration. A backend
138
+ * constructs one (`new PersistenceCoordinator(ctx, this)`), implements
139
+ * {@link PersistenceBackend}, and delegates its write/read service methods to
140
+ * the matching coordinator methods.
141
+ *
142
+ * All per-id operations are serialized (a per-id promise chain) so concurrent
143
+ * flushes / a flush racing a load never interleave storage writes. The
144
+ * constructor installs the write-path listeners, per-session retirement, and
145
+ * the backend dispose effect.
146
+ *
147
+ * @typeParam TornMarker - the backend's opaque torn-tail repair token.
148
+ */
149
+ export declare class PersistenceCoordinator<TornMarker = unknown> {
150
+ private ctx;
151
+ private backend;
152
+ /** Backend bookkeeping keyed by session id (NOT the live Session object). */
153
+ private states;
154
+ /** Lifecycle and write-behind state keyed by the exact live Session. */
155
+ private live;
156
+ /** Exact disposed lifecycles whose buffered tail is still draining. */
157
+ private retirements;
158
+ /** Shared cold reads, unpublished reservations, and completed LRU entries. */
159
+ private readonly preparations;
160
+ /**
161
+ * Per-session serialization: every operation chains onto the prior one for the
162
+ * same id, so writes for one session never interleave. Keyed by session id.
163
+ */
164
+ private chains;
165
+ /** Resolved fixed write-batching window shared by per-session controllers. */
166
+ private readonly writeBatchMaxDelayMs;
167
+ constructor(ctx: Context, backend: PersistenceBackend<TornMarker>, options?: PersistenceCoordinatorOptions);
168
+ /**
169
+ * Register detached session metadata for lazy creation on the first append.
170
+ * @param meta - header to snapshot; duplicate tracked or persisted ids reject.
171
+ */
172
+ create(meta: SessionHeader): Promise<void>;
173
+ private createCore;
174
+ /**
175
+ * Durably persist a batch of events. Honors the append-only and contiguous-seq
176
+ * contracts; rejects non-JSON-serializable `event.data`.
177
+ * @param id - the session the batch belongs to.
178
+ * @param events - the contiguous batch to persist, in seq order; materialized
179
+ * as a detached lossless-JSON snapshot at call time.
180
+ */
181
+ append(id: SessionId, events: readonly SessionEvent[]): Promise<void>;
182
+ private appendCore;
183
+ /**
184
+ * Prepare and reserve the exact unpublished Session used by resume.
185
+ * Revision retries converge once the durable log remains unchanged for one
186
+ * read/check round trip; continuous external writers may delay completion.
187
+ * @param id - persisted session to prepare.
188
+ * @param signal - optional cancellation for reading and repair.
189
+ * @returns an owned preparation released after publication or rollback.
190
+ */
191
+ prepare(id: SessionId, signal?: AbortSignal): Promise<SessionPreparation>;
192
+ /**
193
+ * Commit recovery and return its immutable logical view without publication.
194
+ * Revision retries converge once the durable log remains unchanged for one
195
+ * read/check round trip; continuous external writers may delay completion.
196
+ * @param id - persisted session to load.
197
+ * @returns prepared header and balanced events.
198
+ */
199
+ load(id: SessionId): Promise<SessionInspection>;
200
+ /**
201
+ * Inspect a logical session without publishing it or committing recovery.
202
+ * A stale ready source is reloaded. A source already committing or reserved
203
+ * for resume remains exclusive, and inspection may borrow its immutable view.
204
+ * Revision retries converge once the log is stable for one read/check round
205
+ * trip; continuous external writers may delay completion.
206
+ * @param id - persisted session to inspect.
207
+ * @param signal - optional cancellation for preparation work.
208
+ * @returns immutable prepared metadata and events; a live view may have an open turn.
209
+ */
210
+ inspect(id: SessionId, signal?: AbortSignal): Promise<SessionInspection>;
211
+ /**
212
+ * Read the stored events from `fromSeq` onward, detached and non-mutating
213
+ * (the read-from-seq primitive behind the service's `readFrom`). Runs on
214
+ * the same per-id chain as writes; a backend with the seek-capable
215
+ * {@link PersistenceBackend.loadStoredFrom} hook reads only the suffix,
216
+ * every other backend reads its stored prefix and skips forward here.
217
+ * @param id - persisted session to read.
218
+ * @param fromSeq - first event seq to include; a non-negative safe integer.
219
+ * @param signal - optional cancellation for queued and backend read work.
220
+ * @returns stored header and the valid stored events with `seq >= fromSeq`.
221
+ */
222
+ readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{
223
+ meta: SessionHeader;
224
+ events: SessionEvent[];
225
+ }>;
226
+ private readFromCore;
227
+ /** Read one detached physical prefix without logical recovery or caching. */
228
+ private readStoredPrefix;
229
+ /** Read, repair in memory, validate, and freeze one cold source once. */
230
+ private prepareCore;
231
+ /** Commit one prepared repair and establish its ownerless durable cursor. */
232
+ private commitPrepared;
233
+ /** Whether one cached source still names the current durable log revision. */
234
+ private isPreparedSourceCurrent;
235
+ /** Return one durable immutable view of an already-live Session. */
236
+ private loadLiveSnapshot;
237
+ /** Borrow one immutable view from an already-live Session. */
238
+ private inspectLive;
239
+ /** Await one retiring lifecycle with caller cancellation. */
240
+ private waitForRetirement;
241
+ /**
242
+ * Run `op` after any in-flight operation for the same session id, so writes for
243
+ * one session never interleave. Errors do not poison the chain. NOTE: serialized
244
+ * public methods must NOT call each other (deadlock); they call the unserialized
245
+ * `*Core` helpers instead.
246
+ */
247
+ private serialize;
248
+ /** Build a state for a session discovered in storage but not yet in memory. */
249
+ private adopt;
250
+ private assertVersion;
251
+ /** Reject backend metadata that is not bound to the requested session id. */
252
+ private assertStoredId;
253
+ private installWritePath;
254
+ /** Start and observe one disposed session's final drain. */
255
+ private retire;
256
+ /** Drain and release state owned by one exact disposed Session lifecycle. */
257
+ private retireCore;
258
+ /** Return the one lifecycle controller for a live session, creating it if needed. */
259
+ private initFor;
260
+ /** Bind one exact prepared Session and persist only its unpublished suffix. */
261
+ private attachPrepared;
262
+ /**
263
+ * Whether a live session's `seed` reproduces the first `cursor` persisted
264
+ * events. A `cursor` of 0 (nothing persisted yet) trivially matches. Used when
265
+ * a live session claims ownerless state left by a prior `load()`/`create()`.
266
+ */
267
+ private seedMatchesPersisted;
268
+ /**
269
+ * On session/created: sync the backend's in-memory state to a live Session.
270
+ *
271
+ * Cases, by whether this backend tracks the id and whether an artifact exists:
272
+ * 1. Already tracked → no-op (or claim ownerless state if the seed matches,
273
+ * or reclaim a truly-abandoned id, else reject as a collision).
274
+ * 2. Not tracked, an artifact EXISTS at the same cwd and is a seq-aligned
275
+ * PREFIX of the live events → ADOPT it, persisting any live suffix.
276
+ * 3. Not tracked, an artifact EXISTS at another cwd or is NOT a prefix →
277
+ * REJECT (collision).
278
+ * 4. Not tracked and NO artifact → a genuinely new session: register meta
279
+ * (lazy) and persist its seed once.
280
+ */
281
+ private onCreated;
282
+ /**
283
+ * Adopt a stored prefix as a live session's history (HMR/reload): verify the
284
+ * seed covers the stored prefix, truncate any torn tail (NOT the open turn —
285
+ * the live Session is still the authority), bind ownership, and persist the
286
+ * live suffix that was ahead of the stored prefix.
287
+ */
288
+ private adoptLivePrefix;
289
+ private flush;
290
+ /** Build one package-private write controller around initialization and id serialization. */
291
+ private createWriteBehind;
292
+ /** Append one controller-owned prefix after filtering events initialization already stored. */
293
+ private appendLiveBatch;
294
+ }
295
+ //# sourceMappingURL=coordinator.d.ts.map
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Durable session-persistence Service Definition (`ctx.sessionPersistence`). Backends store
3
+ * {@link SessionEvent}s as the event-sourced log and carry non-replayable
4
+ * {@link SessionHeader} metadata separately.
5
+ * @module @deepseek-ai/dsh-session-persistence
6
+ */
7
+ import { Context, Service } from '@deepseek-ai/cordis';
8
+ import { SessionPreparation } from '@deepseek-ai/dsh-session';
9
+ import type { SessionEvent, SessionId, SessionHeader } from '@deepseek-ai/dsh-session';
10
+ import type { SessionPersistenceRevision } from './revision.ts';
11
+ export type { SessionHeader } from '@deepseek-ai/dsh-session';
12
+ export { SessionPersistenceRevision } from './revision.ts';
13
+ /** Lightweight immutable source identity returned without loading a full log. */
14
+ export interface SessionPersistenceSnapshot {
15
+ /** Detached metadata for one materialized session. */
16
+ header: SessionHeader;
17
+ /** Opaque source-qualified token that changes whenever this stored log changes. */
18
+ revision: SessionPersistenceRevision;
19
+ }
20
+ /** Immutable logical session prepared from persistence or a live owner. */
21
+ export interface SessionInspection {
22
+ /** Validated immutable session metadata. */
23
+ readonly meta: SessionHeader;
24
+ /** Validated contiguous logical event log. */
25
+ readonly events: readonly SessionEvent[];
26
+ }
27
+ export { DEFAULT_PREPARED_SESSION_CACHE_SIZE, DEFAULT_WRITE_BATCH_MAX_DELAY_MS, MAX_WRITE_BATCH_DELAY_MS, PersistenceCoordinator, SessionPersistenceCorruptionError, } from './coordinator.ts';
28
+ export type { PersistenceBackend, PersistenceCoordinatorOptions, StoredPrefix, StoredSuffix, } from './coordinator.ts';
29
+ declare module '@deepseek-ai/cordis' {
30
+ interface Context {
31
+ sessionPersistence: SessionPersistence;
32
+ }
33
+ }
34
+ /**
35
+ * A backend-resolved, per-session local artifact location. The path is an
36
+ * absolute target path and can name an artifact that has not materialized yet.
37
+ * Consumers must treat it as a location hint, never as an authorization token.
38
+ */
39
+ export interface SessionLocation {
40
+ /** Backend-specific artifact kind, for example `jsonl`. */
41
+ readonly kind: string;
42
+ /** Absolute path to this session's backend-owned artifact. */
43
+ readonly path: string;
44
+ }
45
+ /**
46
+ * Durable append-only session storage. Implementations preserve contiguous,
47
+ * losslessly JSON-serializable events; {@link append} resolves only after
48
+ * durability, and {@link load} balances a complete interrupted tail without
49
+ * rewriting committed events.
50
+ */
51
+ export declare abstract class SessionPersistence extends Service {
52
+ constructor(ctx: Context);
53
+ /**
54
+ * Resolve this backend's independent local artifact for a session without
55
+ * reading, creating, flushing, or otherwise materializing it. Backends such
56
+ * as SQLite that do not own one artifact per session return `undefined`.
57
+ * @param meta - the immutable session header whose artifact is requested.
58
+ * @returns the backend-specific absolute location, when one exists.
59
+ */
60
+ abstract locate(meta: SessionHeader): SessionLocation | undefined;
61
+ /**
62
+ * Register a new session's metadata. A backend MAY defer the physical write
63
+ * until the first {@link append} (lazy materialization), in which case a
64
+ * created-but-never-appended session is absent from {@link list}
65
+ * — abandoned sessions leave nothing behind.
66
+ * @param meta - the immutable header (id, version, cwd, lineage) to record.
67
+ */
68
+ abstract create(meta: SessionHeader): Promise<void>;
69
+ /**
70
+ * Durably persist a batch of events. Honors the append-only and contiguous-
71
+ * seq contracts: the first event's `seq` MUST equal the stored next-seq
72
+ * (after `load` has durably closed any interrupted turn). Rejects non-JSON-
73
+ * serializable `event.data` with an error naming the offending event type.
74
+ * @param id - the session the batch belongs to.
75
+ * @param events - the contiguous batch to persist, in seq order.
76
+ */
77
+ abstract append(id: SessionId, events: readonly SessionEvent[]): Promise<void>;
78
+ /**
79
+ * Prepare the exact unpublished Session used by resume. Implementations may
80
+ * reuse object graphs retained by an earlier {@link inspect} after confirming
81
+ * their durable revision is still current; disposal releases an unpublished
82
+ * reservation. Revision retries require the durable log to remain unchanged
83
+ * for one read/check round trip; continuous external writers may delay completion.
84
+ * @param id - persisted session to prepare.
85
+ * @param signal - optional cancellation for preparation work.
86
+ * @returns one owned unpublished Session preparation.
87
+ */
88
+ prepare(id: SessionId, signal?: AbortSignal): Promise<SessionPreparation>;
89
+ /**
90
+ * Load an immutable balanced logical view and commit any required cold
91
+ * recovery. A complete interrupted final turn is preserved and durably
92
+ * closed with missing tool errors plus any open step and turn boundaries;
93
+ * only a torn final record is discarded. Unknown versions and corruption in
94
+ * the committed prefix reject. Implementations MUST NOT crash-repair an
95
+ * identity still bound to a live Session: a balanced live log may return as a
96
+ * durable snapshot, while an open live turn rejects. Returned values may be
97
+ * shared with immutable live or prepared state and must not be mutated.
98
+ * Revision-based implementations may wait for one stable read/check round trip.
99
+ * @param id - the persisted session to reload.
100
+ * @returns the header and a log ending on a balanced `turn/end`.
101
+ */
102
+ abstract load(id: SessionId): Promise<SessionInspection>;
103
+ /**
104
+ * Inspect an immutable logical session without committing recovery or
105
+ * publishing it. A cold complete interrupted turn receives synthetic closers
106
+ * in memory and a torn physical tail remains untouched. An already-live
107
+ * Session instead yields its current immutable snapshot, which may contain an
108
+ * open turn and its `session/end-seed` boundary. Coordinator-backed
109
+ * implementations retain the exact cold unpublished Session for bounded
110
+ * reuse by a later {@link prepare}. A stale ready source is reloaded; a source
111
+ * already committing or reserved for resume remains exclusive, and inspection
112
+ * may borrow its immutable view. Callers borrow only the immutable header and
113
+ * log. Continuous external writers may delay revision convergence.
114
+ * @param id - the persisted session to inspect.
115
+ * @param signal - optional cancellation for queued and backend read work.
116
+ * @returns the validated header and current logical event log.
117
+ */
118
+ abstract inspect(id: SessionId, signal?: AbortSignal): Promise<SessionInspection>;
119
+ /**
120
+ * Read the stored events from `fromSeq` onward — the read-from-seq
121
+ * primitive for read models that resume from a watermark (e.g. a persisted
122
+ * projection cache folding only the tail past its checkpoint). Unlike
123
+ * {@link inspect}, it is a detached physical suffix read: no preparation
124
+ * cache, torn-tail truncation, synthetic closers, or coordinator-state
125
+ * publication. Only events from the valid contiguous stored prefix are
126
+ * returned, so a torn fragment never reaches the caller. `fromSeq` at or
127
+ * beyond the stored prefix returns an empty event list (never an error).
128
+ * Backends whose medium can seek by seq
129
+ * (SQLite) read only the suffix; sequential media (JSONL, both encodings)
130
+ * still parse the whole artifact and skip forward — the primitive bounds
131
+ * what is RETURNED and refolded, not every backend's physical read.
132
+ * @param id - the persisted session to read.
133
+ * @param fromSeq - first event seq to include; a non-negative safe integer.
134
+ * @param signal - optional cancellation for queued and backend read work.
135
+ * @returns the header and the stored events with `seq >= fromSeq`.
136
+ */
137
+ abstract readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{
138
+ meta: SessionHeader;
139
+ events: SessionEvent[];
140
+ }>;
141
+ /**
142
+ * Lightweight listing from metadata, without a full-log parse.
143
+ * @param signal - optional cancellation for backend listing work.
144
+ * @returns one header per materialized session.
145
+ */
146
+ abstract list(signal?: AbortSignal): Promise<SessionHeader[]>;
147
+ /**
148
+ * List materialized sessions with cheap per-log change tokens.
149
+ *
150
+ * Repeated observations of an unchanged log return the same revision. A
151
+ * successful mutating {@link load} repair changes the next listed revision.
152
+ * Revisions also distinguish independently backed stores so backend-local
153
+ * counters cannot compare equal across different persistence sources.
154
+ * @param signal - optional cancellation for backend snapshot-listing work.
155
+ * @returns one header and opaque revision per materialized session without loading full logs.
156
+ */
157
+ abstract listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]>;
158
+ }
159
+ export default SessionPersistence;
160
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@deepseek-ai/dsh-session-persistence`.
3
+ * @module @deepseek-ai/dsh-session-persistence/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "session-persistence-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Bounded sharing and exclusive reservation of unpublished Sessions.
3
+ * @module @deepseek-ai/dsh-session-persistence/preparations
4
+ */
5
+ import type { Session, SessionId } from '@deepseek-ai/dsh-session';
6
+ interface PreparedSource {
7
+ readonly session: Session;
8
+ }
9
+ type PreparationPhase = 'loading' | 'ready' | 'committing' | 'reserved';
10
+ interface PreparationEntry<Source, CommitState> {
11
+ readonly id: SessionId;
12
+ readonly result: Promise<Source>;
13
+ phase: PreparationPhase;
14
+ source?: Source;
15
+ reservation?: SessionPreparationReservation<Source, CommitState>;
16
+ reservationSettled?: Promise<void>;
17
+ settleReservation?: () => void;
18
+ }
19
+ /** One exclusively held prepared source and its committed persistence state. */
20
+ export interface SessionPreparationReservation<Source, CommitState> {
21
+ readonly entry: PreparationEntry<Source, CommitState>;
22
+ readonly source: Source;
23
+ readonly state: CommitState;
24
+ }
25
+ /** Per-coordinator cold-read sharing, exclusive reservation, and ready-entry LRU. */
26
+ export declare class SessionPreparations<Source extends PreparedSource, CommitState> {
27
+ private readonly capacity;
28
+ private readonly entries;
29
+ constructor(capacity: number);
30
+ /**
31
+ * Whether this pool currently knows about an unpublished identity.
32
+ * @param id - session identity.
33
+ * @returns whether an entry exists for the identity.
34
+ */
35
+ has(id: SessionId): boolean;
36
+ /**
37
+ * Observe one prepared source, sharing an in-flight read for the same id.
38
+ * @param id - session identity.
39
+ * @param load - cold loader used when no entry exists.
40
+ * @param signal - optional cancellation signal while waiting.
41
+ * @returns the shared prepared source.
42
+ */
43
+ inspect(id: SessionId, load: () => Promise<Source>, signal?: AbortSignal): Promise<Source>;
44
+ /**
45
+ * Reserve one ready source after committing its pending durable repair.
46
+ * @param id - session identity.
47
+ * @param load - cold loader used when no entry exists.
48
+ * @param commit - durable repair and cursor-state commit.
49
+ * @param signal - optional cancellation signal while waiting.
50
+ * @returns the exclusive reservation, or undefined if its entry was invalidated.
51
+ */
52
+ reserve(id: SessionId, load: () => Promise<Source>, commit: (source: Source) => Promise<{
53
+ source: Source;
54
+ state: CommitState;
55
+ } | undefined>, signal?: AbortSignal): Promise<SessionPreparationReservation<Source, CommitState> | undefined>;
56
+ /**
57
+ * Return the exact reservation for Session publication, rejecting aliases.
58
+ * @param session - exact Session candidate for publication.
59
+ * @returns its reservation, or undefined when no preparation exists.
60
+ */
61
+ reservationFor(session: Session): SessionPreparationReservation<Source, CommitState> | undefined;
62
+ /**
63
+ * Consume a reservation after its exact Session has attached.
64
+ * @param reservation - reservation to consume.
65
+ */
66
+ attach(reservation: SessionPreparationReservation<Source, CommitState>): void;
67
+ /**
68
+ * Consume a reservation whose caller only needs the committed inspection.
69
+ * @param reservation - reservation to consume.
70
+ */
71
+ discard(reservation: SessionPreparationReservation<Source, CommitState>): void;
72
+ /**
73
+ * Return a reusable unpublished reservation to the ready LRU.
74
+ * @param reservation - reservation to release.
75
+ * @param reusable - whether the source remains valid for reuse.
76
+ */
77
+ release(reservation: SessionPreparationReservation<Source, CommitState>, reusable: boolean): void;
78
+ /**
79
+ * Discard a prepared view after the durable log changes.
80
+ * @param id - changed session identity.
81
+ */
82
+ invalidate(id: SessionId): void;
83
+ /**
84
+ * Discard an exact stale ready source without disturbing an exclusive owner.
85
+ * @param id - changed session identity.
86
+ * @param expected - exact source observed before its revision check.
87
+ * @returns whether the source was discarded, retained by a reservation, or is absent.
88
+ */
89
+ discardReady(id: SessionId, expected: Source): 'discarded' | 'retained' | 'missing';
90
+ /**
91
+ * Reject writes while an unpublished Session exclusively reserves the id.
92
+ * @param id - session identity to check.
93
+ */
94
+ assertWritable(id: SessionId): void;
95
+ /**
96
+ * Remove a completed entry for an already-serialized append adoption.
97
+ * @param id - adopted session identity.
98
+ * @returns the prepared source, or undefined when no ready entry exists.
99
+ */
100
+ takeReady(id: SessionId): Source | undefined;
101
+ private entryFor;
102
+ private makeReady;
103
+ private remove;
104
+ private touch;
105
+ }
106
+ /**
107
+ * Give a queued observer a prompt cancellation view without cancelling shared work.
108
+ * @param operation - shared operation whose settlement remains authoritative.
109
+ * @param signal - observer-local cancellation signal.
110
+ * @param started - whether the operation has crossed its cancellation cutoff.
111
+ * @returns the operation result or the observer's prompt cancellation.
112
+ */
113
+ export declare function observeQueuedAbort<T>(operation: Promise<T>, signal: AbortSignal, started?: () => boolean): Promise<T>;
114
+ export {};
115
+ //# sourceMappingURL=preparations.d.ts.map
@@ -0,0 +1,14 @@
1
+ /** Opaque revision identity for lightweight persistence observations. */
2
+ import type { Branded } from '@deepseek-ai/dsh-brand';
3
+ /**
4
+ * Backend-owned token that identifies both one storage source and one revision
5
+ * of a persisted session log.
6
+ */
7
+ export type SessionPersistenceRevision = Branded<'SessionPersistenceRevision'>;
8
+ /**
9
+ * Brand a backend revision for the provider-neutral persistence contract.
10
+ * @param value - backend-owned opaque revision representation.
11
+ * @returns the same runtime string with persistence-revision identity.
12
+ */
13
+ export declare function SessionPersistenceRevision(value: string): SessionPersistenceRevision;
14
+ //# sourceMappingURL=revision.d.ts.map