@deepseek-ai/dsh-session-persistence 0.1.2-rc.1 → 0.1.3-alpha.2
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.
- package/README.i18n.yaml +2 -2
- package/README.md +45 -28
- package/README.zh.md +46 -29
- package/lib/index.js +167 -1494
- package/lib/types/errors.d.ts +98 -2
- package/lib/types/handle.d.ts +107 -0
- package/lib/types/index.d.ts +114 -191
- package/lib/types/storage-contract.d.ts +62 -0
- package/package.json +9 -9
- package/lib/types/coordinator.d.ts +0 -361
- package/lib/types/preparations.d.ts +0 -129
- package/lib/types/write-behind.d.ts +0 -62
package/lib/types/errors.d.ts
CHANGED
|
@@ -1,9 +1,105 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Stable failures exposed by the session-persistence service and its handles,
|
|
3
|
+
* including the format refusals shared by every backend: a stored log this
|
|
4
|
+
* build cannot faithfully interpret is refused, never misread, and the
|
|
5
|
+
* refusal points at the raw artifact when the backend keeps one per session.
|
|
6
|
+
* @module @deepseek-ai/dsh-session-persistence/errors
|
|
7
|
+
*/
|
|
2
8
|
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
3
|
-
/** The requested Session identity has no
|
|
9
|
+
/** The requested Session identity has no durable log visible to this caller. */
|
|
4
10
|
export declare class SessionPersistenceNotFoundError extends Error {
|
|
5
11
|
readonly sessionId: SessionId;
|
|
6
12
|
/** @param sessionId - absent durable Session identity. */
|
|
7
13
|
constructor(sessionId: SessionId);
|
|
8
14
|
}
|
|
15
|
+
/** `create` targeted a Session identity that already exists in this backend. */
|
|
16
|
+
export declare class SessionAlreadyExistsError extends Error {
|
|
17
|
+
readonly sessionId: SessionId;
|
|
18
|
+
/** @param sessionId - the occupied durable Session identity. */
|
|
19
|
+
constructor(sessionId: SessionId);
|
|
20
|
+
}
|
|
21
|
+
/** A write open found the session already bound to an active write handle. */
|
|
22
|
+
export declare class SessionAlreadyOwnedError extends Error {
|
|
23
|
+
readonly sessionId: SessionId;
|
|
24
|
+
/** @param sessionId - the session whose write ownership is taken. */
|
|
25
|
+
constructor(sessionId: SessionId);
|
|
26
|
+
}
|
|
27
|
+
/** A mutation (`append`/`flush`) was called on a read handle. */
|
|
28
|
+
export declare class SessionReadOnlyError extends Error {
|
|
29
|
+
readonly sessionId: SessionId;
|
|
30
|
+
/**
|
|
31
|
+
* @param sessionId - the session the read handle observes.
|
|
32
|
+
* @param operation - the refused mutating operation name.
|
|
33
|
+
*/
|
|
34
|
+
constructor(sessionId: SessionId, operation: string);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A write handle's ownership is permanently gone: its lease expired, a renewal
|
|
38
|
+
* failed, or the durable ownership record no longer names this handle. The
|
|
39
|
+
* handle never re-acquires ownership — close it and reopen for write.
|
|
40
|
+
*
|
|
41
|
+
* Declared for the cross-process lease layer; the shipped in-process backends
|
|
42
|
+
* never throw it yet.
|
|
43
|
+
*/
|
|
44
|
+
export declare class SessionOwnershipLostError extends Error {
|
|
45
|
+
readonly sessionId: SessionId;
|
|
46
|
+
/** @param sessionId - the session whose write ownership this handle lost. */
|
|
47
|
+
constructor(sessionId: SessionId);
|
|
48
|
+
}
|
|
49
|
+
/** An operation was called on a handle after `close()` was called. */
|
|
50
|
+
export declare class SessionHandleClosedError extends Error {
|
|
51
|
+
readonly sessionId: SessionId;
|
|
52
|
+
/**
|
|
53
|
+
* @param sessionId - the session the closed handle addressed.
|
|
54
|
+
* @param operation - the refused operation name.
|
|
55
|
+
*/
|
|
56
|
+
constructor(sessionId: SessionId, operation: string);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A backend-resolved, per-session local artifact location. Carried only by
|
|
60
|
+
* refusal diagnostics ({@link SessionFormatUnsupportedError}) so a user can
|
|
61
|
+
* find the raw log a build refused to interpret; it is not a consumer-facing
|
|
62
|
+
* query — log access goes through a session handle's `read`.
|
|
63
|
+
*/
|
|
64
|
+
export interface SessionLocation {
|
|
65
|
+
/** Backend-specific artifact kind, for example `jsonl`. */
|
|
66
|
+
readonly kind: string;
|
|
67
|
+
/** Absolute path to this session's backend-owned artifact. */
|
|
68
|
+
readonly path: string;
|
|
69
|
+
}
|
|
70
|
+
/** Durable session contents failed validation after a successful backend read. */
|
|
71
|
+
export declare class SessionPersistenceCorruptionError extends Error {
|
|
72
|
+
/**
|
|
73
|
+
* @param message - stable corruption context.
|
|
74
|
+
* @param options - original validation failure.
|
|
75
|
+
*/
|
|
76
|
+
constructor(message: string, options: ErrorOptions);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The stored log is intact but this runtime cannot faithfully interpret it:
|
|
80
|
+
* the header carries an unsupported format version, or an event's type is
|
|
81
|
+
* unknown to this build. Distinct from {@link SessionPersistenceCorruptionError}
|
|
82
|
+
* — nothing is damaged; the raw log remains readable at {@link location} when
|
|
83
|
+
* the backend keeps one artifact per session.
|
|
84
|
+
*/
|
|
85
|
+
export declare class SessionFormatUnsupportedError extends Error {
|
|
86
|
+
readonly location?: SessionLocation | undefined;
|
|
87
|
+
/**
|
|
88
|
+
* @param message - stable reason the log cannot be interpreted, already
|
|
89
|
+
* including the raw-log path when one exists.
|
|
90
|
+
* @param location - the backend's artifact location, when one exists.
|
|
91
|
+
*/
|
|
92
|
+
constructor(message: string, location?: SessionLocation | undefined);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Direction-aware refusal text for a stored session whose format version this
|
|
96
|
+
* build does not read. Shared by load-time checks and by backends that must
|
|
97
|
+
* refuse BEFORE decoding version-dependent structure (a future format may not
|
|
98
|
+
* satisfy this build's structural checks at all, and the user must see
|
|
99
|
+
* "upgrade the harness", never "corrupt").
|
|
100
|
+
* @param id - the stored session id, for message context.
|
|
101
|
+
* @param version - the stored format version.
|
|
102
|
+
* @returns the stable refusal text, without a raw-log path suffix.
|
|
103
|
+
*/
|
|
104
|
+
export declare function sessionFormatVersionRefusal(id: string, version: number): string;
|
|
9
105
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-session storage handle: one open channel onto a stored session's
|
|
3
|
+
* append-only event log, returned by `SessionPersistence.create`/`open`.
|
|
4
|
+
* @module @deepseek-ai/dsh-session-persistence/handle
|
|
5
|
+
*/
|
|
6
|
+
import type { SessionEvent, SessionHeader, SessionId, SessionLogOffset, SessionSeedEventState } from '@deepseek-ai/dsh-session';
|
|
7
|
+
/**
|
|
8
|
+
* Log access granted by an open. `write` is read-write: the session's single
|
|
9
|
+
* mutator, which also reads its own log. `read` only observes — it never
|
|
10
|
+
* takes ownership and works while another handle or process holds `write`.
|
|
11
|
+
*/
|
|
12
|
+
export type SessionAccess = 'read' | 'write';
|
|
13
|
+
/** Options for {@link SessionHandle.read}. */
|
|
14
|
+
export interface SessionHandleReadOptions {
|
|
15
|
+
/** Optional cancellation for backend read work. */
|
|
16
|
+
readonly signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
/** One persistence event slice returned by {@link SessionHandle.read}. */
|
|
19
|
+
export interface SessionHandleReadResult {
|
|
20
|
+
/**
|
|
21
|
+
* Whether event values are exclusively owned or shared only after deep
|
|
22
|
+
* freezing. Slicing preserves the producer's state even when no events remain.
|
|
23
|
+
*/
|
|
24
|
+
readonly eventState: SessionSeedEventState;
|
|
25
|
+
/** Event values in a caller-owned outer array. */
|
|
26
|
+
readonly events: readonly SessionEvent[];
|
|
27
|
+
}
|
|
28
|
+
/** Options for {@link SessionHandle.append}. */
|
|
29
|
+
export interface SessionHandleAppendOptions {
|
|
30
|
+
/** Optional cancellation observed before the write starts. */
|
|
31
|
+
readonly signal?: AbortSignal;
|
|
32
|
+
}
|
|
33
|
+
/** Options for {@link SessionHandle.flush}. */
|
|
34
|
+
export interface SessionHandleFlushOptions {
|
|
35
|
+
/** Optional cancellation observed before the barrier starts. */
|
|
36
|
+
readonly signal?: AbortSignal;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One open channel onto a stored session. A handle is single-owner state, not
|
|
40
|
+
* a shared service: `read` never backtracks below what this handle already
|
|
41
|
+
* observed, a `write` handle reads its own successful appends, and `close()`
|
|
42
|
+
* is the one teardown (idempotent, uncancellable; `Symbol.asyncDispose`
|
|
43
|
+
* delegates to it). Every operation on a closed handle rejects with
|
|
44
|
+
* `SessionHandleClosedError`.
|
|
45
|
+
*
|
|
46
|
+
* Freshness across handles: once an `append` or `flush` resolves on a write
|
|
47
|
+
* handle, every read STARTED afterwards on the same backend instance — on any
|
|
48
|
+
* handle, or through `stat`/`list` — observes at least that prefix.
|
|
49
|
+
* Reads concurrent with a mutation carry no ordering promise beyond the valid
|
|
50
|
+
* contiguous prefix.
|
|
51
|
+
*/
|
|
52
|
+
export interface SessionHandle extends AsyncDisposable {
|
|
53
|
+
/** The stored session this handle addresses. */
|
|
54
|
+
readonly id: SessionId;
|
|
55
|
+
/** The immutable stored header, fixed at `create`/`open`. */
|
|
56
|
+
readonly header: SessionHeader;
|
|
57
|
+
/**
|
|
58
|
+
* Exact fork-inherited prefix length stored with the log; `0` when
|
|
59
|
+
* `header.isSeeded` is false. Storage metadata paired with the header for
|
|
60
|
+
* every body read, never part of the replayable event log.
|
|
61
|
+
*/
|
|
62
|
+
readonly inheritedEventCount: SessionLogOffset;
|
|
63
|
+
/** Whether this handle may mutate the log. */
|
|
64
|
+
readonly access: SessionAccess;
|
|
65
|
+
/**
|
|
66
|
+
* Read a slice of the valid contiguous logical log. The slice is a legal log
|
|
67
|
+
* prefix segment: a torn physical tail is never returned, and repeated reads
|
|
68
|
+
* on this handle never observe an older state than a prior read.
|
|
69
|
+
* @param offset - first logical event seq to include; defaults to `0`.
|
|
70
|
+
* @param length - maximum number of events to return; defaults to the rest
|
|
71
|
+
* of the log. An offset at or past the end returns an empty list.
|
|
72
|
+
* @param options - optional cancellation.
|
|
73
|
+
* @returns the caller-owned outer slice plus the ownership state of its event values.
|
|
74
|
+
*/
|
|
75
|
+
read(offset?: number, length?: number, options?: SessionHandleReadOptions): Promise<SessionHandleReadResult>;
|
|
76
|
+
/**
|
|
77
|
+
* Append a contiguous batch continuing the current logical end. The first
|
|
78
|
+
* event's `seq` MUST equal the stored next-seq; committed events are never
|
|
79
|
+
* rewritten. Persistence is best-effort: on resolution the batch is
|
|
80
|
+
* accepted, ordered, and visible to reads on this backend instance, but
|
|
81
|
+
* only a resolved {@link flush} promises it survives a crash — a backend
|
|
82
|
+
* may buffer or batch physical writes behind append. Rejects with
|
|
83
|
+
* `SessionReadOnlyError` on a read handle and `SessionOwnershipLostError`
|
|
84
|
+
* when write ownership is gone.
|
|
85
|
+
* @param events - the contiguous batch, in seq order.
|
|
86
|
+
* @param options - optional cancellation observed before the write starts.
|
|
87
|
+
*/
|
|
88
|
+
append(events: readonly SessionEvent[], options?: SessionHandleAppendOptions): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* The durability barrier — the one operation that promises storage: on
|
|
91
|
+
* resolution every acknowledged append is durable and the session is
|
|
92
|
+
* materialized for other processes; an empty created session becomes
|
|
93
|
+
* durably listable here. Callers that must survive a crash flush; a backend
|
|
94
|
+
* whose `append` already persists on resolution treats this as
|
|
95
|
+
* materialize-if-needed. Rejects with `SessionReadOnlyError` on a read
|
|
96
|
+
* handle.
|
|
97
|
+
* @param options - optional cancellation observed before the barrier starts.
|
|
98
|
+
*/
|
|
99
|
+
flush(options?: SessionHandleFlushOptions): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Release the handle: a read handle frees local resources; a write handle
|
|
102
|
+
* completes pending durability and releases write ownership. Idempotent,
|
|
103
|
+
* asynchronous, and deliberately not cancellable.
|
|
104
|
+
*/
|
|
105
|
+
close(): Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=handle.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,235 +1,158 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Durable session-persistence Service Definition (`ctx.sessionPersistence`). Backends store
|
|
3
3
|
* {@link SessionEvent}s as the event-sourced log and carry non-replayable
|
|
4
|
-
* {@link SessionHeader} metadata separately
|
|
4
|
+
* {@link SessionHeader} metadata separately; callers address one stored
|
|
5
|
+
* session through a {@link SessionHandle} obtained from `create`/`open`.
|
|
5
6
|
* @module @deepseek-ai/dsh-session-persistence
|
|
6
7
|
*/
|
|
7
8
|
import { Context, Service } from '@deepseek-ai/cordis';
|
|
8
|
-
import {
|
|
9
|
-
import type {
|
|
9
|
+
import type { SessionEvent, SessionHeader, SessionId, SessionLogOffset } from '@deepseek-ai/dsh-session';
|
|
10
|
+
import type { SessionHandle, SessionAccess } from './handle.ts';
|
|
10
11
|
import type { SessionPersistenceRevision } from './revision.ts';
|
|
11
12
|
export type { SessionHeader } from '@deepseek-ai/dsh-session';
|
|
12
13
|
export { SessionPersistenceRevision } from './revision.ts';
|
|
13
|
-
export {
|
|
14
|
-
|
|
14
|
+
export type { SessionAccess, SessionHandle, SessionHandleAppendOptions, SessionHandleFlushOptions, SessionHandleReadOptions, SessionHandleReadResult, } from './handle.ts';
|
|
15
|
+
export { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionFormatUnsupportedError, SessionHandleClosedError, SessionOwnershipLostError, SessionPersistenceCorruptionError, SessionPersistenceNotFoundError, SessionReadOnlyError, sessionFormatVersionRefusal, } from './errors.ts';
|
|
16
|
+
export type { SessionLocation } from './errors.ts';
|
|
17
|
+
export { assertContiguous, assertStoredId, assertVersion, materializeAppendBatch, materializeCreateHeader, validateStoredEvents, } from './storage-contract.ts';
|
|
18
|
+
/**
|
|
19
|
+
* Lightweight stored-session observation returned by {@link SessionPersistence.stat}
|
|
20
|
+
* and {@link SessionPersistence.list} without reading the full event log.
|
|
21
|
+
*/
|
|
15
22
|
export interface SessionPersistenceSnapshot {
|
|
16
|
-
/** Detached metadata for one
|
|
17
|
-
header: SessionHeader;
|
|
18
|
-
/** Opaque
|
|
19
|
-
revision: SessionPersistenceRevision;
|
|
23
|
+
/** Detached metadata for one stored session. */
|
|
24
|
+
readonly header: SessionHeader;
|
|
25
|
+
/** Opaque change token; see {@link SessionPersistence.stat}. */
|
|
26
|
+
readonly revision: SessionPersistenceRevision;
|
|
27
|
+
/** Logical event count, when the backend can provide it cheaply from metadata; otherwise absent. */
|
|
28
|
+
readonly eventCount?: number;
|
|
29
|
+
/** Physical artifact byte size, when the backend can provide it cheaply (JSONL); otherwise absent. */
|
|
30
|
+
readonly sizeBytes?: number;
|
|
20
31
|
}
|
|
21
|
-
/**
|
|
32
|
+
/** Options for {@link SessionPersistence.create}. */
|
|
33
|
+
export interface SessionPersistenceCreateOptions {
|
|
34
|
+
/** Optional cancellation observed before backend work starts. */
|
|
35
|
+
readonly signal?: AbortSignal;
|
|
36
|
+
/**
|
|
37
|
+
* Exact fork-inherited prefix length. Required when `header.isSeeded` is
|
|
38
|
+
* true and must be omitted (or `0`) otherwise; the backend refuses a
|
|
39
|
+
* mismatch at create.
|
|
40
|
+
*/
|
|
41
|
+
readonly inheritedEventCount?: SessionLogOffset;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Logical Session header paired with its exact inherited cut for body-bearing
|
|
45
|
+
* storage operations. `isSeeded` marks fork lineage on the header; the
|
|
46
|
+
* numeric cut travels beside it, never inside the replayable event log.
|
|
47
|
+
*/
|
|
22
48
|
export interface SessionStorageMetadata {
|
|
23
49
|
/** Validated immutable Session header. */
|
|
24
50
|
readonly meta: SessionHeader;
|
|
25
51
|
/** Number of leading events inherited from the Session's fork parent. */
|
|
26
52
|
readonly inheritedEventCount: SessionLogOffset;
|
|
27
53
|
}
|
|
28
|
-
/** Immutable logical session
|
|
54
|
+
/** Immutable logical session read: storage metadata plus the complete validated event log. */
|
|
29
55
|
export interface SessionInspection extends SessionStorageMetadata {
|
|
30
|
-
/**
|
|
56
|
+
/** Contiguous validated events from seq 0. */
|
|
31
57
|
readonly events: readonly SessionEvent[];
|
|
32
58
|
}
|
|
33
|
-
/**
|
|
34
|
-
export interface
|
|
35
|
-
/**
|
|
36
|
-
readonly
|
|
37
|
-
/** Valid contiguous stored events at or after {@link fromSeq}; not a complete Session log when the offset is nonzero. */
|
|
38
|
-
readonly events: readonly SessionEvent[];
|
|
59
|
+
/** Options for {@link SessionPersistence.open}. */
|
|
60
|
+
export interface SessionPersistenceOpenOptions {
|
|
61
|
+
/** Optional cancellation observed before backend work starts. */
|
|
62
|
+
readonly signal?: AbortSignal;
|
|
39
63
|
}
|
|
40
|
-
/**
|
|
41
|
-
export
|
|
42
|
-
/**
|
|
43
|
-
readonly
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
readonly preparedSession: Session;
|
|
50
|
-
} | {
|
|
51
|
-
/** A live Session won source resolution while the persistence read was starting. */
|
|
52
|
-
readonly source: 'live';
|
|
53
|
-
/** Immutable live header and event prefix observed together. */
|
|
54
|
-
readonly inspection: SessionInspection;
|
|
55
|
-
});
|
|
56
|
-
/** A backend's own raw artifact text for one session, verbatim. */
|
|
57
|
-
export interface SessionRawArtifact extends SessionStorageMetadata {
|
|
58
|
-
/** The artifact's base filename on disk, without any physical encoding suffix. */
|
|
59
|
-
readonly filename: string;
|
|
60
|
-
/** The artifact's full text content, decoded from the backend's physical encoding. */
|
|
61
|
-
readonly content: string;
|
|
64
|
+
/** Options for {@link SessionPersistence.stat}. */
|
|
65
|
+
export interface SessionPersistenceStatOptions {
|
|
66
|
+
/** Optional cancellation for backend metadata reads. */
|
|
67
|
+
readonly signal?: AbortSignal;
|
|
68
|
+
}
|
|
69
|
+
/** Options for {@link SessionPersistence.list}. */
|
|
70
|
+
export interface SessionPersistenceListOptions {
|
|
71
|
+
/** Optional cancellation for backend listing work. */
|
|
72
|
+
readonly signal?: AbortSignal;
|
|
62
73
|
}
|
|
63
|
-
export { DEFAULT_PREPARED_SESSION_CACHE_SIZE, DEFAULT_WRITE_BATCH_MAX_DELAY_MS, MAX_WRITE_BATCH_DELAY_MS, PersistenceCoordinator, SessionFormatUnsupportedError, SessionPersistenceCorruptionError, sessionFormatVersionRefusal, } from './coordinator.ts';
|
|
64
|
-
export type { PersistenceBackend, PersistenceCoordinatorOptions, StoredPrefix, StoredSuffix, } from './coordinator.ts';
|
|
65
74
|
declare module '@deepseek-ai/cordis' {
|
|
66
75
|
interface Context {
|
|
67
76
|
sessionPersistence: SessionPersistence;
|
|
68
77
|
}
|
|
69
78
|
}
|
|
70
79
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
80
|
+
* Durable append-only session storage addressed through per-session handles.
|
|
81
|
+
*
|
|
82
|
+
* Storage semantics shared by every backend: events are contiguous from seq 0
|
|
83
|
+
* and never rewritten; a torn physical tail is never returned to a reader and
|
|
84
|
+
* is truncated by the write path before its first append; reads validate
|
|
85
|
+
* current-format records only and refuse unknown vocabulary fail-closed.
|
|
86
|
+
* `append` persists best-effort; `flush` — per handle or service-wide — is
|
|
87
|
+
* the durability barrier.
|
|
88
|
+
*
|
|
89
|
+
* Visibility: a created session is observable through `stat`/`list`/`open`
|
|
90
|
+
* in this process from the moment `create` resolves, even while a backend
|
|
91
|
+
* defers physical materialization (a pure optimization); other processes see
|
|
92
|
+
* the session only once it materializes, and a session that never
|
|
93
|
+
* materialized before a crash never existed. `SessionHandle.flush` forces
|
|
94
|
+
* materialization.
|
|
95
|
+
*
|
|
96
|
+
* Freshness: once an `append` or `flush` resolves, reads started afterwards
|
|
97
|
+
* on this backend instance observe at least that prefix.
|
|
86
98
|
*/
|
|
87
99
|
export declare abstract class SessionPersistence extends Service {
|
|
88
100
|
constructor(ctx: Context);
|
|
89
101
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* @
|
|
94
|
-
* @
|
|
95
|
-
*/
|
|
96
|
-
abstract locate(meta: SessionHeader): SessionLocation | undefined;
|
|
97
|
-
/**
|
|
98
|
-
* Whether this backend exposes one verbatim raw artifact per session.
|
|
99
|
-
* A backend that declares `true` must override {@link readRaw}.
|
|
102
|
+
* Create a new stored session and take its write ownership.
|
|
103
|
+
* @param header - the immutable header (id, version, cwd, lineage) to store.
|
|
104
|
+
* @param options - optional cancellation.
|
|
105
|
+
* @returns a `write` handle owned by the caller; close it to release ownership.
|
|
106
|
+
* @throws {SessionAlreadyExistsError} when the id already exists.
|
|
100
107
|
*/
|
|
101
|
-
abstract
|
|
108
|
+
abstract create(header: SessionHeader, options?: SessionPersistenceCreateOptions): Promise<SessionHandle>;
|
|
102
109
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* @param
|
|
111
|
-
*
|
|
112
|
-
* @
|
|
113
|
-
* @
|
|
114
|
-
* session is absent.
|
|
115
|
-
* @throws when this backend does not expose per-session raw artifacts.
|
|
116
|
-
*/
|
|
117
|
-
readRaw(_id: SessionId, signal?: AbortSignal): Promise<SessionRawArtifact | undefined>;
|
|
118
|
-
/**
|
|
119
|
-
* Register a new session's metadata. A backend MAY defer the physical write
|
|
120
|
-
* until the first {@link append} (lazy materialization), in which case a
|
|
121
|
-
* created-but-never-appended session is absent from {@link list}
|
|
122
|
-
* — abandoned sessions leave nothing behind.
|
|
123
|
-
* @param meta - the immutable header (id, version, cwd, lineage) to record.
|
|
124
|
-
* @param inheritedEventCount - exact fork-inherited prefix length. Required
|
|
125
|
-
* for a seeded header and omitted only for an unseeded header.
|
|
126
|
-
*/
|
|
127
|
-
abstract create(meta: SessionHeader, inheritedEventCount?: SessionLogOffset): Promise<void>;
|
|
128
|
-
/**
|
|
129
|
-
* Ensure a live session has a durable header even when it has no events.
|
|
130
|
-
* Ordinary sessions remain lazily materialized; lifecycle frontends call
|
|
131
|
-
* this only when an empty session itself is a durable resumable resource.
|
|
132
|
-
* @param _session - exact live session whose registered header is materialized.
|
|
133
|
-
*/
|
|
134
|
-
ensureMaterialized(_session: Session): Promise<void>;
|
|
135
|
-
/**
|
|
136
|
-
* Durably persist a batch of events. Honors the append-only and contiguous-
|
|
137
|
-
* seq contracts: the first event's `seq` MUST equal the stored next-seq
|
|
138
|
-
* (after `load` has durably closed any interrupted turn). Rejects non-JSON-
|
|
139
|
-
* serializable `event.data` with an error naming the offending event type.
|
|
140
|
-
* A seeded session's first materializing batch must reach its complete
|
|
141
|
-
* inherited prefix.
|
|
142
|
-
* @param id - the session the batch belongs to.
|
|
143
|
-
* @param events - the contiguous batch to persist, in seq order.
|
|
144
|
-
*/
|
|
145
|
-
abstract append(id: SessionId, events: readonly SessionEvent[]): Promise<void>;
|
|
146
|
-
/**
|
|
147
|
-
* Prepare the exact unpublished Session used by resume. Implementations may
|
|
148
|
-
* reuse object graphs retained by an earlier {@link inspect} after confirming
|
|
149
|
-
* their durable revision is still current; disposal releases an unpublished
|
|
150
|
-
* reservation. Revision retries require the durable log to remain unchanged
|
|
151
|
-
* for one read/check round trip; continuous external writers may delay completion.
|
|
152
|
-
* @param id - persisted session to prepare.
|
|
153
|
-
* @param signal - optional cancellation for preparation work.
|
|
154
|
-
* @returns one owned unpublished Session preparation.
|
|
155
|
-
*/
|
|
156
|
-
prepare(id: SessionId, signal?: AbortSignal): Promise<SessionPreparation>;
|
|
157
|
-
/**
|
|
158
|
-
* Load an immutable balanced logical view and commit any required cold
|
|
159
|
-
* recovery. A complete interrupted final turn is preserved and durably
|
|
160
|
-
* closed with missing tool errors plus any open step and turn boundaries;
|
|
161
|
-
* only a torn final record is discarded. Unknown versions and corruption in
|
|
162
|
-
* the committed prefix reject. Implementations MUST NOT crash-repair an
|
|
163
|
-
* identity still bound to a live Session: a balanced live log may return as a
|
|
164
|
-
* durable snapshot, while an open live turn rejects. Returned values may be
|
|
165
|
-
* shared with immutable live or prepared state and must not be mutated.
|
|
166
|
-
* Revision-based implementations may wait for one stable read/check round trip.
|
|
167
|
-
* @param id - the persisted session to reload.
|
|
168
|
-
* @returns the header and a log ending on a balanced `turn/end`.
|
|
169
|
-
*/
|
|
170
|
-
abstract load(id: SessionId): Promise<SessionInspection>;
|
|
171
|
-
/**
|
|
172
|
-
* Inspect an immutable logical session without committing recovery or
|
|
173
|
-
* publishing it. A cold complete interrupted turn receives synthetic closers
|
|
174
|
-
* in memory and a torn physical tail remains untouched. An already-live
|
|
175
|
-
* Session instead yields its current immutable snapshot, which may contain an
|
|
176
|
-
* open turn and its `session/end-seed` boundary. Coordinator-backed
|
|
177
|
-
* implementations retain the exact cold unpublished Session for bounded
|
|
178
|
-
* reuse by a later {@link prepare}. A stale ready source is reloaded; a source
|
|
179
|
-
* already committing or reserved for resume remains exclusive, and inspection
|
|
180
|
-
* may borrow its immutable view. Callers borrow only the immutable header and
|
|
181
|
-
* log. Continuous external writers may delay revision convergence.
|
|
182
|
-
* @param id - the persisted session to inspect.
|
|
183
|
-
* @param signal - optional cancellation for queued and backend read work.
|
|
184
|
-
* @returns the validated header and current logical event log.
|
|
185
|
-
*/
|
|
186
|
-
abstract inspect(id: SessionId, signal?: AbortSignal): Promise<SessionInspection>;
|
|
187
|
-
/**
|
|
188
|
-
* Borrow one exact inspection while retaining any reusable prepared source.
|
|
189
|
-
* A cold observation must pin the exact prepared Session that a later
|
|
190
|
-
* {@link prepare} reserves. Implementations must not degrade this operation
|
|
191
|
-
* to a detached {@link inspect} result.
|
|
192
|
-
* @param id - persisted session to observe.
|
|
193
|
-
* @param signal - optional cancellation for preparation work.
|
|
194
|
-
* @returns a disposable immutable observation.
|
|
110
|
+
* Open an existing stored session.
|
|
111
|
+
*
|
|
112
|
+
* `read` never takes ownership and works while another handle (or process)
|
|
113
|
+
* holds write ownership. `write` atomically claims single-writer ownership;
|
|
114
|
+
* an existing active owner rejects.
|
|
115
|
+
* @param id - the stored session to open.
|
|
116
|
+
* @param access - `read` or `write`.
|
|
117
|
+
* @param options - optional cancellation.
|
|
118
|
+
* @returns the open handle.
|
|
119
|
+
* @throws {SessionPersistenceNotFoundError} when the session does not exist.
|
|
120
|
+
* @throws {SessionAlreadyOwnedError} for `write` when ownership is taken.
|
|
195
121
|
*/
|
|
196
|
-
abstract
|
|
122
|
+
abstract open(id: SessionId, access: SessionAccess, options?: SessionPersistenceOpenOptions): Promise<SessionHandle>;
|
|
197
123
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
* sequential media such as JSONL still parse the whole artifact and skip
|
|
208
|
-
* forward. The primitive bounds what is returned and refolded, not every
|
|
209
|
-
* backend's physical read.
|
|
210
|
-
* @param id - the persisted session to read.
|
|
211
|
-
* @param fromSeq - first event offset to include.
|
|
212
|
-
* @param signal - optional cancellation for queued and backend read work.
|
|
213
|
-
* @returns storage metadata, the requested offset, and stored events with `seq >= fromSeq`.
|
|
124
|
+
* Flush every active write handle owned by this service instance in one
|
|
125
|
+
* durability barrier: each handle's routed live events drain durably and
|
|
126
|
+
* its session materializes, exactly as that handle's own
|
|
127
|
+
* `SessionHandle.flush` would. Read handles buffer nothing and are
|
|
128
|
+
* untouched. A handle closed concurrently counts as flushed — close itself
|
|
129
|
+
* drains durably.
|
|
130
|
+
* @returns resolution once every write handle active at the call has flushed.
|
|
131
|
+
* @throws {AggregateError} naming each session whose flush failed; the
|
|
132
|
+
* remaining handles still flush.
|
|
214
133
|
*/
|
|
215
|
-
abstract
|
|
134
|
+
abstract flush(): Promise<void>;
|
|
216
135
|
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
136
|
+
* Observe one stored session without reading its event log or taking
|
|
137
|
+
* ownership.
|
|
138
|
+
*
|
|
139
|
+
* The snapshot's `revision` is an opaque change token comparable only
|
|
140
|
+
* against revisions from the same service instance and session id: equal
|
|
141
|
+
* revisions may be treated as an unchanged log; unequal revisions promise
|
|
142
|
+
* nothing. Write-ownership churn does not change a revision. It exists for
|
|
143
|
+
* derived read-model caches keyed off `stat`/`list`; it plays no part in
|
|
144
|
+
* open, read, or resume.
|
|
145
|
+
* @param id - the stored session to observe.
|
|
146
|
+
* @param options - optional cancellation.
|
|
147
|
+
* @returns the snapshot, or `undefined` when the session does not exist.
|
|
220
148
|
*/
|
|
221
|
-
abstract
|
|
149
|
+
abstract stat(id: SessionId, options?: SessionPersistenceStatOptions): Promise<SessionPersistenceSnapshot | undefined>;
|
|
222
150
|
/**
|
|
223
|
-
* List
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
* successful mutating {@link load} repair changes the next listed revision.
|
|
227
|
-
* Revisions also distinguish independently backed stores so backend-local
|
|
228
|
-
* counters cannot compare equal across different persistence sources.
|
|
229
|
-
* @param signal - optional cancellation for backend snapshot-listing work.
|
|
230
|
-
* @returns one header and opaque revision per materialized session without loading full logs.
|
|
151
|
+
* List every stored session visible to this process, in no promised order.
|
|
152
|
+
* @param options - optional cancellation.
|
|
153
|
+
* @returns one snapshot per stored session.
|
|
231
154
|
*/
|
|
232
|
-
abstract
|
|
155
|
+
abstract list(options?: SessionPersistenceListOptions): Promise<readonly SessionPersistenceSnapshot[]>;
|
|
233
156
|
}
|
|
234
157
|
export default SessionPersistence;
|
|
235
158
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend-shared storage validation: the version gate, the fail-closed event
|
|
3
|
+
* vocabulary, append-batch materialization, and contiguity — one place so
|
|
4
|
+
* every backend refuses the same inputs identically.
|
|
5
|
+
* @module @deepseek-ai/dsh-session-persistence/storage-contract
|
|
6
|
+
*/
|
|
7
|
+
import type { SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session';
|
|
8
|
+
import { type SessionLocation } from './errors.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Refuse stored metadata that is not bound to the requested session id.
|
|
11
|
+
* @param id - the requested session id.
|
|
12
|
+
* @param meta - the stored header.
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertStoredId(id: SessionId, meta: SessionHeader): void;
|
|
15
|
+
/**
|
|
16
|
+
* Refuse a stored header whose format version this build does not read.
|
|
17
|
+
* @param meta - the stored header.
|
|
18
|
+
* @param location - the backend's artifact location for the refusal, when one exists.
|
|
19
|
+
*/
|
|
20
|
+
export declare function assertVersion(meta: {
|
|
21
|
+
readonly id: SessionId;
|
|
22
|
+
readonly version: number;
|
|
23
|
+
}, location?: SessionLocation): void;
|
|
24
|
+
/**
|
|
25
|
+
* Validate one exclusively owned stored event array in place: adopt each
|
|
26
|
+
* record (validating and freezing it) and refuse any event type this build
|
|
27
|
+
* does not know, unless its writer marked it `ignorable: true` — silently
|
|
28
|
+
* skipping an unknown required event could reconstruct a wrong session (the
|
|
29
|
+
* envelope contract on `SessionEvent.ignorable`). Both newer vocabularies and
|
|
30
|
+
* retired pre-release shapes refuse here; this build ships no migration.
|
|
31
|
+
* @param meta - the stored header the events belong to.
|
|
32
|
+
* @param events - exclusively owned decoded events; validated in place.
|
|
33
|
+
* @param location - the backend's artifact location for refusals, when one exists.
|
|
34
|
+
* @returns the same array, validated and frozen.
|
|
35
|
+
* @throws {SessionFormatUnsupportedError} for unknown event types.
|
|
36
|
+
* @throws {SessionPersistenceCorruptionError} for records that fail validation.
|
|
37
|
+
*/
|
|
38
|
+
export declare function validateStoredEvents(meta: SessionHeader, events: SessionEvent[], location?: SessionLocation): SessionEvent[];
|
|
39
|
+
/**
|
|
40
|
+
* Validate and deep-snapshot a header passed to `create` in one traversal.
|
|
41
|
+
* @param header - the caller's header.
|
|
42
|
+
* @returns the detached lossless-JSON header.
|
|
43
|
+
* @throws {TypeError} for non-JSON metadata or an invalid `createdAt`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function materializeCreateHeader(header: SessionHeader): SessionHeader;
|
|
46
|
+
/**
|
|
47
|
+
* Validate and deep-snapshot one append batch in a single traversal, so the
|
|
48
|
+
* checked value is exactly the value persisted (a check followed by a copy
|
|
49
|
+
* could reread accessors into a different record).
|
|
50
|
+
* @param events - the caller's batch.
|
|
51
|
+
* @returns the detached lossless-JSON batch.
|
|
52
|
+
* @throws {TypeError} when any event data is not losslessly JSON-serializable.
|
|
53
|
+
*/
|
|
54
|
+
export declare function materializeAppendBatch(events: readonly SessionEvent[]): readonly SessionEvent[];
|
|
55
|
+
/**
|
|
56
|
+
* Refuse a batch that does not contiguously continue the stored log.
|
|
57
|
+
* @param id - the session the batch belongs to.
|
|
58
|
+
* @param events - the batch, in seq order.
|
|
59
|
+
* @param cursor - the stored next-seq.
|
|
60
|
+
*/
|
|
61
|
+
export declare function assertContiguous(id: SessionId, events: readonly SessionEvent[], cursor: number): void;
|
|
62
|
+
//# sourceMappingURL=storage-contract.d.ts.map
|