@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,285 @@
|
|
|
1
|
+
// @whispering233/ai-editor-db 实体 CRUD 查询(S3.1)
|
|
2
|
+
//
|
|
3
|
+
// 单一事实来源:doc/database/schema.md 第 18-39 行(entities 表)、doc/api/endpoints.md 第 150-278 行
|
|
4
|
+
// (实体端点:列表 q/分页/排序 + 摘要字段、详情 + deltaCount、创建/部分更新/软删级联)、
|
|
5
|
+
// 决策 12(软删:常规查询默认过滤、级联软删关系与 Delta)、决策 14(updated_at 提案快照比对)。
|
|
6
|
+
// 时间约定(schema.md 第 16 行):ISO 8601 应用层写入(nowIso),模块内不生成时间。
|
|
7
|
+
//
|
|
8
|
+
// 摘要字段提取(endpoints.md 第 184-188 行):**行内解析**(SELECT 整行 → JSON.parse → JS 提取)——
|
|
9
|
+
// character → role/status、setting → category、location → type、hook → status/payoff_timing;
|
|
10
|
+
// 取舍:json_extract 免全量 parse 但需按类型动态列,SQL 复杂化;MVP 数据量小,行内解析
|
|
11
|
+
// 与 better-sqlite3 字符串列一致(chat.ts 同款风格),数据量大后再优化。
|
|
12
|
+
// 级联软删边界:relations/deltas 的**查询**模块 S3.2 才建——本卡只做级联软删所需 UPDATE。
|
|
13
|
+
import { ENTITY_TYPES, generateEntityId } from "@whispering233/ai-editor-shared";
|
|
14
|
+
import { nowIso } from "../storage/atomic.js";
|
|
15
|
+
import { withTransaction } from "../connection.js";
|
|
16
|
+
/** 行 → 摘要:data 已解析,按类型提取关键字段(字段缺失即不出现——Record 稀疏语义) */
|
|
17
|
+
function toSummary(row) {
|
|
18
|
+
const data = row.data;
|
|
19
|
+
const summary = {};
|
|
20
|
+
switch (row.type) {
|
|
21
|
+
case "character":
|
|
22
|
+
if (data.role !== undefined)
|
|
23
|
+
summary.role = data.role;
|
|
24
|
+
if (data.status !== undefined)
|
|
25
|
+
summary.status = data.status;
|
|
26
|
+
break;
|
|
27
|
+
case "setting":
|
|
28
|
+
if (data.category !== undefined)
|
|
29
|
+
summary.category = data.category;
|
|
30
|
+
break;
|
|
31
|
+
case "location":
|
|
32
|
+
if (data.type !== undefined)
|
|
33
|
+
summary.type = data.type;
|
|
34
|
+
break;
|
|
35
|
+
case "hook":
|
|
36
|
+
if (data.status !== undefined)
|
|
37
|
+
summary.status = data.status;
|
|
38
|
+
if (data.payoff_timing !== undefined)
|
|
39
|
+
summary.payoff_timing = data.payoff_timing;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
id: row.id,
|
|
44
|
+
type: row.type,
|
|
45
|
+
name: row.name,
|
|
46
|
+
summary,
|
|
47
|
+
createdAt: row.created_at,
|
|
48
|
+
updatedAt: row.updated_at,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** 行 → EntityRow(data 从 JSON 字符串解析为对象;坏行防御见 rowToEntityRow 注释) */
|
|
52
|
+
export function rowToEntityRow(row) {
|
|
53
|
+
return {
|
|
54
|
+
id: row.id,
|
|
55
|
+
type: row.type,
|
|
56
|
+
name: row.name,
|
|
57
|
+
data: parseDataColumn(row.data),
|
|
58
|
+
created_at: row.created_at,
|
|
59
|
+
updated_at: row.updated_at,
|
|
60
|
+
deleted_at: row.deleted_at ?? null,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 解析 data 列(oracle 审核建议 1:JSON.parse 防御):
|
|
65
|
+
* 非法 JSON(手改库/异常写入)时返回 {} 而非抛错——否则 listEntities 整表查询
|
|
66
|
+
* 会被单条坏行打挂;与 chat.ts 的 parseToolCalls 防御风格一致。坏行以空 data 呈现,
|
|
67
|
+
* 其余行正常返回;修复入口为回收站清理/手动修正(不在本层静默写回)。
|
|
68
|
+
*/
|
|
69
|
+
function parseDataColumn(value) {
|
|
70
|
+
if (typeof value !== "string")
|
|
71
|
+
return {};
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(value);
|
|
74
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* data 字段过滤(S6.3 工具 search_entities 下沉,filters 语义见 EntityListQuery):
|
|
82
|
+
* status 字符串相等匹配;tags 要求 data.tags 为数组且包含全部指定 tags(AND)。
|
|
83
|
+
* 匹配失败的字段(如非数组 tags)一律视为不匹配——防御,不做宽松猜测。
|
|
84
|
+
*/
|
|
85
|
+
function matchDataFilters(data, filters) {
|
|
86
|
+
if (filters.status !== undefined && data.status !== filters.status)
|
|
87
|
+
return false;
|
|
88
|
+
if (filters.tags !== undefined && filters.tags.length > 0) {
|
|
89
|
+
const tags = data.tags;
|
|
90
|
+
if (!Array.isArray(tags))
|
|
91
|
+
return false;
|
|
92
|
+
for (const tag of filters.tags) {
|
|
93
|
+
if (!tags.includes(tag))
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 实体列表(GET /api/v1/entity/:type,endpoints.md 第 154-193 行):
|
|
101
|
+
* type 过滤 + q 模糊搜索(name LIKE)+ 排序(name/created_at/updated_at × asc/desc,
|
|
102
|
+
* 白名单防注入)+ 分页(limit clamp 1-200)+ **默认过滤软删**(决策 12)。
|
|
103
|
+
* total 为过滤后总数(不含分页)。
|
|
104
|
+
* filters 语义(S6.3 下沉):data 字段 JS 过滤(列表摘要不含 data),此时 SQL 只做
|
|
105
|
+
* type/q/软删过滤,filters + 分页在 JS 层(MVP 数据量小,全行查询可接受);
|
|
106
|
+
* 无 filters 时保持 COUNT + LIMIT SQL 原路径(行为不变)。
|
|
107
|
+
*/
|
|
108
|
+
export function listEntities(db, query) {
|
|
109
|
+
const where = ["deleted_at IS NULL"];
|
|
110
|
+
const params = [];
|
|
111
|
+
if (query.type !== undefined) {
|
|
112
|
+
where.push("type = ?");
|
|
113
|
+
params.push(query.type);
|
|
114
|
+
}
|
|
115
|
+
if (query.q !== undefined && query.q !== "") {
|
|
116
|
+
where.push("name LIKE ?");
|
|
117
|
+
params.push(`%${query.q}%`);
|
|
118
|
+
}
|
|
119
|
+
// 排序白名单(列名不可参数化,只允许枚举值;id 作次级排序保证稳定分页)
|
|
120
|
+
const sortCol = query.sort === "name" ? "name" : query.sort === "created_at" ? "created_at" : "updated_at";
|
|
121
|
+
const orderDir = query.order === "asc" ? "ASC" : "DESC";
|
|
122
|
+
const offset = Math.max(0, Math.trunc(query.offset ?? 0));
|
|
123
|
+
const limit = Math.min(200, Math.max(1, Math.trunc(query.limit ?? 50)));
|
|
124
|
+
// filters 分支:SQL 取全量候选行(type/q/软删),filters + 分页在 JS 层
|
|
125
|
+
if (query.filters !== undefined) {
|
|
126
|
+
const all = db
|
|
127
|
+
.prepare(`SELECT * FROM entities WHERE ${where.join(" AND ")} ORDER BY ${sortCol} ${orderDir}, id ASC`)
|
|
128
|
+
.all(...params);
|
|
129
|
+
const filtered = all.filter((r) => matchDataFilters(rowToEntityRow(r).data, query.filters));
|
|
130
|
+
return {
|
|
131
|
+
items: filtered.slice(offset, offset + limit).map((r) => toSummary(rowToEntityRow(r))),
|
|
132
|
+
total: filtered.length,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const total = db.prepare(`SELECT COUNT(*) AS c FROM entities WHERE ${where.join(" AND ")}`).get(...params).c;
|
|
136
|
+
const rows = db
|
|
137
|
+
.prepare(`SELECT * FROM entities WHERE ${where.join(" AND ")}
|
|
138
|
+
ORDER BY ${sortCol} ${orderDir}, id ASC LIMIT ? OFFSET ?`)
|
|
139
|
+
.all(...params, limit, offset);
|
|
140
|
+
return { items: rows.map((r) => toSummary(rowToEntityRow(r))), total };
|
|
141
|
+
}
|
|
142
|
+
/** 按 id 取实体详情(GET /api/v1/entity/:type/:id);不存在或已软删返回 null(决策 12 过滤) */
|
|
143
|
+
export function getEntity(db, id) {
|
|
144
|
+
const row = db
|
|
145
|
+
.prepare("SELECT * FROM entities WHERE id = ? AND deleted_at IS NULL")
|
|
146
|
+
.get(id);
|
|
147
|
+
return row === undefined ? null : rowToEntityRow(row);
|
|
148
|
+
}
|
|
149
|
+
/** 实体的 Delta 计数(详情响应 deltaCount;决策 12 修订:目标实体软删的 Delta 不可见) */
|
|
150
|
+
export function countDeltasForEntity(db, id) {
|
|
151
|
+
return db
|
|
152
|
+
.prepare("SELECT COUNT(*) AS c FROM delta_records WHERE target_id = ? AND deleted_at IS NULL")
|
|
153
|
+
.get(id).c;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 创建实体(POST /api/v1/entity/:type,endpoints.md 第 221-253 行):
|
|
157
|
+
* id = shared generateEntityId(char-/set-/loc-/hook- 前缀)、created_at/updated_at 应用层 ISO、
|
|
158
|
+
* data 缺省 {}、type 必须 ∈ ENTITY_TYPES(非法抛错——路由层 schema 校验后一般不可达,防御)。
|
|
159
|
+
* @returns 新行(EntityRow,data 已解析)
|
|
160
|
+
*/
|
|
161
|
+
export function createEntity(db, input) {
|
|
162
|
+
if (!ENTITY_TYPES.includes(input.type)) {
|
|
163
|
+
throw new Error(`createEntity: 非法实体类型 ${input.type}`);
|
|
164
|
+
}
|
|
165
|
+
const now = nowIso();
|
|
166
|
+
const row = {
|
|
167
|
+
id: generateEntityId(input.type),
|
|
168
|
+
type: input.type,
|
|
169
|
+
name: input.name,
|
|
170
|
+
data: input.data ?? {},
|
|
171
|
+
created_at: now,
|
|
172
|
+
updated_at: now,
|
|
173
|
+
deleted_at: null,
|
|
174
|
+
};
|
|
175
|
+
db.prepare("INSERT INTO entities (id, type, name, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(row.id, row.type, row.name, JSON.stringify(row.data), row.created_at, row.updated_at);
|
|
176
|
+
return row;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* 部分更新实体(PUT /api/v1/entity/:type/:id,endpoints.md 第 255-278 行):
|
|
180
|
+
* 仅合并传入字段——name 直接替换;data **浅合并**(未传字段保留,endpoints.md 第 267 行);
|
|
181
|
+
* updated_at 刷新(决策 14 提案快照比对)。软删实体不可更新(getEntity 过滤 → null,路由层 404)。
|
|
182
|
+
* 读后写包 withTransaction(oracle 审核建议 2):better-sqlite3 同步单连接下无竞态(安全),
|
|
183
|
+
* 包事务统一风格(与 softDeleteEntity 一致);返回行在事务内直接构造,无额外复杂度。
|
|
184
|
+
* @returns 更新后的行;实体不存在或已软删返回 null
|
|
185
|
+
*/
|
|
186
|
+
export function updateEntity(db, id, patch) {
|
|
187
|
+
return withTransaction(db, () => {
|
|
188
|
+
const existing = getEntity(db, id);
|
|
189
|
+
if (existing === null)
|
|
190
|
+
return null;
|
|
191
|
+
const next = {
|
|
192
|
+
...existing,
|
|
193
|
+
name: patch.name ?? existing.name,
|
|
194
|
+
data: patch.data === undefined ? existing.data : { ...existing.data, ...patch.data },
|
|
195
|
+
updated_at: nowIso(),
|
|
196
|
+
};
|
|
197
|
+
db.prepare("UPDATE entities SET name = ?, data = ?, updated_at = ? WHERE id = ?").run(next.name, JSON.stringify(next.data), next.updated_at, id);
|
|
198
|
+
return next;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 软删实体(DELETE /api/v1/entity/:type/:id,决策 12):
|
|
203
|
+
* **级联软删关联关系与 Delta**(单库内事务保证原子性):
|
|
204
|
+
* - relation_records:source_id = id **或** target_id = id(任一端点软删即不可见,决策 12 修订)
|
|
205
|
+
* - delta_records:target_id = id(目标实体软删即不可见,决策 12 修订)
|
|
206
|
+
* - 自身:deleted_at 置位 + updated_at 刷新(决策 12 修订:软删亦更新版本戳)
|
|
207
|
+
* 幂等:实体已软删(或不存在)→ 返回 null 且无任何副作用(级联 UPDATE 均带 deleted_at IS NULL 过滤)。
|
|
208
|
+
*
|
|
209
|
+
* @returns { relations, deltas } 实际级联行数;实体不存在/已软删返回 null
|
|
210
|
+
*/
|
|
211
|
+
export function softDeleteEntity(db, id, deletedAt) {
|
|
212
|
+
return withTransaction(db, () => {
|
|
213
|
+
const exists = db.prepare("SELECT id FROM entities WHERE id = ? AND deleted_at IS NULL").get(id);
|
|
214
|
+
if (exists === undefined)
|
|
215
|
+
return null;
|
|
216
|
+
const rel = db
|
|
217
|
+
.prepare(`UPDATE relation_records SET deleted_at = ? WHERE deleted_at IS NULL AND (source_id = ? OR target_id = ?)`)
|
|
218
|
+
.run(deletedAt, id, id);
|
|
219
|
+
const delta = db
|
|
220
|
+
.prepare(`UPDATE delta_records SET deleted_at = ? WHERE deleted_at IS NULL AND target_id = ?`)
|
|
221
|
+
.run(deletedAt, id);
|
|
222
|
+
db.prepare("UPDATE entities SET deleted_at = ?, updated_at = ? WHERE id = ? AND deleted_at IS NULL").run(deletedAt, deletedAt, id);
|
|
223
|
+
return { relations: rel.changes, deltas: delta.changes };
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/** 字符串值直方图(非字符串/空串不计入——data 字段稀疏,防御) */
|
|
227
|
+
function countBy(values) {
|
|
228
|
+
const out = {};
|
|
229
|
+
for (const v of values) {
|
|
230
|
+
if (typeof v === "string" && v !== "") {
|
|
231
|
+
out[v] = (out[v] ?? 0) + 1;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
/** abilities 频率统计(数组元素展平计数,取前 limit 名;按频率降序、同频名称序) */
|
|
237
|
+
function topAbilityCounts(rows, limit) {
|
|
238
|
+
const freq = new Map();
|
|
239
|
+
for (const row of rows) {
|
|
240
|
+
const abilities = parseDataColumn(row.data).abilities;
|
|
241
|
+
if (!Array.isArray(abilities))
|
|
242
|
+
continue;
|
|
243
|
+
for (const ability of abilities) {
|
|
244
|
+
if (typeof ability === "string" && ability !== "") {
|
|
245
|
+
freq.set(ability, (freq.get(ability) ?? 0) + 1);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return [...freq.entries()]
|
|
250
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
251
|
+
.slice(0, limit)
|
|
252
|
+
.map(([ability, count]) => ({ ability, count }));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 实体聚合统计(S6.3 工具 get_entity_summary 下沉,tools.md「聚合分析」):
|
|
256
|
+
* 指定类型实体的总数 + 类型专属分布。仅统计非软删实体(决策 12 修订);
|
|
257
|
+
* 分布字段按类型稀疏出现:character→byRole/byStatus/topAbilities、setting→byCategory、
|
|
258
|
+
* location→byType、hook→byStatus/byPayoffTiming;缺字段(data 未填)不报错、不计入。
|
|
259
|
+
*/
|
|
260
|
+
export function getEntitySummaryStats(db, type) {
|
|
261
|
+
const rows = db
|
|
262
|
+
.prepare("SELECT data FROM entities WHERE type = ? AND deleted_at IS NULL")
|
|
263
|
+
.all(type);
|
|
264
|
+
const result = { type, total: rows.length };
|
|
265
|
+
const dataOf = (r) => parseDataColumn(r.data);
|
|
266
|
+
switch (type) {
|
|
267
|
+
case "character":
|
|
268
|
+
result.byRole = countBy(rows.map((r) => dataOf(r).role));
|
|
269
|
+
result.byStatus = countBy(rows.map((r) => dataOf(r).status));
|
|
270
|
+
result.topAbilities = topAbilityCounts(rows, 10);
|
|
271
|
+
break;
|
|
272
|
+
case "setting":
|
|
273
|
+
result.byCategory = countBy(rows.map((r) => dataOf(r).category));
|
|
274
|
+
break;
|
|
275
|
+
case "location":
|
|
276
|
+
result.byType = countBy(rows.map((r) => dataOf(r).type));
|
|
277
|
+
break;
|
|
278
|
+
case "hook":
|
|
279
|
+
result.byStatus = countBy(rows.map((r) => dataOf(r).status));
|
|
280
|
+
result.byPayoffTiming = countBy(rows.map((r) => dataOf(r).payoff_timing));
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity.js","sourceRoot":"","sources":["../../src/queries/entity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { type Db } from "../connection.js";
|
|
2
|
+
import { type Migration } from "../migrations/index.js";
|
|
3
|
+
/** data.db 文件名(决策 8:项目根目录,与 server middleware 的常量一致) */
|
|
4
|
+
export declare const DATA_DB_FILE_NAME = "data.db";
|
|
5
|
+
/** 删库重建的结果(供上层 open 流程提示客户端,endpoints.md 第 69 行) */
|
|
6
|
+
export interface MigrationResult {
|
|
7
|
+
/** 是否发生了删库重建 */
|
|
8
|
+
rebuilt: boolean;
|
|
9
|
+
/** 是否发生了前向迁移(E5:有迁移路径的旧版本经 runMigrations 升级) */
|
|
10
|
+
migrated?: boolean;
|
|
11
|
+
/** 重建/迁移前的旧版本号(rebuilt/migrated 时有值) */
|
|
12
|
+
fromVersion?: number;
|
|
13
|
+
/** 重建/迁移后的版本号(恒等于 SCHEMA_VERSION) */
|
|
14
|
+
toVersion: number;
|
|
15
|
+
/** 重建/迁移产生的备份文件绝对路径(data.db.v{n}.bak / outline.json.v{n}.bak / 迁移快照) */
|
|
16
|
+
backups: string[];
|
|
17
|
+
}
|
|
18
|
+
/** 重建流程的返回:新连接 + 结果(见 rebuildProjectStorage 注释) */
|
|
19
|
+
export interface RebuildOutput {
|
|
20
|
+
/** 重建后的有效连接(旧连接已关闭);未重建时为原连接 */
|
|
21
|
+
db: Db;
|
|
22
|
+
result: MigrationResult;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* data.db user_version 高于当前程序版本(E4 拒绝打开专用错误)。
|
|
26
|
+
*
|
|
27
|
+
* 语义(release-review §一 风险 2):用户安装新版程序后回退旧版 → 旧版程序打开高版本库,
|
|
28
|
+
* 若按「不匹配即重建」处理会把用户全部数据重建清零(且备份仅配合旧版本回滚,普通用户
|
|
29
|
+
* 不可自救)。E4 起该分支改为拒绝打开并抛出本错误——**不触发任何重建/备份/写操作**,
|
|
30
|
+
* 数据文件原封不动;上层(server open 路由)捕获后转 409 + 明确提示升级程序。
|
|
31
|
+
*
|
|
32
|
+
* 独立于 import 侧的 SCHEMA_VERSION_MISMATCH(备份导入校验):本错误是**打开已存在项目**
|
|
33
|
+
* 时发现项目版本高于程序版本;SCHEMA_VERSION_MISMATCH 是导入 zip 备份时版本不匹配。
|
|
34
|
+
*/
|
|
35
|
+
export declare class SchemaVersionError extends Error {
|
|
36
|
+
/** 项目 data.db 的实际 user_version(未来版本) */
|
|
37
|
+
readonly version: number;
|
|
38
|
+
/** 当前程序支持的版本 */
|
|
39
|
+
readonly current: number;
|
|
40
|
+
constructor(version: number, current: number);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* schema 版本检测 + 版本对齐的高层入口(open 流程调用,endpoints.md 第 68-70 行)。
|
|
44
|
+
*
|
|
45
|
+
* - user_version 与 SCHEMA_VERSION **匹配**:直接返回,db 为原连接,rebuilt=false。
|
|
46
|
+
* - user_version **> SCHEMA_VERSION(未来版本,E4)**:**拒绝打开**——关闭连接并抛
|
|
47
|
+
* `SchemaVersionError`(提示升级程序),不触发任何重建/备份/写操作,数据原封不动。
|
|
48
|
+
* - user_version **< SCHEMA_VERSION(旧版本)**:
|
|
49
|
+
* - **有迁移路径**(opts.migrations 中存在从当前版本到 SCHEMA_VERSION 的连续迁移链,
|
|
50
|
+
* 默认 MIGRATIONS)→ runMigrations 前向迁移(迁移前时间戳快照,数据保全完整)
|
|
51
|
+
* - **无迁移路径** → rebuildProjectStorage 删库重建兜底(决策 13,E5 迁移机制
|
|
52
|
+
* 覆盖不到的历史版本保留;备份 v{n}.bak 留档)
|
|
53
|
+
*
|
|
54
|
+
* @param opts.migrations 迁移集注入(默认 MIGRATIONS;测试注入假迁移)
|
|
55
|
+
* @throws SchemaVersionError 未来版本拒绝打开(连接已由本函数关闭,无句柄泄漏)
|
|
56
|
+
* @throws 备份/重建/迁移过程中的 I/O 错误;迁移失败时版本停在前一迁移后(快照保留)
|
|
57
|
+
*/
|
|
58
|
+
export declare function ensureSchemaCompatible(db: Db, dir: string, dbPath: string, opts?: {
|
|
59
|
+
migrations?: readonly Migration[];
|
|
60
|
+
}): RebuildOutput;
|
|
61
|
+
/**
|
|
62
|
+
* 判定从 fromVersion 到 targetVersion 是否存在**连续迁移链**(E5 纯函数):
|
|
63
|
+
* (fromVersion, targetVersion] 区间内每个版本号都恰好有迁移条目 → true。
|
|
64
|
+
* 连续性是硬要求——跳版本迁移意味着中间版本的数据形态未经处理,拒绝走迁移路径。
|
|
65
|
+
*
|
|
66
|
+
* @param migrations 迁移集(默认 MIGRATIONS;测试注入)
|
|
67
|
+
*/
|
|
68
|
+
export declare function hasMigrationPath(fromVersion: number, targetVersion: number, migrations?: readonly Migration[]): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* 前向执行缺失的增量迁移(E5 核心):
|
|
71
|
+
*
|
|
72
|
+
* 1. **迁移前快照一次**(整批;若提供 dbPath):checkpoint 后复制 data.db →
|
|
73
|
+
* `data.db.v{from}.{YYYYMMDDHHmmssZ}.bak`(时间戳命名,不覆盖旧备份)——
|
|
74
|
+
* 失败重试时现场保留(每次调用生成新时间戳快照,旧快照不删)
|
|
75
|
+
* 2. **逐个执行** (fromVersion, targetVersion] 区间内按 version 升序的迁移:
|
|
76
|
+
* 每个迁移一个事务(`db.transaction`):`up(db)` + `setUserVersion(m.version)`
|
|
77
|
+
* **原子提交**——「迁移成功 ⇒ 版本已写入;失败 ⇒ 版本未变」;失败时该迁移整体
|
|
78
|
+
* 回滚(含 DDL 与已写数据)、后续迁移不执行、异常向上传播(快照保留供重试)
|
|
79
|
+
*
|
|
80
|
+
* @param opts.dbPath data.db 绝对路径(提供则迁移前快照;缺省跳过快照——纯逻辑调用)
|
|
81
|
+
* @returns 实际执行的迁移(升序)与快照路径(未快照为 null)
|
|
82
|
+
*/
|
|
83
|
+
export declare function runMigrations(db: Db, opts?: {
|
|
84
|
+
migrations?: readonly Migration[];
|
|
85
|
+
targetVersion?: number;
|
|
86
|
+
dbPath?: string;
|
|
87
|
+
}): {
|
|
88
|
+
applied: Migration[];
|
|
89
|
+
snapshot: string | null;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* 迁移前快照 data.db → `data.db.v{n}.{YYYYMMDDHHmmssSSSZ}.bak`(E5):
|
|
93
|
+
* 复用 rebuild 备份的复制语义(copyFileSync 复制主文件),命名带 UTC 时间戳
|
|
94
|
+
* (含毫秒:20260804T173000.123Z)——同版本多次迁移/重试各自留档,不覆盖。
|
|
95
|
+
* WAL 一致性同 backupDbFile:调用方需先 checkpoint(runMigrations 已做)。
|
|
96
|
+
*/
|
|
97
|
+
export declare function snapshotDbFile(dbPath: string, fromVersion: number): string;
|
|
98
|
+
/**
|
|
99
|
+
* WAL checkpoint(TRUNCATE):把 WAL 内容合并回主文件并截断(busy>0 说明有其他连接
|
|
100
|
+
* 在写——MVP 单进程单连接,不检查)。供**导出前**调用(E1:release-review §二)——
|
|
101
|
+
* 保证 data.db 主文件为完整快照,导出 zip 内的 data.db 无需附带 -wal/-shm。
|
|
102
|
+
*/
|
|
103
|
+
export declare function checkpointWal(db: Db): void;
|
|
104
|
+
/**
|
|
105
|
+
* 删库重建全流程(决策 13):
|
|
106
|
+
*
|
|
107
|
+
* 1. **checkpoint + 关闭连接**:`wal_checkpoint(TRUNCATE)` 把 WAL 内容合并回主文件并截断,
|
|
108
|
+
* 然后关闭连接——保证后续复制的是完整快照(WAL 一致性取舍见 backupDbFile 注释)
|
|
109
|
+
* 2. **备份 data.db** → `data.db.v{n}.bak`(n=旧版本号;文件复制,原文件随后删除重建)
|
|
110
|
+
* 3. **备份 outline.json** → `outline.json.v{n}.bak`(文件复制;非 SQLite 文件无 WAL 问题,
|
|
111
|
+
* 复制保留原始字节,不存在时跳过备份)
|
|
112
|
+
* 4. **删除旧库文件**(data.db + -wal + -shm,checkpoint 后 -wal 已截断但文件仍在,一并删除)
|
|
113
|
+
* 5. **重建空库**:openDatabase(自动建表)→ setUserVersion(SCHEMA_VERSION)
|
|
114
|
+
* 6. **重置 outline.json**:写最小空树(严格三层空树,决策 19;与 T2.2 readOutlineFile
|
|
115
|
+
* 缺失语义的空树同形)——顶层 schema_version 同步写 SCHEMA_VERSION(决策 13 修订)
|
|
116
|
+
*
|
|
117
|
+
* **清空回收站**:新库四张表全空,软删行(deleted_at 非 NULL)天然不存在,无需单独操作。
|
|
118
|
+
* **project.json 不动**:其 schema_version 仅用于 JSON 结构判断(决策 13),id/name 跨启动稳定。
|
|
119
|
+
*
|
|
120
|
+
* **崩溃窗口(MVP 可接受)**:步骤 4(删除旧库)与步骤 5(重建空库)之间进程崩溃 →
|
|
121
|
+
* 磁盘上无 data.db(下次 open 自动重建空库),且旧库备份已留档不丢数据;
|
|
122
|
+
* 但同版本号多次重建会覆盖旧备份(命名 v{n}.bak 不带时间戳,决策 13 修订的
|
|
123
|
+
* 「各自留档」仅对不同版本号成立)——MVP 阶段无真实用户数据,可接受。
|
|
124
|
+
*
|
|
125
|
+
* @param db 待重建的连接(流程内关闭;调用方丢弃原引用,使用返回值中的新连接)
|
|
126
|
+
* @param dir 项目根目录
|
|
127
|
+
* @param dbPath data.db 绝对路径
|
|
128
|
+
* @param oldVersion 重建前的旧版本号(备份文件名与 fromVersion 用)
|
|
129
|
+
* @returns 新连接(已开库、版本号已写、表已建)+ 重建结果
|
|
130
|
+
*/
|
|
131
|
+
export declare function rebuildProjectStorage(db: Db, dir: string, dbPath: string, oldVersion: number): RebuildOutput;
|
|
132
|
+
/**
|
|
133
|
+
* 备份 data.db 主文件 → `data.db.v{n}.bak`(决策 13:备份带版本号、不覆盖旧备份)。
|
|
134
|
+
*
|
|
135
|
+
* **WAL 一致性取舍**:调用方必须在连接关闭前先执行 `wal_checkpoint(TRUNCATE)`(rebuildProjectStorage
|
|
136
|
+
* 已做)——checkpoint 后主文件即完整快照,只需复制主文件;-wal/-shm 内容已合并进主文件
|
|
137
|
+
* (TRUNCATE 后 -wal 为空文件),不再复制。若库处于 DELETE journal 模式(从未开过 WAL),
|
|
138
|
+
* 主文件本身即全部数据,同样安全。
|
|
139
|
+
*
|
|
140
|
+
* @returns 备份文件绝对路径
|
|
141
|
+
*/
|
|
142
|
+
export declare function backupDbFile(dbPath: string, oldVersion: number): string;
|
|
143
|
+
/**
|
|
144
|
+
* 备份 outline.json → `outline.json.v{n}.bak`(决策 13 修订:重建时同步重置并留档)。
|
|
145
|
+
*
|
|
146
|
+
* 取舍:用复制而非 rename——outline.json 不是 SQLite 文件、无 WAL 问题,复制保留原始字节
|
|
147
|
+
* (含格式/顺序);rename 会把原文件移走,与「随后重置为空树」的流程相斥。
|
|
148
|
+
* outline.json 不存在(异常状态)时跳过备份返回 null——重置仍会执行(写空树)。
|
|
149
|
+
*
|
|
150
|
+
* @returns 备份文件绝对路径;文件不存在返回 null
|
|
151
|
+
*/
|
|
152
|
+
export declare function backupOutlineFile(dir: string, oldVersion: number): string | null;
|
|
153
|
+
//# sourceMappingURL=migration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/queries/migration.ts"],"names":[],"mappings":"AAoBA,OAAO,EAA+B,KAAK,EAAE,EAAE,MAAM,kBAAkB,CAAC;AAGxE,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEpE,wDAAwD;AACxD,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAE3C,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,gBAAgB;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC5B,gCAAgC;IAChC,EAAE,EAAE,EAAE,CAAC;IACP,MAAM,EAAE,eAAe,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,wCAAwC;IACxC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,gBAAgB;IAChB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBACb,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAM7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,EAAE,EACN,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;CAAO,GAC/C,aAAa,CAoCf;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EACrB,UAAU,GAAE,SAAS,SAAS,EAAe,GAC5C,OAAO,CAOT;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,EAAE,EACN,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACxF;IAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAyBnD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAK1E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,EAAE,GAAG,IAAI,CAE1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,CA2B5G;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAIvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhF"}
|