@morlay/session-rdb 0.1.10-alpha.0 → 0.1.10-alpha.3

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 (142) hide show
  1. package/README.md +18 -3
  2. package/dist/artifact.d.mts +118 -2
  3. package/dist/artifact.mjs +5 -3
  4. package/dist/{log-TYgd8Xkr.d.mts → backend-BKtJ5sO5.d.mts} +183 -284
  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 -1
  9. package/dist/deletion.mjs +2 -2
  10. package/dist/export-CfJQjQfr.mjs +123 -0
  11. package/dist/export.d.mts +1 -1
  12. package/dist/export.mjs +31 -55
  13. package/dist/import.d.mts +1 -1
  14. package/dist/import.mjs +31 -76
  15. package/dist/index-CUD2ZpTA.d.mts +71 -0
  16. package/dist/index.d.mts +20 -2
  17. package/dist/index.mjs +1561 -4
  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 -48
  23. package/dist/storage.mjs +3 -1
  24. package/dist/testing.d.mts +9 -1
  25. package/dist/testing.mjs +25 -4
  26. package/dist/usage-row-BQQM-_t6.mjs +36 -0
  27. package/dist/usage.d.mts +1 -1
  28. package/dist/usage.mjs +7 -40
  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} +109 -110
  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 -1666
  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 -268
  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-store.ts +675 -0
  74. package/src/stats-fold.ts +44 -0
  75. package/src/storage-takeover/index.ts +2 -5
  76. package/src/storage-takeover/legacy-domain-migration.ts +118 -0
  77. package/src/storage-takeover/projection-cache.ts +122 -71
  78. package/src/storage-takeover/read-model.ts +189 -0
  79. package/src/storage-takeover/repository.ts +236 -442
  80. package/src/storage-takeover/storage-backend.ts +44 -159
  81. package/src/storage-takeover/types.ts +45 -19
  82. package/src/storage-takeover/workspace-documents.ts +65 -0
  83. package/src/storage.ts +4 -2
  84. package/src/testing.ts +21 -1
  85. package/src/usage.ts +11 -65
  86. package/src/version.ts +13 -0
  87. package/src/write-path.ts +286 -0
  88. package/dist/branch-CoPCRKer.mjs +0 -401
  89. package/dist/deletion-CWl89TIR.mjs +0 -2249
  90. package/dist/gc.d.mts +0 -4
  91. package/dist/gc.mjs +0 -48
  92. package/dist/log-CcWy46VE.mjs +0 -558
  93. package/dist/routes-hZjTcX_R.mjs +0 -9
  94. package/dist/sqlite-BNRd1aqR.mjs +0 -1778
  95. package/drizzle/postgres/20260908072805_v2_initial/migration.sql +0 -58
  96. package/drizzle/postgres/20260908072805_v2_initial/snapshot.json +0 -686
  97. package/drizzle/postgres/20260908072806_v3_drop_original_seq/migration.sql +0 -1
  98. package/drizzle/postgres/20260908072806_v3_drop_original_seq/snapshot.json +0 -673
  99. package/drizzle/postgres/20260910120001_v3_storage_tables/migration.sql +0 -65
  100. package/drizzle/postgres/20260910120001_v3_storage_tables/snapshot.json +0 -1183
  101. package/drizzle/postgres/20260910130001_v3_session_title_backfill/migration.sql +0 -10
  102. package/drizzle/postgres/20260910130001_v3_session_title_backfill/snapshot.json +0 -1183
  103. package/drizzle/postgres/20260918120000_v3_event_usage/migration.sql +0 -14
  104. package/drizzle/postgres/20260918120000_v3_event_usage/snapshot.json +0 -1309
  105. package/drizzle/postgres/20260921075900_v3_agent_preset/migration.sql +0 -1
  106. package/drizzle/postgres/20260922090001_v3_pinned_seq/migration.sql +0 -2
  107. package/drizzle/postgres/20260922093001_v3_last_event_at/migration.sql +0 -10
  108. package/drizzle/postgres/20260923120000_v3_usage_materialized/migration.sql +0 -48
  109. package/drizzle/postgres/20261008103001_v3_schedule_tasks/migration.sql +0 -11
  110. package/drizzle/sqlite/20260908072751_v2_initial/migration.sql +0 -53
  111. package/drizzle/sqlite/20260908072751_v2_initial/snapshot.json +0 -492
  112. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/migration.sql +0 -6
  113. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/snapshot.json +0 -513
  114. package/drizzle/sqlite/20260910120000_v3_storage_tables/migration.sql +0 -66
  115. package/drizzle/sqlite/20260910120000_v3_storage_tables/snapshot.json +0 -905
  116. package/drizzle/sqlite/20260910130000_v3_session_title_backfill/migration.sql +0 -13
  117. package/drizzle/sqlite/20260910130000_v3_session_title_backfill/snapshot.json +0 -905
  118. package/drizzle/sqlite/20260918120000_v3_event_usage/migration.sql +0 -14
  119. package/drizzle/sqlite/20260918120000_v3_event_usage/snapshot.json +0 -1031
  120. package/drizzle/sqlite/20260921075855_v3_agent_preset/migration.sql +0 -7
  121. package/drizzle/sqlite/20260921075855_v3_agent_preset/snapshot.json +0 -1041
  122. package/drizzle/sqlite/20260922090000_v3_pinned_seq/migration.sql +0 -4
  123. package/drizzle/sqlite/20260922093000_v3_last_event_at/migration.sql +0 -13
  124. package/drizzle/sqlite/20260923120000_v3_usage_materialized/migration.sql +0 -48
  125. package/drizzle/sqlite/20261008103000_v3_schedule_tasks/migration.sql +0 -14
  126. package/src/adapters/index.ts +0 -12
  127. package/src/adapters/to-postgres.ts +0 -93
  128. package/src/adapters/to-sqlite.ts +0 -83
  129. package/src/adapters/types.ts +0 -44
  130. package/src/drizzle/postgres-v2.ts +0 -10
  131. package/src/drizzle/postgres-v3.ts +0 -18
  132. package/src/drizzle/sqlite-v2.ts +0 -9
  133. package/src/drizzle/sqlite-v3.ts +0 -18
  134. package/src/entities/v2/index.ts +0 -20
  135. package/src/entities/v2/session-events.ts +0 -10
  136. package/src/entities/v3/schedule-tasks.ts +0 -16
  137. package/src/entities/v3/workspace-sessions.ts +0 -19
  138. package/src/entities/v3/workspace-state.ts +0 -12
  139. package/src/entities/v3/workspaces.ts +0 -14
  140. package/src/gc.ts +0 -77
  141. package/src/postgres.ts +0 -1015
  142. package/src/sqlite.ts +0 -1096
