@morlay/session-rdb 0.0.14 → 0.0.16-alpha.0

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 (65) hide show
  1. package/README.md +11 -13
  2. package/dist/artifact.d.mts +6 -16
  3. package/dist/artifact.mjs +3 -3
  4. package/dist/{import-B-tf5vQn.mjs → import-CKrzjXVB.mjs} +73 -83
  5. package/dist/import.d.mts +2 -3
  6. package/dist/import.mjs +2 -2
  7. package/dist/index-CgGDHQSK.d.mts +225 -0
  8. package/dist/index.d.mts +3 -3
  9. package/dist/index.mjs +694 -169
  10. package/dist/log-DTJsxMud.mjs +314 -0
  11. package/dist/{schema-CFXZURX7.d.mts → schema-COK7-wRV.d.mts} +3 -15
  12. package/dist/sqlite-DCy4VTD8.mjs +698 -0
  13. package/dist/storage.d.mts +2 -7
  14. package/dist/storage.mjs +2 -3
  15. package/dist/testing.d.mts +30 -1
  16. package/dist/testing.mjs +676 -1991
  17. package/drizzle/postgres/20260908072805_v2_initial/migration.sql +58 -0
  18. package/drizzle/postgres/20260908072805_v2_initial/snapshot.json +686 -0
  19. package/drizzle/postgres/20260908072806_v3_drop_original_seq/migration.sql +1 -0
  20. package/drizzle/postgres/20260908072806_v3_drop_original_seq/snapshot.json +673 -0
  21. package/drizzle/sqlite/20260908072751_v2_initial/migration.sql +53 -0
  22. package/drizzle/sqlite/20260908072751_v2_initial/snapshot.json +492 -0
  23. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/migration.sql +6 -0
  24. package/drizzle/sqlite/20260908072752_v3_drop_original_seq/snapshot.json +513 -0
  25. package/package.json +17 -12
  26. package/src/adapters/index.ts +10 -2
  27. package/src/adapters/to-postgres.ts +19 -15
  28. package/src/adapters/to-sqlite.ts +20 -14
  29. package/src/{entities → adapters}/types.ts +7 -8
  30. package/src/backend.ts +0 -7
  31. package/src/branch.ts +29 -110
  32. package/src/drizzle/postgres-v2.ts +11 -0
  33. package/src/drizzle/postgres-v3.ts +11 -0
  34. package/src/drizzle/sqlite-v2.ts +10 -0
  35. package/src/drizzle/sqlite-v3.ts +11 -0
  36. package/src/entities/index.ts +2 -30
  37. package/src/entities/v2/index.ts +20 -0
  38. package/src/entities/v2/session-events.ts +11 -0
  39. package/src/entities/v3/events.ts +27 -0
  40. package/src/entities/v3/index.ts +27 -0
  41. package/src/entities/v3/persistence-state.ts +10 -0
  42. package/src/entities/v3/schema-meta.ts +9 -0
  43. package/src/entities/v3/session-events.ts +26 -0
  44. package/src/entities/v3/sessions.ts +20 -0
  45. package/src/import.ts +65 -57
  46. package/src/index.ts +868 -223
  47. package/src/invariant.ts +0 -2
  48. package/src/legacy.ts +67 -0
  49. package/src/log.ts +41 -81
  50. package/src/postgres.ts +75 -93
  51. package/src/schema.ts +3 -14
  52. package/src/sqlite.ts +59 -46
  53. package/src/testing/contract.ts +460 -375
  54. package/src/testing/coordinator-contract.ts +108 -1374
  55. package/src/testing.ts +1 -6
  56. package/dist/index-uUvn6gYp.d.mts +0 -111
  57. package/dist/schema-BfPVmr1X.mjs +0 -875
  58. package/dist/sqlite-BUWnS0so.mjs +0 -356
  59. package/src/adapters/ddl.ts +0 -77
  60. package/src/entities/events.ts +0 -27
  61. package/src/entities/persistence-state.ts +0 -10
  62. package/src/entities/schema-meta.ts +0 -9
  63. package/src/entities/session-events.ts +0 -29
  64. package/src/entities/sessions.ts +0 -20
  65. package/src/migrate.ts +0 -137
package/src/sqlite.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { statSync } from "node:fs";
2
+ import { readdirSync, statSync } from "node:fs";
3
3
  import { mkdir, open } from "node:fs/promises";
4
4
  import { dirname, resolve } from "node:path";
5
5
  import { DatabaseSync } from "node:sqlite";
6
- import { and, desc, eq, gte, sql } from "drizzle-orm";
6
+ import { and, eq, gte, sql } from "drizzle-orm";
7
7
  import { drizzle, type NodeSQLiteDatabase } from "drizzle-orm/node-sqlite";
8
+ import { migrate } from "drizzle-orm/node-sqlite/migrator";
8
9
  import type { SessionId } from "@deepseek-ai/dsh-session";
9
10
  import type { SessionStorageMetadata } from "@deepseek-ai/dsh-session-persistence";
