@whispering233/ai-editor-db 0.0.33 → 0.0.38

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 (51) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/migrations/007_character_ability_panel.d.ts +5 -0
  6. package/dist/migrations/007_character_ability_panel.d.ts.map +1 -0
  7. package/dist/migrations/007_character_ability_panel.js +49 -0
  8. package/dist/migrations/007_character_ability_panel.js.map +1 -0
  9. package/dist/migrations/index.d.ts +2 -1
  10. package/dist/migrations/index.d.ts.map +1 -1
  11. package/dist/migrations/index.js +8 -3
  12. package/dist/migrations/index.js.map +1 -1
  13. package/dist/queries/backup-stats.d.ts +8 -0
  14. package/dist/queries/backup-stats.d.ts.map +1 -0
  15. package/dist/queries/backup-stats.js +53 -0
  16. package/dist/queries/backup-stats.js.map +1 -0
  17. package/dist/queries/compute-state.d.ts +10 -8
  18. package/dist/queries/compute-state.d.ts.map +1 -1
  19. package/dist/queries/compute-state.js +111 -36
  20. package/dist/queries/compute-state.js.map +1 -1
  21. package/dist/queries/delta.d.ts +15 -0
  22. package/dist/queries/delta.d.ts.map +1 -1
  23. package/dist/queries/delta.js +38 -1
  24. package/dist/queries/delta.js.map +1 -1
  25. package/dist/queries/entity.d.ts +6 -4
  26. package/dist/queries/entity.d.ts.map +1 -1
  27. package/dist/queries/entity.js +31 -24
  28. package/dist/queries/entity.js.map +1 -1
  29. package/dist/queries/field-path.d.ts +17 -0
  30. package/dist/queries/field-path.d.ts.map +1 -0
  31. package/dist/queries/field-path.js +113 -0
  32. package/dist/queries/field-path.js.map +1 -0
  33. package/dist/queries/migration.d.ts +12 -5
  34. package/dist/queries/migration.d.ts.map +1 -1
  35. package/dist/queries/migration.js +67 -6
  36. package/dist/queries/migration.js.map +1 -1
  37. package/dist/queries/relation.d.ts +2 -1
  38. package/dist/queries/relation.d.ts.map +1 -1
  39. package/dist/queries/relation.js +6 -4
  40. package/dist/queries/relation.js.map +1 -1
  41. package/dist/schema.d.ts +3 -1
  42. package/dist/schema.d.ts.map +1 -1
  43. package/dist/schema.js +3 -1
  44. package/dist/schema.js.map +1 -1
  45. package/dist/storage/atomic.d.ts +5 -1
  46. package/dist/storage/atomic.d.ts.map +1 -1
  47. package/dist/storage/atomic.js +4 -2
  48. package/dist/storage/atomic.js.map +1 -1
  49. package/dist/storage/outline.d.ts +1 -1
  50. package/dist/storage/outline.js +1 -1
  51. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export * from "./queries/migration.js";
7
7
  export * from "./migrations/index.js";
8
8
  export * from "./queries/outline-ops.js";
9
9
  export * from "./queries/entity.js";
10
+ export * from "./queries/backup-stats.js";
10
11
  export * from "./queries/relation.js";
11
12
  export * from "./queries/delta.js";
12
13
  export * from "./queries/compute-state.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC"}
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export * from "./queries/migration.js";
12
12
  export * from "./migrations/index.js";
13
13
  export * from "./queries/outline-ops.js";
14
14
  export * from "./queries/entity.js";
15
+ export * from "./queries/backup-stats.js";
15
16
  export * from "./queries/relation.js";
16
17
  export * from "./queries/delta.js";
17
18
  export * from "./queries/compute-state.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,4EAA4E;AAC5E,EAAE;AACF,sEAAsE;AACtE,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,4EAA4E;AAC5E,EAAE;AACF,sEAAsE;AACtE,gFAAgF;AAChF,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Migration } from "./index.js";
