@remit/data-ports 0.0.58 → 0.0.60
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/data-ports",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.60",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
"./update-manifest": {
|
|
53
53
|
"types": "./src/update-manifest.ts",
|
|
54
54
|
"default": "./src/update-manifest.ts"
|
|
55
|
+
},
|
|
56
|
+
"./message-settlement": {
|
|
57
|
+
"types": "./src/message-settlement.ts",
|
|
58
|
+
"default": "./src/message-settlement.ts"
|
|
55
59
|
}
|
|
56
60
|
},
|
|
57
61
|
"scripts": {
|
package/src/index.ts
CHANGED
|
@@ -51,6 +51,8 @@ export type {
|
|
|
51
51
|
IUnitOfWork,
|
|
52
52
|
UnitOfWorkRepositories,
|
|
53
53
|
} from "./interfaces/unit-of-work.js";
|
|
54
|
+
export type { MessageSettlementFields } from "./message-settlement.js";
|
|
55
|
+
export { hasAbandonedDelete } from "./message-settlement.js";
|
|
54
56
|
export {
|
|
55
57
|
FILTER_NO_ACTION,
|
|
56
58
|
isSenderMuted,
|
|
@@ -199,6 +199,17 @@ export interface IThreadMessageRepository {
|
|
|
199
199
|
excludeDeleted?: boolean;
|
|
200
200
|
},
|
|
201
201
|
): Promise<ResultList<ThreadMessageItem>>;
|
|
202
|
+
/**
|
|
203
|
+
* COUNT of matching MESSAGES in one mailbox, under the same predicate
|
|
204
|
+
* `searchByMailboxWindow` reads with — the per-mailbox counterpart of
|
|
205
|
+
* `countThreadsInScope`, and not distinct on `threadId` because the listing
|
|
206
|
+
* it answers for renders a row per message.
|
|
207
|
+
*
|
|
208
|
+
* The number is over the whole mailbox. No page size bounds it and no cursor
|
|
209
|
+
* narrows it, so it does not shrink as the caller pages and does not change
|
|
210
|
+
* when the page size does (#305). There is nothing to cap: the count is one
|
|
211
|
+
* aggregate read that returns no rows.
|
|
212
|
+
*/
|
|
202
213
|
countByMailbox(
|
|
203
214
|
accountConfigId: string,
|
|
204
215
|
mailboxId: string,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { MessageStatus, MessageSyncStatus } from "@remit/domain-enums";
|
|
4
|
+
import { hasAbandonedDelete } from "./message-settlement.js";
|
|
5
|
+
|
|
6
|
+
describe("hasAbandonedDelete", () => {
|
|
7
|
+
test("the pair abandonDelete writes, its only writer", () => {
|
|
8
|
+
assert.equal(
|
|
9
|
+
hasAbandonedDelete({
|
|
10
|
+
status: MessageStatus.active,
|
|
11
|
+
syncStatus: MessageSyncStatus.failed,
|
|
12
|
+
}),
|
|
13
|
+
true,
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("a move mid-retry is not a give-up, whatever `failed` suggests", () => {
|
|
18
|
+
assert.equal(
|
|
19
|
+
hasAbandonedDelete({
|
|
20
|
+
status: MessageStatus.moving,
|
|
21
|
+
syncStatus: MessageSyncStatus.failed,
|
|
22
|
+
}),
|
|
23
|
+
false,
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("a delete mid-retry keeps `deleting`, so it is not a give-up", () => {
|
|
28
|
+
assert.equal(
|
|
29
|
+
hasAbandonedDelete({
|
|
30
|
+
status: MessageStatus.deleting,
|
|
31
|
+
syncStatus: MessageSyncStatus.failed,
|
|
32
|
+
}),
|
|
33
|
+
false,
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("a copy that gave up hides its row as `deleted` rather than claiming this", () => {
|
|
38
|
+
assert.equal(
|
|
39
|
+
hasAbandonedDelete({
|
|
40
|
+
status: MessageStatus.deleted,
|
|
41
|
+
syncStatus: MessageSyncStatus.failed,
|
|
42
|
+
}),
|
|
43
|
+
false,
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Both a settled move (`updateUid`) and a delete that exhausted its retries
|
|
49
|
+
* (#1143 repairs the row to where the message actually is) land on this pair.
|
|
50
|
+
* The second is a give-up this predicate deliberately cannot see — pinned so
|
|
51
|
+
* the gap is a decision on record, not an oversight.
|
|
52
|
+
*/
|
|
53
|
+
test("`active` + `synced` is never a give-up, however it was reached", () => {
|
|
54
|
+
assert.equal(
|
|
55
|
+
hasAbandonedDelete({
|
|
56
|
+
status: MessageStatus.active,
|
|
57
|
+
syncStatus: MessageSyncStatus.synced,
|
|
58
|
+
}),
|
|
59
|
+
false,
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("an ordinary inbound row, `pending` forever, says nothing", () => {
|
|
64
|
+
assert.equal(
|
|
65
|
+
hasAbandonedDelete({
|
|
66
|
+
status: MessageStatus.active,
|
|
67
|
+
syncStatus: MessageSyncStatus.pending,
|
|
68
|
+
}),
|
|
69
|
+
false,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { MessageStatus, MessageSyncStatus } from "@remit/domain-enums";
|
|
2
|
+
import type { MessageItem } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export type MessageSettlementFields = Pick<
|
|
5
|
+
MessageItem,
|
|
6
|
+
"status" | "syncStatus"
|
|
7
|
+
>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A delete Remit refused to run, having already removed the message locally.
|
|
11
|
+
* `abandonDelete` reaches this from four checks, all of them made before any
|
|
12
|
+
* expunge: the Trash folder the event names is not on the server (TRYCREATE),
|
|
13
|
+
* the event carries no destination, it names an operation this build does not
|
|
14
|
+
* recognise, or it was minted under an unknown contract. The row is handed back
|
|
15
|
+
* to the folder the server still holds the message in.
|
|
16
|
+
*
|
|
17
|
+
* `status: active` alongside `syncStatus: failed` is the whole signal, and
|
|
18
|
+
* `abandonDelete` (`imap-worker/src/handlers/message-delete.ts`) is its ONLY
|
|
19
|
+
* writer. Every other writer of either value writes the other along with it:
|
|
20
|
+
*
|
|
21
|
+
* - `updateUid` (`drizzle-service/src/repos/message.ts`) settles a confirmed
|
|
22
|
+
* move by writing `active` and `synced` in the same statement, so a move that
|
|
23
|
+
* worked after a failed attempt cannot leave `failed` behind.
|
|
24
|
+
* - `empty-trash` and `message-copy`'s `settleCopied` both hand a row back as
|
|
25
|
+
* `active` + `synced`.
|
|
26
|
+
* - `upsertWithStatus` leaves an existing row alone, so no inbound sync writes
|
|
27
|
+
* either value onto a row that already has them.
|
|
28
|
+
*
|
|
29
|
+
* `syncStatus: failed` ON ITS OWN is NOT a give-up marker, whatever
|
|
30
|
+
* `placement-settled.ts`'s docstring says. `message-move.ts`, `message-delete.ts`
|
|
31
|
+
* and `message-copy.ts` each write it on an ORDINARY TRANSIENT attempt and then
|
|
32
|
+
* re-throw for queue redelivery — the row is mid-retry and about to succeed. In
|
|
33
|
+
* those handlers `status` stays at its in-flight value (`moving`, `deleting`),
|
|
34
|
+
* which is what separates them from the pair above.
|
|
35
|
+
*
|
|
36
|
+
* Three give-ups this cannot see, and must not pretend to:
|
|
37
|
+
*
|
|
38
|
+
* - A MOVE that gave up leaves `moving` + `failed`, the same pair a first
|
|
39
|
+
* dropped connection writes. Nothing persisted tells the two apart.
|
|
40
|
+
* - A DELETE that exhausted its retries settles `active` + `synced` (#1143's
|
|
41
|
+
* `resolveExhaustedMessageDeleteFailure` repairs the row to where the message
|
|
42
|
+
* actually is), so it reads as fully settled here.
|
|
43
|
+
* - `flag-push` and `placement-move-push` never write either field at all.
|
|
44
|
+
* Their give-up state lives on their own marker rows.
|
|
45
|
+
*/
|
|
46
|
+
export const hasAbandonedDelete = (message: MessageSettlementFields): boolean =>
|
|
47
|
+
message.status === MessageStatus.active &&
|
|
48
|
+
message.syncStatus === MessageSyncStatus.failed;
|