@@ -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,57 +1,50 @@
1
- import { A as SessionRow, At as tWorkspaceState, Ct as tSessionCounts, D as SessionCountBucket, Dt as tSessions, E as EventRow, Et as tSessionUsage, F as UsageAggregate, Mt as WriteGuard, O as SessionListRowsPage, Ot as tStorageUnits, St as tSchemaMeta, T as BackendTx, Tt as tSessionProjcacheRows, _t as eventKind, bt as tPersistenceState, dt as EventKind, ft as EventRole, gt as eventDimensions, ht as SESSION_PERSISTENCE_SQLITE_APPLICATION_ID, j as SessionUsageBucket, jt as tWorkspaces, k as SessionListRowsQuery, kt as tWorkspaceSessions, lt as DEFAULT_BUSY_TIMEOUT_MS, mt as SCHEMA_VERSION, pt as JournalMode, ut as EVENT_ENCODING, vt as tEventUsage, w as Backend, wt as tSessionEvents, xt as tScheduleTasks, yt as tEvents, zt as StorageRepository } from "./log-TYgd8Xkr.mjs";
2
- import { SessionId } from "@deepseek-ai/dsh-session";
3
- import { DatabaseSync } from "node:sqlite";
4
- export declare function openDatabase(path: string, journalMode: JournalMode, busyTimeout?: number): Promise<DatabaseSync>;
5
- export interface SqliteBackendOptions {
6
- path: string;
7
- journalMode: JournalMode;
8
- busyTimeout: number;
9
- }
10
- export declare class SqliteBackend implements Backend {
11
- private readonly options;
12
- readonly kind: "sqlite";
13
- 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;
14
22
  readonly storage: StorageRepository;
15
- private dbPath;
16
- private db;
17
- private readonly dbReady;
18
- private resolveDb;
19
- private rejectDb;
20
- 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";
21
33
  open(): Promise<void>;
22
- private doOpen;
23
34
  close(): Promise<void>;
24
- getSession(id: SessionId): Promise<SessionRow | undefined>;
25
- getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]>;
26
- getEventTypeAt(id: SessionId, sequence: number): Promise<string | undefined>;
27
- getEventTypesBefore(id: SessionId, beforeSequence: number, limit: number): Promise<Array<Pick<EventRow, "fSequence" | "fType">>>;
28
- listSessions(): Promise<SessionRow[]>;
29
35
  transaction<T>(fn: (tx: BackendTx) => Promise<T>): Promise<T>;
