@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.
- package/README.md +0 -16
- package/dist/artifact.d.mts +64 -0
- package/dist/artifact.mjs +3 -0
- package/dist/import-D2vUnUZe.mjs +438 -0
- package/dist/import.d.mts +24 -0
- package/dist/import.mjs +2 -0
- package/dist/index-uUvn6gYp.d.mts +111 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +466 -0
- package/dist/invariant.d.mts +7 -0
- package/dist/invariant.mjs +8 -0
- package/dist/magic-string.es-BWtG0AyA.mjs +1017 -0
- package/dist/schema-B-8G4lWc.mjs +852 -0
- package/dist/schema-CFXZURX7.d.mts +116 -0
- package/dist/sqlite-DIXY-dFZ.mjs +356 -0
- package/dist/storage.d.mts +44 -0
- package/dist/storage.mjs +3 -0
- package/dist/testing.d.mts +31 -0
- package/dist/testing.mjs +16051 -0
- package/package.json +39 -22
- package/src/adapters/ddl.ts +77 -0
- package/src/adapters/index.ts +4 -0
- package/src/adapters/to-postgres.ts +91 -0
- package/src/adapters/to-sqlite.ts +79 -0
- package/src/artifact.ts +4 -0
- package/src/backend.ts +112 -0
- package/src/branch.ts +508 -0
- package/src/entities/events.ts +27 -0
- package/src/entities/index.ts +30 -0
- package/src/entities/persistence-state.ts +10 -0
- package/src/entities/schema-meta.ts +9 -0
- package/src/entities/session-events.ts +29 -0
- package/src/entities/sessions.ts +20 -0
- package/src/entities/types.ts +48 -0
- package/src/import.ts +270 -0
- package/src/index.ts +547 -0
- package/src/invariant.ts +15 -0
- package/src/log.ts +456 -0
- package/src/migrate.ts +137 -0
- package/src/postgres.ts +369 -0
- package/src/schema.ts +234 -0
- package/src/sqlite.ts +412 -0
- package/src/storage.ts +5 -0
- package/src/testing/contract.ts +562 -0
- package/src/testing/coordinator-contract.ts +1723 -0
- package/src/testing/helpers.ts +20 -0
- package/src/testing.ts +12 -0
- package/src/write-guard.ts +27 -0
- package/lib/index.d.mts +0 -558
- package/lib/index.d.mts.map +0 -1
- package/lib/index.mjs +0 -2176
- package/lib/index.mjs.map +0 -1
- package/lib/invariant.d.mts +0 -15
- package/lib/invariant.d.mts.map +0 -1
- package/lib/invariant.mjs +0 -21
- package/lib/invariant.mjs.map +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { i as WriteGuard, r as createTablesSql, t as SqliteBackend } from "./sqlite-DIXY-dFZ.mjs";
|
|
2
|
+
import { A as toJsonlArtifact, C as repairOrphanInboxSplices, D as scanRows, E as rowToMeta, O as sessionConflictRow, _ as buildSeqMap, a as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, b as recomputeReplaceProvenance, g as toPostgresSchema, i as SCHEMA_VERSION, k as sessionInsertRow, l as isPersistedEvent, m as postgresTableDefs, n as EPHEMERAL_EVENT_TYPES, o as eventDimensions, r as EVENT_ENCODING, t as DEFAULT_BUSY_TIMEOUT_MS, w as repairSurfaceOps } from "./schema-B-8G4lWc.mjs";
|
|
3
|
+
import { c as SessionBranchRdb, l as SessionBranchRdbProvider, s as registerSessionImport, u as locateTurnEnd } from "./import-D2vUnUZe.mjs";
|
|
4
|
+
import z from "@deepseek-ai/schemastery";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { Pool } from "pg";
|
|
7
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
8
|
+
import { PersistenceCoordinator, SessionPersistence, SessionPersistenceRevision } from "@deepseek-ai/dsh-session-persistence";
|
|
9
|
+
import { SessionLogOffset } from "@deepseek-ai/dsh-session";
|
|
10
|
+
import { and, desc, eq, gte, sql } from "drizzle-orm";
|
|
11
|
+
//#region src/postgres.ts
|
|
12
|
+
var PostgresBackend = class PostgresBackend {
|
|
13
|
+
db;
|
|
14
|
+
options;
|
|
15
|
+
kind = "postgres";
|
|
16
|
+
storeIdentity;
|
|
17
|
+
tables;
|
|
18
|
+
constructor(db, options) {
|
|
19
|
+
this.db = db;
|
|
20
|
+
this.options = options;
|
|
21
|
+
this.tables = toPostgresSchema(postgresTableDefs, this.options.schema ?? "public");
|
|
22
|
+
}
|
|
23
|
+
async open() {
|
|
24
|
+
const storeId = await this.db.transaction(async (tx) => {
|
|
25
|
+
const schema = this.options.schema ?? "public";
|
|
26
|
+
const qualifiedMeta = schema === "public" ? "t_schema_meta" : `"${schema}".t_schema_meta`;
|
|
27
|
+
const metaExists = (await tx.execute(sql`SELECT to_regclass(${qualifiedMeta}) IS NOT NULL AS exists`)).rows[0]?.exists === true;
|
|
28
|
+
for (const statement of createTablesSql("postgres", postgresTableDefs, schema)) await tx.execute(sql.raw(statement));
|
|
29
|
+
if (!metaExists) await tx.insert(this.tables["t_schema_meta"]).values([{
|
|
30
|
+
fKey: "schema_version",
|
|
31
|
+
fValue: String(2)
|
|
32
|
+
}, {
|
|
33
|
+
fKey: "application_id",
|
|
34
|
+
fValue: String(SESSION_PERSISTENCE_SQLITE_APPLICATION_ID)
|
|
35
|
+
}]).execute();
|
|
36
|
+
const version = await this.readMeta(tx, "schema_version");
|
|
37
|
+
const applicationId = await this.readMeta(tx, "application_id");
|
|
38
|
+
if (version === void 0 || applicationId === void 0) throw new Error("session database has an unversioned schema or application identity");
|
|
39
|
+
if (Number(version) !== 2) throw new Error(`session database has schema version ${version}, incompatible with this build (2)`);
|
|
40
|
+
if (Number(applicationId) !== 1146308688) throw new Error(`session database has application id ${applicationId}, expected ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`);
|
|
41
|
+
await tx.insert(this.tables["t_persistence_state"]).values({
|
|
42
|
+
fSingleton: 1,
|
|
43
|
+
fStoreId: randomUUID()
|
|
44
|
+
}).onConflictDoNothing().execute();
|
|
45
|
+
const storeId = (await tx.select({ fStoreId: this.tables["t_persistence_state"].fStoreId }).from(this.tables["t_persistence_state"]).where(eq(this.tables["t_persistence_state"].fSingleton, 1)).execute())[0]?.fStoreId;
|
|
46
|
+
if (storeId === void 0 || storeId.length === 0) throw new Error("session database has no valid store identity");
|
|
47
|
+
return storeId;
|
|
48
|
+
});
|
|
49
|
+
this.storeIdentity = `${this.options.identityBase}:store:${storeId}`;
|
|
50
|
+
}
|
|
51
|
+
async close() {
|
|
52
|
+
await this.options.close();
|
|
53
|
+
}
|
|
54
|
+
async getSession(id) {
|
|
55
|
+
return (await this.db.select().from(this.tables["t_sessions"]).where(eq(this.tables["t_sessions"].fSessionId, id)).execute())[0];
|
|
56
|
+
}
|
|
57
|
+
async getSeqMapRows(id) {
|
|
58
|
+
return this.db.select({
|
|
59
|
+
fSequence: this.tables["t_session_events"].fSequence,
|
|
60
|
+
fOriginalSeq: this.tables["t_session_events"].fOriginalSeq
|
|
61
|
+
}).from(this.tables["t_session_events"]).where(eq(this.tables["t_session_events"].fSessionId, id)).execute();
|
|
62
|
+
}
|
|
63
|
+
async getEventRows(id, fromSequence) {
|
|
64
|
+
return (fromSequence === void 0 ? this.eventRows(this.db).where(eq(this.tables["t_session_events"].fSessionId, id)) : this.eventRows(this.db).where(and(eq(this.tables["t_session_events"].fSessionId, id), gte(this.tables["t_session_events"].fSequence, fromSequence)))).orderBy(this.tables["t_session_events"].fSequence).execute();
|
|
65
|
+
}
|
|
66
|
+
async listSessions() {
|
|
67
|
+
return this.db.select().from(this.tables["t_sessions"]).execute();
|
|
68
|
+
}
|
|
69
|
+
async transaction(fn) {
|
|
70
|
+
return this.db.transaction(async (tx) => fn(this.txFor(tx)));
|
|
71
|
+
}
|
|
72
|
+
txFor(tx) {
|
|
73
|
+
return {
|
|
74
|
+
upsertSession: (storage, incarnation) => this.upsertSession(tx, storage, incarnation),
|
|
75
|
+
getHead: (id) => this.getHead(tx, id),
|
|
76
|
+
getSeedLength: (id) => this.getSeedLength(tx, id),
|
|
77
|
+
updateSeedLength: (id, seedLength) => this.updateSeedLength(tx, id, seedLength),
|
|
78
|
+
insertEvents: (events) => this.insertEvents(tx, events),
|
|
79
|
+
insertBridges: (rows) => this.insertBridges(tx, rows),
|
|
80
|
+
updateHead: (id, headEventId, headSequence) => this.updateHead(tx, id, headEventId, headSequence),
|
|
81
|
+
bumpRevision: (id) => this.bumpRevision(tx, id),
|
|
82
|
+
deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(tx, id, fromSequence),
|
|
83
|
+
getPrevBridge: (id, sequence) => this.getPrevBridge(tx, id, sequence),
|
|
84
|
+
getLastBridge: (id) => this.getLastBridge(tx, id)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async readMeta(exec, key) {
|
|
88
|
+
return (await exec.select({ fValue: this.tables["t_schema_meta"].fValue }).from(this.tables["t_schema_meta"]).where(eq(this.tables["t_schema_meta"].fKey, key)).execute())[0]?.fValue;
|
|
89
|
+
}
|
|
90
|
+
async upsertSession(exec, storage, incarnation) {
|
|
91
|
+
await exec.insert(this.tables["t_sessions"]).values(sessionInsertRow(storage, incarnation)).onConflictDoUpdate({
|
|
92
|
+
target: this.tables["t_sessions"].fSessionId,
|
|
93
|
+
set: sessionConflictRow(storage)
|
|
94
|
+
}).execute();
|
|
95
|
+
}
|
|
96
|
+
async getHead(exec, id) {
|
|
97
|
+
const head = (await exec.select({
|
|
98
|
+
fHeadEventId: this.tables["t_sessions"].fHeadEventId,
|
|
99
|
+
fHeadSequence: this.tables["t_sessions"].fHeadSequence
|
|
100
|
+
}).from(this.tables["t_sessions"]).where(eq(this.tables["t_sessions"].fSessionId, id)).execute())[0];
|
|
101
|
+
/* v8 ignore next -- appendBatch/commitRepair always materialize the row before reading the head */
|
|
102
|
+
if (head === void 0) throw new Error(`session "${id}" has no materialized row`);
|
|
103
|
+
return head;
|
|
104
|
+
}
|
|
105
|
+
async getSeedLength(exec, id) {
|
|
106
|
+
const row = (await exec.select({ fSeedLength: this.tables["t_sessions"].fSeedLength }).from(this.tables["t_sessions"]).where(eq(this.tables["t_sessions"].fSessionId, id)).execute())[0];
|
|
107
|
+
/* v8 ignore next -- rewind always materializes the row before reading the seed length */
|
|
108
|
+
if (row === void 0) throw new Error(`session "${id}" has no materialized row`);
|
|
109
|
+
return row.fSeedLength;
|
|
110
|
+
}
|
|
111
|
+
async updateSeedLength(exec, id, seedLength) {
|
|
112
|
+
await exec.update(this.tables["t_sessions"]).set({ fSeedLength: seedLength }).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
|
|
113
|
+
}
|
|
114
|
+
static INSERT_BATCH_ROWS = 1e3;
|
|
115
|
+
async insertEvents(exec, events) {
|
|
116
|
+
if (events.length === 0) return;
|
|
117
|
+
for (let i = 0; i < events.length; i += PostgresBackend.INSERT_BATCH_ROWS) await exec.insert(this.tables["t_events"]).values(events.slice(i, i + PostgresBackend.INSERT_BATCH_ROWS).map((event) => ({ ...event }))).execute();
|
|
118
|
+
}
|
|
119
|
+
async insertBridges(exec, rows) {
|
|
120
|
+
if (rows.length === 0) return;
|
|
121
|
+
for (let i = 0; i < rows.length; i += PostgresBackend.INSERT_BATCH_ROWS) await exec.insert(this.tables["t_session_events"]).values(rows.slice(i, i + PostgresBackend.INSERT_BATCH_ROWS).map((row) => ({ ...row }))).execute();
|
|
122
|
+
}
|
|
123
|
+
async updateHead(exec, id, headEventId, headSequence) {
|
|
124
|
+
await exec.update(this.tables["t_sessions"]).set({
|
|
125
|
+
fHeadEventId: headEventId,
|
|
126
|
+
fHeadSequence: headSequence
|
|
127
|
+
}).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
|
|
128
|
+
}
|
|
129
|
+
async bumpRevision(exec, id) {
|
|
130
|
+
await exec.update(this.tables["t_sessions"]).set({ fRevision: sql`${this.tables["t_sessions"].fRevision} + 1` }).where(eq(this.tables["t_sessions"].fSessionId, id)).execute();
|
|
131
|
+
}
|
|
132
|
+
async deleteBridgeTail(exec, id, fromSequence) {
|
|
133
|
+
await exec.delete(this.tables["t_session_events"]).where(and(eq(this.tables["t_session_events"].fSessionId, id), gte(this.tables["t_session_events"].fSequence, fromSequence))).execute();
|
|
134
|
+
}
|
|
135
|
+
async getPrevBridge(exec, id, sequence) {
|
|
136
|
+
return (await exec.select({
|
|
137
|
+
fEventId: this.tables["t_session_events"].fEventId,
|
|
138
|
+
fSequence: this.tables["t_session_events"].fSequence
|
|
139
|
+
}).from(this.tables["t_session_events"]).where(and(eq(this.tables["t_session_events"].fSessionId, id), eq(this.tables["t_session_events"].fSequence, sequence))).execute())[0];
|
|
140
|
+
}
|
|
141
|
+
async getLastBridge(exec, id) {
|
|
142
|
+
return (await exec.select({
|
|
143
|
+
fEventId: this.tables["t_session_events"].fEventId,
|
|
144
|
+
fSequence: this.tables["t_session_events"].fSequence
|
|
145
|
+
}).from(this.tables["t_session_events"]).where(eq(this.tables["t_session_events"].fSessionId, id)).orderBy(desc(this.tables["t_session_events"].fSequence)).limit(1).execute())[0];
|
|
146
|
+
}
|
|
147
|
+
eventRows(exec) {
|
|
148
|
+
return exec.select({
|
|
149
|
+
fEventId: this.tables["t_session_events"].fEventId,
|
|
150
|
+
fSequence: this.tables["t_session_events"].fSequence,
|
|
151
|
+
fOriginalSeq: this.tables["t_session_events"].fOriginalSeq,
|
|
152
|
+
fType: this.tables["t_events"].fType,
|
|
153
|
+
fKind: this.tables["t_events"].fKind,
|
|
154
|
+
fRole: this.tables["t_events"].fRole,
|
|
155
|
+
fName: this.tables["t_events"].fName,
|
|
156
|
+
fActionId: this.tables["t_events"].fActionId,
|
|
157
|
+
fCreatedAt: this.tables["t_events"].fCreatedAt,
|
|
158
|
+
fData: this.tables["t_events"].fData,
|
|
159
|
+
fSurfaceOp: this.tables["t_session_events"].fSurfaceOp
|
|
160
|
+
}).from(this.tables["t_session_events"]).innerJoin(this.tables["t_events"], eq(this.tables["t_session_events"].fEventId, this.tables["t_events"].fEventId));
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/index.ts
|
|
165
|
+
var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersistence {
|
|
166
|
+
config;
|
|
167
|
+
static inject = ["sessions", "settings"];
|
|
168
|
+
static Config = z.union([z.object({
|
|
169
|
+
type: z.const("sqlite"),
|
|
170
|
+
path: z.string().required(),
|
|
171
|
+
journalMode: z.union([
|
|
172
|
+
"wal",
|
|
173
|
+
"delete",
|
|
174
|
+
"truncate",
|
|
175
|
+
"persist"
|
|
176
|
+
]).default("wal"),
|
|
177
|
+
busyTimeout: z.number().step(1).min(0).default(DEFAULT_BUSY_TIMEOUT_MS)
|
|
178
|
+
}), z.object({
|
|
179
|
+
type: z.const("postgres"),
|
|
180
|
+
connectionString: z.string().required(),
|
|
181
|
+
schema: z.string().default("public")
|
|
182
|
+
})]);
|
|
183
|
+
static settingsNs = "session-rdb";
|
|
184
|
+
name = "session-rdb";
|
|
185
|
+
supportsRawArtifacts = true;
|
|
186
|
+
async readRaw(id, signal) {
|
|
187
|
+
signal?.throwIfAborted();
|
|
188
|
+
await this.ready;
|
|
189
|
+
signal?.throwIfAborted();
|
|
190
|
+
const log = await this.readLog(id, {}, signal);
|
|
191
|
+
if (log === void 0) return void 0;
|
|
192
|
+
repairSurfaceOps(log.events);
|
|
193
|
+
recomputeReplaceProvenance(log.events);
|
|
194
|
+
const inheritedEventCount = Math.min(log.inheritedEventCount, log.events.length);
|
|
195
|
+
return {
|
|
196
|
+
meta: log.meta,
|
|
197
|
+
inheritedEventCount: SessionLogOffset(inheritedEventCount),
|
|
198
|
+
filename: "session.jsonl",
|
|
199
|
+
content: toJsonlArtifact(log.meta, inheritedEventCount, log.events)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
backend;
|
|
203
|
+
storeIdentity;
|
|
204
|
+
ready;
|
|
205
|
+
coordinator;
|
|
206
|
+
writeGuard = new WriteGuard();
|
|
207
|
+
reuseEventIds = /* @__PURE__ */ new Map();
|
|
208
|
+
constructor(ctx, config, injectedBackend) {
|
|
209
|
+
let resolved = config;
|
|
210
|
+
const settings = ctx.reflect.get("settings");
|
|
211
|
+
if (settings !== void 0) {
|
|
212
|
+
const scope = settings.register(SessionPersistenceRdb.settingsNs, SessionPersistenceRdb.Config, { base: config });
|
|
213
|
+
resolved = scope.get();
|
|
214
|
+
scope.watch(() => {
|
|
215
|
+
ctx.logger.warn("session-rdb: settings changed; restart to apply the new configuration");
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
super(ctx);
|
|
219
|
+
this.config = config;
|
|
220
|
+
this.config = resolved;
|
|
221
|
+
this.backend = injectedBackend ?? createBackend(resolved);
|
|
222
|
+
this.ready = this.init();
|
|
223
|
+
this.coordinator = new PersistenceCoordinator(this.ctx, this);
|
|
224
|
+
new SessionBranchRdb(this.ctx);
|
|
225
|
+
registerSessionImport(this.ctx, this);
|
|
226
|
+
}
|
|
227
|
+
async init() {
|
|
228
|
+
await this.backend.open();
|
|
229
|
+
this.storeIdentity = this.backend.storeIdentity;
|
|
230
|
+
}
|
|
231
|
+
locate(_meta) {}
|
|
232
|
+
create(meta, inheritedEventCount) {
|
|
233
|
+
return this.coordinator.create(meta, inheritedEventCount === void 0 ? void 0 : SessionLogOffset(inheritedEventCount));
|
|
234
|
+
}
|
|
235
|
+
append(id, events) {
|
|
236
|
+
return this.coordinator.append(id, events);
|
|
237
|
+
}
|
|
238
|
+
load(id) {
|
|
239
|
+
return this.coordinator.load(id);
|
|
240
|
+
}
|
|
241
|
+
inspect(id, signal) {
|
|
242
|
+
return this.coordinator.inspect(id, signal);
|
|
243
|
+
}
|
|
244
|
+
readFrom(id, fromSeq, signal) {
|
|
245
|
+
return this.coordinator.readFrom(id, fromSeq, signal);
|
|
246
|
+
}
|
|
247
|
+
borrowSession(id, signal) {
|
|
248
|
+
return this.coordinator.borrowSession(id, signal);
|
|
249
|
+
}
|
|
250
|
+
loadStored(id, signal) {
|
|
251
|
+
return this.readPrefix(id, signal);
|
|
252
|
+
}
|
|
253
|
+
async loadStoredFrom(id, fromSeq, signal) {
|
|
254
|
+
const log = await this.readLog(id, { fromSeq }, signal);
|
|
255
|
+
if (log === void 0) return void 0;
|
|
256
|
+
return {
|
|
257
|
+
meta: log.meta,
|
|
258
|
+
inheritedEventCount: SessionLogOffset(log.inheritedEventCount),
|
|
259
|
+
events: log.events
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async readPrefix(id, signal) {
|
|
263
|
+
const log = await this.readLog(id, {}, signal);
|
|
264
|
+
if (log === void 0) {
|
|
265
|
+
this.writeGuard.confirmHead(id, -1);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.writeGuard.confirmHead(id, log.events.at(-1)?.seq ?? -1);
|
|
269
|
+
recomputeReplaceProvenance(log.events);
|
|
270
|
+
repairOrphanInboxSplices(log.events);
|
|
271
|
+
return {
|
|
272
|
+
meta: log.meta,
|
|
273
|
+
inheritedEventCount: SessionLogOffset(log.inheritedEventCount),
|
|
274
|
+
events: log.events,
|
|
275
|
+
revision: SessionPersistenceRevision(`${this.storeIdentity}:incarnation:${log.incarnation}:revision:${log.revision}`),
|
|
276
|
+
...log.tornFrom !== void 0 ? { tornMarker: log.tornFrom } : {}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
async readStoredRevision(id, signal) {
|
|
280
|
+
signal?.throwIfAborted();
|
|
281
|
+
await this.ready;
|
|
282
|
+
signal?.throwIfAborted();
|
|
283
|
+
const row = await this.backend.getSession(id);
|
|
284
|
+
if (row === void 0) return void 0;
|
|
285
|
+
return SessionPersistenceRevision(`${this.storeIdentity}:incarnation:${row.fIncarnation}:revision:${row.fRevision}`);
|
|
286
|
+
}
|
|
287
|
+
async readLog(id, options = {}, signal) {
|
|
288
|
+
signal?.throwIfAborted();
|
|
289
|
+
await this.ready;
|
|
290
|
+
signal?.throwIfAborted();
|
|
291
|
+
const row = await this.backend.getSession(id);
|
|
292
|
+
if (row === void 0) return void 0;
|
|
293
|
+
const meta = rowToMeta(row);
|
|
294
|
+
let eventRows;
|
|
295
|
+
let seqMap;
|
|
296
|
+
if (options.fromSeq === void 0) {
|
|
297
|
+
eventRows = await this.backend.getEventRows(id);
|
|
298
|
+
seqMap = buildSeqMap(eventRows);
|
|
299
|
+
} else {
|
|
300
|
+
eventRows = await this.backend.getEventRows(id, options.fromSeq);
|
|
301
|
+
const seqRows = await this.backend.getSeqMapRows(id);
|
|
302
|
+
seqMap = buildSeqMap(seqRows);
|
|
303
|
+
}
|
|
304
|
+
signal?.throwIfAborted();
|
|
305
|
+
const { preserved, tornFrom } = scanRows(eventRows, options.fromSeq ?? 0, seqMap);
|
|
306
|
+
return {
|
|
307
|
+
meta,
|
|
308
|
+
inheritedEventCount: row.fSeedLength ?? 0,
|
|
309
|
+
events: preserved,
|
|
310
|
+
incarnation: row.fIncarnation,
|
|
311
|
+
revision: row.fRevision,
|
|
312
|
+
...tornFrom !== void 0 ? { tornFrom } : {}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async appendBatch(storage, events, _isMaterialized) {
|
|
316
|
+
await this.ready;
|
|
317
|
+
const persisted = events.filter(isPersistedEvent);
|
|
318
|
+
if (persisted.length === 0) return;
|
|
319
|
+
const meta = storage.meta;
|
|
320
|
+
const reuse = this.reuseEventIds.get(meta.id);
|
|
321
|
+
if (reuse !== void 0) this.reuseEventIds.delete(meta.id);
|
|
322
|
+
let confirmedHead = -1;
|
|
323
|
+
await this.backend.transaction(async (tx) => {
|
|
324
|
+
await tx.upsertSession(storage, randomUUID());
|
|
325
|
+
const head = await tx.getHead(meta.id);
|
|
326
|
+
this.writeGuard.assertNoConcurrentWriter(meta.id, head.fHeadSequence);
|
|
327
|
+
const { headEventId, headSequence } = await appendEventTail(tx, meta, persisted, {
|
|
328
|
+
parentId: head.fHeadEventId,
|
|
329
|
+
nextSeq: head.fHeadSequence + 1
|
|
330
|
+
}, reuse);
|
|
331
|
+
await tx.updateHead(meta.id, headEventId, headSequence);
|
|
332
|
+
await tx.bumpRevision(meta.id);
|
|
333
|
+
confirmedHead = headSequence;
|
|
334
|
+
});
|
|
335
|
+
this.writeGuard.confirmHead(meta.id, confirmedHead);
|
|
336
|
+
}
|
|
337
|
+
async commitRepair(storage, tornMarker, closers) {
|
|
338
|
+
await this.ready;
|
|
339
|
+
const meta = storage.meta;
|
|
340
|
+
const persistedClosers = closers.filter(isPersistedEvent);
|
|
341
|
+
if (tornMarker === void 0 && persistedClosers.length === 0) return;
|
|
342
|
+
await this.backend.transaction(async (tx) => {
|
|
343
|
+
if (tornMarker !== void 0) {
|
|
344
|
+
await tx.deleteBridgeTail(meta.id, tornMarker);
|
|
345
|
+
const prev = await tx.getPrevBridge(meta.id, tornMarker - 1);
|
|
346
|
+
if (prev === void 0) await tx.updateHead(meta.id, "", -1);
|
|
347
|
+
else await tx.updateHead(meta.id, prev.fEventId, prev.fSequence);
|
|
348
|
+
}
|
|
349
|
+
if (persistedClosers.length > 0) {
|
|
350
|
+
const last = await tx.getLastBridge(meta.id);
|
|
351
|
+
const { headEventId, headSequence } = await appendEventTail(tx, meta, persistedClosers, {
|
|
352
|
+
parentId: last?.fEventId ?? "",
|
|
353
|
+
nextSeq: (last?.fSequence ?? -1) + 1
|
|
354
|
+
});
|
|
355
|
+
await tx.updateHead(meta.id, headEventId, headSequence);
|
|
356
|
+
}
|
|
357
|
+
await tx.bumpRevision(meta.id);
|
|
358
|
+
});
|
|
359
|
+
const row = await this.backend.getSession(meta.id);
|
|
360
|
+
this.writeGuard.confirmHead(meta.id, row?.fHeadSequence ?? -1);
|
|
361
|
+
}
|
|
362
|
+
async list(signal) {
|
|
363
|
+
signal?.throwIfAborted();
|
|
364
|
+
await this.ready;
|
|
365
|
+
signal?.throwIfAborted();
|
|
366
|
+
const rows = await this.backend.listSessions();
|
|
367
|
+
signal?.throwIfAborted();
|
|
368
|
+
return rows.map(rowToMeta);
|
|
369
|
+
}
|
|
370
|
+
async listSnapshots(signal) {
|
|
371
|
+
signal?.throwIfAborted();
|
|
372
|
+
await this.ready;
|
|
373
|
+
signal?.throwIfAborted();
|
|
374
|
+
const rows = await this.backend.listSessions();
|
|
375
|
+
signal?.throwIfAborted();
|
|
376
|
+
return rows.map((row) => ({
|
|
377
|
+
header: rowToMeta(row),
|
|
378
|
+
revision: SessionPersistenceRevision(`${this.storeIdentity}:incarnation:${row.fIncarnation}:revision:${row.fRevision}`),
|
|
379
|
+
inheritedEventCount: row.fSeedLength ?? 0
|
|
380
|
+
}));
|
|
381
|
+
}
|
|
382
|
+
async close() {
|
|
383
|
+
await this.ready;
|
|
384
|
+
await this.backend.close();
|
|
385
|
+
}
|
|
386
|
+
registerReuseEventIds(childId, map) {
|
|
387
|
+
this.reuseEventIds.set(childId, new Map(map));
|
|
388
|
+
}
|
|
389
|
+
internals() {
|
|
390
|
+
return {
|
|
391
|
+
backend: this.backend,
|
|
392
|
+
writeGuard: this.writeGuard,
|
|
393
|
+
create: (meta, inheritedEventCount) => this.create(meta, inheritedEventCount),
|
|
394
|
+
append: (id, events) => this.append(id, events),
|
|
395
|
+
load: (id) => this.load(id),
|
|
396
|
+
inspect: (id, signal) => this.inspect(id, signal),
|
|
397
|
+
readFrom: (id, fromSeq, signal) => this.readFrom(id, fromSeq, signal),
|
|
398
|
+
listSnapshots: (signal) => this.listSnapshots(signal),
|
|
399
|
+
readStoredRevision: (id, signal) => this.readStoredRevision(id, signal),
|
|
400
|
+
registerReuseEventIds: (childId, map) => this.registerReuseEventIds(childId, map)
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
function createBackend(config) {
|
|
405
|
+
if (config.type === "sqlite") return new SqliteBackend({
|
|
406
|
+
path: config.path,
|
|
407
|
+
journalMode: config.journalMode ?? "wal",
|
|
408
|
+
busyTimeout: config.busyTimeout ?? 5e3
|
|
409
|
+
});
|
|
410
|
+
const pool = new Pool({ connectionString: config.connectionString });
|
|
411
|
+
pool.on("error", () => {});
|
|
412
|
+
return new PostgresBackend(drizzle({ client: pool }), {
|
|
413
|
+
identityBase: [
|
|
414
|
+
"postgres",
|
|
415
|
+
pool.options.host ?? "localhost",
|
|
416
|
+
String(pool.options.port ?? 5432),
|
|
417
|
+
pool.options.database ?? "",
|
|
418
|
+
config.schema ?? "public"
|
|
419
|
+
].join(":"),
|
|
420
|
+
schema: config.schema ?? "public",
|
|
421
|
+
close: () => pool.end()
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async function appendEventTail(tx, meta, events, anchor, reuse) {
|
|
425
|
+
let parentId = anchor.parentId;
|
|
426
|
+
let nextSeq = anchor.nextSeq;
|
|
427
|
+
const eventRows = [];
|
|
428
|
+
const bridgeRows = [];
|
|
429
|
+
for (const event of events) {
|
|
430
|
+
const reusedId = reuse?.get(event.seq);
|
|
431
|
+
const eventId = reusedId ?? randomUUID();
|
|
432
|
+
if (reusedId === void 0) {
|
|
433
|
+
const { kind, role, name, actionId } = eventDimensions(event);
|
|
434
|
+
eventRows.push({
|
|
435
|
+
fEventId: eventId,
|
|
436
|
+
fParentId: parentId,
|
|
437
|
+
fType: event.type,
|
|
438
|
+
fKind: kind,
|
|
439
|
+
fRole: role,
|
|
440
|
+
fName: name,
|
|
441
|
+
fActionId: actionId,
|
|
442
|
+
fEncoding: EVENT_ENCODING,
|
|
443
|
+
fData: JSON.stringify(event.data),
|
|
444
|
+
fCreatedAt: event.time
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
const surfaceOp = event.surfaceOp === void 0 ? null : JSON.stringify(event.surfaceOp);
|
|
448
|
+
bridgeRows.push({
|
|
449
|
+
fSessionId: meta.id,
|
|
450
|
+
fEventId: eventId,
|
|
451
|
+
fSequence: nextSeq,
|
|
452
|
+
fOriginalSeq: event.seq,
|
|
453
|
+
fSurfaceOp: surfaceOp
|
|
454
|
+
});
|
|
455
|
+
parentId = eventId;
|
|
456
|
+
nextSeq++;
|
|
457
|
+
}
|
|
458
|
+
if (eventRows.length > 0) await tx.insertEvents(eventRows);
|
|
459
|
+
await tx.insertBridges(bridgeRows);
|
|
460
|
+
return {
|
|
461
|
+
headEventId: parentId,
|
|
462
|
+
headSequence: nextSeq - 1
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
466
|
+
export { EPHEMERAL_EVENT_TYPES, SCHEMA_VERSION, SessionBranchRdb, SessionBranchRdbProvider, SessionPersistenceRdb, SessionPersistenceRdb as default, locateTurnEnd };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
//#region src/invariant.d.ts
|
|
3
|
+
declare const name = "session-rdb-invariant";
|
|
4
|
+
declare const inject: string[];
|
|
5
|
+
declare const apply: (ctx: Context) => Promise<() => void>;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/invariant.ts
|
|
2
|
+
const PACKAGE_NAME = "@morlay/session-rdb";
|
|
3
|
+
const name = "session-rdb-invariant";
|
|
4
|
+
const inject = ["invariants"];
|
|
5
|
+
const install = () => {};
|
|
6
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
7
|
+
//#endregion
|
|
8
|
+
export { apply, inject, name };
|