@whispering233/ai-editor-server 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 (60) hide show
  1. package/client-dist/assets/index-Bi1eymph.js +14 -0
  2. package/client-dist/assets/index-j-zzWAuh.css +1 -0
  3. package/client-dist/index.html +22 -0
  4. package/dist/consistency.d.ts +33 -0
  5. package/dist/consistency.d.ts.map +1 -0
  6. package/dist/consistency.js +53 -0
  7. package/dist/consistency.js.map +1 -0
  8. package/dist/debug.d.ts +29 -0
  9. package/dist/debug.d.ts.map +1 -0
  10. package/dist/debug.js +105 -0
  11. package/dist/debug.js.map +1 -0
  12. package/dist/index.d.ts +61 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +284 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/middleware/error.d.ts +40 -0
  17. package/dist/middleware/error.d.ts.map +1 -0
  18. package/dist/middleware/error.js +64 -0
  19. package/dist/middleware/error.js.map +1 -0
  20. package/dist/middleware/project.d.ts +67 -0
  21. package/dist/middleware/project.d.ts.map +1 -0
  22. package/dist/middleware/project.js +146 -0
  23. package/dist/middleware/project.js.map +1 -0
  24. package/dist/routes/chat.d.ts +98 -0
  25. package/dist/routes/chat.d.ts.map +1 -0
  26. package/dist/routes/chat.js +478 -0
  27. package/dist/routes/chat.js.map +1 -0
  28. package/dist/routes/delta.d.ts +4 -0
  29. package/dist/routes/delta.d.ts.map +1 -0
  30. package/dist/routes/delta.js +144 -0
  31. package/dist/routes/delta.js.map +1 -0
  32. package/dist/routes/entity.d.ts +7 -0
  33. package/dist/routes/entity.d.ts.map +1 -0
  34. package/dist/routes/entity.js +126 -0
  35. package/dist/routes/entity.js.map +1 -0
  36. package/dist/routes/outline.d.ts +11 -0
  37. package/dist/routes/outline.d.ts.map +1 -0
  38. package/dist/routes/outline.js +250 -0
  39. package/dist/routes/outline.js.map +1 -0
  40. package/dist/routes/project.d.ts +27 -0
  41. package/dist/routes/project.d.ts.map +1 -0
  42. package/dist/routes/project.js +552 -0
  43. package/dist/routes/project.js.map +1 -0
  44. package/dist/routes/proposal.d.ts +4 -0
  45. package/dist/routes/proposal.d.ts.map +1 -0
  46. package/dist/routes/proposal.js +145 -0
  47. package/dist/routes/proposal.js.map +1 -0
  48. package/dist/routes/relation.d.ts +6 -0
  49. package/dist/routes/relation.d.ts.map +1 -0
  50. package/dist/routes/relation.js +87 -0
  51. package/dist/routes/relation.js.map +1 -0
  52. package/dist/routes/settings.d.ts +37 -0
  53. package/dist/routes/settings.d.ts.map +1 -0
  54. package/dist/routes/settings.js +91 -0
  55. package/dist/routes/settings.js.map +1 -0
  56. package/dist/routes/trash.d.ts +4 -0
  57. package/dist/routes/trash.d.ts.map +1 -0
  58. package/dist/routes/trash.js +135 -0
  59. package/dist/routes/trash.js.map +1 -0
  60. package/package.json +48 -0
