@morlay/session-rdb 0.1.9 → 0.1.10-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.
Files changed (143) hide show
  1. package/README.md +24 -3
  2. package/dist/artifact.d.mts +118 -3
  3. package/dist/artifact.mjs +5 -3
  4. package/dist/{log-C0q5svm7.d.mts → backend-BKtJ5sO5.d.mts} +189 -291
  5. package/dist/backend-rdb-BDCntVDy.mjs +1341 -0
  6. package/dist/branch-CG1PUlO6.mjs +833 -0
  7. package/dist/deletion-SSX7IqU8.mjs +35 -0
  8. package/dist/deletion.d.mts +1 -3
  9. package/dist/deletion.mjs +2 -2
  10. package/dist/export-CfJQjQfr.mjs +123 -0
  11. package/dist/export.d.mts +1 -3
  12. package/dist/export.mjs +32 -57
  13. package/dist/import.d.mts +1 -3
  14. package/dist/import.mjs +32 -78
  15. package/dist/index-CUD2ZpTA.d.mts +71 -0
  16. package/dist/index.d.mts +20 -2
  17. package/dist/index.mjs +1562 -5
  18. package/dist/open-YoyD_kah.mjs +561 -0
  19. package/dist/organize.d.mts +9 -0
  20. package/dist/organize.mjs +30 -0
  21. package/dist/route-DpfY0zq-.mjs +114 -0
  22. package/dist/storage.d.mts +41 -50
  23. package/dist/storage.mjs +3 -1
  24. package/dist/testing.d.mts +9 -5
  25. package/dist/testing.mjs +27 -8
  26. package/dist/usage-row-BQQM-_t6.mjs +36 -0
  27. package/dist/usage.d.mts +1 -1
  28. package/dist/usage.mjs +8 -42
  29. package/dist/write-guard-CYrY58GM.mjs +174 -0
  30. package/package.json +32 -36
  31. package/src/artifact.ts +4 -1
  32. package/src/backend-rdb.ts +331 -0
  33. package/src/backend.ts +32 -15
  34. package/src/{branch.ts → branch/index.ts} +125 -112
  35. package/src/branch/port.ts +62 -0
  36. package/src/branch/semantics.ts +69 -0
  37. package/src/db/open.ts +180 -0
  38. package/src/db/stats.ts +449 -0
  39. package/src/deletion-error.ts +17 -0
  40. package/src/deletion.ts +19 -72
  41. package/src/entities/index.ts +16 -1
  42. package/src/entities/v3/event-usage.ts +3 -3
  43. package/src/entities/v3/events.ts +3 -3
  44. package/src/entities/v3/index.ts +4 -16
  45. package/src/entities/v3/persistence-state.ts +3 -3
  46. package/src/entities/v3/schema-meta.ts +3 -3
  47. package/src/entities/v3/session-counts.ts +3 -3
  48. package/src/entities/v3/session-events.ts +3 -3
  49. package/src/entities/v3/session-projcache-rows.ts +3 -3
  50. package/src/entities/v3/session-usage.ts +3 -3
  51. package/src/entities/v3/sessions.ts +3 -3
  52. package/src/entities/v3/storage-records.ts +17 -0
  53. package/src/entities/v3/storage-units.ts +3 -3
  54. package/src/{schema.ts → event-shape.ts} +0 -46
  55. package/src/export.ts +32 -78
  56. package/src/handles.ts +349 -0
  57. package/src/http/route.ts +178 -0
  58. package/src/import-storages.ts +5 -2
  59. package/src/import.ts +63 -125
  60. package/src/index.ts +15 -1630
  61. package/src/legacy.ts +3 -1
  62. package/src/live-routing.ts +205 -0
  63. package/src/log/export.ts +84 -0
  64. package/src/{log.ts → log/repair.ts} +1 -272
  65. package/src/log/rows.ts +107 -0
  66. package/src/log/usage-row.ts +64 -0
  67. package/src/migrations.ts +196 -0
  68. package/src/organize.ts +38 -0
  69. package/src/read-path.ts +190 -0
  70. package/src/routes.ts +2 -1
  71. package/src/rows.ts +25 -77
  72. package/src/session-ops.ts +444 -0
  73. package/src/session-query.ts +17 -4
  74. package/src/session-store.ts +675 -0
  75. package/src/stats-fold.ts +44 -0
  76. package/src/storage-takeover/index.ts +2 -5
  77. package/src/storage-takeover/legacy-domain-migration.ts +118 -0
  78. package/src/storage-takeover/projection-cache.ts +139 -77
  79. package/src/storage-takeover/read-model.ts +189 -0
  80. package/src/storage-takeover/repository.ts +238 -425
  81. package/src/storage-takeover/storage-backend.ts +47 -149
  82. package/src/storage-takeover/types.ts +45 -14
  83. package/src/storage-takeover/workspace-documents.ts +65 -0
  84. package/src/storage.ts +4 -2
  85. package/src/testing.ts +21 -1
  86. package/src/usage.ts +11 -65
  87. package/src/version.ts +13 -0
  88. package/src/write-path.ts +286 -0
  89. package/dist/branch-DbFW3rJX.mjs +0 -391
  90. package/dist/deletion-ODZFcbZm.mjs +0 -2218
  91. package/dist/gc.d.mts +0 -6
  92. package/dist/gc.mjs +0 -49
  93. package/dist/log-DYxdjJ-l.mjs +0 -562
  94. package/dist/routes-DGBlBE_I.mjs +0 -10
  95. package/dist/sqlite-DjOnMRh2.mjs +0 -1777
  96. package/drizzle/postgres/20260908072805_v2_initial/migration.sql +0 -58
  97. package/drizzle/postgres/20260908072805_v2_initial/snapshot.json +0 -686
  98. package/drizzle/postgres/20260908072806_v3_drop_original_seq/migration.sql +0 -1
  99. package/drizzle/postgres/20260908072806_v3_drop_original_seq/snapshot.json +0 -673
  100. package/drizzle/postgres/20260910120001_v3_storage_tables/migration.sql +0 -65
  101. package/drizzle/postgres/20260910120001_v3_storage_tables/snapshot.json +0 -1183
  102. package/drizzle/postgres/20260910130001_v3_session_title_backfill/migration.sql +0 -10
  103. package/drizzle/postgres/20260910130001_v3_session_title_backfill/snapshot.json +0 -1183
  104. package/drizzle/postgres/20260918120000_v3_event_usage/migration.sql +0 -14
  105. package/drizzle/postgres/20260918120000_v3_event_usage/snapshot.json +0 -1309
  106. package/drizzle/postgres/20260921075900_v3_agent_preset/migration.sql +0 -1
  107. package/drizzle/postgres/20260922090001_v3_pinned_seq/migration.sql +0 -2
  108. package/drizzle/postgres/20260922093001_v3_last_event_at/migration.sql +0 -10
  109. package/drizzle/postgres/20260923120000_v3_usage_materialized/migration.sql +0 -48
  110. package/drizzle/postgres/20261008103001_v3_schedule_tasks/migration.sql +0 -11
  111. package/drizzle/sqlite/20260908072751_v2_initial/migration.sql +0 -53
  112. package/drizzle/sqlite/20260908072751_v2_initial/snapshot.json +0 -492
  113. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/migration.sql +0 -6
  114. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/snapshot.json +0 -513
  115. package/drizzle/sqlite/20260910120000_v3_storage_tables/migration.sql +0 -66
  116. package/drizzle/sqlite/20260910120000_v3_storage_tables/snapshot.json +0 -905
  117. package/drizzle/sqlite/20260910130000_v3_session_title_backfill/migration.sql +0 -13
  118. package/drizzle/sqlite/20260910130000_v3_session_title_backfill/snapshot.json +0 -905
  119. package/drizzle/sqlite/20260918120000_v3_event_usage/migration.sql +0 -14
  120. package/drizzle/sqlite/20260918120000_v3_event_usage/snapshot.json +0 -1031
  121. package/drizzle/sqlite/20260921075855_v3_agent_preset/migration.sql +0 -7
  122. package/drizzle/sqlite/20260921075855_v3_agent_preset/snapshot.json +0 -1041
  123. package/drizzle/sqlite/20260922090000_v3_pinned_seq/migration.sql +0 -4
  124. package/drizzle/sqlite/20260922093000_v3_last_event_at/migration.sql +0 -13
  125. package/drizzle/sqlite/20260923120000_v3_usage_materialized/migration.sql +0 -48
  126. package/drizzle/sqlite/20261008103000_v3_schedule_tasks/migration.sql +0 -14
  127. package/src/adapters/index.ts +0 -12
  128. package/src/adapters/to-postgres.ts +0 -93
  129. package/src/adapters/to-sqlite.ts +0 -83
  130. package/src/adapters/types.ts +0 -44
  131. package/src/drizzle/postgres-v2.ts +0 -10
  132. package/src/drizzle/postgres-v3.ts +0 -18
  133. package/src/drizzle/sqlite-v2.ts +0 -9
  134. package/src/drizzle/sqlite-v3.ts +0 -18
  135. package/src/entities/v2/index.ts +0 -20
  136. package/src/entities/v2/session-events.ts +0 -10
  137. package/src/entities/v3/schedule-tasks.ts +0 -16
  138. package/src/entities/v3/workspace-sessions.ts +0 -19
  139. package/src/entities/v3/workspace-state.ts +0 -12
  140. package/src/entities/v3/workspaces.ts +0 -14
  141. package/src/gc.ts +0 -77
  142. package/src/postgres.ts +0 -1015
  143. package/src/sqlite.ts +0 -1086
