@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
@@ -0,0 +1,54 @@
1
+ import type { JunkRoleMailboxes, RoleResolution } from "../folder-role.js";
2
+ import type { MailboxSpecialUseItem, MailboxSpecialUseValue } from "../types.js";
3
+ /**
4
+ * Special-use entries, plus the per-role lookup every special-folder operation
5
+ * goes through. The `find<Role>Mailbox` reads answer "which folder holds this
6
+ * role for this account", in the one precedence order `resolveMailboxForRole`
7
+ * defines: the user's appointment first (RFC 032 exclusive-folder-appointment,
8
+ * #976), then the server's SPECIAL-USE flag, then a conventional name.
9
+ */
10
+ export interface IMailboxSpecialUseRepository {
11
+ create(mailboxId: string, specialUse: MailboxSpecialUseValue): Promise<MailboxSpecialUseItem>;
12
+ createMany(mailboxId: string, specialUses: MailboxSpecialUseValue[]): Promise<MailboxSpecialUseItem[]>;
13
+ listByMailboxId(mailboxId: string): Promise<MailboxSpecialUseItem[]>;
14
+ deleteByMailboxId(mailboxId: string): Promise<number>;
15
+ findInboxMailbox(accountId: string): Promise<{
16
+ mailboxId: string;
17
+ fullPath: string;
18
+ } | null>;
19
+ findSentMailbox(accountId: string): Promise<{
20
+ mailboxId: string;
21
+ fullPath: string;
22
+ } | null>;
23
+ findTrashMailbox(accountId: string): Promise<{
24
+ mailboxId: string;
25
+ fullPath: string;
26
+ } | null>;
27
+ /**
28
+ * Trash with the evidence attached, for the two verbs that weigh it: a
29
+ * delete files mail somewhere retrievable, an Empty Trash destroys it, and
30
+ * they refuse on different grounds. `null` cannot tell them apart — an
31
+ * appointment naming a folder that is gone is a different answer from no
32
+ * folder at all, and only this read distinguishes them.
33
+ */
34
+ resolveTrashRole(accountId: string): Promise<RoleResolution<{
35
+ mailboxId: string;
36
+ fullPath: string;
37
+ }>>;
38
+ findArchiveMailbox(accountId: string): Promise<{
39
+ mailboxId: string;
40
+ fullPath: string;
41
+ } | null>;
42
+ findJunkMailbox(accountId: string): Promise<{
43
+ mailboxId: string;
44
+ fullPath: string;
45
+ } | null>;
46
+ /**
47
+ * Junk and Trash for every account under one config, as mailbox ids. The
48
+ * address book is keyed by config and an address carries sightings from all
49
+ * of them, so a predicate over an address must weigh every account's Junk —
50
+ * resolving only the account in hand withholds a sender on one and restores
51
+ * it on the next.
52
+ */
53
+ resolveJunkRolesForConfig(accountConfigId: string): Promise<JunkRoleMailboxes>;
54
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,79 @@
1
+ import type { CreateMailboxInput, CreateMailboxResult, MailboxItem, MailboxSubtreeTransitionIntent, MailboxTransitionIntent, ResultList, UpdateMailboxInput } from "../types.js";
2
+ export interface IMailboxRepository {
3
+ create(input: CreateMailboxInput): Promise<CreateMailboxResult>;
4
+ get(accountId: string, mailboxId: string): Promise<MailboxItem>;
5
+ get(accountId: string, mailboxIds: string[]): Promise<MailboxItem[]>;
6
+ /**
7
+ * Resolve the owning accountId for a mailbox by its (globally unique) id,
8
+ * without a tenant scope. Returns only the owner id (no row content); null
9
+ * when the mailbox does not exist.
10
+ *
11
+ * The result is NOT a trusted tenant — it is derived from the row, so it must
12
+ * never be fed back as the scope of a request-facing read. It has two uses:
13
+ * pure system routing that has no tenant of its own (stream bridge), and ownership bootstrap where a request-facing caller
14
+ * compares the resolved owner's accountConfigId against its OWN authenticated
15
+ * accountConfigId to decide the caller may proceed.
16
+ */
17
+ resolveAccountId(mailboxId: string): Promise<string | null>;
18
+ update(accountId: string, mailboxId: string, input: UpdateMailboxInput, remove?: string[]): Promise<MailboxItem>;
19
+ /**
20
+ * Move one folder's mutation state from the state the caller read to the
21
+ * state it decided on, as one conditional write
22
+ * (docs/architecture/folder-rename-and-delete.md D3). The only writer of
23
+ * `syncStatus` and `pendingPath` — so a folder state written without a
24
+ * predicate is a type error rather than a convention.
25
+ *
26
+ * The predicate names one of the six states, not one of the four enum
27
+ * values: `wherePendingPath` as a string requires equality, `null` requires
28
+ * the column to be NULL, and omitting it predicates on `syncStatus` alone.
29
+ * A create settle that leaves it out matches a row a rename has claimed and
30
+ * strands it `synced` with a target on it, which is the seventh combination
31
+ * the invariant forbids.
32
+ *
33
+ * Resolves with the written row when the predicate matched, and with `null`
34
+ * when it did not — the row is absent, or somebody else won. The loser
35
+ * re-reads and re-decides; it never blind-retries and never writes anyway.
36
+ */
37
+ transition(accountId: string, mailboxId: string, intent: MailboxTransitionIntent): Promise<MailboxItem | null>;
38
+ /**
39
+ * Record one intent across a folder and every descendant, all-or-nothing, in
40
+ * one UPDATE (D6), guarded on no row of the subtree being outside a
41
+ * from-state, so a concurrent single-row transition makes it write nothing
42
+ * rather than being missed by a prior read.
43
+ *
44
+ * Resolves with every written row, or with `null` when any row in the
45
+ * subtree was not in an accepted from-state — in which case nothing was
46
+ * written. The named folder comes first and its descendants follow, but a
47
+ * caller wanting one row should find it by `mailboxId` rather than lean on
48
+ * that. This serves the intent only: a settle is per-row (D15).
49
+ */
50
+ transitionSubtree(accountId: string, mailboxId: string, intent: MailboxSubtreeTransitionIntent): Promise<MailboxItem[] | null>;
51
+ delete(accountId: string, mailboxId: string): Promise<void>;
52
+ deleteMany(accountId: string, mailboxIds: string[]): Promise<void>;
53
+ /**
54
+ * Remove a folder along with the mail it holds (D8): the messages through
55
+ * `deleteMessageSubtree`, so the nine per-message child tables go; then the
56
+ * mailbox's own child rows; then, last, the mailbox row itself.
57
+ *
58
+ * "Its own child rows" includes a `message_placement_move` marker naming this
59
+ * folder as a move's *destination*, not only as its source. The folder it
60
+ * points at is gone, so the move it records can never land, and leaving the
61
+ * marker behind holds a message elsewhere in the account unsettled forever.
62
+ *
63
+ * Ordered, batched and resumable rather than one transaction — the caller
64
+ * keeps the row `deleting` until this returns, so a redelivery re-enters and
65
+ * continues. `filter` rows bound to this mailbox are never touched: D16
66
+ * refuses the delete while any binding stands, so there is nothing to unbind
67
+ * and deleting a user's filters is not a folder delete's decision.
68
+ */
69
+ deleteMailboxWithMail(accountId: string, mailboxId: string): Promise<void>;
70
+ listByAccount(accountId: string, options?: {
71
+ limit?: number;
72
+ continuationToken?: string;
73
+ }): Promise<ResultList<MailboxItem>>;
74
+ listAllByAccount(accountId: string): Promise<MailboxItem[]>;
75
+ findByPath(accountId: string, fullPath: string): Promise<MailboxItem | null>;
76
+ getOrCreateByPath(accountId: string, fullPath: string, defaults: Omit<CreateMailboxInput, "accountId" | "fullPath">): Promise<MailboxItem>;
77
+ findByPathPrefix(accountId: string, pathPrefix: string, delimiter?: string): Promise<MailboxItem[]>;
78
+ findBySyncStatus(accountId: string, syncStatus: MailboxItem["syncStatus"]): Promise<MailboxItem[]>;
79
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { MessageFlagPushItem, PutMessageFlagPushInput } from "../types.js";
2
+ export interface IMessageFlagPushRepository {
3
+ put(input: PutMessageFlagPushInput): Promise<MessageFlagPushItem>;
4
+ find(messageId: string, flagName: string): Promise<MessageFlagPushItem | null>;
5
+ updateState(messageId: string, flagName: string, state: MessageFlagPushItem["state"]): Promise<MessageFlagPushItem>;
6
+ delete(messageId: string, flagName: string): Promise<void>;
7
+ listByAccountId(accountId: string): Promise<MessageFlagPushItem[]>;
8
+ listByMailboxId(mailboxId: string): Promise<MessageFlagPushItem[]>;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { CreateMessageFlagInput, MessageFlagItem } from "../types.js";
2
+ export interface IMessageFlagRepository {
3
+ create(input: CreateMessageFlagInput): Promise<MessageFlagItem>;
4
+ get(messageFlagId: string): Promise<MessageFlagItem>;
5
+ get(messageFlagIds: string[]): Promise<MessageFlagItem[]>;
6
+ delete(messageFlagId: string): Promise<void>;
7
+ deleteMany(messageFlagIds: string[]): Promise<void>;
8
+ getFlags(messageId: string): Promise<MessageFlagItem[]>;
9
+ hasFlag(messageId: string, flagName: string): Promise<boolean>;
10
+ addFlag(messageId: string, flagName: string): Promise<MessageFlagItem>;
11
+ removeFlag(messageId: string, flagName: string): Promise<void>;
12
+ addFlags(messageId: string, flagNames: string[]): Promise<void>;
13
+ removeFlags(messageId: string, flagNames: string[]): Promise<void>;
14
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { CreateMessageLabelInput, MessageLabelItem } from "../types.js";
2
+ export interface IMessageLabelRepository {
3
+ apply(input: CreateMessageLabelInput): Promise<MessageLabelItem>;
4
+ remove(messageId: string, labelId: string): Promise<void>;
5
+ listByMessageId(messageId: string): Promise<MessageLabelItem[]>;
6
+ /**
7
+ * Batch-fetch every MessageLabel row across a page of messages in one query
8
+ * — the read-time enrichment a thread listing needs (issue #26), mirroring
9
+ * the one-BatchGet-per-page contract `enrichThreadRows` already holds for
10
+ * Message/Address.
11
+ */
12
+ listByMessageIds(messageIds: string[]): Promise<MessageLabelItem[]>;
13
+ listByLabelId(accountConfigId: string, labelId: string): Promise<MessageLabelItem[]>;
14
+ /**
15
+ * Delete every MessageLabel row for a label in one statement — the message
16
+ * side of a label delete's cascade (issue #26). A label delete cascades in
17
+ * both directions: this clears the applied-to-messages side, and the
18
+ * caller separately deletes any filter whose actionLabelId is this label.
19
+ */
20
+ removeAllByLabelId(accountConfigId: string, labelId: string): Promise<void>;
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import type { MessagePlacementMoveItem, PutMessagePlacementMoveInput } from "../types.js";
2
+ export interface IMessagePlacementMoveRepository {
3
+ put(input: PutMessagePlacementMoveInput): Promise<MessagePlacementMoveItem>;
4
+ find(messageId: string): Promise<MessagePlacementMoveItem | null>;
5
+ updateState(messageId: string, state: MessagePlacementMoveItem["state"]): Promise<MessagePlacementMoveItem>;
6
+ delete(messageId: string): Promise<void>;
7
+ listByAccountId(accountId: string): Promise<MessagePlacementMoveItem[]>;
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ import type { CreateMessageInput, MessageDescription, MessageIdSource, MessageItem, PlacementPredicate, PlacementTransitionInput, ResultList, UpdateMessageInput } from "../types.js";
2
+ export interface IMessageRepository {
3
+ create(input: CreateMessageInput): Promise<MessageItem>;
4
+ upsert(input: CreateMessageInput): Promise<MessageItem>;
5
+ upsertWithStatus(input: CreateMessageInput): Promise<{
6
+ item: MessageItem;
7
+ created: boolean;
8
+ }>;
9
+ get(messageId: string): Promise<MessageItem>;
10
+ get(messageIds: string[]): Promise<MessageItem[]>;
11
+ update(messageId: string, input: UpdateMessageInput): Promise<MessageItem>;
12
+ clearBodyStorageKey(messageId: string): Promise<MessageItem>;
13
+ clearSpamReport(messageId: string): Promise<MessageItem>;
14
+ delete(messageId: string): Promise<void>;
15
+ deleteMany(messageIds: string[]): Promise<void>;
16
+ /**
17
+ * Delete the row, its per-message subtree and its thread listings, only
18
+ * while its placement still matches what the caller read (imap-mutations
19
+ * R3). Resolves `false` when the predicate did not match and nothing was
20
+ * deleted.
21
+ */
22
+ deleteIfPlacement(messageId: string, expected: PlacementPredicate): Promise<boolean>;
23
+ listByMailbox(mailboxId: string, options?: {
24
+ limit?: number;
25
+ continuationToken?: string;
26
+ }): Promise<ResultList<MessageItem>>;
27
+ listAllByMailbox(mailboxId: string): Promise<MessageItem[]>;
28
+ /** Rows `moving` or `deleting` that nothing has written since `untouchedSince`, uid-0 copies included. */
29
+ listStrandedPlacements(mailboxIds: readonly string[], untouchedSince: number): Promise<MessageItem[]>;
30
+ describe(messageId: string): Promise<MessageDescription>;
31
+ updateUid(messageId: string, newUid: number, newMailboxId: string): Promise<MessageItem>;
32
+ /**
33
+ * Move the row's placement from the state the caller read to the state it
34
+ * decided on, as one conditional write (imap-mutations R3). The only writer
35
+ * of `mailboxId`, `status`, `syncStatus`, `abandonedMutation`,
36
+ * `originalMailboxId` and `originalUid`, apart from the settle in
37
+ * {@link updateUid} — so a placement written without a predicate is a type
38
+ * error rather than a convention.
39
+ *
40
+ * Resolves with the written row when the predicate matched, and with
41
+ * `undefined` when it did not — someone else won, or the row is gone. The
42
+ * loser re-reads and re-decides; it never blind-retries and never writes
43
+ * anyway.
44
+ */
45
+ transitionPlacement(messageId: string, expected: PlacementPredicate, next: PlacementTransitionInput): Promise<MessageItem | undefined>;
46
+ /**
47
+ * Re-point a row at the placement the mail server named for it, settled,
48
+ * only while it still sits where the caller read it (imap-mutations R1/R3):
49
+ * a user move landing in between wins and this writes nothing. The row's
50
+ * thread listings follow in the same batch, so the listing never shows the
51
+ * message in a folder its row has left. Resolves `false` when the
52
+ * predicate did not match.
53
+ */
54
+ repointIfPlacement(messageId: string, expected: PlacementPredicate, to: {
55
+ mailboxId: string;
56
+ uid: number;
57
+ isDeleted: boolean;
58
+ }): Promise<boolean>;
59
+ }
60
+ export type { MessageIdSource };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import type { CreateOrganizeJobRequestInput, OrganizeJobRequestItem, ResultList, UpdateOrganizeJobRequestInput } from "../types.js";
2
+ export interface IOrganizeJobRequestRepository {
3
+ create(input: CreateOrganizeJobRequestInput): Promise<OrganizeJobRequestItem>;
4
+ get(organizeJobId: string): Promise<OrganizeJobRequestItem>;
5
+ update(organizeJobId: string, input: UpdateOrganizeJobRequestInput): Promise<OrganizeJobRequestItem>;
6
+ listByAccountConfig(accountConfigId: string, options?: {
7
+ limit?: number;
8
+ continuationToken?: string;
9
+ }): Promise<ResultList<OrganizeJobRequestItem>>;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,101 @@
1
+ import type { OutboxAttachmentItem } from "../types.js";
2
+ /**
3
+ * Whether an attachment is spoken for: uploaded and confirmed, or reserved and
4
+ * not yet lapsed. The cap counts these and nothing else. The SQLite repository
5
+ * states the same rule as the `WHERE` of the delete that refuses an over-cap
6
+ * reservation, and the two must agree exactly.
7
+ */
8
+ export declare const holdsRoom: (item: OutboxAttachmentItem, nowSeconds: number) => boolean;
9
+ export interface CreateOutboxAttachmentInput {
10
+ outboxAttachmentId: string;
11
+ outboxMessageId: string;
12
+ accountId: string;
13
+ accountConfigId: string;
14
+ filename: string;
15
+ contentType: string;
16
+ sizeBytes: number;
17
+ storageKey: string;
18
+ reservationExpiresAt: number;
19
+ }
20
+ /**
21
+ * The numbers the reservation must fit inside. They come from the caller — the
22
+ * cap is a business rule and belongs with the rest of them — but they are
23
+ * applied here, where the count and the insert can happen together.
24
+ */
25
+ export interface OutboxAttachmentCap {
26
+ maxTotalBytes: number;
27
+ maxCount: number;
28
+ /** Unix seconds. A Pending row past its expiry stops holding room. */
29
+ nowSeconds: number;
30
+ }
31
+ export type ReserveOutboxAttachmentResult = {
32
+ outcome: "Reserved";
33
+ item: OutboxAttachmentItem;
34
+ } | {
35
+ outcome: "OverByteCap";
36
+ usedBytes: number;
37
+ } | {
38
+ outcome: "OverCountCap";
39
+ usedBytes: number;
40
+ };
41
+ export interface IOutboxAttachmentRepository {
42
+ /**
43
+ * Claim room on a draft, or report why there is none.
44
+ *
45
+ * **The invariant an implementation owes its callers:** of two reservations
46
+ * for the same draft running concurrently, at most one may observe a total
47
+ * that does not include the other. Either one of them sees the other's row,
48
+ * or one of them fails. Both being told there was room is a breach, and the
49
+ * consequence is a message that cannot be sent — phase 4 builds a MIME body
50
+ * from these rows and the receiving server rejects it.
51
+ *
52
+ * A count-then-insert only satisfies that under serializable isolation or an
53
+ * external lock. **The drizzle/SQLite implementation gets it from one
54
+ * batch:** the insert, a read of the draft's rows and a delete of the new row
55
+ * when the draft's live rows then break the cap commit as a unit, and
56
+ * batches are serialized, so the later of two reservations sees the earlier
57
+ * one's row. A row holds room while {@link holdsRoom} says so: stored, or a
58
+ * reservation not yet expired.
59
+ *
60
+ * **An adapter without serializable isolation must not do a
61
+ * read-then-write.** On DynamoDB in particular, a Query followed by a
62
+ * PutItem breaches this silently and is the failure mode that matters: it is
63
+ * the deployment that actually scales horizontally. Use a conditional write
64
+ * on a per-draft aggregate instead — an `UpdateItem` that adds the size and
65
+ * carries a `ConditionExpression` bounding the running total, decided on
66
+ * whether the condition held — and derive `usedBytes` for the rejection from
67
+ * the same item.
68
+ *
69
+ * Either way the caller supplies the limits: the cap is a business rule and
70
+ * lives with the rest of them. What is delegated here is only the atomicity.
71
+ */
72
+ reserve(input: CreateOutboxAttachmentInput, cap: OutboxAttachmentCap): Promise<ReserveOutboxAttachmentResult>;
73
+ /** One attachment, scoped to its tenant. Throws NotFound when it is gone. */
74
+ get(accountConfigId: string, outboxAttachmentId: string): Promise<OutboxAttachmentItem>;
75
+ /**
76
+ * Everything a draft holds, lapsed reservations included. Callers that care
77
+ * about what is live filter on state and `reservationExpiresAt`.
78
+ */
79
+ listByOutboxMessage(accountConfigId: string, outboxMessageId: string): Promise<OutboxAttachmentItem[]>;
80
+ /**
81
+ * Move a reservation to Stored at the size storage actually holds. Returns
82
+ * null when the row is gone or no longer Pending, so a caller can tell a
83
+ * lapsed reservation from a confirmed one without a second read.
84
+ */
85
+ markStored(accountConfigId: string, outboxAttachmentId: string, sizeBytes: number): Promise<OutboxAttachmentItem | null>;
86
+ /** Drop specific attachments from a draft. */
87
+ deleteMany(accountConfigId: string, outboxAttachmentIds: string[]): Promise<void>;
88
+ /**
89
+ * Drop the draft's reservations that lapsed without ever being confirmed, and
90
+ * report which ids went.
91
+ *
92
+ * A `Pending` row past its expiry already stops holding room, but until it is
93
+ * gone it still names an object — which is enough for the sweep to treat that
94
+ * object as accounted for and leave it. So the row has to go, and the sweep
95
+ * collects the bytes on the same pass. `complete` refuses a lapsed row, so
96
+ * nothing is racing this.
97
+ */
98
+ deleteLapsedReservations(accountConfigId: string, outboxMessageId: string, nowSeconds: number): Promise<string[]>;
99
+ /** Drop everything a draft holds, as part of retiring the draft. */
100
+ deleteByOutboxMessage(accountConfigId: string, outboxMessageId: string): Promise<void>;
101
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Whether an attachment is spoken for: uploaded and confirmed, or reserved and
3
+ * not yet lapsed. The cap counts these and nothing else. The SQLite repository
4
+ * states the same rule as the `WHERE` of the delete that refuses an over-cap
5
+ * reservation, and the two must agree exactly.
6
+ */
7
+ export const holdsRoom = (item, nowSeconds) => item.state === "Stored" || item.reservationExpiresAt >= nowSeconds;
@@ -0,0 +1,70 @@
1
+ import type { CreateOutboxMessageInput, OutboxMessageItem, ResultList, UpdateOutboxMessageInput } from "../types.js";
2
+ /** `appendedUid` when no IMAP APPEND has been confirmed for the row. */
3
+ export declare const APPENDED_UID_NONE = 0;
4
+ /**
5
+ * `appendedUid` when an APPEND was confirmed but the server named no uid for
6
+ * it. UIDPLUS is an extension; a server without it files the copy and reports
7
+ * nothing, and "filed" still has to be distinguishable from "not filed".
8
+ */
9
+ export declare const APPENDED_UID_UNREPORTED = -1;
10
+ /**
11
+ * Whether the copy in Sent exists. One definition, because the handler deciding
12
+ * whether to APPEND and the repair deciding what a stranded row means both have
13
+ * to agree on it exactly.
14
+ *
15
+ * Only the two values that mean "filed" say so, rather than everything that is
16
+ * not `APPENDED_UID_NONE`. The two mistakes are not each other's equal: reading
17
+ * a filed row as unfiled files a second copy the user then deletes, and reading
18
+ * an unfiled row as filed drops the row of a delivered message that is in no
19
+ * folder — the disappearance the whole repair exists to prevent (#824).
20
+ */
21
+ export declare const isSentCopyFiled: (message: Pick<OutboxMessageItem, "appendedUid">) => boolean;
22
+ export interface IOutboxMessageRepository {
23
+ create(input: CreateOutboxMessageInput): Promise<OutboxMessageItem>;
24
+ /**
25
+ * `mode: "read"` (default) throws NotFoundError on a foreign message so a
26
+ * GET doesn't leak existence. `mode: "act"` throws ForbiddenError instead,
27
+ * for action verbs (PATCH/POST/DELETE) where the caller has already named
28
+ * the resource and the API contract says to explicitly deny rather than
29
+ * feign 404 — see ownedAccount / ownedMessages.
30
+ */
31
+ get(accountConfigId: string, outboxMessageId: string, mode?: "read" | "act"): Promise<OutboxMessageItem>;
32
+ get(accountConfigId: string, outboxMessageIds: string[]): Promise<OutboxMessageItem[]>;
33
+ update(accountConfigId: string, outboxMessageId: string, input: UpdateOutboxMessageInput): Promise<OutboxMessageItem>;
34
+ updateStatus(accountConfigId: string, outboxMessageId: string, status: OutboxMessageItem["status"]): Promise<OutboxMessageItem>;
35
+ /**
36
+ * Write `input` only while the row still holds `expected`, and answer `null`
37
+ * when it does not.
38
+ *
39
+ * Every status transition is a read the caller decided on followed by a
40
+ * write, and the SMTP worker or a second request can move the row between
41
+ * the two. Naming the status that decision was made against turns the write
42
+ * into a compare-and-set: an edit no longer pulls a row back out of `queued`
43
+ * while its send event is on the wire, and a settle no longer overwrites a
44
+ * status the worker has already reached. `null` is the caller's to read —
45
+ * a conflict for an action the user asked for, and nothing to do for a
46
+ * settle that has been overtaken.
47
+ */
48
+ updateIfStatus(accountConfigId: string, outboxMessageId: string, expected: OutboxMessageItem["status"], input: UpdateOutboxMessageInput): Promise<OutboxMessageItem | null>;
49
+ markSent(accountConfigId: string, outboxMessageId: string, fields: {
50
+ sentAt: number;
51
+ smtpMessageId?: string;
52
+ }): Promise<OutboxMessageItem>;
53
+ delete(accountConfigId: string, outboxMessageId: string): Promise<void>;
54
+ deleteMany(accountConfigId: string, outboxMessageIds: string[]): Promise<void>;
55
+ listByAccount(accountId: string, options?: {
56
+ limit?: number;
57
+ continuationToken?: string;
58
+ }): Promise<ResultList<OutboxMessageItem>>;
59
+ /**
60
+ * One keyset scan over several accounts, ordered globally by createdAt then
61
+ * outboxMessageId. The continuation token names a position in that single
62
+ * ordering rather than one position per account, so a second page stays
63
+ * coherent however the accounts' rows interleave.
64
+ */
65
+ listByAccounts(accountIds: string[], options?: {
66
+ limit?: number;
67
+ continuationToken?: string;
68
+ }): Promise<ResultList<OutboxMessageItem>>;
69
+ listQueued(accountId: string): Promise<OutboxMessageItem[]>;
70
+ }
@@ -0,0 +1,20 @@
1
+ /** `appendedUid` when no IMAP APPEND has been confirmed for the row. */
2
+ export const APPENDED_UID_NONE = 0;
3
+ /**
4
+ * `appendedUid` when an APPEND was confirmed but the server named no uid for
5
+ * it. UIDPLUS is an extension; a server without it files the copy and reports
6
+ * nothing, and "filed" still has to be distinguishable from "not filed".
7
+ */
8
+ export const APPENDED_UID_UNREPORTED = -1;
9
+ /**
10
+ * Whether the copy in Sent exists. One definition, because the handler deciding
11
+ * whether to APPEND and the repair deciding what a stranded row means both have
12
+ * to agree on it exactly.
13
+ *
14
+ * Only the two values that mean "filed" say so, rather than everything that is
15
+ * not `APPENDED_UID_NONE`. The two mistakes are not each other's equal: reading
16
+ * a filed row as unfiled files a second copy the user then deletes, and reading
17
+ * an unfiled row as filed drops the row of a delivered message that is in no
18
+ * folder — the disappearance the whole repair exists to prevent (#824).
19
+ */
20
+ export const isSentCopyFiled = (message) => message.appendedUid > 0 || message.appendedUid === APPENDED_UID_UNREPORTED;
@@ -0,0 +1,20 @@
1
+ import type { QuarantineItem, QuarantineUpsertInput } from "../types.js";
2
+ export interface IQuarantineRepository {
3
+ /**
4
+ * Every quarantined message for one user, newest first. Unpaginated: the
5
+ * list is small by design, and it is read whole both by the settings
6
+ * surface and by a sync round that keeps it in memory.
7
+ */
8
+ listByAccountConfigId(accountConfigId: string): Promise<QuarantineItem[]>;
9
+ /**
10
+ * Record a message the sync path could not apply.
11
+ *
12
+ * Idempotent on the message's identity: the row is keyed by an id derived
13
+ * from (accountId, mailboxId, uidValidity, uid), so quarantining the same
14
+ * message twice rewrites one row instead of accumulating. The derivation
15
+ * happens here rather than at the call site — see `QuarantineUpsertInput`.
16
+ *
17
+ * A cursor may only move past a message once this has resolved.
18
+ */
19
+ upsert(input: QuarantineUpsertInput): Promise<void>;
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import type { ObserveSenderSignerStandingInput, SenderSignerStandingItem } from "../types.js";
2
+ /**
3
+ * Per-account record of how often one sender has been seen arriving under one
4
+ * DKIM signing identity.
5
+ *
6
+ * The authenticity derivation asks this port two things: whether a key already
7
+ * had standing when a message arrived, and — afterwards — that this message be
8
+ * counted. `get` answers the first and must not create anything; `observe`
9
+ * answers the second and is the only writer.
10
+ */
11
+ export interface ISenderSignerStandingRepository {
12
+ /**
13
+ * Record one observation of a key, creating the row at `messageCount = 1`
14
+ * or incrementing an existing one.
15
+ *
16
+ * `firstSeenAt` is set once, on creation, and never moves — an
17
+ * implementation that rewrites it on the increment path erases the age the
18
+ * derivation reads standing from. `lastSeenAt` advances on every call.
19
+ */
20
+ observe(input: ObserveSenderSignerStandingInput): Promise<SenderSignerStandingItem>;
21
+ /**
22
+ * The standing of one key. Throws the adapter's not-found error when the
23
+ * key has never been observed, and creates nothing.
24
+ */
25
+ get(accountConfigId: string, senderKey: string, signerDomain: string): Promise<SenderSignerStandingItem>;
26
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { SyncWarningItem, SyncWarningKey } from "../types.js";
2
+ export interface SurfacedSyncWarnings {
3
+ items: SyncWarningItem[];
4
+ total: number;
5
+ }
6
+ export interface ISyncWarningRepository {
7
+ listByMailboxId(mailboxId: string): Promise<SyncWarningItem[]>;
8
+ listSurfacedByAccountConfigId(accountConfigId: string, minAttempts: number, limit: number): Promise<SurfacedSyncWarnings>;
9
+ recordFailures(keys: SyncWarningKey[]): Promise<void>;
10
+ deleteMany(syncWarningIds: string[]): Promise<void>;
11
+ }
@@ -0,0 +1 @@
1
+ export {};