@morlay/session-rdb 0.0.11 → 0.0.13

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.
Files changed (56) hide show
  1. package/README.md +0 -16
  2. package/dist/artifact.d.mts +64 -0
  3. package/dist/artifact.mjs +3 -0
  4. package/dist/import-D2vUnUZe.mjs +438 -0
  5. package/dist/import.d.mts +24 -0
  6. package/dist/import.mjs +2 -0
  7. package/dist/index-uUvn6gYp.d.mts +111 -0
  8. package/dist/index.d.mts +3 -0
  9. package/dist/index.mjs +466 -0
  10. package/dist/invariant.d.mts +7 -0
  11. package/dist/invariant.mjs +8 -0
  12. package/dist/magic-string.es-BWtG0AyA.mjs +1017 -0
  13. package/dist/schema-B-8G4lWc.mjs +852 -0
  14. package/dist/schema-CFXZURX7.d.mts +116 -0
  15. package/dist/sqlite-DIXY-dFZ.mjs +356 -0
  16. package/dist/storage.d.mts +44 -0
  17. package/dist/storage.mjs +3 -0
  18. package/dist/testing.d.mts +31 -0
  19. package/dist/testing.mjs +16051 -0
  20. package/package.json +39 -22
  21. package/src/adapters/ddl.ts +77 -0
  22. package/src/adapters/index.ts +4 -0
  23. package/src/adapters/to-postgres.ts +91 -0
  24. package/src/adapters/to-sqlite.ts +79 -0
  25. package/src/artifact.ts +4 -0
  26. package/src/backend.ts +112 -0
  27. package/src/branch.ts +508 -0
  28. package/src/entities/events.ts +27 -0
  29. package/src/entities/index.ts +30 -0
  30. package/src/entities/persistence-state.ts +10 -0
  31. package/src/entities/schema-meta.ts +9 -0
  32. package/src/entities/session-events.ts +29 -0
  33. package/src/entities/sessions.ts +20 -0
  34. package/src/entities/types.ts +48 -0
  35. package/src/import.ts +270 -0
  36. package/src/index.ts +547 -0
  37. package/src/invariant.ts +15 -0
  38. package/src/log.ts +456 -0
  39. package/src/migrate.ts +137 -0
  40. package/src/postgres.ts +369 -0
  41. package/src/schema.ts +234 -0
  42. package/src/sqlite.ts +412 -0
  43. package/src/storage.ts +5 -0
  44. package/src/testing/contract.ts +562 -0
  45. package/src/testing/coordinator-contract.ts +1723 -0
  46. package/src/testing/helpers.ts +20 -0
  47. package/src/testing.ts +12 -0
  48. package/src/write-guard.ts +27 -0
  49. package/lib/index.d.mts +0 -558
  50. package/lib/index.d.mts.map +0 -1
  51. package/lib/index.mjs +0 -2176
  52. package/lib/index.mjs.map +0 -1
  53. package/lib/invariant.d.mts +0 -15
  54. package/lib/invariant.d.mts.map +0 -1
  55. package/lib/invariant.mjs +0 -21
  56. package/lib/invariant.mjs.map +0 -1