@@ -0,0 +1,114 @@
1
+ // src/routes.ts
2
+ const MORLAY_SESSION_API_PREFIX = "/api/morlay/v1/session";
3
+ const SESSION_ROWS_PATH = `${MORLAY_SESSION_API_PREFIX}/rows`;
4
+ const SESSION_EXPORT_PATH = `${MORLAY_SESSION_API_PREFIX}/export`;
5
+ const SESSION_IMPORT_PATH = `${MORLAY_SESSION_API_PREFIX}/import`;
6
+ const SESSION_DELETE_PATH = `${MORLAY_SESSION_API_PREFIX}/delete`;
7
+ const SESSION_ORGANIZE_PATH = `${MORLAY_SESSION_API_PREFIX}/organize`;
8
+ const SESSION_USAGE_PATH = `${MORLAY_SESSION_API_PREFIX}/usage`;
9
+ // src/http/route.ts
10
+ /** 一条路由自己决定的状态码(鉴权 / 方法 / 解析之外的那些校验)。 */
11
+ var HttpError = class extends Error {
12
+ status;
13
+ payload;
14
+ constructor(status, message, payload = {}) {
15
+ super(message);
16
+ this.status = status;
17
+ this.payload = payload;
18
+ this.name = "HttpError";
19
+ }
20
+ };
21
+ function writeJson(res, status, body) {
22
+ res.writeHead(status, { "content-type": "application/json" });
23
+ res.end(JSON.stringify(body));
24
+ }
25
+ /**
26
+ * 挂一条路由:鉴权与方法检查这些每处都一样的部分收在这里。
27
+ *
28
+ * 二进制响应(导出 zip)自己写响应体,所以只共用鉴权 / 405 / 错误兜底。
29
+ */
30
+ function mountRoute(ctx, path, handler) {
31
+ ctx.inject(["webServer", "connection"], (webCtx) => {
32
+ const webServer = webCtx.webServer;
33
+ const connection = webCtx.get("connection");
34
+ return webCtx.effect(() => webServer.register({
35
+ kind: "exact",
36
+ path,
37
+ handler: async (req, res) => {
38
+ const rejection = connection.requestRejection(req);
39
+ if (rejection !== void 0) {
40
+ res.writeHead(rejection);
41
+ res.end(rejection === 401 ? "unauthorized" : "forbidden");
42
+ return;
43
+ }
44
+ if (req.method !== "POST") {
45
+ writeJson(res, 405, { error: "method not allowed" });
46
+ return;
47
+ }
48
+ try {
49
+ await handler(req, res);
50
+ } catch (error) {
51
+ if (res.headersSent) return;
52
+ writeJson(res, 500, { error: error instanceof Error ? error.message : "request failed" });
53
+ }
54
+ }
55
+ }), `session-rdb: ${path}`);
56
+ });
57
+ }
58
+ /**
59
+ * 挂一条 JSON 路由:鉴权、方法、body 解析、错误码这些每处都一样的部分收在这里。
60
+ *
61
+ * 5 个 JSON 路由(rows / usage / gc / deletion / import)共用 `mountJsonRoute`;`export` 的响应体是 zip,走
62
+ * `mountRoute`,只复用鉴权 / 405 / 500。之前每个文件各抄一遍(约 270 行样板,而且 `import` 那条漏了 405)。
63
+ */
64
+ function mountJsonRoute(ctx, spec) {
65
+ ctx.inject(["webServer", "connection"], (webCtx) => {
66
+ const webServer = webCtx.webServer;
67
+ const connection = webCtx.get("connection");
68
+ return webCtx.effect(() => webServer.register({
69
+ kind: "exact",
70
+ path: spec.path,
71
+ handler: async (req, res) => {
72
+ const rejection = connection.requestRejection(req);
73
+ if (rejection !== void 0) {
74
+ res.writeHead(rejection);
75
+ res.end(rejection === 401 ? "unauthorized" : "forbidden");
76
+ return;
77
+ }
78
+ if (req.method !== "POST") {
79
+ writeJson(res, 405, { error: "method not allowed" });
80
+ return;
81
+ }
82
+ const chunks = [];
83
+ for await (const chunk of req) chunks.push(chunk);
84
+ const raw = Buffer.concat(chunks).toString("utf8");
85
+ let body = {};
86
+ if (raw !== "") try {
87
+ body = JSON.parse(raw);
88
+ } catch {
89
+ writeJson(res, 400, { error: "request body is not JSON" });
90
+ return;
91
+ }
92
+ try {
93
+ const reply = await spec.handle(body, webCtx);
94
+ writeJson(res, reply.status ?? 200, reply.body);
95
+ } catch (error) {
96
+ if (error instanceof HttpError) {
97
+ writeJson(res, error.status, {
98
+ error: error.message,
99
+ ...error.payload
100
+ });
101
+ return;
102
+ }
103
+ const mapped = spec.statusOfError?.(error);
104
+ if (mapped !== void 0) {
105
+ writeJson(res, mapped.status, mapped.body);
106
+ return;
107
+ }
108
+ writeJson(res, 500, { error: error instanceof Error ? error.message : "request failed" });
109
+ }
110
+ }
111
+ }), `session-rdb: ${spec.path}`);
112
+ });
113
+ }
114
+ export { SESSION_EXPORT_PATH as a, SESSION_ROWS_PATH as c, SESSION_DELETE_PATH as i, SESSION_USAGE_PATH as l, mountJsonRoute as n, SESSION_IMPORT_PATH as o, mountRoute as r, SESSION_ORGANIZE_PATH as s, HttpError as t };
@@ -1,59 +1,50 @@
1
- import { A as SessionRow, At as tWorkspaces, Ct as tSessionEvents, D as SessionCountBucket, Dt as tStorageUnits, E as EventRow, Et as tSessions, F as UsageAggregate, O as SessionListRowsPage, Ot as tWorkspaceSessions, Rt as StorageRepository, St as tSessionCounts, T as BackendTx, Tt as tSessionUsage, _t as tEventUsage, bt as tScheduleTasks, ct as DEFAULT_BUSY_TIMEOUT_MS, dt as EventRole, ft as JournalMode, gt as eventKind, ht as eventDimensions, j as SessionUsageBucket, jt as WriteGuard, k as SessionListRowsQuery, kt as tWorkspaceState, lt as EVENT_ENCODING, mt as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, pt as SCHEMA_VERSION, ut as EventKind, vt as tEvents, w as Backend, wt as tSessionProjcacheRows, xt as tSchemaMeta, yt as tPersistenceState } from "./log-C0q5svm7.mjs";
2
- import { SessionId } from "@deepseek-ai/dsh-session";
3
- import { DatabaseSync } from "node:sqlite";
4
- //#region src/sqlite.d.ts
5
- export declare function openDatabase(path: string, journalMode: JournalMode, busyTimeout?: number): Promise<DatabaseSync>;
6
- export interface SqliteBackendOptions {
7
- path: string;
8
- journalMode: JournalMode;
9
- busyTimeout: number;
10
- }
11
- export declare class SqliteBackend implements Backend {
12
- private readonly options;
13
- readonly kind: "sqlite";
14
- storeIdentity: string;
1
+ import { F as JournalMode, G as StorageRepository, I as SCHEMA_VERSION, L as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, N as DEFAULT_BUSY_TIMEOUT_MS, P as EVENT_ENCODING, R as WriteGuard, a as SessionCountBucket, c as SessionRow, i as ReadModel, l as SessionUsageBucket, n as BackendTx, o as SessionListRowsPage, p as UsageAggregate, r as EventRow, s as SessionListRowsQuery, t as Backend } from "./backend-BKtJ5sO5.mjs";
2
+ import { Database } from "@morlay/rdbkit";
3
+ import { SessionEvent, SessionId } from "@deepseek-ai/dsh-session";
4
+ export type EventKind = "message" | "thinking" | "turn" | "tool" | "request" | "config" | "audit" | "lifecycle" | "inbox" | "compaction" | "llm" | "subagent" | "team" | "workflow" | "goal" | "schedule" | "todo" | "web";
5
+ export type EventRole = "user" | "assistant" | "tool";
6
+ export declare function eventKind(event: {
7
+ type: string;
8
+ data?: unknown;
9
+ }): EventKind | "";
10
+ export declare function eventDimensions(event: SessionEvent): {
11
+ kind: string;
12
+ role: string;
13
+ name: string;
14
+ actionId: string;
15
+ };
16
+ /**
17
+ * 唯一的 backend 实现:读写方法收敛在这里(读模型 + `session-ops` + 表句柄);打开流程在 `db/open.ts`,
18
+ * 统计面在 `db/stats.ts`。方言差异全部由 rdbkit 的表达式承担,这里不出现任何 SQL 文本。
19
+ */
20
+ export declare class RdbBackend implements Backend {
21
+ #private;
15
22
  readonly storage: StorageRepository;
16
- private dbPath;
17
- private db;
18
- private readonly dbReady;
19
- private resolveDb;
20
- private rejectDb;
21
- constructor(options: SqliteBackendOptions);
23
+ readonly storeIdentity: string;
24
+ readonly reads: ReadModel;
25
+ constructor(options: {
26
+ kind: "sqlite" | "postgres";
27
+ database: Database;
28
+ storage: StorageRepository;
29
+ readModel: ReadModel;
30
+ storeIdentity: string;
31
+ });
32
+ readonly kind: "sqlite" | "postgres";
22
33
  open(): Promise<void>;
23
- private doOpen;
24
34
  close(): Promise<void>;
25
- getSession(id: SessionId): Promise<SessionRow | undefined>;
26
- getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]>;
27
- getEventTypeAt(id: SessionId, sequence: number): Promise<string | undefined>;
28
- getEventTypesBefore(id: SessionId, beforeSequence: number, limit: number): Promise<Array<Pick<EventRow, "fSequence" | "fType">>>;
29
- listSessions(): Promise<SessionRow[]>;
30
35
  transaction<T>(fn: (tx: BackendTx) => Promise<T>): Promise<T>;
31
- private readonly tx;
32
- private upsertSession;
33
- private getHead;
34
- private getSeedLength;
35
- private updateSeedLength;
36
- private static readonly INSERT_BATCH_ROWS;
37
- private insertEvents;
38
- private insertBridges;
39
- private updateHead;
40
- private bumpRevision;
41
- private refreshTitle;
42
- private deleteBridgeTail;
43
- private getPrevBridge;
44
- private deleteSession;
45
- collectOrphans(): Promise<number>;
46
36
  listOrphanSubagentSessions(): Promise<SessionId[]>;
47
- private deleteSessions;
48
- listSessionRows(query?: SessionListRowsQuery): Promise<SessionListRowsPage>;
37
+ /** 当前 head 指针(分支 / 测试读路径用;写路径在 `BackendTx` 里)。 */
38
+ getHead(id: SessionId): Promise<Pick<SessionRow, "fHeadEventId" | "fHeadSequence">>;
39
+ collectOrphans(): Promise<number>;
49
40
  usageReport(sinceMs?: number): Promise<UsageAggregate>;
50
- incrementSessionUsage(id: SessionId, buckets: readonly SessionUsageBucket[]): Promise<void>;
51
- incrementSessionCounts(id: SessionId, buckets: readonly SessionCountBucket[]): Promise<void>;
52
- rebuildSessionStats(id: SessionId): Promise<void>;
41
+ listSessionRows(query?: SessionListRowsQuery): Promise<SessionListRowsPage>;
42
+ incrementSessionUsage(id: string, buckets: readonly SessionUsageBucket[]): Promise<void>;
43
+ incrementSessionCounts(id: string, buckets: readonly SessionCountBucket[]): Promise<void>;
44
+ deleteSessionStats(id: string): Promise<void>;
45
+ rebuildSessionStats(id: string): Promise<void>;
46
+ rebuildAllStats(): Promise<number>;
53
47
  refreshEventUsageFlags(eventIds: readonly string[]): Promise<void>;
54
- deleteSessionStats(id: SessionId): Promise<void>;
55
48
  vacuum(): Promise<void>;
56
- private eventRows;
57
49
  }
