@remit/backend 0.0.143 → 0.0.144
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-server/content-handler.d.ts +38 -0
- package/dist/dev-server/content-handler.js +46 -0
- package/dist/src/api.d.ts +7 -0
- package/dist/src/api.js +144 -0
- package/dist/src/auth.d.ts +30 -0
- package/dist/src/auth.js +73 -0
- package/dist/src/config/msoauth.d.ts +30 -0
- package/dist/src/config/msoauth.js +30 -0
- package/{src/data-backend.ts → dist/src/data-backend.d.ts} +2 -4
- package/dist/src/data-backend.js +12 -0
- package/{src/derive/autoMoved.ts → dist/src/derive/autoMoved.d.ts} +1 -29
- package/dist/src/derive/autoMoved.js +49 -0
- package/dist/src/derive/contentSignature.d.ts +41 -0
- package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +27 -72
- package/dist/src/derive/contentUrl.d.ts +46 -0
- package/dist/src/derive/contentUrl.js +46 -0
- package/{src/derive/deriveMuted.ts → dist/src/derive/deriveMuted.d.ts} +1 -3
- package/dist/src/derive/deriveMuted.js +7 -0
- package/dist/src/derive/enrichThreadRows.d.ts +118 -0
- package/dist/src/derive/enrichThreadRows.js +151 -0
- package/dist/src/derive/filterThreadCriteria.d.ts +23 -0
- package/dist/src/derive/filterThreadCriteria.js +24 -0
- package/{src/derive/pendingMoveCounts.ts → dist/src/derive/pendingMoveCounts.d.ts} +5 -53
- package/dist/src/derive/pendingMoveCounts.js +53 -0
- package/{src/derive/senderTrust.ts → dist/src/derive/senderTrust.d.ts} +1 -9
- package/dist/src/derive/senderTrust.js +19 -0
- package/dist/src/error.d.ts +21 -0
- package/dist/src/error.js +85 -0
- package/dist/src/handlers/account-guards.d.ts +36 -0
- package/dist/src/handlers/account-guards.js +107 -0
- package/dist/src/handlers/account-oauth.d.ts +14 -0
- package/dist/src/handlers/account-oauth.js +387 -0
- package/dist/src/handlers/account-overrides.d.ts +53 -0
- package/dist/src/handlers/account-overrides.js +233 -0
- package/dist/src/handlers/account-signature.d.ts +28 -0
- package/dist/src/handlers/account-signature.js +85 -0
- package/dist/src/handlers/account.d.ts +6 -0
- package/dist/src/handlers/account.js +350 -0
- package/dist/src/handlers/address.d.ts +35 -0
- package/dist/src/handlers/address.js +141 -0
- package/dist/src/handlers/calendar-event.d.ts +70 -0
- package/dist/src/handlers/calendar-event.js +425 -0
- package/dist/src/handlers/calendar-feed.d.ts +47 -0
- package/dist/src/handlers/calendar-feed.js +192 -0
- package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
- package/dist/src/handlers/calendar-suggestion.js +212 -0
- package/dist/src/handlers/calendar.d.ts +116 -0
- package/dist/src/handlers/calendar.js +330 -0
- package/dist/src/handlers/config.d.ts +43 -0
- package/dist/src/handlers/config.js +176 -0
- package/dist/src/handlers/ensure-account-config.d.ts +9 -0
- package/dist/src/handlers/ensure-account-config.js +27 -0
- package/dist/src/handlers/filter.d.ts +3 -0
- package/dist/src/handlers/filter.js +103 -0
- package/dist/src/handlers/folder-role-appointments.d.ts +68 -0
- package/dist/src/handlers/folder-role-appointments.js +168 -0
- package/dist/src/handlers/folder-role-labels.d.ts +26 -0
- package/dist/src/handlers/folder-role-labels.js +40 -0
- package/dist/src/handlers/folder-role.d.ts +9 -0
- package/dist/src/handlers/folder-role.js +40 -0
- package/dist/src/handlers/index.d.ts +2 -0
- package/dist/src/handlers/index.js +60 -0
- package/dist/src/handlers/label.d.ts +43 -0
- package/dist/src/handlers/label.js +114 -0
- package/dist/src/handlers/mailbox-bindings.d.ts +41 -0
- package/dist/src/handlers/mailbox-bindings.js +61 -0
- package/dist/src/handlers/mailbox.d.ts +23 -0
- package/dist/src/handlers/mailbox.js +219 -0
- package/dist/src/handlers/me.d.ts +2 -0
- package/dist/src/handlers/me.js +138 -0
- package/dist/src/handlers/message.d.ts +84 -0
- package/dist/src/handlers/message.js +607 -0
- package/dist/src/handlers/organize.d.ts +10 -0
- package/dist/src/handlers/organize.js +112 -0
- package/dist/src/handlers/outbox-attachment.d.ts +23 -0
- package/dist/src/handlers/outbox-attachment.js +81 -0
- package/dist/src/handlers/outbox.d.ts +4 -0
- package/dist/src/handlers/outbox.js +144 -0
- package/dist/src/handlers/sync-progress.d.ts +46 -0
- package/dist/src/handlers/sync-progress.js +63 -0
- package/dist/src/handlers/sync.d.ts +28 -0
- package/dist/src/handlers/sync.js +82 -0
- package/dist/src/handlers/system-update.d.ts +2 -0
- package/dist/src/handlers/system-update.js +172 -0
- package/dist/src/handlers/thread.d.ts +148 -0
- package/dist/src/handlers/thread.js +233 -0
- package/dist/src/handlers/unified-threads.d.ts +247 -0
- package/dist/src/handlers/unified-threads.js +409 -0
- package/{src/handlers/vip-suggestions.ts → dist/src/handlers/vip-suggestions.d.ts} +1 -13
- package/dist/src/handlers/vip-suggestions.js +16 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +37 -0
- package/{src/json.ts → dist/src/json.d.ts} +1 -4
- package/dist/src/json.js +10 -0
- package/dist/src/jwt-auth.d.ts +18 -0
- package/dist/src/jwt-auth.js +97 -0
- package/dist/src/request-context.d.ts +9 -0
- package/dist/src/request-context.js +22 -0
- package/dist/src/request.d.ts +3 -0
- package/dist/src/request.js +7 -0
- package/dist/src/response.d.ts +27 -0
- package/dist/src/response.js +87 -0
- package/dist/src/service/account-deletion.d.ts +22 -0
- package/dist/src/service/account-deletion.js +39 -0
- package/dist/src/service/compose-sqlite.d.ts +2 -0
- package/dist/src/service/compose-sqlite.js +11 -0
- package/dist/src/service/create-remit-client.d.ts +95 -0
- package/dist/src/service/create-remit-client.js +207 -0
- package/dist/src/service/data-client.d.ts +12 -0
- package/dist/src/service/data-client.js +44 -0
- package/dist/src/service/export-identity.d.ts +3 -0
- package/dist/src/service/export-identity.js +35 -0
- package/dist/src/service/fire-and-forget.d.ts +54 -0
- package/dist/src/service/fire-and-forget.js +38 -0
- package/dist/src/service/organize.d.ts +184 -0
- package/dist/src/service/organize.js +384 -0
- package/dist/src/service/refusal.d.ts +4 -0
- package/dist/src/service/refusal.js +20 -0
- package/dist/src/service/sender-category.d.ts +51 -0
- package/{src/service/sender-category.ts → dist/src/service/sender-category.js} +40 -81
- package/dist/src/service/sqs.d.ts +1 -0
- package/{src/service/sqs.ts → dist/src/service/sqs.js} +0 -1
- package/dist/src/service/trigger-sync.d.ts +51 -0
- package/dist/src/service/trigger-sync.js +39 -0
- package/dist/src/types.d.ts +49 -0
- package/dist/src/types.js +1 -0
- package/package.json +113 -98
- package/.env.test +0 -4
- package/dev-server/content-auth.test.ts +0 -113
- package/dev-server/content-auth.ts +0 -54
- package/dev-server/content-handler.test.ts +0 -105
- package/dev-server/content-handler.ts +0 -79
- package/dev-server/content-path.test.ts +0 -37
- package/dev-server/content-path.ts +0 -27
- package/dev-server/cors.test.ts +0 -48
- package/dev-server/cors.ts +0 -25
- package/dev-server/keep-alive.test.ts +0 -24
- package/dev-server/lambda-helpers.ts +0 -69
- package/dev-server/log-output.test.ts +0 -173
- package/dev-server/oauth-callback-route.test.ts +0 -66
- package/dev-server/relational-health.test.ts +0 -83
- package/dev-server/relational-health.ts +0 -39
- package/dev-server/server.ts +0 -384
- package/dev-server/sync-age.test.ts +0 -91
- package/dev-server/sync-age.ts +0 -55
- package/dev-server/upload-handler.ts +0 -132
- package/dev-server/upload-route.test.ts +0 -196
- package/scripts/backfill-classification.ts +0 -91
- package/scripts/backfill-list-id.ts +0 -83
- package/scripts/config-save.ts +0 -128
- package/src/auth.test.ts +0 -12
- package/src/auth.ts +0 -92
- package/src/config/msoauth.test.ts +0 -48
- package/src/config/msoauth.ts +0 -48
- package/src/data-backend.test.ts +0 -23
- package/src/derive/autoMoved.test.ts +0 -104
- package/src/derive/contentSignature.test.ts +0 -161
- package/src/derive/contentUrl.test.ts +0 -172
- package/src/derive/contentUrl.ts +0 -86
- package/src/derive/deriveMuted.test.ts +0 -34
- package/src/derive/enrichThreadRows.test.ts +0 -360
- package/src/derive/enrichThreadRows.ts +0 -234
- package/src/derive/filterThreadCriteria.test.ts +0 -126
- package/src/derive/filterThreadCriteria.ts +0 -51
- package/src/derive/senderTrust.test.ts +0 -67
- package/src/error.test.ts +0 -201
- package/src/error.ts +0 -123
- package/src/handlers/account-guards.test.ts +0 -67
- package/src/handlers/account-guards.ts +0 -169
- package/src/handlers/account-oauth-callback.test.ts +0 -155
- package/src/handlers/account-oauth-consent.test.ts +0 -336
- package/src/handlers/account-oauth-start.test.ts +0 -191
- package/src/handlers/account-oauth.test.ts +0 -439
- package/src/handlers/account-oauth.ts +0 -635
- package/src/handlers/account-overrides.test.ts +0 -88
- package/src/handlers/account-overrides.ts +0 -323
- package/src/handlers/account-ownership.ts +0 -25
- package/src/handlers/account-signature.ts +0 -117
- package/src/handlers/account-synced-services.test.ts +0 -245
- package/src/handlers/account.ts +0 -552
- package/src/handlers/address.test.ts +0 -381
- package/src/handlers/address.ts +0 -200
- package/src/handlers/calendar-event.test.ts +0 -685
- package/src/handlers/calendar-event.ts +0 -738
- package/src/handlers/calendar-feed-validators.test.ts +0 -228
- package/src/handlers/calendar-feed.test.ts +0 -439
- package/src/handlers/calendar-feed.ts +0 -291
- package/src/handlers/calendar-sqlite-fixture.ts +0 -36
- package/src/handlers/calendar-suggestion.test.ts +0 -1269
- package/src/handlers/calendar-suggestion.ts +0 -425
- package/src/handlers/calendar.test.ts +0 -1080
- package/src/handlers/calendar.ts +0 -517
- package/src/handlers/config.test.ts +0 -245
- package/src/handlers/config.ts +0 -332
- package/src/handlers/ensure-account-config.ts +0 -30
- package/src/handlers/filter.test.ts +0 -644
- package/src/handlers/filter.ts +0 -518
- package/src/handlers/folder-role-appointments.test.ts +0 -438
- package/src/handlers/folder-role-appointments.ts +0 -249
- package/src/handlers/folder-role-labels.ts +0 -63
- package/src/handlers/folder-role.test.ts +0 -209
- package/src/handlers/folder-role.ts +0 -80
- package/src/handlers/index.ts +0 -83
- package/src/handlers/label.test.ts +0 -112
- package/src/handlers/label.ts +0 -210
- package/src/handlers/mailbox-bindings.ts +0 -111
- package/src/handlers/mailbox-rename-refusal.test.ts +0 -247
- package/src/handlers/mailbox-settled-target.test.ts +0 -251
- package/src/handlers/mailbox.test.ts +0 -280
- package/src/handlers/mailbox.ts +0 -581
- package/src/handlers/me.test.ts +0 -81
- package/src/handlers/me.ts +0 -203
- package/src/handlers/message-read-backfill.test.ts +0 -103
- package/src/handlers/message.test.ts +0 -131
- package/src/handlers/message.ts +0 -1131
- package/src/handlers/organize.test.ts +0 -277
- package/src/handlers/organize.ts +0 -197
- package/src/handlers/outbox-attachment.test.ts +0 -765
- package/src/handlers/outbox-attachment.ts +0 -126
- package/src/handlers/outbox-list.test.ts +0 -179
- package/src/handlers/outbox.test.ts +0 -772
- package/src/handlers/outbox.ts +0 -258
- package/src/handlers/search.test.ts +0 -161
- package/src/handlers/search.ts +0 -105
- package/src/handlers/sync-progress.test.ts +0 -205
- package/src/handlers/sync-progress.ts +0 -99
- package/src/handlers/sync.test.ts +0 -168
- package/src/handlers/sync.ts +0 -118
- package/src/handlers/system-update.test.ts +0 -497
- package/src/handlers/system-update.ts +0 -230
- package/src/handlers/thread.test.ts +0 -396
- package/src/handlers/thread.ts +0 -427
- package/src/handlers/unified-threads-mail-sources.sqlite.test.ts +0 -197
- package/src/handlers/unified-threads.test.ts +0 -785
- package/src/handlers/unified-threads.ts +0 -669
- package/src/index.test.ts +0 -83
- package/src/index.ts +0 -228
- package/src/jwt-auth.test.ts +0 -152
- package/src/jwt-auth.ts +0 -132
- package/src/log-redaction.test.ts +0 -144
- package/src/openapi-spec.test.ts +0 -118
- package/src/request-context.test.ts +0 -61
- package/src/request-context.ts +0 -33
- package/src/request-pipeline.test.ts +0 -88
- package/src/request.ts +0 -10
- package/src/response.test.ts +0 -130
- package/src/response.ts +0 -144
- package/src/service/compose-sqlite.ts +0 -98
- package/src/service/config-import.ts +0 -39
- package/src/service/create-remit-client.ts +0 -522
- package/src/service/data-client.test.ts +0 -97
- package/src/service/data-client.ts +0 -58
- package/src/service/export-identity.ts +0 -39
- package/src/service/filter.ts +0 -40
- package/src/service/fire-and-forget.test.ts +0 -126
- package/src/service/fire-and-forget.ts +0 -79
- package/src/service/localhost-env-config.test.ts +0 -36
- package/src/service/organize-queue.ts +0 -98
- package/src/service/organize.test.ts +0 -1451
- package/src/service/organize.ts +0 -1025
- package/src/service/read-path-filter-config.test.ts +0 -243
- package/src/service/semantic-capability.test.ts +0 -78
- package/src/service/semantic-capability.ts +0 -95
- package/src/service/sender-category.test.ts +0 -235
- package/src/service/trigger-sync.test.ts +0 -205
- package/src/service/trigger-sync.ts +0 -100
- package/src/stdout-capture-fixture.ts +0 -40
- package/src/types.ts +0 -271
- 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
|
-
};
|