2
+ /** data 字段 JSON 迁移(旧 abilities 标签 → 能力面板「能力」分组叶子) */
3
+ declare const migration007: Migration;
4
+ export default migration007;
5
+ //# sourceMappingURL=007_character_ability_panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"007_character_ability_panel.d.ts","sourceRoot":"","sources":["../../src/migrations/007_character_ability_panel.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAS5C,qDAAqD;AACrD,QAAA,MAAM,YAAY,EAAE,SAmCnB,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { ABILITY_PANEL_DATA_KEY } from "@whispering233/ai-editor-shared";
2
+ import { nowIso } from "../storage/atomic.js";
3
+ /** 旧标签数组字段名(迁移后不再读写) */
4
+ const LEGACY_ABILITIES_KEY = "abilities";
5
+ /** 迁移产出的顶层分组名(旧标签一律归入此分组) */
6
+ const PANEL_ROOT_GROUP_NAME = "能力";
7
+ /** data 字段 JSON 迁移(旧 abilities 标签 → 能力面板「能力」分组叶子) */
8
+ const migration007 = {
9
+ version: 7,
10
+ up(db) {
11
+ const rows = db
12
+ .prepare("SELECT id, data FROM entities WHERE type = 'character'")
13
+ .all();
14
+ const update = db.prepare("UPDATE entities SET data = ?, updated_at = ? WHERE id = ?");
15
+ const stamp = nowIso();
16
+ for (const row of rows) {
17
+ let parsed;
18
+ try {
19
+ parsed = JSON.parse(row.data);
20
+ }
21
+ catch {
22
+ continue; // 坏 JSON:不动(防御)
23
+ }
24
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
25
+ continue;
26
+ const rec = parsed;
27
+ const legacy = rec[LEGACY_ABILITIES_KEY];
28
+ if (!Array.isArray(legacy))
29
+ continue; // 无旧字段(或非数组——不做宽松猜测):原样保留
30
+ if (rec[ABILITY_PANEL_DATA_KEY] !== undefined)
31
+ continue; // 已有面板:不覆盖
32
+ // 有效标签 = 字符串 → trim → 丢弃空串(旧数组里的非字符串项静默丢弃)
33
+ const labels = legacy
34
+ .filter((t) => typeof t === "string")
35
+ .map((t) => t.trim())
36
+ .filter((t) => t !== "");
37
+ delete rec[LEGACY_ABILITIES_KEY];
38
+ if (labels.length > 0) {
39
+ const panel = [
40
+ { name: PANEL_ROOT_GROUP_NAME, children: labels.map((name) => ({ name })) },
41
+ ];
42
+ rec[ABILITY_PANEL_DATA_KEY] = panel;
43
+ }
44
+ update.run(JSON.stringify(rec), stamp, row.id);
45
+ }
46
+ },
47
+ };
48
+ export default migration007;
49
+ //# sourceMappingURL=007_character_ability_panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"007_character_ability_panel.js","sourceRoot":"","sources":["../../src/migrations/007_character_ability_panel.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAGzE,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,wBAAwB;AACxB,MAAM,oBAAoB,GAAG,WAAW,CAAC;AAEzC,6BAA6B;AAC7B,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAEnC,qDAAqD;AACrD,MAAM,YAAY,GAAc;IAC9B,OAAO,EAAE,CAAC;IACV,EAAE,CAAC,EAAM;QACP,MAAM,IAAI,GAAG,EAAE;aACZ,OAAO,CAAC,wDAAwD,CAAC;aACjE,GAAG,EAAyC,CAAC;QAChD,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CAAC,2DAA2D,CAAC,CAAC;QACvF,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC;QACvB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS,CAAC,gBAAgB;YAC5B,CAAC;YACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,SAAS;YACrF,MAAM,GAAG,GAAG,MAAiC,CAAC;YAC9C,MAAM,MAAM,GAAG,GAAG,CAAC,oBAAoB,CAAC,CAAC;YACzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,SAAS,CAAC,0BAA0B;YAChE,IAAI,GAAG,CAAC,sBAAsB,CAAC,KAAK,SAAS;gBAAE,SAAS,CAAC,WAAW;YACzE,2CAA2C;YACtC,MAAM,MAAM,GAAG,MAAM;iBAClB,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;iBACjD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YAC3B,OAAO,GAAG,CAAC,oBAAoB,CAAC,CAAC;YACjC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACtB,MAAM,KAAK,GAAuB;oBAChC,EAAE,IAAI,EAAE,qBAAqB,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;iBAC5E,CAAC;gBACF,GAAG,CAAC,sBAAsB,CAAC,GAAG,KAAK,CAAC;YACtC,CAAC;YACD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;CACF,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -11,6 +11,7 @@ export interface Migration {
11
11
  up: (db: Db, ctx: MigrationContext) => void;
12
12
  }
13
13
  /** 全量迁移集(按 version 升序:002 时间轴事件、003 时间标签点实体化(G2)、
14
- * 004 设定分类字段 tags(K2 修订)、005 参考资料 reference、006 对话历史出库) */
14
+ * 004 设定分类字段 tags(K2 修订)、005 参考资料 reference、006 对话历史出库、
15
+ * 007 角色能力面板(旧 abilities 标签迁为 ability_panel)) */
15
16
  export declare const MIGRATIONS: readonly Migration[];
16
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAO3C,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAChC,0DAA0D;IACzD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC9C,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC7C;AAED;2DAC2D;AAC3D,eAAO,MAAM,UAAU,EAAE,SAAS,SAAS,EAM1C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAQ3C,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAChC,0DAA0D;IACzD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC9C,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC7C;AAED;;iDAEiD;AACjD,eAAO,MAAM,UAAU,EAAE,SAAS,SAAS,EAO1C,CAAC"}
@@ -16,23 +16,28 @@
16
16
  // - 失败 → 该迁移回滚 + 版本停在前一迁移后,下次 open 重试
17
17
  // - 无迁移路径的旧版本(如 v0 且无 0→1 条目)保持删库重建兜底
18
18
  //
19
- // **当前状态**:SCHEMA_VERSION = 6;真实迁移 002(v1→v2:entities 表 CHECK 扩为 5 种 +
19
+ // **当前状态**:SCHEMA_VERSION = 7;真实迁移 002(v1→v2:entities 表 CHECK 扩为 5 种 +
20
20
  // sort_order 列,时间轴)、003(v2→v3:entities CHECK 扩 6 种含 timepoint +
21
21
  // event.data.time_label 迁移为 timepoint 实体 + occurs_at 挂载关系,G2)、
22
- // 006(v5→v6:对话历史出库——chat_messages 导出为 sessions/<id>.jsonl 后 DROP 表)。
22
+ // 004(v3→v4:setting 旧 rules 分类值 → data.tags,仅 data JSON)、005(v4→v5:CHECK 扩
23
+ // 'reference')、006(v5→v6:对话历史出库——chat_messages 导出为 sessions/<id>.jsonl 后 DROP 表)、
24
+ // 007(v6→v7:character 旧 abilities[] → ability_panel,仅 data JSON)。
23
25
  // v0 库无 0→1 迁移条目,仍走删库重建兜底。
24
26
  import migration002 from "./002_event_timeline.js";
25
27
  import migration003 from "./003_timepoint.js";