@@ -0,0 +1,144 @@
1
+ // Delta 路由(S5.3):POST /api/v1/delta(追加)/ GET /node/:nodeId(按节点查询)/ POST /compute(状态计算)
2
+ //
3
+ // 契约来源:doc/api/endpoints.md 第 395-510 行(Delta 变更追踪)、决策 9(computeState 累积规则)、
4
+ // 决策 12 修订(可见性联动)、S13.3 收紧(变更目标仅实体类型——大纲节点不可作为变更目标,
5
+ // 历史 outline_node 目标数据保留展示,创建路径拒绝)。
6
+ // 错误映射(对照 endpoints.md 错误码):
7
+ // 参数校验失败 → 400 VALIDATION_ERROR(zod 抛错由 errorHandler 统一映射,含 fields;
8
+ // per-op 必填字段(endpoints.md 第 407-418 行)因 op 而异、zod 无法表达,路由层补校验
9
+ // → HttpError 400 VALIDATION_ERROR,与 entity.ts validateDataByType 同思路;
10
+ // S13.3 target_type 白名单校验同此 400——schema 层 target_type 为宽松 z.string(),
11
+ // 收紧在路由层(shared schema 不动,同 S12.1 节点 data 校验模式))
12
+ // 触发节点不存在或已软删 → 404 OUTLINE_NODE_NOT_FOUND(POST /delta 与 /delta/compute 路由层前置校验;
13
+ // POST /delta 契约虽未定义该错误码,但 db 层 insertDelta 不校验节点(delta.ts 注释:缺失节点记录
14
+ // 因可见性规则永久不可见),路由层拦截防死记录——oracle 建议)
15
+ // 目标实体不存在或已软删 → 404 ENTITY_NOT_FOUND(POST /delta/compute)
16
+ import { Hono } from "hono";
17
+ import { computeState, findOutlineNode, getEntity, insertDelta, listDeltasByNode, readOutlineFile } from "@whispering233/ai-editor-db";
18
+ import { ENTITY_TYPES, mapRowToDelta } from "@whispering233/ai-editor-shared";
19
+ import { deltaComputeReqSchema, deltaCreateReqSchema } from "@whispering233/ai-editor-shared/schemas";
20
+ import { HttpError, ok } from "../middleware/error.js";
21
+ import { requireCurrentProject } from "../middleware/project.js";
22
+ /** Delta 路由(挂载于 /api/v1/delta,index.ts) */
23
+ export const deltaRoutes = new Hono();
24
+ /**
25
+ * 前置校验大纲节点存在且未软删(oracle 建议,防死记录):
26
+ * db 层 insertDelta / computeState 均不校验触发节点存在性(契约未定义该错误码,delta.ts 注释),
27
+ * 缺失节点记录会因可见性规则(决策 12 修订:触发节点缺失视同不可见)永久不可见,
28
+ * 故路由层拦截并映射 404 OUTLINE_NODE_NOT_FOUND;computeState 的 getOutlinePathIds
29
+ * 对缺失节点抛错(视为调用方 bug),同样由本校验先行兜底。
30
+ */
31
+ function assertOutlineNode(project, nodeId) {
32
+ const tree = readOutlineFile(project.root);
33
+ const node = findOutlineNode(tree, nodeId);
34
+ if (node === undefined || node.deleted === true) {
35
+ throw new HttpError(404, "OUTLINE_NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
36
+ }
37
+ }
38
+ /**
39
+ * per-op 必填字段校验(endpoints.md 第 407-418 行):
40
+ * op=set → to 必填;op=update → from+to 必填;op=add → value 必填;op=remove → value 必填。
41
+ * deltaChangeSchema 的字段均 optional——必填语义因 op 而异,schema 层无法表达,
42
+ * 由路由层按 op 分派校验(与 entity.ts validateDataByType「schema 之外按类型精校验」同思路)。
43
+ * 不满足 → 400 VALIDATION_ERROR(message 注明 changes 下标与缺失字段)。
44
+ */
45
+ function validateChangesByOp(changes) {
46
+ for (let i = 0; i < changes.length; i++) {
47
+ const { op, from, to, value } = changes[i];
48
+ const missing = [];
49
+ switch (op) {
50
+ case "set":
51
+ if (to === undefined)
52
+ missing.push("to");
53
+ break;
54
+ case "update":
55
+ if (from === undefined)
56
+ missing.push("from");
57
+ if (to === undefined)
58
+ missing.push("to");
59
+ break;
60
+ case "add":
61
+ if (value === undefined)
62
+ missing.push("value");
63
+ break;
64
+ case "remove":
65
+ if (value === undefined)
66
+ missing.push("value");
67
+ break;
68
+ }
69
+ if (missing.length > 0) {
70
+ throw new HttpError(400, "VALIDATION_ERROR", `changes[${i}] op=${op} 缺少必填字段: ${missing.join("/")}`);
71
+ }
72
+ }
73
+ }
74
+ /**
75
+ * 变更目标类型白名单校验(S13.3 收紧:仅实体类型 character/setting/location/hook——大纲节点代表的
76
+ * 故事导致实体发生变更,节点结构化信息不应出现在变更记录中;历史 outline_node 目标数据保留展示,
77
+ * 仅创建路径拒绝)。schema 层 target_type 为宽松 z.string()(shared 不动),收紧在路由层
78
+ * (与 S12.1 节点 data 按层级校验同模式)。不通过 → 400 VALIDATION_ERROR(参照 entity.ts parseTypeParam 错误风格)。
79
+ */
80
+ function assertDeltaTargetType(targetType) {
81
+ if (!ENTITY_TYPES.includes(targetType)) {
82
+ throw new HttpError(400, "VALIDATION_ERROR", `非法变更目标类型: ${targetType}(变更目标仅限实体类型: ${ENTITY_TYPES.join("/")})`);
83
+ }
84
+ }
85
+ // POST /api/v1/delta —— 追加属性变更(201;order 服务端全局单调生成)
86
+ // 校验顺序(oracle 建议):schema → target_type 白名单(400,S13.3 请求形状校验,无 DB 读)→
87
+ // 触发节点存在性(404)→ per-op 必填(400)→ insert
88
+ deltaRoutes.post("/", async (c) => {
89
+ const project = requireCurrentProject();
90
+ const raw = await c.req.json().catch(() => null);
91
+ const parsed = deltaCreateReqSchema.safeParse(raw);
92
+ if (!parsed.success) {
93
+ throw parsed.error; // → 400 VALIDATION_ERROR(含 fields)
94
+ }
95
+ const { node_id, target_type, target_id, changes, description } = parsed.data;
96
+ assertDeltaTargetType(target_type); // S13.3:变更目标仅实体类型(shared schema 不动,路由层收紧)
97
+ assertOutlineNode(project, node_id); // 触发节点必须存在且未软删(防死记录)
98
+ validateChangesByOp(changes); // per-op 必填字段(endpoints.md 第 407-418 行)
99
+ const row = insertDelta(project.db, {
100
+ nodeId: node_id,
101
+ targetType: target_type,
102
+ targetId: target_id,
103
+ changes,
104
+ description,
105
+ });
106
+ return c.json(ok({ id: row.id, applied: mapRowToDelta(row) }), 201);
107
+ });
108
+ // GET /api/v1/delta/node/:nodeId —— 按触发节点查询(200)
109
+ // 契约未定义该端点 404(endpoints.md 第 436-462 行):节点缺失/软删 → 空数组(listDeltasByNode
110
+ // 内置可见性三态过滤,触发节点缺失视同不可见),不 404。
111
+ deltaRoutes.get("/node/:nodeId", (c) => {
112
+ const project = requireCurrentProject();
113
+ const nodeId = c.req.param("nodeId");
114
+ const deltas = listDeltasByNode(project.db, nodeId, project.root);
115
+ return c.json(ok({ nodeId, deltas }));
116
+ });
117
+ // POST /api/v1/delta/compute —— 累积状态计算(决策 9,endpoints.md 第 464-510 行)
118
+ // 校验顺序(任务规格):schema → at_node_id 存在性(404 OUTLINE_NODE_NOT_FOUND,
119
+ // computeState 的 getOutlinePathIds 对缺失节点抛错——路由层先拦截)→ 目标实体存在性
120
+ // (404 ENTITY_NOT_FOUND;computeState 对缺失/软删实体返回 null)→ computeState → 200
121
+ deltaRoutes.post("/compute", async (c) => {
122
+ const project = requireCurrentProject();
123
+ const raw = await c.req.json().catch(() => null);
124
+ const parsed = deltaComputeReqSchema.safeParse(raw);
125
+ if (!parsed.success) {
126
+ throw parsed.error; // → 400 VALIDATION_ERROR(含 fields)
127
+ }
128
+ const { target_type, target_id, at_node_id } = parsed.data;
129
+ assertOutlineNode(project, at_node_id);
130
+ if (getEntity(project.db, target_id) === null) {
131
+ throw new HttpError(404, "ENTITY_NOT_FOUND", `实体不存在: ${target_id}`);
132
+ }
133
+ const result = computeState(project.db, project.root, {
134
+ targetType: target_type,
135
+ targetId: target_id,
136
+ atNodeId: at_node_id,
137
+ });
138
+ if (result === null) {
139
+ // 不可达防御分支(上面 getEntity 已确认实体存在;computeState 内部同源查询不会翻案)
140
+ throw new HttpError(404, "ENTITY_NOT_FOUND", `实体不存在: ${target_id}`);
141
+ }
142
+ return c.json(ok(result));
143
+ });
144
+ //# sourceMappingURL=delta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/routes/delta.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { Hono } from "hono";
2
+ import type { EntityType } from "@whispering233/ai-editor-shared";
3
+ /** 实体路由(挂载于 /api/v1/entity,index.ts) */
4
+ export declare const entityRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
5
+ /** 校验 :type 路径参数为合法实体类型(非法 → 400 VALIDATION_ERROR;trash 路由复用) */
6
+ export declare function parseTypeParam(type: string): EntityType;
7
+ //# sourceMappingURL=entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../src/routes/entity.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAKlE,wCAAwC;AACxC,eAAO,MAAM,YAAY,4EAAa,CAAC;AAEvC,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAMvD"}
@@ -0,0 +1,126 @@
1
+ // 实体路由(S3.3):GET 列表 / POST 创建 / GET 详情 / PUT 部分更新 / DELETE 软删
2
+ //
3
+ // 契约来源:doc/api/endpoints.md 第 124-276 行(实体 CRUD)、决策 12(软删级联)。
4
+ // 错误映射(对照 endpoints.md 错误码):
5
+ // type 参数非法 / 参数校验失败 → 400 VALIDATION_ERROR(zod 抛错由 errorHandler 统一映射,含 fields)
6
+ // 实体不存在或已软删 → 404 ENTITY_NOT_FOUND
7
+ import { Hono } from "hono";
8
+ import { countDeltasForEntity, createEntity, getEntity, listEntities, listRelations, nowIso, softDeleteEntity, updateEntity } from "@whispering233/ai-editor-db";
9
+ import { ENTITY_DATA_SCHEMAS, entityCreateReqSchema, entityListQuerySchema, entityTypeSchema, entityUpdateReqSchema } from "@whispering233/ai-editor-shared/schemas";
10
+ import { HttpError, ok } from "../middleware/error.js";
11
+ import { requireCurrentProject } from "../middleware/project.js";
12
+ /** 实体路由(挂载于 /api/v1/entity,index.ts) */
13
+ export const entityRoutes = new Hono();
14
+ /** 校验 :type 路径参数为合法实体类型(非法 → 400 VALIDATION_ERROR;trash 路由复用) */
15
+ export function parseTypeParam(type) {
16
+ const parsed = entityTypeSchema.safeParse(type);
17
+ if (!parsed.success) {
18
+ throw new HttpError(400, "VALIDATION_ERROR", `非法实体类型: ${type}`);
19
+ }
20
+ return parsed.data;
21
+ }
22
+ /** 按类型精确校验 data(endpoints.md 第 211-216 行:各 type 的 data 字段 schema;宽松 record 之外的精校验) */
23
+ function validateDataByType(type, data) {
24
+ const check = ENTITY_DATA_SCHEMAS[type].safeParse(data);
25
+ if (!check.success) {
26
+ throw check.error; // → errorHandler → 400 VALIDATION_ERROR(含 fields)
27
+ }
28
+ }
29
+ // GET /api/v1/entity/:type —— 列表(q/offset/limit/sort/order;响应 camelCase)
30
+ entityRoutes.get("/:type", (c) => {
31
+ const project = requireCurrentProject();
32
+ const type = parseTypeParam(c.req.param("type"));
33
+ const parsed = entityListQuerySchema.safeParse(c.req.query());
34
+ if (!parsed.success)
35
+ throw parsed.error;
36
+ const { q, offset, limit, sort, order } = parsed.data;
37
+ const result = listEntities(project.db, { type, q, offset, limit, sort, order });
38
+ return c.json(ok({
39
+ items: result.items, // EntitySummary(db 已提取:id/type/name/summary/createdAt/updatedAt,camelCase)
40
+ total: result.total,
41
+ offset: offset ?? 0,
42
+ limit: limit ?? 50,
43
+ }));
44
+ });
45
+ // GET /api/v1/entity/:type/:id —— 详情(含紧邻 relations + deltaCount)
46
+ entityRoutes.get("/:type/:id", (c) => {
47
+ const project = requireCurrentProject();
48
+ parseTypeParam(c.req.param("type"));
49
+ const id = c.req.param("id");
50
+ const row = getEntity(project.db, id);
51
+ if (row === null) {
52
+ throw new HttpError(404, "ENTITY_NOT_FOUND", `实体不存在: ${id}`);
53
+ }
54
+ const deltaCount = countDeltasForEntity(project.db, id);
55
+ // relations 紧邻(S3.2:listRelations depth=1,任一端点软删即不可见,决策 12 修订;
56
+ // outline.json 校验路径 = project.root)。
57
+ // **双向邻接**(endpoints.md「紧邻 1 跳」未明示方向,产品语义为展示所有关联):
58
+ // source 方向(该实体作为起点) + target 方向(该实体作为终点)两次查询,
59
+ // 按关系 id 去重(source 方向优先、保持稳定顺序;自环 A→A 两方向均命中但只出现一次)
60
+ const sourceRels = listRelations(project.db, { sourceId: row.id }, 1, project.root).relations;
61
+ const targetRels = listRelations(project.db, { targetId: row.id }, 1, project.root).relations;
62
+ const seenIds = new Set();
63
+ const relations = [...sourceRels, ...targetRels].filter((r) => {
64
+ if (seenIds.has(r.id))
65
+ return false;
66
+ seenIds.add(r.id);
67
+ return true;
68
+ });
69
+ return c.json(ok({
70
+ id: row.id,
71
+ type: row.type,
72
+ name: row.name,
73
+ data: row.data,
74
+ relations,
75
+ deltaCount,
76
+ createdAt: row.created_at,
77
+ updatedAt: row.updated_at,
78
+ }));
79
+ });
80
+ // POST /api/v1/entity/:type —— 创建(name 必填 1-100;data 按类型精确校验;201)
81
+ entityRoutes.post("/:type", async (c) => {
82
+ const project = requireCurrentProject();
83
+ const type = parseTypeParam(c.req.param("type"));
84
+ const raw = await c.req.json().catch(() => null);
85
+ const parsed = entityCreateReqSchema.safeParse(raw);
86
+ if (!parsed.success)
87
+ throw parsed.error;
88
+ if (parsed.data.data !== undefined) {
89
+ validateDataByType(type, parsed.data.data);
90
+ }
91
+ const row = createEntity(project.db, { type, name: parsed.data.name, data: parsed.data.data });
92
+ return c.json(ok({ id: row.id, type: row.type, name: row.name, data: row.data, createdAt: row.created_at }), 201);
93
+ });
94
+ // PUT /api/v1/entity/:type/:id —— 部分更新(仅合并传入字段;data 浅合并)
95
+ entityRoutes.put("/:type/:id", async (c) => {
96
+ const project = requireCurrentProject();
97
+ const type = parseTypeParam(c.req.param("type"));
98
+ const id = c.req.param("id");
99
+ const raw = await c.req.json().catch(() => null);
100
+ const parsed = entityUpdateReqSchema.safeParse(raw);
101
+ if (!parsed.success)
102
+ throw parsed.error;
103
+ if (parsed.data.data !== undefined) {
104
+ validateDataByType(type, parsed.data.data);
105
+ }
106
+ const row = updateEntity(project.db, id, parsed.data);
107
+ if (row === null) {
108
+ throw new HttpError(404, "ENTITY_NOT_FOUND", `实体不存在: ${id}`);
109
+ }
110
+ return c.json(ok({ id: row.id, updated: true }));
111
+ });
112
+ // DELETE /api/v1/entity/:type/:id —— 软删(决策 12:级联软删关系与 Delta,本体保留可还原)
113
+ entityRoutes.delete("/:type/:id", (c) => {
114
+ const project = requireCurrentProject();
115
+ parseTypeParam(c.req.param("type"));
116
+ const id = c.req.param("id");
117
+ const result = softDeleteEntity(project.db, id, nowIso());
118
+ if (result === null) {
119
+ throw new HttpError(404, "ENTITY_NOT_FOUND", `实体不存在: ${id}`);
120
+ }
121
+ return c.json(ok({
122
+ deleted: true,
123
+ cascaded: { relations: result.relations, deltas: result.deltas },
124
+ }));
125
+ });
126
+ //# sourceMappingURL=entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity.js","sourceRoot":"","sources":["../../src/routes/entity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { Hono } from "hono";
2
+ import type { OutlineFileTree } from "@whispering233/ai-editor-shared";
3
+ /** 大纲路由(挂载于 /api/v1/outline,index.ts) */
4
+ export declare const outlineRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
5
+ /** 收集 nodeId 及其整棵子树的 id 集(含自身;供 relation/delta 级联软删/还原/清除用) */
6
+ export declare function collectSubtreeIds(tree: OutlineFileTree, nodeId: string): string[];
7
+ /** 生成 SQL IN 占位符串(id 集来自服务端生成的 nanoid,无注入面;空集返回 "(NULL)" 恒假) */
8
+ export declare function inPlaceholders(ids: string[]): string;
9
+ /** OutlineError → HttpError 映射(对照 endpoints.md 错误码,文件头注释表) */
10
+ export declare function mapOutlineError(err: unknown): never;
11
+ //# sourceMappingURL=outline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outline.d.ts","sourceRoot":"","sources":["../../src/routes/outline.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAW5B,OAAO,KAAK,EAAmB,eAAe,EAA6C,MAAM,iCAAiC,CAAC;AAanI,yCAAyC;AACzC,eAAO,MAAM,aAAa,4EAAa,CAAC;AA4BxC,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAajF;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,MAAM,CAEpD;AA0LD,8DAA8D;AAC9D,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,CAcnD"}
@@ -0,0 +1,250 @@
1
+ // 大纲路由(S2.2):GET/POST /outline、PUT /outline/:nodeId、PUT move、DELETE、GET path
2
+ //
3
+ // 契约来源:doc/api/endpoints.md 第 516-657 行(大纲操作)、决策 12(软删/级联)、决策 19(严格三层)。
4
+ // 错误映射(db OutlineError → HttpError,对照 endpoints.md 错误码):
5
+ // NODE_NOT_FOUND → 404 OUTLINE_NODE_NOT_FOUND
6
+ // PARENT_NOT_FOUND → 400 OUTLINE_NODE_NOT_FOUND(父不存在是参数问题,非资源访问)
7
+ // INVALID_HIERARCHY → 400 VALIDATION_ERROR(严格三层违反)
8
+ // OUTLINE_ANCESTOR_DELETED → 409 OUTLINE_ANCESTOR_DELETED(决策 12 修订)
9
+ // 说明:级联还原/物理清除(cascadeRestore/cascadePurge)已下沉 db 包 queries/trash.ts(S4.1);
10
+ // 级联软删(cascadeSoftDelete)保留在路由层——与软删端点同文件内联,S4.1 任务边界只下沉
11
+ // 还原/清除两个 helper(trash.ts 路由直接 import @whispering233/ai-editor-db)。
12
+ import { Hono } from "hono";
13
+ import { findOutlineNode, readOutlineFile } from "@whispering233/ai-editor-db";
14
+ import { getOutlinePathIds } from "@whispering233/ai-editor-db";
15
+ import { createOutlineNode, deleteOutlineNode, moveOutlineNode, OutlineError, updateOutlineNodeInfo, } from "@whispering233/ai-editor-db";
16
+ import { HOOK_RELATION_TYPES, mapOutlineFileToTree } from "@whispering233/ai-editor-shared";
17
+ import { nowIso } from "@whispering233/ai-editor-db";
18
+ import { outlineCreateReqSchema, outlineGetQuerySchema, outlineMoveReqSchema, outlineUpdateReqSchema, OUTLINE_NODE_DATA_SCHEMAS, } from "@whispering233/ai-editor-shared/schemas";
19
+ import { HttpError, ok } from "../middleware/error.js";
20
+ import { requireCurrentProject } from "../middleware/project.js";
21
+ /** 大纲路由(挂载于 /api/v1/outline,index.ts) */
22
+ export const outlineRoutes = new Hono();
23
+ /**
24
+ * 按节点层级精确校验 data(决策 23:麦基字段集,OUTLINE_NODE_DATA_SCHEMAS;
25
+ * 宽松 record 之外的精校验,与 entity.ts validateDataByType 同构;
26
+ * 失败 → errorHandler → 400 VALIDATION_ERROR(含 fields))
27
+ */
28
+ function validateNodeData(type, data) {
29
+ const check = OUTLINE_NODE_DATA_SCHEMAS[type].safeParse(data);
30
+ if (!check.success) {
31
+ throw check.error;
32
+ }
33
+ }
34
+ /** 递归过滤软删节点(决策 12:常规查询默认过滤;deleted 节点整棵子树丢弃) */
35
+ function filterDeletedTree(tree) {
36
+ const filterNodes = (nodes) => nodes
37
+ .filter((n) => n.deleted !== true)
38
+ .map((n) => {
39
+ const kids = n.children;
40
+ return (kids === undefined ? { ...n } : { ...n, children: filterNodes(kids) });
41
+ });
42
+ // 递归产物为宽 OutlineFileNode[],结构上满足严格三层(输入树合法 ⇒ 过滤后仍合法),
43
+ // 此处断言收窄回 OutlineFileTree(storage 形态判别联合的已知递归映射限制)
44
+ return { ...tree, children: filterNodes(tree.children) };
45
+ }
46
+ /** 收集 nodeId 及其整棵子树的 id 集(含自身;供 relation/delta 级联软删/还原/清除用) */
47
+ export function collectSubtreeIds(tree, nodeId) {
48
+ const ids = [];
49
+ const visit = (nodes) => {
50
+ for (const n of nodes) {
51
+ if (n.id === nodeId || visit(n.children ?? [])) {
52
+ ids.push(n.id);
53
+ return true;
54
+ }
55
+ }
56
+ return false;
57
+ };
58
+ if (visit(tree.children))
59
+ return ids;
60
+ throw new HttpError(404, "OUTLINE_NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
61
+ }
62
+ /** 生成 SQL IN 占位符串(id 集来自服务端生成的 nanoid,无注入面;空集返回 "(NULL)" 恒假) */
63
+ export function inPlaceholders(ids) {
64
+ return ids.length === 0 ? "(NULL)" : `(${ids.map(() => "?").join(",")})`;
65
+ }
66
+ /**
67
+ * 级联软删该节点及子树关联的 relation/delta(决策 12:任一端点软删即不可见):
68
+ * - relations:source 或 target 命中子树任一节点 → 标 deleted_at(含 plot_edge 画布连线)
69
+ * - deltas:node_id 命中子树任一节点 → 标 deleted_at
70
+ * 返回 { relations, deltas } 实际级联数(UPDATE.changes)。
71
+ */
72
+ function cascadeSoftDelete(db, subtreeIds, deletedAt) {
73
+ const rel = db
74
+ .prepare(`UPDATE relation_records SET deleted_at = ? WHERE deleted_at IS NULL AND
75
+ (source_id IN ${inPlaceholders(subtreeIds)} OR target_id IN ${inPlaceholders(subtreeIds)})`)
76
+ .run(deletedAt, ...subtreeIds, ...subtreeIds);
77
+ const delta = db
78
+ .prepare(`UPDATE delta_records SET deleted_at = ? WHERE deleted_at IS NULL AND node_id IN ${inPlaceholders(subtreeIds)}`)
79
+ .run(deletedAt, ...subtreeIds);
80
+ return { relations: rel.changes, deltas: delta.changes };
81
+ }
82
+ // GET /api/v1/outline —— 完整大纲树(软删过滤;with_metadata=true 联查统计)
83
+ outlineRoutes.get("/", (c) => {
84
+ const project = requireCurrentProject();
85
+ const query = outlineGetQuerySchema.safeParse(c.req.query());
86
+ if (!query.success) {
87
+ throw query.error;
88
+ }
89
+ const tree = readOutlineFile(project.root);
90
+ // shared 映射(oracle 回修后支持决策 19「chapter 直挂 root」——换回 mapOutlineFileToTree,
91
+ // 删除 S2.2 的自写 mapTreeToApi 绕过)
92
+ const apiTree = mapOutlineFileToTree(filterDeletedTree(tree));
93
+ if (query.data.with_metadata === true) {
94
+ attachMetadata(apiTree, project.db);
95
+ }
96
+ return c.json(ok(apiTree));
97
+ });
98
+ /**
99
+ * with_metadata 联查统计(跨 outline.json × data.db,endpoints.md 第 523 行):
100
+ * - hookCount:该节点出发的伏笔管理关系(plants/advances/resolves,source=outline_node → hook,hooks.md)
101
+ * - charCount:appears_in 指向该节点的关系数(决策 2 示例:char → 大纲节点)
102
+ * - deltaCount:该节点触发的 Delta 数(delta_records.node_id)
103
+ * 均为运行时计算,不写回数据(决策 21 _health 同款口径)。
104
+ */
105
+ function attachMetadata(tree, db) {
106
+ const hookStmt = db.prepare(`SELECT COUNT(*) AS c FROM relation_records
107
+ WHERE source_type = 'outline_node' AND source_id = ?
108
+ AND relation_type IN (${HOOK_RELATION_TYPES.map(() => "?").join(",")})
109
+ AND deleted_at IS NULL`);
110
+ const charStmt = db.prepare(`SELECT COUNT(*) AS c FROM relation_records
111
+ WHERE target_type = 'outline_node' AND target_id = ? AND relation_type = 'appears_in'
112
+ AND deleted_at IS NULL`);
113
+ const deltaStmt = db.prepare(`SELECT COUNT(*) AS c FROM delta_records WHERE node_id = ? AND deleted_at IS NULL`);
114
+ const visit = (node) => {
115
+ const metadata = {};
116
+ metadata.hookCount = hookStmt.get(node.id, ...HOOK_RELATION_TYPES).c;
117
+ metadata.charCount = charStmt.get(node.id).c;
118
+ metadata.deltaCount = deltaStmt.get(node.id).c;
119
+ node.metadata = metadata;
120
+ for (const child of node.children ?? [])
121
+ visit(child);
122
+ };
123
+ for (const vol of tree.children)
124
+ visit(vol);
125
+ }
126
+ // POST /api/v1/outline —— 创建节点(严格三层,parent_id 必填,决策 19;data 按层级精校验,决策 23)
127
+ outlineRoutes.post("/", async (c) => {
128
+ const project = requireCurrentProject();
129
+ const raw = await c.req.json().catch(() => null);
130
+ const parsed = outlineCreateReqSchema.safeParse(raw);
131
+ if (!parsed.success) {
132
+ throw parsed.error; // → 400 VALIDATION_ERROR(含 fields)
133
+ }
134
+ const { type, title, parent_id, summary, data } = parsed.data;
135
+ if (data !== undefined) {
136
+ validateNodeData(type, data);
137
+ }
138
+ let node;
139
+ try {
140
+ node = createOutlineNode(project.root, { type, title, parentId: parent_id, summary, data, updatedAt: nowIso() });
141
+ }
142
+ catch (err) {
143
+ throw mapOutlineError(err);
144
+ }
145
+ return c.json(ok({
146
+ id: node.id,
147
+ type: node.type,
148
+ title: node.title,
149
+ parentId: parent_id,
150
+ updatedAt: node.updated_at,
151
+ }), 201);
152
+ });
153
+ // PUT /api/v1/outline/:nodeId —— 更新标题/描述/结构化 data(data 部分合并,决策 23)
154
+ outlineRoutes.put("/:nodeId", async (c) => {
155
+ const project = requireCurrentProject();
156
+ const raw = await c.req.json().catch(() => null);
157
+ const parsed = outlineUpdateReqSchema.safeParse(raw);
158
+ if (!parsed.success) {
159
+ throw parsed.error;
160
+ }
161
+ const nodeId = c.req.param("nodeId");
162
+ // data 精校验需要节点实际层级(请求体不含 type):先定位节点(404 语义),再按层级校验
163
+ if (parsed.data.data !== undefined) {
164
+ const node = findOutlineNode(readOutlineFile(project.root), nodeId);
165
+ if (node === undefined) {
166
+ throw new HttpError(404, "OUTLINE_NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
167
+ }
168
+ validateNodeData(node.type, parsed.data.data);
169
+ }
170
+ try {
171
+ updateOutlineNodeInfo(project.root, nodeId, parsed.data, nowIso());
172
+ }
173
+ catch (err) {
174
+ throw mapOutlineError(err);
175
+ }
176
+ return c.json(ok({ updated: true }));
177
+ });
178
+ // PUT /api/v1/outline/:nodeId/move —— 移动/重排(严格三层约束同创建)
179
+ outlineRoutes.put("/:nodeId/move", async (c) => {
180
+ const project = requireCurrentProject();
181
+ const raw = await c.req.json().catch(() => null);
182
+ const parsed = outlineMoveReqSchema.safeParse(raw);
183
+ if (!parsed.success) {
184
+ throw parsed.error;
185
+ }
186
+ let result;
187
+ try {
188
+ result = moveOutlineNode(project.root, c.req.param("nodeId"), { parentId: parsed.data.parent_id, order: parsed.data.order }, // snake_case → camelCase
189
+ nowIso());
190
+ }
191
+ catch (err) {
192
+ throw mapOutlineError(err);
193
+ }
194
+ return c.json(ok({ moved: true, ...result }));
195
+ });
196
+ // DELETE /api/v1/outline/:nodeId —— 软删 + 递归级联(决策 12)
197
+ // 写序(决策 16):**先 DB 后 JSON**——先级联软删 relation/delta,再原子写 outline.json;
198
+ // 崩溃残留方向(DB 已级联、JSON 未标,节点未标 deleted):无法从 DB 记录可靠反推
199
+ // (实体侧级联会软删节点↔实体关系而节点仍存活),不在 S4.2 补标范围;
200
+ // S4.2 启动一致性校验(consistency.ts)兜底幽灵反向:节点已软删而关联 relation/delta
201
+ // 未软删 → 以节点软删为准补标 DB 记录(决策 16 修订,幂等自愈)
202
+ outlineRoutes.delete("/:nodeId", (c) => {
203
+ const project = requireCurrentProject();
204
+ const nodeId = c.req.param("nodeId");
205
+ const tree = readOutlineFile(project.root);
206
+ const subtreeIds = collectSubtreeIds(tree, nodeId); // 404 语义
207
+ const deletedAt = nowIso();
208
+ // 1. DB 级联软删关联关系与 Delta(决策 12:节点/端点软删即不可见)
209
+ const { relations, deltas } = cascadeSoftDelete(project.db, subtreeIds, deletedAt);
210
+ // 2. JSON 原子写(软删节点 + 递归子树,决策 19 版本戳)
211
+ let children;
212
+ try {
213
+ children = deleteOutlineNode(project.root, nodeId, deletedAt).children;
214
+ }
215
+ catch (err) {
216
+ throw mapOutlineError(err);
217
+ }
218
+ return c.json(ok({ deleted: true, cascaded: { children, relations, deltas } }));
219
+ });
220
+ // GET /api/v1/outline/:nodeId/path —— 根 → 节点路径
221
+ outlineRoutes.get("/:nodeId/path", (c) => {
222
+ const project = requireCurrentProject();
223
+ const nodeId = c.req.param("nodeId");
224
+ const tree = readOutlineFile(project.root);
225
+ let path;
226
+ try {
227
+ path = getOutlinePathIds(tree, nodeId);
228
+ }
229
+ catch {
230
+ throw new HttpError(404, "OUTLINE_NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
231
+ }
232
+ return c.json(ok({ nodeId, path }));
233
+ });
234
+ /** OutlineError → HttpError 映射(对照 endpoints.md 错误码,文件头注释表) */
235
+ export function mapOutlineError(err) {
236
+ if (err instanceof OutlineError) {
237
+ switch (err.code) {
238
+ case "NODE_NOT_FOUND":
239
+ throw new HttpError(404, "OUTLINE_NODE_NOT_FOUND", err.message);
240
+ case "PARENT_NOT_FOUND":
241
+ throw new HttpError(400, "OUTLINE_NODE_NOT_FOUND", err.message);
242
+ case "INVALID_HIERARCHY":
243
+ throw new HttpError(400, "VALIDATION_ERROR", err.message);
244
+ case "OUTLINE_ANCESTOR_DELETED":
245
+ throw new HttpError(409, "OUTLINE_ANCESTOR_DELETED", err.message);
246
+ }
247
+ }
248
+ throw err instanceof Error ? err : new HttpError(500, "INTERNAL_ERROR", "大纲操作失败");
249
+ }
250
+ //# sourceMappingURL=outline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outline.js","sourceRoot":"","sources":["../../src/routes/outline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { Hono } from "hono";
2
+ /** 设置创作根(index.ts startServer 调用;测试隔离用传 null 重置) */
3
+ export declare function setProjectRoot(root: string | null): void;
4
+ /** books/ 子目录名(书架模式:创作根/books/<书名>/) */
5
+ export declare const BOOKS_DIR_NAME = "books";
6
+ /**
7
+ * 规范化并校验项目路径(决策 17,create/open 通用):
8
+ *
9
+ * 1. **必须是绝对路径**:endpoints.md 契约要求绝对路径;path.resolve 对相对输入会基于
10
+ * process.cwd() 折叠,语义歧义(cwd 变化结果即变),显式拒绝
11
+ * 2. **path.resolve 规范化**:折叠 `..` 与重复分隔符(防 `..` 逃逸——规范化后即为最终操作目录,
12
+ * 不再拼接用户输入)
13
+ * 3. **realpath 一致性校验**:解析后的真实路径(符号链接展开)必须与规范化路径一致,否则拒绝——
14
+ * 校验语义(决策 17「防越权读写任意目录」):本应用单项目根模型下「允许范围」即规范化后的
15
+ * 路径本身,符号链接会把读写导向意外位置(链接指向任意目录即越权),故链接跳转一律拒绝
16
+ *
17
+ * 调用方约定:create 流程在调用前先 mkdirSync(目录可能尚不存在,realpath 需要目录存在);
18
+ * open 流程目录不存在时 realpath 抛错 → 同样 400。
19
+ *
20
+ * @param rawPath 用户提供的路径(绝对路径)
21
+ * @returns 规范化后的项目目录绝对路径
22
+ * @throws HttpError 400 INVALID_PROJECT_PATH
23
+ */
24
+ export declare function resolveProjectDir(rawPath: string): string;
25
+ /** 项目路由(挂载于 /api/v1/project,index.ts) */
26
+ export declare const projectRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
27
+ //# sourceMappingURL=project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/routes/project.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAwC1C,oDAAoD;AACpD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAExD;AAED,wCAAwC;AACxC,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkBzD;AAED,yCAAyC;AACzC,eAAO,MAAM,aAAa,4EAAa,CAAC"}