@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.
- package/README.md +11 -13
- package/dist/artifact.d.mts +6 -16
- package/dist/artifact.mjs +3 -3
- package/dist/{import-B-tf5vQn.mjs → import-CKrzjXVB.mjs} +73 -83
- package/dist/import.d.mts +2 -3
- package/dist/import.mjs +2 -2
- package/dist/index-CgGDHQSK.d.mts +225 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +694 -169
- package/dist/log-DTJsxMud.mjs +314 -0
- package/dist/{schema-CFXZURX7.d.mts → schema-COK7-wRV.d.mts} +3 -15
- package/dist/sqlite-DCy4VTD8.mjs +698 -0
- package/dist/storage.d.mts +2 -7
- package/dist/storage.mjs +2 -3
- package/dist/testing.d.mts +30 -1
- package/dist/testing.mjs +676 -1991
- package/drizzle/postgres/20260908072805_v2_initial/migration.sql +58 -0
- package/drizzle/postgres/20260908072805_v2_initial/snapshot.json +686 -0
- package/drizzle/postgres/20260908072806_v3_drop_original_seq/migration.sql +1 -0
- package/drizzle/postgres/20260908072806_v3_drop_original_seq/snapshot.json +673 -0
- package/drizzle/sqlite/20260908072751_v2_initial/migration.sql +53 -0
- package/drizzle/sqlite/20260908072751_v2_initial/snapshot.json +492 -0
- package/drizzle/sqlite/20260908072752_v3_drop_original_seq/migration.sql +6 -0
- package/drizzle/sqlite/20260908072752_v3_drop_original_seq/snapshot.json +513 -0
- package/package.json +17 -12
- package/src/adapters/index.ts +10 -2
- package/src/adapters/to-postgres.ts +19 -15
- package/src/adapters/to-sqlite.ts +20 -14
- package/src/{entities → adapters}/types.ts +7 -8
- package/src/backend.ts +0 -7
- package/src/branch.ts +29 -110
- package/src/drizzle/postgres-v2.ts +11 -0
- package/src/drizzle/postgres-v3.ts +11 -0
- package/src/drizzle/sqlite-v2.ts +10 -0
- package/src/drizzle/sqlite-v3.ts +11 -0
- package/src/entities/index.ts +2 -30
- package/src/entities/v2/index.ts +20 -0
- package/src/entities/v2/session-events.ts +11 -0
- package/src/entities/v3/events.ts +27 -0
- package/src/entities/v3/index.ts +27 -0
- package/src/entities/v3/persistence-state.ts +10 -0
- package/src/entities/v3/schema-meta.ts +9 -0
- package/src/entities/v3/session-events.ts +26 -0
- package/src/entities/v3/sessions.ts +20 -0
- package/src/import.ts +65 -57
- package/src/index.ts +868 -223
- package/src/invariant.ts +0 -2
- package/src/legacy.ts +67 -0
- package/src/log.ts +41 -81
- package/src/postgres.ts +75 -93
- package/src/schema.ts +3 -14
- package/src/sqlite.ts +59 -46
- package/src/testing/contract.ts +460 -375
- package/src/testing/coordinator-contract.ts +108 -1374
- package/src/testing.ts +1 -6
- package/dist/index-uUvn6gYp.d.mts +0 -111
- package/dist/schema-BfPVmr1X.mjs +0 -875
- package/dist/sqlite-BUWnS0so.mjs +0 -356
- package/src/adapters/ddl.ts +0 -77
- package/src/entities/events.ts +0 -27
- package/src/entities/persistence-state.ts +0 -10
- package/src/entities/schema-meta.ts +0 -9
- package/src/entities/session-events.ts +0 -29
- package/src/entities/sessions.ts +0 -20
- 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,
|
|
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
|
|
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,
|