@deepseek-ai/dsh-session-persistence-jsonl 0.1.2-alpha.5 → 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 +24 -17
- package/README.zh.md +24 -17
- package/lib/index.js +2313 -479
- package/lib/types/format.d.ts +53 -31
- package/lib/types/generation.d.ts +163 -0
- package/lib/types/index.d.ts +176 -89
- package/lib/types/lease.d.ts +57 -0
- package/lib/types/migration-verifier.d.ts +15 -0
- package/lib/types/storage.d.ts +249 -0
- package/lib/types/testing/generation.d.ts +8 -0
- package/lib/types/win32.d.ts +18 -0
- package/lib/types/worker.d.ts +3 -0
- package/lib/worker.cjs +1235 -0
- package/package.json +13 -6
package/lib/types/format.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module dsh-session-persistence-jsonl/format
|
|
9
9
|
*/
|
|
10
10
|
import type { SessionEvent, SessionHeader, SessionId, SessionLogOffset as SessionLogOffsetType } from '@deepseek-ai/dsh-session';
|
|
11
|
-
import {
|
|
11
|
+
import type { SessionFormatRecovery } from '@deepseek-ai/dsh-session-format';
|
|
12
12
|
/** Physical encoding selected for JSONL session artifacts. */
|
|
13
13
|
export type JsonlCompression = 'zstd' | 'none';
|
|
14
14
|
/**
|
|
@@ -18,9 +18,26 @@ export type JsonlCompression = 'zstd' | 'none';
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function logSuffix(compression: JsonlCompression): '.jsonl.zstd' | '.jsonl';
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Return the canonical filename for one immutable Session format generation.
|
|
22
|
+
* Version zero retains the original suffix-only name; every later generation
|
|
23
|
+
* carries a lowercase numeric `vN` component.
|
|
24
|
+
* @param version - non-negative safe Session format version.
|
|
25
|
+
* @param compression - configured JSONL artifact encoding.
|
|
26
|
+
* @returns the generation filename inside one Session directory.
|
|
27
|
+
*/
|
|
28
|
+
export declare function generationLogFilename(version: number, compression: JsonlCompression): string;
|
|
29
|
+
/**
|
|
30
|
+
* Parse one canonical generation filename for the selected physical encoding.
|
|
31
|
+
* Noncanonical, temporary, uppercase, leading-zero, and version-zero-tagged names do
|
|
32
|
+
* not identify committed generations.
|
|
33
|
+
* @param filename - one entry from a Session directory.
|
|
34
|
+
* @param compression - configured JSONL artifact encoding.
|
|
35
|
+
* @returns its format version, or `undefined` when the name is not canonical.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseGenerationLogFilename(filename: string, compression: JsonlCompression): number | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The current v2 physical header stored as the first JSONL record. The exact
|
|
40
|
+
* inherited cut lives on the last tagged `session/end-seed` event.
|
|
24
41
|
*/
|
|
25
42
|
interface HeaderLine {
|
|
26
43
|
type: 'session';
|
|
@@ -29,11 +46,17 @@ interface HeaderLine {
|
|
|
29
46
|
createdAt: number;
|
|
30
47
|
cwd?: string;
|
|
31
48
|
parentSession?: SessionId;
|
|
32
|
-
|
|
49
|
+
isSeeded: boolean;
|
|
33
50
|
origin?: 'subagent';
|
|
34
51
|
delegationDepth: number;
|
|
35
52
|
agentPreset?: string;
|
|
36
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Refuse policy fields that never belong to a released Session header.
|
|
56
|
+
* @param value - parsed physical header candidate.
|
|
57
|
+
* @returns nothing after successful validation.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertNoRetiredHeaderFields(value: unknown): void;
|
|
37
60
|
/**
|
|
38
61
|
* Build the header line object from a {@link SessionHeader}.
|
|
39
62
|
* @param header - the immutable session metadata to serialize.
|
|
@@ -82,26 +105,37 @@ export declare function projectDir(root: string, cwd: string | undefined): strin
|
|
|
82
105
|
*/
|
|
83
106
|
export declare function sessionDir(root: string, cwd: string | undefined, id: SessionId): string;
|
|
84
107
|
/**
|
|
85
|
-
*
|
|
108
|
+
* Build one immutable Session format generation path.
|
|
109
|
+
* @param root - the backend's session root directory.
|
|
110
|
+
* @param cwd - the session's project directory (`undefined` → `_no-cwd`).
|
|
111
|
+
* @param id - the session id, path-encoded via {@link encodeSegment} before filesystem use.
|
|
112
|
+
* @param version - physical Session format generation.
|
|
113
|
+
* @param compression - physical artifact encoding and filename suffix.
|
|
114
|
+
* @returns the selected generation's configured JSONL artifact path.
|
|
115
|
+
*/
|
|
116
|
+
export declare function generationLogPath(root: string, cwd: string | undefined, id: SessionId, version: number, compression: JsonlCompression): string;
|
|
117
|
+
/**
|
|
118
|
+
* Build the current generation's append target path for a Session.
|
|
86
119
|
* @param root - the backend's session root directory.
|
|
87
120
|
* @param cwd - the session's project directory (`undefined` → `_no-cwd`).
|
|
88
121
|
* @param id - the session id, path-encoded via {@link encodeSegment} before filesystem use.
|
|
89
122
|
* @param compression - physical artifact encoding and filename suffix.
|
|
90
|
-
* @returns the
|
|
123
|
+
* @returns the current Session format generation path.
|
|
91
124
|
*/
|
|
92
125
|
export declare function logPath(root: string, cwd: string | undefined, id: SessionId, compression: JsonlCompression): string;
|
|
93
126
|
/**
|
|
94
|
-
* Serialize
|
|
95
|
-
*
|
|
96
|
-
* `reasoning-chunks` / `tool-call-chunks` storage rows; off writes one event
|
|
97
|
-
* per line. Both modes range-encode provenance at the storage boundary.
|
|
98
|
-
* Reading is layout-blind either way ({@link scanLog} always decodes rows),
|
|
99
|
-
* so the switch changes only newly written bytes.
|
|
127
|
+
* Serialize a v2 event batch as JSONL lines (no trailing newline). Compact
|
|
128
|
+
* Assistant streams are nested event data; every event occupies one row.
|
|
100
129
|
* @param events - the batch to serialize, in log order.
|
|
101
|
-
* @param packChunks - whether to pack delta runs into storage rows.
|
|
102
130
|
* @returns the batch's JSONL text; the writer adds the final newline.
|
|
103
131
|
*/
|
|
104
|
-
export declare function eventLines(events: readonly SessionEvent[]
|
|
132
|
+
export declare function eventLines(events: readonly SessionEvent[]): string;
|
|
133
|
+
/**
|
|
134
|
+
* Serialize one v2 event as one JSONL record without its trailing newline.
|
|
135
|
+
* @param event - current event to encode.
|
|
136
|
+
* @returns one physical JSON record.
|
|
137
|
+
*/
|
|
138
|
+
export declare function eventLine(event: SessionEvent): string;
|
|
105
139
|
interface SessionLogScan {
|
|
106
140
|
meta: SessionHeader;
|
|
107
141
|
inheritedEventCount: SessionLogOffsetType;
|
|
@@ -115,9 +149,10 @@ interface SessionLogScan {
|
|
|
115
149
|
* copied because a decoder may reuse its output buffer after `write()` returns.
|
|
116
150
|
*/
|
|
117
151
|
export declare class SessionLogScanner {
|
|
152
|
+
private readonly recovery;
|
|
118
153
|
private readonly meta;
|
|
119
|
-
private readonly
|
|
120
|
-
private
|
|
154
|
+
private readonly restore;
|
|
155
|
+
private eventCount;
|
|
121
156
|
private fragments;
|
|
122
157
|
private fragmentBytes;
|
|
123
158
|
private inputBytes;
|
|
@@ -129,7 +164,7 @@ export declare class SessionLogScanner {
|
|
|
129
164
|
* Create an event scanner from exactly one newline-terminated header record.
|
|
130
165
|
* @param headerRecord - the complete first JSONL record, including its newline.
|
|
131
166
|
*/
|
|
132
|
-
constructor(headerRecord: Buffer);
|
|
167
|
+
constructor(headerRecord: Buffer, recovery?: SessionFormatRecovery);
|
|
133
168
|
/**
|
|
134
169
|
* Consume the next raw plaintext chunk, retaining only an incomplete final record.
|
|
135
170
|
* @param chunk - bytes immediately following all previously supplied bytes.
|
|
@@ -161,18 +196,5 @@ export declare class SessionLogScanner {
|
|
|
161
196
|
* @returns the header, preserved event prefix, and byte offset safe to append at.
|
|
162
197
|
*/
|
|
163
198
|
export declare function scanLog(buffer: Buffer): SessionLogScan;
|
|
164
|
-
/**
|
|
165
|
-
* Parse just the header line of a log into logical metadata plus its exact
|
|
166
|
-
* inherited cut, or `undefined` if it is missing/not a header.
|
|
167
|
-
* @param firstLine - the first line of a log file (without its trailing newline).
|
|
168
|
-
* @returns parsed storage metadata, or `undefined` for a malformed header.
|
|
169
|
-
*/
|
|
170
|
-
export declare function parseHeader(firstLine: string): SessionStorageMetadata | undefined;
|
|
171
|
-
/**
|
|
172
|
-
* Parse only the logical header fields needed by lightweight listing.
|
|
173
|
-
* @param firstLine - first JSONL line without its trailing newline.
|
|
174
|
-
* @returns the logical Session header, or `undefined` for a malformed line.
|
|
175
|
-
*/
|
|
176
|
-
export declare function parseHeaderMeta(firstLine: string): SessionHeader | undefined;
|
|
177
199
|
export {};
|
|
178
200
|
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable whole-generation publication for JSONL Session artifacts.
|
|
3
|
+
*
|
|
4
|
+
* Format packages transform parsed JSON values. This module owns the physical
|
|
5
|
+
* encoding, exact source identity, immutable generation files, and exclusive
|
|
6
|
+
* current-generation publication for both configured JSONL suffixes.
|
|
7
|
+
* @module @deepseek-ai/dsh-session-persistence-jsonl/generation
|
|
8
|
+
*/
|
|
9
|
+
import { type FileHandle } from 'node:fs/promises';
|
|
10
|
+
import type { SessionFormatArtifact, SessionFormatJsonValue, SessionFormatRestore } from '@deepseek-ai/dsh-session-format';
|
|
11
|
+
import type { JsonlCompression } from './format.ts';
|
|
12
|
+
import { publishNewFileWin32 } from './win32.ts';
|
|
13
|
+
/** Pure adapter between backend-owned JSONL framing and the format catalog. */
|
|
14
|
+
export interface JsonlGenerationFormatAdapter {
|
|
15
|
+
readonly currentVersion: number;
|
|
16
|
+
/** Create the single-pass codec and migration state for a historical header. */
|
|
17
|
+
createRestore(header: Record<string, unknown>): SessionFormatRestore;
|
|
18
|
+
/** Encode one current header record without materializing body rows. */
|
|
19
|
+
encodeHeader(header: SessionFormatArtifact['header'], inheritedEventCount: number): SessionFormatJsonValue;
|
|
20
|
+
/** Encode one current event record. */
|
|
21
|
+
encodeEvent(event: SessionFormatArtifact['events'][number]): SessionFormatJsonValue;
|
|
22
|
+
/** Classify a supported-version artifact that policy refuses to migrate. */
|
|
23
|
+
isUnsupportedMigrationError?(error: unknown): error is Error;
|
|
24
|
+
}
|
|
25
|
+
/** Inputs for preparing one historical generation and publishing its current successor later. */
|
|
26
|
+
export interface PrepareJsonlMigrationOptions {
|
|
27
|
+
/** Immutable generation selected by the backend resolver. */
|
|
28
|
+
readonly sourcePath: string;
|
|
29
|
+
/** Version selected from the source filename and independently checked against its header. */
|
|
30
|
+
readonly sourceVersion: number;
|
|
31
|
+
/** Canonical filename for `format.currentVersion` in the same Session directory. */
|
|
32
|
+
readonly currentPath: string;
|
|
33
|
+
readonly compression: JsonlCompression;
|
|
34
|
+
readonly format: JsonlGenerationFormatAdapter;
|
|
35
|
+
/** Validate one selected historical header's identity before any migration write. */
|
|
36
|
+
readonly validateHistoricalHeader?: (header: Readonly<Record<string, unknown>>) => void | Promise<void>;
|
|
37
|
+
/** Validate the staged file in an isolated worker before publication. */
|
|
38
|
+
readonly verifyCurrentFile: (path: string, compression: JsonlCompression, expectedId: string, expectedEventCount: number, expectedPrefix?: JsonlExpectedPrefix, signal?: AbortSignal) => Promise<JsonlVerifiedGeneration>;
|
|
39
|
+
readonly signal?: AbortSignal;
|
|
40
|
+
}
|
|
41
|
+
/** Small physical identity returned by an isolated generation verifier. */
|
|
42
|
+
export interface JsonlVerifiedGeneration {
|
|
43
|
+
readonly identity: JsonlPhysicalIdentity;
|
|
44
|
+
readonly bytes: number;
|
|
45
|
+
readonly digest: string;
|
|
46
|
+
}
|
|
47
|
+
/** Physical byte prefix already proven to be a valid complete generation. */
|
|
48
|
+
export interface JsonlExpectedPrefix {
|
|
49
|
+
readonly bytes: number;
|
|
50
|
+
readonly digest: string;
|
|
51
|
+
}
|
|
52
|
+
/** A historical source changed after its single decode and migration pass. */
|
|
53
|
+
export declare class JsonlGenerationSourceChangedError extends Error {
|
|
54
|
+
readonly path: string;
|
|
55
|
+
readonly name = "JsonlGenerationSourceChangedError";
|
|
56
|
+
/** @param path - historical generation whose revision changed. */
|
|
57
|
+
constructor(path: string);
|
|
58
|
+
}
|
|
59
|
+
/** Current logical state prepared independently from durable publication. */
|
|
60
|
+
export interface PreparedJsonlMigration {
|
|
61
|
+
readonly sourceIdentity: JsonlPhysicalIdentity;
|
|
62
|
+
readonly artifact: SessionFormatArtifact;
|
|
63
|
+
/** Encode, verify, and exclusively publish once; every call shares the same success or failure. */
|
|
64
|
+
publish(): Promise<JsonlPhysicalIdentity>;
|
|
65
|
+
}
|
|
66
|
+
/** A historical artifact is intact, but the format edge refuses its contents. */
|
|
67
|
+
export declare class JsonlGenerationUnsupportedMigrationError extends Error {
|
|
68
|
+
readonly fromVersion: number;
|
|
69
|
+
readonly reason: Error;
|
|
70
|
+
readonly name = "JsonlGenerationUnsupportedMigrationError";
|
|
71
|
+
/**
|
|
72
|
+
* @param fromVersion - unchanged source generation version.
|
|
73
|
+
* @param reason - format-edge refusal.
|
|
74
|
+
*/
|
|
75
|
+
constructor(fromVersion: number, reason: Error);
|
|
76
|
+
}
|
|
77
|
+
/** A current-generation filename already names different or invalid bytes. */
|
|
78
|
+
export declare class JsonlGenerationTargetConflictError extends Error {
|
|
79
|
+
readonly path: string;
|
|
80
|
+
readonly reason: Error;
|
|
81
|
+
readonly name = "JsonlGenerationTargetConflictError";
|
|
82
|
+
/**
|
|
83
|
+
* @param path - immutable target that prevented exclusive publication.
|
|
84
|
+
* @param reason - why the existing target cannot be accepted.
|
|
85
|
+
*/
|
|
86
|
+
constructor(path: string, reason: Error);
|
|
87
|
+
}
|
|
88
|
+
/** Stat identity captured together with exact generation bytes. */
|
|
89
|
+
export interface JsonlPhysicalIdentity {
|
|
90
|
+
readonly dev: bigint;
|
|
91
|
+
readonly ino: bigint;
|
|
92
|
+
readonly size: bigint;
|
|
93
|
+
readonly mtimeNs: bigint;
|
|
94
|
+
readonly ctimeNs: bigint;
|
|
95
|
+
}
|
|
96
|
+
/** Exact bytes of one stable file revision together with the stat identity that proved it stable. */
|
|
97
|
+
export interface StablePhysicalFile {
|
|
98
|
+
readonly bytes: Buffer;
|
|
99
|
+
readonly identity: JsonlPhysicalIdentity;
|
|
100
|
+
}
|
|
101
|
+
interface GenerationFileSystem {
|
|
102
|
+
open(path: string, flags: string, mode?: number): Promise<FileHandle>;
|
|
103
|
+
readFile(path: string, signal?: AbortSignal): Promise<Buffer>;
|
|
104
|
+
readdir(path: string): Promise<string[]>;
|
|
105
|
+
stat(path: string): Promise<JsonlPhysicalIdentity>;
|
|
106
|
+
lstat(path: string): Promise<{
|
|
107
|
+
isFile(): boolean;
|
|
108
|
+
isSymbolicLink(): boolean;
|
|
109
|
+
}>;
|
|
110
|
+
link(existingPath: string, newPath: string): Promise<void>;
|
|
111
|
+
rm(path: string): Promise<void>;
|
|
112
|
+
}
|
|
113
|
+
type GenerationBarrierPhase = 'before-source-check' | 'after-publication';
|
|
114
|
+
interface JsonlGenerationInternals {
|
|
115
|
+
readonly fs: GenerationFileSystem;
|
|
116
|
+
readonly randomToken: () => string;
|
|
117
|
+
readonly platform: NodeJS.Platform;
|
|
118
|
+
readonly publishNewWin32: typeof publishNewFileWin32;
|
|
119
|
+
readonly barrier: (phase: GenerationBarrierPhase, attempt: number) => void | Promise<void>;
|
|
120
|
+
}
|
|
121
|
+
/** Dependency overrides for an isolated generation runtime. */
|
|
122
|
+
export type JsonlGenerationRuntimeOverrides = Partial<Omit<JsonlGenerationInternals, 'fs'>> & {
|
|
123
|
+
readonly fs?: Partial<GenerationFileSystem>;
|
|
124
|
+
};
|
|
125
|
+
/** Bound generation operations used by production defaults and deterministic tests. */
|
|
126
|
+
export interface JsonlGenerationRuntime {
|
|
127
|
+
readStable(path: string, signal?: AbortSignal): Promise<StablePhysicalFile>;
|
|
128
|
+
prepare(options: PrepareJsonlMigrationOptions): Promise<PreparedJsonlMigration>;
|
|
129
|
+
verify(path: string, compression: JsonlCompression, expectedId: string, expectedEventCount: number, expectedPrefix?: JsonlExpectedPrefix): Promise<JsonlVerifiedGeneration>;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Read one stable revision of a JSONL file with a single retry. If an append
|
|
133
|
+
* overlaps both reads, return the second read's committed pre-read prefix
|
|
134
|
+
* instead of starving behind a continuous writer.
|
|
135
|
+
* @param path - the generation file to read.
|
|
136
|
+
* @param signal - optional cancellation for the stat/read work.
|
|
137
|
+
* @returns the stable bytes (or the committed prefix) and their stat identity.
|
|
138
|
+
*/
|
|
139
|
+
export declare function readStableJsonlFile(path: string, signal?: AbortSignal): Promise<StablePhysicalFile>;
|
|
140
|
+
/**
|
|
141
|
+
* Read and validate one complete current generation for an isolated verifier.
|
|
142
|
+
* @param path - staged or competing current-generation path.
|
|
143
|
+
* @param compression - configured physical encoding.
|
|
144
|
+
* @param expectedId - Session identity expected in the header.
|
|
145
|
+
* @param expectedEventCount - exact logical event count expected after decoding.
|
|
146
|
+
* @param expectedPrefix - verified migration prefix; an append tail may be present and is not validated.
|
|
147
|
+
* @returns stable physical identity and digest for publication comparison.
|
|
148
|
+
*/
|
|
149
|
+
export declare function verifyJsonlCurrentGeneration(path: string, compression: JsonlCompression, expectedId: string, expectedEventCount: number, expectedPrefix?: JsonlExpectedPrefix): Promise<JsonlVerifiedGeneration>;
|
|
150
|
+
/**
|
|
151
|
+
* Decode and migrate one historical generation without writing its successor.
|
|
152
|
+
* @param options - resolved source, current target, format adapter, and load cancellation.
|
|
153
|
+
* @returns the current artifact and an idempotent explicit publication operation.
|
|
154
|
+
*/
|
|
155
|
+
export declare function prepareJsonlMigration(options: PrepareJsonlMigrationOptions): Promise<PreparedJsonlMigration>;
|
|
156
|
+
/**
|
|
157
|
+
* Create one generation runtime with fixed filesystem and publication dependencies.
|
|
158
|
+
* @param overrides - deterministic filesystem, platform, and race dependencies.
|
|
159
|
+
* @returns bound generation operations.
|
|
160
|
+
*/
|
|
161
|
+
export declare function createJsonlGenerationRuntime(overrides?: JsonlGenerationRuntimeOverrides): JsonlGenerationRuntime;
|
|
162
|
+
export {};
|
|
163
|
+
//# sourceMappingURL=generation.d.ts.map
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JSONL durable session-persistence backend. It stores a header and contiguous
|
|
3
|
-
* events in one
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* events in immutable generation files under one directory per session and serves the handle-based
|
|
4
|
+
* `SessionPersistence` API: `create`/`open` return per-session handles, and
|
|
5
|
+
* every read validates the same fail-closed storage contract.
|
|
6
6
|
* @module @deepseek-ai/dsh-session-persistence-jsonl
|
|
7
7
|
*/
|
|
8
8
|
import { Context } from '@deepseek-ai/cordis';
|
|
9
9
|
import z from '@deepseek-ai/schemastery';
|
|
10
|
-
import { SessionPersistence, type
|
|
11
|
-
import
|
|
10
|
+
import { SessionPersistence, type SessionAccess, type SessionHandle, type SessionHandleReadResult, type SessionPersistenceCreateOptions, type SessionPersistenceListOptions, type SessionPersistenceOpenOptions, type SessionPersistenceSnapshot, type SessionPersistenceStatOptions, type SessionPersistenceRevision as PersistenceRevision } from '@deepseek-ai/dsh-session-persistence';
|
|
11
|
+
import { JsonlSessionHandle } from './storage.ts';
|
|
12
|
+
import { SessionWriteLease } from './lease.ts';
|
|
13
|
+
import type { SessionEvent, SessionId, SessionHeader, SessionLogOffset as SessionLogOffsetType } from '@deepseek-ai/dsh-session';
|
|
12
14
|
import { type JsonlCompression } from './format.ts';
|
|
13
15
|
export type { JsonlCompression } from './format.ts';
|
|
14
16
|
/** Loader schema for the JSONL artifact's physical encoding. */
|
|
15
17
|
export declare const JsonlCompressionSchema: z<JsonlCompression>;
|
|
16
|
-
/** Plugin config
|
|
18
|
+
/** Plugin config for the JSONL backend's root and physical encoding. */
|
|
17
19
|
export interface Config {
|
|
18
20
|
/**
|
|
19
21
|
* Root directory for all session files. Required (no default): a default of
|
|
@@ -23,111 +25,185 @@ export interface Config {
|
|
|
23
25
|
* readable directory; an absent root is created on first materialization.
|
|
24
26
|
*/
|
|
25
27
|
root: string;
|
|
26
|
-
/**
|
|
27
|
-
* Write runs of consecutive `assistant/chunk` delta events as packed
|
|
28
|
-
* `text-chunks`/`reasoning-chunks`/`tool-call-chunks` rows (lossless,
|
|
29
|
-
* ~60% smaller logs measured on a real session). Defaults to true; false
|
|
30
|
-
* keeps one `SessionEvent` per line for diagnostics. Reading packed rows is
|
|
31
|
-
* unconditional: a log's layout never depends on this switch.
|
|
32
|
-
*/
|
|
33
|
-
packChunks?: boolean;
|
|
34
28
|
/** Physical encoding; defaults to checksummed Zstandard frames. */
|
|
35
29
|
compression?: JsonlCompression;
|
|
36
|
-
/** Maximum cold Session preparations retained for history-to-resume reuse. */
|
|
37
|
-
preparedSessionCacheSize?: number;
|
|
38
|
-
/** Fixed live-event coalescing window; not a backend completion deadline. */
|
|
39
|
-
writeBatchMaxDelayMs?: number;
|
|
40
30
|
}
|
|
41
|
-
/**
|
|
42
|
-
interface
|
|
43
|
-
|
|
44
|
-
|
|
31
|
+
/** One stored event graph whose producer has established immutable sharing. */
|
|
32
|
+
interface FrozenStoredEvents extends SessionHandleReadResult {
|
|
33
|
+
readonly eventState: 'shared-frozen';
|
|
34
|
+
}
|
|
35
|
+
/** State shared by prepared historical and published current logs. */
|
|
36
|
+
interface StoredLogBase extends FrozenStoredEvents {
|
|
37
|
+
readonly meta: SessionHeader;
|
|
38
|
+
readonly tornTruncateTo: number | undefined;
|
|
39
|
+
/** Complete events recovered from the torn final frame; the write path rewrites them durably. */
|
|
40
|
+
readonly recoveredTail: SessionEvent[];
|
|
41
|
+
/** Exact fork-inherited prefix length stored in the header line. */
|
|
42
|
+
readonly inheritedEventCount: SessionLogOffsetType;
|
|
43
|
+
readonly revision: PersistenceRevision;
|
|
44
|
+
}
|
|
45
|
+
/** A decoded current generation that is already durable. */
|
|
46
|
+
interface CurrentStoredLog extends StoredLogBase {
|
|
47
|
+
readonly status: 'current';
|
|
45
48
|
}
|
|
46
49
|
/**
|
|
47
50
|
* The JSONL persistence backend. Load as a plugin; it registers as
|
|
48
|
-
* `ctx.sessionPersistence
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
+
* `ctx.sessionPersistence`. Sessions materialize lazily: a created session is
|
|
52
|
+
* visible to this process immediately, reaches disk on its first append or
|
|
53
|
+
* flush, and never existed if the process crashes before that.
|
|
51
54
|
*/
|
|
52
|
-
|
|
55
|
+
declare class JsonlSessionPersistence extends SessionPersistence {
|
|
53
56
|
config: Config;
|
|
54
|
-
readonly supportsRawArtifacts = true;
|
|
55
|
-
static inject: string[];
|
|
56
57
|
static Config: z<Config>;
|
|
57
|
-
/**
|
|
58
|
-
* Backend label for coordinator diagnostics and effects. It shadows
|
|
59
|
-
* `Service.name` without changing the service key captured by the base
|
|
60
|
-
* constructor.
|
|
61
|
-
*/
|
|
58
|
+
/** Backend label for diagnostics and effects; shadows `Service.name` without changing the service key. */
|
|
62
59
|
readonly name = "session-persistence-jsonl";
|
|
63
60
|
private root;
|
|
64
|
-
private packChunks;
|
|
65
61
|
private compression;
|
|
66
|
-
private coordinator;
|
|
67
62
|
private rootEncodingCheck;
|
|
63
|
+
private readonly tracker;
|
|
64
|
+
private readonly generationFormat;
|
|
65
|
+
/**
|
|
66
|
+
* Bounded LRU of parsed, validated stored logs keyed by session id and
|
|
67
|
+
* guarded by the stat-derived revision, so an immediate cold-read handoff
|
|
68
|
+
* (observation then resume) parses the artifact once. Every local mutation
|
|
69
|
+
* for an id invalidates its entry; a foreign write misses through the
|
|
70
|
+
* revision guard.
|
|
71
|
+
*/
|
|
72
|
+
private readonly coldLogMemo;
|
|
73
|
+
/** One joinable decode/migration operation per selected historical Session file revision. */
|
|
74
|
+
private readonly migrationPreparations;
|
|
68
75
|
constructor(ctx: Context, config: Config);
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
append(id: SessionId, events: readonly SessionEvent[]): Promise<void>;
|
|
74
|
-
prepare(id: SessionId, signal?: AbortSignal): Promise<SessionPreparation>;
|
|
75
|
-
load(id: SessionId): Promise<SessionInspection>;
|
|
76
|
-
inspect(id: SessionId, signal?: AbortSignal): Promise<SessionInspection>;
|
|
77
|
-
borrowSession(id: SessionId, signal?: AbortSignal): Promise<BorrowedSessionSource>;
|
|
78
|
-
readFrom(id: SessionId, fromSeq: SessionLogOffset, signal?: AbortSignal): Promise<SessionEventSuffix>;
|
|
79
|
-
/** Read a stored prefix by id across all project directories when cwd is unknown. */
|
|
80
|
-
loadStored(id: SessionId, signal?: AbortSignal): Promise<StoredPrefix<JsonlTornMarker> | undefined>;
|
|
81
|
-
/**
|
|
82
|
-
* Read one log's stat-derived revision without loading its event bytes.
|
|
83
|
-
* Resolving an id with unknown cwd still scans the project directories.
|
|
84
|
-
*/
|
|
85
|
-
readStoredRevision(id: SessionId, signal?: AbortSignal): Promise<PersistenceRevision | undefined>;
|
|
86
|
-
/**
|
|
87
|
-
* Read a session's stored artifact text verbatim: the durable file bytes
|
|
88
|
-
* decoded from this backend's physical encoding (complete zstd frames
|
|
89
|
-
* concatenated, or UTF-8 plaintext). The content is the exact JSONL text the
|
|
90
|
-
* backend wrote — never a reconstruction from parsed events — so packed-
|
|
91
|
-
* chunk rows, key order, and line breaks survive byte-for-byte. A torn
|
|
92
|
-
* final frame is omitted, matching the committed-prefix semantics of every
|
|
93
|
-
* other read.
|
|
94
|
-
* @param id - the persisted session to read.
|
|
95
|
-
* @param signal - optional cancellation for the stat/read/decode work.
|
|
96
|
-
* @returns the raw artifact text plus the header parsed from its own first
|
|
97
|
-
* line, or `undefined` when the session has no stored artifact.
|
|
76
|
+
/**
|
|
77
|
+
* Refusal-diagnostics hook: the absolute target path, without touching the filesystem.
|
|
78
|
+
* @param meta - the stored header naming the session and its cwd.
|
|
79
|
+
* @returns the artifact kind and absolute path.
|
|
98
80
|
*/
|
|
99
|
-
|
|
81
|
+
private locate;
|
|
100
82
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
83
|
+
* Create a new stored session and take its write ownership. The session is
|
|
84
|
+
* visible to this process immediately; the physical artifact appears on the
|
|
85
|
+
* first append or flush.
|
|
86
|
+
* @param header - the immutable header to store; must be losslessly
|
|
87
|
+
* JSON-serializable with a non-negative safe-integer `createdAt`.
|
|
88
|
+
* @param options - optional cancellation.
|
|
89
|
+
* @returns the owned write handle.
|
|
90
|
+
*/
|
|
91
|
+
create(header: SessionHeader, options?: SessionPersistenceCreateOptions): Promise<SessionHandle>;
|
|
92
|
+
/**
|
|
93
|
+
* Open an existing stored session for `read` or single-writer `write`.
|
|
94
|
+
* @param id - the stored session to open.
|
|
95
|
+
* @param access - `read` (no ownership) or `write` (atomic in-process claim).
|
|
96
|
+
* @param options - optional cancellation.
|
|
97
|
+
* @returns the open handle.
|
|
98
|
+
*/
|
|
99
|
+
open(id: SessionId, access: SessionAccess, options?: SessionPersistenceOpenOptions): Promise<SessionHandle>;
|
|
100
|
+
/**
|
|
101
|
+
* Flush every active write handle in one durability barrier; see the seam
|
|
102
|
+
* contract.
|
|
103
|
+
* @returns resolution once every write handle active at the call has flushed.
|
|
104
|
+
*/
|
|
105
|
+
flush(): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Observe one stored session without reading its event log.
|
|
108
|
+
* @param id - the stored session to observe.
|
|
109
|
+
* @param options - optional cancellation.
|
|
110
|
+
* @returns the snapshot (`sizeBytes` carries the physical artifact size), or
|
|
111
|
+
* `undefined` when the session does not exist.
|
|
112
|
+
*/
|
|
113
|
+
stat(id: SessionId, options?: SessionPersistenceStatOptions): Promise<SessionPersistenceSnapshot | undefined>;
|
|
114
|
+
/**
|
|
115
|
+
* List every stored session visible to this process: materialized artifacts
|
|
116
|
+
* plus this process's created-but-unmaterialized sessions.
|
|
117
|
+
* @param options - optional cancellation.
|
|
118
|
+
* @returns one snapshot per session, in no promised order.
|
|
119
|
+
*/
|
|
120
|
+
list(options?: SessionPersistenceListOptions): Promise<readonly SessionPersistenceSnapshot[]>;
|
|
121
|
+
/** Resolve and read one stored log, refusing loudly when the artifact is absent. */
|
|
122
|
+
private requireStoredLog;
|
|
123
|
+
/** Probe the memo and otherwise decode one historical generation under backend cancellation. */
|
|
124
|
+
private loadStoredMigration;
|
|
125
|
+
/** Await shared preparation for one caller and abort it only after its last waiter leaves. */
|
|
126
|
+
private waitForPreparation;
|
|
127
|
+
/** Decode one historical generation without publishing a successor. */
|
|
128
|
+
private prepareStoredMigration;
|
|
129
|
+
/** Publish a prepared historical log before granting write access. */
|
|
130
|
+
private publishStoredMigration;
|
|
131
|
+
/** Translate generation-layer failures into the persistence seam's error vocabulary. */
|
|
132
|
+
private generationFailure;
|
|
133
|
+
/**
|
|
134
|
+
* Read, parse, and validate one stored log as the current logical prefix.
|
|
104
135
|
* @param path - the artifact file to read.
|
|
105
|
-
* @param
|
|
106
|
-
* @
|
|
136
|
+
* @param expectedId - the session identity the artifact must carry.
|
|
137
|
+
* @param signal - optional cancellation for the stat/read/decode work.
|
|
138
|
+
* @returns the validated stored log with any torn-tail truncation point.
|
|
139
|
+
*/
|
|
140
|
+
readStoredLog(path: string, expectedId: SessionId, signal?: AbortSignal): Promise<CurrentStoredLog>;
|
|
141
|
+
/** Decode and memoize one already-stable current physical snapshot. */
|
|
142
|
+
private decodeStoredLog;
|
|
143
|
+
/** Insert one parsed log into the bounded handoff cache. */
|
|
144
|
+
private memoizeStoredLog;
|
|
145
|
+
/**
|
|
146
|
+
* Resolve a session's current-generation log path.
|
|
147
|
+
* @param id - the stored session to locate.
|
|
148
|
+
* @param signal - optional cancellation for the directory scans.
|
|
149
|
+
* @returns the current artifact path, or `undefined` while only a historical generation exists.
|
|
150
|
+
*/
|
|
151
|
+
resolveCurrentLog(id: SessionId, signal?: AbortSignal): Promise<string | undefined>;
|
|
152
|
+
/**
|
|
153
|
+
* Durably append one validated batch; lazily materializes on the first write.
|
|
154
|
+
* @param header - the session's stored header.
|
|
155
|
+
* @param events - the validated contiguous batch, in seq order.
|
|
156
|
+
* @param isMaterialized - whether the session already has a durable artifact.
|
|
157
|
+
* @param inheritedEventCount - the exact fork-inherited prefix length written into a materializing header line.
|
|
158
|
+
*/
|
|
159
|
+
persistBatch(header: SessionHeader, events: readonly SessionEvent[], isMaterialized: boolean, inheritedEventCount: SessionLogOffsetType): Promise<void>;
|
|
160
|
+
/**
|
|
161
|
+
* Materialize a header-only artifact for an explicitly durable empty session.
|
|
162
|
+
* @param header - the session's stored header.
|
|
163
|
+
* @param inheritedEventCount - the exact fork-inherited prefix length written into the header line.
|
|
164
|
+
*/
|
|
165
|
+
persistHeader(header: SessionHeader, inheritedEventCount: SessionLogOffsetType): Promise<void>;
|
|
166
|
+
/**
|
|
167
|
+
* Truncate a torn physical tail durably before this session's first new append.
|
|
168
|
+
* @param header - the session's stored header.
|
|
169
|
+
* @param truncateTo - the byte offset the artifact is truncated to.
|
|
170
|
+
*/
|
|
171
|
+
truncateTornTail(header: SessionHeader, truncateTo: number): Promise<void>;
|
|
172
|
+
/**
|
|
173
|
+
* Whether this process still tracks a created-but-unmaterialized session.
|
|
174
|
+
* @param id - the session to test.
|
|
175
|
+
* @returns true while the pending entry exists.
|
|
176
|
+
*/
|
|
177
|
+
hasPendingSession(id: SessionId): boolean;
|
|
178
|
+
/**
|
|
179
|
+
* Release one handle's backend bookkeeping on close.
|
|
180
|
+
* @param handle - the closing handle.
|
|
181
|
+
* @param materialized - whether the session reached durable storage.
|
|
107
182
|
*/
|
|
108
|
-
|
|
183
|
+
releaseHandle(handle: JsonlSessionHandle, materialized: boolean): void;
|
|
109
184
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
185
|
+
* Acquire the session directory's kernel write lock; the kernel holds it
|
|
186
|
+
* until the handle's close releases the descriptor, including on process death.
|
|
187
|
+
* @param id - the session the lock guards.
|
|
188
|
+
* @param cwd - header cwd used to derive the directory for a fresh session.
|
|
189
|
+
* @param dir - the resolved directory of an existing artifact, when known.
|
|
190
|
+
* @returns the held lock.
|
|
112
191
|
*/
|
|
113
|
-
private
|
|
192
|
+
private acquireLease;
|
|
193
|
+
/**
|
|
194
|
+
* Acquire the cross-process write lock for a materializing created session,
|
|
195
|
+
* called by its handle immediately before the first log bytes publish.
|
|
196
|
+
* @param header - the session's stored header (its cwd derives the directory).
|
|
197
|
+
* @returns the held lock.
|
|
198
|
+
*/
|
|
199
|
+
acquireWriteLease(header: SessionHeader): Promise<SessionWriteLease>;
|
|
114
200
|
/** Decode complete frames and retain complete JSONL records from a torn final frame. */
|
|
115
201
|
private readZstdPrefix;
|
|
116
|
-
/** Durably append a batch, lazily materializing the file when not yet present. */
|
|
117
|
-
appendBatch(storage: SessionStorageMetadata, events: readonly SessionEvent[], isMaterialized: boolean): Promise<void>;
|
|
118
|
-
/** Materialize a header-only JSONL artifact for an explicitly durable empty session. */
|
|
119
|
-
materializeHeader(storage: SessionStorageMetadata): Promise<void>;
|
|
120
|
-
/**
|
|
121
|
-
* Make a crash repair durable: truncate a torn tail, restore complete events
|
|
122
|
-
* decoded from it, then append synthetic closers. Two fsync'd steps — the seam
|
|
123
|
-
* does not require this to be atomic.
|
|
124
|
-
*/
|
|
125
|
-
commitRepair(storage: SessionStorageMetadata, tornMarker: JsonlTornMarker | undefined, closers: readonly SessionEvent[]): Promise<void>;
|
|
126
|
-
/** List valid unique stored sessions' metadata (header line only — no full-log parse). */
|
|
127
|
-
list(signal?: AbortSignal): Promise<SessionHeader[]>;
|
|
128
|
-
/** List metadata plus a stat-derived identity for each append-only log. */
|
|
129
|
-
listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]>;
|
|
130
202
|
private listArtifacts;
|
|
203
|
+
/** Read and translate one selected generation header without inspecting its body. */
|
|
204
|
+
private readGenerationHeader;
|
|
205
|
+
/** Convert format-catalog string identities to current branded Session metadata. */
|
|
206
|
+
private currentHeader;
|
|
131
207
|
/** Atomically write the header line + first batch (temp-write, fsync, publish). */
|
|
132
208
|
private materialize;
|
|
133
209
|
private materializePosix;
|
|
@@ -157,12 +233,16 @@ export declare class JsonlSessionPersistence extends SessionPersistence implemen
|
|
|
157
233
|
private readFirstLine;
|
|
158
234
|
/** Read and validate only the independently compressed header frame. */
|
|
159
235
|
private readFirstZstdLine;
|
|
160
|
-
/**
|
|
236
|
+
/** Select the numerically highest canonical generation in one Session directory. */
|
|
237
|
+
private resolveGenerationInDirectory;
|
|
238
|
+
/** Find the unique authoritative generation for an id across project directories. */
|
|
161
239
|
private findLog;
|
|
162
240
|
/** Require an existing configured root to be a readable directory. */
|
|
163
241
|
private assertUsableRoot;
|
|
164
242
|
/** Reject metadata that does not identify the selected physical log. */
|
|
165
243
|
private assertStoredIdentity;
|
|
244
|
+
/** Validate a supported historical header against the selected source path. */
|
|
245
|
+
private validateSourceIdentity;
|
|
166
246
|
/**
|
|
167
247
|
* Whether two path spellings resolve to the same physical file. This admits
|
|
168
248
|
* case aliases on case-insensitive filesystems without weakening identity
|
|
@@ -178,11 +258,18 @@ export declare class JsonlSessionPersistence extends SessionPersistence implemen
|
|
|
178
258
|
private checkRootEncoding;
|
|
179
259
|
private rejectLegacyFlatArtifact;
|
|
180
260
|
private rejectOppositeArtifact;
|
|
261
|
+
/** Return the highest canonical generation encoded with the other configured suffix. */
|
|
262
|
+
private findOppositeGenerationInDirectory;
|
|
181
263
|
private oppositeCompression;
|
|
182
264
|
private encodingMismatch;
|
|
183
265
|
private legacyLayout;
|
|
184
266
|
private exists;
|
|
185
267
|
private assertLogParentAllowsAbsence;
|
|
186
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* One open channel onto a JSONL-stored session: the shared storage-handle
|
|
271
|
+
* scaffolding over this backend's file primitives. Reads re-scan the artifact
|
|
272
|
+
* under the stable-read loop.
|
|
273
|
+
*/
|
|
187
274
|
export default JsonlSessionPersistence;
|
|
188
275
|
//# sourceMappingURL=index.d.ts.map
|