@liuyoumi/codex-history 0.2.0 → 0.4.0
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/CHANGELOG.md +12 -0
- package/README.en.md +24 -3
- package/README.md +24 -3
- package/dist/cli.js +212 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 - 2026-05-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Allow `purge` to select regular conversations with `--cwd`, `--grep`, and `--archived` filters.
|
|
8
|
+
|
|
9
|
+
## 0.3.0 - 2026-05-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Allow `purge` to accept multiple thread ids or unique short id prefixes in one guarded batch.
|
|
14
|
+
|
|
3
15
|
## 0.2.0 - 2026-05-29
|
|
4
16
|
|
|
5
17
|
### Removed
|
package/README.en.md
CHANGED
|
@@ -51,6 +51,9 @@ codex-history doctor
|
|
|
51
51
|
codex-history list
|
|
52
52
|
codex-history list --grep "Astro"
|
|
53
53
|
codex-history purge 019e6885
|
|
54
|
+
codex-history purge 019e6885 019e6874
|
|
55
|
+
codex-history purge --cwd /Users/me/Projects/example
|
|
56
|
+
codex-history purge --grep "Astro"
|
|
54
57
|
codex-history purge-orphans
|
|
55
58
|
```
|
|
56
59
|
|
|
@@ -75,7 +78,9 @@ Type 019e6885 to confirm:
|
|
|
75
78
|
| `codex-history doctor` | Check whether the local Codex data layout is supported. |
|
|
76
79
|
| `codex-history list` | List local conversations. |
|
|
77
80
|
| `codex-history list --grep <keyword>` | Filter conversations by title, id, or cwd. |
|
|
78
|
-
| `codex-history purge <id
|
|
81
|
+
| `codex-history purge <id...>` | Remove one or more resolved local conversations after confirmation. |
|
|
82
|
+
| `codex-history purge --cwd <path>` | Remove local conversations matching a working directory after confirmation. |
|
|
83
|
+
| `codex-history purge --grep <keyword>` | Remove local conversations matching title, id, or cwd after confirmation. |
|
|
79
84
|
| `codex-history purge-orphans` | Remove orphaned local data after confirmation. |
|
|
80
85
|
|
|
81
86
|
### `doctor`
|
|
@@ -129,19 +134,35 @@ When `list` runs in an interactive terminal without `--limit`, output is sent th
|
|
|
129
134
|
|
|
130
135
|
### `purge`
|
|
131
136
|
|
|
132
|
-
Remove one local Codex
|
|
137
|
+
Remove one or more local Codex conversations by full id or unique short id prefix.
|
|
133
138
|
|
|
134
139
|
```bash
|
|
135
140
|
codex-history purge 019e6885
|
|
141
|
+
codex-history purge 019e6885 019e6874
|
|
136
142
|
```
|
|
137
143
|
|
|
144
|
+
When multiple targets are provided, the tool resolves all targets and prints a batch plan before deletion. If any id is missing, ambiguous, or active, the whole batch is refused before mutation. Interactive batch purge requires typing `purge-selected`.
|
|
145
|
+
|
|
146
|
+
You can also select local conversations with filters:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
codex-history purge --cwd /Users/me/Projects/example
|
|
150
|
+
codex-history purge --grep "Astro"
|
|
151
|
+
codex-history purge --archived
|
|
152
|
+
codex-history purge --cwd /Users/me/Projects/example --grep "Astro"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`--cwd`, `--grep`, and `--archived` can be combined. Combined filters delete only conversations that match every provided condition. By default, filter mode matches non-archived conversations only; use `--archived` to target archived conversations. Filtered purge prints a batch plan and requires typing `purge-selected`. Filters cannot be combined with explicit ids.
|
|
156
|
+
|
|
138
157
|
For scripts or non-interactive shells, use `--force`:
|
|
139
158
|
|
|
140
159
|
```bash
|
|
141
160
|
codex-history purge 019e6885 --force
|
|
161
|
+
codex-history purge 019e6885 019e6874 --force
|
|
162
|
+
codex-history purge --cwd /Users/me/Projects/example --force
|
|
142
163
|
```
|
|
143
164
|
|
|
144
|
-
`--force` skips only
|
|
165
|
+
`--force` skips only interactive confirmation. It still keeps schema validation, active-thread protection, and post-purge verification.
|
|
145
166
|
|
|
146
167
|
### `purge-orphans`
|
|
147
168
|
|
package/README.md
CHANGED
|
@@ -51,6 +51,9 @@ codex-history doctor
|
|
|
51
51
|
codex-history list
|
|
52
52
|
codex-history list --grep "Astro"
|
|
53
53
|
codex-history purge 019e6885
|
|
54
|
+
codex-history purge 019e6885 019e6874
|
|
55
|
+
codex-history purge --cwd /Users/me/Projects/example
|
|
56
|
+
codex-history purge --grep "Astro"
|
|
54
57
|
codex-history purge-orphans
|
|
55
58
|
```
|
|
56
59
|
|
|
@@ -75,7 +78,9 @@ Type 019e6885 to confirm:
|
|
|
75
78
|
| `codex-history doctor` | 检查当前本地 Codex 数据结构是否受支持。 |
|
|
76
79
|
| `codex-history list` | 列出本地对话。 |
|
|
77
80
|
| `codex-history list --grep <keyword>` | 按标题、id 或 cwd 过滤对话。 |
|
|
78
|
-
| `codex-history purge <id
|
|
81
|
+
| `codex-history purge <id...>` | 确认后删除一条或多条解析到的本地对话。 |
|
|
82
|
+
| `codex-history purge --cwd <path>` | 确认后删除匹配工作目录的本地对话。 |
|
|
83
|
+
| `codex-history purge --grep <keyword>` | 确认后删除匹配标题、id 或 cwd 的本地对话。 |
|
|
79
84
|
| `codex-history purge-orphans` | 确认后清理本地孤儿数据。 |
|
|
80
85
|
|
|
81
86
|
### `doctor`
|
|
@@ -129,19 +134,35 @@ codex-history list --grep "Astro 博客"
|
|
|
129
134
|
|
|
130
135
|
### `purge`
|
|
131
136
|
|
|
132
|
-
通过完整 id 或唯一短 id
|
|
137
|
+
通过完整 id 或唯一短 id 前缀删除一条或多条本地 Codex 对话。
|
|
133
138
|
|
|
134
139
|
```bash
|
|
135
140
|
codex-history purge 019e6885
|
|
141
|
+
codex-history purge 019e6885 019e6874
|
|
136
142
|
```
|
|
137
143
|
|
|
144
|
+
删除多个目标时,工具会先解析全部目标并展示批量计划;只要任意一个 id 不存在、不唯一,或命中 active thread,整个批量操作都会拒绝执行,不会部分删除。交互式批量删除需要输入 `purge-selected` 确认。
|
|
145
|
+
|
|
146
|
+
也可以直接用过滤条件选择要删除的本地对话:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
codex-history purge --cwd /Users/me/Projects/example
|
|
150
|
+
codex-history purge --grep "Astro"
|
|
151
|
+
codex-history purge --archived
|
|
152
|
+
codex-history purge --cwd /Users/me/Projects/example --grep "Astro"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`--cwd`、`--grep`、`--archived` 可以组合,组合后只删除同时满足全部条件的对话。默认只匹配未归档对话;如果要删除已归档对话,请使用 `--archived`。过滤式删除会展示批量计划,并要求输入 `purge-selected` 确认。过滤条件不能和 id 同时使用。
|
|
156
|
+
|
|
138
157
|
脚本或非交互环境可以使用 `--force`:
|
|
139
158
|
|
|
140
159
|
```bash
|
|
141
160
|
codex-history purge 019e6885 --force
|
|
161
|
+
codex-history purge 019e6885 019e6874 --force
|
|
162
|
+
codex-history purge --cwd /Users/me/Projects/example --force
|
|
142
163
|
```
|
|
143
164
|
|
|
144
|
-
`--force`
|
|
165
|
+
`--force` 只跳过交互确认,不会跳过数据结构校验、active thread 保护和删除后的验证。
|
|
145
166
|
|
|
146
167
|
### `purge-orphans`
|
|
147
168
|
|
package/dist/cli.js
CHANGED
|
@@ -837,15 +837,71 @@ function findShellSnapshots(shellSnapshotsDir, threadId) {
|
|
|
837
837
|
}
|
|
838
838
|
|
|
839
839
|
// src/commands/purge.ts
|
|
840
|
-
function
|
|
840
|
+
function planBatchPurgeCommand(paths, threadIds) {
|
|
841
841
|
validateSupportedDataModel(paths);
|
|
842
|
-
const
|
|
843
|
-
|
|
842
|
+
const plans = [];
|
|
843
|
+
const seenTargets = /* @__PURE__ */ new Set();
|
|
844
|
+
const duplicateInputs = [];
|
|
845
|
+
for (const threadId of threadIds) {
|
|
846
|
+
const target = resolvePurgeTarget(paths, threadId);
|
|
847
|
+
if (seenTargets.has(target.id)) {
|
|
848
|
+
duplicateInputs.push({ input: threadId, targetId: target.id });
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
seenTargets.add(target.id);
|
|
852
|
+
plans.push(buildPurgePlan(paths, target));
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
mode: "planned",
|
|
856
|
+
source: "explicit",
|
|
857
|
+
requestedCount: threadIds.length,
|
|
858
|
+
plans,
|
|
859
|
+
duplicateInputs
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function planFilteredPurgeCommand(paths, filters) {
|
|
863
|
+
validateSupportedDataModel(paths);
|
|
864
|
+
if (!hasPurgeFilter(filters)) {
|
|
865
|
+
throw new UsageError("Provide thread id(s) or at least one filter such as --cwd, --grep, or --archived.");
|
|
866
|
+
}
|
|
867
|
+
const listOptions = {
|
|
868
|
+
cwd: filters.cwd,
|
|
869
|
+
grep: filters.grep,
|
|
870
|
+
archived: filters.archived
|
|
871
|
+
};
|
|
872
|
+
const targets = listThreads(paths, listOptions);
|
|
873
|
+
return {
|
|
874
|
+
mode: "planned",
|
|
875
|
+
source: "filtered",
|
|
876
|
+
requestedCount: targets.length,
|
|
877
|
+
plans: targets.map((target) => buildPurgePlan(paths, target)),
|
|
878
|
+
filters,
|
|
879
|
+
duplicateInputs: []
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function hasPurgeFilter(filters) {
|
|
883
|
+
return Boolean(filters.cwd || filters.grep || filters.archived);
|
|
844
884
|
}
|
|
845
885
|
function executePurgePlanCommand(paths, plan) {
|
|
846
886
|
validateSupportedDataModel(paths);
|
|
847
887
|
return executePurge(paths, plan);
|
|
848
888
|
}
|
|
889
|
+
function executeBatchPurgePlanCommand(paths, plan) {
|
|
890
|
+
validateSupportedDataModel(paths);
|
|
891
|
+
const activeThreadChecks = plan.plans.flatMap((item) => assertThreadIsNotActive(item.target));
|
|
892
|
+
const purgeReports = plan.plans.map((item) => executePurge(paths, item, { skipActiveThreadCheck: true }));
|
|
893
|
+
const remainingReferences = purgeReports.flatMap((report) => report.verification.remainingReferences);
|
|
894
|
+
return {
|
|
895
|
+
mode: "executed",
|
|
896
|
+
plan,
|
|
897
|
+
activeThreadChecks,
|
|
898
|
+
purgeReports,
|
|
899
|
+
verification: {
|
|
900
|
+
success: remainingReferences.length === 0,
|
|
901
|
+
remainingReferences
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
}
|
|
849
905
|
|
|
850
906
|
// src/core/orphans.ts
|
|
851
907
|
import { existsSync as existsSync10 } from "fs";
|
|
@@ -1098,15 +1154,39 @@ program.command("list").description("List local Codex conversations.").option("-
|
|
|
1098
1154
|
)
|
|
1099
1155
|
)
|
|
1100
1156
|
);
|
|
1101
|
-
program.command("purge").argument("
|
|
1102
|
-
(
|
|
1157
|
+
program.command("purge").argument("[threadIds...]", "Codex thread id(s) or unique short id prefix(es) to purge").option("--force", "Skip interactive confirmation").option("--cwd <path>", "Filter conversations by exact working directory").option("--grep <keyword>", "Filter conversations by title, id, or cwd keyword").option("--archived", "Filter archived conversations").description("Purge local Codex conversation(s) after target confirmation.").action(
|
|
1158
|
+
(threadIds, options) => runCommand(async () => {
|
|
1103
1159
|
const paths = currentPaths();
|
|
1104
|
-
const
|
|
1160
|
+
const requestedThreadIds = threadIds ?? [];
|
|
1161
|
+
const filters = {
|
|
1162
|
+
cwd: options.cwd,
|
|
1163
|
+
grep: options.grep,
|
|
1164
|
+
archived: Boolean(options.archived)
|
|
1165
|
+
};
|
|
1166
|
+
const hasExplicitTargets = requestedThreadIds.length > 0;
|
|
1167
|
+
const hasFilters = hasPurgeFilter(filters);
|
|
1168
|
+
if (hasExplicitTargets && hasFilters) {
|
|
1169
|
+
throw new UsageError("Thread ids cannot be combined with --cwd, --grep, or --archived.");
|
|
1170
|
+
}
|
|
1171
|
+
if (!hasExplicitTargets && !hasFilters) {
|
|
1172
|
+
throw new UsageError("Provide thread id(s) or at least one filter such as --cwd, --grep, or --archived.");
|
|
1173
|
+
}
|
|
1174
|
+
const plan = hasExplicitTargets ? planBatchPurgeCommand(paths, requestedThreadIds) : planFilteredPurgeCommand(paths, filters);
|
|
1105
1175
|
const force = Boolean(options.force);
|
|
1176
|
+
const useBatchConfirmation = plan.source === "filtered" || plan.requestedCount > 1;
|
|
1177
|
+
if (plan.plans.length === 0) {
|
|
1178
|
+
return "No matching local Codex conversations found.";
|
|
1179
|
+
}
|
|
1180
|
+
if (plan.plans.length === 1 && !useBatchConfirmation) {
|
|
1181
|
+
if (!force) {
|
|
1182
|
+
await confirmPurge(plan.plans[0]);
|
|
1183
|
+
}
|
|
1184
|
+
return formatPurgeResult(executePurgePlanCommand(paths, plan.plans[0]));
|
|
1185
|
+
}
|
|
1106
1186
|
if (!force) {
|
|
1107
|
-
await
|
|
1187
|
+
await confirmBatchPurge(plan);
|
|
1108
1188
|
}
|
|
1109
|
-
return
|
|
1189
|
+
return formatBatchPurgeResult(executeBatchPurgePlanCommand(paths, plan));
|
|
1110
1190
|
})
|
|
1111
1191
|
);
|
|
1112
1192
|
program.command("purge-orphans").option("--force", "Skip interactive confirmation").description("Purge orphaned local Codex data after target confirmation.").action(
|
|
@@ -1172,6 +1252,25 @@ async function confirmPurge(plan) {
|
|
|
1172
1252
|
readline.close();
|
|
1173
1253
|
}
|
|
1174
1254
|
}
|
|
1255
|
+
async function confirmBatchPurge(plan) {
|
|
1256
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1257
|
+
throw new SafetyRefusalError("Batch purge requires an interactive terminal. Use --force to skip confirmation.");
|
|
1258
|
+
}
|
|
1259
|
+
const expected = "purge-selected";
|
|
1260
|
+
process.stdout.write(formatBatchPurgeConfirmation(plan));
|
|
1261
|
+
const readline = createInterface({
|
|
1262
|
+
input: process.stdin,
|
|
1263
|
+
output: process.stdout
|
|
1264
|
+
});
|
|
1265
|
+
try {
|
|
1266
|
+
const answer = await readline.question(`Type ${colorize("yellow", expected)} to confirm: `);
|
|
1267
|
+
if (answer.trim() !== expected) {
|
|
1268
|
+
throw new SafetyRefusalError("Confirmation did not match. No local Codex data was modified.");
|
|
1269
|
+
}
|
|
1270
|
+
} finally {
|
|
1271
|
+
readline.close();
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1175
1274
|
async function confirmPurgeOrphans(plan) {
|
|
1176
1275
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1177
1276
|
throw new SafetyRefusalError("purge-orphans requires an interactive terminal. Use --force to skip confirmation.");
|
|
@@ -1277,6 +1376,38 @@ function formatPurgeResult(result) {
|
|
|
1277
1376
|
}
|
|
1278
1377
|
return lines.join("\n");
|
|
1279
1378
|
}
|
|
1379
|
+
function formatBatchPurgeResult(result) {
|
|
1380
|
+
const sqliteChanges = aggregateBatchSqliteChanges(result);
|
|
1381
|
+
const jsonChanged = result.purgeReports.flatMap((report) => report.json).filter((change) => change.changed).length;
|
|
1382
|
+
const filesDeleted = result.purgeReports.flatMap((report) => report.files).filter((change) => change.deleted).length;
|
|
1383
|
+
const lines = [
|
|
1384
|
+
colorize("green", "Batch purge executed."),
|
|
1385
|
+
"",
|
|
1386
|
+
"Summary:",
|
|
1387
|
+
`- Selection: ${formatBatchPurgeSelection(result.plan)}`,
|
|
1388
|
+
`- Unique targets purged: ${result.plan.plans.length}`,
|
|
1389
|
+
`- Duplicate inputs skipped: ${result.plan.duplicateInputs.length}`,
|
|
1390
|
+
`- SQLite row changes: ${sqliteChanges.reduce((sum, change) => sum + change.rows, 0)}`,
|
|
1391
|
+
`- JSON/JSONL file changes: ${jsonChanged}`,
|
|
1392
|
+
`- Files deleted: ${filesDeleted}`,
|
|
1393
|
+
"",
|
|
1394
|
+
"SQLite changes:",
|
|
1395
|
+
...formatSqliteRows(sqliteChanges),
|
|
1396
|
+
"",
|
|
1397
|
+
`Verification: ${result.verification.success ? colorize("green", "passed") : colorize("red", "failed")}`
|
|
1398
|
+
];
|
|
1399
|
+
if (result.verification.remainingReferences.length > 0) {
|
|
1400
|
+
lines.push(
|
|
1401
|
+
"",
|
|
1402
|
+
"Remaining references:",
|
|
1403
|
+
...result.verification.remainingReferences.map(
|
|
1404
|
+
(reference) => `- ${reference.store}: ${reference.path} (${reference.detail})`
|
|
1405
|
+
)
|
|
1406
|
+
);
|
|
1407
|
+
process.exitCode = 1;
|
|
1408
|
+
}
|
|
1409
|
+
return lines.join("\n");
|
|
1410
|
+
}
|
|
1280
1411
|
function formatPurgeOrphansPlan(plan) {
|
|
1281
1412
|
if (!hasPurgeOrphansWork(plan)) {
|
|
1282
1413
|
return "No orphaned local Codex data found.";
|
|
@@ -1330,6 +1461,41 @@ function formatPurgeConfirmation(plan) {
|
|
|
1330
1461
|
""
|
|
1331
1462
|
].join("\n");
|
|
1332
1463
|
}
|
|
1464
|
+
function formatBatchPurgeConfirmation(plan) {
|
|
1465
|
+
return [
|
|
1466
|
+
"About to purge selected local Codex conversations:",
|
|
1467
|
+
"",
|
|
1468
|
+
`Selection: ${formatBatchPurgeSelection(plan)}`,
|
|
1469
|
+
`Unique targets: ${plan.plans.length}`,
|
|
1470
|
+
`Duplicate inputs skipped: ${plan.duplicateInputs.length}`,
|
|
1471
|
+
"",
|
|
1472
|
+
"SQLite row changes:",
|
|
1473
|
+
...formatSqliteRows(summarizePlannedSqliteRows(plan.plans)),
|
|
1474
|
+
"",
|
|
1475
|
+
...formatBatchPurgeExamples(plan),
|
|
1476
|
+
colorize("dim", "This cannot be undone."),
|
|
1477
|
+
""
|
|
1478
|
+
].join("\n");
|
|
1479
|
+
}
|
|
1480
|
+
function formatBatchPurgeSelection(plan) {
|
|
1481
|
+
if (plan.source === "explicit") {
|
|
1482
|
+
return `${plan.requestedCount} requested target(s)`;
|
|
1483
|
+
}
|
|
1484
|
+
return formatPurgeFilters(plan.filters ?? {});
|
|
1485
|
+
}
|
|
1486
|
+
function formatPurgeFilters(filters) {
|
|
1487
|
+
const parts = [];
|
|
1488
|
+
if (filters.cwd) {
|
|
1489
|
+
parts.push(`cwd=${filters.cwd}`);
|
|
1490
|
+
}
|
|
1491
|
+
if (filters.grep) {
|
|
1492
|
+
parts.push(`grep=${filters.grep}`);
|
|
1493
|
+
}
|
|
1494
|
+
if (filters.archived) {
|
|
1495
|
+
parts.push("archived=true");
|
|
1496
|
+
}
|
|
1497
|
+
return parts.length > 0 ? parts.join(", ") : "filtered conversations";
|
|
1498
|
+
}
|
|
1333
1499
|
function formatPurgeOrphansConfirmation(plan) {
|
|
1334
1500
|
return [
|
|
1335
1501
|
"About to purge orphaned local Codex data:",
|
|
@@ -1373,6 +1539,44 @@ function formatOrphanExamples(plan) {
|
|
|
1373
1539
|
}
|
|
1374
1540
|
return lines;
|
|
1375
1541
|
}
|
|
1542
|
+
function formatBatchPurgeExamples(plan) {
|
|
1543
|
+
const examples = plan.plans.slice(0, 5).map((item) => {
|
|
1544
|
+
const target = item.target;
|
|
1545
|
+
return `- ${shortId(target.id)} ${displayTitle(target.title)} ${colorize("dim", target.cwd)}`;
|
|
1546
|
+
});
|
|
1547
|
+
if (examples.length === 0) {
|
|
1548
|
+
return [];
|
|
1549
|
+
}
|
|
1550
|
+
const lines = ["Targets:", ...examples, ""];
|
|
1551
|
+
if (plan.plans.length > examples.length) {
|
|
1552
|
+
lines.push(`Showing ${examples.length} of ${plan.plans.length} target(s).`, "");
|
|
1553
|
+
}
|
|
1554
|
+
return lines;
|
|
1555
|
+
}
|
|
1556
|
+
function summarizePlannedSqliteRows(plans) {
|
|
1557
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1558
|
+
for (const plan of plans) {
|
|
1559
|
+
for (const store of plan.stores) {
|
|
1560
|
+
if (store.action !== "delete_rows" && store.store !== "state_db.agent_job_items") {
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
if (!store.count || store.count <= 0) {
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1566
|
+
rows.set(store.store, (rows.get(store.store) ?? 0) + store.count);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
return [...rows.entries()].map(([store, count]) => ({ store, rows: count })).sort((a, b) => a.store.localeCompare(b.store));
|
|
1570
|
+
}
|
|
1571
|
+
function aggregateBatchSqliteChanges(result) {
|
|
1572
|
+
const changes = /* @__PURE__ */ new Map();
|
|
1573
|
+
for (const report of result.purgeReports) {
|
|
1574
|
+
for (const change of report.sqlite) {
|
|
1575
|
+
changes.set(change.store, (changes.get(change.store) ?? 0) + change.changedRows);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
return [...changes.entries()].filter(([, rows]) => rows > 0).map(([store, rows]) => ({ store, rows })).sort((a, b) => a.store.localeCompare(b.store));
|
|
1579
|
+
}
|
|
1376
1580
|
function aggregateOrphanSqliteChanges(result) {
|
|
1377
1581
|
const changes = /* @__PURE__ */ new Map();
|
|
1378
1582
|
for (const report of result.purgeReports) {
|