@whispering233/ai-editor-db 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connection.d.ts +24 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +39 -0
- package/dist/connection.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/index.d.ts +10 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +34 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/queries/atomic.d.ts +15 -0
- package/dist/queries/atomic.d.ts.map +1 -0
- package/dist/queries/atomic.js +44 -0
- package/dist/queries/atomic.js.map +1 -0
- package/dist/queries/chat.d.ts +74 -0
- package/dist/queries/chat.d.ts.map +1 -0
- package/dist/queries/chat.js +188 -0
- package/dist/queries/chat.js.map +1 -0
- package/dist/queries/compute-state.d.ts +32 -0
- package/dist/queries/compute-state.d.ts.map +1 -0
- package/dist/queries/compute-state.js +135 -0
- package/dist/queries/compute-state.js.map +1 -0
- package/dist/queries/delta.d.ts +76 -0
- package/dist/queries/delta.d.ts.map +1 -0
- package/dist/queries/delta.js +287 -0
- package/dist/queries/delta.js.map +1 -0
- package/dist/queries/entity.d.ts +110 -0
- package/dist/queries/entity.d.ts.map +1 -0
- package/dist/queries/entity.js +285 -0
- package/dist/queries/entity.js.map +1 -0
- package/dist/queries/migration.d.ts +153 -0
- package/dist/queries/migration.d.ts.map +1 -0
- package/dist/queries/migration.js +260 -0
- package/dist/queries/migration.js.map +1 -0
- package/dist/queries/outline-ops.d.ts +126 -0
- package/dist/queries/outline-ops.d.ts.map +1 -0
- package/dist/queries/outline-ops.js +346 -0
- package/dist/queries/outline-ops.js.map +1 -0
- package/dist/queries/outline.d.ts +48 -0
- package/dist/queries/outline.d.ts.map +1 -0
- package/dist/queries/outline.js +134 -0
- package/dist/queries/outline.js.map +1 -0
- package/dist/queries/project.d.ts +21 -0
- package/dist/queries/project.d.ts.map +1 -0
- package/dist/queries/project.js +43 -0
- package/dist/queries/project.js.map +1 -0
- package/dist/queries/relation.d.ts +83 -0
- package/dist/queries/relation.d.ts.map +1 -0
- package/dist/queries/relation.js +418 -0
- package/dist/queries/relation.js.map +1 -0
- package/dist/queries/trash.d.ts +65 -0
- package/dist/queries/trash.d.ts.map +1 -0
- package/dist/queries/trash.js +121 -0
- package/dist/queries/trash.js.map +1 -0
- package/dist/schema.d.ts +29 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +102 -0
- package/dist/schema.js.map +1 -0
- package/dist/storage/atomic.d.ts +32 -0
- package/dist/storage/atomic.d.ts.map +1 -0
- package/dist/storage/atomic.js +112 -0
- package/dist/storage/atomic.js.map +1 -0
- package/dist/storage/outline.d.ts +68 -0
- package/dist/storage/outline.d.ts.map +1 -0
- package/dist/storage/outline.js +154 -0
- package/dist/storage/outline.js.map +1 -0
- package/dist/storage/project.d.ts +24 -0
- package/dist/storage/project.d.ts.map +1 -0
- package/dist/storage/project.js +55 -0
- package/dist/storage/project.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// @whispering233/ai-editor-db 大纲树操作(S2.1):创建/更新/移动/软删/还原/物理删除/回收站列表/章节序
|
|
2
|
+
//
|
|
3
|
+
// 单一事实来源:
|
|
4
|
+
// - doc/design/decisions.md 决策 19(严格三层:volume 挂 root、chapter 挂 volume 或 root、
|
|
5
|
+
// scene 必须挂 chapter;创建必须显式 parent_id;无游离节点;节点 updated_at 版本戳)、
|
|
6
|
+
// 决策 12(软删 deleted/deleted_at、级联软删子树、restore 祖先链校验 409)、
|
|
7
|
+
// 决策 21(章节序:root → 卷 → 章先序遍历,全局章序号跨卷连续,scene 归入所属章)
|
|
8
|
+
// - doc/api/endpoints.md 第 550-736 行(大纲操作与回收站端点语义)
|
|
9
|
+
// - doc/database/schema.md 第 123-155 行(outline.json 契约)
|
|
10
|
+
//
|
|
11
|
+
// 风格:本模块为「读树 → 操作 → 原子写回」一体化(dir 语义),内部复用 storage/outline.ts
|
|
12
|
+
// 的内存操作(readOutlineFile/writeOutlineFile/findOutlineNode/touch/update/getOutlinePathIds);
|
|
13
|
+
// relations/deltas 的级联软删由上层(S2.2 路由层)组合 relation/delta 模块完成——本模块
|
|
14
|
+
// 只处理 outline.json 侧(任务卡边界)。
|
|
15
|
+
//
|
|
16
|
+
// 错误约定:抛 OutlineError(带 code),server 层 catch 映射 HttpError:
|
|
17
|
+
// NODE_NOT_FOUND → 404 OUTLINE_NODE_NOT_FOUND
|
|
18
|
+
// PARENT_NOT_FOUND → 400 OUTLINE_NODE_NOT_FOUND(父不存在是请求参数错误——
|
|
19
|
+
// S2.2 实际映射为 400,2026-08 审核同步注释)
|
|
20
|
+
// INVALID_HIERARCHY → 400 VALIDATION_ERROR(严格三层违反,决策 19)
|
|
21
|
+
// OUTLINE_ANCESTOR_DELETED → 409 OUTLINE_ANCESTOR_DELETED(决策 12 修订)
|
|
22
|
+
import { generateOutlineNodeId } from "@whispering233/ai-editor-shared";
|
|
23
|
+
import { findOutlineNode, getOutlinePathIds, readOutlineFile, writeOutlineFile, } from "../storage/outline.js";
|
|
24
|
+
/** 大纲操作错误:带 code,供上层精确映射 HTTP 语义(替代 message 字符串匹配) */
|
|
25
|
+
export class OutlineError extends Error {
|
|
26
|
+
code;
|
|
27
|
+
constructor(code, message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "OutlineError";
|
|
30
|
+
this.code = code;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** 取节点 children 的可变引用(卷→章、章→场景;场景返回 undefined) */
|
|
34
|
+
function childrenOf(node) {
|
|
35
|
+
return node.children;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 严格三层约束校验(决策 19):childType 能否挂 parentType 下。
|
|
39
|
+
* volume → root;chapter → volume 或 root;scene → 必须 chapter。
|
|
40
|
+
*/
|
|
41
|
+
export function assertCanHold(parentType, childType) {
|
|
42
|
+
const ok = (parentType === "root" && (childType === "volume" || childType === "chapter")) ||
|
|
43
|
+
(parentType === "volume" && childType === "chapter") ||
|
|
44
|
+
(parentType === "chapter" && childType === "scene");
|
|
45
|
+
if (!ok) {
|
|
46
|
+
throw new OutlineError("INVALID_HIERARCHY", `层级非法(决策 19 严格三层): ${childType} 不能挂在 ${parentType} 下`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 创建大纲节点(POST /api/v1/outline,endpoints.md 第 550-577 行):
|
|
51
|
+
* 读树 → 校验父存在 + 三层约束 → 生成 id(shared generateOutlineNodeId:vol-/ch-/sc- 前缀)
|
|
52
|
+
* → 插入父 children 尾部 → **父节点 updated_at 统一更新**(决策 19)→ 原子写回。
|
|
53
|
+
*
|
|
54
|
+
* @param updatedAt ISO 8601 由调用方(应用层)传入,模块不生成时间
|
|
55
|
+
* @param data 节点结构化信息(决策 23,可选;校验由路由层按层级 schema 执行,本模块仅透传存储)
|
|
56
|
+
* @returns 新建节点(含 id/type/title/updated_at/data)
|
|
57
|
+
* @throws OutlineError PARENT_NOT_FOUND(父不存在)/ INVALID_HIERARCHY(三层违反)
|
|
58
|
+
*/
|
|
59
|
+
export function createOutlineNode(dir, input) {
|
|
60
|
+
const tree = readOutlineFile(dir);
|
|
61
|
+
const parent = input.parentId === "root" ? tree : findOutlineNode(tree, input.parentId);
|
|
62
|
+
if (parent === undefined) {
|
|
63
|
+
throw new OutlineError("PARENT_NOT_FOUND", `父节点不存在: ${input.parentId}`);
|
|
64
|
+
}
|
|
65
|
+
assertCanHold(parent.type, input.type);
|
|
66
|
+
const node = {
|
|
67
|
+
id: generateOutlineNodeId(input.type),
|
|
68
|
+
type: input.type,
|
|
69
|
+
title: input.title,
|
|
70
|
+
updated_at: input.updatedAt,
|
|
71
|
+
...(input.summary !== undefined ? { summary: input.summary } : {}),
|
|
72
|
+
...(input.data !== undefined ? { data: input.data } : {}),
|
|
73
|
+
// 非 scene 节点必须初始化 children 数组(volume→chapter[]、chapter→scene[]),
|
|
74
|
+
// 否则 childrenOf 视为叶子、后续挂载失败
|
|
75
|
+
...(input.type !== "scene" ? { children: [] } : {}),
|
|
76
|
+
};
|
|
77
|
+
const kids = childrenOf(parent);
|
|
78
|
+
if (kids === undefined) {
|
|
79
|
+
throw new OutlineError("INVALID_HIERARCHY", `父节点是叶子(scene),无法挂子节点: ${input.parentId}`);
|
|
80
|
+
}
|
|
81
|
+
kids.push(node);
|
|
82
|
+
// 父节点版本戳(决策 19:children 变更由服务端原子写时统一更新)。
|
|
83
|
+
// root 是树根非节点(schema.md 顶层契约仅 id/type/schema_version/children),
|
|
84
|
+
// 不写 updated_at——版本戳只适用于节点(oracle 审核修复)
|
|
85
|
+
if (parent !== tree) {
|
|
86
|
+
parent.updated_at = input.updatedAt;
|
|
87
|
+
}
|
|
88
|
+
writeOutlineFile(dir, tree);
|
|
89
|
+
return node;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 更新大纲节点信息(PUT /api/v1/outline/:nodeId,endpoints.md 第 579-597 行):
|
|
93
|
+
* title/summary 更新 + 节点 updated_at 统一更新(决策 19),原子写回;
|
|
94
|
+
* data **浅合并**(决策 23:未传字段保留,与实体 updateEntity 的 data 浅合并同语义)。
|
|
95
|
+
* @throws OutlineError NODE_NOT_FOUND
|
|
96
|
+
*/
|
|
97
|
+
export function updateOutlineNodeInfo(dir, nodeId, patch, updatedAt) {
|
|
98
|
+
const tree = readOutlineFile(dir);
|
|
99
|
+
const node = findOutlineNode(tree, nodeId);
|
|
100
|
+
if (node === undefined) {
|
|
101
|
+
throw new OutlineError("NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
|
|
102
|
+
}
|
|
103
|
+
if (patch.title !== undefined)
|
|
104
|
+
node.title = patch.title;
|
|
105
|
+
if (patch.summary !== undefined)
|
|
106
|
+
node.summary = patch.summary;
|
|
107
|
+
// data 浅合并:仅合并传入字段,现有字段保留(决策 23;与 updateEntity 同语义)
|
|
108
|
+
if (patch.data !== undefined) {
|
|
109
|
+
node.data = { ...(node.data ?? {}), ...patch.data };
|
|
110
|
+
}
|
|
111
|
+
node.updated_at = updatedAt;
|
|
112
|
+
writeOutlineFile(dir, tree);
|
|
113
|
+
}
|
|
114
|
+
/** 在树中找 nodeId 的直接父(返回树根或节点引用);不在树中返回 undefined */
|
|
115
|
+
function findParentOf(tree, nodeId) {
|
|
116
|
+
for (const child of tree.children) {
|
|
117
|
+
if (child.id === nodeId)
|
|
118
|
+
return tree;
|
|
119
|
+
const found = findParentRecursive(child, nodeId);
|
|
120
|
+
if (found !== undefined)
|
|
121
|
+
return found;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
function findParentRecursive(node, nodeId) {
|
|
126
|
+
for (const child of childrenOf(node) ?? []) {
|
|
127
|
+
if (child.id === nodeId)
|
|
128
|
+
return node;
|
|
129
|
+
const found = findParentRecursive(child, nodeId);
|
|
130
|
+
if (found !== undefined)
|
|
131
|
+
return found;
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 移动大纲节点(PUT /api/v1/outline/:nodeId/move,endpoints.md 第 599-621 行):
|
|
137
|
+
* 从原父移除 → 插入新父指定 order(0-based;同父重排 = 移除后插入)→
|
|
138
|
+
* 三层约束校验(同 create)→ 新旧父 updated_at 统一更新(决策 19)→ 原子写回。
|
|
139
|
+
* order 越界时 clamp 到有效范围(拖拽场景边界值,宽松处理)。
|
|
140
|
+
*
|
|
141
|
+
* @returns { previousParentId, newParentId }(endpoints.md 语义;root 用 "root")
|
|
142
|
+
* @throws OutlineError NODE_NOT_FOUND / PARENT_NOT_FOUND / INVALID_HIERARCHY
|
|
143
|
+
*/
|
|
144
|
+
export function moveOutlineNode(dir, nodeId, input, updatedAt) {
|
|
145
|
+
const tree = readOutlineFile(dir);
|
|
146
|
+
const node = findOutlineNode(tree, nodeId);
|
|
147
|
+
if (node === undefined) {
|
|
148
|
+
throw new OutlineError("NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
|
|
149
|
+
}
|
|
150
|
+
const prevParent = findParentOf(tree, nodeId);
|
|
151
|
+
if (prevParent === undefined) {
|
|
152
|
+
throw new OutlineError("NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
|
|
153
|
+
}
|
|
154
|
+
const newParent = input.parentId === "root" ? tree : findOutlineNode(tree, input.parentId);
|
|
155
|
+
if (newParent === undefined) {
|
|
156
|
+
throw new OutlineError("PARENT_NOT_FOUND", `父节点不存在: ${input.parentId}`);
|
|
157
|
+
}
|
|
158
|
+
assertCanHold(newParent.type, node.type);
|
|
159
|
+
// 从原父移除
|
|
160
|
+
const prevKids = childrenOf(prevParent);
|
|
161
|
+
const idx = prevKids?.indexOf(node) ?? -1;
|
|
162
|
+
if (idx < 0) {
|
|
163
|
+
throw new OutlineError("NODE_NOT_FOUND", `大纲节点不在父节点 children 中: ${nodeId}`);
|
|
164
|
+
}
|
|
165
|
+
prevKids.splice(idx, 1);
|
|
166
|
+
// 插入新父指定位置(order clamp 到 [0, len])
|
|
167
|
+
const newKids = childrenOf(newParent);
|
|
168
|
+
if (newKids === undefined) {
|
|
169
|
+
throw new OutlineError("INVALID_HIERARCHY", `新父节点是叶子(scene): ${input.parentId}`);
|
|
170
|
+
}
|
|
171
|
+
const order = Math.max(0, Math.min(Math.trunc(input.order), newKids.length));
|
|
172
|
+
newKids.splice(order, 0, node);
|
|
173
|
+
// 新旧父版本戳(决策 19);同父时只更新一次。
|
|
174
|
+
// root 是树根非节点,不写 updated_at(schema.md 顶层契约,oracle 审核修复)
|
|
175
|
+
if (prevParent !== tree) {
|
|
176
|
+
prevParent.updated_at = updatedAt;
|
|
177
|
+
}
|
|
178
|
+
if (newParent !== prevParent && newParent !== tree) {
|
|
179
|
+
newParent.updated_at = updatedAt;
|
|
180
|
+
}
|
|
181
|
+
writeOutlineFile(dir, tree);
|
|
182
|
+
return {
|
|
183
|
+
previousParentId: prevParent.type === "root" ? "root" : prevParent.id,
|
|
184
|
+
newParentId: newParent.type === "root" ? "root" : newParent.id,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/** 递归软删子树(决策 12 级联):标记 deleted + deleted_at + updated_at,返回子节点数 */
|
|
188
|
+
function softDeleteSubtree(node, deletedAt) {
|
|
189
|
+
let count = 0;
|
|
190
|
+
for (const child of childrenOf(node) ?? []) {
|
|
191
|
+
count += 1 + softDeleteSubtree(child, deletedAt);
|
|
192
|
+
child.deleted = true;
|
|
193
|
+
child.deleted_at = deletedAt;
|
|
194
|
+
child.updated_at = deletedAt;
|
|
195
|
+
}
|
|
196
|
+
return count;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 软删大纲节点(DELETE /api/v1/outline/:nodeId,endpoints.md 第 623-640 行,决策 12):
|
|
200
|
+
* 标记 deleted + deleted_at,**递归软删整棵子树**(本体保留可还原)。
|
|
201
|
+
* 已软删节点再次软删:幂等重标(不报错)。
|
|
202
|
+
*
|
|
203
|
+
* @returns { children } 级联软删的子节点数(不含自身)
|
|
204
|
+
* —— relations/deltas 的级联软删由上层(S2.2)组合 relation/delta 模块处理(本模块边界)
|
|
205
|
+
* @throws OutlineError NODE_NOT_FOUND
|
|
206
|
+
*/
|
|
207
|
+
export function deleteOutlineNode(dir, nodeId, updatedAt) {
|
|
208
|
+
const tree = readOutlineFile(dir);
|
|
209
|
+
const node = findOutlineNode(tree, nodeId);
|
|
210
|
+
if (node === undefined) {
|
|
211
|
+
throw new OutlineError("NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
|
|
212
|
+
}
|
|
213
|
+
const children = softDeleteSubtree(node, updatedAt);
|
|
214
|
+
node.deleted = true;
|
|
215
|
+
node.deleted_at = updatedAt;
|
|
216
|
+
node.updated_at = updatedAt;
|
|
217
|
+
writeOutlineFile(dir, tree);
|
|
218
|
+
return { children };
|
|
219
|
+
}
|
|
220
|
+
/** 递归还原子树(决策 12 修订:子节点若仍在回收站则一并还原),返回还原的子节点数 */
|
|
221
|
+
function restoreSubtree(node, updatedAt) {
|
|
222
|
+
let count = 0;
|
|
223
|
+
for (const child of childrenOf(node) ?? []) {
|
|
224
|
+
count += 1 + restoreSubtree(child, updatedAt);
|
|
225
|
+
delete child.deleted;
|
|
226
|
+
delete child.deleted_at;
|
|
227
|
+
child.updated_at = updatedAt;
|
|
228
|
+
}
|
|
229
|
+
return count;
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* 还原软删大纲节点(POST /api/v1/trash/outline/:nodeId/restore,endpoints.md 第 694-711 行):
|
|
233
|
+
* 清除 deleted/deleted_at → **递归还原子树**(仍软删的子孙一并还原,决策 12 修订)→
|
|
234
|
+
* **祖先链校验**:存在软删祖先 → 抛 OUTLINE_ANCESTOR_DELETED(409 语义,决策 12 修订,
|
|
235
|
+
* 杜绝「可见节点挂在不可见父」的畸形树——校验在还原前执行,祖先必须已还原)。
|
|
236
|
+
*
|
|
237
|
+
* @returns { children } 级联还原的子节点数
|
|
238
|
+
* @throws OutlineError NODE_NOT_FOUND / OUTLINE_ANCESTOR_DELETED
|
|
239
|
+
*/
|
|
240
|
+
export function restoreOutlineNode(dir, nodeId, updatedAt) {
|
|
241
|
+
const tree = readOutlineFile(dir);
|
|
242
|
+
const node = findOutlineNode(tree, nodeId);
|
|
243
|
+
if (node === undefined) {
|
|
244
|
+
throw new OutlineError("NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
|
|
245
|
+
}
|
|
246
|
+
// 祖先链校验(决策 12 修订):路径 [root, ..., nodeId] 中排除 root 与自身的中间祖先
|
|
247
|
+
const path = getOutlinePathIds(tree, nodeId);
|
|
248
|
+
for (const ancestorId of path.slice(1, -1)) {
|
|
249
|
+
const ancestor = findOutlineNode(tree, ancestorId);
|
|
250
|
+
if (ancestor?.deleted === true) {
|
|
251
|
+
throw new OutlineError("OUTLINE_ANCESTOR_DELETED", `存在软删祖先 ${ancestorId},请先还原祖先再还原本节点`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const children = restoreSubtree(node, updatedAt);
|
|
255
|
+
delete node.deleted;
|
|
256
|
+
delete node.deleted_at;
|
|
257
|
+
node.updated_at = updatedAt;
|
|
258
|
+
writeOutlineFile(dir, tree);
|
|
259
|
+
return { children };
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* 物理删除大纲节点(DELETE /api/v1/trash/outline/:nodeId,endpoints.md 第 726-736 行):
|
|
263
|
+
* 从树中移除整棵子树(递归,文件内物理清除,不可恢复)。
|
|
264
|
+
* 边界:不涉及 data.db(relations/deltas 的物理清除由上层组合);
|
|
265
|
+
* 不更新父节点 updated_at——purge 属回收站清理,节点已消失,
|
|
266
|
+
* 基于它的提案快照必然失效(节点不存在),父版本戳无需联动。
|
|
267
|
+
* @throws OutlineError NODE_NOT_FOUND
|
|
268
|
+
*/
|
|
269
|
+
export function purgeOutlineNode(dir, nodeId) {
|
|
270
|
+
const tree = readOutlineFile(dir);
|
|
271
|
+
const parent = findParentOf(tree, nodeId);
|
|
272
|
+
if (parent === undefined) {
|
|
273
|
+
throw new OutlineError("NODE_NOT_FOUND", `大纲节点不存在: ${nodeId}`);
|
|
274
|
+
}
|
|
275
|
+
const kids = childrenOf(parent);
|
|
276
|
+
const idx = kids.findIndex((n) => n.id === nodeId);
|
|
277
|
+
if (idx < 0) {
|
|
278
|
+
throw new OutlineError("NODE_NOT_FOUND", `大纲节点不在父节点 children 中: ${nodeId}`);
|
|
279
|
+
}
|
|
280
|
+
kids.splice(idx, 1);
|
|
281
|
+
writeOutlineFile(dir, tree);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 回收站列表(大纲侧):收集整棵树中 deleted 标记的节点(含子树中的软删节点),
|
|
285
|
+
* 按 deleted_at 倒序(回收站排序约定,schema.md:跨 SQLite 与 outline.json 统一 ISO 格式)。
|
|
286
|
+
*/
|
|
287
|
+
export function listDeletedNodes(dir) {
|
|
288
|
+
const tree = readOutlineFile(dir);
|
|
289
|
+
const out = [];
|
|
290
|
+
const visit = (node) => {
|
|
291
|
+
if (node.deleted === true && node.deleted_at !== undefined) {
|
|
292
|
+
out.push({ id: node.id, type: node.type, title: node.title, deleted_at: node.deleted_at });
|
|
293
|
+
}
|
|
294
|
+
for (const child of childrenOf(node) ?? [])
|
|
295
|
+
visit(child);
|
|
296
|
+
};
|
|
297
|
+
for (const child of tree.children)
|
|
298
|
+
visit(child);
|
|
299
|
+
return out.sort((a, b) => b.deleted_at.localeCompare(a.deleted_at));
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* 章节序推导(决策 21):按大纲树先序遍历编号——root → 卷 → 章;
|
|
303
|
+
* 全局章序号**跨卷连续**;直接挂 root 的 chapter 按兄弟顺序编号(决策 19 允许);
|
|
304
|
+
* scene 归入所属章,不单独编号(见 getChapterNumber)。
|
|
305
|
+
*/
|
|
306
|
+
export function deriveChapterOrder(dir) {
|
|
307
|
+
const tree = readOutlineFile(dir);
|
|
308
|
+
const result = [];
|
|
309
|
+
let number = 0;
|
|
310
|
+
// root.children 类型已放宽为 (volume|chapter) 联合(决策 19 允许 chapter 直挂 root,
|
|
311
|
+
// oracle 回修 shared 类型后此处无需断言),按 type 分支:volume → 卷内章;chapter → 直挂章
|
|
312
|
+
for (const child of tree.children) {
|
|
313
|
+
if (child.type === "chapter") {
|
|
314
|
+
result.push({ chapterId: child.id, chapterNumber: ++number });
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
for (const ch of childrenOf(child) ?? []) {
|
|
318
|
+
result.push({ chapterId: ch.id, chapterNumber: ++number });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* 节点所属章序号(决策 21):
|
|
326
|
+
* - scene → 所属 chapter 的全局序号(严格三层下 scene 的父必为 chapter)
|
|
327
|
+
* - chapter → 自身序号
|
|
328
|
+
* - volume / root / 节点不存在 → null(卷无章节号;不存在防御性返回 null 供上层展示)
|
|
329
|
+
*/
|
|
330
|
+
export function getChapterNumber(dir, nodeId) {
|
|
331
|
+
const tree = readOutlineFile(dir);
|
|
332
|
+
let path;
|
|
333
|
+
try {
|
|
334
|
+
path = getOutlinePathIds(tree, nodeId);
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return null; // 节点不存在(防御,伏笔指标/展示场景不因脏引用崩)
|
|
338
|
+
}
|
|
339
|
+
// 路径上最后一个 chapter(scene 的父 或 自身)
|
|
340
|
+
const chapterId = [...path].reverse().find((id) => findOutlineNode(tree, id)?.type === "chapter");
|
|
341
|
+
if (chapterId === undefined)
|
|
342
|
+
return null;
|
|
343
|
+
const order = deriveChapterOrder(dir).find((c) => c.chapterId === chapterId);
|
|
344
|
+
return order ?? null;
|
|
345
|
+
}
|
|
346
|
+
//# sourceMappingURL=outline-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outline-ops.js","sourceRoot":"","sources":["../../src/queries/outline-ops.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { OutlineFileNode, OutlineFileTree, OutlineFileVolume } from "@ai-editor/shared";
|
|
2
|
+
/** outline.json 文件名(与 project.json / data.db 并列的项目数据文件,schema.md 第 3-14 行) */
|
|
3
|
+
export declare const OUTLINE_FILE_NAME = "outline.json";
|
|
4
|
+
/** 大纲节点或树根的公共形态(findOutlineNode 的 parent 引用用) */
|
|
5
|
+
export type OutlineParent = OutlineFileTree | OutlineFileNode;
|
|
6
|
+
/**
|
|
7
|
+
* 轻量结构校验:是否为合法 OutlineFileTree(根 id/type、schema_version、children 数组)。
|
|
8
|
+
* shared 未提供 file 形态的校验函数(T1.3 只做字段映射),此处守卫 root 级形状即可,
|
|
9
|
+
* 节点级字段由写入方(服务端)保证。
|
|
10
|
+
*/
|
|
11
|
+
export declare function isOutlineFileTree(value: unknown): value is OutlineFileTree;
|
|
12
|
+
/**
|
|
13
|
+
* 读取 outline.json:文件不存在返回 null(由调用方决定初始化,如 initOutlineFile);
|
|
14
|
+
* 文件存在但结构非法则抛错(不静默吞坏数据)。
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadOutlineFile(dir: string): OutlineFileTree | null;
|
|
17
|
+
/**
|
|
18
|
+
* 原子写 outline.json(决策 11:临时文件 + fsync + rename)。
|
|
19
|
+
* 禁止直接覆盖原文件。
|
|
20
|
+
*/
|
|
21
|
+
export declare function saveOutlineFile(dir: string, tree: OutlineFileTree): void;
|
|
22
|
+
/**
|
|
23
|
+
* 首次初始化:创建空根树(决策 19:严格三层 root → volume → chapter → scene,无游离节点)
|
|
24
|
+
* 并原子写入;顶层 schema_version 与 project.json / data.db 的 user_version 同步(决策 13)。
|
|
25
|
+
*/
|
|
26
|
+
export declare function initOutlineFile(dir: string): OutlineFileTree;
|
|
27
|
+
/**
|
|
28
|
+
* 递归查找节点:返回 { node, parent }(parent 为直接父引用,root 的子节点 parent 为树根)。
|
|
29
|
+
* 未找到返回 null。供 updateOutlineNode 与后续大纲操作(T2.7 移动/删除)复用。
|
|
30
|
+
*/
|
|
31
|
+
export declare function findOutlineNode(tree: OutlineFileTree, id: string): {
|
|
32
|
+
node: OutlineFileNode;
|
|
33
|
+
parent: OutlineParent | null;
|
|
34
|
+
} | null;
|
|
35
|
+
/**
|
|
36
|
+
* 根到节点的路径 id 数组(含树根 "root" 与节点自身),如 ["root", "vol-1", "ch-1", "sc-1"]。
|
|
37
|
+
* 未找到返回 null。T2.7 大纲操作(移动/删除的祖先链校验)复用。
|
|
38
|
+
*/
|
|
39
|
+
export declare function getNodePathIds(tree: OutlineFileTree, id: string): string[] | null;
|
|
40
|
+
/**
|
|
41
|
+
* 更新大纲节点:读树 → 定位节点 → 应用 updater 修改 → 统一更新该节点 updated_at(决策 19:
|
|
42
|
+
* 节点任何字段变更——title/summary/children 重排——都在原子写流程中刷新版本戳)→ 原子写回。
|
|
43
|
+
* 返回更新后的整树。outline.json 不存在或节点未找到则抛错。
|
|
44
|
+
*/
|
|
45
|
+
export declare function updateOutlineNode(dir: string, nodeId: string, updater: (node: OutlineFileNode) => void): OutlineFileTree;
|
|
46
|
+
/** 便捷类型导出:卷节点(T2.7 移动操作等会用到) */
|
|
47
|
+
export type { OutlineFileVolume };
|
|
48
|
+
//# sourceMappingURL=outline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outline.d.ts","sourceRoot":"","sources":["../../src/queries/outline.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAI7F,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,iDAAiD;AACjD,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,eAAe,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAM1E;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAUnE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,IAAI,CAExE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAS5D;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,eAAe,EACrB,EAAE,EAAE,MAAM,GACT;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAQhE;AAuBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAMjF;AAiBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,GACvC,eAAe,CAajB;AAED,gCAAgC;AAChC,YAAY,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// @ai-editor/db 大纲 JSON 存储(outline.json,T2.2)
|
|
2
|
+
//
|
|
3
|
+
// 契约来源:doc/database/schema.md(outline.json 第 123-155 行)、决策 11(原子写)、决策 19(节点版本戳)。
|
|
4
|
+
// 存储形态为 OutlineFileTree(snake_case,shared 定义);API 形态转换(mapOutlineFileToTree 等)在
|
|
5
|
+
// shared/utils/mapping.ts,由上层(server)在返回前调用,本模块只做文件形态的读写与节点操作。
|
|
6
|
+
//
|
|
7
|
+
// 时间约定:节点 updated_at 由本模块在原子写流程中统一更新(决策 19),应用层写入 ISO 8601。
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { SCHEMA_VERSION } from "../schema";
|
|
10
|
+
import { readJsonFile, writeJsonAtomic } from "./atomic";
|
|
11
|
+
/** outline.json 文件名(与 project.json / data.db 并列的项目数据文件,schema.md 第 3-14 行) */
|
|
12
|
+
export const OUTLINE_FILE_NAME = "outline.json";
|
|
13
|
+
/**
|
|
14
|
+
* 轻量结构校验:是否为合法 OutlineFileTree(根 id/type、schema_version、children 数组)。
|
|
15
|
+
* shared 未提供 file 形态的校验函数(T1.3 只做字段映射),此处守卫 root 级形状即可,
|
|
16
|
+
* 节点级字段由写入方(服务端)保证。
|
|
17
|
+
*/
|
|
18
|
+
export function isOutlineFileTree(value) {
|
|
19
|
+
if (typeof value !== "object" || value === null) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const v = value;
|
|
23
|
+
return v.id === "root" && v.type === "root" && typeof v.schema_version === "number" && Array.isArray(v.children);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 读取 outline.json:文件不存在返回 null(由调用方决定初始化,如 initOutlineFile);
|
|
27
|
+
* 文件存在但结构非法则抛错(不静默吞坏数据)。
|
|
28
|
+
*/
|
|
29
|
+
export function loadOutlineFile(dir) {
|
|
30
|
+
const filePath = join(dir, OUTLINE_FILE_NAME);
|
|
31
|
+
const raw = readJsonFile(filePath);
|
|
32
|
+
if (raw === null) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
if (!isOutlineFileTree(raw)) {
|
|
36
|
+
throw new Error(`loadOutlineFile: ${filePath} 结构非法(非 OutlineFileTree)`);
|
|
37
|
+
}
|
|
38
|
+
return raw;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 原子写 outline.json(决策 11:临时文件 + fsync + rename)。
|
|
42
|
+
* 禁止直接覆盖原文件。
|
|
43
|
+
*/
|
|
44
|
+
export function saveOutlineFile(dir, tree) {
|
|
45
|
+
writeJsonAtomic(join(dir, OUTLINE_FILE_NAME), tree);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 首次初始化:创建空根树(决策 19:严格三层 root → volume → chapter → scene,无游离节点)
|
|
49
|
+
* 并原子写入;顶层 schema_version 与 project.json / data.db 的 user_version 同步(决策 13)。
|
|
50
|
+
*/
|
|
51
|
+
export function initOutlineFile(dir) {
|
|
52
|
+
const tree = {
|
|
53
|
+
id: "root",
|
|
54
|
+
type: "root",
|
|
55
|
+
schema_version: SCHEMA_VERSION,
|
|
56
|
+
children: [],
|
|
57
|
+
};
|
|
58
|
+
saveOutlineFile(dir, tree);
|
|
59
|
+
return tree;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 递归查找节点:返回 { node, parent }(parent 为直接父引用,root 的子节点 parent 为树根)。
|
|
63
|
+
* 未找到返回 null。供 updateOutlineNode 与后续大纲操作(T2.7 移动/删除)复用。
|
|
64
|
+
*/
|
|
65
|
+
export function findOutlineNode(tree, id) {
|
|
66
|
+
for (const child of tree.children) {
|
|
67
|
+
const found = findNodeRecursive(child, id, tree);
|
|
68
|
+
if (found) {
|
|
69
|
+
return found;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/** findOutlineNode 的递归实现(scene 为叶子,无 children 分支) */
|
|
75
|
+
function findNodeRecursive(node, id, parent) {
|
|
76
|
+
if (node.id === id) {
|
|
77
|
+
return { node, parent };
|
|
78
|
+
}
|
|
79
|
+
if (node.type === "scene") {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
for (const child of node.children ?? []) {
|
|
83
|
+
const found = findNodeRecursive(child, id, node);
|
|
84
|
+
if (found) {
|
|
85
|
+
return found;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 根到节点的路径 id 数组(含树根 "root" 与节点自身),如 ["root", "vol-1", "ch-1", "sc-1"]。
|
|
92
|
+
* 未找到返回 null。T2.7 大纲操作(移动/删除的祖先链校验)复用。
|
|
93
|
+
*/
|
|
94
|
+
export function getNodePathIds(tree, id) {
|
|
95
|
+
const path = [];
|
|
96
|
+
if (collectPath(tree, id, path)) {
|
|
97
|
+
return path;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
/** getNodePathIds 的递归实现:回溯式收集路径(先压栈,失败弹栈) */
|
|
102
|
+
function collectPath(node, id, path) {
|
|
103
|
+
if (node.type === "scene") {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
for (const child of node.children ?? []) {
|
|
107
|
+
path.push(child.id);
|
|
108
|
+
if (child.id === id || (child.type !== "scene" && collectPath(child, id, path))) {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
path.pop();
|
|
112
|
+
}
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 更新大纲节点:读树 → 定位节点 → 应用 updater 修改 → 统一更新该节点 updated_at(决策 19:
|
|
117
|
+
* 节点任何字段变更——title/summary/children 重排——都在原子写流程中刷新版本戳)→ 原子写回。
|
|
118
|
+
* 返回更新后的整树。outline.json 不存在或节点未找到则抛错。
|
|
119
|
+
*/
|
|
120
|
+
export function updateOutlineNode(dir, nodeId, updater) {
|
|
121
|
+
const tree = loadOutlineFile(dir);
|
|
122
|
+
if (!tree) {
|
|
123
|
+
throw new Error(`updateOutlineNode: ${join(dir, OUTLINE_FILE_NAME)} 不存在`);
|
|
124
|
+
}
|
|
125
|
+
const found = findOutlineNode(tree, nodeId);
|
|
126
|
+
if (!found) {
|
|
127
|
+
throw new Error(`updateOutlineNode: 未找到节点 ${nodeId}`);
|
|
128
|
+
}
|
|
129
|
+
updater(found.node);
|
|
130
|
+
found.node.updated_at = new Date().toISOString();
|
|
131
|
+
saveOutlineFile(dir, tree);
|
|
132
|
+
return tree;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=outline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outline.js","sourceRoot":"","sources":["../../src/queries/outline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ProjectFileConfig, type ProjectLanguage } from "@ai-editor/shared";
|
|
2
|
+
/** project.json 文件名 */
|
|
3
|
+
export declare const PROJECT_FILE_NAME = "project.json";
|
|
4
|
+
/** 读取 project.json:文件不存在返回 null(由调用方决定初始化) */
|
|
5
|
+
export declare function loadProjectFile(dir: string): ProjectFileConfig | null;
|
|
6
|
+
/** 原子写 project.json(决策 11:临时文件 + fsync + rename,与 outline.json 同款) */
|
|
7
|
+
export declare function saveProjectFile(dir: string, config: ProjectFileConfig): void;
|
|
8
|
+
/**
|
|
9
|
+
* 首次初始化 project.json(决策 8:自动创建,此后跨启动稳定):
|
|
10
|
+
* - id:proj- 前缀 + nanoid(shared generateProjectId,跨启动稳定,画布布局 localStorage 隔离 key)
|
|
11
|
+
* - created_at / updated_at:ISO 8601 应用层写入(schema.md 第 16 行),初始两者相同
|
|
12
|
+
* - schema_version:与 outline.json 顶层 / data.db user_version 同步(决策 13)
|
|
13
|
+
* - current_position:null(未设置)
|
|
14
|
+
* 写入后返回配置对象(调用方持有即可,无需再读)。
|
|
15
|
+
*/
|
|
16
|
+
export declare function initProjectFile(dir: string, init: {
|
|
17
|
+
name: string;
|
|
18
|
+
language: ProjectLanguage;
|
|
19
|
+
prompt: string;
|
|
20
|
+
}): ProjectFileConfig;
|
|
21
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/queries/project.ts"],"names":[],"mappings":"AAOA,OAAO,EAAqB,KAAK,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIpG,uBAAuB;AACvB,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD,8CAA8C;AAC9C,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAErE;AAED,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAE5E;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChE,iBAAiB,CAcnB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @ai-editor/db 项目配置 JSON 存储(project.json,T2.2)
|
|
2
|
+
//
|
|
3
|
+
// 契约来源:doc/database/schema.md(project.json 第 157-186 行)、决策 8(首次初始化自动创建)、
|
|
4
|
+
// 决策 11(同款原子写)、决策 13(schema_version 与 outline.json / data.db user_version 同步)。
|
|
5
|
+
// 硬约束(决策 17):DeepSeek API key 绝不写入本文件。
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { generateProjectId } from "@ai-editor/shared";
|
|
8
|
+
import { SCHEMA_VERSION } from "../schema";
|
|
9
|
+
import { readJsonFile, writeJsonAtomic } from "./atomic";
|
|
10
|
+
/** project.json 文件名 */
|
|
11
|
+
export const PROJECT_FILE_NAME = "project.json";
|
|
12
|
+
/** 读取 project.json:文件不存在返回 null(由调用方决定初始化) */
|
|
13
|
+
export function loadProjectFile(dir) {
|
|
14
|
+
return readJsonFile(join(dir, PROJECT_FILE_NAME));
|
|
15
|
+
}
|
|
16
|
+
/** 原子写 project.json(决策 11:临时文件 + fsync + rename,与 outline.json 同款) */
|
|
17
|
+
export function saveProjectFile(dir, config) {
|
|
18
|
+
writeJsonAtomic(join(dir, PROJECT_FILE_NAME), config);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 首次初始化 project.json(决策 8:自动创建,此后跨启动稳定):
|
|
22
|
+
* - id:proj- 前缀 + nanoid(shared generateProjectId,跨启动稳定,画布布局 localStorage 隔离 key)
|
|
23
|
+
* - created_at / updated_at:ISO 8601 应用层写入(schema.md 第 16 行),初始两者相同
|
|
24
|
+
* - schema_version:与 outline.json 顶层 / data.db user_version 同步(决策 13)
|
|
25
|
+
* - current_position:null(未设置)
|
|
26
|
+
* 写入后返回配置对象(调用方持有即可,无需再读)。
|
|
27
|
+
*/
|
|
28
|
+
export function initProjectFile(dir, init) {
|
|
29
|
+
const now = new Date().toISOString();
|
|
30
|
+
const config = {
|
|
31
|
+
id: generateProjectId(),
|
|
32
|
+
name: init.name,
|
|
33
|
+
language: init.language,
|
|
34
|
+
prompt: init.prompt,
|
|
35
|
+
schema_version: SCHEMA_VERSION,
|
|
36
|
+
current_position: null,
|
|
37
|
+
created_at: now,
|
|
38
|
+
updated_at: now,
|
|
39
|
+
};
|
|
40
|
+
saveProjectFile(dir, config);
|
|
41
|
+
return config;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/queries/project.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,EAAE;AACF,yEAAyE;AACzE,+EAA+E;AAC/E,uCAAuC;AAEvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAgD,MAAM,mBAAmB,CAAC;AACpG,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEzD,uBAAuB;AACvB,MAAM,CAAC,MAAM,iBAAiB,GAAG,cAAc,CAAC;AAEhD,8CAA8C;AAC9C,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,YAAY,CAAoB,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,MAAyB;IACpE,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,GAAW,EACX,IAAiE;IAEjE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,MAAM,GAAsB;QAChC,EAAE,EAAE,iBAAiB,EAAE;QACvB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,cAAc,EAAE,cAAc;QAC9B,gBAAgB,EAAE,IAAI;QACtB,UAAU,EAAE,GAAG;QACf,UAAU,EAAE,GAAG;KAChB,CAAC;IACF,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC;AAChB,CAAC"}
|