@remit/backend 0.0.143 → 0.0.145

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 (279) hide show
  1. package/dist/dev-server/content-handler.d.ts +38 -0
  2. package/dist/dev-server/content-handler.js +46 -0
  3. package/dist/src/api.d.ts +5 -0
  4. package/dist/src/api.js +73 -0
  5. package/dist/src/auth.d.ts +24 -0
  6. package/dist/src/auth.js +81 -0
  7. package/dist/src/config/api-config.d.ts +20 -0
  8. package/dist/src/config/api-config.js +1 -0
  9. package/dist/src/config/from-env.d.ts +2 -0
  10. package/dist/src/config/from-env.js +65 -0
  11. package/dist/src/config/msoauth-secrets-manager.d.ts +2 -0
  12. package/dist/src/config/msoauth-secrets-manager.js +28 -0
  13. package/dist/src/config/msoauth.d.ts +19 -0
  14. package/dist/src/config/msoauth.js +6 -0
  15. package/{src/derive/autoMoved.ts → dist/src/derive/autoMoved.d.ts} +1 -29
  16. package/dist/src/derive/autoMoved.js +49 -0
  17. package/dist/src/derive/contentSignature.d.ts +40 -0
  18. package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +23 -77
  19. package/dist/src/derive/contentUrl.d.ts +33 -0
  20. package/dist/src/derive/contentUrl.js +27 -0
  21. package/{src/derive/deriveMuted.ts → dist/src/derive/deriveMuted.d.ts} +1 -3
  22. package/dist/src/derive/deriveMuted.js +7 -0
  23. package/dist/src/derive/enrichThreadRows.d.ts +118 -0
  24. package/dist/src/derive/enrichThreadRows.js +151 -0
  25. package/dist/src/derive/filterThreadCriteria.d.ts +23 -0
  26. package/dist/src/derive/filterThreadCriteria.js +24 -0
  27. package/{src/derive/pendingMoveCounts.ts → dist/src/derive/pendingMoveCounts.d.ts} +5 -53
  28. package/dist/src/derive/pendingMoveCounts.js +53 -0
  29. package/{src/derive/senderTrust.ts → dist/src/derive/senderTrust.d.ts} +1 -9
  30. package/dist/src/derive/senderTrust.js +19 -0
  31. package/dist/src/error.d.ts +21 -0
  32. package/dist/src/error.js +85 -0
  33. package/dist/src/handlers/account-guards.d.ts +36 -0
  34. package/dist/src/handlers/account-guards.js +107 -0
  35. package/dist/src/handlers/account-oauth.d.ts +15 -0
  36. package/dist/src/handlers/account-oauth.js +334 -0
  37. package/dist/src/handlers/account-overrides.d.ts +53 -0
  38. package/dist/src/handlers/account-overrides.js +233 -0
  39. package/dist/src/handlers/account-signature.d.ts +28 -0
  40. package/dist/src/handlers/account-signature.js +85 -0
  41. package/dist/src/handlers/account.d.ts +7 -0
  42. package/dist/src/handlers/account.js +337 -0
  43. package/dist/src/handlers/address.d.ts +35 -0
  44. package/dist/src/handlers/address.js +133 -0
  45. package/dist/src/handlers/calendar-event.d.ts +70 -0
  46. package/dist/src/handlers/calendar-event.js +417 -0
  47. package/dist/src/handlers/calendar-feed.d.ts +47 -0
  48. package/dist/src/handlers/calendar-feed.js +184 -0
  49. package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
  50. package/dist/src/handlers/calendar-suggestion.js +204 -0
  51. package/dist/src/handlers/calendar.d.ts +116 -0
  52. package/dist/src/handlers/calendar.js +323 -0
  53. package/dist/src/handlers/config.d.ts +43 -0
  54. package/dist/src/handlers/config.js +164 -0
  55. package/dist/src/handlers/ensure-account-config.d.ts +9 -0
  56. package/dist/src/handlers/ensure-account-config.js +27 -0
  57. package/dist/src/handlers/filter.d.ts +3 -0
  58. package/dist/src/handlers/filter.js +88 -0
  59. package/dist/src/handlers/folder-role-appointments.d.ts +68 -0
  60. package/dist/src/handlers/folder-role-appointments.js +168 -0
  61. package/dist/src/handlers/folder-role-labels.d.ts +26 -0
  62. package/dist/src/handlers/folder-role-labels.js +40 -0
  63. package/dist/src/handlers/folder-role.d.ts +9 -0
  64. package/dist/src/handlers/folder-role.js +37 -0
  65. package/dist/src/handlers/index.d.ts +88 -0
  66. package/dist/src/handlers/index.js +62 -0
  67. package/dist/src/handlers/label.d.ts +43 -0
  68. package/dist/src/handlers/label.js +107 -0
  69. package/dist/src/handlers/mailbox-bindings.d.ts +41 -0
  70. package/dist/src/handlers/mailbox-bindings.js +61 -0
  71. package/dist/src/handlers/mailbox.d.ts +23 -0
  72. package/dist/src/handlers/mailbox.js +211 -0
  73. package/dist/src/handlers/me.d.ts +2 -0
  74. package/dist/src/handlers/me.js +117 -0
  75. package/dist/src/handlers/message.d.ts +82 -0
  76. package/dist/src/handlers/message.js +595 -0
  77. package/dist/src/handlers/organize.d.ts +10 -0
  78. package/dist/src/handlers/organize.js +103 -0
  79. package/dist/src/handlers/outbox-attachment.d.ts +25 -0
  80. package/dist/src/handlers/outbox-attachment.js +77 -0
  81. package/dist/src/handlers/outbox.d.ts +4 -0
  82. package/dist/src/handlers/outbox.js +136 -0
  83. package/dist/src/handlers/sync-progress.d.ts +46 -0
  84. package/dist/src/handlers/sync-progress.js +63 -0
  85. package/dist/src/handlers/sync.d.ts +28 -0
  86. package/dist/src/handlers/sync.js +73 -0
  87. package/dist/src/handlers/system-update.d.ts +2 -0
  88. package/dist/src/handlers/system-update.js +159 -0
  89. package/dist/src/handlers/thread.d.ts +148 -0
  90. package/dist/src/handlers/thread.js +228 -0
  91. package/dist/src/handlers/unified-threads.d.ts +247 -0
  92. package/dist/src/handlers/unified-threads.js +406 -0
  93. package/{src/handlers/vip-suggestions.ts → dist/src/handlers/vip-suggestions.d.ts} +1 -13
  94. package/dist/src/handlers/vip-suggestions.js +16 -0
  95. package/dist/src/index.d.ts +5 -0
  96. package/dist/src/index.js +53 -0
  97. package/{src/json.ts → dist/src/json.d.ts} +1 -4
  98. package/dist/src/json.js +10 -0
  99. package/dist/src/jwt-auth.d.ts +15 -0
  100. package/dist/src/jwt-auth.js +45 -0
  101. package/dist/src/lambda.d.ts +13 -0
  102. package/dist/src/lambda.js +33 -0
  103. package/dist/src/request.d.ts +9 -0
  104. package/dist/src/request.js +17 -0
  105. package/dist/src/response.d.ts +44 -0
  106. package/dist/src/response.js +80 -0
  107. package/dist/src/serve.d.ts +31 -0
  108. package/dist/src/serve.js +117 -0
  109. package/dist/src/service/account-deletion.d.ts +20 -0
  110. package/dist/src/service/account-deletion.js +33 -0
  111. package/dist/src/service/compose-sqlite.d.ts +3 -0
  112. package/dist/src/service/compose-sqlite.js +13 -0
  113. package/dist/src/service/create-remit-client.d.ts +109 -0
  114. package/dist/src/service/create-remit-client.js +188 -0
  115. package/dist/src/service/data-client.d.ts +12 -0
  116. package/dist/src/service/data-client.js +42 -0
  117. package/dist/src/service/export-identity.d.ts +4 -0
  118. package/dist/src/service/export-identity.js +33 -0
  119. package/dist/src/service/fire-and-forget.d.ts +54 -0
  120. package/dist/src/service/fire-and-forget.js +37 -0
  121. package/dist/src/service/organize.d.ts +181 -0
  122. package/dist/src/service/organize.js +367 -0
  123. package/dist/src/service/queues.d.ts +12 -0
  124. package/dist/src/service/queues.js +1 -0
  125. package/dist/src/service/refusal.d.ts +4 -0
  126. package/dist/src/service/refusal.js +20 -0
  127. package/dist/src/service/sender-category.d.ts +51 -0
  128. package/{src/service/sender-category.ts → dist/src/service/sender-category.js} +40 -81
  129. package/dist/src/service/shared-deps.d.ts +6 -0
  130. package/dist/src/service/shared-deps.js +24 -0
  131. package/dist/src/service/trigger-sync.d.ts +57 -0
  132. package/dist/src/service/trigger-sync.js +31 -0
  133. package/dist/src/types.d.ts +72 -0
  134. package/dist/src/types.js +1 -0
  135. package/package.json +125 -98
  136. package/.env.test +0 -4
  137. package/dev-server/content-auth.test.ts +0 -113
  138. package/dev-server/content-auth.ts +0 -54
  139. package/dev-server/content-handler.test.ts +0 -105
  140. package/dev-server/content-handler.ts +0 -79
  141. package/dev-server/content-path.test.ts +0 -37
  142. package/dev-server/content-path.ts +0 -27
  143. package/dev-server/cors.test.ts +0 -48
  144. package/dev-server/cors.ts +0 -25
  145. package/dev-server/keep-alive.test.ts +0 -24
  146. package/dev-server/lambda-helpers.ts +0 -69
  147. package/dev-server/log-output.test.ts +0 -173
  148. package/dev-server/oauth-callback-route.test.ts +0 -66
  149. package/dev-server/relational-health.test.ts +0 -83
  150. package/dev-server/relational-health.ts +0 -39
  151. package/dev-server/server.ts +0 -384
  152. package/dev-server/sync-age.test.ts +0 -91
  153. package/dev-server/sync-age.ts +0 -55
  154. package/dev-server/upload-handler.ts +0 -132
  155. package/dev-server/upload-route.test.ts +0 -196
  156. package/scripts/backfill-classification.ts +0 -91
  157. package/scripts/backfill-list-id.ts +0 -83
  158. package/scripts/config-save.ts +0 -128
  159. package/src/auth.test.ts +0 -12
  160. package/src/auth.ts +0 -92
  161. package/src/config/msoauth.test.ts +0 -48
  162. package/src/config/msoauth.ts +0 -48
  163. package/src/data-backend.test.ts +0 -23
  164. package/src/data-backend.ts +0 -14
  165. package/src/derive/autoMoved.test.ts +0 -104
  166. package/src/derive/contentSignature.test.ts +0 -161
  167. package/src/derive/contentUrl.test.ts +0 -172
  168. package/src/derive/contentUrl.ts +0 -86
  169. package/src/derive/deriveMuted.test.ts +0 -34
  170. package/src/derive/enrichThreadRows.test.ts +0 -360
  171. package/src/derive/enrichThreadRows.ts +0 -234
  172. package/src/derive/filterThreadCriteria.test.ts +0 -126
  173. package/src/derive/filterThreadCriteria.ts +0 -51
  174. package/src/derive/senderTrust.test.ts +0 -67
  175. package/src/error.test.ts +0 -201
  176. package/src/error.ts +0 -123
  177. package/src/handlers/account-guards.test.ts +0 -67
  178. package/src/handlers/account-guards.ts +0 -169
  179. package/src/handlers/account-oauth-callback.test.ts +0 -155
  180. package/src/handlers/account-oauth-consent.test.ts +0 -336
  181. package/src/handlers/account-oauth-start.test.ts +0 -191
  182. package/src/handlers/account-oauth.test.ts +0 -439
  183. package/src/handlers/account-oauth.ts +0 -635
  184. package/src/handlers/account-overrides.test.ts +0 -88
  185. package/src/handlers/account-overrides.ts +0 -323
  186. package/src/handlers/account-ownership.ts +0 -25
  187. package/src/handlers/account-signature.ts +0 -117
  188. package/src/handlers/account-synced-services.test.ts +0 -245
  189. package/src/handlers/account.ts +0 -552
  190. package/src/handlers/address.test.ts +0 -381
  191. package/src/handlers/address.ts +0 -200
  192. package/src/handlers/calendar-event.test.ts +0 -685
  193. package/src/handlers/calendar-event.ts +0 -738
  194. package/src/handlers/calendar-feed-validators.test.ts +0 -228
  195. package/src/handlers/calendar-feed.test.ts +0 -439
  196. package/src/handlers/calendar-feed.ts +0 -291
  197. package/src/handlers/calendar-sqlite-fixture.ts +0 -36
  198. package/src/handlers/calendar-suggestion.test.ts +0 -1269
  199. package/src/handlers/calendar-suggestion.ts +0 -425
  200. package/src/handlers/calendar.test.ts +0 -1080
  201. package/src/handlers/calendar.ts +0 -517
  202. package/src/handlers/config.test.ts +0 -245
  203. package/src/handlers/config.ts +0 -332
  204. package/src/handlers/ensure-account-config.ts +0 -30
  205. package/src/handlers/filter.test.ts +0 -644
  206. package/src/handlers/filter.ts +0 -518
  207. package/src/handlers/folder-role-appointments.test.ts +0 -438
  208. package/src/handlers/folder-role-appointments.ts +0 -249
  209. package/src/handlers/folder-role-labels.ts +0 -63
  210. package/src/handlers/folder-role.test.ts +0 -209
  211. package/src/handlers/folder-role.ts +0 -80
  212. package/src/handlers/index.ts +0 -83
  213. package/src/handlers/label.test.ts +0 -112
  214. package/src/handlers/label.ts +0 -210
  215. package/src/handlers/mailbox-bindings.ts +0 -111
  216. package/src/handlers/mailbox-rename-refusal.test.ts +0 -247
  217. package/src/handlers/mailbox-settled-target.test.ts +0 -251
  218. package/src/handlers/mailbox.test.ts +0 -280
  219. package/src/handlers/mailbox.ts +0 -581
  220. package/src/handlers/me.test.ts +0 -81
  221. package/src/handlers/me.ts +0 -203
  222. package/src/handlers/message-read-backfill.test.ts +0 -103
  223. package/src/handlers/message.test.ts +0 -131
  224. package/src/handlers/message.ts +0 -1131
  225. package/src/handlers/organize.test.ts +0 -277
  226. package/src/handlers/organize.ts +0 -197
  227. package/src/handlers/outbox-attachment.test.ts +0 -765
  228. package/src/handlers/outbox-attachment.ts +0 -126
  229. package/src/handlers/outbox-list.test.ts +0 -179
  230. package/src/handlers/outbox.test.ts +0 -772
  231. package/src/handlers/outbox.ts +0 -258
  232. package/src/handlers/search.test.ts +0 -161
  233. package/src/handlers/search.ts +0 -105
  234. package/src/handlers/sync-progress.test.ts +0 -205
  235. package/src/handlers/sync-progress.ts +0 -99
  236. package/src/handlers/sync.test.ts +0 -168
  237. package/src/handlers/sync.ts +0 -118
  238. package/src/handlers/system-update.test.ts +0 -497
  239. package/src/handlers/system-update.ts +0 -230
  240. package/src/handlers/thread.test.ts +0 -396
  241. package/src/handlers/thread.ts +0 -427
  242. package/src/handlers/unified-threads-mail-sources.sqlite.test.ts +0 -197
  243. package/src/handlers/unified-threads.test.ts +0 -785
  244. package/src/handlers/unified-threads.ts +0 -669
  245. package/src/index.test.ts +0 -83
  246. package/src/index.ts +0 -228
  247. package/src/jwt-auth.test.ts +0 -152
  248. package/src/jwt-auth.ts +0 -132
  249. package/src/log-redaction.test.ts +0 -144
  250. package/src/openapi-spec.test.ts +0 -118
  251. package/src/request-context.test.ts +0 -61
  252. package/src/request-context.ts +0 -33
  253. package/src/request-pipeline.test.ts +0 -88
  254. package/src/request.ts +0 -10
  255. package/src/response.test.ts +0 -130
  256. package/src/response.ts +0 -144
  257. package/src/service/compose-sqlite.ts +0 -98
  258. package/src/service/config-import.ts +0 -39
  259. package/src/service/create-remit-client.ts +0 -522
  260. package/src/service/data-client.test.ts +0 -97
  261. package/src/service/data-client.ts +0 -58
  262. package/src/service/export-identity.ts +0 -39
  263. package/src/service/filter.ts +0 -40
  264. package/src/service/fire-and-forget.test.ts +0 -126
  265. package/src/service/fire-and-forget.ts +0 -79
  266. package/src/service/localhost-env-config.test.ts +0 -36
  267. package/src/service/organize-queue.ts +0 -98
  268. package/src/service/organize.test.ts +0 -1451
  269. package/src/service/organize.ts +0 -1025
  270. package/src/service/read-path-filter-config.test.ts +0 -243
  271. package/src/service/semantic-capability.test.ts +0 -78
  272. package/src/service/semantic-capability.ts +0 -95
  273. package/src/service/sender-category.test.ts +0 -235
  274. package/src/service/sqs.ts +0 -4
  275. package/src/service/trigger-sync.test.ts +0 -205
  276. package/src/service/trigger-sync.ts +0 -100
  277. package/src/stdout-capture-fixture.ts +0 -40
  278. package/src/types.ts +0 -271
  279. package/tsconfig.json +0 -7
