@whispering233/ai-editor-db 0.0.1

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 (73) hide show
  1. package/dist/connection.d.ts +24 -0
  2. package/dist/connection.d.ts.map +1 -0
  3. package/dist/connection.js +39 -0
  4. package/dist/connection.js.map +1 -0
  5. package/dist/index.d.ts +15 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +17 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/migrations/index.d.ts +10 -0
  10. package/dist/migrations/index.d.ts.map +1 -0
  11. package/dist/migrations/index.js +34 -0
  12. package/dist/migrations/index.js.map +1 -0
  13. package/dist/queries/atomic.d.ts +15 -0
  14. package/dist/queries/atomic.d.ts.map +1 -0
  15. package/dist/queries/atomic.js +44 -0
  16. package/dist/queries/atomic.js.map +1 -0
  17. package/dist/queries/chat.d.ts +74 -0
  18. package/dist/queries/chat.d.ts.map +1 -0
  19. package/dist/queries/chat.js +188 -0
  20. package/dist/queries/chat.js.map +1 -0
  21. package/dist/queries/compute-state.d.ts +32 -0
  22. package/dist/queries/compute-state.d.ts.map +1 -0
  23. package/dist/queries/compute-state.js +135 -0
  24. package/dist/queries/compute-state.js.map +1 -0
  25. package/dist/queries/delta.d.ts +76 -0
  26. package/dist/queries/delta.d.ts.map +1 -0
  27. package/dist/queries/delta.js +287 -0
  28. package/dist/queries/delta.js.map +1 -0
  29. package/dist/queries/entity.d.ts +110 -0
  30. package/dist/queries/entity.d.ts.map +1 -0
  31. package/dist/queries/entity.js +285 -0
  32. package/dist/queries/entity.js.map +1 -0
  33. package/dist/queries/migration.d.ts +153 -0
  34. package/dist/queries/migration.d.ts.map +1 -0
  35. package/dist/queries/migration.js +260 -0
  36. package/dist/queries/migration.js.map +1 -0
  37. package/dist/queries/outline-ops.d.ts +126 -0
  38. package/dist/queries/outline-ops.d.ts.map +1 -0
  39. package/dist/queries/outline-ops.js +346 -0
  40. package/dist/queries/outline-ops.js.map +1 -0
  41. package/dist/queries/outline.d.ts +48 -0
  42. package/dist/queries/outline.d.ts.map +1 -0
  43. package/dist/queries/outline.js +134 -0
  44. package/dist/queries/outline.js.map +1 -0
  45. package/dist/queries/project.d.ts +21 -0
  46. package/dist/queries/project.d.ts.map +1 -0
  47. package/dist/queries/project.js +43 -0
  48. package/dist/queries/project.js.map +1 -0
  49. package/dist/queries/relation.d.ts +83 -0
  50. package/dist/queries/relation.d.ts.map +1 -0
  51. package/dist/queries/relation.js +418 -0
  52. package/dist/queries/relation.js.map +1 -0
  53. package/dist/queries/trash.d.ts +65 -0
  54. package/dist/queries/trash.d.ts.map +1 -0
  55. package/dist/queries/trash.js +121 -0
  56. package/dist/queries/trash.js.map +1 -0
  57. package/dist/schema.d.ts +29 -0
  58. package/dist/schema.d.ts.map +1 -0
  59. package/dist/schema.js +102 -0
  60. package/dist/schema.js.map +1 -0
  61. package/dist/storage/atomic.d.ts +32 -0
  62. package/dist/storage/atomic.d.ts.map +1 -0
  63. package/dist/storage/atomic.js +112 -0
  64. package/dist/storage/atomic.js.map +1 -0
  65. package/dist/storage/outline.d.ts +68 -0
  66. package/dist/storage/outline.d.ts.map +1 -0
  67. package/dist/storage/outline.js +154 -0
  68. package/dist/storage/outline.js.map +1 -0
  69. package/dist/storage/project.d.ts +24 -0
  70. package/dist/storage/project.d.ts.map +1 -0
  71. package/dist/storage/project.js +55 -0
  72. package/dist/storage/project.js.map +1 -0
  73. package/package.json +36 -0