10
11
  import {
@@ -14,10 +15,7 @@ import {
14
15
  type EventRow,
15
16
  type SessionRow,
16
17
  } from "./backend.ts";
17
- import { createTablesSql } from "./adapters/index.ts";
18
- import { sqliteTableDefs } from "./entities/index.ts";
19
18
  import { sessionConflictRow, sessionInsertRow } from "./log.ts";
20
- import { migrateSqliteV1ToV2 } from "./migrate.ts";
21
19
  import {
22
20
  DEFAULT_BUSY_TIMEOUT_MS,
23
21
  SCHEMA_VERSION,
@@ -31,6 +29,9 @@ import {
31
29
 
32
30
  type SqliteDb = NodeSQLiteDatabase & { $client: DatabaseSync };
33
31
 
32
+ /** drizzle-kit 生成的迁移目录(随包根 drizzle/ 发布;src/dist 形态经相对 URL 统一解析)。 */
33
+ const sqliteMigrationsDir = new URL("../drizzle/sqlite/", import.meta.url).pathname;
34
+
34
35
  const sqliteTxQueues = new Map<string, Promise<void>>();
35
36
 
36
37
  function enqueueSqliteTx<T>(path: string, fn: () => Promise<T>): Promise<T> {
@@ -64,6 +65,28 @@ export function openDatabase(
64
65
  const db = new DatabaseSync(path);
65
66
  try {
66
67
  configureDatabase(db, path, journalMode, busyTimeout);
68
+ const dbx = drizzle({ client: db });
69
+ const { user_version: onDisk } = db.prepare("PRAGMA user_version").get() as {
70
+ user_version: number;
71
+ };
72
+ // v2 库(旧版本建表)首次打开:建迁移表并标记 v2 baseline 已应用,
73
+ // 使 migrate 只执行 v3 diff(删 f_original_seq、建 t_schema_meta)。
74
+ if (onDisk === 2 && !hasMigrationsTable(db)) {
75
+ baselineV2(db);
76
+ }
77
+ migrate(dbx, { migrationsFolder: sqliteMigrationsDir });
78
+ // store 身份单例:新库由 migrate 建表后插入;v2/v3 库已有行(no-op)。
79
+ dbx
80
+ .insert(tPersistenceState)
81
+ .values({ fSingleton: 1, fStoreId: randomUUID() })
82
+ .onConflictDoNothing()
83
+ .run();
84
+ if (onDisk === 0 || onDisk === 2) {
85
+ db.exec(`PRAGMA user_version = ${SCHEMA_VERSION}`);
86
+ }
87
+ // journal_mode 不可绑定,经校验后的联合可直接插值;在归属校验、迁移
88
+ // 与身份写入全部成功之后应用(失败时保持磁盘原状)。
89
+ db.exec(`PRAGMA journal_mode = ${journalMode.toUpperCase()}`);
67
90
  return db;
68
91
  } catch (error: unknown) {
69
92
  db.close();
@@ -71,6 +94,32 @@ export function openDatabase(
71
94
  }
72
95
  }
73
96
 
97
+ function hasMigrationsTable(db: DatabaseSync): boolean {
98
+ return (
99
+ db
100
+ .prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '__drizzle_migrations'")
101
+ .get() !== undefined
102
+ );
103
+ }
104
+
105
+ /** 标记 v2 baseline 已应用(迁移表 v1 结构:id/hash/created_at/name/applied_at)。 */
106
+ function baselineV2(db: DatabaseSync): void {
107
+ const dir = readdirSync(sqliteMigrationsDir).find((name) => name.endsWith("_v2_initial"));
108
+ if (dir === undefined) throw new Error("missing v2 baseline migration in drizzle/sqlite");
109
+ db.exec(`
110
+ CREATE TABLE __drizzle_migrations (
111
+ id INTEGER PRIMARY KEY,
112
+ hash text NOT NULL,
113
+ created_at numeric,
114
+ name text,
115
+ applied_at TEXT
116
+ )
117
+ `);
118
+ db.prepare(
119
+ "INSERT INTO __drizzle_migrations (hash, created_at, name, applied_at) VALUES (?, ?, ?, ?)",
120
+ ).run("baseline", 0, dir, new Date().toISOString());
121
+ }
122
+
74
123
  function configureDatabase(
75
124
  db: DatabaseSync,
76
125
  path: string,
@@ -99,29 +148,16 @@ function configureDatabase(
99
148
  `session database at "${path}" has an unversioned schema or application identity`,
100
149
  );
101
150
  }
102
- if (onDisk === 1) {
103
- // 启动时自动迁移 v1 → v2(同一写锁事务内,失败整体回滚)。
104
- migrateSqliteV1ToV2(db);
105
- } else if (onDisk !== 0 && onDisk !== SCHEMA_VERSION) {
151
+ if (onDisk !== 0 && onDisk !== 2 && onDisk !== SCHEMA_VERSION) {
106
152
  throw new Error(
107
153
  `session database at "${path}" has schema version ${onDisk}, incompatible with this build (${SCHEMA_VERSION})`,
108
154
  );
109
155
  }
110
- if (
111
- (onDisk === SCHEMA_VERSION || onDisk === 1) &&
112
- applicationId !== SESSION_PERSISTENCE_SQLITE_APPLICATION_ID
113
- ) {
156
+ if (onDisk >= 1 && applicationId !== SESSION_PERSISTENCE_SQLITE_APPLICATION_ID) {
114
157
  throw new Error(
115
158
  `session database at "${path}" has application id ${applicationId}, expected ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`,
116
159
  );
117
160
  }
118
- for (const statement of createTablesSql("sqlite", sqliteTableDefs)) {
119
- tx.run(sql.raw(statement));
120
- }
121
- tx.insert(tPersistenceState)
122
- .values({ fSingleton: 1, fStoreId: randomUUID() })
123
- .onConflictDoNothing()
124
- .run();
125
161
  if (onDisk === 0) {
126
162
  tx.run(sql.raw(`PRAGMA application_id = ${SESSION_PERSISTENCE_SQLITE_APPLICATION_ID}`));
127
163
  tx.run(sql.raw(`PRAGMA user_version = ${SCHEMA_VERSION}`));
@@ -129,9 +165,6 @@ function configureDatabase(
129
165
  },
130
166
  { behavior: "immediate" },
131
167
  );
132
- // journal_mode 不可绑定,经校验后的联合可直接插值;在归属校验与初始化
133
- // 提交之后应用。
134
- db.exec(`PRAGMA journal_mode = ${journalMode.toUpperCase()}`);
135
168
  }
136
169
 
137
170
  export interface SqliteBackendOptions {
@@ -164,6 +197,9 @@ export class SqliteBackend implements Backend {
164
197
  this.db = drizzle({
165
198
  client: openDatabase(actual, this.options.journalMode, this.options.busyTimeout),
166
199
  });
200
+ // drizzle-kit 迁移:v2 baseline 由旧版本建表(首次打开时标记为已应用),
201
+ // 本版本只执行 v3 diff(删 f_original_seq、建 t_schema_meta)。
202
+ migrate(this.db, { migrationsFolder: sqliteMigrationsDir });
167
203
  });
168
204
  try {
169
205
  const row = this.db
@@ -203,14 +239,6 @@ export class SqliteBackend implements Backend {
203
239
  | undefined;
204
240
  }
205
241
 
206
- async getSeqMapRows(id: SessionId): Promise<Array<{ fSequence: number; fOriginalSeq: number }>> {
207
- return this.db
208
- .select({ fSequence: tSessionEvents.fSequence, fOriginalSeq: tSessionEvents.fOriginalSeq })
209
- .from(tSessionEvents)
210
- .where(eq(tSessionEvents.fSessionId, id))
211
- .all();
212
- }
213
-
214
242
  async getEventRows(id: SessionId, fromSequence?: number): Promise<EventRow[]> {
215
243
  const scoped =
216
244
  fromSequence === undefined
@@ -263,7 +291,6 @@ export class SqliteBackend implements Backend {
263
291
  bumpRevision: (id) => this.bumpRevision(id),
264
292
  deleteBridgeTail: (id, fromSequence) => this.deleteBridgeTail(id, fromSequence),
265
293
  getPrevBridge: (id, sequence) => this.getPrevBridge(id, sequence),
266
- getLastBridge: (id) => this.getLastBridge(id),
267
294
  };
268
295
 
269
296
  // --- row primitives (transaction-internal or standalone) ---
@@ -328,7 +355,6 @@ export class SqliteBackend implements Backend {
328
355
  fSessionId: SessionId;
329
356
  fEventId: string;
330
357
  fSequence: number;
331
- fOriginalSeq: number;
332
358
  fSurfaceOp: string | null;
333
359
  }>,
334
360
  ): Promise<void> {
@@ -379,24 +405,11 @@ export class SqliteBackend implements Backend {
379
405
  .get() as { fEventId: string; fSequence: number } | undefined;
380
406
  }
381
407
 
382
- private async getLastBridge(
383
- id: SessionId,
384
- ): Promise<{ fEventId: string; fSequence: number } | undefined> {
385
- return this.db
386
- .select({ fEventId: tSessionEvents.fEventId, fSequence: tSessionEvents.fSequence })
387
- .from(tSessionEvents)
388
- .where(eq(tSessionEvents.fSessionId, id))
389
- .orderBy(desc(tSessionEvents.fSequence))
390
- .limit(1)
391
- .get() as { fEventId: string; fSequence: number } | undefined;
392
- }
393
-
394
408
  private eventRows() {
395
409
  return this.db
396
410
  .select({
397
411
  fEventId: tSessionEvents.fEventId,
398
412
  fSequence: tSessionEvents.fSequence,
399
- fOriginalSeq: tSessionEvents.fOriginalSeq,
400
413
  fType: tEvents.fType,
401
414
  fKind: tEvents.fKind,
402
415
  fRole: tEvents.fRole,