@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.
Files changed (174) hide show
  1. package/dist/account-settings.d.ts +371 -0
  2. package/dist/account-settings.js +114 -0
  3. package/dist/conformance/account.d.ts +3 -0
  4. package/dist/conformance/account.js +118 -0
  5. package/dist/conformance/calendar-collection.d.ts +3 -0
  6. package/dist/conformance/calendar-collection.js +251 -0
  7. package/dist/conformance/calendar-event-index.d.ts +3 -0
  8. package/dist/conformance/calendar-event-index.js +114 -0
  9. package/dist/conformance/calendar-feed-token.d.ts +3 -0
  10. package/dist/conformance/calendar-feed-token.js +97 -0
  11. package/dist/conformance/calendar-object.d.ts +3 -0
  12. package/dist/conformance/calendar-object.js +156 -0
  13. package/dist/conformance/calendar-suggestion.d.ts +3 -0
  14. package/dist/conformance/calendar-suggestion.js +255 -0
  15. package/{src/conformance/harness.ts → dist/conformance/harness.d.ts} +4 -4
  16. package/dist/conformance/harness.js +1 -0
  17. package/dist/conformance/index.js +8 -0
  18. package/dist/conformance/label.d.ts +3 -0
  19. package/dist/conformance/label.js +92 -0
  20. package/dist/conformance/sender-signer-standing.d.ts +3 -0
  21. package/dist/conformance/sender-signer-standing.js +81 -0
  22. package/dist/display-name.d.ts +1 -0
  23. package/dist/display-name.js +26 -0
  24. package/dist/errors.d.ts +150 -0
  25. package/dist/errors.js +181 -0
  26. package/dist/filter-ttl.d.ts +7 -0
  27. package/{src/filter-ttl.ts → dist/filter-ttl.js} +8 -11
  28. package/dist/folder-role.d.ts +239 -0
  29. package/dist/folder-role.js +276 -0
  30. package/dist/id.d.ts +124 -0
  31. package/{src/id.ts → dist/id.js} +41 -127
  32. package/dist/index.d.ts +37 -0
  33. package/dist/index.js +5 -0
  34. package/dist/interfaces/account-config.d.ts +15 -0
  35. package/dist/interfaces/account-config.js +1 -0
  36. package/dist/interfaces/account-export-request.d.ts +10 -0
  37. package/dist/interfaces/account-export-request.js +1 -0
  38. package/dist/interfaces/account-setting.d.ts +7 -0
  39. package/dist/interfaces/account-setting.js +1 -0
  40. package/dist/interfaces/account.d.ts +29 -0
  41. package/dist/interfaces/account.js +1 -0
  42. package/dist/interfaces/address.d.ts +69 -0
  43. package/dist/interfaces/address.js +1 -0
  44. package/dist/interfaces/calendar-collection.d.ts +38 -0
  45. package/dist/interfaces/calendar-collection.js +1 -0
  46. package/dist/interfaces/calendar-event-index.d.ts +19 -0
  47. package/dist/interfaces/calendar-event-index.js +1 -0
  48. package/dist/interfaces/calendar-feed-token.d.ts +31 -0
  49. package/dist/interfaces/calendar-feed-token.js +1 -0
  50. package/dist/interfaces/calendar-object.d.ts +36 -0
  51. package/dist/interfaces/calendar-object.js +1 -0
  52. package/dist/interfaces/calendar-store.d.ts +93 -0
  53. package/dist/interfaces/calendar-store.js +1 -0
  54. package/dist/interfaces/calendar-suggestion.d.ts +62 -0
  55. package/dist/interfaces/calendar-suggestion.js +1 -0
  56. package/dist/interfaces/config-import.d.ts +8 -0
  57. package/dist/interfaces/config-import.js +1 -0
  58. package/dist/interfaces/envelope.d.ts +24 -0
  59. package/dist/interfaces/envelope.js +1 -0
  60. package/dist/interfaces/filter.d.ts +28 -0
  61. package/dist/interfaces/filter.js +1 -0
  62. package/dist/interfaces/label.d.ts +17 -0
  63. package/dist/interfaces/label.js +1 -0
  64. package/dist/interfaces/mailbox-lock.d.ts +12 -0
  65. package/dist/interfaces/mailbox-lock.js +1 -0
  66. package/dist/interfaces/mailbox-special-use.d.ts +54 -0
  67. package/dist/interfaces/mailbox-special-use.js +1 -0
  68. package/dist/interfaces/mailbox.d.ts +79 -0
  69. package/dist/interfaces/mailbox.js +1 -0
  70. package/dist/interfaces/message-flag-push.d.ts +9 -0
  71. package/dist/interfaces/message-flag-push.js +1 -0
  72. package/dist/interfaces/message-flag.d.ts +14 -0
  73. package/dist/interfaces/message-flag.js +1 -0
  74. package/dist/interfaces/message-label.d.ts +21 -0
  75. package/dist/interfaces/message-label.js +1 -0
  76. package/dist/interfaces/message-placement-move.d.ts +8 -0
  77. package/dist/interfaces/message-placement-move.js +1 -0
  78. package/dist/interfaces/message.d.ts +60 -0
  79. package/dist/interfaces/message.js +1 -0
  80. package/dist/interfaces/organize-job-request.d.ts +10 -0
  81. package/dist/interfaces/organize-job-request.js +1 -0
  82. package/dist/interfaces/outbox-attachment.d.ts +101 -0
  83. package/dist/interfaces/outbox-attachment.js +7 -0
  84. package/dist/interfaces/outbox-message.d.ts +70 -0
  85. package/dist/interfaces/outbox-message.js +20 -0
  86. package/dist/interfaces/quarantine.d.ts +20 -0
  87. package/dist/interfaces/quarantine.js +1 -0
  88. package/dist/interfaces/sender-signer-standing.d.ts +26 -0
  89. package/dist/interfaces/sender-signer-standing.js +1 -0
  90. package/dist/interfaces/sync-warning.d.ts +11 -0
  91. package/dist/interfaces/sync-warning.js +1 -0
  92. package/dist/interfaces/thread-message.d.ts +202 -0
  93. package/dist/interfaces/thread-message.js +1 -0
  94. package/dist/mailbox-name.d.ts +35 -0
  95. package/dist/mailbox-name.js +68 -0
  96. package/dist/message-parts.d.ts +22 -0
  97. package/dist/message-parts.js +18 -0
  98. package/dist/message-settlement.d.ts +35 -0
  99. package/{src/message-settlement.ts → dist/message-settlement.js} +5 -19
  100. package/dist/mutation-events.d.ts +19 -0
  101. package/{src/mutation-events.ts → dist/mutation-events.js} +2 -5
  102. package/dist/sender-mute.d.ts +26 -0
  103. package/{src/sender-mute.ts → dist/sender-mute.js} +12 -34
  104. package/dist/sync-warning-state.d.ts +3 -0
  105. package/dist/sync-warning-state.js +5 -0
  106. package/dist/types.d.ts +491 -0
  107. package/dist/types.js +1 -0
  108. package/dist/update-manifest.d.ts +24 -0
  109. package/dist/update-manifest.js +20 -0
  110. package/dist/virtual-copy.d.ts +8 -0
  111. package/{src/virtual-copy.ts → dist/virtual-copy.js} +10 -15
  112. package/dist/wellknown.d.ts +47 -0
  113. package/{src/wellknown.ts → dist/wellknown.js} +26 -49
  114. package/package.json +44 -79
  115. package/src/account-settings.ts +0 -194
  116. package/src/conformance/account.ts +0 -104
  117. package/src/conformance/calendar-collection.ts +0 -342
  118. package/src/conformance/calendar-event-index.ts +0 -203
  119. package/src/conformance/calendar-feed-token.ts +0 -148
  120. package/src/conformance/calendar-object.ts +0 -269
  121. package/src/conformance/calendar-suggestion.ts +0 -490
  122. package/src/conformance/label.ts +0 -136
  123. package/src/conformance/sender-signer-standing.ts +0 -117
  124. package/src/display-name.test.ts +0 -101
  125. package/src/display-name.ts +0 -40
  126. package/src/errors.ts +0 -244
  127. package/src/folder-role.test.ts +0 -422
  128. package/src/folder-role.ts +0 -447
  129. package/src/id.test.ts +0 -352
  130. package/src/index.ts +0 -170
  131. package/src/interfaces/account-config.ts +0 -25
  132. package/src/interfaces/account-export-request.ts +0 -21
  133. package/src/interfaces/account-setting.ts +0 -14
  134. package/src/interfaces/account.ts +0 -43
  135. package/src/interfaces/address.ts +0 -122
  136. package/src/interfaces/calendar-collection.ts +0 -60
  137. package/src/interfaces/calendar-event-index.ts +0 -34
  138. package/src/interfaces/calendar-feed-token.ts +0 -38
  139. package/src/interfaces/calendar-object.ts +0 -58
  140. package/src/interfaces/calendar-suggestion.ts +0 -93
  141. package/src/interfaces/calendar-unit-of-work.ts +0 -47
  142. package/src/interfaces/config-import.ts +0 -16
  143. package/src/interfaces/envelope.ts +0 -32
  144. package/src/interfaces/filter-anchor-transaction.ts +0 -23
  145. package/src/interfaces/filter-anchor.ts +0 -11
  146. package/src/interfaces/filter.ts +0 -41
  147. package/src/interfaces/label.ts +0 -30
  148. package/src/interfaces/mailbox-lock.ts +0 -32
  149. package/src/interfaces/mailbox-special-use.ts +0 -60
  150. package/src/interfaces/mailbox.ts +0 -115
  151. package/src/interfaces/message-flag-push.ts +0 -17
  152. package/src/interfaces/message-flag.ts +0 -15
  153. package/src/interfaces/message-label.ts +0 -25
  154. package/src/interfaces/message-placement-move.ts +0 -15
  155. package/src/interfaces/message.ts +0 -65
  156. package/src/interfaces/organize-job-request.ts +0 -19
  157. package/src/interfaces/outbox-attachment.ts +0 -132
  158. package/src/interfaces/outbox-message.ts +0 -106
  159. package/src/interfaces/quarantine.ts +0 -22
  160. package/src/interfaces/sender-signer-standing.ts +0 -37
  161. package/src/interfaces/thread-message.ts +0 -264
  162. package/src/interfaces/unit-of-work.ts +0 -28
  163. package/src/mailbox-name.test.ts +0 -106
  164. package/src/mailbox-name.ts +0 -90
  165. package/src/mailbox-state-doors.test.ts +0 -104
  166. package/src/message-placement-doors.test.ts +0 -100
  167. package/src/message-settlement.test.ts +0 -132
  168. package/src/sender-mute.test.ts +0 -76
  169. package/src/types.ts +0 -820
  170. package/src/update-manifest.test.ts +0 -130
  171. package/src/update-manifest.ts +0 -22
  172. package/src/virtual-copy.test.ts +0 -34
  173. package/tsconfig.json +0 -8
  174. /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
- }
@@ -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
- });
@@ -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
- }