58
- //#endregion
59
- export { DEFAULT_BUSY_TIMEOUT_MS, EVENT_ENCODING, EventKind, EventRole, type EventRow, JournalMode, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, type SessionRow, WriteGuard, eventDimensions, eventKind, tEventUsage, tEvents, tPersistenceState, tScheduleTasks, tSchemaMeta, tSessionCounts, tSessionEvents, tSessionProjcacheRows, tSessionUsage, tSessions, tStorageUnits, tWorkspaceSessions, tWorkspaceState, tWorkspaces };
50
+ export { DEFAULT_BUSY_TIMEOUT_MS, EVENT_ENCODING, type EventRow, JournalMode, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, type SessionRow, WriteGuard };
package/dist/storage.mjs CHANGED
@@ -1 +1,3 @@
1
- export { i as DEFAULT_BUSY_TIMEOUT_MS, a as EVENT_ENCODING, o as SCHEMA_VERSION, s as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, t as SqliteBackend, E as WriteGuard, c as eventDimensions, l as eventKind, n as openDatabase, u as tEventUsage, d as tEvents, f as tPersistenceState, p as tScheduleTasks, m as tSchemaMeta, h as tSessionCounts, g as tSessionEvents, _ as tSessionProjcacheRows, v as tSessionUsage, y as tSessions, b as tStorageUnits, x as tWorkspaceSessions, S as tWorkspaceState, C as tWorkspaces } from "./sqlite-DjOnMRh2.mjs";
1
+ import { d as EVENT_ENCODING, f as SCHEMA_VERSION, p as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, t as RdbBackend, u as DEFAULT_BUSY_TIMEOUT_MS } from "./backend-rdb-BDCntVDy.mjs";
2
+ import { n as eventDimensions, r as eventKind, t as WriteGuard } from "./write-guard-CYrY58GM.mjs";
3
+ export { DEFAULT_BUSY_TIMEOUT_MS, EVENT_ENCODING, RdbBackend, SCHEMA_VERSION, SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, WriteGuard, eventDimensions, eventKind };
@@ -1,8 +1,9 @@
1
- import { at as truncateLiveSession } from "./log-C0q5svm7.mjs";
1
+ import { F as JournalMode } from "./backend-BKtJ5sO5.mjs";
2
+ import { i as truncateLiveSession } from "./index-CUD2ZpTA.mjs";
2
3
  import { Session, SessionEvent, SessionHeader, SessionId } from "@deepseek-ai/dsh-session";
