@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,145 @@
1
+ // 提案路由(S7.5):POST /api/v1/proposal/:proposalId/confirm | reject
2
+ //
3
+ // 契约来源:doc/api/endpoints.md「提案确认」(第 848-888 行)、决策 14(仅内存 / TTL / 快照重校验 /
4
+ // 项目绑定)、决策 19(大纲节点用节点级 updated_at 快照)。
5
+ // 错误映射(对照 endpoints.md 错误码):
6
+ // 提案不存在/已过期 → 404 PROPOSAL_NOT_FOUND(store.get 与 peek 均不可见)
7
+ // 提案所属项目 ≠ 当前项目 → 409 PROPOSAL_PROJECT_MISMATCH(决策 14 修订:get 返回 null 后
8
+ // peek 仍可见 ⇒ 跨项目误操作;防御性——正常流程切换项目时提案已被清空)
9
+ // 快照重校验失败 → 409 PROPOSAL_STALE(决策 14:引用对象不存在 **或** updated_at 不一致,
10
+ // 任一失败即 STALE;前端提示重新生成提案)
11
+ // 执行失败 → 500 INTERNAL_ERROR(契约未定义该错误码——执行失败属服务端状态异常/数据竞争,
12
+ // 前端按通用错误呈现)
13
+ //
14
+ // 消费方:defaultProposalStore(@whispering233/ai-editor-agent S7.4 提案仓,与调度同仓消费)+ executeProposal
15
+ // (@whispering233/ai-editor-tools S6.7 执行门面,按 proposal.type 映射执行函数,args 直接消费执行形态)。
16
+ //
17
+ // 提案移除语义(一次性消费,决策 14「提案是毫秒级交互对象、无跨会话恢复」落地):
18
+ // confirm/reject 任一动作到达即消费——确认成功、快照过期、执行失败、拒绝均为终态,
19
+ // 处理完立即 remove 出仓。残留只会造成两类问题:① 重复 confirm 同一无引用提案
20
+ // (如 propose_create_entity)会**重复执行**产生重复数据;② 已过期提案反复返回 409 无意义
21
+ // (前端按 PROPOSAL_STALE 引导重新生成,保留不提供任何重试价值)。故失败同样移除。
22
+ import { Hono } from "hono";
23
+ import { defaultProposalStore } from "@whispering233/ai-editor-agent";
24
+ import { executeProposal } from "@whispering233/ai-editor-tools";
25
+ import { findOutlineNode, getDeltaRow, getEntity, getRelation, readOutlineFile } from "@whispering233/ai-editor-db";
26
+ import { HttpError, ok } from "../middleware/error.js";
27
+ import { requireCurrentProject } from "../middleware/project.js";
28
+ /** 提案路由(挂载于 /api/v1/proposal,index.ts) */
29
+ export const proposalRoutes = new Hono();
30
+ /** 快照校验失败统一映射 409 PROPOSAL_STALE(决策 14:存在性 + updated_at 任一失败即 STALE) */
31
+ function throwProposalStale(ref) {
32
+ throw new HttpError(409, "PROPOSAL_STALE", `提案引用对象已变化或不存在: kind=${ref.kind} id=${ref.id}(请重新生成提案)`);
33
+ }
34
+ /**
35
+ * 单条引用快照重校验(决策 14/19):
36
+ * - kind=entity:getEntity 查 entities(已过滤软删,决策 12)——不存在/软删 → null → STALE;
37
+ * 存在则比对实体自身 updated_at(软删/还原亦刷新版本戳,决策 12 修订,语义统一)
38
+ * - kind=relation:getRelation(自身软删 + 端点软删联动过滤,决策 12 修订)——不可见 → STALE;
39
+ * 存在则比对关系自身 updated_at
40
+ * - kind=delta:getDeltaRow(记录级:自身软删视为不存在)——不存在 → STALE;比对自身 updated_at
41
+ * - kind=outline_node:findOutlineNode 查**调用方预读的树**(S2:references 含多条 outline_node
42
+ * 时不重复读文件;校验前同步读树一次,无间隙)——节点不存在或软删(决策 12)→ STALE;
43
+ * 存在则比对**节点级** updated_at(决策 19)
44
+ * - default(防静默,oracle S1):未知 kind 属上层调度/构造 bug(决策 14 契约仅 4 类引用),
45
+ * 跳过校验直接放行会让提案绕过快照重校验,按内部错误显式拒绝
46
+ * 三种 DB 引用不查大纲可见性联动(getRelation 例外——其 API 内置端点过滤):
47
+ * 提案引用的是记录本身,「存在性 + 自身 updated_at」即决策 14 契约字面语义。
48
+ */
49
+ function assertReferenceFresh(project, tree, ref) {
50
+ switch (ref.kind) {
51
+ case "entity": {
52
+ const row = getEntity(project.db, ref.id);
53
+ if (row === null || row.updated_at !== ref.updated_at)
54
+ throwProposalStale(ref);
55
+ return;
56
+ }
57
+ case "relation": {
58
+ const row = getRelation(project.db, ref.id, project.root);
59
+ if (row === null || row.updated_at !== ref.updated_at)
60
+ throwProposalStale(ref);
61
+ return;
62
+ }
63
+ case "delta": {
64
+ const row = getDeltaRow(project.db, ref.id);
65
+ if (row === null || row.updated_at !== ref.updated_at)
66
+ throwProposalStale(ref);
67
+ return;
68
+ }
69
+ case "outline_node": {
70
+ const node = findOutlineNode(tree, ref.id);
71
+ if (node === undefined || node.deleted === true || node.updated_at !== ref.updated_at) {
72
+ throwProposalStale(ref);
73
+ }
74
+ return;
75
+ }
76
+ default: {
77
+ // 防静默:never 断言提供编译期穷尽性——未来 ProposalReferenceKind 新增成员时
78
+ // 本行编译报错,强制补 case 而非静默漏校验(oracle S1)
79
+ const exhaustive = ref.kind;
80
+ throw new HttpError(500, "INTERNAL_ERROR", `未知提案引用类型: ${String(exhaustive)}`);
81
+ }
82
+ }
83
+ }
84
+ /**
85
+ * 取当前项目提案(confirm/reject 共用,决策 14 修订项目绑定):
86
+ * store.get(id, projectId) 校验项目归属;返回 null 时 peek 区分两种不可见——
87
+ * peek 可见 ⇒ 提案属于其他项目 → 409 PROPOSAL_PROJECT_MISMATCH(不误报 404);
88
+ * peek 不可见 ⇒ 不存在或已过期 → 404 PROPOSAL_NOT_FOUND。
89
+ * 注意 peek 走仓内惰性过期清理(决策 14 TTL),超期条目同样归入 404。
90
+ */
91
+ function resolveProposal(proposalId, projectId) {
92
+ const proposal = defaultProposalStore.get(proposalId, projectId);
93
+ if (proposal !== null)
94
+ return proposal;
95
+ if (defaultProposalStore.peek(proposalId) !== null) {
96
+ throw new HttpError(409, "PROPOSAL_PROJECT_MISMATCH", `提案所属项目与当前项目不一致: ${proposalId}`);
97
+ }
98
+ throw new HttpError(404, "PROPOSAL_NOT_FOUND", `提案不存在或已过期: ${proposalId}`);
99
+ }
100
+ // POST /api/v1/proposal/:proposalId/confirm —— 用户确认提案(endpoints.md 第 850-874 行)
101
+ // 流程:项目归属解析(404/409)→ 快照逐条重校验(409 PROPOSAL_STALE)→
102
+ // executeProposal({ db, outlineDir: project.root, projectId }, proposal) →
103
+ // 200 { confirmed: true, result }(ok 包裹,result 为执行结果如新创建的 entity id)。
104
+ // 提案移除在 finally 中(一次性消费,见文件头注释):校验通过 / 快照过期 / 执行失败均为终态,
105
+ // 确认动作即消费;404/409 MISMATCH 由 resolveProposal 在 try 之前抛出,不进入本块
106
+ // (跨项目提案不消费)。executeProposal 为同步调用(withTransaction 包复合写),
107
+ // 单线程下「执行后移除」与「执行前移除」无竞态差异。
108
+ proposalRoutes.post("/:proposalId/confirm", (c) => {
109
+ const project = requireCurrentProject();
110
+ const proposal = resolveProposal(c.req.param("proposalId"), project.config.id);
111
+ try {
112
+ // 快照重校验(决策 14:存在性 + updated_at 比对,任一失败 409 PROPOSAL_STALE);
113
+ // 树在校验前读一次(S2:多条 outline_node 引用不重复读文件,同步读取无间隙)
114
+ const tree = readOutlineFile(project.root);
115
+ for (const ref of proposal.references) {
116
+ assertReferenceFresh(project, tree, ref);
117
+ }
118
+ let result;
119
+ try {
120
+ result = executeProposal({ db: project.db, outlineDir: project.root, projectId: project.config.id }, proposal);
121
+ }
122
+ catch (err) {
123
+ // 执行失败(幂等冲突/数据竞争/防御校验抛错):契约未定义该错误码,按 500 内部错误呈现;
124
+ // 前端按通用错误引导重新生成,不保留重试(重试仍会失败)
125
+ throw new HttpError(500, "INTERNAL_ERROR", err instanceof Error ? err.message : "提案执行失败");
126
+ }
127
+ return c.json(ok({ confirmed: true, result }));
128
+ }
129
+ finally {
130
+ // 一次性消费(决策 14 瞬态交互对象):终态即移除——残留只会让重复 confirm
131
+ // 对无引用提案(propose_create_entity)产生重复执行,或对过期提案反复返回 409
132
+ defaultProposalStore.remove(proposal.proposal_id);
133
+ }
134
+ });
135
+ // POST /api/v1/proposal/:proposalId/reject —— 用户拒绝提案(endpoints.md 第 876-888 行)
136
+ // 项目归属校验与 confirm 同语义(决策 14 修订原文「confirm/reject 时校验与当前项目一致」——
137
+ // 拒绝同样是不可逆消费动作,跨项目拒绝会误伤他项目待确认提案,故 404/409 MISMATCH 同 confirm)。
138
+ // 校验通过 → 移除(拒绝即消费)→ 200 { rejected: true }。
139
+ proposalRoutes.post("/:proposalId/reject", (c) => {
140
+ const project = requireCurrentProject();
141
+ const proposal = resolveProposal(c.req.param("proposalId"), project.config.id);
142
+ defaultProposalStore.remove(proposal.proposal_id);
143
+ return c.json(ok({ rejected: true }));
144
+ });
145
+ //# sourceMappingURL=proposal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proposal.js","sourceRoot":"","sources":["../../src/routes/proposal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { Hono } from "hono";
2
+ /** 关系路由(挂载于 /api/v1/relation,index.ts) */
3
+ export declare const relationRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
4
+ /** RelationError → HttpError 映射(文件头注释表) */
5
+ export declare function mapRelationError(err: unknown): never;
6
+ //# sourceMappingURL=relation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relation.d.ts","sourceRoot":"","sources":["../../src/routes/relation.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAS5B,0CAA0C;AAC1C,eAAO,MAAM,cAAc,4EAAa,CAAC;AAyEzC,2CAA2C;AAC3C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,CAWpD"}
@@ -0,0 +1,87 @@
1
+ // 关系路由(S3.4):GET / 查询(k 跳遍历)、POST / 创建(判重)、DELETE /:id 物理删
2
+ //
3
+ // 契约来源:doc/api/endpoints.md 第 304-391 行(关系管理)、决策 2(通用关系表)、
4
+ // 决策 12 修订(可见性联动端点状态;手动删关系 = 物理删)。
5
+ // 错误映射(db RelationError → HttpError,对照 endpoints.md 错误码):
6
+ // RELATION_EXISTS → 409 RELATION_EXISTS(同三元组已存在,endpoints.md 第 372-374 行)
7
+ // ENDPOINT_NOT_FOUND → 400 VALIDATION_ERROR(端点不存在/软删是参数问题)
8
+ // INVALID_RELATION_TYPE → 400 VALIDATION_ERROR(白名单外——schema 层 enum 已拦截,防御分支)
9
+ // DELETE 0 影响行 → 404 RELATION_NOT_FOUND
10
+ import { Hono } from "hono";
11
+ import { createRelation, deleteRelation, listRelations, RelationError } from "@whispering233/ai-editor-db";
12
+ import { relationCreateReqSchema, relationQuerySchema, } from "@whispering233/ai-editor-shared/schemas";
13
+ import { HttpError, ok } from "../middleware/error.js";
14
+ import { requireCurrentProject } from "../middleware/project.js";
15
+ /** 关系路由(挂载于 /api/v1/relation,index.ts) */
16
+ export const relationRoutes = new Hono();
17
+ // GET /api/v1/relation —— 查询(depth 必填 1|2|3;过滤条件组合;响应 camelCase)
18
+ relationRoutes.get("/", (c) => {
19
+ const project = requireCurrentProject();
20
+ const parsed = relationQuerySchema.safeParse(c.req.query());
21
+ if (!parsed.success) {
22
+ throw parsed.error; // → 400 VALIDATION_ERROR(depth 缺失/非法等,含 fields)
23
+ }
24
+ const { depth, source_type, source_id, target_type, target_id, relation_type } = parsed.data;
25
+ // snake_case(请求约定)→ camelCase(db 层接口);undefined 字段透传(db 可选过滤)。
26
+ // depth 经 schema min(1).max(3) 校验,收窄断言到 db 的 1|2|3 字面量联合
27
+ const result = listRelations(project.db, { sourceType: source_type, sourceId: source_id, targetType: target_type, targetId: target_id, relationType: relation_type }, depth, project.root);
28
+ return c.json(ok(result));
29
+ });
30
+ // POST /api/v1/relation —— 创建(判重 409;端点存在性校验;201)
31
+ relationRoutes.post("/", async (c) => {
32
+ const project = requireCurrentProject();
33
+ const raw = await c.req.json().catch(() => null);
34
+ const parsed = relationCreateReqSchema.safeParse(raw);
35
+ if (!parsed.success) {
36
+ throw parsed.error; // → 400 VALIDATION_ERROR(含 relation_type enum 白名单、字段校验)
37
+ }
38
+ const { source_type, source_id, target_type, target_id, relation_type, metadata } = parsed.data;
39
+ let row;
40
+ try {
41
+ row = createRelation(project.db, {
42
+ sourceType: source_type,
43
+ sourceId: source_id,
44
+ targetType: target_type,
45
+ targetId: target_id,
46
+ relationType: relation_type,
47
+ ...(metadata !== undefined ? { metadata } : {}),
48
+ }, project.root);
49
+ }
50
+ catch (err) {
51
+ throw mapRelationError(err);
52
+ }
53
+ return c.json(ok({
54
+ id: row.id,
55
+ relation: {
56
+ sourceType: row.source_type,
57
+ sourceId: row.source_id,
58
+ targetType: row.target_type,
59
+ targetId: row.target_id,
60
+ relationType: row.relation_type,
61
+ },
62
+ }), 201);
63
+ });
64
+ // DELETE /api/v1/relation/:id —— 物理删除(决策 12 修订:不进回收站)
65
+ relationRoutes.delete("/:id", (c) => {
66
+ const project = requireCurrentProject();
67
+ const id = c.req.param("id");
68
+ const changes = deleteRelation(project.db, id);
69
+ if (changes === 0) {
70
+ throw new HttpError(404, "RELATION_NOT_FOUND", `关系不存在: ${id}`);
71
+ }
72
+ return c.json(ok({ deleted: true }));
73
+ });
74
+ /** RelationError → HttpError 映射(文件头注释表) */
75
+ export function mapRelationError(err) {
76
+ if (err instanceof RelationError) {
77
+ switch (err.code) {
78
+ case "RELATION_EXISTS":
79
+ throw new HttpError(409, "RELATION_EXISTS", err.message);
80
+ case "ENDPOINT_NOT_FOUND":
81
+ case "INVALID_RELATION_TYPE":
82
+ throw new HttpError(400, "VALIDATION_ERROR", err.message);
83
+ }
84
+ }
85
+ throw err instanceof Error ? err : new HttpError(500, "INTERNAL_ERROR", "关系操作失败");
86
+ }
87
+ //# sourceMappingURL=relation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relation.js","sourceRoot":"","sources":["../../src/routes/relation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { Hono } from "hono";
2
+ /** 默认模型名(endpoints.md settings 端点) */
3
+ export declare const DEFAULT_MODEL = "deepseek-v4-flash";
4
+ /** DeepSeek key 环境变量(决策 17:环境变量优先于用户级配置) */
5
+ export declare const DEEPSEEK_API_KEY_ENV = "DEEPSEEK_API_KEY";
6
+ /** 用户级配置文件相对 HOME 的路径(决策 17:不入项目文件) */
7
+ export declare const USER_CONFIG_RELATIVE_PATH: string;
8
+ /** 用户级配置文件结构(~/.ai-editor/config.json) */
9
+ export interface UserConfigFile {
10
+ model?: string;
11
+ api_key?: string;
12
+ }
13
+ /** 用户级配置文件绝对路径(os.homedir() 读 $HOME,测试设 HOME 即可隔离) */
14
+ export declare function userConfigPath(): string;
15
+ /**
16
+ * 读取用户级配置;文件不存在或 JSON 损坏 → 返回空配置(默认值语义,不抛错)
17
+ */
18
+ export declare function getUserConfig(): UserConfigFile;
19
+ /**
20
+ * 写入用户级配置(合并 partial;显式 undefined 字段不覆盖已有值);
21
+ * api_key 为空字符串 = 清除 key 字段
22
+ * 原子写:复用 @whispering233/ai-editor-db 的 writeJsonAtomic(决策 11 精神完整版:临时文件 wx 独占 +
23
+ * 文件 fsync + rename + 目录 fsync)。目标在 $HOME/.ai-editor/ 下,目录 fsync 在部分
24
+ * 平台/文件系统受限时由 db 实现静默忽略(主链路已保证文件内容完整)
25
+ */
26
+ export declare function saveUserConfig(partial: Partial<UserConfigFile>): UserConfigFile;
27
+ /**
28
+ * 计算有效 key 与掩码:环境变量 > config.json(决策 17 来源优先级)
29
+ * 两者都没有 → null(apiKeySet=false,无掩码)
30
+ */
31
+ export declare function effectiveApiKey(): {
32
+ key: string | null;
33
+ masked: string | undefined;
34
+ };
35
+ /** 设置路由(挂载于 /api/v1/settings) */
36
+ export declare const settingsRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
37
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/routes/settings.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAM5B,sCAAsC;AACtC,eAAO,MAAM,aAAa,sBAAsB,CAAC;AAEjD,4CAA4C;AAC5C,eAAO,MAAM,oBAAoB,qBAAqB,CAAC;AAEvD,uCAAuC;AACvC,eAAO,MAAM,yBAAyB,QAAoC,CAAC;AAE3E,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,sDAAsD;AACtD,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,cAAc,CAO9C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAW/E;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI;IAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAKpF;AAED,iCAAiC;AACjC,eAAO,MAAM,cAAc,4EAAa,CAAC"}
@@ -0,0 +1,91 @@
1
+ // 设置路由(S1.3):GET/PUT /api/v1/settings/llm
2
+ // 契约来源:doc/api/endpoints.md 第 858-890 行、doc/design/decisions.md 决策 17
3
+ // 关键约束(决策 17):DeepSeek key 只走环境变量 DEEPSEEK_API_KEY 或用户级配置
4
+ // ~/.ai-editor/config.json(HOME 可覆盖——测试隔离依赖),**绝不写入项目文件**
5
+ import { mkdirSync, readFileSync } from "node:fs";
6
+ import { homedir } from "node:os";
7
+ import { dirname, join } from "node:path";
8
+ import { Hono } from "hono";
9
+ import { maskApiKey } from "@whispering233/ai-editor-shared";
10
+ import { writeJsonAtomic } from "@whispering233/ai-editor-db";
11
+ import { settingsLlmGetResSchema, settingsLlmPutReqSchema } from "@whispering233/ai-editor-shared/schemas";
12
+ import { ok } from "../middleware/error.js";
13
+ /** 默认模型名(endpoints.md settings 端点) */
14
+ export const DEFAULT_MODEL = "deepseek-v4-flash";
15
+ /** DeepSeek key 环境变量(决策 17:环境变量优先于用户级配置) */
16
+ export const DEEPSEEK_API_KEY_ENV = "DEEPSEEK_API_KEY";
17
+ /** 用户级配置文件相对 HOME 的路径(决策 17:不入项目文件) */
18
+ export const USER_CONFIG_RELATIVE_PATH = join(".ai-editor", "config.json");
19
+ /** 用户级配置文件绝对路径(os.homedir() 读 $HOME,测试设 HOME 即可隔离) */
20
+ export function userConfigPath() {
21
+ return join(homedir(), USER_CONFIG_RELATIVE_PATH);
22
+ }
23
+ /**
24
+ * 读取用户级配置;文件不存在或 JSON 损坏 → 返回空配置(默认值语义,不抛错)
25
+ */
26
+ export function getUserConfig() {
27
+ try {
28
+ const raw = readFileSync(userConfigPath(), "utf8");
29
+ return JSON.parse(raw);
30
+ }
31
+ catch {
32
+ return {};
33
+ }
34
+ }
35
+ /**
36
+ * 写入用户级配置(合并 partial;显式 undefined 字段不覆盖已有值);
37
+ * api_key 为空字符串 = 清除 key 字段
38
+ * 原子写:复用 @whispering233/ai-editor-db 的 writeJsonAtomic(决策 11 精神完整版:临时文件 wx 独占 +
39
+ * 文件 fsync + rename + 目录 fsync)。目标在 $HOME/.ai-editor/ 下,目录 fsync 在部分
40
+ * 平台/文件系统受限时由 db 实现静默忽略(主链路已保证文件内容完整)
41
+ */
42
+ export function saveUserConfig(partial) {
43
+ const file = userConfigPath();
44
+ const next = { ...getUserConfig() };
45
+ if (partial.model !== undefined)
46
+ next.model = partial.model;
47
+ if (partial.api_key !== undefined)
48
+ next.api_key = partial.api_key;
49
+ if (next.api_key === "") {
50
+ delete next.api_key; // 空字符串 = 清除已保存 key(endpoints.md PUT 语义)
51
+ }
52
+ mkdirSync(dirname(file), { recursive: true }); // writeJsonAtomic 不负责建目录
53
+ writeJsonAtomic(file, next);
54
+ return next;
55
+ }
56
+ /**
57
+ * 计算有效 key 与掩码:环境变量 > config.json(决策 17 来源优先级)
58
+ * 两者都没有 → null(apiKeySet=false,无掩码)
59
+ */
60
+ export function effectiveApiKey() {
61
+ const envKey = process.env[DEEPSEEK_API_KEY_ENV] ?? "";
62
+ const fileKey = getUserConfig().api_key ?? "";
63
+ const key = envKey || fileKey || null; // 环境变量优先
64
+ return key === null ? { key, masked: undefined } : { key, masked: maskApiKey(key) };
65
+ }
66
+ /** 设置路由(挂载于 /api/v1/settings) */
67
+ export const settingsRoutes = new Hono();
68
+ // GET /api/v1/settings/llm —— 读取 LLM 配置(key 不回传明文,仅掩码;决策 17)
69
+ settingsRoutes.get("/llm", (c) => {
70
+ const { key, masked } = effectiveApiKey();
71
+ const payload = settingsLlmGetResSchema.parse({
72
+ model: getUserConfig().model ?? DEFAULT_MODEL,
73
+ apiKeySet: key !== null,
74
+ ...(masked ? { apiKeyMasked: masked } : {}),
75
+ });
76
+ return c.json(ok(payload));
77
+ });
78
+ // PUT /api/v1/settings/llm —— 更新 LLM 配置(写入 ~/.ai-editor/config.json,绝不入项目文件,决策 17)
79
+ settingsRoutes.put("/llm", async (c) => {
80
+ const raw = await c.req.json().catch(() => null); // 空 body / 非法 JSON → 校验失败
81
+ const parsed = settingsLlmPutReqSchema.safeParse(raw);
82
+ if (!parsed.success) {
83
+ throw parsed.error; // → app.onError → 400 VALIDATION_ERROR(含 fields)
84
+ }
85
+ saveUserConfig({
86
+ model: parsed.data.model,
87
+ api_key: parsed.data.api_key,
88
+ });
89
+ return c.json(ok({ saved: true })); // settingsLlmPutResSchema 形状
90
+ });
91
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/routes/settings.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,sEAAsE;AACtE,0DAA0D;AAC1D,4DAA4D;AAC5D,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAC3G,OAAO,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;AAE5C,sCAAsC;AACtC,MAAM,CAAC,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAEjD,4CAA4C;AAC5C,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;AAEvD,uCAAuC;AACvC,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;AAQ3E,sDAAsD;AACtD,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,yBAAyB,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,cAAc,EAAE,EAAE,MAAM,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAmB,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgC;IAC7D,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAmB,EAAE,GAAG,aAAa,EAAE,EAAE,CAAC;IACpD,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;QAAE,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5D,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS;QAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAClE,IAAI,IAAI,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,wCAAwC;IAC/D,CAAC;IACD,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,yBAAyB;IACxE,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,EAAE,CAAC;IACvD,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC;IAC9C,MAAM,GAAG,GAAG,MAAM,IAAI,OAAO,IAAI,IAAI,CAAC,CAAC,SAAS;IAChD,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;AACtF,CAAC;AAED,iCAAiC;AACjC,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC;AAEzC,6DAA6D;AAC7D,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE;IAC/B,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,uBAAuB,CAAC,KAAK,CAAC;QAC5C,KAAK,EAAE,aAAa,EAAE,CAAC,KAAK,IAAI,aAAa;QAC7C,SAAS,EAAE,GAAG,KAAK,IAAI;QACvB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5C,CAAC,CAAC;IACH,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEH,mFAAmF;AACnF,cAAc,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IACrC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,0BAA0B;IAC5E,MAAM,MAAM,GAAG,uBAAuB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,iDAAiD;IACvE,CAAC;IACD,cAAc,CAAC;QACb,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK;QACxB,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO;KAC7B,CAAC,CAAC;IACH,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,6BAA6B;AACnE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Hono } from "hono";
2
+ /** 回收站路由(挂载于 /api/v1/trash,index.ts) */
3
+ export declare const trashRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
4
+ //# sourceMappingURL=trash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trash.d.ts","sourceRoot":"","sources":["../../src/routes/trash.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAU5B,wCAAwC;AACxC,eAAO,MAAM,WAAW,4EAAa,CAAC"}
@@ -0,0 +1,135 @@
1
+ // 回收站路由(S2.2 大纲侧 + S4.3 实体侧):GET /trash、restore 实体/节点、purge 实体/节点
2
+ //
3
+ // 契约来源:doc/api/endpoints.md 第 660-736 行、决策 12(软删/级联还原/物理清除)。
4
+ // 组织说明:大纲侧(S2.2)与实体侧(S4.3)同文件;relation/delta 级联 helper 在 db 包
5
+ // queries/trash.ts(S4.1 下沉);实体 restore/purge 为纯 DB 操作(无 outline.json 侧),
6
+ // 写序天然满足「先 DB 后 JSON」(决策 16)。
7
+ // restore 祖先链校验(大纲侧):存在软删祖先 → 409 OUTLINE_ANCESTOR_DELETED(决策 12 修订)。
8
+ // purge 语义拦截(两侧同构):未软删对象拒绝 purge——仅用于回收站清理。
9
+ import { Hono } from "hono";
10
+ import { cascadePurge, cascadeRestore, getOutlinePathIds, readOutlineFile } from "@whispering233/ai-editor-db";
11
+ import { listDeletedEntities, listDeletedNodes, purgeEntity, purgeOutlineNode, restoreEntity, restoreOutlineNode } from "@whispering233/ai-editor-db";
12
+ import { nowIso } from "@whispering233/ai-editor-db";
13
+ import { HttpError, ok } from "../middleware/error.js";
14
+ import { requireCurrentProject } from "../middleware/project.js";
15
+ import { collectSubtreeIds, mapOutlineError } from "./outline.js";
16
+ import { parseTypeParam } from "./entity.js";
17
+ /** 回收站路由(挂载于 /api/v1/trash,index.ts) */
18
+ export const trashRoutes = new Hono();
19
+ // GET /api/v1/trash —— 回收站列表(大纲侧 + 实体侧,endpoints.md 第 664-674 行)
20
+ trashRoutes.get("/", (c) => {
21
+ const project = requireCurrentProject();
22
+ const nodes = listDeletedNodes(project.root).map((n) => ({
23
+ id: n.id,
24
+ type: n.type,
25
+ title: n.title,
26
+ deletedAt: n.deleted_at,
27
+ }));
28
+ // 实体侧:db 层 listDeletedEntities 已返回 camelCase deletedAt(S4.1),直接透传
29
+ return c.json(ok({ entities: listDeletedEntities(project.db), nodes }));
30
+ });
31
+ // POST /api/v1/trash/outline/:nodeId/restore —— 还原(级联还原子树 + 关联关系与 Delta)
32
+ // 写序(决策 16):**先 DB 后 JSON**——先级联还原 relation/delta,再原子写 outline.json。
33
+ // 祖先链预校验必须在 DB 级联**之前**:409 拒绝的请求不产生任何副作用
34
+ // (restoreOutlineNode 内部的校验在 JSON 写前,但 DB 已先行——此处路由层先拦截)
35
+ trashRoutes.post("/outline/:nodeId/restore", (c) => {
36
+ const project = requireCurrentProject();
37
+ const nodeId = c.req.param("nodeId");
38
+ const tree = readOutlineFile(project.root);
39
+ const subtreeIds = collectSubtreeIds(tree, nodeId); // 404 语义
40
+ // 0. 祖先链预校验(决策 12 修订):存在软删祖先 → 409,且不产生任何 DB 副作用
41
+ const path = getOutlinePathIds(tree, nodeId);
42
+ for (const ancestorId of path.slice(1, -1)) {
43
+ const ancestor = findNodeById(tree, ancestorId);
44
+ if (ancestor?.deleted === true) {
45
+ throw new HttpError(409, "OUTLINE_ANCESTOR_DELETED", `存在软删祖先 ${ancestorId},请先还原祖先再还原本节点`);
46
+ }
47
+ }
48
+ // 1. DB 级联还原(决策 12 修订:全部还原,不因另一端仍软删而跳过)
49
+ const { relations, deltas } = cascadeRestore(project.db, subtreeIds);
50
+ // 2. JSON 原子写(还原节点 + 递归子树;db 层校验为双保险)
51
+ let restoredChildren;
52
+ try {
53
+ restoredChildren = restoreOutlineNode(project.root, nodeId, nowIso()).children;
54
+ }
55
+ catch (err) {
56
+ throw mapOutlineError(err); // OUTLINE_ANCESTOR_DELETED → 409(预校验后不可达,防御)
57
+ }
58
+ return c.json(ok({ restored: true, restoredChildren, restoredRelations: relations, restoredDeltas: deltas }));
59
+ });
60
+ // DELETE /api/v1/trash/outline/:nodeId —— 物理清除(purge,不可恢复;仅用于回收站清理)
61
+ // 写序(决策 16):**先 DB 后 JSON**——先物理清除 relation/delta,再原子写 outline.json
62
+ trashRoutes.delete("/outline/:nodeId", (c) => {
63
+ const project = requireCurrentProject();
64
+ const nodeId = c.req.param("nodeId");
65
+ const tree = readOutlineFile(project.root);
66
+ const subtreeIds = collectSubtreeIds(tree, nodeId); // 404 语义
67
+ // 回收站语义拦截(oracle 审核建议):purge 仅用于回收站清理——节点未软删拒绝,
68
+ // 防误调把未进回收站的数据物理清掉
69
+ if (!isSoftDeleted(tree, nodeId)) {
70
+ throw new HttpError(400, "VALIDATION_ERROR", `节点未软删,purge 仅用于回收站清理: ${nodeId}`);
71
+ }
72
+ // 1. DB 物理清除关联关系与 Delta
73
+ cascadePurge(project.db, subtreeIds);
74
+ // 2. JSON 原子写(移除整棵子树;物理删除的极端崩溃窗口:DB 已清、JSON 未清,
75
+ // 不在 S4.2 一致性校验范围——其只兜软删联动(决策 16 修订),同步操作中途
76
+ // 崩溃概率极低,注释明示)
77
+ purgeOutlineNode(project.root, nodeId);
78
+ return c.json(ok({ purged: true }));
79
+ });
80
+ // POST /api/v1/trash/entity/:type/:id/restore —— 还原软删实体(endpoints.md 第 676-692 行,决策 12 修订)
81
+ // 纯 DB 操作:自身 deleted_at 置 NULL + updated_at 刷新(S4.1 db 层),级联还原关联关系与 Delta
82
+ // (全部还原,不因另一端仍软删而跳过——可见性由查询层兜底,端点还原后自动可见)。
83
+ // db 层幂等:实体不存在、类型不匹配或未软删 → null → 404(endpoints.md 第 689 行)。
84
+ trashRoutes.post("/entity/:type/:id/restore", (c) => {
85
+ const project = requireCurrentProject();
86
+ const type = parseTypeParam(c.req.param("type"));
87
+ const id = c.req.param("id");
88
+ const result = restoreEntity(project.db, type, id);
89
+ if (result === null) {
90
+ throw new HttpError(404, "ENTITY_NOT_FOUND", `实体不存在或未软删: ${id}`);
91
+ }
92
+ return c.json(ok({ restored: true, restoredRelations: result.restoredRelations, restoredDeltas: result.restoredDeltas }));
93
+ });
94
+ // DELETE /api/v1/trash/entity/:type/:id —— 物理清除(purge,endpoints.md 第 713-724 行;不可恢复,
95
+ // 仅用于回收站清理)。拦截顺序与 outline purge 同构:先 404(不存在)后 400(未软删)——
96
+ // 未软删对象拒绝,防误调把未进回收站的数据物理清掉(oracle 审核建议)。
97
+ trashRoutes.delete("/entity/:type/:id", (c) => {
98
+ const project = requireCurrentProject();
99
+ const type = parseTypeParam(c.req.param("type"));
100
+ const id = c.req.param("id");
101
+ // 存在性 + 软删状态检查(原始 SQL:路由守卫,S4.1 purgeEntity 明确不校验「必须已软删」,
102
+ // outline purge 的 isSoftDeleted 同构——db 层只保证原子物理清除)
103
+ const row = project.db
104
+ .prepare("SELECT deleted_at FROM entities WHERE id = ? AND type = ?")
105
+ .get(id, type);
106
+ if (row === undefined) {
107
+ throw new HttpError(404, "ENTITY_NOT_FOUND", `实体不存在: ${id}`);
108
+ }
109
+ if (row.deleted_at === null) {
110
+ throw new HttpError(400, "VALIDATION_ERROR", `实体未软删,purge 仅用于回收站清理: ${id}`);
111
+ }
112
+ if (purgeEntity(project.db, type, id) === null) {
113
+ throw new HttpError(404, "ENTITY_NOT_FOUND", `实体不存在: ${id}`); // 防御(上一步已确认存在)
114
+ }
115
+ return c.json(ok({ purged: true }));
116
+ });
117
+ /** 从树中按 id 找节点(restore 祖先预校验 / purge 拦截用;不存在由 collectSubtreeIds 先抛 404) */
118
+ function findNodeById(tree, nodeId) {
119
+ const visit = (nodes) => {
120
+ for (const n of nodes) {
121
+ if (n.id === nodeId)
122
+ return n;
123
+ const found = visit(n.children ?? []);
124
+ if (found !== undefined)
125
+ return found;
126
+ }
127
+ return undefined;
128
+ };
129
+ return visit(tree.children);
130
+ }
131
+ /** 判断节点是否已软删(purge 拦截校验用) */
132
+ function isSoftDeleted(tree, nodeId) {
133
+ return findNodeById(tree, nodeId)?.deleted === true;
134
+ }
135
+ //# sourceMappingURL=trash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trash.js","sourceRoot":"","sources":["../../src/routes/trash.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@whispering233/ai-editor-server",
3
+ "version": "0.0.1",
4
+ "description": "Hono API 服务端(单进程:API + SPA 静态托管,决策 8)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "ai-editor": "dist/index.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "client-dist"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "dev": "NODE_ENV=development tsx watch src/index.ts",
26
+ "build": "tsc -p tsconfig.json",
27
+ "test": "vitest run",
28
+ "lint": "eslint .",
29
+ "prepack": "node ../../scripts/copy-client-dist.mjs && node ../../scripts/prepare-package-for-publish.mjs",
30
+ "postpack": "node ../../scripts/restore-package-json.mjs"
31
+ },
32
+ "dependencies": {
33
+ "@whispering233/ai-editor-agent": "0.0.1",
34
+ "@whispering233/ai-editor-db": "0.0.1",
35
+ "@whispering233/ai-editor-llm": "0.0.1",
36
+ "@whispering233/ai-editor-shared": "0.0.1",
37
+ "@whispering233/ai-editor-tools": "0.0.1",
38
+ "@hono/node-server": "^2.0.12",
39
+ "fflate": "^0.8.3",
40
+ "hono": "^4.7.0",
41
+ "zod": "^4.4.3"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^24.13.3",
45
+ "tsx": "^4.23.1",
46
+ "typescript": "^5.8.0"
47
+ }
48
+ }