@@ -0,0 +1,24 @@
1
+ import Database from "better-sqlite3";
2
+ /** better-sqlite3 连接实例类型 */
3
+ export type Db = Database.Database;
4
+ /**
5
+ * 打开(或创建)data.db 并初始化:
6
+ * 1. better-sqlite3 打开指定路径(文件不存在则创建)
7
+ * 2. WAL 模式(journal_mode 为数据库持久属性,重复设置幂等)
8
+ * 3. synchronous = FULL:每个事务提交后 WAL 落盘(fsync),写入即时持久化
9
+ * 4. 自动建表(CREATE TABLE IF NOT EXISTS,表不存在时创建)
10
+ */
11
+ export declare function openDatabase(path: string): Db;
12
+ /** 关闭数据库连接(同步 API,close 后该连接不可再用;幂等:已关闭则直接返回) */
13
+ export declare function closeDatabase(db: Db): void;
14
+ /**
15
+ * 事务辅助:fn 正常返回则提交并返回其结果;fn 内任一语句抛错则整体回滚、异常向上传播。
16
+ *
17
+ * 基于 better-sqlite3 的 db.transaction(内部自动处理 BEGIN/COMMIT/ROLLBACK,
18
+ * 嵌套调用自动升级为 SAVEPOINT)。
19
+ *
20
+ * 注意:better-sqlite3 事务是同步语义——fn 必须是同步函数;若在异步回调内抛错,
21
+ * 事务已在同步流程结束后提交,错误无法触发回滚。
22
+ */
23
+ export declare function withTransaction<T>(db: Db, fn: () => T): T;
24
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAKA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAGtC,4BAA4B;AAC5B,MAAM,MAAM,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC;AAEnC;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAM7C;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,EAAE,EAAE,EAAE,GAAG,IAAI,CAI1C;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEzD"}
@@ -0,0 +1,39 @@
1
+ // @whispering233/ai-editor-db 连接与事务管理(T2.1)
2
+ //
3
+ // better-sqlite3 为同步 API(架构选型:内嵌零配置、同步简单可靠,见 doc/design/architecture.md 第 11 行)。
4
+ // 持久化语义(doc/api/data-flow.md 第 51 行):WAL 模式 + synchronous=FULL,写入即时落盘。
5
+ import Database from "better-sqlite3";
6
+ import { createTables } from "./schema.js";
7
+ /**
8
+ * 打开(或创建)data.db 并初始化:
9
+ * 1. better-sqlite3 打开指定路径(文件不存在则创建)
10
+ * 2. WAL 模式(journal_mode 为数据库持久属性,重复设置幂等)
11
+ * 3. synchronous = FULL:每个事务提交后 WAL 落盘(fsync),写入即时持久化
12
+ * 4. 自动建表(CREATE TABLE IF NOT EXISTS,表不存在时创建)
13
+ */
14
+ export function openDatabase(path) {
15
+ const db = new Database(path);
16
+ db.pragma("journal_mode = WAL");
17
+ db.pragma("synchronous = FULL");
18
+ createTables(db);
19
+ return db;
20
+ }
21
+ /** 关闭数据库连接(同步 API,close 后该连接不可再用;幂等:已关闭则直接返回) */
22
+ export function closeDatabase(db) {
23
+ if (db.open) {
24
+ db.close();
25
+ }
26
+ }
27
+ /**
28
+ * 事务辅助:fn 正常返回则提交并返回其结果;fn 内任一语句抛错则整体回滚、异常向上传播。
29
+ *
30
+ * 基于 better-sqlite3 的 db.transaction(内部自动处理 BEGIN/COMMIT/ROLLBACK,
31
+ * 嵌套调用自动升级为 SAVEPOINT)。
32
+ *
33
+ * 注意:better-sqlite3 事务是同步语义——fn 必须是同步函数;若在异步回调内抛错,
34
+ * 事务已在同步流程结束后提交,错误无法触发回滚。
35
+ */
36
+ export function withTransaction(db, fn) {
37
+ return db.transaction(fn)();
38
+ }
39
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,EAAE;AACF,iFAAiF;AACjF,uEAAuE;AAEvE,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAK3C;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC9B,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,CAAC,CAAC;IACjB,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,aAAa,CAAC,EAAM;IAClC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QACZ,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAI,EAAM,EAAE,EAAW;IACpD,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,15 @@
1
+ export * from "./schema.js";
2
+ export * from "./connection.js";
3
+ export * from "./storage/atomic.js";
4
+ export * from "./storage/outline.js";
5
+ export * from "./storage/project.js";
6
+ export * from "./queries/chat.js";
7
+ export * from "./queries/migration.js";
8
+ export * from "./migrations/index.js";
9
+ export * from "./queries/outline-ops.js";
10
+ export * from "./queries/entity.js";
11
+ export * from "./queries/relation.js";
12
+ export * from "./queries/delta.js";
13
+ export * from "./queries/compute-state.js";
14
+ export * from "./queries/trash.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ // @whispering233/ai-editor-db 入口:导出建表(schema)、连接(connection)、对话历史查询(queries/chat)与
2
+ // JSON 存储(storage/:outline.json / project.json 原子读写,T2.2)、增量迁移(migrations/,E5)
3
+ export * from "./schema.js";
4
+ export * from "./connection.js";
5
+ export * from "./storage/atomic.js";
6
+ export * from "./storage/outline.js";
7
+ export * from "./storage/project.js";
8
+ export * from "./queries/chat.js";
9
+ export * from "./queries/migration.js";
10
+ export * from "./migrations/index.js";
11
+ export * from "./queries/outline-ops.js";
12
+ export * from "./queries/entity.js";
13
+ export * from "./queries/relation.js";
14
+ export * from "./queries/delta.js";
15
+ export * from "./queries/compute-state.js";
16
+ export * from "./queries/trash.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,+EAA+E;AAC/E,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Db } from "../connection.js";
2
+ /** 单条增量迁移(version = 迁移完成后 data.db 的 user_version) */
3
+ export interface Migration {
4
+ version: number;
5
+ /** 迁移逻辑(DDL/数据变更);抛错 → 该迁移事务整体回滚,版本号不变 */
6
+ up: (db: Db) => void;
7
+ }
8
+ /** 全量迁移集(按 version 升序;SCHEMA_VERSION = 1 当前为空) */
9
+ export declare const MIGRATIONS: readonly Migration[];
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAE3C,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,IAAI,CAAC;CACtB;AAED,kDAAkD;AAClD,eAAO,MAAM,UAAU,EAAE,SAAS,SAAS,EAAO,CAAC"}
@@ -0,0 +1,34 @@
1
+ // 增量迁移脚本目录(E5:release-review §一 建议动作 2)
2
+ //
3
+ // **形态**:每个迁移一个 TS 文件(001_xxx.ts → version 1、002_xxx.ts → version 2...),
4
+ // 文件内 `export default { version, up }` 形态的 Migration 对象;本目录 index.ts 按
5
+ // version 升序聚合导出 MIGRATIONS(tsc 编译进 dist 随包分发,无运行时目录读取——
6
+ // 迁移集在构建期冻结,避免「读目录顺序不稳定/漏文件」的运行时不确定性)。
7
+ //
8
+ // **version 语义**:迁移完成后 data.db 的 user_version(`setUserVersion(m.version)`)。
9
+ // 从 v=N 库升级到 v=N+1 的迁移条目 version = N+1(SCHEMA_VERSION 即目标版本)。
10
+ //
11
+ // **写法示例**(首个真实迁移(SCHEMA_VERSION 提升到 2 时)加入):
12
+ // ```ts
13
+ // import type { Db } from "../connection.js";
14
+ // import type { Migration } from "./index.js";
15
+ // export default {
16
+ // version: 2,
17
+ // up: (db: Db) => {
18
+ // // DDL/数据变更(better-sqlite3 同步 API;抛错 → 该迁移整体回滚)
19
+ // db.exec("ALTER TABLE entities ADD COLUMN note TEXT");
20
+ // },
21
+ // } satisfies Migration;
22
+ // ```
23
+ //
24
+ // **执行语义**(runMigrations,见 queries/migration.ts):
25
+ // - 按 version 升序逐个执行,每个迁移一个事务(up + setUserVersion 原子提交)
26
+ // - 整批迁移前自动快照 data.db(`data.db.v{n}.{时间戳}.bak`,不覆盖旧备份)
27
+ // - 失败 → 该迁移回滚 + 版本停在前一迁移后,下次 open 重试
28
+ // - 无迁移路径的旧版本(如 v0 且无 0→1 条目)保持删库重建兜底(决策 13)
29
+ //
30
+ // **当前状态**:SCHEMA_VERSION = 1 且无历史版本 → 数组为空(机制已就绪,
31
+ // 首个真实条目在未来的 schema 变更时加入)。
32
+ /** 全量迁移集(按 version 升序;SCHEMA_VERSION = 1 当前为空) */
33
+ export const MIGRATIONS = [];
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,yDAAyD;AACzD,uCAAuC;AACvC,EAAE;AACF,4EAA4E;AAC5E,8DAA8D;AAC9D,EAAE;AACF,8CAA8C;AAC9C,QAAQ;AACR,8CAA8C;AAC9C,+CAA+C;AAC/C,mBAAmB;AACnB,gBAAgB;AAChB,sBAAsB;AACtB,sDAAsD;AACtD,4DAA4D;AAC5D,OAAO;AACP,yBAAyB;AACzB,MAAM;AACN,EAAE;AACF,kDAAkD;AAClD,wDAAwD;AACxD,uDAAuD;AACvD,sCAAsC;AACtC,6CAA6C;AAC7C,EAAE;AACF,mDAAmD;AACnD,4BAA4B;AAW5B,kDAAkD;AAClD,MAAM,CAAC,MAAM,UAAU,GAAyB,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 原子写 JSON 文件(决策 11):
3
+ * 1. 写同目录临时文件 `{filePath}.tmp`(writeSync 到已打开的 fd)
4
+ * 2. fsync 数据落盘(保证 rename 后文件内容完整,崩溃不产生半写文件)
5
+ * 3. rename 覆盖目标文件(同文件系统内原子操作)
6
+ *
7
+ * 任一步抛错:原文件保持完好;临时文件可能残留(由调用方/启动清理兜底)。
8
+ */
9
+ export declare function writeJsonAtomic(filePath: string, data: unknown): void;
10
+ /**
11
+ * 读取 JSON 文件:文件不存在返回 null,存在则 JSON.parse 并返回(parse 失败抛错,不静默吞)。
12
+ * 用于 loadOutlineFile / loadProjectFile 的统一入口。
13
+ */
14
+ export declare function readJsonFile<T>(filePath: string): T | null;
15
+ //# sourceMappingURL=atomic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic.d.ts","sourceRoot":"","sources":["../../src/queries/atomic.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAWrE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAW1D"}
@@ -0,0 +1,44 @@
1
+ // @ai-editor/db JSON 文件原子读写辅助(T2.2)
2
+ // 决策 11:outline.json / project.json 保存必须「写临时文件 → fsync → rename 覆盖」,
3
+ // 保证崩溃/断电时磁盘上要么是旧文件要么是新文件;禁止直接覆盖写。
4
+ // 时间约定(schema.md 第 16 行):时间字段由应用层写入 ISO 8601,本模块只负责文件 I/O。
5
+ import { closeSync, fsyncSync, openSync, readFileSync, renameSync, writeSync } from "node:fs";
6
+ /**
7
+ * 原子写 JSON 文件(决策 11):
8
+ * 1. 写同目录临时文件 `{filePath}.tmp`(writeSync 到已打开的 fd)
9
+ * 2. fsync 数据落盘(保证 rename 后文件内容完整,崩溃不产生半写文件)
10
+ * 3. rename 覆盖目标文件(同文件系统内原子操作)
11
+ *
12
+ * 任一步抛错:原文件保持完好;临时文件可能残留(由调用方/启动清理兜底)。
13
+ */
14
+ export function writeJsonAtomic(filePath, data) {
15
+ const tmpPath = `${filePath}.tmp`;
16
+ const content = `${JSON.stringify(data, null, 2)}\n`;
17
+ const fd = openSync(tmpPath, "w");
18
+ try {
19
+ writeSync(fd, content);
20
+ fsyncSync(fd);
21
+ }
22
+ finally {
23
+ closeSync(fd);
24
+ }
25
+ renameSync(tmpPath, filePath);
26
+ }
27
+ /**
28
+ * 读取 JSON 文件:文件不存在返回 null,存在则 JSON.parse 并返回(parse 失败抛错,不静默吞)。
29
+ * 用于 loadOutlineFile / loadProjectFile 的统一入口。
30
+ */
31
+ export function readJsonFile(filePath) {
32
+ let raw;
33
+ try {
34
+ raw = readFileSync(filePath, "utf8");
35
+ }
36
+ catch (err) {
37
+ if (err.code === "ENOENT") {
38
+ return null;
39
+ }
40
+ throw err;
41
+ }
42
+ return JSON.parse(raw);
43
+ }
44
+ //# sourceMappingURL=atomic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic.js","sourceRoot":"","sources":["../../src/queries/atomic.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,qEAAqE;AACrE,mCAAmC;AACnC,2DAA2D;AAE3D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAE9F;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,IAAa;IAC7D,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;IAClC,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IACrD,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QACvB,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;IACD,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAI,QAAgB;IAC9C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { type ChatMessage, type ChatMessageRow, type ChatRole, type ChatSessionSummary } from "@whispering233/ai-editor-shared";
2
+ import type { Db } from "../connection.js";
3
+ /** 会话列表 lastMessage 截断长度(endpoints.md 仅要求「截断」,长度为本实现约定,未入文档契约) */
4
+ export declare const SESSION_LAST_MESSAGE_MAX_LEN = 50;
5
+ /**
6
+ * 插入一条对话消息。
7
+ *
8
+ * id 生成约定:chat_messages 表 id 无文档前缀(endpoints.md id 约定仅覆盖
9
+ * char-/set-/loc-/hook-、sc-/ch-/vol-、proj- 与运行时 prop_/sess_/call_),
10
+ * 省略 id 时直接用 nanoid 生成(与消息 id 前缀空缺保持一致)。
11
+ *
12
+ * 存储形态转换:row.tool_calls 按 shared 类型契约为「解析后的数组」,
13
+ * 本函数负责 JSON.stringify 落库(列存 TEXT);created_at 必须由调用方
14
+ * 提供 ISO 8601 字符串(应用层写入,决策 18)。
15
+ */
16
+ export declare function insertChatMessage(db: Db, row: Pick<ChatMessageRow, "session_id" | "project_id" | "role" | "created_at"> & Partial<Pick<ChatMessageRow, "id" | "content" | "tool_calls" | "tool_call_id">>): void;
17
+ /**
18
+ * 会话列表(GET /api/v1/chat/sessions):
19
+ * - 按 project_id 隔离(决策 18 修订:会话按项目隔离)
20
+ * - 仅返回含消息的会话;updatedAt = 该会话最后一条消息的 created_at
21
+ * - 按最后活动时间(updatedAt)倒序,同时间戳按 session_id 升序保证稳定
22
+ * - lastMessage = 最后一条消息的 content 截断(无 content 时为空串)
23
+ */
24
+ export declare function listSessions(db: Db, projectId: string): ChatSessionSummary[];
25
+ /**
26
+ * 解析 chat_messages.tool_calls 的 JSON 文本列为数组(ChatMessage.toolCalls)。
27
+ * 防御性处理:NULL / 非法 JSON / 非数组一律返回 null(视为无工具调用)。
28
+ */
29
+ export declare function parseToolCalls(json: string | null): unknown[] | null;
30
+ /**
31
+ * 消息历史(GET /api/v1/chat/sessions/:id/messages):
32
+ * - 按 project_id 隔离(决策 18 修订)
33
+ * - 按 created_at 升序,同时间戳按 rowid(插入序)升序保证稳定
34
+ * - 存储形态 → API 形态:tool_calls JSON 解析、tool_call_id → toolCallId、snake_case → camelCase
35
+ */
36
+ export declare function listMessages(db: Db, sessionId: string, projectId: string): ChatMessage[];
37
+ /**
38
+ * 消息历史原始行(S7.6 续聊重建专用):
39
+ * - listMessages 输出 API 形态(camelCase,供 GET /messages);本函数输出**存储形态**
40
+ * (snake_case ChatMessageRow,tool_calls 已解析为数组)——直供 agent 包
41
+ * loadHistory/restoreSession(决策 18 成对重组),避免 server 层做 API→存储形态反映射。
42
+ * - 查询语义与 listMessages 一致:按 project_id 隔离、created_at 升序 + rowid 稳定序。
43
+ */
44
+ export declare function listMessageRows(db: Db, sessionId: string, projectId: string): ChatMessageRow[];
45
+ /** 成对重组后喂给 LLM 的历史消息形态(决策 18 修订:DeepSeek 要求 tool_call 与 tool 结果严格配对) */
46
+ export interface ReassembledChatMessage {
47
+ role: ChatRole;
48
+ content?: string | null;
49
+ /** assistant 消息的工具调用数组(仅当该组全部调用均成对成功时保留) */
50
+ toolCalls?: unknown[];
51
+ /** tool 消息关联的调用 id(与所属 assistant 消息 tool_calls[].id 配对) */
52
+ toolCallId?: string | null;
53
+ }
54
+ /**
55
+ * 成对重组历史消息(决策 18 修订:assistant.tool_calls[].id ↔ tool.tool_call_id 成对重组喂回模型)。
56
+ *
57
+ * 用途:服务重启后凭 session_id 重建「继续上次对话」上下文,或会话级滑动窗口裁剪前的重组。
58
+ * 注意:本函数是纯函数,输入为 ChatMessageRow(tool_calls 已按 shared 类型契约为解析后的数组,
59
+ * 由调用方先经 parseToolCalls / 直接构造)。
60
+ *
61
+ * 算法:
62
+ * 1. 按 created_at 升序稳定排序(同时间戳保持输入顺序)
63
+ * 2. 第一遍收集 tool 消息:tool_call_id → content(同一 id 多条结果取最先到达者,防御性处理)
64
+ * 3. 第二遍顺序扫描:
65
+ * - user 消息:原样保留
66
+ * - assistant 消息(无 tool_calls):原样保留
67
+ * - assistant 消息(有 tool_calls):逐个调用校验——任一调用缺 id 或缺对应 tool 结果
68
+ * 即判定为孤儿半对,该 assistant 消息与其工具结果**整组丢弃**(决策 18 修订:
69
+ * 孤儿半对整对丢弃,不返回 409 也不部分保留);全部配对成功则输出 assistant 消息,
70
+ * 工具结果按 tool_calls 数组顺序紧随其后(tool 消息保留 toolCallId 供模型配对)
71
+ * - tool 消息:不单独输出——未被任何保留的 assistant 消息引用的即孤儿,整对丢弃
72
+ */
73
+ export declare function reassembleMessages(rows: ChatMessageRow[]): ReassembledChatMessage[];
74
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/queries/chat.ts"],"names":[],"mappings":"AASA,OAAO,EAAY,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAC1I,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAE3C,kEAAkE;AAClE,eAAO,MAAM,4BAA4B,KAAK,CAAC;AAE/C;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,EAAE,EACN,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,GAAG,YAAY,GAAG,MAAM,GAAG,YAAY,CAAC,GAC5E,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,SAAS,GAAG,YAAY,GAAG,cAAc,CAAC,CAAC,GAChF,IAAI,CAcN;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAqC5E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,EAAE,GAAG,IAAI,CASpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,EAAE,CA6BxF;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE,CAsB9F;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC;IACtB,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,sBAAsB,EAAE,CAwCnF"}
@@ -0,0 +1,188 @@
1
+ // @whispering233/ai-editor-db 对话历史数据层查询(T2.3)
2
+ //
3
+ // 单一事实来源:doc/database/schema.md(chat_messages 表结构,决策 18)、
4
+ // doc/api/endpoints.md(chat/sessions 会话列表、chat/sessions/:id/messages 消息历史)。
5
+ // 时间约定(schema.md 第 16 行):created_at 统一 ISO 8601 字符串、由应用层写入,本模块不生成时间。
6
+ // 注意:本模块只处理 chat_messages 表,不涉及会话元数据——会话列表信息(createdAt/updatedAt/
7
+ // messageCount/lastMessage)全部由消息行实时聚合得出,无独立会话表。
8
+ import { nanoid } from "nanoid";
9
+ import { truncate } from "@whispering233/ai-editor-shared";
10
+ /** 会话列表 lastMessage 截断长度(endpoints.md 仅要求「截断」,长度为本实现约定,未入文档契约) */
11
+ export const SESSION_LAST_MESSAGE_MAX_LEN = 50;
12
+ /**
13
+ * 插入一条对话消息。
14
+ *
15
+ * id 生成约定:chat_messages 表 id 无文档前缀(endpoints.md id 约定仅覆盖
16
+ * char-/set-/loc-/hook-、sc-/ch-/vol-、proj- 与运行时 prop_/sess_/call_),
17
+ * 省略 id 时直接用 nanoid 生成(与消息 id 前缀空缺保持一致)。
18
+ *
19
+ * 存储形态转换:row.tool_calls 按 shared 类型契约为「解析后的数组」,
20
+ * 本函数负责 JSON.stringify 落库(列存 TEXT);created_at 必须由调用方
21
+ * 提供 ISO 8601 字符串(应用层写入,决策 18)。
22
+ */
23
+ export function insertChatMessage(db, row) {
24
+ db.prepare(`INSERT INTO chat_messages (id, session_id, project_id, role, content, tool_calls, tool_call_id, created_at)
25
+ VALUES (@id, @session_id, @project_id, @role, @content, @tool_calls, @tool_call_id, @created_at)`).run({
26
+ id: row.id ?? nanoid(),
27
+ session_id: row.session_id,
28
+ project_id: row.project_id,
29
+ role: row.role,
30
+ content: row.content ?? null,
31
+ tool_calls: row.tool_calls == null ? null : JSON.stringify(row.tool_calls),
32
+ tool_call_id: row.tool_call_id ?? null,
33
+ created_at: row.created_at,
34
+ });
35
+ }
36
+ /**
37
+ * 会话列表(GET /api/v1/chat/sessions):
38
+ * - 按 project_id 隔离(决策 18 修订:会话按项目隔离)
39
+ * - 仅返回含消息的会话;updatedAt = 该会话最后一条消息的 created_at
40
+ * - 按最后活动时间(updatedAt)倒序,同时间戳按 session_id 升序保证稳定
41
+ * - lastMessage = 最后一条消息的 content 截断(无 content 时为空串)
42
+ */
43
+ export function listSessions(db, projectId) {
44
+ const rows = db
45
+ .prepare(`SELECT
46
+ t.session_id AS id,
47
+ t.message_count,
48
+ t.created_at,
49
+ t.updated_at,
50
+ (SELECT lm.content FROM chat_messages lm
51
+ WHERE lm.session_id = t.session_id AND lm.project_id = ?
52
+ ORDER BY lm.created_at DESC, lm.rowid DESC LIMIT 1) AS last_content
53
+ FROM (
54
+ SELECT session_id,
55
+ COUNT(*) AS message_count,
56
+ MIN(created_at) AS created_at,
57
+ MAX(created_at) AS updated_at
58
+ FROM chat_messages
59
+ WHERE project_id = ?
60
+ GROUP BY session_id
61
+ ) t
62
+ ORDER BY t.updated_at DESC, t.session_id ASC`)
63
+ .all(projectId, projectId);
64
+ return rows.map((r) => ({
65
+ id: r.id,
66
+ lastMessage: truncate(r.last_content ?? "", SESSION_LAST_MESSAGE_MAX_LEN),
67
+ messageCount: r.message_count,
68
+ createdAt: r.created_at,
69
+ updatedAt: r.updated_at,
70
+ }));
71
+ }
72
+ /**
73
+ * 解析 chat_messages.tool_calls 的 JSON 文本列为数组(ChatMessage.toolCalls)。
74
+ * 防御性处理:NULL / 非法 JSON / 非数组一律返回 null(视为无工具调用)。
75
+ */
76
+ export function parseToolCalls(json) {
77
+ if (json === null)
78
+ return null;
79
+ try {
80
+ const parsed = JSON.parse(json);
81
+ return Array.isArray(parsed) ? parsed : null;
82
+ }
83
+ catch {
84
+ // 非法 JSON:按无工具调用处理,不向上抛(查询层不阻断会话恢复)
85
+ return null;
86
+ }
87
+ }
88
+ /**
89
+ * 消息历史(GET /api/v1/chat/sessions/:id/messages):
90
+ * - 按 project_id 隔离(决策 18 修订)
91
+ * - 按 created_at 升序,同时间戳按 rowid(插入序)升序保证稳定
92
+ * - 存储形态 → API 形态:tool_calls JSON 解析、tool_call_id → toolCallId、snake_case → camelCase
93
+ */
94
+ export function listMessages(db, sessionId, projectId) {
95
+ const rows = db
96
+ .prepare(`SELECT id, session_id, project_id, role, content, tool_calls, tool_call_id, created_at
97
+ FROM chat_messages
98
+ WHERE session_id = ? AND project_id = ?
99
+ ORDER BY created_at ASC, rowid ASC`)
100
+ .all(sessionId, projectId);
101
+ return rows.map((r) => ({
102
+ id: r.id,
103
+ sessionId: r.session_id,
104
+ projectId: r.project_id,
105
+ role: r.role,
106
+ content: r.content,
107
+ toolCalls: parseToolCalls(r.tool_calls) ?? undefined,
108
+ toolCallId: r.tool_call_id,
109
+ createdAt: r.created_at,
110
+ }));
111
+ }
112
+ /**
113
+ * 消息历史原始行(S7.6 续聊重建专用):
114
+ * - listMessages 输出 API 形态(camelCase,供 GET /messages);本函数输出**存储形态**
115
+ * (snake_case ChatMessageRow,tool_calls 已解析为数组)——直供 agent 包
116
+ * loadHistory/restoreSession(决策 18 成对重组),避免 server 层做 API→存储形态反映射。
117
+ * - 查询语义与 listMessages 一致:按 project_id 隔离、created_at 升序 + rowid 稳定序。
118
+ */
119
+ export function listMessageRows(db, sessionId, projectId) {
120
+ const rows = db
121
+ .prepare(`SELECT id, session_id, project_id, role, content, tool_calls, tool_call_id, created_at
122
+ FROM chat_messages
123
+ WHERE session_id = ? AND project_id = ?
124
+ ORDER BY created_at ASC, rowid ASC`)
125
+ .all(sessionId, projectId);
126
+ return rows.map((r) => ({
127
+ ...r,
128
+ tool_calls: parseToolCalls(r.tool_calls), // TEXT 列 → 解析后的数组(NULL/损坏 → null)
129
+ }));
130
+ }
131
+ /**
132
+ * 成对重组历史消息(决策 18 修订:assistant.tool_calls[].id ↔ tool.tool_call_id 成对重组喂回模型)。
133
+ *
134
+ * 用途:服务重启后凭 session_id 重建「继续上次对话」上下文,或会话级滑动窗口裁剪前的重组。
135
+ * 注意:本函数是纯函数,输入为 ChatMessageRow(tool_calls 已按 shared 类型契约为解析后的数组,
136
+ * 由调用方先经 parseToolCalls / 直接构造)。
137
+ *
138
+ * 算法:
139
+ * 1. 按 created_at 升序稳定排序(同时间戳保持输入顺序)
140
+ * 2. 第一遍收集 tool 消息:tool_call_id → content(同一 id 多条结果取最先到达者,防御性处理)
141
+ * 3. 第二遍顺序扫描:
142
+ * - user 消息:原样保留
143
+ * - assistant 消息(无 tool_calls):原样保留
144
+ * - assistant 消息(有 tool_calls):逐个调用校验——任一调用缺 id 或缺对应 tool 结果
145
+ * 即判定为孤儿半对,该 assistant 消息与其工具结果**整组丢弃**(决策 18 修订:
146
+ * 孤儿半对整对丢弃,不返回 409 也不部分保留);全部配对成功则输出 assistant 消息,
147
+ * 工具结果按 tool_calls 数组顺序紧随其后(tool 消息保留 toolCallId 供模型配对)
148
+ * - tool 消息:不单独输出——未被任何保留的 assistant 消息引用的即孤儿,整对丢弃
149
+ */
150
+ export function reassembleMessages(rows) {
151
+ // 1. 按 created_at 升序稳定排序
152
+ const sorted = [...rows].sort((a, b) => a.created_at.localeCompare(b.created_at));
153
+ // 2. 收集 tool 消息:tool_call_id → content
154
+ const toolResults = new Map();
155
+ for (const r of sorted) {
156
+ if (r.role === "tool" && r.tool_call_id !== null && !toolResults.has(r.tool_call_id)) {
157
+ toolResults.set(r.tool_call_id, r.content);
158
+ }
159
+ }
160
+ // 3. 顺序扫描重组
161
+ const out = [];
162
+ for (const r of sorted) {
163
+ if (r.role === "user") {
164
+ out.push({ role: "user", content: r.content });
165
+ continue;
166
+ }
167
+ if (r.role === "assistant") {
168
+ const calls = r.tool_calls ?? [];
169
+ if (calls.length === 0) {
170
+ out.push({ role: "assistant", content: r.content });
171
+ continue;
172
+ }
173
+ const callIds = calls.map((c) => c.id);
174
+ // 任一调用缺 id 或缺对应结果 → 整组丢弃
175
+ const allPaired = callIds.every((cid) => typeof cid === "string" && toolResults.has(cid));
176
+ if (!allPaired)
177
+ continue;
178
+ out.push({ role: "assistant", content: r.content, toolCalls: calls });
179
+ for (const cid of callIds) {
180
+ out.push({ role: "tool", toolCallId: cid, content: toolResults.get(cid) });
181
+ }
182
+ continue;
183
+ }
184
+ // role === "tool":仅作为上面对应的结果被引用;孤儿在此自然丢弃
185
+ }
186
+ return out;
187
+ }
188
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/queries/chat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import type { ComputeStateResult } from "@whispering233/ai-editor-shared";
2
+ import type { Db } from "../connection.js";
3
+ /**
4
+ * 计算实体到达指定大纲节点时的累积状态(POST /api/v1/delta/compute,endpoints.md 第 464-510 行)。
5
+ *
6
+ * **前置约定**:
7
+ * - atNodeId 必须存在——路由层先校验节点存在性并映射 404 OUTLINE_NODE_NOT_FOUND 后调用;
8
+ * getOutlinePathIds 对缺失节点抛错,视为调用方 bug,本模块不捕获。
9
+ * - 目标实体不存在(或已软删,getEntity 过滤)→ 返回 **null**,路由层映射 404 ENTITY_NOT_FOUND。
10
+ *
11
+ * 累积流程(决策 9 + schema.md 第 107 行):
12
+ * 1. state 基座 = 实体初始 data 深拷贝(structuredClone,不污染 getEntity 返回行)
13
+ * 2. 树路径 = getOutlinePathIds(tree, atNodeId)(根 → at_node,含 root 哨兵——无 delta 挂它,
14
+ * 无害);路径上每节点调 listDeltasByNode(内置可见性三态过滤与 order ASC),
15
+ * 过滤 target_id === targetId——天然满足「节点间树路径序 + 节点内 order 序」双层排序
16
+ * 与软删过滤
17
+ * 3. 逐 change 应用(applyChange,决策 9 修订四段语义);update 冲突跳过不打断后续累积
18
+ * 4. 响应:appliedDeltas 每项 { nodeId, description, changes(原样数组), skipped?(仅该 delta
19
+ * 有跳过时出现)};conflicts 为跨全部 delta 的扁平数组
20
+ *
21
+ * target_type 不参与过滤(仅回显):id 前缀体系(char-/set-/loc-/hook-/sc-/ch-/vol- 等)保证
22
+ * target_id 全局唯一,targetId 即足以定位目标;endpoints.md Req 携带 target_type 用于响应回显。
23
+ *
24
+ * @param outlineDir 项目根目录(outline.json 读取:树路径 + 节点软删校验)
25
+ * @returns ComputeStateResult;目标实体不存在/已软删返回 null
26
+ */
27
+ export declare function computeState(db: Db, outlineDir: string, input: {
28
+ targetType: string;
29
+ targetId: string;
30
+ atNodeId: string;
31
+ }): ComputeStateResult | null;
32
+ //# sourceMappingURL=compute-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-state.d.ts","sourceRoot":"","sources":["../../src/queries/compute-state.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAGV,kBAAkB,EAGnB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAyD3C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,EAAE,EACN,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAChE,kBAAkB,GAAG,IAAI,CA0C3B"}