@remit/backend 0.0.122 → 0.0.123

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/backend",
3
- "version": "0.0.122",
3
+ "version": "0.0.123",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -0,0 +1,89 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { logger } from "@remit/logger-lambda";
5
+ import {
6
+ backfillClassifications,
7
+ type ClassificationBackfillCheckpoint,
8
+ type ClassificationBackfillCheckpointStore,
9
+ } from "@remit/mailbox-service";
10
+ import { getClient } from "../src/service/data-client.js";
11
+
12
+ /**
13
+ * One-time, full-corpus classification backfill over the stored-body cohort
14
+ * the classifier never examined (issue #1197): rows whose bodies were synced
15
+ * before the classifier reached them, which carry `NotExamined` on
16
+ * `Message.classificationState` and so stay `uncategorized` in the list. The
17
+ * same alternate-entrypoint shape `backfill-list-id.mjs` uses: baked into the
18
+ * backend image, never wired into a compose one-shot, run by hand once per
19
+ * install:
20
+ *
21
+ * docker compose -f docker-compose.sqlite.yml run --rm backend \
22
+ * node backfill-classification.mjs
23
+ *
24
+ * Safe to interrupt: progress is checkpointed to disk after every page and
25
+ * picked back up on the next run, every already-examined row reads as
26
+ * `alreadyExamined` on a rerun, and a row whose category was already decided
27
+ * is recorded without re-deriving it, so a partial pass resumes rather than
28
+ * repeats.
29
+ */
30
+
31
+ const CHECKPOINT_PATH =
32
+ process.env.CLASSIFICATION_BACKFILL_CHECKPOINT_PATH ??
33
+ "/data/sqlite/classification-backfill-checkpoint.json";
34
+
35
+ const isCheckpoint = (
36
+ value: unknown,
37
+ ): value is ClassificationBackfillCheckpoint => {
38
+ if (typeof value !== "object" || value === null) return false;
39
+ if (!("accountConfigId" in value)) return false;
40
+ const candidate = value as Record<string, unknown>;
41
+ return (
42
+ typeof candidate.accountConfigId === "string" &&
43
+ (candidate.continuationToken === undefined ||
44
+ typeof candidate.continuationToken === "string")
45
+ );
46
+ };
47
+
48
+ const fileCheckpointStore: ClassificationBackfillCheckpointStore = {
49
+ load: async () => {
50
+ if (!existsSync(CHECKPOINT_PATH)) return undefined;
51
+ const raw = await readFile(CHECKPOINT_PATH, "utf8");
52
+ const parsed: unknown = JSON.parse(raw);
53
+ return isCheckpoint(parsed) ? parsed : undefined;
54
+ },
55
+ save: async (checkpoint) => {
56
+ await mkdir(dirname(CHECKPOINT_PATH), { recursive: true });
57
+ const staging = `${CHECKPOINT_PATH}.writing`;
58
+ await writeFile(staging, JSON.stringify(checkpoint));
59
+ await rename(staging, CHECKPOINT_PATH);
60
+ },
61
+ clear: async () => {
62
+ await rm(CHECKPOINT_PATH, { force: true });
63
+ },
64
+ };
65
+
66
+ const run = async (): Promise<void> => {
67
+ const client = await getClient();
68
+
69
+ const result = await backfillClassifications(
70
+ {
71
+ accountConfigService: client.accountConfig,
72
+ addressService: client.address,
73
+ threadMessageService: client.threadMessage,
74
+ messageService: client.message,
75
+ storageService: client.storage,
76
+ },
77
+ { checkpointStore: fileCheckpointStore, logger },
78
+ );
79
+
80
+ // biome-ignore lint/plugin/no-logger-info: a completed full-corpus backfill is an audit-grade signal
81
+ logger.info({ result }, "backfill done");
82
+ };
83
+
84
+ run()
85
+ .then(() => process.exit(0))
86
+ .catch((error: unknown) => {
87
+ logger.error({ error }, "backfill failed");
88
+ process.exit(1);
89
+ });