@@ -1,522 +0,0 @@
1
- import type {
2
- IAccountConfigRepository,
3
- IAccountExportRequestRepository,
4
- IAccountRepository,
5
- IAccountSettingRepository,
6
- IAddressRepository,
7
- ICalendarCollectionRepository,
8
- ICalendarEventIndexRepository,
9
- ICalendarFeedTokenRepository,
10
- ICalendarObjectRepository,
11
- ICalendarSuggestionRepository,
12
- ICalendarUnitOfWork,
13
- IConfigImportRepository,
14
- IEnvelopeRepository,
15
- IFilterAnchorRepository,
16
- IFilterAnchorTransaction,
17
- IFilterRepository,
18
- ILabelRepository,
19
- IMailboxLockRepository,
20
- IMailboxRepository,
21
- IMailboxSpecialUseRepository,
22
- IMessageFlagPushRepository,
23
- IMessageFlagRepository,
24
- IMessageLabelRepository,
25
- IMessagePlacementMoveRepository,
26
- IMessageRepository,
27
- IOrganizeJobRequestRepository,
28
- IOutboxAttachmentRepository,
29
- IOutboxMessageRepository,
30
- IQuarantineRepository,
31
- ISenderSignerStandingRepository,
32
- IThreadMessageRepository,
33
- IUnitOfWork,
34
- } from "@remit/data-ports";
35
- import { logger } from "@remit/logger-lambda";
36
- import {
37
- BodySyncQueueService,
38
- BodySyncService,
39
- createConnection,
40
- type FilterConfig,
41
- FlagPushService,
42
- FlagQueueService,
43
- type IImapConnection,
44
- MailboxQueueService,
45
- MessageMoveService,
46
- OutboxAttachmentService,
47
- OutboxQueueService,
48
- PlacementMoveService,
49
- SpamReportService,
50
- } from "@remit/mailbox-service";
51
- import { buildFilterConfig } from "@remit/mailbox-service/filter-config";
52
- import { createSearchService, type SearchService } from "@remit/search-service";
53
- import {
54
- buildEmbeddingServiceFromEnv,
55
- buildVectorStoreFromEnv,
56
- } from "@remit/search-service/from-env";
57
- import {
58
- createCachedDataKeyProvider,
59
- createKmsDataKeyProvider,
60
- createSecretsService,
61
- deserializeEncryptedPayload,
62
- FAKE_KMS_KEY_ID,
63
- type SecretsService,
64
- } from "@remit/secrets-service";
65
- import type { StorageService } from "@remit/storage-service";
66
- import { createStorageService } from "@remit/storage-service/s3";
67
- import { env } from "expect-env";
68
-
69
- export interface ConnectionScope {
70
- getConnection: () => Promise<IImapConnection>;
71
- disconnect: () => Promise<void>;
72
- }
73
-
74
- export interface RemitClient {
75
- // Data repositories (satisfied by ElectroDB services or Drizzle repos)
76
- accountConfig: IAccountConfigRepository;
77
- account: IAccountRepository;
78
- accountSetting: IAccountSettingRepository;
79
- address: IAddressRepository;
80
- mailbox: IMailboxRepository;
81
- mailboxSpecialUse: IMailboxSpecialUseRepository;
82
- mailboxLock: IMailboxLockRepository;
83
- message: IMessageRepository;
84
- messageFlag: IMessageFlagRepository;
85
- outboxMessage: IOutboxMessageRepository;
86
- threadMessage: IThreadMessageRepository;
87
- envelope: IEnvelopeRepository;
88
- accountExportRequest: IAccountExportRequestRepository;
89
-
90
- // Applied configuration imports (#1021), and the folder references each is
91
- // still waiting for. Read by GET /config to surface what a file named but
92
- // IMAP has not produced yet, and written by the binder when it does.
93
- configImport: IConfigImportRepository;
94
-
95
- // Messages the sync path could not read (issue #72). Read-only from the API
96
- // process: the sync worker writes the rows, settings lists them.
97
- quarantine: IQuarantineRepository;
98
-
99
- // Smart Organize back-apply job (RFC 034, #1278). Present on both backends,
100
- // modeled on accountExportRequest — a Pending row the account-fanout worker
101
- // picks up and drives to Complete/Failed. Never a Filter.
102
- organizeJobRequest: IOrganizeJobRequestRepository;
103
-
104
- // Smart Organize (RFC 034, epic #1280). Present on both backends
105
- // (`FilterService`/`LabelService`/… on DynamoDB, `FilterRepo`/`LabelRepo`/…
106
- // relationally) so the matching pipeline and filter CRUD run unchanged on
107
- // either. The relational side has no TTL reaper for expired Temporary filters;
108
- // match-time correctness gates on `expiresAt` (RFC 034 Decision 1.1), never
109
- // on the row still existing, so the missing reaper is housekeeping only.
110
- filter: IFilterRepository;
111
- filterAnchor: IFilterAnchorRepository;
112
-
113
- // Atomically creates a Filter and its optional sibling FilterAnchor row in
114
- // one transaction (#351) — a failed anchor write can never leave a Filter
115
- // durably marked hasAnchor: true with no matching FilterAnchor row.
116
- filterAnchorTransaction: IFilterAnchorTransaction;
117
- label: ILabelRepository;
118
- messageLabel: IMessageLabelRepository;
119
-
120
- // How often a sender has been seen arriving under one DKIM signing identity,
121
- // per account. Read by the authenticity derivation to tell a familiar
122
- // third-party signer from a first sighting; written once per message by
123
- // body-sync, which is its only writer.
124
- senderSignerStanding: ISenderSignerStandingRepository;
125
-
126
- // The calendar store (issue #15) and the cards a message offers into it
127
- // (issue #1033). Every write goes through `calendarUnitOfWork`: the object,
128
- // its occurrence rows, the collection's sequence bump and — when the write
129
- // came from accepting a suggestion — that suggestion's own state are one
130
- // fact, and the repositories beside it are the read side. A backend that
131
- // cannot supply them cannot serve the calendar at all, so they are part of
132
- // the client rather than something each handler checks for.
133
- calendarCollection: ICalendarCollectionRepository;
134
- calendarObject: ICalendarObjectRepository;
135
- calendarEventIndex: ICalendarEventIndexRepository;
136
- calendarFeedToken: ICalendarFeedTokenRepository;
137
- calendarSuggestion: ICalendarSuggestionRepository;
138
- calendarUnitOfWork: ICalendarUnitOfWork;
139
-
140
- // Atomic write set for a message save. Present on the relational backend (real
141
- // transaction); absent on DynamoDB, where callers fall back to per-repo
142
- // writes with that backend's own (non-transactional) guarantees.
143
- unitOfWork?: IUnitOfWork;
144
-
145
- // Storage service
146
- storage: StorageService;
147
-
148
- // Search service (semantic vector search)
149
- search: SearchService;
150
-
151
- // Secrets service (KMS encryption)
152
- secrets: SecretsService;
153
-
154
- // Body sync service (on-demand IMAP body fetch)
155
- bodySync: BodySyncService;
156
-
157
- // Re-arms the SYNC_MESSAGE_BODY cue when a read-path body fetch finds the
158
- // storage object missing. Absent when SQS_QUEUE_URL_BODY is not configured
159
- // (the deployed API Lambda does not carry the body queue today).
160
- bodySyncQueue?: BodySyncQueueService;
161
-
162
- // Pending placement-move markers (issue #1271). Present on both backends
163
- // (`MessagePlacementMoveService` on DynamoDB, `MessagePlacementMoveRepo` on
164
- // relationally) so a caller never needs to guess which backend is active. Used
165
- // for read-time count prediction (epic #1281 invariant 4) and by the
166
- // account-worker cascade delete. Written by the imap-worker's bulk
167
- // body-sync path through this `placementMove`, so the placement producer
168
- // runs on whatever backend is active — DynamoDB or SQLite.
169
- placementMove: IMessagePlacementMoveRepository;
170
-
171
- // Pending flag-push markers (issue #1273). Present on both backends
172
- // (`MessageFlagPushService` on DynamoDB, `MessageFlagPushRepo` on
173
- // relationally). Used for read-time unseenCount prediction (epic #1281
174
- // invariant 4), by the account-worker cascade delete, and by the periodic
175
- // per-mailbox sync tick to re-arm a marker stuck `pending`. Unlike
176
- // `placementMove`, this one IS written on both backends — `flagQueue`
177
- // below writes through it regardless of `DATA_BACKEND`.
178
- flagPush: IMessageFlagPushRepository;
179
-
180
- // Queue services (writes with IMAP sync)
181
- flagQueue: FlagQueueService;
182
- mailboxQueue: MailboxQueueService;
183
- messageMove: MessageMoveService;
184
- outboxAttachment: OutboxAttachmentService;
185
- outboxQueue: OutboxQueueService;
186
-
187
- // Report-spam / not-spam (block-and-move, unified from the old separate
188
- // Block + Mark spam actions). Composed over `messageMove` and the same
189
- // `flagPushService` instance `flagQueue` shares below.
190
- spamReport: SpamReportService;
191
-
192
- // Helper to create IMAP connection scope from accountId
193
- createConnectionScope: (accountId: string) => Promise<ConnectionScope>;
194
-
195
- // Runs an arbitrary set of repository writes as one transaction. Absent on a
196
- // backend with no cross-entity transaction, where the caller's contract is
197
- // instead: validate before the first write, fail fast, and report what
198
- // landed. The message-save write set has its own bound repositories and uses
199
- // `unitOfWork`; this one takes the repos as they are.
200
- writeSet?: <T>(run: () => Promise<T>) => Promise<T>;
201
- }
202
-
203
- export interface RemitClientRepositories {
204
- accountConfig: IAccountConfigRepository;
205
- account: IAccountRepository;
206
- accountSetting: IAccountSettingRepository;
207
- address: IAddressRepository;
208
- mailbox: IMailboxRepository;
209
- mailboxSpecialUse: IMailboxSpecialUseRepository;
210
- mailboxLock: IMailboxLockRepository;
211
- message: IMessageRepository;
212
- messageFlag: IMessageFlagRepository;
213
- outboxMessage: IOutboxMessageRepository;
214
- outboxAttachment: IOutboxAttachmentRepository;
215
- threadMessage: IThreadMessageRepository;
216
- envelope: IEnvelopeRepository;
217
- accountExportRequest: IAccountExportRequestRepository;
218
- configImport: IConfigImportRepository;
219
- quarantine: IQuarantineRepository;
220
- organizeJobRequest: IOrganizeJobRequestRepository;
221
- placementMove: IMessagePlacementMoveRepository;
222
- flagPush: IMessageFlagPushRepository;
223
- filter: IFilterRepository;
224
- filterAnchor: IFilterAnchorRepository;
225
- filterAnchorTransaction: IFilterAnchorTransaction;
226
- label: ILabelRepository;
227
- messageLabel: IMessageLabelRepository;
228
- senderSignerStanding: ISenderSignerStandingRepository;
229
- calendarCollection: ICalendarCollectionRepository;
230
- calendarObject: ICalendarObjectRepository;
231
- calendarEventIndex: ICalendarEventIndexRepository;
232
- calendarFeedToken: ICalendarFeedTokenRepository;
233
- calendarSuggestion: ICalendarSuggestionRepository;
234
- calendarUnitOfWork: ICalendarUnitOfWork;
235
- unitOfWork?: IUnitOfWork;
236
- writeSet?: <T>(run: () => Promise<T>) => Promise<T>;
237
- }
238
-
239
- export interface RemitClientSharedDeps {
240
- storage: StorageService;
241
- search: SearchService;
242
- secrets: SecretsService;
243
- sqsQueueUrl: string;
244
- sqsSmtpQueueUrl: string;
245
- bodySyncQueue?: BodySyncQueueService;
246
- }
247
-
248
- export interface RemitClientDeps extends RemitClientSharedDeps {
249
- repositories: RemitClientRepositories;
250
- }
251
-
252
- const buildConnectionScope =
253
- (accountService: IAccountRepository, secretsService: SecretsService) =>
254
- async (accountId: string): Promise<ConnectionScope> => {
255
- const account = await accountService.get(accountId);
256
- if (!account.passwordHash) {
257
- throw new Error(
258
- `Account ${accountId} has no passwordHash — OAuth accounts cannot use this connection path`,
259
- );
260
- }
261
- const password = await secretsService.decrypt(
262
- deserializeEncryptedPayload(JSON.parse(account.passwordHash)),
263
- );
264
-
265
- let connection: IImapConnection | null = null;
266
- let connectPromise: Promise<IImapConnection> | null = null;
267
-
268
- const getConnection = async (): Promise<IImapConnection> => {
269
- if (connectPromise) {
270
- return connectPromise;
271
- }
272
-
273
- const conn = createConnection({
274
- user: account.username,
275
- credentials: { kind: "password", password },
276
- host: account.imapHost,
277
- port: account.imapPort,
278
- tls: account.imapTls,
279
- });
280
- connection = conn;
281
- connectPromise = conn.connect().then(() => conn);
282
-
283
- return connectPromise;
284
- };
285
-
286
- const disconnect = async (): Promise<void> => {
287
- if (connection) {
288
- await connection.disconnect();
289
- connection = null;
290
- connectPromise = null;
291
- }
292
- };
293
-
294
- return { getConnection, disconnect };
295
- };
296
-
297
- // The vector store and embedder are selected from the environment by the shared
298
- // builders: S3 Vectors + Bedrock Titan in production, persistent sqlite-vec +
299
- // Transformers.js when the LOCAL_* / local flags are set (local dev), and the
300
- // in-memory store + deterministic embedder otherwise (tests).
301
- const buildSearchService = (): SearchService => {
302
- // Build the embedder first so we can pass its dimension count to the
303
- // sqlite-vec store — the vec0 table dimension must match the embedder.
304
- const embedder = buildEmbeddingServiceFromEnv();
305
- return createSearchService({
306
- store: buildVectorStoreFromEnv(embedder.dimensions),
307
- embedder,
308
- });
309
- };
310
-
311
- // The body queue lives on the worker infra; the API only knows it locally
312
- // (dev / e2e set SQS_QUEUE_URL_BODY). When unset the read path still returns a
313
- // retryable 202 — it just cannot re-arm the cue itself.
314
- const buildBodySyncQueue = (): BodySyncQueueService | undefined => {
315
- const bodyQueueUrl = process.env.SQS_QUEUE_URL_BODY;
316
- if (!bodyQueueUrl) return undefined;
317
- return new BodySyncQueueService({ sqsQueueUrl: bodyQueueUrl, logger });
318
- };
319
-
320
- // Everything a RemitClient needs that is chosen from the environment and does
321
- // not vary by data backend: storage, search, secrets, the SQS queue URLs, and
322
- // the optional body-sync queue. Each composition root builds its backend's
323
- // repositories and merges them with this.
324
- export const buildSharedDeps = (): RemitClientSharedDeps => {
325
- const storage = createStorageService();
326
- const search = buildSearchService();
327
- const kmsKeyId = process.env.KMS_KEY_ID ?? FAKE_KMS_KEY_ID;
328
- const dataKeyProvider = createCachedDataKeyProvider(
329
- createKmsDataKeyProvider(kmsKeyId),
330
- );
331
- const secrets = createSecretsService(dataKeyProvider);
332
- const sqsQueueUrl = env.SQS_QUEUE_URL;
333
- const sqsSmtpQueueUrl = process.env.SQS_QUEUE_URL_SMTP ?? sqsQueueUrl;
334
- return {
335
- storage,
336
- search,
337
- secrets,
338
- sqsQueueUrl,
339
- sqsSmtpQueueUrl,
340
- bodySyncQueue: buildBodySyncQueue(),
341
- };
342
- };
343
-
344
- // The read-path body backfill (describeMessage / getRawMessage →
345
- // fetchAndGetBody) materializes a body that was never sync-synced, and must run
346
- // the account's standing filters the same way the imap-worker's sync path does.
347
- // Without this, a message a user opens before background body-sync is classified
348
- // but never filter-evaluated — and once its body is stored the filter-capable
349
- // sync path skips it, so an explicit standing filter silently never fires (issue
350
- // #223). Only the explicit user-rule half is wired here; heuristic classifier
351
- // placement stays on the sync path. It shares `buildFilterConfig` with
352
- // `sync-message-body.ts`, so both paths embed a candidate message under the same
353
- // env-selected model that produced the anchors and a semantic (anchor-only)
354
- // filter fires on either (issue #298). Over the message-management queue's
355
- // local-first PlacementMoveService; filters stay off when that queue is unset.
356
- export const buildReadPathFilterConfig = (
357
- repositories: RemitClientRepositories,
358
- ): FilterConfig | undefined => {
359
- const placementMoveQueueUrl = process.env.SQS_QUEUE_URL_MESSAGE_MGMT;
360
- return buildFilterConfig({
361
- filterService: repositories.filter,
362
- filterAnchorService: repositories.filterAnchor,
363
- messageLabelService: repositories.messageLabel,
364
- placementMoveService: placementMoveQueueUrl
365
- ? new PlacementMoveService({
366
- messageService: repositories.message,
367
- threadMessageService: repositories.threadMessage,
368
- markerService: repositories.placementMove,
369
- addressService: repositories.address,
370
- mailboxSpecialUseService: repositories.mailboxSpecialUse,
371
- sqsQueueUrl: placementMoveQueueUrl,
372
- })
373
- : undefined,
374
- });
375
- };
376
-
377
- // Backend-neutral composition root: given repositories (from any data-ports
378
- // implementation) and the shared services, wire the domain and queue services
379
- // and assemble a RemitClient. Imports neither ElectroDB nor Drizzle — the
380
- // caller chooses the backend.
381
- export const createRemitClient = (deps: RemitClientDeps): RemitClient => {
382
- const {
383
- repositories,
384
- storage,
385
- search,
386
- secrets,
387
- sqsQueueUrl,
388
- sqsSmtpQueueUrl,
389
- bodySyncQueue,
390
- } = deps;
391
-
392
- const filterConfig = buildReadPathFilterConfig(repositories);
393
-
394
- const flagPushService = new FlagPushService({
395
- markerService: repositories.flagPush,
396
- sqsQueueUrl,
397
- logger,
398
- });
399
-
400
- // Shared with the returned `flagQueue` below (issue #302): body sync
401
- // auto-marks-read a message from an unsubscribed sender through the exact
402
- // same `FlagQueueService.markAsRead` a manual mark-as-read goes through, not
403
- // a second instance or a second primitive.
404
- const flagQueueService = new FlagQueueService({
405
- messageFlagService: repositories.messageFlag,
406
- messageService: repositories.message,
407
- threadMessageService: repositories.threadMessage,
408
- flagPushService,
409
- logger,
410
- });
411
-
412
- const messageMoveService = new MessageMoveService({
413
- messageService: repositories.message,
414
- mailboxService: repositories.mailbox,
415
- mailboxSpecialUseService: repositories.mailboxSpecialUse,
416
- threadMessageService: repositories.threadMessage,
417
- addressService: repositories.address,
418
- sqsQueueUrl,
419
- logger,
420
- });
421
-
422
- // One instance, shared with the queue service below: the per-draft chain that
423
- // makes the attachment cap safe under concurrency lives on it, and a discard
424
- // racing an upload has to take the same lock the upload does.
425
- const outboxAttachmentService = new OutboxAttachmentService({
426
- outboxMessageService: repositories.outboxMessage,
427
- outboxAttachmentService: repositories.outboxAttachment,
428
- storage,
429
- });
430
-
431
- const bodySync = new BodySyncService(
432
- repositories.message,
433
- storage,
434
- repositories.threadMessage,
435
- repositories.address,
436
- repositories.envelope,
437
- logger,
438
- undefined,
439
- filterConfig,
440
- undefined,
441
- { flagQueueService },
442
- // The read-path backfill materializes a body the sync path never got to,
443
- // so it is a message's first sight as much as the sync pass is — and an
444
- // invitation the user opens before background sync must still get its
445
- // card (issue #1033, the same argument as the filter wiring above).
446
- {
447
- calendarSuggestionService: repositories.calendarSuggestion,
448
- calendarUnitOfWork: repositories.calendarUnitOfWork,
449
- filterService: repositories.filter,
450
- },
451
- repositories.unitOfWork,
452
- );
453
-
454
- return {
455
- accountConfig: repositories.accountConfig,
456
- account: repositories.account,
457
- accountSetting: repositories.accountSetting,
458
- address: repositories.address,
459
- mailbox: repositories.mailbox,
460
- mailboxSpecialUse: repositories.mailboxSpecialUse,
461
- mailboxLock: repositories.mailboxLock,
462
- message: repositories.message,
463
- messageFlag: repositories.messageFlag,
464
- outboxMessage: repositories.outboxMessage,
465
- threadMessage: repositories.threadMessage,
466
- envelope: repositories.envelope,
467
- accountExportRequest: repositories.accountExportRequest,
468
- configImport: repositories.configImport,
469
- quarantine: repositories.quarantine,
470
- organizeJobRequest: repositories.organizeJobRequest,
471
- filter: repositories.filter,
472
- filterAnchor: repositories.filterAnchor,
473
- filterAnchorTransaction: repositories.filterAnchorTransaction,
474
- label: repositories.label,
475
- messageLabel: repositories.messageLabel,
476
- senderSignerStanding: repositories.senderSignerStanding,
477
- calendarCollection: repositories.calendarCollection,
478
- calendarObject: repositories.calendarObject,
479
- calendarEventIndex: repositories.calendarEventIndex,
480
- calendarFeedToken: repositories.calendarFeedToken,
481
- calendarSuggestion: repositories.calendarSuggestion,
482
- calendarUnitOfWork: repositories.calendarUnitOfWork,
483
- unitOfWork: repositories.unitOfWork,
484
- writeSet: repositories.writeSet,
485
-
486
- storage,
487
- search,
488
- secrets,
489
-
490
- bodySync,
491
- bodySyncQueue,
492
- placementMove: repositories.placementMove,
493
- flagPush: repositories.flagPush,
494
-
495
- flagQueue: flagQueueService,
496
- mailboxQueue: new MailboxQueueService({
497
- mailboxService: repositories.mailbox,
498
- sqsQueueUrl,
499
- logger,
500
- }),
501
- messageMove: messageMoveService,
502
- outboxAttachment: outboxAttachmentService,
503
- outboxQueue: new OutboxQueueService({
504
- outboxMessageService: repositories.outboxMessage,
505
- outboxAttachmentService,
506
- accountService: repositories.account,
507
- sqsSmtpQueueUrl,
508
- logger,
509
- }),
510
- spamReport: new SpamReportService({
511
- messageService: repositories.message,
512
- addressService: repositories.address,
513
- accountService: repositories.account,
514
- mailboxSpecialUseService: repositories.mailboxSpecialUse,
515
- messageMoveService,
516
- flagPushService,
517
- logger,
518
- }),
519
-
520
- createConnectionScope: buildConnectionScope(repositories.account, secrets),
521
- };
522
- };
@@ -1,97 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import { afterEach, test } from "node:test";
6
- import {
7
- _resetForTest,
8
- getClient,
9
- type RemitClient,
10
- setClient,
11
- } from "./data-client.js";
12
-
13
- const REQUIRED_KEYS: ReadonlyArray<keyof RemitClient> = [
14
- "accountConfig",
15
- "account",
16
- "accountSetting",
17
- "address",
18
- "mailbox",
19
- "mailboxSpecialUse",
20
- "message",
21
- "messageFlag",
22
- "outboxMessage",
23
- "threadMessage",
24
- "envelope",
25
- "accountExportRequest",
26
- "quarantine",
27
- "storage",
28
- "search",
29
- "secrets",
30
- "bodySync",
31
- "flagQueue",
32
- "mailboxQueue",
33
- "messageMove",
34
- "outboxQueue",
35
- "spamReport",
36
- "createConnectionScope",
37
- ] as const;
38
-
39
- afterEach(() => {
40
- _resetForTest();
41
- });
42
-
43
- const withSqliteDbPath = async (run: () => Promise<void>): Promise<void> => {
44
- const saved = process.env.SQLITE_DB_PATH;
45
- const dir = mkdtempSync(join(tmpdir(), "remit-data-client-"));
46
- process.env.SQLITE_DB_PATH = join(dir, "remit.db");
47
- try {
48
- await run();
49
- } finally {
50
- _resetForTest();
51
- if (saved === undefined) delete process.env.SQLITE_DB_PATH;
52
- else process.env.SQLITE_DB_PATH = saved;
53
- rmSync(dir, { recursive: true, force: true });
54
- }
55
- };
56
-
57
- test("getClient() falls back to the in-package composition and constructs every service", async () => {
58
- await withSqliteDbPath(async () => {
59
- const client = await getClient();
60
-
61
- for (const key of REQUIRED_KEYS) {
62
- assert.ok(
63
- client[key] !== undefined && client[key] !== null,
64
- `RemitClient.${String(key)} must be defined on the fallback path`,
65
- );
66
- }
67
-
68
- assert.equal(typeof client.createConnectionScope, "function");
69
- });
70
- });
71
-
72
- test("getClient() names setClient when nothing is registered and there is no database to open", async () => {
73
- const saved = process.env.SQLITE_DB_PATH;
74
- delete process.env.SQLITE_DB_PATH;
75
-
76
- try {
77
- await assert.rejects(getClient(), /register one with setClient\(\)/);
78
- } finally {
79
- if (saved !== undefined) process.env.SQLITE_DB_PATH = saved;
80
- }
81
- });
82
-
83
- test("getClient() returns the injected client without reading the environment", async () => {
84
- const saved = process.env.SQLITE_DB_PATH;
85
- delete process.env.SQLITE_DB_PATH;
86
-
87
- const injected = { account: {} } as unknown as RemitClient;
88
- setClient(injected);
89
-
90
- try {
91
- const client = await getClient();
92
- assert.equal(client, injected);
93
- } finally {
94
- _resetForTest();
95
- if (saved !== undefined) process.env.SQLITE_DB_PATH = saved;
96
- }
97
- });
@@ -1,58 +0,0 @@
1
- import type { RemitClient } from "./create-remit-client.js";
2
-
3
- export type {
4
- ConnectionScope,
5
- RemitClient,
6
- } from "./create-remit-client.js";
7
-
8
- let clientPromise: Promise<RemitClient> | null = null;
9
- let injected: RemitClient | null = null;
10
-
11
- /**
12
- * Register the client from the composition root. An adapter that lives outside
13
- * this shared, open-core module — DynamoDB, or a Postgres adapter in the
14
- * repository that deploys it — is never imported here; its composition root
15
- * calls this before handling a request.
16
- */
17
- export const setClient = (client: RemitClient): void => {
18
- injected = client;
19
- clientPromise = null;
20
- };
21
-
22
- /**
23
- * The SQLite composition this build contains, reached only by a process that
24
- * registered no client of its own. The `import()` is dynamic so a bundler
25
- * following the entry graph of a process that injects its client never reaches
26
- * `@remit/drizzle-service` or drizzle-orm (both `external` for the Lambda
27
- * esbuild build).
28
- *
29
- * `SQLITE_DB_PATH` is the precondition the composition needs, checked ahead of
30
- * the import so its absence names `setClient` instead of failing on module
31
- * resolution inside a bundle, or — worse — opening an empty database and
32
- * serving an empty mailbox. It is a precondition, not a backend selection.
33
- */
34
- const buildRelationalClient = async (): Promise<RemitClient> => {
35
- if (!process.env.SQLITE_DB_PATH) {
36
- throw new Error(
37
- "no client registered — register one with setClient() from your composition root",
38
- );
39
- }
40
- const composition = await import("./compose-sqlite.js");
41
- return composition.buildSqliteClient();
42
- };
43
-
44
- export const getClient = (): Promise<RemitClient> => {
45
- if (!clientPromise) {
46
- clientPromise = injected
47
- ? Promise.resolve(injected)
48
- : buildRelationalClient();
49
- }
50
-
51
- return clientPromise;
52
- };
53
-
54
- /** Reset the singleton and any injected client — test use only. */
55
- export const _resetForTest = (): void => {
56
- clientPromise = null;
57
- injected = null;
58
- };