@remit/drizzle-service 0.0.75 → 0.0.77
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/drizzle-service",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.77",
|
|
4
4
|
"description": "Drizzle ORM service over SQLite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./src/index.ts",
|
|
11
11
|
"default": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./test-sqlite": {
|
|
14
|
+
"types": "./src/test-sqlite.ts",
|
|
15
|
+
"default": "./src/test-sqlite.ts"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"scripts": {
|
|
@@ -53,10 +53,9 @@
|
|
|
53
53
|
* `message.category` is still `uncategorized`, so the pending exclusion
|
|
54
54
|
* above refuses the row outright.
|
|
55
55
|
*
|
|
56
|
-
* Only the fourth is interesting, and it is not impossible. It is
|
|
57
|
-
* through
|
|
58
|
-
*
|
|
59
|
-
* skip guard is `if (message.bodyStorageKey && !force)` (`body-sync.ts`), and
|
|
56
|
+
* Only the fourth is interesting, and it is not impossible. It is reachable
|
|
57
|
+
* through the one path that classifies, which is re-enterable: the skip guard
|
|
58
|
+
* is `if (hasStoredBody(message.bodyStorageKey) && !force)` (`body-sync.ts`), and
|
|
60
59
|
* `force` is a live event flag — the read-miss re-arm cue, resolved in the
|
|
61
60
|
* imap-worker's `sync-message-body.ts`. A forced re-fetch re-runs
|
|
62
61
|
* `classifyByHeaders` over the same bytes, so it normally rewrites the value it
|
|
@@ -351,7 +350,7 @@ export const formatCheckReport = (
|
|
|
351
350
|
` ahead: ${report.ahead} ${plural(report.ahead)} classified against a pending message — after #326 body-sync writes the row before the message, so this is a classification in flight. Not repaired: pushing it back to pending would undo a correct classification and serve Unclassified for mail that is already classified. Expected non-zero on a live instance mid-sync, zero on a quiescent one.`,
|
|
352
351
|
`fan-out: ${report.fanOutMessages} messages holding ${report.fanOutRows} thread_message rows — the multi-row shape #326 hardens against. Expected zero: deriveMessageId and deriveThreadMessageId are both mailbox-independent, so a message in two mailboxes collapses to one row, and the reachable case is thread-root drift.`,
|
|
353
352
|
`orphans: ${report.orphanRows} ${plural(report.orphanRows)} with no message row — not repaired, nothing to copy. Expected zero.`,
|
|
354
|
-
`
|
|
353
|
+
`uncategorized: ${report.notYetClassified} ${plural(report.notYetClassified)} uncategorized against an uncategorized message — the row agrees with its message, so there is nothing to repair and this figure is untouched by the repair. It is NOT the same as "not classified yet": it mixes mail the classifier has not reached with mail it reached and had nothing to say about. Only \`message.classification_state\` separates them (\`SELECT classification_state, count(*) FROM message WHERE category = 'uncategorized' GROUP BY 1\`), and a large \`NotExamined\` count is the cohort worth acting on. Expected non-zero on a live instance.`,
|
|
355
354
|
`divergent per mailbox: ${
|
|
356
355
|
report.divergentByMailbox.length === 0
|
|
357
356
|
? "none"
|
package/src/repos/i4-account.ts
CHANGED
|
@@ -19,6 +19,17 @@ import { rowToMailbox } from "./i4-mailbox.js";
|
|
|
19
19
|
|
|
20
20
|
type DB = Db<Record<string, unknown>>;
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* A stored failure is a sentence the account card shows, not a transcript.
|
|
24
|
+
* Long enough for what a mail server says when it refuses, short enough that a
|
|
25
|
+
* chatty one — or a stack-carrying `Error.message` from a sync — cannot fill the
|
|
26
|
+
* column. Clamped here so every writer is bounded, whatever it hands over.
|
|
27
|
+
*/
|
|
28
|
+
const LAST_ERROR_MAX_LENGTH = 500;
|
|
29
|
+
|
|
30
|
+
const clampLastError = (value: string | undefined): string | undefined =>
|
|
31
|
+
value === undefined ? undefined : value.slice(0, LAST_ERROR_MAX_LENGTH);
|
|
32
|
+
|
|
22
33
|
export function rowToAccount(
|
|
23
34
|
row: typeof accountTable.$inferSelect,
|
|
24
35
|
): AccountItem {
|
|
@@ -88,7 +99,7 @@ export class AccountRepo implements IAccountRepository {
|
|
|
88
99
|
connectionState: input.connectionState,
|
|
89
100
|
lastConnectedAt: input.lastConnectedAt,
|
|
90
101
|
lastSyncAt: input.lastSyncAt,
|
|
91
|
-
lastError: input.lastError,
|
|
102
|
+
lastError: clampLastError(input.lastError),
|
|
92
103
|
syncPhase: input.syncPhase,
|
|
93
104
|
mailboxCountTotal: input.mailboxCountTotal,
|
|
94
105
|
mailboxCountSynced: input.mailboxCountSynced,
|
|
@@ -159,7 +170,8 @@ export class AccountRepo implements IAccountRepository {
|
|
|
159
170
|
if (input.lastConnectedAt !== undefined)
|
|
160
171
|
updates.lastConnectedAt = input.lastConnectedAt;
|
|
161
172
|
if (input.lastSyncAt !== undefined) updates.lastSyncAt = input.lastSyncAt;
|
|
162
|
-
if (input.lastError !== undefined)
|
|
173
|
+
if (input.lastError !== undefined)
|
|
174
|
+
updates.lastError = clampLastError(input.lastError);
|
|
163
175
|
if (input.syncPhase !== undefined) updates.syncPhase = input.syncPhase;
|
|
164
176
|
if (input.mailboxCountTotal !== undefined)
|
|
165
177
|
updates.mailboxCountTotal = input.mailboxCountTotal;
|
package/src/repos/message.ts
CHANGED
|
@@ -59,6 +59,7 @@ function toMessageItem(row: typeof messageTable.$inferSelect): MessageItem {
|
|
|
59
59
|
status: row.status,
|
|
60
60
|
syncStatus: row.syncStatus,
|
|
61
61
|
category: row.category,
|
|
62
|
+
classificationState: row.classificationState,
|
|
62
63
|
authenticityVerdict: row.authenticityVerdict,
|
|
63
64
|
hasListUnsubscribe: row.hasListUnsubscribe,
|
|
64
65
|
movedByRemit: row.movedByRemit,
|
|
@@ -185,6 +186,8 @@ export class DrizzleMessageRepository implements IMessageRepository {
|
|
|
185
186
|
status: input.status ?? ("active" as const),
|
|
186
187
|
syncStatus: input.syncStatus ?? ("pending" as const),
|
|
187
188
|
category: input.category ?? ("uncategorized" as const),
|
|
189
|
+
classificationState:
|
|
190
|
+
input.classificationState ?? ("NotExamined" as const),
|
|
188
191
|
authenticityVerdict:
|
|
189
192
|
input.authenticityVerdict ?? ("NotEvaluated" as const),
|
|
190
193
|
hasListUnsubscribe: input.hasListUnsubscribe ?? false,
|
|
@@ -368,6 +371,9 @@ export class DrizzleMessageRepository implements IMessageRepository {
|
|
|
368
371
|
? { syncStatus: input.syncStatus }
|
|
369
372
|
: {}),
|
|
370
373
|
...(input.category !== undefined ? { category: input.category } : {}),
|
|
374
|
+
...(input.classificationState !== undefined
|
|
375
|
+
? { classificationState: input.classificationState }
|
|
376
|
+
: {}),
|
|
371
377
|
...(input.authenticityVerdict !== undefined
|
|
372
378
|
? { authenticityVerdict: input.authenticityVerdict }
|
|
373
379
|
: {}),
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
3
|
+
import type { Db } from "./db.js";
|
|
4
|
+
import {
|
|
5
|
+
applyMigration,
|
|
6
|
+
migrationJournal,
|
|
7
|
+
} from "./test-shipped-sqlite-schema.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The store a test runs against: the committed SQLite entity migrations, in the
|
|
11
|
+
* order the migrator runs them, over the same engine a self-host deployment
|
|
12
|
+
* boots. Applying the shipped DDL rather than pushing the drizzle table objects
|
|
13
|
+
* is the point — a test then fails when the two drift (reader#73).
|
|
14
|
+
*/
|
|
15
|
+
export const applyShippedMigrations = (sqlite: Database.Database): void => {
|
|
16
|
+
for (const entry of [...migrationJournal()].sort(
|
|
17
|
+
(left, right) => left.idx - right.idx,
|
|
18
|
+
)) {
|
|
19
|
+
applyMigration(sqlite, entry.tag);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A migrated in-memory database and a drizzle handle over it, for a test that
|
|
25
|
+
* wants one repo and no file to clean up.
|
|
26
|
+
*/
|
|
27
|
+
export const createShippedSqliteDb = (): {
|
|
28
|
+
db: Db<Record<string, unknown>>;
|
|
29
|
+
sqlite: Database.Database;
|
|
30
|
+
close: () => void;
|
|
31
|
+
} => {
|
|
32
|
+
const sqlite = new Database(":memory:");
|
|
33
|
+
applyShippedMigrations(sqlite);
|
|
34
|
+
return {
|
|
35
|
+
db: drizzle(sqlite) as unknown as Db<Record<string, unknown>>,
|
|
36
|
+
sqlite,
|
|
37
|
+
close: () => sqlite.close(),
|
|
38
|
+
};
|
|
39
|
+
};
|