30
- private readonly tx;
31
- private upsertSession;
32
- private getHead;
33
- private getSeedLength;
34
- private updateSeedLength;
35
- private static readonly INSERT_BATCH_ROWS;
36
- private insertEvents;
37
- private insertBridges;
38
- private updateHead;
39
- private bumpRevision;
40
- private refreshTitle;
41
- private deleteBridgeTail;
42
- private getPrevBridge;
43
- private deleteSession;
44
- collectOrphans(): Promise<number>;
45
36
  listOrphanSubagentSessions(): Promise<SessionId[]>;
46
- private deleteSessions;
47
- listSessionRows(query?: SessionListRowsQuery): Promise<SessionListRowsPage>;
37
+ /** 当前 head 指针(分支 / 测试读路径用;写路径在 `BackendTx` 里)。 */
38
+ getHead(id: SessionId): Promise<Pick<SessionRow, "fHeadEventId" | "fHeadSequence">>;
39
+ collectOrphans(): Promise<number>;
48
40
  usageReport(sinceMs?: number): Promise<UsageAggregate>;
49
- incrementSessionUsage(id: SessionId, buckets: readonly SessionUsageBucket[]): Promise<void>;
50
- incrementSessionCounts(id: SessionId, buckets: readonly SessionCountBucket[]): Promise<void>;
51
- 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>;
52
47
  refreshEventUsageFlags(eventIds: readonly string[]): Promise<void>;
53
- deleteSessionStats(id: SessionId): Promise<void>;
54
48
  vacuum(): Promise<void>;
55
- private eventRows;
56
49
  }
57
- 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-BNRd1aqR.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,7 +1,9 @@
1
- import { ot as truncateLiveSession } from "./log-TYgd8Xkr.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";
6
+ import { DatabaseSync } from "node:sqlite";
5
7
  interface ContractBackendInstance {
6
8
  persistence: SessionPersistence;
7
9
  dispose: () => Promise<void>;
@@ -20,4 +22,10 @@ interface CoordinatorFixture {
20
22
  cleanup: () => Promise<void>;
21
23
  }
22
24
  export declare function runCoordinatorContract(name: string, makeFixture: () => Promise<CoordinatorFixture>): void;
25
+ /**
26
+ * 测试辅助:先把库初始化(建表 / 迁移 / 身份),再给一个同步句柄——造损坏数据、读 `PRAGMA` 用。
27
+ *
28
+ * 同步口只在测试里存在:生产路径拿到的永远是 `Database`。
29
+ */
30
+ export declare function openInitializedSqlite(path: string, journalMode?: JournalMode, busyTimeout?: number): Promise<DatabaseSync>;
23
31
  export { type ContractBackend, type CoordinatorFixture, truncateLiveSession };
package/dist/testing.mjs CHANGED
@@ -1,9 +1,12 @@
1
- import { o as truncateLiveSession } from "./branch-CoPCRKer.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";
8
+ import { SessionAlreadyExistsError, SessionAlreadyOwnedError, SessionFormatUnsupportedError, SessionHandleClosedError, SessionPersistenceNotFoundError, SessionReadOnlyError } from "@deepseek-ai/dsh-session-persistence";
9
+ import { Context } from "@deepseek-ai/cordis";
7
10
  // src/testing/contract.ts
8
11
  function meta(id, cwd) {
9
12
  return {
@@ -1047,4 +1050,22 @@ function runCoordinatorContract(name, makeFixture) {
1047
1050
  });
1048
1051
  });
1049
1052
  }
1050
- 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, M as COUNTED_EVENT_TYPES, N as SessionUsageReport, P as UsageActivityTotals, R as UsageSessionRow, Rt as SESSION_USAGE_PATH, U as addTokenTotals, V as addActivityCount, W as addTotals, q as registerSessionUsage, z as UsageTokenTotals } from "./log-TYgd8Xkr.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,4 +1,4 @@
1
- import { o as SESSION_USAGE_PATH } from "./routes-hZjTcX_R.mjs";
1
+ import { l as SESSION_USAGE_PATH, n as mountJsonRoute } from "./route-DpfY0zq-.mjs";
2
2
  // src/usage.ts
3
3
  const ROLLING_DAYS = {
4
4
  "7d": 7,
@@ -91,45 +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
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 };