3
4
  import { SessionPersistence } from "@deepseek-ai/dsh-session-persistence";
4
5
  import { Context, Fiber } from "@deepseek-ai/cordis";
5
- //#region src/testing/contract.d.ts
6
+ import { DatabaseSync } from "node:sqlite";
6
7
  interface ContractBackendInstance {
7
8
  persistence: SessionPersistence;
8
9
  dispose: () => Promise<void>;
@@ -15,13 +16,16 @@ export declare function meta(id: string, cwd?: string): SessionHeader;
15
16
  export declare function oneTurnLog(): SessionEvent[];
16
17
  export declare function appendLog(session: Session, events: readonly SessionEvent[]): void;
17
18
  export declare function runPersistenceContract(name: string, make: () => Promise<ContractBackend>): void;
18
- //#endregion
19
- //#region src/testing/coordinator-contract.d.ts
20
19
  interface CoordinatorFixture {
21
20
  mount: (ctx: Context) => Promise<Fiber>;
22
21
  corruptTail?: (id: SessionId, cwd: string | undefined) => Promise<void>;
23
22
  cleanup: () => Promise<void>;
24
23
  }
25
24
  export declare function runCoordinatorContract(name: string, makeFixture: () => Promise<CoordinatorFixture>): void;
26
- //#endregion
25
+ /**
26
+ * 测试辅助:先把库初始化(建表 / 迁移 / 身份),再给一个同步句柄——造损坏数据、读 `PRAGMA` 用。
27
+ *
28
+ * 同步口只在测试里存在:生产路径拿到的永远是 `Database`。
29
+ */
30
+ export declare function openInitializedSqlite(path: string, journalMode?: JournalMode, busyTimeout?: number): Promise<DatabaseSync>;
27
31
  export { type ContractBackend, type CoordinatorFixture, truncateLiveSession };
package/dist/testing.mjs CHANGED
@@ -1,10 +1,13 @@
1
- import { a as truncateLiveSession } from "./branch-DbFW3rJX.mjs";
1
+ import { a as truncateLiveSession } from "./branch-CG1PUlO6.mjs";
2
+ import { u as DEFAULT_BUSY_TIMEOUT_MS } from "./backend-rdb-BDCntVDy.mjs";
3
+ import { t as openRdbBackend } from "./open-YoyD_kah.mjs";
2
4
  import SessionStore, { SESSION_FORMAT_VERSION, SessionId, SessionLogOffset, SessionSeq } from "@deepseek-ai/dsh-session";
3
- import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionFormatUnsupportedError, SessionHandleClosedError, SessionPersistenceNotFoundError, SessionReadOnlyError } from "@deepseek-ai/dsh-session-persistence";
4
- import { Context } from "@deepseek-ai/cordis";
5
+ import { openSqliteHandle } from "@morlay/rdbkit/testing";
5
6
  import { describe, expect, it } from "vitest";