26
28
  import migration004 from "./004_setting_tags.js";
27
29
  import migration005 from "./005_reference.js";
28
30
  import migration006 from "./006_sessions_jsonl.js";
31
+ import migration007 from "./007_character_ability_panel.js";
29
32
  /** 全量迁移集(按 version 升序:002 时间轴事件、003 时间标签点实体化(G2)、
30
- * 004 设定分类字段 tags(K2 修订)、005 参考资料 reference、006 对话历史出库) */
33
+ * 004 设定分类字段 tags(K2 修订)、005 参考资料 reference、006 对话历史出库、
34
+ * 007 角色能力面板(旧 abilities 标签迁为 ability_panel)) */
31
35
  export const MIGRATIONS = [
32
36
  migration002,
33
37
  migration003,
34
38
  migration004,
35
39
  migration005,
36
40
  migration006,
41
+ migration007,
37
42
  ];
38
43
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,yDAAyD;AACzD,uCAAuC;AACvC,EAAE;AACF,4EAA4E;AAC5E,8DAA8D;AAC9D,EAAE;AACF,8DAA8D;AAC9D,EAAE;AACF,kDAAkD;AAClD,wDAAwD;AACxD,uDAAuD;AACvD,sCAAsC;AACtC,sCAAsC;AACtC,EAAE;AACF,uEAAuE;AACvE,gEAAgE;AAChE,+DAA+D;AAC/D,qEAAqE;AACrE,2BAA2B;AAG3B,OAAO,YAAY,MAAM,yBAAyB,CAAC;AACnD,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAC9C,OAAO,YAAY,MAAM,uBAAuB,CAAC;AACjD,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAC9C,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAenD;2DAC2D;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAyB;IAC9C,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;CACb,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,EAAE;AACF,yEAAyE;AACzE,uEAAuE;AACvE,yDAAyD;AACzD,uCAAuC;AACvC,EAAE;AACF,4EAA4E;AAC5E,8DAA8D;AAC9D,EAAE;AACF,8DAA8D;AAC9D,EAAE;AACF,kDAAkD;AAClD,wDAAwD;AACxD,uDAAuD;AACvD,sCAAsC;AACtC,sCAAsC;AACtC,EAAE;AACF,uEAAuE;AACvE,gEAAgE;AAChE,+DAA+D;AAC/D,2EAA2E;AAC3E,kFAAkF;AAClF,kEAAkE;AAClE,2BAA2B;AAG3B,OAAO,YAAY,MAAM,yBAAyB,CAAC;AACnD,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAC9C,OAAO,YAAY,MAAM,uBAAuB,CAAC;AACjD,OAAO,YAAY,MAAM,oBAAoB,CAAC;AAC9C,OAAO,YAAY,MAAM,yBAAyB,CAAC;AACnD,OAAO,YAAY,MAAM,kCAAkC,CAAC;AAe5D;;iDAEiD;AACjD,MAAM,CAAC,MAAM,UAAU,GAAyB;IAC9C,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,YAAY;CACb,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { BackupStats } from "@whispering233/ai-editor-shared";
2
+ import type { Db } from "../connection.js";
3
+ /**
4
+ * 备份命名用的规模快照(未软删口径):data.db 的实体计数 + 项目目录 outline.json 的章计数。
5
+ * outline.json 损坏时 `readOutlineFile` 抛错(与备份管道一致:不静默产出半成品备份)。
6
+ */
7
+ export declare function getBackupStats(db: Db, dir: string): BackupStats;
8
+ //# sourceMappingURL=backup-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-stats.d.ts","sourceRoot":"","sources":["../../src/queries/backup-stats.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,iCAAiC,CAAC;AAEpF,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AA8B3C;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,WAAW,CAM/D"}
@@ -0,0 +1,53 @@
1
+ // 备份命名统计段(人物 / 设定 / 章)——未软删口径
2
+ //
3
+ // 用途:writeBackup 生成文件名时顺便算出「这份备份里有多少东西」,让用户在文件管理器 /
4
+ // 云盘网页里一眼看出备份内容的规模(契约见 `docs/api/20-api-backup.md`「设备与统计」)。
5
+ //
6
+ // 口径(与 `docs/db/schema.md`、`docs/api/20-api-backup.md` 逐字一致):
7
+ // - 人物 / 设定 = entities 表 type 匹配且 `deleted_at IS NULL` 的行数(**不含回收站**)
8
+ // - 章 = outline.json 中 `type === "chapter"` 且未软删的节点数(严格三层:章挂卷下或直挂 root,场景不计数)
9
+ //
10
+ // 调用时机:备份打包之前(同一连接、同一时刻视角);统计描述的是**备份内容**,
11
+ // 因此重命名备份时一律沿用原文件名里的统计段,不得用当前项目状态重算
12
+ // (见 server `renameBackup` 与 shared `formatBackupFileName` 注释)。
13
+ import { and, count, eq, isNull } from "drizzle-orm";
14
+ import { queryDb } from "../query-db.js";
15
+ import { readOutlineFile } from "../storage/outline.js";
16
+ import { entities } from "../tables.js";
17
+ /** 非软删实体计数(单条 COUNT 查询;类型取值域与 entities CHECK 约束同域) */
18
+ function countEntities(db, type) {
19
+ const row = queryDb(db)
20
+ .select({ n: count() })
21
+ .from(entities)
22
+ .where(and(eq(entities.type, type), isNull(entities.deleted_at)))
23
+ .get();
24
+ return row?.n ?? 0;
25
+ }
26
+ /** 未软删章节点计数(根下可挂卷或直挂章;卷内为章) */
27
+ function countChapters(children) {
28
+ let n = 0;
29
+ for (const node of children) {
30
+ if (node.type === "chapter") {
31
+ if (node.deleted !== true)
32
+ n += 1;
33
+ continue;
34
+ }
35
+ for (const chapter of node.children ?? []) {
36
+ if (chapter.deleted !== true)
37
+ n += 1;
38
+ }
39
+ }
40
+ return n;
41
+ }
42
+ /**
43
+ * 备份命名用的规模快照(未软删口径):data.db 的实体计数 + 项目目录 outline.json 的章计数。
44
+ * outline.json 损坏时 `readOutlineFile` 抛错(与备份管道一致:不静默产出半成品备份)。
45
+ */
46
+ export function getBackupStats(db, dir) {
47
+ return {
48
+ characters: countEntities(db, "character"),
49
+ settings: countEntities(db, "setting"),
50
+ chapters: countChapters(readOutlineFile(dir).children),
51
+ };
52
+ }
53
+ //# sourceMappingURL=backup-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup-stats.js","sourceRoot":"","sources":["../../src/queries/backup-stats.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,oDAAoD;AACpD,4DAA4D;AAC5D,EAAE;AACF,8DAA8D;AAC9D,sEAAsE;AACtE,8EAA8E;AAC9E,EAAE;AACF,2CAA2C;AAC3C,oCAAoC;AACpC,gEAAgE;AAGhE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,sDAAsD;AACtD,SAAS,aAAa,CAAC,EAAM,EAAE,IAA6B;IAC1D,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,CAAC;SACpB,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;SACtB,IAAI,CAAC,QAAQ,CAAC;SACd,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;SAChE,GAAG,EAAE,CAAC;IACT,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,+BAA+B;AAC/B,SAAS,aAAa,CAAC,QAAqC;IAC1D,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;gBAAE,CAAC,IAAI,CAAC,CAAC;YAClC,SAAS;QACX,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YAC1C,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI;gBAAE,CAAC,IAAI,CAAC,CAAC;QACvC,CAAC;IACH,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,EAAM,EAAE,GAAW;IAChD,OAAO;QACL,UAAU,EAAE,aAAa,CAAC,EAAE,EAAE,WAAW,CAAC;QAC1C,QAAQ,EAAE,aAAa,CAAC,EAAE,EAAE,SAAS,CAAC;QACtC,QAAQ,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;KACvD,CAAC;AACJ,CAAC"}
@@ -8,20 +8,22 @@ import type { Db } from "../connection.js";
8
8
  * getOutlinePathIds 对缺失节点抛错,视为调用方 bug,本模块不捕获。
