@whispering233/ai-editor-server 0.0.4 → 0.0.6

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/backup.js ADDED
@@ -0,0 +1,629 @@
1
+ // 自动备份与恢复(B2.2,决策 27)
2
+ //
3
+ // 单一事实来源:doc/design/decisions.md 决策 27、doc/api/endpoints.md「备份管理」节。
4
+ // 职责:
5
+ // - 备份管道:三文件 + wal_checkpoint → .backups/<YYYYMMDD-HHmmss>.zip(复用 E1 打包)
6
+ // - 保留策略:每项目保留最近 MAX_BACKUPS_PER_PROJECT 份,超出删除最旧(含覆盖前快照)
7
+ // - 自动定时器:跟随当前项目生命周期(middleware/project.ts setCurrentProject 挂载启停),
8
+ // 有变更才备份(三文件 mtime 与 .backups/ 最新备份时间比较,无状态、服务重启不丢)
9
+ // - 备份包校验(restore 与 import 共用):zip 解析/白名单/三文件齐全/顶层契约/
10
+ // data.db user_version 三态分流(E4/E5,绝不静默重建)
11
+ // - restore:fileName 白名单 → 覆盖前自动快照 → 校验 → 原子替换三文件
12
+ //
13
+ // 依赖方向:本模块不依赖 middleware(避免循环依赖——定时器持有所调度项目引用,
14
+ // 由 setCurrentProject 显式启停);仅 import type ProjectContext(类型擦除)。
15
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
16
+ import { closeSync, fsyncSync, openSync } from "node:fs";
17
+ import { mkdtempSync, rmSync } from "node:fs";
18
+ import { tmpdir } from "node:os";
19
+ import { basename, dirname, join } from "node:path";
20
+ import { Unzip, UnzipInflate, zipSync } from "fflate";
21
+ import { BACKUP_FREQUENCIES, DEFAULT_BACKUP_FREQUENCY_MINUTES, formatBackupFileName, MAX_BACKUPS_PER_PROJECT, parseBackupFileName } from "@whispering233/ai-editor-shared";
22
+ import { PROJECT_EXPORT_FILE_NAMES } from "@whispering233/ai-editor-shared/schemas";
23
+ import { closeDatabase, checkpointWal, DATA_DB_FILE_NAME, ensureSchemaCompatible, getUserVersion, hasMigrationPath, migrateChatMessagesProject, openDatabase, OUTLINE_FILE_NAME, PROJECT_FILE_NAME, readProjectFile, SCHEMA_VERSION, } from "@whispering233/ai-editor-db";
24
+ import { HttpError } from "./middleware/error.js";
25
+ /** 备份目录名(决策 27:项目目录内 .backups/,随书籍移动自然携带) */
26
+ export const BACKUPS_DIR_NAME = ".backups";
27
+ /** 解压总字节预算(200MB,zip 炸弹防御——与 import 同款,restore 复用) */
28
+ const MAX_UNZIP_BUDGET = 200 * 1024 * 1024;
29
+ /**
30
+ * mtime 变更判定容差(毫秒):备份管道内 wal_checkpoint 会把 WAL 合并回 data.db 主文件,
31
+ * 其 mtime 刷新到「备份时刻」(毫秒精度),而上次备份时刻来自备份文件名(秒精度截断)——
32
+ * 若用严格 `mtime > lastBackupAt` 判定,checkpoint 后的 data.db mtime 恒晚于文件名秒时间,
33
+ * 每轮 tick 都会误判「有变更」产生自激垃圾备份。加 1s 容差:备份后三文件 mtime 落在
34
+ * [备份时刻秒, 备份时刻秒+1s) 内一律视为「已包含在本次备份中」;最小 tick 5 分钟,
35
+ * 用户变更必然超出容差窗口。
36
+ */
37
+ const BACKUP_CHANGE_TOLERANCE_MS = 1000;
38
+ // ============ 备份管道(复用 E1 打包,避免复制) ============
39
+ /**
40
+ * 打包当前项目三文件为 zip(E1 export 同款管道):
41
+ * wal_checkpoint(TRUNCATE) 把 WAL 合并回主文件(zip 内 data.db 为完整快照,
42
+ * 无需附带 -wal/-shm)→ zipSync 打包(键序稳定:project.json → outline.json → data.db)。
43
+ * 三文件缺失任一 → 抛错(打开的项目三文件必然齐全,缺失即损坏,不导出半成品包)。
44
+ */
45
+ export function createBackupZip(project) {
46
+ const dir = project.root;
47
+ checkpointWal(project.db);
48
+ return zipSync({
49
+ [PROJECT_FILE_NAME]: readFileSync(join(dir, PROJECT_FILE_NAME)),
50
+ [OUTLINE_FILE_NAME]: readFileSync(join(dir, OUTLINE_FILE_NAME)),
51
+ [DATA_DB_FILE_NAME]: readFileSync(join(dir, DATA_DB_FILE_NAME)),
52
+ }, { level: 6 });
53
+ }
54
+ /**
55
+ * 生成不冲突的备份文件名:`<YYYYMMDD-HHmmss>.zip`(shared formatBackupFileName)。
56
+ *
57
+ * 同秒冲突(如「立即备份 + restore 覆盖前快照」连续触发)处理:时间戳 +1 秒循环去重,
58
+ * **保持 <YYYYMMDD-HHmmss>.zip 格式契约**——不追加毫秒/后缀(那会破坏
59
+ * parseBackupFileName 解析与 restore 白名单校验),加 1 秒后文件名仍合法且排序位置正确。
60
+ */
61
+ function uniqueBackupFileName(backupsDir, date) {
62
+ let name = formatBackupFileName(date);
63
+ while (existsSync(join(backupsDir, name))) {
64
+ date = new Date(date.getTime() + 1000);
65
+ name = formatBackupFileName(date);
66
+ }
67
+ return name;
68
+ }
69
+ /**
70
+ * 立即备份当前项目(手动触发 / 自动定时器 / restore 覆盖前快照共用):
71
+ * 打包 → 写入 .backups/<时间戳>.zip(同秒去重)→ 触发保留策略清理(失败不阻塞)。
72
+ * 写盘失败向上抛(errorHandler → 500 INTERNAL_ERROR,endpoints.md POST /backup 语义)。
73
+ */
74
+ export function writeBackup(project) {
75
+ const zip = createBackupZip(project);
76
+ const backupsDir = join(project.root, BACKUPS_DIR_NAME);
77
+ mkdirSync(backupsDir, { recursive: true });
78
+ const fileName = uniqueBackupFileName(backupsDir, new Date());
79
+ writeFileSync(join(backupsDir, fileName), zip); // 失败抛错 → 500(不产出半截备份)
80
+ pruneBackups(backupsDir); // 清理失败仅记日志,不阻塞备份主流程
81
+ return { fileName, size: zip.length, createdAt: toIso(parseBackupFileName(fileName)) };
82
+ }
83
+ /**
84
+ * 保留策略(决策 27):每项目保留最近 MAX_BACKUPS_PER_PROJECT 份,超出删除最旧。
85
+ * 排序依据 = 文件名时间戳(可解析的才参与;非法文件名不参与保留判定)。
86
+ * 清理失败不阻塞调用方(记日志即可,决策 27「清理失败不阻塞备份主流程」)。
87
+ */
88
+ export function pruneBackups(backupsDir) {
89
+ let files;
90
+ try {
91
+ files = readdirSync(backupsDir);
92
+ }
93
+ catch {
94
+ return; // 目录不存在 → 无事可做
95
+ }
96
+ const parsed = files
97
+ .map((f) => ({ fileName: f, time: parseBackupFileName(f) }))
98
+ .filter((x) => x.time !== null)
99
+ .sort((a, b) => b.time.getTime() - a.time.getTime()); // 最新在前
100
+ for (const extra of parsed.slice(MAX_BACKUPS_PER_PROJECT)) {
101
+ try {
102
+ unlinkSync(join(backupsDir, extra.fileName));
103
+ }
104
+ catch (err) {
105
+ console.error(`[backup] 清理旧备份失败(跳过,不阻塞): ${extra.fileName}`, err);
106
+ }
107
+ }
108
+ }
109
+ /** 备份列表(GET /backups):时间倒序(最新在前);.backups/ 不存在 → 空数组(不报错) */
110
+ export function listBackups(project) {
111
+ const backupsDir = join(project.root, BACKUPS_DIR_NAME);
112
+ let files;
113
+ try {
114
+ files = readdirSync(backupsDir);
115
+ }
116
+ catch {
117
+ return [];
118
+ }
119
+ return files
120
+ .map((f) => {
121
+ const time = parseBackupFileName(f);
122
+ if (time === null)
123
+ return null; // 非法文件名(手工放入等)不展示
124
+ try {
125
+ return { fileName: f, size: statSync(join(backupsDir, f)).size, createdAt: toIso(time) };
126
+ }
127
+ catch {
128
+ return null; // 列表读取瞬间被删(竞态)→ 跳过
129
+ }
130
+ })
131
+ .filter((x) => x !== null)
132
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); // ISO 字符串字典序 = 时间序
133
+ }
134
+ /** Date → ISO 8601(文件名时间戳解析结果转列表/响应字段) */
135
+ function toIso(date) {
136
+ return date.toISOString();
137
+ }
138
+ // ============ 备份包校验(restore 与 import 共用,import 校验顺序 3-7 提取) ============
139
+ /** 拼接 Uint8Array 分块(Unzip ondata 流累计) */
140
+ function concatChunks(chunks, totalSize) {
141
+ const out = new Uint8Array(totalSize);
142
+ let offset = 0;
143
+ for (const c of chunks) {
144
+ out.set(c, offset);
145
+ offset += c.length;
146
+ }
147
+ return out;
148
+ }
149
+ /**
150
+ * 带预算的 zip 解压(ora-4 zip 炸弹防御,自 E2 import 提取):
151
+ * fflate Unzip 流式解压(同步 push)——onfile 回调中**只解压白名单条目**(未知条目
152
+ * 仅记名不 start,省预算;白名单检查由调用方用 names 列表执行),ondata 累计解压总字节,
153
+ * 超过预算抛 HttpError 中止。返回 { entries(白名单条目解压结果), names(全部条目名)}。
154
+ */
155
+ function unzipWithBudget(zipData, budget) {
156
+ const entries = {};
157
+ const names = [];
158
+ let total = 0;
159
+ const unzipper = new Unzip((file) => {
160
+ names.push(file.name);
161
+ if (!PROJECT_EXPORT_FILE_NAMES.includes(file.name))
162
+ return; // 未知条目不解压
163
+ const chunks = [];
164
+ let size = 0;
165
+ file.ondata = (err, chunk, final) => {
166
+ if (err)
167
+ throw err;
168
+ size += chunk.length;
169
+ total += chunk.length;
170
+ if (total > budget) {
171
+ throw new HttpError(400, "VALIDATION_ERROR", `备份包解压超出预算(${budget} 字节上限,zip 炸弹防御)`);
172
+ }
173
+ chunks.push(chunk);
174
+ if (final)
175
+ entries[file.name] = concatChunks(chunks, size);
176
+ };
177
+ file.start();
178
+ });
179
+ // 关键(fflate API 契约):Unzip 默认仅注册 stored(0) 解码器——deflate(8) 压缩的
180
+ // zip(zipSync 默认)必须显式 register(UnzipInflate),否则 start() 报 unknown
181
+ // compression type(fflate README 明确要求)
182
+ unzipper.register(UnzipInflate);
183
+ unzipper.push(zipData, true);
184
+ // 防御:无 EOCD/零条目的输入 Unzip 流式**静默返回空**(unzipSync 会抛)——
185
+ // 显式判定坏包(非 zip 内容 → 「不是有效的项目备份包」而非「缺少文件」)
186
+ if (names.length === 0) {
187
+ throw new HttpError(400, "VALIDATION_ERROR", "不是有效的项目备份包(zip 解析失败)");
188
+ }
189
+ return { entries, names };
190
+ }
191
+ /** project.json 顶层契约最小校验(shared 无文件形态 schema,不扩契约范围) */
192
+ function isValidProjectFile(parsed) {
193
+ if (typeof parsed !== "object" || parsed === null)
194
+ return false;
195
+ const p = parsed;
196
+ return typeof p.id === "string" && p.id.length > 0 && typeof p.name === "string" && typeof p.schema_version === "number";
197
+ }
198
+ /** outline.json 顶层契约校验(与 db 包 validateOutlineFile 同构:root 根 + schema_version 数字 + children 数组) */
199
+ function isValidOutlineFile(parsed) {
200
+ if (typeof parsed !== "object" || parsed === null)
201
+ return false;
202
+ const p = parsed;
203
+ return p.id === "root" && p.type === "root" && typeof p.schema_version === "number" && Array.isArray(p.children);
204
+ }
205
+ /**
206
+ * 备份包完整校验(自 E2 import 校验顺序 3-7 提取,restore 与 import 共用):
207
+ *
208
+ * 1. zip 解析(流式 + 解压总字节预算 200MB,zip 炸弹防御;失败 400「不是有效的项目备份包」)
209
+ * 2. 条目白名单(只接受 PROJECT_EXPORT_FILE_NAMES 三文件名,逐名比对天然防 zip 路径穿越)
210
+ * 3. 三文件齐全
211
+ * 4. 临时目录写入 → project.json / outline.json 顶层契约(JSON 损坏 / 契约不符 → 400)
212
+ * 5. data.db 校验(**大小 > 0 → 打开成功 → user_version**):
213
+ * - v === SCHEMA_VERSION → 接受(现状)
214
+ * - v < SCHEMA_VERSION → **有迁移路径** → 接受(open/restore 后自动前向迁移,E5);
215
+ * **无迁移路径** → 409(文案标注版本过旧无路径)
216
+ * - v > SCHEMA_VERSION → 409(E4 语义:备份来自更高版本程序)
217
+ * 拒绝均不静默重建——恢复备份须明示版本不兼容。
218
+ *
219
+ * 校验在 mkdtemp 临时目录完成(finally 清理,无论成败);通过后返回解压内容与
220
+ * project_id(调用方负责搬入/替换)。**校验通过前不触碰目标项目任何数据**(零触碰)。
221
+ *
222
+ * @throws HttpError 400(坏包/缺文件/未知条目/契约不符)/ 409 SCHEMA_VERSION_MISMATCH
223
+ */
224
+ export function validateBackupPackage(zipData) {
225
+ // 1/2. 解压 + 白名单(严格拒绝):只接受三数据文件名
226
+ let entries;
227
+ let entryNames;
228
+ try {
229
+ const result = unzipWithBudget(zipData, MAX_UNZIP_BUDGET);
230
+ entries = result.entries;
231
+ entryNames = result.names;
232
+ }
233
+ catch (err) {
234
+ if (err instanceof HttpError)
235
+ throw err;
236
+ throw new HttpError(400, "VALIDATION_ERROR", "不是有效的项目备份包(zip 解析失败)");
237
+ }
238
+ const unknown = entryNames.filter((k) => !PROJECT_EXPORT_FILE_NAMES.includes(k));
239
+ if (unknown.length > 0) {
240
+ throw new HttpError(400, "VALIDATION_ERROR", `备份包含未知条目: ${unknown.join(", ")}(只接受 ${PROJECT_EXPORT_FILE_NAMES.join("/")})`);
241
+ }
242
+ const missing = PROJECT_EXPORT_FILE_NAMES.filter((f) => !(f in entries));
243
+ if (missing.length > 0) {
244
+ throw new HttpError(400, "VALIDATION_ERROR", `备份包缺少文件: ${missing.join(", ")}(非完整项目备份)`);
245
+ }
246
+ // 3-5. 临时目录校验(全部通过才算有效;finally 清理)
247
+ const tmpDir = mkdtempSync(join(tmpdir(), "ai-editor-restore-"));
248
+ try {
249
+ for (const f of PROJECT_EXPORT_FILE_NAMES) {
250
+ writeFileSync(join(tmpDir, f), entries[f]);
251
+ }
252
+ // project.json 顶层契约(JSON 可解析 + id/name/schema_version)
253
+ let projectConfig;
254
+ try {
255
+ projectConfig = JSON.parse(readFileSync(join(tmpDir, PROJECT_FILE_NAME), "utf8"));
256
+ }
257
+ catch {
258
+ throw new HttpError(400, "VALIDATION_ERROR", "备份包内 project.json 不是合法 JSON");
259
+ }
260
+ if (!isValidProjectFile(projectConfig)) {
261
+ throw new HttpError(400, "VALIDATION_ERROR", "备份包内 project.json 顶层契约不符(需 id/name/schema_version)");
262
+ }
263
+ // outline.json 顶层契约({id:"root",type:"root",schema_version,children[]})
264
+ try {
265
+ const outline = JSON.parse(readFileSync(join(tmpDir, OUTLINE_FILE_NAME), "utf8"));
266
+ if (!isValidOutlineFile(outline)) {
267
+ throw new HttpError(400, "VALIDATION_ERROR", "备份包内 outline.json 顶层契约不符(需 {id:root,type:root,schema_version,children[]})");
268
+ }
269
+ }
270
+ catch (err) {
271
+ if (err instanceof HttpError)
272
+ throw err;
273
+ throw new HttpError(400, "VALIDATION_ERROR", "备份包内 outline.json 不是合法 JSON");
274
+ }
275
+ // data.db 校验顺序(ora-4):**文件大小 > 0 → 打开成功 → user_version**——
276
+ // 0 字节文件 SQLite 会当新库打开(user_version=0),必须先按坏包拒绝(400 而非 409);
277
+ // 非 SQLite 内容打开失败 → 400 坏包。
278
+ // user_version 判定(E5 决议,tasks.md E5 卡规格追加句):
279
+ // v === SCHEMA_VERSION → 接受(现状)
280
+ // v < SCHEMA_VERSION → **有迁移路径**(MIGRATIONS 存在连续链,替换后 open 时自动前向
281
+ // 迁移,数据保全完整)→ 接受;**无迁移路径** → 409(文案标注版本过旧无路径)
282
+ // v > SCHEMA_VERSION → 409(E4 语义:备份来自更高版本程序)
283
+ const dbPath = join(tmpDir, DATA_DB_FILE_NAME);
284
+ if (statSync(dbPath).size === 0) {
285
+ throw new HttpError(400, "VALIDATION_ERROR", "备份包内 data.db 为空文件(损坏包)");
286
+ }
287
+ let db = null;
288
+ try {
289
+ db = openDatabase(dbPath);
290
+ const v = getUserVersion(db);
291
+ const acceptable = v === SCHEMA_VERSION || (v < SCHEMA_VERSION && hasMigrationPath(v, SCHEMA_VERSION));
292
+ if (!acceptable) {
293
+ const hint = v > SCHEMA_VERSION ? "备份来自更高版本程序" : "备份来自旧版本程序且无可用迁移路径";
294
+ throw new HttpError(409, "SCHEMA_VERSION_MISMATCH", `备份包 data.db 版本 (${v}) ${hint}(当前程序 ${SCHEMA_VERSION}),暂不支持导入或恢复`);
295
+ }
296
+ }
297
+ catch (err) {
298
+ if (err instanceof HttpError)
299
+ throw err;
300
+ throw new HttpError(400, "VALIDATION_ERROR", "备份包内 data.db 不是有效的 SQLite 数据库");
301
+ }
302
+ finally {
303
+ if (db !== null)
304
+ closeDatabase(db);
305
+ }
306
+ return { entries, projectId: projectConfig.id };
307
+ }
308
+ finally {
309
+ rmSync(tmpDir, { recursive: true, force: true });
310
+ }
311
+ }
312
+ // ============ 自动定时器(决策 27:有变更才备份,跟随项目生命周期) ============
313
+ /** 备份频率读侧语义(与 shared 读映射一致 + B2.1 疑问裁决 2):缺失 → 缺省 10;null/0/非枚举 → 关闭(null) */
314
+ function resolveBackupFrequency(value) {
315
+ if (value === undefined)
316
+ return DEFAULT_BACKUP_FREQUENCY_MINUTES;
317
+ if (value === null || value === 0)
318
+ return null;
319
+ return BACKUP_FREQUENCIES.includes(value) ? value : null;
320
+ }
321
+ /** .backups/ 中最新备份时间(文件名时间戳最大值);目录不存在/无合法备份 → null */
322
+ function latestBackupTime(backupsDir) {
323
+ let files;
324
+ try {
325
+ files = readdirSync(backupsDir);
326
+ }
327
+ catch {
328
+ return null;
329
+ }
330
+ let latest = null;
331
+ for (const f of files) {
332
+ const t = parseBackupFileName(f);
333
+ if (t !== null && (latest === null || t.getTime() > latest.getTime()))
334
+ latest = t;
335
+ }
336
+ return latest;
337
+ }
338
+ /**
339
+ * 三文件是否在 since 之后有变更(决策 27「任一 mtime 晚于上次备份时刻」):
340
+ * 任一文件 mtime > since + 容差 → 有变更;文件缺失 → 视为有变更(防御:不静默跳过,
341
+ * 让备份管道报错暴露损坏);mtime 判定见 BACKUP_CHANGE_TOLERANCE_MS 注释。
342
+ */
343
+ function hasFileChangesSince(project, since) {
344
+ const limit = since.getTime() + BACKUP_CHANGE_TOLERANCE_MS;
345
+ for (const name of [PROJECT_FILE_NAME, OUTLINE_FILE_NAME, DATA_DB_FILE_NAME]) {
346
+ try {
347
+ if (statSync(join(project.root, name)).mtimeMs > limit)
348
+ return true;
349
+ }
350
+ catch {
351
+ return true;
352
+ }
353
+ }
354
+ return false;
355
+ }
356
+ /**
357
+ * 自动备份单次检查(定时器 tick 核心,纯同步、可单测):
358
+ *
359
+ * 1. 频率判定:关闭(null/0/非枚举)→ 直接返回 false(决策 27:null/0 = 关闭;
360
+ * B2.1 疑问裁决 2:读侧非枚举值按关闭处理,不开启自动备份)
361
+ * 2. 变更判定:`.backups/` 为空(无上次备份时刻)→ 需要备份;否则三文件 mtime
362
+ * 均早于「上次备份时刻 + 容差」→ 跳过(无变更不产生垃圾备份,决策 27)
363
+ * 3. 有变更 → writeBackup(含保留策略清理)
364
+ *
365
+ * @returns 本次是否生成了备份
366
+ */
367
+ export function maybeAutoBackup(project) {
368
+ if (resolveBackupFrequency(project.config.backup_frequency_minutes) === null)
369
+ return false;
370
+ const backupsDir = join(project.root, BACKUPS_DIR_NAME);
371
+ const lastBackupAt = latestBackupTime(backupsDir);
372
+ if (lastBackupAt !== null && !hasFileChangesSince(project, lastBackupAt))
373
+ return false;
374
+ writeBackup(project);
375
+ return true;
376
+ }
377
+ // ============ 定时器调度(跟随当前项目生命周期,middleware setCurrentProject 挂载) ============
378
+ /** 当前排程的定时器句柄(null = 未排程/已停止) */
379
+ let backupTimer = null;
380
+ /** 定时器当前服务的项目(stop 时清空;restore 替换 config/db 后同一引用仍有效) */
381
+ let scheduledProject = null;
382
+ /**
383
+ * 启动/重启自动备份调度(open/切换项目时调用;restore 后频率可能变化也调用):
384
+ * 按当前项目频率 setTimeout 链——每 tick 检查「有变更才备份」后按最新频率重新排程
385
+ * (tick 内重读 config,restore 改变频率无需显式通知)。
386
+ * 频率关闭(null/0/非枚举)→ 不排程(等价停止)。
387
+ */
388
+ export function startAutoBackup(project) {
389
+ stopAutoBackup();
390
+ scheduledProject = project;
391
+ scheduleNext(project);
392
+ }
393
+ /** 停止自动备份调度(close 项目/服务关闭时调用;幂等) */
394
+ export function stopAutoBackup() {
395
+ if (backupTimer !== null) {
396
+ clearTimeout(backupTimer);
397
+ backupTimer = null;
398
+ }
399
+ scheduledProject = null;
400
+ }
401
+ /** 按项目当前频率排程下一次检查;频率关闭 → 不排程 */
402
+ function scheduleNext(project) {
403
+ const freq = resolveBackupFrequency(project.config.backup_frequency_minutes);
404
+ if (freq === null)
405
+ return;
406
+ backupTimer = setTimeout(() => {
407
+ backupTimer = null;
408
+ try {
409
+ maybeAutoBackup(project); // 单次检查失败(如 I/O 错)不中断调度,记日志继续
410
+ }
411
+ catch (err) {
412
+ console.error("[backup] 自动备份检查失败:", err);
413
+ }
414
+ scheduleNext(project); // 每次 tick 重读频率(restore/切换可能改变)
415
+ }, freq * 60_000);
416
+ // unref:定时器不阻止进程退出(测试/服务关闭后无残留句柄)
417
+ backupTimer.unref?.();
418
+ }
419
+ /** 当前被定时器服务的项目(测试用;null = 未排程) */
420
+ export function getScheduledProject() {
421
+ return scheduledProject;
422
+ }
423
+ // ============ 恢复(restore:白名单 → 快照 → 校验 → 原子替换) ============
424
+ // ============ 覆盖恢复共享管道(restore / import 覆盖 / 新书导入共用,B2.3 提取) ============
425
+ /**
426
+ * 用备份包内容替换目录三文件(restore 与 import 分流共用,B2.3 提取):
427
+ *
428
+ * - project.json:JSON 解析后按 opts 覆盖——`keepId`(决策 27:覆盖恢复以 project_id 为
429
+ * 唯一 key,换 id 即断连 chat_messages 会话历史——决策 18;备份包可能来自异项目,
430
+ * 防御性强制保留)与 `name`(「目录名 = 书名」不变式:import 覆盖归一为目录名、
431
+ * 新书导入同步为去重名);其余字段随备份替换。序列化走 2 空格缩进 + 尾换行
432
+ * (与 db 包 writeJsonAtomic 同款格式惯例)
433
+ * - outline.json / data.db:原样字节原子写
434
+ *
435
+ * 注意:本函数只管文件层,不含 data.db 连接管理(重连见 overwriteProjectFiles)。
436
+ */
437
+ export function writeProjectFilesFromBackup(dir, entries, opts = {}) {
438
+ // 替换顺序:JSON 两文件在前,data.db 最后——db 替换失败时其余已替换(校验已通过,
439
+ // 文件内容本身有效,部分替换可经重新恢复修复);replaced 清单供失败日志使用(P1-2)
440
+ const targetNames = [PROJECT_FILE_NAME, OUTLINE_FILE_NAME, DATA_DB_FILE_NAME];
441
+ const replaced = [];
442
+ try {
443
+ const parsedProject = JSON.parse(new TextDecoder().decode(entries[PROJECT_FILE_NAME]));
444
+ const nextProject = {
445
+ ...parsedProject,
446
+ ...(opts.keepId !== undefined ? { id: opts.keepId } : {}),
447
+ ...(opts.name !== undefined ? { name: opts.name } : {}),
448
+ };
449
+ writeFileAtomic(join(dir, PROJECT_FILE_NAME), new TextEncoder().encode(`${JSON.stringify(nextProject, null, 2)}\n`));
450
+ replaced.push(PROJECT_FILE_NAME);
451
+ writeFileAtomic(join(dir, OUTLINE_FILE_NAME), entries[OUTLINE_FILE_NAME]);
452
+ replaced.push(OUTLINE_FILE_NAME);
453
+ writeFileAtomic(join(dir, DATA_DB_FILE_NAME), entries[DATA_DB_FILE_NAME]);
454
+ replaced.push(DATA_DB_FILE_NAME);
455
+ }
456
+ catch (err) {
457
+ // P1-2:失败路径日志(对齐「记日志暴露部分替换」承诺)——已替换/未替换文件清单 + 覆盖前快照名
458
+ const notReplaced = targetNames.filter((n) => !replaced.includes(n));
459
+ console.error(`[backup] 覆盖文件替换失败(已替换: ${replaced.join(", ") || "无"};未替换: ${notReplaced.join(", ") || "无"}${opts.snapshotFileName !== undefined ? `;覆盖前快照: ${opts.snapshotFileName}` : ""})`, err);
460
+ throw err;
461
+ }
462
+ }
463
+ /**
464
+ * 覆盖管道(restore 与 import 覆盖当前打开的书共用,B2.3 从 restoreBackup 提取):
465
+ *
466
+ * 1. 释放当前 data.db 连接(替换 data.db 前必须;顺带清理陈旧 WAL/SHM 残留——
467
+ * 替换后新库不得复用旧 WAL)
468
+ * 2. writeProjectFilesFromBackup 原子替换三文件(保留当前项目 id;opts.name 归一)
469
+ * 3. 重连 + 版本对齐(v < 当前且有迁移路径 → 前向迁移,E5;v === 当前 → 原样。
470
+ * 校验阶段已拒绝 v > 当前与无路径旧版,此处不会触发重建/拒绝)
471
+ * 4. 同步内存 config(刚原子写入,readProjectFile 必非 null;损坏抛错由 catch 恢复连接)
472
+ * 5. 重启定时器(备份包内频率可能不同:5 → 60 等)
473
+ *
474
+ * 调用方职责:覆盖前自动快照 + 备份包校验(validateBackupPackage)。
475
+ * 失败时尝试恢复连接(原库可能未被替换或已替换为校验过的有效库),保证后续请求
476
+ * 不因悬挂连接报 500「connection not open」;**重连失败时连接悬挂**——由路由层
477
+ * (restore/import)在 catch 后检查 project.db.open 清空 currentProject 单例
478
+ * (backup 模块不依赖 middleware,避免运行时循环依赖)。
479
+ */
480
+ export function overwriteProjectFiles(project, entries, opts = {}) {
481
+ const dbPath = join(project.root, DATA_DB_FILE_NAME);
482
+ closeDatabase(project.db); // 释放当前连接(替换 data.db 前必须;替换失败恢复见 catch)
483
+ try {
484
+ // 清理陈旧 WAL/SHM 残留(正常关闭通常已清理;防御:替换后新库不得复用旧 WAL)
485
+ for (const suffix of ["-wal", "-shm"]) {
486
+ try {
487
+ unlinkSync(`${dbPath}${suffix}`);
488
+ }
489
+ catch (err) {
490
+ if (err.code !== "ENOENT")
491
+ throw err;
492
+ }
493
+ }
494
+ writeProjectFilesFromBackup(project.root, entries, {
495
+ keepId: project.config.id,
496
+ ...(opts.name !== undefined ? { name: opts.name } : {}),
497
+ ...(opts.snapshotFileName !== undefined ? { snapshotFileName: opts.snapshotFileName } : {}),
498
+ });
499
+ // 重连 + 版本对齐
500
+ let active = openDatabase(dbPath);
501
+ const out = ensureSchemaCompatible(active, project.root, dbPath);
502
+ active = out.db;
503
+ project.db = active;
504
+ // 同步内存 config(覆盖后的 project.json——含 name/prompt/backup_frequency_minutes 等)
505
+ const restoredConfig = readProjectFile(project.root);
506
+ if (restoredConfig === null) {
507
+ throw new HttpError(500, "INTERNAL_ERROR", "覆盖后 project.json 读取失败");
508
+ }
509
+ project.config = restoredConfig;
510
+ }
511
+ catch (err) {
512
+ // P1-2:替换/重连失败——writeProjectFilesFromBackup 已记录文件替换清单(含快照名),
513
+ // 此处尝试恢复连接(原库可能未被替换或已替换为校验过的有效库);重连失败时连接悬挂,
514
+ // 记日志供排查,由路由层(restore/import)catch 后检查 db.open 清空单例
515
+ let reopened = true;
516
+ try {
517
+ project.db = openDatabase(dbPath);
518
+ }
519
+ catch {
520
+ reopened = false;
521
+ }
522
+ console.error(`[backup] 覆盖失败${opts.snapshotFileName !== undefined ? `,覆盖前快照: ${opts.snapshotFileName}` : ""}${reopened ? ",data.db 已恢复连接" : ",data.db 重连失败(连接悬挂,路由层将清空单例)"}`, err);
523
+ throw err;
524
+ }
525
+ startAutoBackup(project); // 重启定时器(覆盖包内频率可能不同)
526
+ }
527
+ /**
528
+ * 为**未打开的书**生成覆盖前快照(import 覆盖书架其他书场景,决策 27「覆盖前自动快照」):
529
+ * 临时打开 data.db 连接走备份管道(wal_checkpoint 保证快照完整性),完成后关闭连接。
530
+ * 已打开的书走 writeBackup(project)(复用现有连接,见 import 路由分流)。
531
+ */
532
+ export function snapshotBookDir(dir) {
533
+ const config = readProjectFile(dir);
534
+ if (config === null) {
535
+ throw new HttpError(500, "INTERNAL_ERROR", `目录不是项目(缺 project.json): ${dir}`);
536
+ }
537
+ const db = openDatabase(join(dir, DATA_DB_FILE_NAME));
538
+ try {
539
+ return writeBackup({ root: dir, config, db });
540
+ }
541
+ finally {
542
+ closeDatabase(db);
543
+ }
544
+ }
545
+ /**
546
+ * 原子写任意字节(决策 11 同款流程:写同目录临时文件 → fsync → rename 覆盖;
547
+ * 供 restore/import 替换三文件用——JSON 版见 db 包 writeJsonAtomic,此处为通用 bytes 版)。
548
+ * 失败保留临时文件供排查,原文件未被触碰。
549
+ */
550
+ function writeFileAtomic(filePath, data) {
551
+ const tmpPath = join(dirname(filePath), `.${basename(filePath)}.restore-tmp`);
552
+ let fd;
553
+ try {
554
+ // 清理上次崩溃可能残留的临时文件(ENOENT 忽略),再独占创建——杜绝并发/残留覆盖
555
+ try {
556
+ unlinkSync(tmpPath);
557
+ }
558
+ catch (err) {
559
+ if (err.code !== "ENOENT")
560
+ throw err;
561
+ }
562
+ fd = openSync(tmpPath, "wx", 0o644);
563
+ writeFileSync(fd, data);
564
+ fsyncSync(fd);
565
+ closeSync(fd);
566
+ fd = undefined;
567
+ renameSync(tmpPath, filePath); // 原子覆盖
568
+ }
569
+ catch (err) {
570
+ if (fd !== undefined) {
571
+ try {
572
+ closeSync(fd);
573
+ }
574
+ catch {
575
+ // 忽略关闭失败,原始异常优先
576
+ }
577
+ }
578
+ throw err;
579
+ }
580
+ }
581
+ /**
582
+ * 从备份恢复当前项目(POST /project/backup/restore,决策 27):
583
+ *
584
+ * 1. fileName 白名单校验:仅允许 `.backups/` 下 `<YYYYMMDD-HHmmss>.zip` 格式
585
+ * (shared parseBackupFileName,^$ 锚定天然拒绝路径分隔符/`..`,防路径穿越);
586
+ * 格式合法但文件不存在 → 404
587
+ * 2. **覆盖前自动快照**:当前三文件打包为快照存入 .backups/(复用备份管道,
588
+ * 就是普通备份文件,自然参与保留策略——后悔药,决策 27)
589
+ * 3. 备份包校验(validateBackupPackage:zip/白名单/契约/user_version 三态,
590
+ * E4/E5 语义,拒绝时数据零触碰——校验通过前不触碰任何数据文件)
591
+ * 4. **原子替换**:closeDatabase(释放当前连接)→ 三文件逐文件原子写
592
+ * (临时文件 + rename;data.db 顺带清理 -wal/-shm 残留)→ 重新 openDatabase
593
+ * + ensureSchemaCompatible(校验阶段已保证 v ≤ 当前且有迁移路径或匹配——
594
+ * v < 有路径时此处前向迁移,E5)→ project.config 同步新 project.json
595
+ * 5. 项目 id 保留(当前项目引用不变,决策 27);重启定时器(频率可能随备份变化)
596
+ *
597
+ * @throws HttpError 400(文件名非法/坏包)、404(备份不存在)、409 SCHEMA_VERSION_MISMATCH
598
+ */
599
+ export function restoreBackup(project, fileName) {
600
+ // 1. 白名单校验(parseBackupFileName 全格式校验,防路径穿越)
601
+ if (parseBackupFileName(fileName) === null) {
602
+ throw new HttpError(400, "VALIDATION_ERROR", `备份文件名格式非法(仅接受 <YYYYMMDD-HHmmss>.zip): ${fileName}`);
603
+ }
604
+ const backupPath = join(project.root, BACKUPS_DIR_NAME, fileName);
605
+ if (!existsSync(backupPath)) {
606
+ throw new HttpError(404, "VALIDATION_ERROR", `备份不存在: ${fileName}`);
607
+ }
608
+ // 2. 覆盖前自动快照(复用备份管道;误操作/选错备份永远有后悔药)
609
+ const snapshot = writeBackup(project);
610
+ // 3. 备份包校验(零触碰:通过前不写任何数据文件);projectId = zip 内 project.json 的 id
611
+ const zip = readFileSync(backupPath);
612
+ const { entries, projectId: zipProjectId } = validateBackupPackage(zip);
613
+ // 4. 覆盖管道(B2.3 提取,import 覆盖复用):原子替换三文件(保留当前 id)+
614
+ // 重连 data.db + 同步 config + 重启定时器。
615
+ // name 归一为当前目录名(审核裁决:与 import 覆盖一致,维持「目录名 = 书名」
616
+ // 不变式——id 是身份、name 是展示名;改名需求走 /project/rename)
617
+ overwriteProjectFiles(project, entries, { name: basename(project.root), snapshotFileName: snapshot.fileName });
618
+ // 5. 会话归属迁移(B2.2 审核 P1-1,决策 18/27):备份包内 project_id ≠ 当前项目 id
619
+ // (跨项目恢复,如手工放入 .backups/ 的异项目备份)→ chat_messages 旧 id 行迁移为
620
+ // 当前 id——「保留 id 保会话」的理由在跨项目场景同样成立:不迁移则恢复后聊天面板
621
+ // 静默为空、旧会话行成孤儿数据。同项目恢复(id 相等)跳过,不执行多余迁移。
622
+ // (import 覆盖无需迁移:id 匹配才走覆盖分支,zip id = 书架 id)
623
+ if (zipProjectId !== project.config.id) {
624
+ migrateChatMessagesProject(project.db, zipProjectId, project.config.id);
625
+ }
626
+ // 契约(endpoints.md):snapshot 仅含 fileName/createdAt(size 属内部信息不暴露)
627
+ return { snapshot: { fileName: snapshot.fileName, createdAt: snapshot.createdAt } };
628
+ }
629
+ //# sourceMappingURL=backup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAAA,sBAAsB;AACtB,EAAE;AACF,oEAAoE;AACpE,MAAM;AACN,2EAA2E;AAC3E,4DAA4D;AAC5D,sEAAsE;AACtE,wDAAwD;AACxD,wDAAwD;AACxD,8CAA8C;AAC9C,oDAAoD;AACpD,EAAE;AACF,+CAA+C;AAC/C,gEAAgE;AAEhE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5H,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,gCAAgC,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3K,OAAO,EAAE,yBAAyB,EAAE,MAAM,yCAAyC,CAAC;AACpF,OAAO,EACL,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,sBAAsB,EACtB,cAAc,EACd,gBAAgB,EAChB,0BAA0B,EAC1B,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,cAAc,GACf,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGlD,6CAA6C;AAC7C,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAE3C,sDAAsD;AACtD,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,0BAA0B,GAAG,IAAI,CAAC;AASxC,gDAAgD;AAEhD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAAuB;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IACzB,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC1B,OAAO,OAAO,CACZ;QACE,CAAC,iBAAiB,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;QAC/D,CAAC,iBAAiB,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;QAC/D,CAAC,iBAAiB,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;KAChE,EACD,EAAE,KAAK,EAAE,CAAC,EAAE,CACb,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,SAAS,oBAAoB,CAAC,UAAkB,EAAE,IAAU;IAC1D,IAAI,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;QAC1C,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;QACvC,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAuB;IACjD,MAAM,GAAG,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACxD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,oBAAoB,CAAC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IAC9D,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,sBAAsB;IACtE,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,oBAAoB;IAC9C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,mBAAmB,CAAC,QAAQ,CAAS,CAAC,EAAE,CAAC;AACjG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,eAAe;IACzB,CAAC;IACD,MAAM,MAAM,GAAG,KAAK;SACjB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAC3D,MAAM,CAAC,CAAC,CAAC,EAAyC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;SACrE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO;IAC/D,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,uBAAuB,CAAC,EAAE,CAAC;QAC1D,IAAI,CAAC;YACH,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,6BAA6B,KAAK,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,WAAW,CAAC,OAAuB;IACjD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACxD,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,IAAI,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,CAAC,kBAAkB;QAClD,IAAI,CAAC;YACH,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3F,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC,CAAC,mBAAmB;QAClC,CAAC;IACH,CAAC,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAuB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC;SAC9C,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,mBAAmB;AAChF,CAAC;AAED,0CAA0C;AAC1C,SAAS,KAAK,CAAC,IAAU;IACvB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC5B,CAAC;AAED,0EAA0E;AAE1E,yCAAyC;AACzC,SAAS,YAAY,CAAC,MAAoB,EAAE,SAAiB;IAC3D,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACnB,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC;IACrB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,OAAmB,EAAE,MAAc;IAC1D,MAAM,OAAO,GAA+B,EAAE,CAAC;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,QAAQ,GAAG,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE;QAClC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAE,yBAA+C,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,CAAC,UAAU;QAC7F,MAAM,MAAM,GAAiB,EAAE,CAAC;QAChC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YAClC,IAAI,GAAG;gBAAE,MAAM,GAAG,CAAC;YACnB,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;YACrB,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;YACtB,IAAI,KAAK,GAAG,MAAM,EAAE,CAAC;gBACnB,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,aAAa,MAAM,iBAAiB,CAAC,CAAC;YACrF,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnB,IAAI,KAAK;gBAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7D,CAAC,CAAC;QACF,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IACH,8DAA8D;IAC9D,kEAAkE;IAClE,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAChC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC7B,qDAAqD;IACrD,0CAA0C;IAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,sBAAsB,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED,wDAAwD;AACxD,SAAS,kBAAkB,CAAC,MAAe;IACzC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAChE,MAAM,CAAC,GAAG,MAAiC,CAAC;IAC5C,OAAO,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,cAAc,KAAK,QAAQ,CAAC;AAC3H,CAAC;AAED,kGAAkG;AAClG,SAAS,kBAAkB,CAAC,MAAe;IACzC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAChE,MAAM,CAAC,GAAG,MAAiC,CAAC;IAC5C,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,cAAc,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AACnH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAmB;IACvD,gCAAgC;IAChC,IAAI,OAAmC,CAAC;IACxC,IAAI,UAAoB,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAC1D,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QACzB,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,SAAS;YAAE,MAAM,GAAG,CAAC;QACxC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,sBAAsB,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,OAAO,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAE,yBAA+C,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACxG,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,aAAa,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,yBAAyB,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC9H,CAAC;IACD,MAAM,OAAO,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC;IACzE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,YAAY,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1F,CAAC;IAED,mCAAmC;IACnC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC,CAAC,CAAC;IACjE,IAAI,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,yBAAyB,EAAE,CAAC;YAC1C,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,CAAC;QAED,uDAAuD;QACvD,IAAI,aAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACpF,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,6BAA6B,CAAC,CAAC;QAC9E,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,oDAAoD,CAAC,CAAC;QACrG,CAAC;QAED,uEAAuE;QACvE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;YAClF,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;gBACjC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,2EAA2E,CAAC,CAAC;YAC5H,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,SAAS;gBAAE,MAAM,GAAG,CAAC;YACxC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,6BAA6B,CAAC,CAAC;QAC9E,CAAC;QAED,2DAA2D;QAC3D,6DAA6D;QAC7D,4BAA4B;QAC5B,6CAA6C;QAC7C,kCAAkC;QAClC,mEAAmE;QACnE,kDAAkD;QAClD,+CAA+C;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;QAC/C,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YAChC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,wBAAwB,CAAC,CAAC;QACzE,CAAC;QACD,IAAI,EAAE,GAA2C,IAAI,CAAC;QACtD,IAAI,CAAC;YACH,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YAC1B,MAAM,CAAC,GAAG,cAAc,CAAC,EAAE,CAAC,CAAC;YAC7B,MAAM,UAAU,GAAG,CAAC,KAAK,cAAc,IAAI,CAAC,CAAC,GAAG,cAAc,IAAI,gBAAgB,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC;YACvG,IAAI,CAAC,UAAU,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,mBAAmB,CAAC;gBACrE,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,yBAAyB,EACzB,mBAAmB,CAAC,KAAK,IAAI,SAAS,cAAc,aAAa,CAClE,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,SAAS;gBAAE,MAAM,GAAG,CAAC;YACxC,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,+BAA+B,CAAC,CAAC;QAChF,CAAC;gBAAS,CAAC;YACT,IAAI,EAAE,KAAK,IAAI;gBAAE,aAAa,CAAC,EAAE,CAAC,CAAC;QACrC,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,CAAC,EAAE,EAAE,CAAC;IAClD,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAED,yDAAyD;AAEzD,8EAA8E;AAC9E,SAAS,sBAAsB,CAAC,KAAgC;IAC9D,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,gCAAgC,CAAC;IACjE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,OAAQ,kBAAwC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClF,CAAC;AAED,sDAAsD;AACtD,SAAS,gBAAgB,CAAC,UAAkB;IAC1C,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,MAAM,GAAgB,IAAI,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAAE,MAAM,GAAG,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,OAAuB,EAAE,KAAW;IAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,GAAG,0BAA0B,CAAC;IAC3D,KAAK,MAAM,IAAI,IAAI,CAAC,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,EAAE,CAAC;QAC7E,IAAI,CAAC;YACH,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK;gBAAE,OAAO,IAAI,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,OAAuB;IACrD,IAAI,sBAAsB,CAAC,OAAO,CAAC,MAAM,CAAC,wBAAwB,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC3F,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACxD,MAAM,YAAY,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAClD,IAAI,YAAY,KAAK,IAAI,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC;QAAE,OAAO,KAAK,CAAC;IACvF,WAAW,CAAC,OAAO,CAAC,CAAC;IACrB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8EAA8E;AAE9E,iCAAiC;AACjC,IAAI,WAAW,GAAyC,IAAI,CAAC;AAC7D,yDAAyD;AACzD,IAAI,gBAAgB,GAA0B,IAAI,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAAuB;IACrD,cAAc,EAAE,CAAC;IACjB,gBAAgB,GAAG,OAAO,CAAC;IAC3B,YAAY,CAAC,OAAO,CAAC,CAAC;AACxB,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,cAAc;IAC5B,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,YAAY,CAAC,WAAW,CAAC,CAAC;QAC1B,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC;IACD,gBAAgB,GAAG,IAAI,CAAC;AAC1B,CAAC;AAED,gCAAgC;AAChC,SAAS,YAAY,CAAC,OAAuB;IAC3C,MAAM,IAAI,GAAG,sBAAsB,CAAC,OAAO,CAAC,MAAM,CAAC,wBAAwB,CAAC,CAAC;IAC7E,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO;IAC1B,WAAW,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,WAAW,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC;YACH,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,6BAA6B;QACzD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;QAC3C,CAAC;QACD,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,+BAA+B;IACxD,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;IAClB,kCAAkC;IACjC,WAAsC,CAAC,KAAK,EAAE,EAAE,CAAC;AACpD,CAAC;AAED,kCAAkC;AAClC,MAAM,UAAU,mBAAmB;IACjC,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,6DAA6D;AAE7D,2EAA2E;AAE3E;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,2BAA2B,CACzC,GAAW,EACX,OAAmC,EACnC,OAAsE,EAAE;IAExE,mDAAmD;IACnD,kDAAkD;IAClD,MAAM,WAAW,GAAG,CAAC,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,CAAC;IAC9E,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAA4B,CAAC;QAClH,MAAM,WAAW,GAAG;YAClB,GAAG,aAAa;YAChB,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxD,CAAC;QACF,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACrH,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACjC,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC1E,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACjC,eAAe,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC1E,QAAQ,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,qDAAqD;QACrD,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACrE,OAAO,CAAC,KAAK,CACX,0BAA0B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,SAAS,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,GACxF,IAAI,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAC7E,GAAG,EACH,GAAG,CACJ,CAAC;QACF,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;IAgBI;AACJ,MAAM,UAAU,qBAAqB,CACnC,OAAuB,EACvB,OAAmC,EACnC,OAAqD,EAAE;IAEvD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IACrD,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,uCAAuC;IAClE,IAAI,CAAC;QACH,+CAA+C;QAC/C,KAAK,MAAM,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC;gBACH,UAAU,CAAC,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;YACnC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;oBAAE,MAAM,GAAG,CAAC;YAClE,CAAC;QACH,CAAC;QACD,2BAA2B,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE;YACjD,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE;YACzB,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvD,GAAG,CAAC,IAAI,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5F,CAAC,CAAC;QACH,YAAY;QACZ,IAAI,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,GAAG,GAAG,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,CAAC,EAAE,CAAC;QAChB,OAAO,CAAC,EAAE,GAAG,MAAM,CAAC;QACpB,2EAA2E;QAC3E,MAAM,cAAc,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC5B,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,uBAAuB,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,CAAC,MAAM,GAAG,cAAc,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,6DAA6D;QAC7D,4CAA4C;QAC5C,oDAAoD;QACpD,IAAI,QAAQ,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,CAAC,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,KAAK,CAAC;QACnB,CAAC;QACD,OAAO,CAAC,KAAK,CACX,gBAAgB,IAAI,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,GAC3F,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,8BAChC,EAAE,EACF,GAAG,CACJ,CAAC;QACF,MAAM,GAAG,CAAC;IACZ,CAAC;IACD,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB;AAChD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,2BAA2B,GAAG,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,MAAM,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;IAChD,CAAC;YAAS,CAAC;QACT,aAAa,CAAC,EAAE,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,QAAgB,EAAE,IAAgB;IACzD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;IAC9E,IAAI,EAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,8CAA8C;QAC9C,IAAI,CAAC;YACH,UAAU,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;QAClE,CAAC;QACD,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACpC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACxB,SAAS,CAAC,EAAE,CAAC,CAAC;QACd,SAAS,CAAC,EAAE,CAAC,CAAC;QACd,EAAE,GAAG,SAAS,CAAC;QACf,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACrB,IAAI,CAAC;gBACH,SAAS,CAAC,EAAE,CAAC,CAAC;YAChB,CAAC;YAAC,MAAM,CAAC;gBACP,gBAAgB;YAClB,CAAC;QACH,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;IAiBI;AACJ,MAAM,UAAU,aAAa,CAAC,OAAuB,EAAE,QAAgB;IACrE,4CAA4C;IAC5C,IAAI,mBAAmB,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,yCAAyC,QAAQ,EAAE,CAAC,CAAC;IACpG,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IAClE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,kBAAkB,EAAE,UAAU,QAAQ,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,oCAAoC;IACpC,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAEtC,gEAAgE;IAChE,MAAM,GAAG,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IACrC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;IAExE,iDAAiD;IACjD,qCAAqC;IACrC,mDAAmD;IACnD,kDAAkD;IAClD,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,gBAAgB,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE/G,6DAA6D;IAC7D,6DAA6D;IAC7D,iDAAiD;IACjD,4CAA4C;IAC5C,gDAAgD;IAChD,IAAI,YAAY,KAAK,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACvC,0BAA0B,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,iEAAiE;IACjE,OAAO,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC;AACtF,CAAC"}