6
7
  import { MessageId, createUserMessage, freezeMessage } from "@deepseek-ai/dsh-llm";
7
- //#region src/testing/contract.ts
8
+ import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionFormatUnsupportedError, SessionHandleClosedError, SessionPersistenceNotFoundError, SessionReadOnlyError } from "@deepseek-ai/dsh-session-persistence";
9
+ import { Context } from "@deepseek-ai/cordis";
10
+ // src/testing/contract.ts
8
11
  function meta(id, cwd) {
9
12
  return {
10
13
  version: SESSION_FORMAT_VERSION,
@@ -636,8 +639,7 @@ function runPersistenceContract(name, make) {
636
639
  });
637
640
  });
638
641
  }
639
- //#endregion
640
- //#region src/testing/coordinator-contract.ts
642
+ // src/testing/coordinator-contract.ts
641
643
  const WORK = "/w";
642
644
  function send(session, events) {
643
645
  appendLog(session, events);
@@ -1048,5 +1050,22 @@ function runCoordinatorContract(name, makeFixture) {
1048
1050
  });
1049
1051
  });
1050
1052
  }
1051
- //#endregion
1052
- export { appendLog, meta, oneTurnLog, runCoordinatorContract, runPersistenceContract, truncateLiveSession };
1053
+ // src/testing.ts
1054
+ /**
1055
+ * 测试辅助:先把库初始化(建表 / 迁移 / 身份),再给一个同步句柄——造损坏数据、读 `PRAGMA` 用。
1056
+ *
1057
+ * 同步口只在测试里存在:生产路径拿到的永远是 `Database`。
1058
+ */
1059
+ async function openInitializedSqlite(path, journalMode = "wal", busyTimeout = DEFAULT_BUSY_TIMEOUT_MS) {
1060
+ await (await openRdbBackend({
1061
+ type: "sqlite",
1062
+ path,
1063
+ journalMode,
1064
+ busyTimeout
1065
+ })).close();
1066
+ return openSqliteHandle(path, {
1067
+ journalMode,
1068
+ busyTimeout
1069
+ });
1070
+ }
1071
+ export { appendLog, meta, oneTurnLog, openInitializedSqlite, runCoordinatorContract, runPersistenceContract, truncateLiveSession };
@@ -0,0 +1,36 @@
1
+ import { localDayKey } from "./usage.mjs";
2
+ // src/log/usage-row.ts
3
+ function numericField(value) {
4
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : 0;
5
+ }
6
+ function textField(value) {
7
+ return typeof value === "string" && value !== "" ? value : null;
8
+ }
9
+ function usageRowOf(event, subagent) {
10
+ if (event.fType !== "assistant/message") return void 0;
11
+ let parsed;
12
+ try {
13
+ parsed = JSON.parse(event.fData);
14
+ } catch {
15
+ return;
16
+ }
17
+ const envelope = parsed["data"] ?? parsed;
18
+ const usage = envelope["usage"];
19
+ if (typeof usage !== "object" || usage === null) return void 0;
20
+ const source = envelope["message"]?.source;
21
+ return {
22
+ fEventId: event.fEventId,
23
+ fCreatedAt: event.fCreatedAt,
24
+ fDay: localDayKey(event.fCreatedAt),
25
+ fProvider: textField(source?.["provider"]),
26
+ fModel: textField(source?.["model"]),
27
+ fReferenced: 1,
28
+ fSubagent: subagent ? 1 : 0,
29
+ fInputTokens: numericField(usage["inputTokens"]),
30
+ fOutputTokens: numericField(usage["outputTokens"]),
31
+ fCacheReadTokens: numericField(usage["cacheReadTokens"]),
32
+ fReasoningTokens: numericField(usage["reasoningTokens"]),
33
+ fTotalTokens: numericField(usage["totalTokens"])
34
+ };
35
+ }
36
+ export { usageRowOf as t };
package/dist/usage.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { B as UsageTotals, F as UsageAggregate, G as emptyTotals, H as addActivityTotals, I as UsageBucket, J as resolveUsageSince, K as localDayKey, L as UsageRangeKey, Lt as SESSION_USAGE_PATH, M as COUNTED_EVENT_TYPES, N as SessionUsageReport, P as UsageActivityTotals, R as UsageSessionRow, U as addTokenTotals, V as addActivityCount, W as addTotals, q as registerSessionUsage, z as UsageTokenTotals } from "./log-C0q5svm7.mjs";
1
+ import { C as emptyTotals, E as resolveUsageSince, S as addTotals, T as registerSessionUsage, W as SESSION_USAGE_PATH, _ as UsageTokenTotals, b as addActivityTotals, d as SessionUsageReport, f as UsageActivityTotals, g as UsageSessionRow, h as UsageRangeKey, m as UsageBucket, p as UsageAggregate, u as COUNTED_EVENT_TYPES, v as UsageTotals, w as localDayKey, x as addTokenTotals, y as addActivityCount } from "./backend-BKtJ5sO5.mjs";
2
2
  export { COUNTED_EVENT_TYPES, SESSION_USAGE_PATH, SessionUsageReport, UsageActivityTotals, UsageAggregate, UsageBucket, UsageRangeKey, UsageSessionRow, UsageTokenTotals, UsageTotals, addActivityCount, addActivityTotals, addTokenTotals, addTotals, emptyTotals, localDayKey, registerSessionUsage, resolveUsageSince };