@@ -0,0 +1,116 @@
1
+ import { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
2
+ import { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
3
+ //#region src/backend.d.ts
4
+ interface SessionRow {
5
+ fSessionId: string;
6
+ fHeadEventId: string;
7
+ fHeadSequence: number;
8
+ fVersion: number;
9
+ fCreatedAt: number;
10
+ fCwd: string | null;
11
+ fParentSession: string | null;
12
+ fSeedLength: number | null;
13
+ fOrigin: string | null;
14
+ fDelegationDepth: number | null;
15
+ fIncarnation: string;
16
+ fRevision: number;
17
+ }
18
+ interface EventInsert {
19
+ fEventId: string;
20
+ fParentId: string;
21
+ fType: string;
22
+ fKind: string;
23
+ fRole: string;
24
+ fName: string;
25
+ fActionId: string;
26
+ fEncoding: string;
27
+ fData: string;
28
+ fCreatedAt: number;
29
+ }
30
+ interface EventRow {
31
+ fEventId: string;
32
+ fSequence: number;
33
+ fOriginalSeq: number;
34
+ fType: string;
35
+ fKind: string;
36
+ fRole: string;
37
+ fName: string;
38
+ fActionId: string;
39
+ fCreatedAt: number;
40
+ fData: string;
41
+ fSurfaceOp: string | null;
42
+ }
43
+ interface BackendTx {
44
+ upsertSession(storage: SessionStorageMetadata, incarnation: string): Promise<void>;
45
+ getHead(id: SessionId): Promise<Pick<SessionRow, "fHeadEventId" | "fHeadSequence">>;
46
+ getSeedLength(id: SessionId): Promise<number | null>;
47
+ updateSeedLength(id: SessionId, seedLength: number): Promise<void>;
48
+ insertEvents(events: EventInsert[]): Promise<void>;
49
+ insertBridges(rows: Array<{
50
+ fSessionId: SessionId;
51
+ fEventId: string;
52
+ fSequence: number;
53
+ fOriginalSeq: number;
54
+ fSurfaceOp: string | null;
55
+ }>): Promise<void>;
56
+ updateHead(id: SessionId, headEventId: string, headSequence: number): Promise<void>;
57
+ bumpRevision(id: SessionId): Promise<void>;
58
+ deleteBridgeTail(id: SessionId, fromSequence: number): Promise<void>;
59
+ getPrevBridge(id: SessionId, sequence: number): Promise<{
60
+ fEventId: string;
61
+ fSequence: number;
62
+ } | undefined>;
63
+ getLastBridge(id: SessionId): Promise<{
64
+ fEventId: string;
65
+ fSequence: number;
66
+ } | undefined>;
67
+ }
68
+ interface Backend {
69
+ readonly kind: "sqlite" | "postgres";
70
+ readonly storeIdentity: string;
71
+ open(): Promise<void>;
72
+ getSession(id: SessionId): Promise<SessionRow | undefined>;
73
+ getSeqMapRows(id: SessionId): Promise<Array<{
74
+ fSequence: number;
75
+ fOriginalSeq: number;
76
+ }>>;
77
+ getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]>;
78
+ listSessions(): Promise<SessionRow[]>;
79
+ transaction<T>(fn: (tx: BackendTx) => Promise<T>): Promise<T>;
80
+ close(): Promise<void>;
81
+ }
82
+ //#endregion
83
+ //#region src/write-guard.d.ts
84
+ declare class WriteGuard {
85
+ private readonly headSeqs;
86
+ confirmHead(id: SessionId, head: number): void;
87
+ assertNoConcurrentWriter(id: SessionId, storedHead: number): void;
88
+ }
89
+ //#endregion
90
+ //#region src/schema.d.ts
91
+ declare const SCHEMA_VERSION = 2;
92
+ declare const SESSION_PERSISTENCE_SQLITE_APPLICATION_ID = 1146308688;
93
+ declare const EPHEMERAL_EVENT_TYPES: readonly ["assistant/chunk"];
94
+ declare const EVENT_ENCODING = "json";
95
+ declare const tPersistenceState: any;
96
+ declare const tSessions: any;
97
+ declare const tEvents: any;
98
+ declare const tSessionEvents: any;
99
+ type JournalMode = "wal" | "delete" | "truncate" | "persist";
100
+ declare const DEFAULT_BUSY_TIMEOUT_MS = 5000;
101
+ declare function isEphemeralType(type: string): boolean;
102
+ declare function isPersistedEvent(event: SessionEvent): boolean;
103
+ type EventKind = "message" | "thinking" | "turn" | "tool" | "request" | "config" | "audit" | "lifecycle" | "inbox" | "compaction" | "llm" | "subagent" | "team" | "workflow" | "goal" | "schedule" | "todo" | "web";
104
+ type EventRole = "user" | "assistant" | "tool";
105
+ declare function eventKind(event: {
106
+ type: string;
107
+ data?: unknown;
108
+ }): EventKind | "";
109
+ declare function eventDimensions(event: SessionEvent): {
110
+ kind: string;
111
+ role: string;
112
+ name: string;
113
+ actionId: string;
114
+ };
115
+ //#endregion
116
+ export { WriteGuard as _, EventRole as a, EventRow as b, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID as c, isEphemeralType as d, isPersistedEvent as f, tSessions as g, tSessionEvents as h, EventKind as i, eventDimensions as l, tPersistenceState as m, EPHEMERAL_EVENT_TYPES as n, JournalMode as o, tEvents as p, EVENT_ENCODING as r, SCHEMA_VERSION as s, DEFAULT_BUSY_TIMEOUT_MS as t, eventKind as u, Backend as v, SessionRow as x, BackendTx as y };
@@ -0,0 +1,356 @@
1
+ import { O as sessionConflictRow, a as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, d as tPersistenceState, f as tSessionEvents, h as sqliteTableDefs, k as sessionInsertRow, o as eventDimensions, p as tSessions, t as DEFAULT_BUSY_TIMEOUT_MS, u as tEvents } from "./schema-B-8G4lWc.mjs";
2
+ import { randomUUID } from "node:crypto";
3
+ import { and, desc, eq, gte, sql } from "drizzle-orm";
4
+ import { statSync } from "node:fs";
5
+ import { mkdir, open } from "node:fs/promises";
6
+ import { dirname, resolve } from "node:path";
7
+ import { DatabaseSync } from "node:sqlite";
8
+ import { drizzle } from "drizzle-orm/node-sqlite";
9
+ //#region src/write-guard.ts
10
+ var WriteGuard = class {
11
+ headSeqs = /* @__PURE__ */ new Map();
12
+ confirmHead(id, head) {
13
+ this.headSeqs.set(id, head);
14
+ }
15
+ assertNoConcurrentWriter(id, storedHead) {
16
+ const known = this.headSeqs.get(id);
17
+ if (known === void 0) {
18
+ if (storedHead !== -1) throw new Error(`session "${id}" has a persisted log this instance has not read; another writer may own it — load the session first`);
19
+ return;
20
+ }
21
+ if (known !== storedHead) throw new Error(`session "${id}" was modified by another writer (stored head ${storedHead}, this instance last confirmed head ${known}); concurrent writers on one session are not supported`);
22
+ }
23
+ };
24
+ //#endregion
25
+ //#region src/adapters/ddl.ts
26
+ function sqlType(dialect, type) {
27
+ switch (type) {
28
+ case "serial": return dialect === "sqlite" ? "INTEGER" : "SERIAL";
29
+ case "integer": return "INTEGER";
30
+ case "bigint": return dialect === "sqlite" ? "INTEGER" : "BIGINT";
31
+ case "text": return "TEXT";
32
+ }
33
+ }
34
+ function literal(value) {
35
+ return typeof value === "string" ? `'${value.replace(/'/g, "''")}'` : String(value);
36
+ }
37
+ function quote(name) {
38
+ return `"${name}"`;
39
+ }
40
+ function columnSql(dialect, c) {
41
+ let sql = `${quote(c.name)} ${sqlType(dialect, c.type)}`;
42
+ if (c.primaryKey) sql += " PRIMARY KEY";
43
+ if (c.type === "serial" && dialect === "sqlite") sql += " AUTOINCREMENT";
44
+ if (c.notNull) sql += " NOT NULL";
45
+ if (c.default !== void 0) sql += ` DEFAULT ${literal(c.default)}`;
46
+ if (c.unique) sql += " UNIQUE";
47
+ if (c.references) {
48
+ sql += ` REFERENCES ${quote(c.references.table)}(${quote(c.references.column)})`;
49
+ if (c.references.onDelete) sql += ` ON DELETE ${c.references.onDelete.toUpperCase()}`;
50
+ }
51
+ return sql;
52
+ }
53
+ function createTableSql(dialect, def, schema) {
54
+ const parts = def.columns.map((c) => columnSql(dialect, c));
55
+ for (const ck of def.checks ?? []) parts.push(`CHECK (${ck.expression})`);
56
+ for (const u of def.uniques ?? []) parts.push(`UNIQUE (${u.columns.map(quote).join(", ")})`);
57
+ const strict = dialect === "sqlite" ? " STRICT" : "";
58
+ return `CREATE TABLE IF NOT EXISTS ${schema === void 0 || schema === "public" ? quote(def.name) : `${quote(schema)}.${quote(def.name)}`} (\n ${parts.join(",\n ")}\n)${strict}`;
59
+ }
60
+ function createIndexSql(def, name, schema) {
61
+ const idx = def.indexes?.find((i) => i.name === name);
62
+ if (idx === void 0) throw new Error(`unknown index "${name}" on table "${def.name}"`);
63
+ const qualified = schema === void 0 || schema === "public" ? quote(def.name) : `${quote(schema)}.${quote(def.name)}`;
64
+ return `CREATE INDEX IF NOT EXISTS ${quote(idx.name)} ON ${qualified}(${idx.columns.map(quote).join(", ")})`;
65
+ }
66
+ function createTablesSql(dialect, defs, schema) {
67
+ const statements = [];
68
+ for (const def of defs) {
69
+ statements.push(createTableSql(dialect, def, schema));
70
+ for (const idx of def.indexes ?? []) statements.push(createIndexSql(def, idx.name, schema));
71
+ }
72
+ return statements;
73
+ }
74
+ function migrateSqliteV1ToV2(db) {
75
+ const { user_version: onDisk } = db.prepare("PRAGMA user_version").get();
76
+ if (onDisk === 2) return 0;
77
+ if (onDisk !== 1) throw new Error(`unsupported schema version ${onDisk} (expected 1)`);
78
+ db.exec(`
79
+ CREATE TABLE t_events_v2 (
80
+ f_id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ f_event_id TEXT NOT NULL UNIQUE,
82
+ f_parent_id TEXT NOT NULL DEFAULT '',
83
+ f_type TEXT NOT NULL DEFAULT '',
84
+ f_kind TEXT NOT NULL DEFAULT '',
85
+ f_role TEXT NOT NULL DEFAULT '',
86
+ f_name TEXT NOT NULL DEFAULT '',
87
+ f_action_id TEXT NOT NULL DEFAULT '',
88
+ f_encoding TEXT NOT NULL DEFAULT '',
89
+ f_data TEXT NOT NULL,
90
+ f_created_at INTEGER NOT NULL DEFAULT 0
91
+ ) STRICT;
92
+ CREATE TABLE t_session_events_v2 (
93
+ f_id INTEGER PRIMARY KEY AUTOINCREMENT,
94
+ f_session_id TEXT NOT NULL REFERENCES t_sessions(f_session_id) ON DELETE CASCADE,
95
+ f_event_id TEXT NOT NULL REFERENCES t_events_v2(f_event_id) ON DELETE CASCADE,
96
+ f_sequence INTEGER NOT NULL,
97
+ f_original_seq INTEGER NOT NULL,
98
+ f_surface_op TEXT,
99
+ UNIQUE (f_session_id, f_sequence)
100
+ ) STRICT;
101
+ CREATE INDEX idx_events_v2_kind ON t_events_v2(f_kind);
102
+ CREATE INDEX idx_events_v2_role ON t_events_v2(f_role);
103
+ CREATE INDEX idx_events_v2_name ON t_events_v2(f_name);
104
+ CREATE INDEX idx_events_v2_action_id ON t_events_v2(f_action_id);
105
+ CREATE INDEX idx_session_events_v2_event_id ON t_session_events_v2(f_event_id);
106
+ `);
107
+ const oldEvents = db.prepare(`SELECT f_event_id, f_parent_id, f_kind, f_data, f_created_at FROM t_events ORDER BY f_id`).all();
108
+ const insertEvent = db.prepare(`
109
+ INSERT INTO t_events_v2
110
+ (f_event_id, f_parent_id, f_type, f_kind, f_role, f_name, f_action_id, f_encoding, f_data, f_created_at)
111
+ VALUES (?, ?, ?, ?, ?, ?, ?, 'json', ?, ?)
112
+ `);
113
+ for (const row of oldEvents) {
114
+ let data;
115
+ try {
116
+ data = JSON.parse(row.f_data);
117
+ } catch {
118
+ throw new Error(`t_events row ${row.f_event_id} has unparsable f_data; aborting`);
119
+ }
120
+ const dims = eventDimensions({
121
+ type: row.f_kind,
122
+ seq: 0,
123
+ time: row.f_created_at,
124
+ data
125
+ });
126
+ insertEvent.run(row.f_event_id, row.f_parent_id, row.f_kind, dims.kind, dims.role, dims.name, dims.actionId, row.f_data, row.f_created_at);
127
+ }
128
+ const oldBridges = db.prepare(`SELECT se.f_session_id, se.f_event_id, se.f_sequence
129
+ FROM t_session_events se ORDER BY se.f_id`).all();
130
+ const oldEventMeta = new Map(db.prepare(`SELECT f_event_id, f_original_seq, f_surface_op FROM t_events`).all().map((row) => [row.f_event_id, row]));
131
+ const insertBridge = db.prepare(`
132
+ INSERT INTO t_session_events_v2
133
+ (f_session_id, f_event_id, f_sequence, f_original_seq, f_surface_op)
134
+ VALUES (?, ?, ?, ?, ?)
135
+ `);
136
+ for (const row of oldBridges) {
137
+ const meta = oldEventMeta.get(row.f_event_id);
138
+ if (meta === void 0) throw new Error(`bridge row ${row.f_session_id}:${row.f_sequence} references missing event ${row.f_event_id}`);
139
+ insertBridge.run(row.f_session_id, row.f_event_id, row.f_sequence, meta.f_original_seq, meta.f_surface_op);
140
+ }
141
+ db.exec(`
142
+ DROP TABLE t_session_events;
143
+ DROP TABLE t_events;
144
+ ALTER TABLE t_events_v2 RENAME TO t_events;
145
+ ALTER TABLE t_session_events_v2 RENAME TO t_session_events;
146
+ `);
147
+ db.exec(`PRAGMA user_version = 2`);
148
+ return oldEvents.length;
149
+ }
150
+ //#endregion
151
+ //#region src/sqlite.ts
152
+ const sqliteTxQueues = /* @__PURE__ */ new Map();
153
+ function enqueueSqliteTx(path, fn) {
154
+ const run = (sqliteTxQueues.get(path) ?? Promise.resolve()).then(fn);
155
+ sqliteTxQueues.set(path, run.then(() => void 0, () => void 0));
156
+ return run;
157
+ }
158
+ async function createDatabaseFile(path) {
159
+ try {
160
+ await (await open(path, "wx", 384)).close();
161
+ } catch (error) {
162
+ if (error.code !== "EEXIST") throw error;
163
+ }
164
+ }
165
+ function openDatabase(path, journalMode, busyTimeout = DEFAULT_BUSY_TIMEOUT_MS) {
166
+ const db = new DatabaseSync(path);
167
+ try {
168
+ configureDatabase(db, path, journalMode, busyTimeout);
169
+ return db;
170
+ } catch (error) {
171
+ db.close();
172
+ throw error;
173
+ }
174
+ }
175
+ function configureDatabase(db, path, journalMode, busyTimeout) {
176
+ db.exec("PRAGMA foreign_keys = ON");
177
+ db.exec(`PRAGMA busy_timeout = ${busyTimeout}`);
178
+ drizzle({ client: db }).transaction((tx) => {
179
+ const { user_version: onDisk } = tx.get(sql`PRAGMA user_version`);
180
+ const { application_id: applicationId } = tx.get(sql`PRAGMA application_id`);
181
+ const { count: userObjectCount } = tx.get(sql`SELECT COUNT(*) AS count FROM sqlite_schema WHERE name NOT GLOB 'sqlite_*'`);
182
+ if (onDisk === 0 && (applicationId !== 0 || userObjectCount > 0)) throw new Error(`session database at "${path}" has an unversioned schema or application identity`);
183
+ if (onDisk === 1) migrateSqliteV1ToV2(db);
184
+ else if (onDisk !== 0 && onDisk !== 2) throw new Error(`session database at "${path}" has schema version ${onDisk}, incompatible with this build (2)`);
185
+ if ((onDisk === 2 || onDisk === 1) && applicationId !== 1146308688) throw new Error(`session database at "${path}" has application id ${applicationId}, expected ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`);
186
+ for (const statement of createTablesSql("sqlite", sqliteTableDefs)) tx.run(sql.raw(statement));
187
+ tx.insert(tPersistenceState).values({
188
+ fSingleton: 1,
189
+ fStoreId: randomUUID()
190
+ }).onConflictDoNothing().run();
191
+ if (onDisk === 0) {
192
+ tx.run(sql.raw(`PRAGMA application_id = ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`));
193
+ tx.run(sql.raw(`PRAGMA user_version = 2`));
194
+ }
195
+ }, { behavior: "immediate" });
196
+ db.exec(`PRAGMA journal_mode = ${journalMode.toUpperCase()}`);
197
+ }
198
+ var SqliteBackend = class SqliteBackend {
199
+ options;
200
+ kind = "sqlite";
201
+ storeIdentity;
202
+ dbPath = "";
203
+ db;
204
+ constructor(options) {
205
+ this.options = options;
206
+ }
207
+ async open() {
208
+ const actual = this.options.path === ":memory:" ? this.options.path : resolve(this.options.path);
209
+ this.dbPath = actual;
210
+ if (actual !== ":memory:") {
211
+ await mkdir(dirname(actual), {
212
+ recursive: true,
213
+ mode: 448
214
+ });
215
+ await createDatabaseFile(actual);
216
+ }
217
+ await enqueueSqliteTx(actual, async () => {
218
+ this.db = drizzle({ client: openDatabase(actual, this.options.journalMode, this.options.busyTimeout) });
219
+ });
220
+ try {
221
+ const row = this.db.select({ fStoreId: tPersistenceState.fStoreId }).from(tPersistenceState).where(eq(tPersistenceState.fSingleton, 1)).get();
222
+ /* v8 ignore next -- openDatabase inserts the singleton before returning. */
223
+ if (row === void 0) throw new Error(`session database at "${actual}" has no store identity`);
224
+ if (row.fStoreId.length === 0) throw new Error(`session database at "${actual}" has no valid store identity`);
225
+ if (actual !== ":memory:") {
226
+ const identity = statSync(actual, { bigint: true });
227
+ this.storeIdentity = `file:${identity.dev}:${identity.ino}:${identity.birthtimeNs}:store:${row.fStoreId}`;
228
+ } else this.storeIdentity = `memory:store:${row.fStoreId}`;
229
+ } catch (error) {
230
+ this.db.$client.close();
231
+ throw error;
232
+ }
233
+ }
234
+ async close() {
235
+ if (this.db === void 0) return;
236
+ this.db.$client.close();
237
+ }
238
+ async getSession(id) {
239
+ return this.db.select().from(tSessions).where(eq(tSessions.fSessionId, id)).get();
240
+ }
241
+ async getSeqMapRows(id) {
242
+ return this.db.select({
243
+ fSequence: tSessionEvents.fSequence,
244
+ fOriginalSeq: tSessionEvents.fOriginalSeq
245
+ }).from(tSessionEvents).where(eq(tSessionEvents.fSessionId, id)).all();
246
+ }
247
+ async getEventRows(id, fromSequence) {
248
+ return (fromSequence === void 0 ? this.eventRows().where(eq(tSessionEvents.fSessionId, id)) : this.eventRows().where(and(eq(tSessionEvents.fSessionId, id), gte(tSessionEvents.fSequence, fromSequence)))).orderBy(tSessionEvents.fSequence).all();
249
+ }
250
+ async listSessions() {
251
+ return this.db.select().from(tSessions).all();
252
+ }
253
+ async transaction(fn) {
254
+ return enqueueSqliteTx(this.dbPath, async () => {
255
+ this.db.$client.exec("BEGIN IMMEDIATE");
256
+ try {
257
+ const result = await fn(this.tx);
258
+ this.db.$client.exec("COMMIT");
259
+ return result;
260
+ } catch (error) {
261
+ /* v8 ignore start */
262
+ try {
263
+ this.db.$client.exec("ROLLBACK");
264
+ } catch {}
265
+ throw error;
266
+ }
267
+ });
268
+ }
269
+ tx = {
270
+ upsertSession: (storage, incarnation) => this.upsertSession(storage, incarnation),
271
+ getHead: (id) => this.getHead(id),
272
+ getSeedLength: (id) => this.getSeedLength(id),
273
+ updateSeedLength: (id, seedLength) => this.updateSeedLength(id, seedLength),
274
+ insertEvents: (events) => this.insertEvents(events),
275
+ insertBridges: (rows) => this.insertBridges(rows),
276
+ updateHead: (id, headEventId, headSequence) => this.updateHead(id, headEventId, headSequence),
277
+ bumpRevision: (id) => this.bumpRevision(id),
278
+ deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(id, fromSequence),
279
+ getPrevBridge: (id, sequence) => this.getPrevBridge(id, sequence),
280
+ getLastBridge: (id) => this.getLastBridge(id)
281
+ };
282
+ async upsertSession(storage, incarnation) {
283
+ this.db.insert(tSessions).values(sessionInsertRow(storage, incarnation)).onConflictDoUpdate({
284
+ target: tSessions.fSessionId,
285
+ set: sessionConflictRow(storage)
286
+ }).run();
287
+ }
288
+ async getHead(id) {
289
+ const head = this.db.select({
290
+ fHeadEventId: tSessions.fHeadEventId,
291
+ fHeadSequence: tSessions.fHeadSequence
292
+ }).from(tSessions).where(eq(tSessions.fSessionId, id)).get();
293
+ /* v8 ignore next -- appendBatch/commitRepair always materialize the row before reading the head */
294
+ if (head === void 0) throw new Error(`session "${id}" has no materialized row`);
295
+ return head;
296
+ }
297
+ async getSeedLength(id) {
298
+ const row = this.db.select({ fSeedLength: tSessions.fSeedLength }).from(tSessions).where(eq(tSessions.fSessionId, id)).get();
299
+ /* v8 ignore next -- rewind always materializes the row before reading the seed length */
300
+ if (row === void 0) throw new Error(`session "${id}" has no materialized row`);
301
+ return row.fSeedLength;
302
+ }
303
+ async updateSeedLength(id, seedLength) {
304
+ this.db.update(tSessions).set({ fSeedLength: seedLength }).where(eq(tSessions.fSessionId, id)).run();
305
+ }
306
+ static INSERT_BATCH_ROWS = 1e3;
307
+ async insertEvents(events) {
308
+ if (events.length === 0) return;
309
+ for (let i = 0; i < events.length; i += SqliteBackend.INSERT_BATCH_ROWS) this.db.insert(tEvents).values(events.slice(i, i + SqliteBackend.INSERT_BATCH_ROWS).map((event) => ({ ...event }))).run();
310
+ }
311
+ async insertBridges(rows) {
312
+ if (rows.length === 0) return;
313
+ for (let i = 0; i < rows.length; i += SqliteBackend.INSERT_BATCH_ROWS) this.db.insert(tSessionEvents).values(rows.slice(i, i + SqliteBackend.INSERT_BATCH_ROWS).map((row) => ({ ...row }))).run();
314
+ }
315
+ async updateHead(id, headEventId, headSequence) {
316
+ this.db.update(tSessions).set({
317
+ fHeadEventId: headEventId,
318
+ fHeadSequence: headSequence
319
+ }).where(eq(tSessions.fSessionId, id)).run();
320
+ }
321
+ async bumpRevision(id) {
322
+ this.db.update(tSessions).set({ fRevision: sql`${tSessions.fRevision} + 1` }).where(eq(tSessions.fSessionId, id)).run();
323
+ }
324
+ async deleteBridgeTail(id, fromSequence) {
325
+ this.db.delete(tSessionEvents).where(and(eq(tSessionEvents.fSessionId, id), gte(tSessionEvents.fSequence, fromSequence))).run();
326
+ }
327
+ async getPrevBridge(id, sequence) {
328
+ return this.db.select({
329
+ fEventId: tSessionEvents.fEventId,
330
+ fSequence: tSessionEvents.fSequence
331
+ }).from(tSessionEvents).where(and(eq(tSessionEvents.fSessionId, id), eq(tSessionEvents.fSequence, sequence))).get();
332
+ }
333
+ async getLastBridge(id) {
334
+ return this.db.select({
335
+ fEventId: tSessionEvents.fEventId,
336
+ fSequence: tSessionEvents.fSequence
337
+ }).from(tSessionEvents).where(eq(tSessionEvents.fSessionId, id)).orderBy(desc(tSessionEvents.fSequence)).limit(1).get();
338
+ }
339
+ eventRows() {
340
+ return this.db.select({
341
+ fEventId: tSessionEvents.fEventId,
342
+ fSequence: tSessionEvents.fSequence,
343
+ fOriginalSeq: tSessionEvents.fOriginalSeq,
344
+ fType: tEvents.fType,
345
+ fKind: tEvents.fKind,
346
+ fRole: tEvents.fRole,
347
+ fName: tEvents.fName,
348
+ fActionId: tEvents.fActionId,
349
+ fCreatedAt: tEvents.fCreatedAt,
350
+ fData: tEvents.fData,
351
+ fSurfaceOp: tSessionEvents.fSurfaceOp
352
+ }).from(tSessionEvents).innerJoin(tEvents, eq(tSessionEvents.fEventId, tEvents.fEventId));
353
+ }
354
+ };
355
+ //#endregion
356
+ export { WriteGuard as i, openDatabase as n, createTablesSql as r, SqliteBackend as t };
@@ -0,0 +1,44 @@
1
+ import { _ as WriteGuard, a as EventRole, b as EventRow, c as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, d as isEphemeralType, f as isPersistedEvent, g as tSessions, h as tSessionEvents, i as EventKind, l as eventDimensions, m as tPersistenceState, n as EPHEMERAL_EVENT_TYPES, o as JournalMode, p as tEvents, r as EVENT_ENCODING, s as SCHEMA_VERSION, t as DEFAULT_BUSY_TIMEOUT_MS, u as eventKind, v as Backend, x as SessionRow, y as BackendTx } from "./schema-CFXZURX7.mjs";
2
+ import { SessionId } from "@deepseek-ai/dsh-session";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ //#region src/sqlite.d.ts
5
+ declare function openDatabase(path: string, journalMode: JournalMode, busyTimeout?: number): DatabaseSync;
6
+ interface SqliteBackendOptions {
7
+ path: string;
8
+ journalMode: JournalMode;
9
+ busyTimeout: number;
10
+ }
11
+ declare class SqliteBackend implements Backend {
12
+ private readonly options;
13
+ readonly kind: "sqlite";
14
+ storeIdentity: string;
15
+ private dbPath;
16
+ private db;
17
+ constructor(options: SqliteBackendOptions);
18
+ open(): Promise<void>;
19
+ close(): Promise<void>;
20
+ getSession(id: SessionId): Promise<SessionRow | undefined>;
21
+ getSeqMapRows(id: SessionId): Promise<Array<{
22
+ fSequence: number;
23
+ fOriginalSeq: number;
24
+ }>>;
25
+ getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]>;
26
+ listSessions(): Promise<SessionRow[]>;
27
+ transaction<T>(fn: (tx: BackendTx) => Promise<T>): Promise<T>;
28
+ private readonly tx;
29
+ private upsertSession;
30
+ private getHead;
31
+ private getSeedLength;
32
+ private updateSeedLength;
33
+ private static readonly INSERT_BATCH_ROWS;
34
+ private insertEvents;
35
+ private insertBridges;
36
+ private updateHead;
37
+ private bumpRevision;
38
+ private deleteBridgeTail;
39
+ private getPrevBridge;
40
+ private getLastBridge;
41
+ private eventRows;
42
+ }
43
+ //#endregion
44
+ export { DEFAULT_BUSY_TIMEOUT_MS, EPHEMERAL_EVENT_TYPES, EVENT_ENCODING, EventKind, EventRole, type EventRow, JournalMode, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, type SessionRow, SqliteBackend, SqliteBackendOptions, WriteGuard, eventDimensions, eventKind, isEphemeralType, isPersistedEvent, openDatabase, tEvents, tPersistenceState, tSessionEvents, tSessions };
@@ -0,0 +1,3 @@
1
+ import { i as WriteGuard, n as openDatabase, t as SqliteBackend } from "./sqlite-DIXY-dFZ.mjs";
2
+ import { a as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, c as isEphemeralType, d as tPersistenceState, f as tSessionEvents, i as SCHEMA_VERSION, l as isPersistedEvent, n as EPHEMERAL_EVENT_TYPES, o as eventDimensions, p as tSessions, r as EVENT_ENCODING, s as eventKind, t as DEFAULT_BUSY_TIMEOUT_MS, u as tEvents } from "./schema-B-8G4lWc.mjs";
3
+ export { DEFAULT_BUSY_TIMEOUT_MS, EPHEMERAL_EVENT_TYPES, EVENT_ENCODING, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, SqliteBackend, WriteGuard, eventDimensions, eventKind, isEphemeralType, isPersistedEvent, openDatabase, tEvents, tPersistenceState, tSessionEvents, tSessions };
@@ -0,0 +1,31 @@
1
+ import { SessionPersistence } from "@deepseek-ai/dsh-session-persistence";
2
+ import { Session, SessionEvent, SessionHeader, SessionId } from "@deepseek-ai/dsh-session";
3
+ import { SettingsNamespace, SettingsProvider } from "@deepseek-ai/dsh-settings";
4
+ import { Context, Fiber } from "@deepseek-ai/cordis";
5
+ //#region src/testing/helpers.d.ts
6
+ declare class EmptySettings extends SettingsProvider {
7
+ constructor(ctx: Context);
8
+ get writable(): boolean;
9
+ protected load(): Promise<Record<string, unknown>>;
10
+ protected persist(_ns: SettingsNamespace, _section: Record<string, unknown>): Promise<void>;
11
+ }
12
+ //#endregion
13
+ //#region src/testing/contract.d.ts
14
+ interface ContractBackend {
15
+ persistence: SessionPersistence;
16
+ dispose: () => Promise<void>;
17
+ }
18
+ declare function meta(id: string, cwd?: string): SessionHeader;
19
+ declare function oneTurnLog(): SessionEvent[];
20
+ declare function appendLog(session: Session, events: readonly SessionEvent[]): void;
21
+ declare function runPersistenceContract(name: string, make: () => Promise<ContractBackend>): void;
22
+ //#endregion
23
+ //#region src/testing/coordinator-contract.d.ts
24
+ interface CoordinatorFixture {
25
+ mount: (ctx: Context) => Promise<Fiber>;
26
+ corruptTail?: (id: SessionId, cwd: string | undefined) => Promise<void>;
27
+ cleanup: () => Promise<void>;
28
+ }
29
+ declare function runCoordinatorContract(name: string, makeFixture: () => Promise<CoordinatorFixture>): void;
30
+ //#endregion
31
+ export { type ContractBackend, type CoordinatorFixture, EmptySettings, appendLog, meta, oneTurnLog, runCoordinatorContract, runPersistenceContract };