@remit/data-ports 0.0.83 → 0.0.84
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/account-settings.d.ts +371 -0
- package/dist/account-settings.js +114 -0
- package/dist/conformance/account.d.ts +3 -0
- package/dist/conformance/account.js +118 -0
- package/dist/conformance/calendar-collection.d.ts +3 -0
- package/dist/conformance/calendar-collection.js +251 -0
- package/dist/conformance/calendar-event-index.d.ts +3 -0
- package/dist/conformance/calendar-event-index.js +114 -0
- package/dist/conformance/calendar-feed-token.d.ts +3 -0
- package/dist/conformance/calendar-feed-token.js +97 -0
- package/dist/conformance/calendar-object.d.ts +3 -0
- package/dist/conformance/calendar-object.js +156 -0
- package/dist/conformance/calendar-suggestion.d.ts +3 -0
- package/dist/conformance/calendar-suggestion.js +255 -0
- package/{src/conformance/harness.ts → dist/conformance/harness.d.ts} +4 -4
- package/dist/conformance/harness.js +1 -0
- package/dist/conformance/index.js +8 -0
- package/dist/conformance/label.d.ts +3 -0
- package/dist/conformance/label.js +92 -0
- package/dist/conformance/sender-signer-standing.d.ts +3 -0
- package/dist/conformance/sender-signer-standing.js +81 -0
- package/dist/display-name.d.ts +1 -0
- package/dist/display-name.js +26 -0
- package/dist/errors.d.ts +150 -0
- package/dist/errors.js +181 -0
- package/dist/filter-ttl.d.ts +7 -0
- package/{src/filter-ttl.ts → dist/filter-ttl.js} +8 -11
- package/dist/folder-role.d.ts +239 -0
- package/dist/folder-role.js +276 -0
- package/dist/id.d.ts +124 -0
- package/{src/id.ts → dist/id.js} +41 -127
- package/dist/index.d.ts +37 -0
- package/dist/index.js +5 -0
- package/dist/interfaces/account-config.d.ts +15 -0
- package/dist/interfaces/account-config.js +1 -0
- package/dist/interfaces/account-export-request.d.ts +10 -0
- package/dist/interfaces/account-export-request.js +1 -0
- package/dist/interfaces/account-setting.d.ts +7 -0
- package/dist/interfaces/account-setting.js +1 -0
- package/dist/interfaces/account.d.ts +29 -0
- package/dist/interfaces/account.js +1 -0
- package/dist/interfaces/address.d.ts +69 -0
- package/dist/interfaces/address.js +1 -0
- package/dist/interfaces/calendar-collection.d.ts +38 -0
- package/dist/interfaces/calendar-collection.js +1 -0
- package/dist/interfaces/calendar-event-index.d.ts +19 -0
- package/dist/interfaces/calendar-event-index.js +1 -0
- package/dist/interfaces/calendar-feed-token.d.ts +31 -0
- package/dist/interfaces/calendar-feed-token.js +1 -0
- package/dist/interfaces/calendar-object.d.ts +36 -0
- package/dist/interfaces/calendar-object.js +1 -0
- package/dist/interfaces/calendar-store.d.ts +93 -0
- package/dist/interfaces/calendar-store.js +1 -0
- package/dist/interfaces/calendar-suggestion.d.ts +62 -0
- package/dist/interfaces/calendar-suggestion.js +1 -0
- package/dist/interfaces/config-import.d.ts +8 -0
- package/dist/interfaces/config-import.js +1 -0
- package/dist/interfaces/envelope.d.ts +24 -0
- package/dist/interfaces/envelope.js +1 -0
- package/dist/interfaces/filter.d.ts +28 -0
- package/dist/interfaces/filter.js +1 -0
- package/dist/interfaces/label.d.ts +17 -0
- package/dist/interfaces/label.js +1 -0
- package/dist/interfaces/mailbox-lock.d.ts +12 -0
- package/dist/interfaces/mailbox-lock.js +1 -0
- package/dist/interfaces/mailbox-special-use.d.ts +54 -0
- package/dist/interfaces/mailbox-special-use.js +1 -0
- package/dist/interfaces/mailbox.d.ts +79 -0
- package/dist/interfaces/mailbox.js +1 -0
- package/dist/interfaces/message-flag-push.d.ts +9 -0
- package/dist/interfaces/message-flag-push.js +1 -0
- package/dist/interfaces/message-flag.d.ts +14 -0
- package/dist/interfaces/message-flag.js +1 -0
- package/dist/interfaces/message-label.d.ts +21 -0
- package/dist/interfaces/message-label.js +1 -0
- package/dist/interfaces/message-placement-move.d.ts +8 -0
- package/dist/interfaces/message-placement-move.js +1 -0
- package/dist/interfaces/message.d.ts +60 -0
- package/dist/interfaces/message.js +1 -0
- package/dist/interfaces/organize-job-request.d.ts +10 -0
- package/dist/interfaces/organize-job-request.js +1 -0
- package/dist/interfaces/outbox-attachment.d.ts +101 -0
- package/dist/interfaces/outbox-attachment.js +7 -0
- package/dist/interfaces/outbox-message.d.ts +70 -0
- package/dist/interfaces/outbox-message.js +20 -0
- package/dist/interfaces/quarantine.d.ts +20 -0
- package/dist/interfaces/quarantine.js +1 -0
- package/dist/interfaces/sender-signer-standing.d.ts +26 -0
- package/dist/interfaces/sender-signer-standing.js +1 -0
- package/dist/interfaces/sync-warning.d.ts +11 -0
- package/dist/interfaces/sync-warning.js +1 -0
- package/dist/interfaces/thread-message.d.ts +202 -0
- package/dist/interfaces/thread-message.js +1 -0
- package/dist/mailbox-name.d.ts +35 -0
- package/dist/mailbox-name.js +68 -0
- package/dist/message-parts.d.ts +22 -0
- package/dist/message-parts.js +18 -0
- package/dist/message-settlement.d.ts +35 -0
- package/{src/message-settlement.ts → dist/message-settlement.js} +5 -19
- package/dist/mutation-events.d.ts +19 -0
- package/{src/mutation-events.ts → dist/mutation-events.js} +2 -5
- package/dist/sender-mute.d.ts +26 -0
- package/{src/sender-mute.ts → dist/sender-mute.js} +12 -34
- package/dist/sync-warning-state.d.ts +3 -0
- package/dist/sync-warning-state.js +5 -0
- package/dist/types.d.ts +491 -0
- package/dist/types.js +1 -0
- package/dist/update-manifest.d.ts +24 -0
- package/dist/update-manifest.js +20 -0
- package/dist/virtual-copy.d.ts +8 -0
- package/{src/virtual-copy.ts → dist/virtual-copy.js} +10 -15
- package/dist/wellknown.d.ts +47 -0
- package/{src/wellknown.ts → dist/wellknown.js} +26 -49
- package/package.json +44 -79
- package/src/account-settings.ts +0 -194
- package/src/conformance/account.ts +0 -104
- package/src/conformance/calendar-collection.ts +0 -342
- package/src/conformance/calendar-event-index.ts +0 -203
- package/src/conformance/calendar-feed-token.ts +0 -148
- package/src/conformance/calendar-object.ts +0 -269
- package/src/conformance/calendar-suggestion.ts +0 -490
- package/src/conformance/label.ts +0 -136
- package/src/conformance/sender-signer-standing.ts +0 -117
- package/src/display-name.test.ts +0 -101
- package/src/display-name.ts +0 -40
- package/src/errors.ts +0 -244
- package/src/folder-role.test.ts +0 -422
- package/src/folder-role.ts +0 -447
- package/src/id.test.ts +0 -352
- package/src/index.ts +0 -170
- package/src/interfaces/account-config.ts +0 -25
- package/src/interfaces/account-export-request.ts +0 -21
- package/src/interfaces/account-setting.ts +0 -14
- package/src/interfaces/account.ts +0 -43
- package/src/interfaces/address.ts +0 -122
- package/src/interfaces/calendar-collection.ts +0 -60
- package/src/interfaces/calendar-event-index.ts +0 -34
- package/src/interfaces/calendar-feed-token.ts +0 -38
- package/src/interfaces/calendar-object.ts +0 -58
- package/src/interfaces/calendar-suggestion.ts +0 -93
- package/src/interfaces/calendar-unit-of-work.ts +0 -47
- package/src/interfaces/config-import.ts +0 -16
- package/src/interfaces/envelope.ts +0 -32
- package/src/interfaces/filter-anchor-transaction.ts +0 -23
- package/src/interfaces/filter-anchor.ts +0 -11
- package/src/interfaces/filter.ts +0 -41
- package/src/interfaces/label.ts +0 -30
- package/src/interfaces/mailbox-lock.ts +0 -32
- package/src/interfaces/mailbox-special-use.ts +0 -60
- package/src/interfaces/mailbox.ts +0 -115
- package/src/interfaces/message-flag-push.ts +0 -17
- package/src/interfaces/message-flag.ts +0 -15
- package/src/interfaces/message-label.ts +0 -25
- package/src/interfaces/message-placement-move.ts +0 -15
- package/src/interfaces/message.ts +0 -65
- package/src/interfaces/organize-job-request.ts +0 -19
- package/src/interfaces/outbox-attachment.ts +0 -132
- package/src/interfaces/outbox-message.ts +0 -106
- package/src/interfaces/quarantine.ts +0 -22
- package/src/interfaces/sender-signer-standing.ts +0 -37
- package/src/interfaces/thread-message.ts +0 -264
- package/src/interfaces/unit-of-work.ts +0 -28
- package/src/mailbox-name.test.ts +0 -106
- package/src/mailbox-name.ts +0 -90
- package/src/mailbox-state-doors.test.ts +0 -104
- package/src/message-placement-doors.test.ts +0 -100
- package/src/message-settlement.test.ts +0 -132
- package/src/sender-mute.test.ts +0 -76
- package/src/types.ts +0 -820
- package/src/update-manifest.test.ts +0 -130
- package/src/update-manifest.ts +0 -22
- package/src/virtual-copy.test.ts +0 -34
- package/tsconfig.json +0 -8
- /package/{src/conformance/index.ts → dist/conformance/index.d.ts} +0 -0
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { after, before, describe, test } from "node:test";
|
|
3
|
-
import type { ISenderSignerStandingRepository } from "../interfaces/sender-signer-standing.js";
|
|
4
|
-
import type { RepositoryConformanceHarness } from "./harness.js";
|
|
5
|
-
|
|
6
|
-
export function senderSignerStandingRepositoryConformance(
|
|
7
|
-
harness: RepositoryConformanceHarness<ISenderSignerStandingRepository>,
|
|
8
|
-
): void {
|
|
9
|
-
describe("ISenderSignerStandingRepository conformance", () => {
|
|
10
|
-
let repo: ISenderSignerStandingRepository;
|
|
11
|
-
|
|
12
|
-
before(async () => {
|
|
13
|
-
repo = await harness.createRepository();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
after(() => harness.teardown());
|
|
17
|
-
|
|
18
|
-
test("the first observation creates the row at a count of one", async () => {
|
|
19
|
-
const accountConfigId = harness.makeId();
|
|
20
|
-
const observedAt = 1_700_000_000_000;
|
|
21
|
-
|
|
22
|
-
const standing = await repo.observe({
|
|
23
|
-
accountConfigId,
|
|
24
|
-
senderKey: "example.com",
|
|
25
|
-
signerDomain: "esp.example",
|
|
26
|
-
observedAt,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
assert.equal(standing.messageCount, 1);
|
|
30
|
-
assert.equal(standing.firstSeenAt, observedAt);
|
|
31
|
-
assert.equal(standing.lastSeenAt, observedAt);
|
|
32
|
-
assert.equal(standing.userAffirmedAt, 0);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("a repeat observation increments the count", async () => {
|
|
36
|
-
const accountConfigId = harness.makeId();
|
|
37
|
-
const key = {
|
|
38
|
-
accountConfigId,
|
|
39
|
-
senderKey: "example.com",
|
|
40
|
-
signerDomain: "esp.example",
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
await repo.observe({ ...key, observedAt: 1_700_000_000_000 });
|
|
44
|
-
const second = await repo.observe({
|
|
45
|
-
...key,
|
|
46
|
-
observedAt: 1_700_000_060_000,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
assert.equal(second.messageCount, 2);
|
|
50
|
-
assert.equal(
|
|
51
|
-
(await repo.get(accountConfigId, key.senderKey, key.signerDomain))
|
|
52
|
-
.messageCount,
|
|
53
|
-
2,
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("firstSeenAt survives every later observation while lastSeenAt advances", async () => {
|
|
58
|
-
const accountConfigId = harness.makeId();
|
|
59
|
-
const key = {
|
|
60
|
-
accountConfigId,
|
|
61
|
-
senderKey: "news.example.com",
|
|
62
|
-
signerDomain: "unverified",
|
|
63
|
-
};
|
|
64
|
-
const first = 1_700_000_000_000;
|
|
65
|
-
|
|
66
|
-
await repo.observe({ ...key, observedAt: first });
|
|
67
|
-
await repo.observe({ ...key, observedAt: first + 60_000 });
|
|
68
|
-
const third = await repo.observe({ ...key, observedAt: first + 120_000 });
|
|
69
|
-
|
|
70
|
-
assert.equal(third.messageCount, 3);
|
|
71
|
-
assert.equal(third.firstSeenAt, first);
|
|
72
|
-
assert.equal(third.lastSeenAt, first + 120_000);
|
|
73
|
-
|
|
74
|
-
const reread = await repo.get(
|
|
75
|
-
accountConfigId,
|
|
76
|
-
key.senderKey,
|
|
77
|
-
key.signerDomain,
|
|
78
|
-
);
|
|
79
|
-
assert.equal(reread.firstSeenAt, first);
|
|
80
|
-
assert.equal(reread.lastSeenAt, first + 120_000);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("a key differing only in signer domain is a separate row", async () => {
|
|
84
|
-
const accountConfigId = harness.makeId();
|
|
85
|
-
const observedAt = 1_700_000_000_000;
|
|
86
|
-
|
|
87
|
-
await repo.observe({
|
|
88
|
-
accountConfigId,
|
|
89
|
-
senderKey: "example.com",
|
|
90
|
-
signerDomain: "esp-one.example",
|
|
91
|
-
observedAt,
|
|
92
|
-
});
|
|
93
|
-
const other = await repo.observe({
|
|
94
|
-
accountConfigId,
|
|
95
|
-
senderKey: "example.com",
|
|
96
|
-
signerDomain: "esp-two.example",
|
|
97
|
-
observedAt,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
assert.equal(other.messageCount, 1);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("get reports not-found for a key never observed, and does not create it", async () => {
|
|
104
|
-
const accountConfigId = harness.makeId();
|
|
105
|
-
|
|
106
|
-
await assert.rejects(
|
|
107
|
-
repo.get(accountConfigId, "stranger.example", "esp.example"),
|
|
108
|
-
(error) => harness.isNotFoundError(error),
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
await assert.rejects(
|
|
112
|
-
repo.get(accountConfigId, "stranger.example", "esp.example"),
|
|
113
|
-
(error) => harness.isNotFoundError(error),
|
|
114
|
-
);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
}
|
package/src/display-name.test.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import { storedDisplayName } from "./display-name.js";
|
|
4
|
-
|
|
5
|
-
const OWN = "matthijs@ischen.nl";
|
|
6
|
-
const SPOOFED = "aramirez@secresaludguaviare.gov.co";
|
|
7
|
-
|
|
8
|
-
const emptied: ReadonlyArray<readonly [string, string]> = [
|
|
9
|
-
["the incident", "matthijs@ischen.nl"],
|
|
10
|
-
["quoted", '"matthijs@ischen.nl"'],
|
|
11
|
-
["angle-bracketed", "<matthijs@ischen.nl>"],
|
|
12
|
-
["BATV-wrapped", "prvs=0068b51f37=matthijs@ischen.nl"],
|
|
13
|
-
["upper case", "MATTHIJS@ISCHEN.NL"],
|
|
14
|
-
["a subdomain apart", "matthijs@mail.ischen.nl"],
|
|
15
|
-
["separated by a tab", "\tmatthijs@ischen.nl"],
|
|
16
|
-
["separated by a no-break space", " matthijs@ischen.nl"],
|
|
17
|
-
["separated by a zero-width space", "matthijs@ischen.nl"],
|
|
18
|
-
["a non-ASCII local part", "Özcan@example.com"],
|
|
19
|
-
];
|
|
20
|
-
|
|
21
|
-
const stripped: ReadonlyArray<readonly [string, string, string]> = [
|
|
22
|
-
["parenthesised", "Support (support@acme.com)", "Support"],
|
|
23
|
-
["angle-bracketed", "Matthijs <matthijs@ischen.nl>", "Matthijs"],
|
|
24
|
-
["comma-separated", "matthijs@ischen.nl, team", "team"],
|
|
25
|
-
["semicolon-separated", "Team; matthijs@ischen.nl", "Team"],
|
|
26
|
-
["colon-separated", "Reply to: matthijs@ischen.nl", "Reply to"],
|
|
27
|
-
["leading and parenthesised", "(matthijs@ischen.nl) Support", "Support"],
|
|
28
|
-
["mid-name", "Support <matthijs@ischen.nl> Team", "Support Team"],
|
|
29
|
-
["with a trailing verb", "matthijs@ischen.nl wrote", "wrote"],
|
|
30
|
-
[
|
|
31
|
-
"quoted around a name",
|
|
32
|
-
'Bob "The Builder" <bob@acme.com>',
|
|
33
|
-
'Bob "The Builder"',
|
|
34
|
-
],
|
|
35
|
-
["a tab away from the name", "Support\tmatthijs@ischen.nl", "Support"],
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const kept: ReadonlyArray<readonly [string, string]> = [
|
|
39
|
-
["an ordinary human name", "Matthijs van Henten"],
|
|
40
|
-
["a name with a full stop", "Dr. M. van Henten"],
|
|
41
|
-
["a company name", "ING Bank N.V."],
|
|
42
|
-
["a bare word", "Newsletter"],
|
|
43
|
-
["an empty name", ""],
|
|
44
|
-
["a name with an at-sign but no domain", "me @ home"],
|
|
45
|
-
["a social handle", "@matthijs"],
|
|
46
|
-
["a version string", "release 2.11"],
|
|
47
|
-
["a domain with no local part", "ischen.nl"],
|
|
48
|
-
["a single-letter tld", "a@b.c"],
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
describe("a display name that claims to be an address", () => {
|
|
52
|
-
for (const [what, name] of emptied) {
|
|
53
|
-
it(`empties ${what}`, () => {
|
|
54
|
-
assert.equal(storedDisplayName(name, SPOOFED), "");
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
for (const [what, name, remainder] of stripped) {
|
|
59
|
-
it(`keeps what is left after ${what}`, () => {
|
|
60
|
-
assert.equal(storedDisplayName(name, SPOOFED), remainder);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
for (const [what, name] of kept) {
|
|
65
|
-
it(`leaves ${what} alone`, () => {
|
|
66
|
-
assert.equal(storedDisplayName(name, OWN), name);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
it("keeps the address it labels", () => {
|
|
71
|
-
assert.equal(storedDisplayName(OWN, OWN), OWN);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("keeps its own address inside a longer name", () => {
|
|
75
|
-
const name = "Matthijs <matthijs@ischen.nl>";
|
|
76
|
-
assert.equal(storedDisplayName(name, OWN), name);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("folds case beyond ASCII on both sides", () => {
|
|
80
|
-
assert.equal(
|
|
81
|
-
storedDisplayName("Özcan@example.com", "özcan@example.com"),
|
|
82
|
-
"Özcan@example.com",
|
|
83
|
-
);
|
|
84
|
-
assert.equal(
|
|
85
|
-
storedDisplayName("ÖZCAN@EXAMPLE.COM", "özcan@example.com"),
|
|
86
|
-
"ÖZCAN@EXAMPLE.COM",
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("removes any address when the envelope carried none", () => {
|
|
91
|
-
assert.equal(storedDisplayName(`Support ${OWN}`, undefined), "Support");
|
|
92
|
-
assert.equal(storedDisplayName("Matthijs", undefined), "Matthijs");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("removes a second address and keeps its own", () => {
|
|
96
|
-
assert.equal(
|
|
97
|
-
storedDisplayName(`${OWN} on behalf of ${SPOOFED}`, OWN),
|
|
98
|
-
`${OWN} on behalf of`,
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
});
|
package/src/display-name.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
const EMBEDDED_ADDRESS =
|
|
2
|
-
/[^\s@<>()[\],;:"'\\]+@[^\s@<>()[\],;:"'\\]+\.[^\s@<>()[\],;:"'\\.]{2,}/gu;
|
|
3
|
-
|
|
4
|
-
const DELIMITER_PAIR_LEFT_EMPTY = /[<([{"']\s*[>)\]}"']/gu;
|
|
5
|
-
|
|
6
|
-
const LEADING_OR_TRAILING_SEPARATORS = /^[\s,;:|/\\-]+|[\s,;:|/\\-]+$/gu;
|
|
7
|
-
|
|
8
|
-
const tidyAfterAddressRemoval = (text: string): string =>
|
|
9
|
-
text
|
|
10
|
-
.replace(DELIMITER_PAIR_LEFT_EMPTY, " ")
|
|
11
|
-
.replace(/\s+/gu, " ")
|
|
12
|
-
.replace(LEADING_OR_TRAILING_SEPARATORS, "")
|
|
13
|
-
.trim();
|
|
14
|
-
|
|
15
|
-
const carriesAddressOtherThan = (
|
|
16
|
-
text: string,
|
|
17
|
-
ownAddress: string | undefined,
|
|
18
|
-
): boolean => {
|
|
19
|
-
for (const [address] of text.matchAll(EMBEDDED_ADDRESS)) {
|
|
20
|
-
if (address.toLowerCase() !== ownAddress) return true;
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const storedDisplayName = (
|
|
26
|
-
displayName: string | undefined,
|
|
27
|
-
normalizedEmail: string | undefined,
|
|
28
|
-
): string => {
|
|
29
|
-
if (!displayName) return "";
|
|
30
|
-
const ownAddress = normalizedEmail?.toLowerCase();
|
|
31
|
-
if (!carriesAddressOtherThan(displayName, ownAddress)) return displayName;
|
|
32
|
-
|
|
33
|
-
const remainder = tidyAfterAddressRemoval(
|
|
34
|
-
displayName.replace(EMBEDDED_ADDRESS, (address) =>
|
|
35
|
-
address.toLowerCase() === ownAddress ? address : " ",
|
|
36
|
-
),
|
|
37
|
-
);
|
|
38
|
-
if (!remainder || carriesAddressOtherThan(remainder, ownAddress)) return "";
|
|
39
|
-
return remainder;
|
|
40
|
-
};
|
package/src/errors.ts
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CanonicalMailboxRoleValue,
|
|
3
|
-
FolderRoleUnresolvedReason,
|
|
4
|
-
} from "./folder-role.js";
|
|
5
|
-
|
|
6
|
-
export type { FolderRoleUnresolvedReason };
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* The half of an error a client may read: a stable `code` it branches on and
|
|
10
|
-
* string `details` it words its own prompt from. Opt-in — an error that
|
|
11
|
-
* declares none keeps today's `{ message }` body, so no response gains a
|
|
12
|
-
* shape the API never promised and no internal failure leaks its innards.
|
|
13
|
-
*/
|
|
14
|
-
export interface PublicApiError {
|
|
15
|
-
code: string;
|
|
16
|
-
details?: Record<string, string>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const isPublicApiError = (value: unknown): value is PublicApiError => {
|
|
20
|
-
if (typeof value !== "object" || value === null) return false;
|
|
21
|
-
if (!("code" in value) || typeof value.code !== "string") return false;
|
|
22
|
-
if (!("details" in value) || value.details === undefined) return true;
|
|
23
|
-
if (typeof value.details !== "object" || value.details === null) return false;
|
|
24
|
-
return Object.values(value.details).every(
|
|
25
|
-
(detail) => typeof detail === "string",
|
|
26
|
-
);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export class HTTPError extends Error {
|
|
30
|
-
public statusCode = 500;
|
|
31
|
-
publicApiError?: PublicApiError;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export class BadRequestError extends HTTPError {
|
|
35
|
-
name = "BadRequestError";
|
|
36
|
-
public statusCode = 400;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class ClientError extends HTTPError {
|
|
40
|
-
name = "ClientError";
|
|
41
|
-
public statusCode = 401;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export class ForbiddenError extends HTTPError {
|
|
45
|
-
name = "ForbiddenError";
|
|
46
|
-
public statusCode = 403;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export class NotFoundError extends HTTPError {
|
|
50
|
-
name = "NotFoundError";
|
|
51
|
-
public statusCode = 404;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class ConflictError extends HTTPError {
|
|
55
|
-
name = "ConflictError";
|
|
56
|
-
public statusCode = 409;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* A destructive action refused because the role it needs is unresolved. The
|
|
61
|
-
* client reads `code` and words its prompt from `details` — never from the
|
|
62
|
-
* message — so the copy can change without breaking the branch, and the
|
|
63
|
-
* account the prompt has to appoint a folder on travels with the refusal
|
|
64
|
-
* (the delete endpoint's body carries no accountId).
|
|
65
|
-
*/
|
|
66
|
-
export class FolderRoleUnresolvedError extends ConflictError {
|
|
67
|
-
name = "FolderRoleUnresolvedError";
|
|
68
|
-
|
|
69
|
-
constructor(
|
|
70
|
-
message: string,
|
|
71
|
-
role: CanonicalMailboxRoleValue,
|
|
72
|
-
reason: FolderRoleUnresolvedReason,
|
|
73
|
-
accountId: string,
|
|
74
|
-
) {
|
|
75
|
-
super(message);
|
|
76
|
-
this.publicApiError = {
|
|
77
|
-
code: "folder_role_unresolved",
|
|
78
|
-
details: { role, reason, accountId },
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* A durable reference cannot be bound to a mailbox whose own mutation has not
|
|
85
|
-
* settled (folder-rename-and-delete.md D12, imap-mutations R2: wait). Its own
|
|
86
|
-
* code, because the role is not unresolved — the target is: the client words a
|
|
87
|
-
* wait, not a retry. Clearing a role is never refused this way.
|
|
88
|
-
*
|
|
89
|
-
* **422, not 409** (D4). 409 already means "this folder is being changed right
|
|
90
|
-
* now, refresh"; this means "the folder you are pointing at has not settled
|
|
91
|
-
* yet", which is a different sentence and a different remedy. The status is
|
|
92
|
-
* what separates them for a client that reads no further.
|
|
93
|
-
*/
|
|
94
|
-
export class MailboxNotSettledError extends HTTPError {
|
|
95
|
-
name = "MailboxNotSettledError";
|
|
96
|
-
public statusCode = 422;
|
|
97
|
-
|
|
98
|
-
constructor(message: string, mailboxId: string, syncStatus: string) {
|
|
99
|
-
super(message);
|
|
100
|
-
this.publicApiError = {
|
|
101
|
-
code: "mailbox_not_settled",
|
|
102
|
-
details: { mailboxId, syncStatus },
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Why the row's folder and uid do not name the same message. One value, because
|
|
109
|
-
* one state produces it: a mutation is still in flight and the pair clears on
|
|
110
|
-
* its own, so the client words a wait rather than a repair. A mutation that
|
|
111
|
-
* gave up does not reach here — it hands the row back to a placement the server
|
|
112
|
-
* confirmed first (docs/architecture/imap-mutations.md R3), which is a pair
|
|
113
|
-
* every dependent mutation can act on.
|
|
114
|
-
*/
|
|
115
|
-
export type MessagePlacementUnsettledReason = "in_flight";
|
|
116
|
-
|
|
117
|
-
export class MessagePlacementUnsettledError extends ConflictError {
|
|
118
|
-
name = "MessagePlacementUnsettledError";
|
|
119
|
-
|
|
120
|
-
constructor(
|
|
121
|
-
message: string,
|
|
122
|
-
accountId: string,
|
|
123
|
-
messageId: string,
|
|
124
|
-
reason: MessagePlacementUnsettledReason,
|
|
125
|
-
) {
|
|
126
|
-
super(message);
|
|
127
|
-
this.publicApiError = {
|
|
128
|
-
code: "message_placement_unsettled",
|
|
129
|
-
details: { accountId, messageId, reason },
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* The row named a message the mail server no longer holds at that UID —
|
|
136
|
-
* another client expunged it or moved it away. Distinct from a plain
|
|
137
|
-
* not-found so the client can say where the message went rather than
|
|
138
|
-
* claiming the user deleted it.
|
|
139
|
-
*/
|
|
140
|
-
export class MessageGoneFromServerError extends NotFoundError {
|
|
141
|
-
name = "MessageGoneFromServerError";
|
|
142
|
-
|
|
143
|
-
constructor(messageId: string) {
|
|
144
|
-
super("This message is no longer on the mail server.");
|
|
145
|
-
this.publicApiError = {
|
|
146
|
-
code: "message_gone_from_server",
|
|
147
|
-
details: { messageId },
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* A configuration import refused because this configuration is not empty
|
|
154
|
-
* (#1021). The default is to refuse rather than to fold the file in: a person
|
|
155
|
-
* importing over a configuration they forgot they had should hear about it
|
|
156
|
-
* before anything moves. `details` counts what is already here, so the client
|
|
157
|
-
* can say what would be merged, and the same request with `onExisting: merge`
|
|
158
|
-
* goes through.
|
|
159
|
-
*/
|
|
160
|
-
export class ConfigNotEmptyError extends ConflictError {
|
|
161
|
-
name = "ConfigNotEmptyError";
|
|
162
|
-
|
|
163
|
-
constructor(message: string, details: Record<string, string>) {
|
|
164
|
-
super(message);
|
|
165
|
-
this.publicApiError = { code: "config_not_empty", details };
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* A message exists but its body could not be fetched/parsed after every
|
|
171
|
-
* body-sync retry was spent (issue #1270 / epic #1281 invariant 3). This is
|
|
172
|
-
* distinct from `NotFoundError`: the message is real, but its content is
|
|
173
|
-
* permanently unavailable — the client must show an explicit, actionable
|
|
174
|
-
* error instead of retrying (no 202, no silent skip).
|
|
175
|
-
*/
|
|
176
|
-
export class UnrecoverableBodyError extends HTTPError {
|
|
177
|
-
name = "UnrecoverableBodyError";
|
|
178
|
-
public statusCode = 422;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export class CreateFailedConflictError extends ConflictError {
|
|
182
|
-
public statusCode = 409;
|
|
183
|
-
name = "CreateFailedConflictError";
|
|
184
|
-
constructor(resourceType: string, params: unknown) {
|
|
185
|
-
super(
|
|
186
|
-
`${resourceType} with properties "${JSON.stringify(params)}" already exists.`,
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export class UnhandledError extends HTTPError {
|
|
192
|
-
name = "UnhandledError";
|
|
193
|
-
public statusCode = 500;
|
|
194
|
-
readonly cause: Error | undefined;
|
|
195
|
-
|
|
196
|
-
constructor(message: string, cause?: Error) {
|
|
197
|
-
super(message);
|
|
198
|
-
this.message = message;
|
|
199
|
-
if (cause) this.cause = cause;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
toJSON() {
|
|
203
|
-
return {
|
|
204
|
-
message: this.message,
|
|
205
|
-
cause: this.cause?.message,
|
|
206
|
-
stack: this.cause?.stack || this.stack,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export class InternalServerError extends UnhandledError {
|
|
212
|
-
name = "InternalServerError";
|
|
213
|
-
public statusCode = 500;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* A repository lookup or write for a row that no longer exists rejects with a
|
|
218
|
-
* `NotFoundError`, name-matched rather than by `instanceof`: the class crosses
|
|
219
|
-
* the adapter boundary and an adapter may throw its own.
|
|
220
|
-
*
|
|
221
|
-
* A worker event that references a deliberately-deleted mailbox is completed or
|
|
222
|
-
* moot work, never a transient fault: every redelivery re-throws the same
|
|
223
|
-
* `NotFoundError`, and because the sync queues carry `MessageGroupId=accountId`
|
|
224
|
-
* that permanently-failing head message stalls the whole account's per-group
|
|
225
|
-
* FIFO. Handlers use this predicate to resolve such an event terminally (ack
|
|
226
|
-
* with a WARN) instead of retrying forever (issues #287, #289, #290).
|
|
227
|
-
*
|
|
228
|
-
* The guard is narrow on purpose: only a genuine not-found terminates. Real
|
|
229
|
-
* IMAP/infra failures carry other errors and must still propagate to be retried.
|
|
230
|
-
*/
|
|
231
|
-
export const isNotFoundError = (error: unknown): boolean =>
|
|
232
|
-
(error as { name?: string })?.name === "NotFoundError";
|
|
233
|
-
|
|
234
|
-
export class MailSyncOffError extends BadRequestError {
|
|
235
|
-
name = "MailSyncOffError";
|
|
236
|
-
|
|
237
|
-
constructor(message: string, accountId: string) {
|
|
238
|
-
super(message);
|
|
239
|
-
this.publicApiError = {
|
|
240
|
-
code: "mail_sync_off",
|
|
241
|
-
details: { accountId },
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}
|