9
9
  * - 目标实体不存在(或已软删,getEntity 过滤)→ 返回 **null**,路由层映射 404 ENTITY_NOT_FOUND。
10
10
  *
11
- * 累积流程:
11
+ * 累积流程(章序前缀累积,2026-09 修订):
12
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,四段语义);update 冲突跳过不打断后续累积
18
- * 4. 响应:appliedDeltas 每项 { nodeId, description, changes(原样数组), skipped?(仅该 delta
13
+ * 2. 树路径 = getOutlinePathIds(tree, atNodeId)——用于目标节点 → 进度章的映射
14
+ * (同时保留「节点不存在抛错」语义);全局章序 = deriveChapterOrder(outlineDir)
15
+ * 3. 收集范围 = **章序 ≤ 进度章序**的全部章(跨卷/跨章),按(章序 ASC, 章内 order ASC)
16
+ * 应用——**一次批量取数**(listDeltasByNodes:一次 `node_id IN (...)` + 一次 readOutlineFile,
17
+ * 内置可见性三态过滤与全局 order ASC),再按触发节点分组回放;过滤 target_id === targetId
18
+ * ——天然满足双层排序与软删过滤;场景/卷/root 上的存量 Delta 不参与(锚点仅章)
19
+ * 4. 逐 change 应用(applyChange,四段语义);update 冲突跳过不打断后续累积
20
+ * 5. 响应:appliedDeltas 每项 { nodeId, description, changes(原样数组), skipped?(仅该 delta
19
21
  * 有跳过时出现)};conflicts 为跨全部 delta 的扁平数组
20
22
  *
21
23
  * target_type 不参与过滤(仅回显):id 前缀体系(char-/set-/loc-/hook-/sc-/ch-/vol- 等)保证
22
24
  * target_id 全局唯一,targetId 即足以定位目标;Req 携带 target_type 用于响应回显。
23
25
  *
24
- * @param outlineDir 项目根目录(outline.json 读取:树路径 + 节点软删校验)
26
+ * @param outlineDir 项目根目录(outline.json 读取:树路径/章序推导 + 节点软删校验)
25
27
  * @returns ComputeStateResult;目标实体不存在/已软删返回 null
26
28
  */
27
29
  export declare function computeState(db: Db, outlineDir: string, input: {
@@ -1 +1 @@
1
- {"version":3,"file":"compute-state.d.ts","sourceRoot":"","sources":["../../src/queries/compute-state.ts"],"names":[],"mappings":"AAcA,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"}
1
+ {"version":3,"file":"compute-state.d.ts","sourceRoot":"","sources":["../../src/queries/compute-state.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAGV,kBAAkB,EAKnB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAiH3C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;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,CAiD3B"}
@@ -1,30 +1,48 @@
1
- // @whispering233/ai-editor-db 状态计算(S5.2 computeState):沿大纲树父链累积 Delta 得到实体到达状态
1
+ // @whispering233/ai-editor-db 状态计算(S5.2 computeState):按**章序前缀**累积 Delta 得到实体到达状态
2
2
  //
3
+ // 累积口径(2026-09 修订,见 docs/design/10-data-model.md §4):
4
+ // 状态 = 实体初始 data + 「章序 ≤ 目标进度章」的全部已确认 Delta(跨卷/跨章累积),
5
+ // 按(章序 ASC, 章节内 order ASC)双层排序应用。旧口径「只沿大纲树父链」在锚点仅章
6
+ // (卡片 1.2)后失效——严格三层下父链至多含一个章,兄弟章的 Delta 不可见。
3
7
  //
4
8
  // 复用边界(不重复实现):
5
- // - listDeltasByNode(delta.ts):按节点查询 + 可见性三态过滤(delta 自身 /
6
- // 触发节点 / 目标实体任一软删即不可见)+ 节点内按 order 升序——computeState 的软删过滤
7
- // 与「节点内 order 序」由此获得
8
- // - getOutlinePathIds(storage/outline.ts):根 → at_node 树路径(严格三层下路径唯一);
9
- // 节点不存在时抛错——视为调用方 bug 不捕获
9
+ // - listDeltasByNodes(delta.ts):按触发节点**批量**查询 + 可见性三态过滤(delta 自身 /
10
+ // 触发节点 / 目标实体任一软删即不可见)+ 全局 order 升序——computeState 的软删过滤
11
+ // 与「章节内 order 序」由此获得(卡 2.6:取代逐章 listDeltasByNode 的 O(章数) 次文件读)
12
+ // - deriveChapterOrder(outline-ops.ts):全局章序(先序遍历、跨卷连续累计)——前缀边界与
13
+ // 应用序由此获得
14
+ // - getOutlinePathIds(storage/outline.ts):根 → at_node 树路径(场景 → 所属章的映射 +
15
+ // 节点缺失时抛错——视为调用方 bug 不捕获)
10
16
  // - getEntity(entity.ts):目标实体行(data 已解析为对象);不存在/已软删返回 null
17
+ // - readFieldPath / writeFieldPath(field-path.ts):字段路径解析(卡片 2.4)——顶层精确键优先 /
18
+ // 点分下钻(数组段按 name 匹配)/ 中途缺失 → 未命中(交本模块标注 skipped+conflicts)
19
+ //
20
+ // 字段路径(2026-09,契约:docs/db/schema.md「字段路径」+「面板路径解析口径」+「解析步骤」):
21
+ // `set`/`update` 支持点分嵌套路径(如 `ability_panel.火系.等级`);`add`/`remove` 仅顶层字段。
22
+ // 无分隔符的字段保持历史语义(顶层键、`set` 可新建);含 `.` 的字段逐段必须已存在,
23
+ // 缺失或末段落在分支上 → **不抛错**,按「跳过 + skipped/conflicts 标注」处理(不静默 inert)。
11
24
  //
12
25
  // plot_edge 不参与:本实现只读 delta_records,relation_records 的剧情连线
13
26
  // 天然不进入累积,无需额外过滤。
14
- import { listDeltasByNode } from "./delta.js";
27
+ //
28
+ // 已知代价(登记在案):appliedDeltas 随写作进度增长(含前面所有章的 Delta),
29
+ // 由上层截断机制兜底——本模块不新增截断。
30
+ import { listDeltasByNodes } from "./delta.js";
15
31
  import { getEntity } from "./entity.js";
16
- import { getOutlinePathIds, readOutlineFile } from "../storage/outline.js";
32
+ import { readFieldPath, writeFieldPath } from "./field-path.js";
33
+ import { deriveChapterOrder } from "./outline-ops.js";
34
+ import { findOutlineNode, getOutlinePathIds, readOutlineFile } from "../storage/outline.js";
17
35
  /**
18
36
  * 应用单条 change 到 state(四段规则 +):
19
- * - `set`:state[field] = to(直接替换)
20
- * - `update`:state[field] === from → state[field] = to;否则**跳过该 change**——
37
+ * - `set`:按字段路径写入(顶层直接替换;嵌套路径需逐段命中,未命中 → **跳过 + 冲突标注**)
38
+ * - `update`:字段路径处当前值 === from → 写入 to;否则(不相等 **或路径未命中**)**跳过该 change**——
21
39
  * 在 skipped 追加 { index, field, expected: from, actual } 且 conflicts 追加
22
40
  * { deltaId, field, expected: from, actual }(继续累积后续 change/delta,不抛 409——
23
- * 手动编辑 data 不产生 Delta 属正常用户行为)
41
+ * 手动编辑 data 不产生 Delta 属正常用户行为;面板结构改名/删叶子使旧路径失效,产生同款冲突)
24
42
  * - `add`:state[field] 为数组 → 按 value 追加;非数组**静默跳过**(防御,不标 conflicts——
25
- * 仅定义 update 冲突)
43
+ * 仅定义 update/set 路径冲突);**不走点分路径**(数组语义仅顶层字段)
26
44
  * - `remove`:state[field] 为数组 → 按值匹配移除**首个**匹配;值不存在静默忽略;
27
- * 非数组静默跳过
45
+ * 非数组静默跳过;**不走点分路径**
28
46
  * 字段约定:add/remove 用 value,set/update 用 to。
29
47
  * 防御:非对象 change 静默忽略(changes 列来自 JSON 解析,坏项不打挂整条计算)。
30
48
  */
@@ -33,20 +51,22 @@ function applyChange(state, change, index, deltaId, skipped, conflicts) {
33
51
  return;
34
52
  const { field, op } = change;
35
53
  switch (op) {
36
- case "set": // set:直接替换(to)
37
- state[field] = change.to;
38
- break;
39
- case "update": // update:旧值 → 新值,校验当前值 === from
40
- if (state[field] === change.from) {
41
- state[field] = change.to;
42
- }
43
- else {
44
- const actual = state[field];
45
- skipped.push({ index, field, expected: change.from, actual });
46
- conflicts.push({ deltaId, field, expected: change.from, actual });
54
+ case "set": // set:按字段路径写入(嵌套路径未命中 → 跳过 + 冲突标注)
55
+ if (!writeFieldPath(state, field, change.to)) {
56
+ skipped.push({ index, field, expected: change.from, actual: undefined });
57
+ conflicts.push({ deltaId, field, expected: change.from, actual: undefined });
47
58
  }
48
59
  break;
49
- case "add": // add:按 value 向数组追加(非数组静默跳过)
60
+ case "update": { // update:旧值 → 新值,校验当前值 === from(路径未命中同不走写入 → 冲突)
61
+ const { found, value } = readFieldPath(state, field);
62
+ if (found && value === change.from && writeFieldPath(state, field, change.to))
63
+ break;
64
+ const actual = found ? value : undefined;
65
+ skipped.push({ index, field, expected: change.from, actual });
66
+ conflicts.push({ deltaId, field, expected: change.from, actual });
67
+ break;
68
+ }
69
+ case "add": // add:按 value 向数组追加(非数组静默跳过;不走点分路径)
50
70
  if (Array.isArray(state[field])) {
51
71
  state[field].push(change.value);
52
72
  }
@@ -61,6 +81,54 @@ function applyChange(state, change, index, deltaId, skipped, conflicts) {
61
81
  break;
62
82
  }
63
83
  }
84
+ /**
85
+ * 目标节点 → **进度章序**(0 = 尚未进入任何章:root / 无章的卷):
86
+ * - `chapter` → 自身章序;
87
+ * - `scene` → 沿路径向上最近的 `chapter` 祖先(严格三层下即其父);
88
+ * - `volume` → 该卷**最后一个未软删章**(无章 → 0,表示该卷尚无写作进度);
89
+ * - `root` / 未知 → 0。
90
+ * 防御:节点缺失返回 0(路由层已前置校验存在性,且 getOutlinePathIds 先行抛错)。
91
+ */
92
+ function resolveProgressChapterNumber(tree, path, order, atNodeId) {
93
+ const node = findOutlineNode(tree, atNodeId);
94
+ if (node === undefined)
95
+ return 0;
96
+ const numberOf = (chapterId) => order.find((c) => c.chapterId === chapterId)?.chapterNumber ?? 0;
97
+ switch (node.type) {
98
+ case "chapter":
99
+ return numberOf(node.id);
100
+ case "scene":
101
+ // 路径 = [root, (vol), (ch), scene]——从末段前一项回溯找章(章直挂 root 时同样命中)
102
+ for (let i = path.length - 2; i >= 0; i--) {
103
+ const ancestor = path[i];
104
+ if (findOutlineNode(tree, ancestor)?.type === "chapter")
105
+ return numberOf(ancestor);
106
+ }
107
+ return 0; // 防御:无章祖先(严格三层下不可达)
108
+ case "volume": {
109
+ const chapters = (node.children ?? []).filter((c) => c.type === "chapter" && c.deleted !== true);
110
+ const last = chapters[chapters.length - 1];
111
+ return last === undefined ? 0 : numberOf(last.id);
112
+ }
113
+ default:
114
+ return 0; // root:只返回初始值
115
+ }
116
+ }
117
+ /**
118
+ * 批量 Delta 结果 → 按触发节点分组(组内保持全局 order ASC——listDeltasByNodes 已按 order 升序,
119
+ * 同节点内的相对序因此天然正确)。
120
+ */
121
+ function groupDeltasByNode(deltas) {
122
+ const byNode = new Map();
123
+ for (const delta of deltas) {
124
+ const list = byNode.get(delta.nodeId);
125
+ if (list === undefined)
126
+ byNode.set(delta.nodeId, [delta]);
127
+ else
128
+ list.push(delta);
129
+ }
130
+ return byNode;
131
+ }
64
132
  /**
65
133
  * 计算实体到达指定大纲节点时的累积状态(POST /api/v1/delta/compute)。
66
134
  *
@@ -69,20 +137,22 @@ function applyChange(state, change, index, deltaId, skipped, conflicts) {
69
137
  * getOutlinePathIds 对缺失节点抛错,视为调用方 bug,本模块不捕获。
70
138
  * - 目标实体不存在(或已软删,getEntity 过滤)→ 返回 **null**,路由层映射 404 ENTITY_NOT_FOUND。
71
139
  *
72
- * 累积流程:
140
+ * 累积流程(章序前缀累积,2026-09 修订):
73
141
  * 1. state 基座 = 实体初始 data 深拷贝(structuredClone,不污染 getEntity 返回行)
74
- * 2. 树路径 = getOutlinePathIds(tree, atNodeId)(根 → at_node,含 root 哨兵——无 delta 挂它,
75
- * 无害);路径上每节点调 listDeltasByNode(内置可见性三态过滤与 order ASC),
76
- * 过滤 target_id === targetId——天然满足「节点间树路径序 + 节点内 order 序」双层排序
77
- * 与软删过滤
78
- * 3. 逐 change 应用(applyChange,四段语义);update 冲突跳过不打断后续累积
79
- * 4. 响应:appliedDeltas 每项 { nodeId, description, changes(原样数组), skipped?(仅该 delta
142
+ * 2. 树路径 = getOutlinePathIds(tree, atNodeId)——用于目标节点 → 进度章的映射
143
+ * (同时保留「节点不存在抛错」语义);全局章序 = deriveChapterOrder(outlineDir)
144
+ * 3. 收集范围 = **章序 ≤ 进度章序**的全部章(跨卷/跨章),按(章序 ASC, 章内 order ASC)
145
+ * 应用——**一次批量取数**(listDeltasByNodes:一次 `node_id IN (...)` + 一次 readOutlineFile,
146
+ * 内置可见性三态过滤与全局 order ASC),再按触发节点分组回放;过滤 target_id === targetId
147
+ * ——天然满足双层排序与软删过滤;场景/卷/root 上的存量 Delta 不参与(锚点仅章)
148
+ * 4. 逐 change 应用(applyChange,四段语义);update 冲突跳过不打断后续累积
149
+ * 5. 响应:appliedDeltas 每项 { nodeId, description, changes(原样数组), skipped?(仅该 delta
80
150
  * 有跳过时出现)};conflicts 为跨全部 delta 的扁平数组
81
151
  *
82
152
  * target_type 不参与过滤(仅回显):id 前缀体系(char-/set-/loc-/hook-/sc-/ch-/vol- 等)保证
83
153
  * target_id 全局唯一,targetId 即足以定位目标;Req 携带 target_type 用于响应回显。
84
154
  *
85
- * @param outlineDir 项目根目录(outline.json 读取:树路径 + 节点软删校验)
155
+ * @param outlineDir 项目根目录(outline.json 读取:树路径/章序推导 + 节点软删校验)
86
156
  * @returns ComputeStateResult;目标实体不存在/已软删返回 null
87
157
  */
88
158
  export function computeState(db, outlineDir, input) {
@@ -97,9 +167,14 @@ export function computeState(db, outlineDir, input) {
97
167
  const state = structuredClone(entity.data);
98
168
  const appliedDeltas = [];
99
169
  const conflicts = [];
100
- // 4. 双层排序累积:节点间按路径序(根 → at_node),节点内按 order ASC(listDeltasByNode)
101
- for (const nodeId of path) {
102
- for (const delta of listDeltasByNode(db, nodeId, outlineDir)) {
170
+ // 4. 章序前缀(deriveChapterOrder 已是先序升序,filter 保持升序)→ 一次批量取数,
171
+ // 再按(章序 ASC, 章内 order ASC)累积(分组回放,同节点内相对序保持)
172
+ const chapterOrder = deriveChapterOrder(outlineDir);
173
+ const progressChapter = resolveProgressChapterNumber(tree, path, chapterOrder, input.atNodeId);
174
+ const prefixChapters = chapterOrder.filter((c) => c.chapterNumber <= progressChapter);
175
+ const deltasByNode = groupDeltasByNode(listDeltasByNodes(db, prefixChapters.map((c) => c.chapterId), outlineDir));
176
+ for (const chapter of prefixChapters) {
177
+ for (const delta of deltasByNode.get(chapter.chapterId) ?? []) {
103
178
  if (delta.targetId !== input.targetId)
104
179
  continue; // 只取目标实体的 Delta
105
180
  const skipped = [];
@@ -1 +1 @@
1
- {"version":3,"file":"compute-state.js","sourceRoot":"","sources":["../../src/queries/compute-state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compute-state.js","sourceRoot":"","sources":["../../src/queries/compute-state.ts"],"names":[],"mappings":"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"}
@@ -41,6 +41,21 @@ export declare function getDeltaRow(db: Db, id: string): DeltaRow | null;
41
41
  * @param outlineDir 项目根(触发节点与大纲 target 的软删/标题校验读 outline.json)
42
42
  */
43
43
  export declare function listDeltasByNode(db: Db, nodeId: string, outlineDir: string): DeltaRecord[];
44
+ /**
45
+ * 按触发节点**批量**查询 Delta(卡 2.6):computeState 的章序前缀收集专用——
46
+ * 逐章调 listDeltasByNode 会 O(章数) 次 `readOutlineFile` + JSON parse(300 章 / 300 条 delta
47
+ * 实测:逐章口径 132.9ms → 批量 2.9ms),本函数一次 `node_id IN (...)` + **一次** readOutlineFile
48
+ * 支撑全部可见性过滤。
49
+ * - SQL:node_id IN (...) AND deleted_at IS NULL,按 "order" 递增(全局单调 = 创建顺序)
50
+ * - 可见性三态与 targetName 联表与 listDeltasByNode **完全一致**(filterVisibleDeltas 共享)
51
+ * - 触发节点缺失/软删的行按行丢弃(与 listDeltasByNode 的整节点短路语义等价),
52
+ * 且在实体 IN 查询前先过滤(不为不可见行做无谓查询)
53
+ * - **调用方负责传章 id**(层级收窄不在本函数:传场景 id 会照实返回其 Delta;
54
+ * 锚点仅章的写入侧约束见 `docs/db/schema.md`)
55
+ * @param nodeIds 触发节点 id 集合;**空数组 → 空结果**(不查库、不读文件)
56
+ * @param outlineDir 项目根(触发节点与大纲 target 的软删/标题校验读 outline.json)
57
+ */
58
+ export declare function listDeltasByNodes(db: Db, nodeIds: readonly string[], outlineDir: string): DeltaRecord[];
44
59
  /**
45
60
  * 按目标端点查询 Delta(S6.3 工具 get_delta_history 下沉,「状态查询」):
46
61
  * - SQL:target_id = ? AND deleted_at IS NULL,按 "order" 递增(order 全局单调 =
@@ -1 +1 @@
1
- {"version":3,"file":"delta.d.ts","sourceRoot":"","sources":["../../src/queries/delta.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAmB,MAAM,iCAAiC,CAAC;AAK3G,OAAO,EAAmB,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AA2C5D,8BAA8B;AAC9B,MAAM,WAAW,gBAAgB;IAChC,mBAAmB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;IACxB,aAAa;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,gBAAgB,GAAG,QAAQ,CAuCrE;AAwED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAS/D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CAiB1F;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CAY9F;AAID,sCAAsC;AACtC,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAE9G,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAsElF"}
1
+ {"version":3,"file":"delta.d.ts","sourceRoot":"","sources":["../../src/queries/delta.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAmB,MAAM,iCAAiC,CAAC;AAK3G,OAAO,EAAmB,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AA2C5D,8BAA8B;AAC9B,MAAM,WAAW,gBAAgB;IAChC,mBAAmB;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;IACxB,aAAa;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,gBAAgB,GAAG,QAAQ,CAuCrE;AAwED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAS/D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CAiB1F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CAoBvG;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,EAAE,CAY9F;AAID,sCAAsC;AACtC,MAAM,MAAM,mBAAmB,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAE9G,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,mBAAmB,CAAC;CAC7B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAsElF"}
@@ -14,6 +14,7 @@
14
14
  // - name 联表同路径:实体 → entities.name、大纲节点 → outline.json title
15
15
  //
16
16
  // 查询经 queryDb 走 drizzle 构建器(混合风格 4A)。
17
+ // 批量查询(卡 2.6):listDeltasByNodes —— 一次 IN + 一次 readOutlineFile,供 computeState 章序前缀收集。
17
18
  // 语义逐句对照旧实现(git show 52c7c13^:packages/db/src/queries/delta.ts):
18
19
  // - order 全局单调:SELECT COALESCE(MAX("order"),0)+1 ↔ select({ next: sql<number> }) 聚合模板
19
20
  // (MAX 聚合无法用 builder 列表达,用 sql 模板;COALESCE 保证恒有行——聚合无分组恒单行)
@@ -128,7 +129,7 @@ export function insertDelta(db, input) {
128
129
  * - targetName 联表(参照 relation.ts buildEndpointContext):实体 → entities.name
129
130
  * (一次 IN 查询);大纲节点 → outline.json title;解析失败/缺失 → 省略字段
130
131
  *
131
- * 供 listDeltasByNode / listDeltasByTarget 共用(S6.3 工具下沉:按目标查询复用同一语义)。
132
+ * 供 listDeltasByNode / listDeltasByNodes / listDeltasByTarget 共用(S6.3 工具下沉:按目标查询复用同一语义)。
132
133
  * @param tree 已读取的 outline.json 树(调用方读取一次,避免重复 I/O)
133
134
  */
134
135
  function filterVisibleDeltas(db, rows, tree) {
@@ -235,6 +236,42 @@ export function listDeltasByNode(db, nodeId, outlineDir) {
235
236
  return [];
236
237
  return filterVisibleDeltas(db, rows, tree);
237
238
  }
239
+ /**
240
+ * 按触发节点**批量**查询 Delta(卡 2.6):computeState 的章序前缀收集专用——
241
+ * 逐章调 listDeltasByNode 会 O(章数) 次 `readOutlineFile` + JSON parse(300 章 / 300 条 delta
242
+ * 实测:逐章口径 132.9ms → 批量 2.9ms),本函数一次 `node_id IN (...)` + **一次** readOutlineFile
243
+ * 支撑全部可见性过滤。
244
+ * - SQL:node_id IN (...) AND deleted_at IS NULL,按 "order" 递增(全局单调 = 创建顺序)
245
+ * - 可见性三态与 targetName 联表与 listDeltasByNode **完全一致**(filterVisibleDeltas 共享)
246
+ * - 触发节点缺失/软删的行按行丢弃(与 listDeltasByNode 的整节点短路语义等价),
247
+ * 且在实体 IN 查询前先过滤(不为不可见行做无谓查询)
248
+ * - **调用方负责传章 id**(层级收窄不在本函数:传场景 id 会照实返回其 Delta;
249
+ * 锚点仅章的写入侧约束见 `docs/db/schema.md`)
250
+ * @param nodeIds 触发节点 id 集合;**空数组 → 空结果**(不查库、不读文件)
251
+ * @param outlineDir 项目根(触发节点与大纲 target 的软删/标题校验读 outline.json)
252
+ */
253
+ export function listDeltasByNodes(db, nodeIds, outlineDir) {
254
+ if (nodeIds.length === 0)
255
+ return [];
256
+ const q = queryDb(db);
257
+ const rows = q
258
+ .select()
259
+ .from(deltaRecords)
260
+ .where(and(inArray(deltaRecords.node_id, [...nodeIds]), isNull(deltaRecords.deleted_at)))
261
+ .orderBy(asc(deltaRecords.order))
262
+ .all();
263
+ if (rows.length === 0)
264
+ return [];
265
+ const tree = readOutlineFile(outlineDir);
266
+ // 触发节点缺失/软删 → 该行不可见(与 listDeltasByNode 的整节点短路同结果,此处按行表达)
267
+ const visibleRows = rows.filter((r) => {
268
+ const trigger = findOutlineNode(tree, r.node_id);
269
+ return trigger !== undefined && trigger.deleted !== true;
270
+ });
271
+ if (visibleRows.length === 0)
272
+ return [];
273
+ return filterVisibleDeltas(db, visibleRows, tree);
274
+ }
238
275
  /**
239
276
  * 按目标端点查询 Delta(S6.3 工具 get_delta_history 下沉,「状态查询」):
240
277
  * - SQL:target_id = ? AND deleted_at IS NULL,按 "order" 递增(order 全局单调 =