package/dist/usage.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { o as SESSION_USAGE_PATH } from "./routes-DGBlBE_I.mjs";
2
- //#region src/usage.ts
1
+ import { l as SESSION_USAGE_PATH, n as mountJsonRoute } from "./route-DpfY0zq-.mjs";
2
+ // src/usage.ts
3
3
  const ROLLING_DAYS = {
4
4
  "7d": 7,
5
5
  "30d": 30,
@@ -91,46 +91,12 @@ function addTotals(target, row) {
91
91
  return target;
92
92
  }
93
93
  function registerSessionUsage(ctx, persistence) {
94
- ctx.inject(["webServer", "connection"], (webCtx) => {
95
- const webServer = webCtx.webServer;
96
- const connection = webCtx.get("connection");
97
- return webCtx.effect(() => webServer.register({
98
- kind: "exact",
99
- path: SESSION_USAGE_PATH,
100
- handler: async (req, res) => {
101
- const rejection = connection.requestRejection(req);
102
- if (rejection !== void 0) {
103
- res.writeHead(rejection);
104
- res.end(rejection === 401 ? "unauthorized" : "forbidden");
105
- return;
106
- }
107
- if (req.method !== "POST") {
108
- res.writeHead(405, { "content-type": "application/json" });
109
- res.end(JSON.stringify({ error: "method not allowed" }));
110
- return;
111
- }
112
- const chunks = [];
113
- for await (const chunk of req) chunks.push(chunk);
114
- const raw = Buffer.concat(chunks).toString("utf8");
115
- let envelope = {};
116
- if (raw !== "") try {
117
- envelope = JSON.parse(raw);
118
- } catch {
119
- res.writeHead(400, { "content-type": "application/json" });
120
- res.end(JSON.stringify({ error: "request body is not JSON" }));
121
- return;
122
- }
123
- try {
124
- const report = await persistence.usageReport(resolveUsageSince(parseUsageRange(envelope.range), Date.now()));
125
- res.writeHead(200, { "content-type": "application/json" });
126
- res.end(JSON.stringify(report));
127
- } catch (error) {
128
- res.writeHead(500, { "content-type": "application/json" });
129
- res.end(JSON.stringify({ error: error instanceof Error ? error.message : "usage report failed" }));
130
- }
131
- }
132
- }), `session-rdb: ${SESSION_USAGE_PATH} route`);
94
+ mountJsonRoute(ctx, {
95
+ path: SESSION_USAGE_PATH,
96
+ handle: async (body) => {
97
+ const { range } = body;
98
+ return { body: await persistence.usageReport(resolveUsageSince(parseUsageRange(range), Date.now())) };
99
+ }
133
100
  });
134
101
  }
135
- //#endregion
136
102
  export { COUNTED_EVENT_TYPES, SESSION_USAGE_PATH, addActivityCount, addActivityTotals, addTokenTotals, addTotals, emptyTotals, localDayKey, registerSessionUsage, resolveUsageSince };
@@ -0,0 +1,174 @@
1
+ // src/event-shape.ts
2
+ function eventKind(event) {
3
+ switch (event.type) {
4
+ case "user/message": return "message";
5
+ case "assistant/message":
6
+ if ((event.data.message?.content)?.some((block) => block?.type === "reasoning")) return "thinking";
7
+ return "message";
8
+ case "turn/start":
9
+ case "turn/end":
10
+ case "step/start":
11
+ case "step/end":
12
+ case "session/end-seed": return "turn";
13
+ case "tool/call":
14
+ case "tool/result":
15
+ case "tool/ptc-dispatch-start":
16
+ case "tool/ptc-dispatch":
17
+ case "tool/code-dispatch-start":
18
+ case "tool/code-dispatch": return "tool";
19
+ case "request/header":
20
+ case "request/context": return "request";
21
+ case "model/selection":
22
+ case "permission/preset":
23
+ case "approval/policy":
24
+ case "sandbox/mode":
25
+ case "plan/mode":
26
+ case "agent-preset/selected": return "config";
27
+ case "approval/asked":
28
+ case "approval/decided":
29
+ case "command/run":
30
+ case "command/done":
31
+ case "hook/invoked":
32
+ case "hook/result":
33
+ case "feedback/record": return "audit";
34
+ case "session/title":
35
+ case "session/title-llm-request":
36
+ case "session-log-deepseek/delivery-accepted": return "lifecycle";
37
+ case "agent/inbox/spliced": return "inbox";
38
+ case "compaction/start":
39
+ case "compaction/end":
40
+ case "compaction/summary":
41
+ case "compaction/prune": return "compaction";
42
+ case "llm/retry":
43
+ case "llm/retry-started": return "llm";
44
+ case "subagent/descriptor":
45
+ case "subagent/model-selection-policy": return "subagent";
46
+ case "team/member":
47
+ case "team/task":
48
+ case "team/message/queued":
49
+ case "team/message/delivered": return "team";
50
+ case "tool-workflow/run-start":
51
+ case "tool-workflow/run-end":
52
+ case "tool-workflow/agent-start":
53
+ case "tool-workflow/agent-end": return "workflow";
54
+ case "goal/change": return "goal";
55
+ case "schedule/change": return "schedule";
56
+ case "todo/write": return "todo";
57
+ case "web/deepseek-search-llm-request": return "web";
58
+ default: return "";
59
+ }
60
+ }
61
+ function eventDimensions(event) {
62
+ const kind = eventKind(event);
63
+ const type = event.type;
64
+ const data = event.data;
65
+ switch (type) {
66
+ case "user/message": return {
67
+ kind,
68
+ role: "user",
69
+ name: "",
70
+ actionId: ""
71
+ };
72
+ case "assistant/message": return {
73
+ kind,
74
+ role: "assistant",
75
+ name: "",
76
+ actionId: ""
77
+ };
78
+ case "tool/result": return {
79
+ kind,
80
+ role: "tool",
81
+ name: "",
82
+ actionId: data["message"]?.toolCallId ?? ""
83
+ };
84
+ case "tool/call": return {
85
+ kind,
86
+ role: "",
87
+ name: typeof data["name"] === "string" ? data["name"] : "",
88
+ actionId: typeof data["callId"] === "string" ? data["callId"] : ""
89
+ };
90
+ case "tool/ptc-dispatch-start":
91
+ case "tool/ptc-dispatch":
92
+ case "tool/code-dispatch-start":
93
+ case "tool/code-dispatch": return {
94
+ kind,
95
+ role: "",
96
+ name: "",
97
+ actionId: typeof data["subCallId"] === "string" ? data["subCallId"] : ""
98
+ };
99
+ case "command/run":
100
+ case "command/done": return {
101
+ kind,
102
+ role: "",
103
+ name: type === "command/run" && typeof data["name"] === "string" ? data["name"] : "",
104
+ actionId: typeof data["commandId"] === "string" ? data["commandId"] : ""
105
+ };
106
+ case "approval/asked":
107
+ case "approval/decided": return {
108
+ kind,
109
+ role: "",
110
+ name: "",
111
+ actionId: typeof data["id"] === "string" ? data["id"] : ""
112
+ };
113
+ case "hook/invoked":
114
+ case "hook/result": return {
115
+ kind,
116
+ role: "",
117
+ name: "",
118
+ actionId: typeof data["handlerId"] === "string" ? data["handlerId"] : ""
119
+ };
120
+ case "llm/retry":
121
+ case "llm/retry-started": return {
122
+ kind,
123
+ role: "",
124
+ name: "",
125
+ actionId: typeof data["retryId"] === "string" ? data["retryId"] : ""
126
+ };
127
+ case "tool-workflow/run-start":
128
+ case "tool-workflow/run-end":
129
+ case "tool-workflow/agent-start":
130
+ case "tool-workflow/agent-end": return {
131
+ kind,
132
+ role: "",
133
+ name: "",
134
+ actionId: typeof data["runId"] === "string" ? data["runId"] : ""
135
+ };
136
+ case "todo/write": return {
137
+ kind,
138
+ role: "",
139
+ name: "todos",
140
+ actionId: ""
141
+ };
142
+ case "subagent/descriptor": return {
143
+ kind,
144
+ role: "",
145
+ name: typeof data["label"] === "string" ? data["label"] : "",
146
+ actionId: ""
147
+ };
148
+ default: return {
149
+ kind,
150
+ role: "",
151
+ name: "",
152
+ actionId: ""
153
+ };
154
+ }
155
+ }
156
+ // src/write-guard.ts
157
+ var WriteGuard = class {
158
+ headSeqs = /* @__PURE__ */ new Map();
159
+ confirmHead(id, head) {
160
+ this.headSeqs.set(id, head);
161
+ }
162
+ has(id) {
163
+ return this.headSeqs.has(id);
164
+ }
165
+ assertNoConcurrentWriter(id, storedHead) {
166
+ const known = this.headSeqs.get(id);
167
+ if (known === void 0) {
168
+ 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`);
169
+ return;
170
+ }
171
+ 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`);
172
+ }
173
+ };
174
+ export { eventDimensions as n, eventKind as r, WriteGuard as t };