@morlay/session-rdb 0.0.17 → 0.0.19
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/dist/artifact.d.mts +1 -1
- package/dist/artifact.mjs +1 -1
- package/dist/{import-DZIAjOUr.mjs → import-Bc2QQa5G.mjs} +14 -5
- package/dist/import.d.mts +3 -2
- package/dist/import.mjs +1 -1
- package/dist/{index-BEKoV1Uy.d.mts → index-CCcK9tia.d.mts} +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +93 -39
- package/dist/{magic-string.es-BWtG0AyA.mjs → magic-string.es-BgJoa-3K.mjs} +2 -2
- package/dist/{schema-Bo6Hy5gW.d.mts → schema-DPcuEh_a.d.mts} +36 -47
- package/dist/{sqlite-BIPdMNQb.mjs → sqlite-DYExtbLo.mjs} +27 -6
- package/dist/storage.d.mts +1 -1
- package/dist/storage.mjs +1 -1
- package/dist/testing.mjs +20 -20
- package/drizzle/postgres/20260910120001_v3_storage_tables/snapshot.json +25 -78
- package/drizzle/postgres/20260910130001_v3_session_title_backfill/snapshot.json +25 -78
- package/drizzle/sqlite/20260910120000_v3_storage_tables/snapshot.json +25 -78
- package/drizzle/sqlite/20260910130000_v3_session_title_backfill/snapshot.json +25 -78
- package/package.json +20 -19
- package/src/entities/v3/workspace-state.ts +1 -1
- package/src/import-storages.ts +8 -3
- package/src/import.ts +41 -4
- package/src/index.ts +10 -2
- package/src/postgres.ts +15 -9
- package/src/session-query.ts +27 -0
- package/src/storage-takeover/index.ts +8 -8
- package/src/storage-takeover/projection-cache.ts +50 -24
- package/src/storage-takeover/repository.ts +66 -20
- package/src/storage-takeover/storage-backend.ts +65 -21
- package/src/storage-takeover/types.ts +11 -3
package/dist/artifact.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as EventRow, w as SessionRow } from "./schema-
|
|
1
|
+
import { C as EventRow, w as SessionRow } from "./schema-DPcuEh_a.mjs";
|
|
2
2
|
import { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport } from "./import.mjs";
|
|
3
3
|
import { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
|
|
4
4
|
import { SessionEvent, SessionHeader } from "@deepseek-ai/dsh-session";
|
package/dist/artifact.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { _ as toJsonlArtifact, a as repairAssistantSettlement, c as repairRequestHeaders, d as rowToMeta, f as scanRows, g as titleOfEventData, h as syncMeteringRanges, i as renameLegacyPtcEvents, l as repairSurfaceOps, m as sessionInsertRow, n as orphanInboxSpliceSeqs, o as repairOrphanInboxSplices, p as sessionConflictRow, r as recomputeReplaceProvenance, s as repairReadView, t as findSurfaceRepairs, u as rowToEvent } from "./log-DO69NQnn.mjs";
|
|
2
|
-
import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-
|
|
2
|
+
import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-Bc2QQa5G.mjs";
|
|
3
3
|
export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, findSurfaceRepairs, orphanInboxSpliceSeqs, parseImportZip, parseJsonlArtifact, persistImport, recomputeReplaceProvenance, registerSessionImport, renameLegacyPtcEvents, repairAssistantSettlement, repairOrphanInboxSplices, repairReadView, repairRequestHeaders, repairSurfaceOps, rowToEvent, rowToMeta, scanRows, sessionConflictRow, sessionInsertRow, syncMeteringRanges, titleOfEventData, toJsonlArtifact };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { d as rowToMeta } from "./log-DO69NQnn.mjs";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
|
|
4
|
+
import { parseSessionFormatLogFilename } from "@deepseek-ai/dsh-session-format";
|
|
4
5
|
import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
|
|
5
6
|
import { SessionBranch, SessionBranchError, balanceRewindPrefix, buildTimeline } from "@morlay/session-branch";
|
|
6
7
|
import { unzipSync } from "fflate";
|
|
@@ -279,6 +280,7 @@ var SessionBranchRdb = class extends SessionBranch {
|
|
|
279
280
|
};
|
|
280
281
|
//#endregion
|
|
281
282
|
//#region src/import.ts
|
|
283
|
+
/** 当前世代产物的文件名(读入侧接受任意 canonical 世代名,见 parseImportZip)。 */
|
|
282
284
|
const SESSION_LOG_ARTIFACT_FILENAME = "session.jsonl";
|
|
283
285
|
const SESSION_IMPORT_PATH = "/api/session.import";
|
|
284
286
|
const MAX_IMPORT_ZIP_BYTES = 67108864;
|
|
@@ -348,14 +350,21 @@ function parseImportZip(zip) {
|
|
|
348
350
|
} catch {
|
|
349
351
|
throw new Error("imported zip is not a valid ZIP archive");
|
|
350
352
|
}
|
|
351
|
-
const artifact = entries[
|
|
352
|
-
if (artifact === void 0) throw new Error(`imported zip
|
|
353
|
-
return parseJsonlArtifact(new TextDecoder().decode(artifact));
|
|
353
|
+
const artifact = Object.entries(entries).find(([name]) => parseSessionFormatLogFilename(name) !== void 0);
|
|
354
|
+
if (artifact === void 0) throw new Error(`imported zip has no session log artifact (expected ${SESSION_LOG_ARTIFACT_FILENAME} or a canonical session.vN.jsonl)`);
|
|
355
|
+
return parseJsonlArtifact(new TextDecoder().decode(artifact[1]));
|
|
354
356
|
}
|
|
355
|
-
async function
|
|
357
|
+
async function stopAgentLoop(ctx, sessionId) {
|
|
358
|
+
const agent = ctx.get("agents")?.get(sessionId);
|
|
359
|
+
if (agent === void 0) return;
|
|
360
|
+
agent.cancel?.({ kind: "user" }, { keepInbox: true });
|
|
361
|
+
await agent.whenIdle();
|
|
362
|
+
}
|
|
363
|
+
async function persistImport(persistence, branch, imported, targetId, sessions, stopLoop) {
|
|
356
364
|
const id = targetId ?? `session-${randomUUID()}`;
|
|
357
365
|
if (targetId !== void 0) {
|
|
358
366
|
if (branch === void 0) throw new Error("sessionBranch service is unavailable");
|
|
367
|
+
if (stopLoop !== void 0) await stopLoop(targetId);
|
|
359
368
|
await branch.rewind(targetId, -1);
|
|
360
369
|
const liveHandle = persistence.tracker.writerOf(targetId);
|
|
361
370
|
if (liveHandle !== void 0) {
|
|
@@ -442,7 +451,7 @@ function registerSessionImport(ctx, persistence) {
|
|
|
442
451
|
const branch = webCtx.get("sessionBranch");
|
|
443
452
|
try {
|
|
444
453
|
const sessions = webCtx.get("sessions");
|
|
445
|
-
const id = await persistImport(persistence, branch, imported, targetId, sessions);
|
|
454
|
+
const id = await persistImport(persistence, branch, imported, targetId, sessions, (sessionId) => stopAgentLoop(webCtx, sessionId));
|
|
446
455
|
res.writeHead(200, { "content-type": "application/json" });
|
|
447
456
|
res.end(JSON.stringify({ sessionId: id }));
|
|
448
457
|
} catch (error) {
|
package/dist/import.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { r as SessionPersistenceRdb } from "./index-
|
|
1
|
+
import { r as SessionPersistenceRdb } from "./index-CCcK9tia.mjs";
|
|
2
2
|
import { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
|
|
3
3
|
import { Session, SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
|
|
4
4
|
import { Context } from "@deepseek-ai/cordis";
|
|
5
5
|
//#region src/import.d.ts
|
|
6
|
+
/** 当前世代产物的文件名(读入侧接受任意 canonical 世代名,见 parseImportZip)。 */
|
|
6
7
|
declare const SESSION_LOG_ARTIFACT_FILENAME = "session.jsonl";
|
|
7
8
|
declare const SESSION_IMPORT_PATH = "/api/session.import";
|
|
8
9
|
declare function parseJsonlArtifact(content: string): SessionStorageMetadata & {
|
|
@@ -17,7 +18,7 @@ declare function persistImport(persistence: SessionPersistenceRdb, branch: {
|
|
|
17
18
|
events: SessionEvent[];
|
|
18
19
|
}, targetId?: SessionId, sessions?: {
|
|
19
20
|
get(id: SessionId): Session | undefined;
|
|
20
|
-
}): Promise<SessionId>;
|
|
21
|
+
}, stopLoop?: (id: SessionId) => Promise<void>): Promise<SessionId>;
|
|
21
22
|
declare function registerSessionImport(ctx: Context, persistence: SessionPersistenceRdb): void;
|
|
22
23
|
//#endregion
|
|
23
24
|
export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport };
|
package/dist/import.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-
|
|
1
|
+
import { a as persistImport, i as parseJsonlArtifact, n as SESSION_LOG_ARTIFACT_FILENAME, o as registerSessionImport, r as parseImportZip, t as SESSION_IMPORT_PATH } from "./import-Bc2QQa5G.mjs";
|
|
2
2
|
export { SESSION_IMPORT_PATH, SESSION_LOG_ARTIFACT_FILENAME, parseImportZip, parseJsonlArtifact, persistImport, registerSessionImport };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as JournalMode, b as WriteGuard, x as Backend } from "./schema-
|
|
1
|
+
import { a as JournalMode, b as WriteGuard, x as Backend } from "./schema-DPcuEh_a.mjs";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { SessionAccess, SessionHandle, SessionHandleAppendOptions, SessionHandleFlushOptions, SessionHandleReadOptions, SessionHandleReadResult, SessionPersistence, SessionPersistenceCreateOptions, SessionPersistenceListOptions, SessionPersistenceOpenOptions, SessionPersistenceRevision, SessionPersistenceSnapshot, SessionPersistenceStatOptions } from "@deepseek-ai/dsh-session-persistence";
|
|
4
4
|
import { Session, SessionEvent, SessionHeader, SessionId, SessionLogOffset } from "@deepseek-ai/dsh-session";
|
|
@@ -203,7 +203,7 @@ declare class SessionPersistenceRdb extends SessionPersistence {
|
|
|
203
203
|
flush(): Promise<void>;
|
|
204
204
|
stat(id: SessionId, options?: SessionPersistenceStatOptions): Promise<SessionPersistenceSnapshot | undefined>;
|
|
205
205
|
list(options?: SessionPersistenceListOptions): Promise<readonly SessionPersistenceSnapshot[]>;
|
|
206
|
-
/**
|
|
206
|
+
/** 导出当前世代的 jsonl artifact,视图只读不落库。 */
|
|
207
207
|
readRaw(id: SessionId, signal?: AbortSignal): Promise<{
|
|
208
208
|
meta: SessionHeader;
|
|
209
209
|
inheritedEventCount: number;
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { o as SCHEMA_VERSION } from "./schema-
|
|
2
|
-
import { a as SessionBranchRdb, i as SessionPersistenceRdbInternals, n as ProjectionCacheOptions, o as SessionBranchRdbProvider, r as SessionPersistenceRdb, s as locateTurnEnd, t as Config } from "./index-
|
|
1
|
+
import { o as SCHEMA_VERSION } from "./schema-DPcuEh_a.mjs";
|
|
2
|
+
import { a as SessionBranchRdb, i as SessionPersistenceRdbInternals, n as ProjectionCacheOptions, o as SessionBranchRdbProvider, r as SessionPersistenceRdb, s as locateTurnEnd, t as Config } from "./index-CCcK9tia.mjs";
|
|
3
3
|
export { Config, ProjectionCacheOptions, SCHEMA_VERSION, SessionBranchRdb, SessionBranchRdbProvider, SessionPersistenceRdb, SessionPersistenceRdb as default, SessionPersistenceRdbInternals, locateTurnEnd };
|
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { S as WriteGuard, a as EVENT_ENCODING, b as postgresTableDefs, c as eventDimensions, i as DEFAULT_BUSY_TIMEOUT_MS, o as SCHEMA_VERSION, r as createStorageRepository, t as SqliteBackend, x as toPostgresSchema } from "./sqlite-
|
|
1
|
+
import { S as WriteGuard, a as EVENT_ENCODING, b as postgresTableDefs, c as eventDimensions, i as DEFAULT_BUSY_TIMEOUT_MS, o as SCHEMA_VERSION, r as createStorageRepository, t as SqliteBackend, x as toPostgresSchema } from "./sqlite-DYExtbLo.mjs";
|
|
2
2
|
import { _ as toJsonlArtifact, c as repairRequestHeaders, d as rowToMeta, f as scanRows, g as titleOfEventData, m as sessionInsertRow, p as sessionConflictRow, s as repairReadView } from "./log-DO69NQnn.mjs";
|
|
3
|
-
import { c as SessionBranchRdbProvider, l as locateTurnEnd, o as registerSessionImport, s as SessionBranchRdb } from "./import-
|
|
3
|
+
import { c as SessionBranchRdbProvider, l as locateTurnEnd, o as registerSessionImport, s as SessionBranchRdb } from "./import-Bc2QQa5G.mjs";
|
|
4
4
|
import z from "@deepseek-ai/schemastery";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
6
6
|
import { Pool } from "pg";
|
|
7
7
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
8
8
|
import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionHandleClosedError, SessionPersistence, SessionPersistenceNotFoundError, SessionPersistenceRevision, SessionReadOnlyError, assertContiguous, assertVersion, materializeAppendBatch, materializeCreateHeader, validateStoredEvents } from "@deepseek-ai/dsh-session-persistence";
|
|
9
9
|
import { SESSION_FORMAT_VERSION, SessionLogOffset } from "@deepseek-ai/dsh-session";
|
|
10
|
+
import { sessionFormatLogFilename } from "@deepseek-ai/dsh-session-format";
|
|
10
11
|
import { sessionFormatCatalog } from "@deepseek-ai/dsh-session-format-catalog";
|
|
11
12
|
import { and, desc, eq, gte, sql } from "drizzle-orm";
|
|
12
13
|
import { readdirSync } from "node:fs";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
15
|
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
16
|
+
import { SessionQueryEngine, SessionQueryError } from "@deepseek-ai/dsh-session-query";
|
|
15
17
|
import { StorageError, storageBackendServiceKey } from "@deepseek-ai/dsh-storage";
|
|
16
18
|
import { Service } from "@deepseek-ai/cordis";
|
|
17
19
|
//#region src/postgres.ts
|
|
@@ -49,7 +51,7 @@ var PostgresBackend = class PostgresBackend {
|
|
|
49
51
|
} finally {
|
|
50
52
|
this.txOverride = previous;
|
|
51
53
|
}
|
|
52
|
-
}),
|
|
54
|
+
}, { isolationLevel: "serializable" }),
|
|
53
55
|
tables: this.tables
|
|
54
56
|
});
|
|
55
57
|
}
|
|
@@ -212,6 +214,22 @@ var PostgresBackend = class PostgresBackend {
|
|
|
212
214
|
}
|
|
213
215
|
};
|
|
214
216
|
//#endregion
|
|
217
|
+
//#region src/session-query.ts
|
|
218
|
+
var SessionQueryRdb = class extends SessionQueryEngine {
|
|
219
|
+
constructor(ctx, config = {}) {
|
|
220
|
+
super(ctx, config);
|
|
221
|
+
}
|
|
222
|
+
async searchSessions() {
|
|
223
|
+
throw searchDisabled();
|
|
224
|
+
}
|
|
225
|
+
async searchEvents() {
|
|
226
|
+
throw searchDisabled();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
function searchDisabled() {
|
|
230
|
+
return new SessionQueryError("session search is disabled: this deployment serves session queries from the rdb backend without a full-text index", "SESSION_QUERY_SEARCH_DISABLED");
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
215
233
|
//#region src/legacy.ts
|
|
216
234
|
/** 重建 v0/v1/v2 物理 header 记录(RDB 行 → 物理 JSON 对象)。 */
|
|
217
235
|
function physicalHeader(row) {
|
|
@@ -319,36 +337,41 @@ const SESSION_PROJECTION_CACHE_SERVICE = "sessionProjectionCache";
|
|
|
319
337
|
const PREDECESSOR_TITLE_KEY = "title";
|
|
320
338
|
/** rdb 持久化的投影 checkpoint 服务:同步驱动直读介质,异步驱动用写穿镜像支撑同步签名。 */
|
|
321
339
|
var SessionProjectionCacheRdb = class extends Service {
|
|
322
|
-
config;
|
|
323
|
-
repository;
|
|
324
|
-
ready;
|
|
325
340
|
static inject = ["sessionProjections", "sessions"];
|
|
326
341
|
/** 异步驱动(PostgreSQL)的同步读镜像:同步驱动(SQLite)直读介质,这里恒为空。 */
|
|
327
342
|
records = /* @__PURE__ */ new Map();
|
|
328
343
|
dirty = /* @__PURE__ */ new Map();
|
|
329
344
|
/** 读路径是否直读介质(`readProjcacheSync` 存在即同步驱动)。 */
|
|
330
345
|
directReads;
|
|
346
|
+
config;
|
|
347
|
+
repository;
|
|
348
|
+
ready;
|
|
331
349
|
/**
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
* @param
|
|
350
|
+
* 本类按 cordis class plugin 装载(`ctx.plugin(SessionProjectionCacheRdb, options)`):
|
|
351
|
+
* `static inject` 等依赖就绪后构造,构造后框架调用 `[Service.init]` 安装写入
|
|
352
|
+
* 路径——直接 `new` 不会触发 init,写入路径会静默缺失。
|
|
353
|
+
* @param ctx - 插件上下文(已注入 sessionProjections 与 sessions)。
|
|
354
|
+
* @param options - 写节流 + storages 接管访问层 + 介质就绪信号。
|
|
336
355
|
*/
|
|
337
|
-
constructor(ctx,
|
|
356
|
+
constructor(ctx, options) {
|
|
338
357
|
super(ctx, SESSION_PROJECTION_CACHE_SERVICE);
|
|
339
|
-
this.config =
|
|
340
|
-
this.repository = repository;
|
|
341
|
-
this.ready = ready;
|
|
342
|
-
this.directReads = repository.readProjcacheSync !== void 0;
|
|
358
|
+
this.config = options;
|
|
359
|
+
this.repository = options.repository;
|
|
360
|
+
this.ready = options.ready;
|
|
361
|
+
this.directReads = options.repository.readProjcacheSync !== void 0;
|
|
343
362
|
}
|
|
344
363
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
364
|
+
* Install the write path before the async medium wait: listeners must exist
|
|
365
|
+
* from plugin activation on, or sessions created while the medium settles
|
|
366
|
+
* lose their mandatory checkpoints. Only the async driver needs the startup
|
|
367
|
+
* mirror; the sync driver serves every read from the table.
|
|
347
368
|
*/
|
|
348
369
|
async [Service.init]() {
|
|
370
|
+
this.installWritePath();
|
|
349
371
|
await this.ready;
|
|
372
|
+
const pruned = await this.repository.pruneStaleProjcache();
|
|
373
|
+
if (pruned > 0) this.ctx.logger.info(`session projection cache: pruned stale snapshot(s) for ${String(pruned)} session(s)`);
|
|
350
374
|
if (!this.directReads) for (const entry of await this.repository.loadProjcache()) this.records.set(entry.sessionId, entry);
|
|
351
|
-
this.installWritePath();
|
|
352
375
|
}
|
|
353
376
|
/** Read one session's stored record: straight from the medium, or from the async mirror. */
|
|
354
377
|
lookup(id) {
|
|
@@ -601,8 +624,8 @@ const WORKSPACE_TABLE = "workspaces";
|
|
|
601
624
|
var RdbStorageBackend = class {
|
|
602
625
|
repository;
|
|
603
626
|
kv = { open: (descriptor) => this.openUnit(descriptor) };
|
|
604
|
-
/** 已打开(或打开中)的 unit
|
|
605
|
-
open = /* @__PURE__ */ new
|
|
627
|
+
/** 已打开(或打开中)的 unit;重复 open 是调用方 bug。 */
|
|
628
|
+
open = /* @__PURE__ */ new Map();
|
|
606
629
|
closed = false;
|
|
607
630
|
/**
|
|
608
631
|
* @param repository - storages 接管表访问层(与事件日志同介质)。
|
|
@@ -614,35 +637,52 @@ var RdbStorageBackend = class {
|
|
|
614
637
|
if (this.closed) throw new StorageError("closed", "rdb storage backend is closed");
|
|
615
638
|
if (descriptor.name !== WORKSPACE_UNIT) throw new Error(`rdb storage backend serves only the '${WORKSPACE_UNIT}' domain (requested '${descriptor.name}')`);
|
|
616
639
|
if (!descriptor.tables.includes(WORKSPACE_TABLE) || descriptor.hasGlobal !== true) throw new Error(`rdb storage backend expects the '${WORKSPACE_UNIT}' domain shape (table '${WORKSPACE_TABLE}' plus a global slot)`);
|
|
640
|
+
if (descriptor.layout !== void 0 && descriptor.layout !== "single") throw new Error(`rdb storage backend serves only the 'single' layout (domain '${descriptor.name}' declares '${descriptor.layout}')`);
|
|
617
641
|
if (this.open.has(descriptor.name)) throw new Error(`kv unit '${descriptor.name}' is already open (double-open is a caller bug)`);
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
642
|
+
const unit = new WorkspaceKvUnit(this.repository, descriptor);
|
|
643
|
+
this.open.set(descriptor.name, unit);
|
|
644
|
+
try {
|
|
645
|
+
const stored = await this.repository.readUnitVersion(descriptor.name);
|
|
646
|
+
if (stored === void 0) await this.repository.insertUnitVersion(descriptor.name, descriptor.version);
|
|
647
|
+
else if (stored !== descriptor.version) throw new StorageError("version-mismatch", `kv unit '${descriptor.name}' is stamped version ${stored} on the medium, incompatible with descriptor version ${descriptor.version}`);
|
|
648
|
+
} catch (error) {
|
|
649
|
+
this.open.delete(descriptor.name);
|
|
650
|
+
throw error;
|
|
651
|
+
}
|
|
652
|
+
unit.onClose(() => {
|
|
623
653
|
this.open.delete(descriptor.name);
|
|
624
654
|
});
|
|
655
|
+
return unit;
|
|
625
656
|
}
|
|
626
657
|
/**
|
|
627
|
-
* Release the backend.
|
|
628
|
-
*
|
|
629
|
-
*
|
|
658
|
+
* Release the backend. New writes are rejected immediately; already-queued
|
|
659
|
+
* unit writes drain first (upstream backend contract). The medium (the
|
|
660
|
+
* session database) belongs to the owning session-rdb plugin, so closing
|
|
661
|
+
* open units here does not close it.
|
|
662
|
+
* @returns resolution after every open unit drained.
|
|
630
663
|
*/
|
|
631
664
|
async close() {
|
|
632
665
|
this.closed = true;
|
|
666
|
+
const units = [...this.open.values()];
|
|
633
667
|
this.open.clear();
|
|
668
|
+
await Promise.all(units.map((unit) => unit.close()));
|
|
634
669
|
}
|
|
635
670
|
};
|
|
636
671
|
/** workspace 域的 KV unit:每个原语一条 SQL 语句,值形状与上游记录一致。 */
|
|
637
672
|
var WorkspaceKvUnit = class {
|
|
638
673
|
repository;
|
|
639
674
|
descriptor;
|
|
640
|
-
onClose;
|
|
641
675
|
closed = false;
|
|
642
|
-
|
|
676
|
+
/** 在途写操作:close 先拒绝新写,再 drain 它们(不丢已受理的写)。 */
|
|
677
|
+
inflight = /* @__PURE__ */ new Set();
|
|
678
|
+
onClosed;
|
|
679
|
+
constructor(repository, descriptor) {
|
|
643
680
|
this.repository = repository;
|
|
644
681
|
this.descriptor = descriptor;
|
|
645
|
-
|
|
682
|
+
}
|
|
683
|
+
/** Install the name-release callback after the backend registered this unit. */
|
|
684
|
+
onClose(release) {
|
|
685
|
+
this.onClosed = release;
|
|
646
686
|
}
|
|
647
687
|
async loadAll() {
|
|
648
688
|
this.assertOpen();
|
|
@@ -657,21 +697,32 @@ var WorkspaceKvUnit = class {
|
|
|
657
697
|
async putRecord(table, key, value) {
|
|
658
698
|
this.assertOpen();
|
|
659
699
|
this.assertTable(table);
|
|
660
|
-
await this.repository.putWorkspace(key, workspaceRecordOf(value));
|
|
700
|
+
await this.track(this.repository.putWorkspace(key, workspaceRecordOf(value)));
|
|
661
701
|
}
|
|
662
702
|
async deleteRecord(table, key) {
|
|
663
703
|
this.assertOpen();
|
|
664
704
|
this.assertTable(table);
|
|
665
|
-
await this.repository.deleteWorkspace(key);
|
|
705
|
+
await this.track(this.repository.deleteWorkspace(key));
|
|
666
706
|
}
|
|
667
707
|
async setGlobal(value) {
|
|
668
708
|
this.assertOpen();
|
|
669
|
-
await this.repository.writeWorkspaceState(workspaceStateOf(value));
|
|
709
|
+
await this.track(this.repository.writeWorkspaceState(workspaceStateOf(value)));
|
|
670
710
|
}
|
|
671
711
|
async close() {
|
|
672
712
|
if (this.closed) return;
|
|
673
713
|
this.closed = true;
|
|
674
|
-
this.
|
|
714
|
+
while (this.inflight.size > 0) await Promise.allSettled(this.inflight);
|
|
715
|
+
this.onClosed?.();
|
|
716
|
+
}
|
|
717
|
+
/** Register one accepted write so close can drain it. */
|
|
718
|
+
async track(operation) {
|
|
719
|
+
const settled = operation.then(() => void 0, () => void 0);
|
|
720
|
+
this.inflight.add(settled);
|
|
721
|
+
try {
|
|
722
|
+
await operation;
|
|
723
|
+
} finally {
|
|
724
|
+
this.inflight.delete(settled);
|
|
725
|
+
}
|
|
675
726
|
}
|
|
676
727
|
assertOpen() {
|
|
677
728
|
if (this.closed) throw new StorageError("closed", `kv unit '${this.descriptor.name}' is closed`);
|
|
@@ -722,8 +773,10 @@ function installStorageTakeover(ctx, options) {
|
|
|
722
773
|
}, "session-rdb.storageBackend");
|
|
723
774
|
storageCtx.provide(storageBackendServiceKey("rdb"), backend);
|
|
724
775
|
});
|
|
725
|
-
ctx.
|
|
726
|
-
|
|
776
|
+
ctx.plugin(SessionProjectionCacheRdb, {
|
|
777
|
+
...options.projectionCache,
|
|
778
|
+
repository: options.repository,
|
|
779
|
+
ready: options.ready
|
|
727
780
|
});
|
|
728
781
|
}
|
|
729
782
|
//#endregion
|
|
@@ -1047,6 +1100,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1047
1100
|
this.ready = this.init();
|
|
1048
1101
|
this.installLiveRouting(ctx);
|
|
1049
1102
|
new SessionBranchRdb(this.ctx);
|
|
1103
|
+
this.ctx.plugin(SessionQueryRdb, {});
|
|
1050
1104
|
registerSessionImport(this.ctx, this);
|
|
1051
1105
|
installStorageTakeover(this.ctx, {
|
|
1052
1106
|
repository: this.backend.storage,
|
|
@@ -1166,7 +1220,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1166
1220
|
}
|
|
1167
1221
|
return snapshots;
|
|
1168
1222
|
}
|
|
1169
|
-
/**
|
|
1223
|
+
/** 导出当前世代的 jsonl artifact,视图只读不落库。 */
|
|
1170
1224
|
async readRaw(id, signal) {
|
|
1171
1225
|
signal?.throwIfAborted();
|
|
1172
1226
|
await this.ready;
|
|
@@ -1178,7 +1232,7 @@ var SessionPersistenceRdb = class SessionPersistenceRdb extends SessionPersisten
|
|
|
1178
1232
|
return {
|
|
1179
1233
|
meta: log.meta,
|
|
1180
1234
|
inheritedEventCount,
|
|
1181
|
-
filename:
|
|
1235
|
+
filename: sessionFormatLogFilename(SESSION_FORMAT_VERSION),
|
|
1182
1236
|
content: toJsonlArtifact(log.meta, inheritedEventCount, log.events)
|
|
1183
1237
|
};
|
|
1184
1238
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region ../../../node_modules/.pnpm/@jridgewell+sourcemap-codec@1.6.0/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs
|
|
2
2
|
var comma = ",".charCodeAt(0);
|
|
3
3
|
var semicolon = ";".charCodeAt(0);
|
|
4
4
|
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
@@ -78,7 +78,7 @@ function encode(decoded) {
|
|
|
78
78
|
return writer.flush();
|
|
79
79
|
}
|
|
80
80
|
//#endregion
|
|
81
|
-
//#region
|
|
81
|
+
//#region ../../../node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.mjs
|
|
82
82
|
var BitSet = class BitSet {
|
|
83
83
|
constructor(arg) {
|
|
84
84
|
this.bits = arg instanceof BitSet ? arg.bits.slice() : [];
|