@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,39 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { join } from "node:path";
3
- import type { ConfigExportIdentity } from "@remit/config-transfer";
4
-
5
- const DEFAULT_CONTROL_DIR = "/data/control";
6
-
7
- /**
8
- * The version a file says it was written by. The updater owns this fact: it
9
- * reads the tag from `.env` and writes it into `state.json`, which is the only
10
- * honest source across an update that rewrites that tag underneath a
11
- * long-lived process. With no state file there is nothing authoritative to
12
- * stamp, and an export says so rather than inventing a version an importing
13
- * reader would then trust.
14
- */
15
- const runningVersion = (): string => {
16
- const dir = process.env.REMIT_UPDATE_CONTROL_DIR ?? DEFAULT_CONTROL_DIR;
17
- let raw: string;
18
- try {
19
- raw = readFileSync(join(dir, "state.json"), "utf8");
20
- } catch {
21
- return "unknown";
22
- }
23
- const state: unknown = JSON.parse(raw);
24
- if (typeof state !== "object" || state === null) return "unknown";
25
- const version = (state as { currentVersion?: unknown }).currentVersion;
26
- return typeof version === "string" && version.length > 0
27
- ? version
28
- : "unknown";
29
- };
30
-
31
- /** Who wrote a configuration file, and from where. Provenance only. */
32
- export const exportIdentity = (
33
- now: Date = new Date(),
34
- ): ConfigExportIdentity => ({
35
- app: "reader",
36
- version: runningVersion(),
37
- exportedAt: now.toISOString(),
38
- instance: process.env.PUBLIC_ORIGIN ?? "",
39
- });
@@ -1,40 +0,0 @@
1
- import { type AnchorPayload, buildMessageAnchor } from "@remit/search-service";
2
- import {
3
- buildEmbeddingServiceFromEnv,
4
- buildVectorStoreFromEnv,
5
- } from "@remit/search-service/from-env";
6
-
7
- /**
8
- * Build a filter's persisted semantic anchor from an already-indexed message's
9
- * chunk vectors (RFC 034 Decision 2.1), or `null` when the message has no
10
- * indexed chunks. Never embeds anything new — the vectors already exist from
11
- * index time.
12
- *
13
- * This is the only piece of filter CRUD not on the `RemitClient` port bundle:
14
- * the vector store and embedder are selected by env, independent of
15
- * `DATA_BACKEND`, so it works unchanged on either backend. The filter and
16
- * anchor rows themselves are written through
17
- * `client.filter`/`client.filterAnchor` so they land in the same backend as
18
- * everything else.
19
- */
20
- export type BuildFilterAnchor = (
21
- accountConfigId: string,
22
- anchorMessageId: string,
23
- ) => Promise<AnchorPayload | null>;
24
-
25
- let cached: BuildFilterAnchor | null = null;
26
-
27
- const build = (): BuildFilterAnchor => {
28
- const embedder = buildEmbeddingServiceFromEnv();
29
- const store = buildVectorStoreFromEnv(embedder.dimensions);
30
- return (accountConfigId, anchorMessageId) =>
31
- buildMessageAnchor({ store }, { accountConfigId, anchorMessageId });
32
- };
33
-
34
- export const buildFilterAnchor: BuildFilterAnchor = (
35
- accountConfigId,
36
- anchorMessageId,
37
- ) => {
38
- if (!cached) cached = build();
39
- return cached(accountConfigId, anchorMessageId);
40
- };
@@ -1,126 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
3
- import { type FireAndForgetLogger, fireAndForget } from "./fire-and-forget.js";
4
-
5
- interface LogCall {
6
- fields: Record<string, unknown>;
7
- message: string;
8
- }
9
-
10
- const createLoggerSpy = () => {
11
- const error: LogCall[] = [];
12
- const logger: FireAndForgetLogger = {
13
- error: (fields: Record<string, unknown>, message: string): void => {
14
- error.push({ fields, message });
15
- },
16
- };
17
- return { error, logger };
18
- };
19
-
20
- describe("fireAndForget", () => {
21
- it("runs the work and stays quiet on success", async () => {
22
- const { error, logger } = createLoggerSpy();
23
- let ran = false;
24
-
25
- await fireAndForget(
26
- async () => {
27
- ran = true;
28
- },
29
- { source: "test", message: "should not log", logger },
30
- );
31
-
32
- assert.equal(ran, true);
33
- assert.equal(error.length, 0);
34
- });
35
-
36
- it("does NOT reject when the work throws", async () => {
37
- const { logger } = createLoggerSpy();
38
-
39
- await assert.doesNotReject(
40
- fireAndForget(
41
- async () => {
42
- throw new Error("boom");
43
- },
44
- { source: "test", message: "contained", logger },
45
- ),
46
- );
47
- });
48
-
49
- it("logs the failure loudly with the alertable structured fields and ids", async () => {
50
- const { error, logger } = createLoggerSpy();
51
- const econnrefused = Object.assign(new Error(""), {
52
- name: "AggregateError",
53
- code: "ECONNREFUSED",
54
- });
55
-
56
- await fireAndForget(
57
- async () => {
58
- throw econnrefused;
59
- },
60
- {
61
- source: "config_load",
62
- message: "Failed to trigger account sync on config load",
63
- ids: { accountId: "acc-a", accountConfigId: "config-1" },
64
- logger,
65
- },
66
- );
67
-
68
- assert.equal(error.length, 1);
69
- const call = error[0];
70
- if (!call) throw new Error("expected an error log");
71
- assert.equal(call.fields.alert, "sync_trigger_failed");
72
- assert.equal(call.fields.source, "config_load");
73
- assert.equal(call.fields.accountId, "acc-a");
74
- assert.equal(call.fields.accountConfigId, "config-1");
75
- assert.equal(call.fields.errorName, "AggregateError");
76
- assert.equal(call.fields.errorCode, "ECONNREFUSED");
77
- assert.equal(call.message, "Failed to trigger account sync on config load");
78
- });
79
-
80
- it("reads the SDK error code from the .Code field when present", async () => {
81
- const { error, logger } = createLoggerSpy();
82
- const sdkError = Object.assign(new Error("boom"), {
83
- name: "QueueDoesNotExist",
84
- Code: "AWS.SimpleQueueService.NonExistentQueue",
85
- });
86
-
87
- await fireAndForget(
88
- async () => {
89
- throw sdkError;
90
- },
91
- { source: "test", message: "contained", logger },
92
- );
93
-
94
- const call = error[0];
95
- if (!call) throw new Error("expected an error log");
96
- assert.equal(
97
- call.fields.errorCode,
98
- "AWS.SimpleQueueService.NonExistentQueue",
99
- );
100
- });
101
-
102
- it("never leaks an unhandled rejection even when void-fired", async () => {
103
- const leaked: unknown[] = [];
104
- const onUnhandled = (reason: unknown): void => {
105
- leaked.push(reason);
106
- };
107
- process.on("unhandledRejection", onUnhandled);
108
-
109
- try {
110
- // Fire-and-forget exactly as a request handler does: no await.
111
- void fireAndForget(
112
- async () => {
113
- throw Object.assign(new Error(""), { code: "ECONNREFUSED" });
114
- },
115
- { source: "test", message: "contained", logger: { error: () => {} } },
116
- );
117
- // Let the microtask queue drain so any escaped rejection would surface.
118
- await new Promise((resolve) => setImmediate(resolve));
119
- await new Promise((resolve) => setImmediate(resolve));
120
- } finally {
121
- process.off("unhandledRejection", onUnhandled);
122
- }
123
-
124
- assert.equal(leaked.length, 0);
125
- });
126
- });
@@ -1,79 +0,0 @@
1
- import { inspect } from "node:util";
2
- import { logger } from "@remit/logger-lambda";
3
-
4
- type StructuredLog = (fields: Record<string, unknown>, message: string) => void;
5
-
6
- export interface FireAndForgetLogger {
7
- error: StructuredLog;
8
- }
9
-
10
- export interface FireAndForgetContext {
11
- /**
12
- * Stable discriminator for the originating call site (e.g. `"config_load"`,
13
- * `"account_create"`). Surfaces on the log line as `source` so a CloudWatch
14
- * metric filter / alarm can attribute a leak to its origin.
15
- */
16
- source: string;
17
- /**
18
- * Message logged when the background work rejects. Should describe the work
19
- * and reassure that the failure is contained (e.g. "...best-effort").
20
- */
21
- message: string;
22
- /**
23
- * Identifiers carried onto the structured error line (accountId,
24
- * accountConfigId, …) so a failure is traceable without a stack dive.
25
- */
26
- ids?: Record<string, unknown>;
27
- /**
28
- * Alertable discriminator on the structured error line, the field CloudWatch
29
- * metric filters key on. Defaults to `"sync_trigger_failed"` for the original
30
- * sync-kick sites; a different fire-and-forget surface (e.g. a flag enqueue)
31
- * must pass its own so it is not mis-attributed to the sync-trigger alarm.
32
- */
33
- alert?: string;
34
- logger?: FireAndForgetLogger;
35
- }
36
-
37
- /**
38
- * Run unawaited background work that MUST NOT be able to fail the request that
39
- * spawned it.
40
- *
41
- * The dev/Lambda runtime shares one event loop across concurrent requests, so a
42
- * rejection escaping an unawaited background promise (a `void trigger()`) lands
43
- * on whatever request happens to be in flight when the deferred work finally
44
- * fails — turning an unrelated READ into a spurious 500. That is exactly how an
45
- * unreachable SQS queue 500'd `/mailboxes`, `/threads`, `/outbox` and `/config`.
46
- *
47
- * This helper is the single containment point: it awaits the work inside a
48
- * try/catch, logs every rejection LOUDLY with the alertable structured fields
49
- * (`alert` — defaulting to `"sync_trigger_failed"`, `source`, the SDK error
50
- * name/code, the caller ids), and ALWAYS resolves to void. Callers `void
51
- * fireAndForget(...)` with no
52
- * chance of an unhandled rejection escaping. Routing all fire-and-forget sites
53
- * through here keeps the containment from regressing one handler at a time.
54
- *
55
- * Use this ONLY for genuine side effects whose failure must not change the
56
- * response. A failure the caller is responsible for (the real DynamoDB read, an
57
- * enqueue that IS the request's purpose) must still propagate and 500.
58
- */
59
- export const fireAndForget = async (
60
- work: () => Promise<unknown>,
61
- context: FireAndForgetContext,
62
- ): Promise<void> => {
63
- await work().catch((error: unknown) => {
64
- const log = context.logger ?? logger;
65
- log.error(
66
- {
67
- alert: context.alert ?? "sync_trigger_failed",
68
- source: context.source,
69
- ...context.ids,
70
- errorName: (error as { name?: string })?.name,
71
- errorCode:
72
- (error as { Code?: string })?.Code ??
73
- (error as { code?: string })?.code,
74
- error: inspect(error),
75
- },
76
- context.message,
77
- );
78
- });
79
- };
@@ -1,36 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { describe, it } from "node:test";
4
- import { fileURLToPath } from "node:url";
5
-
6
- const repoRoot = fileURLToPath(new URL("../../../../", import.meta.url));
7
-
8
- const parseEnvFile = (relativePath: string): Record<string, string> => {
9
- const contents = readFileSync(`${repoRoot}${relativePath}`, "utf8");
10
- const entries: Record<string, string> = {};
11
- for (const line of contents.split("\n")) {
12
- const trimmed = line.trim();
13
- if (!trimmed || trimmed.startsWith("#")) continue;
14
- const eq = trimmed.indexOf("=");
15
- if (eq === -1) continue;
16
- entries[trimmed.slice(0, eq)] = trimmed.slice(eq + 1);
17
- }
18
- return entries;
19
- };
20
-
21
- describe("local dev SQS routing (localhost-dev-aws.env)", {
22
- // localhost-dev-aws.env is AWS-local-dev config, stripped from the open-core
23
- // tree; skip where it is absent.
24
- skip: !existsSync(`${repoRoot}localhost-dev-aws.env`),
25
- }, () => {
26
- const env = parseEnvFile("localhost-dev-aws.env");
27
-
28
- it("publishes backend writes to the FIFO mailboxes queue the sync consumer drains", () => {
29
- // Prod wires the API's SQS_QUEUE_URL to the mailboxes FIFO queue
30
- // (infra remit-api-stack.ts). Onboarding publishes SYNC_MAILBOXES there;
31
- // the imap worker consumes it. Local dev must mirror that or onboarding
32
- // events land in a queue nothing drains and accounts never sync.
33
- assert.equal(env.SQS_QUEUE_URL, env.SQS_QUEUE_URL_MAILBOXES);
34
- assert.ok(env.SQS_QUEUE_URL?.endsWith(".fifo"));
35
- });
36
- });
@@ -1,98 +0,0 @@
1
- import { SendMessageCommand } from "@aws-sdk/client-sqs";
2
- import type {
3
- CreateOrganizeJobRequestInput,
4
- OrganizeJobRequestItem,
5
- } from "@remit/data-ports";
6
- import { FilterMatchOperator } from "@remit/domain-enums";
7
- import { logger } from "@remit/logger-lambda";
8
- import {
9
- DEFAULT_SEMANTIC_MATCH_THRESHOLD,
10
- NO_ACTION,
11
- } from "@remit/mailbox-service";
12
- import { env } from "expect-env";
13
- import type { RemitClient } from "./data-client.js";
14
- import type { OrganizePredicate } from "./organize.js";
15
- import { sqsClient } from "./sqs.js";
16
-
17
- /**
18
- * How long a finished (or abandoned) job row lingers before the table-wide TTL
19
- * reclaims it (RFC 034 Decision 1). A back-apply runs once and needs no standing
20
- * lifetime; a week is ample for a client to poll the result.
21
- */
22
- const ORGANIZE_JOB_TTL_SECONDS = 7 * 24 * 60 * 60;
23
-
24
- interface JobOwner {
25
- accountConfigId: string;
26
- userId: string;
27
- }
28
-
29
- /**
30
- * An "all like these" pass carries its predicate; a filter job names only the
31
- * filter, which the worker reads when it runs.
32
- */
33
- export type OrganizeJobRequest = JobOwner &
34
- ({ predicate: OrganizePredicate } | { filterId: string });
35
-
36
- /** A filter job's row: the filter id, every predicate column at its sentinel. */
37
- const filterJobColumns = (
38
- filterId: string,
39
- ): Omit<CreateOrganizeJobRequestInput, keyof JobOwner | "ttl"> => ({
40
- filterId,
41
- anchorMessageId: NO_ACTION,
42
- matchOperator: FilterMatchOperator.And,
43
- literalClauses: [],
44
- similarityThreshold: DEFAULT_SEMANTIC_MATCH_THRESHOLD,
45
- actionLabelId: NO_ACTION,
46
- actionMailboxId: NO_ACTION,
47
- });
48
-
49
- const predicateColumns = (
50
- predicate: OrganizePredicate,
51
- ): Omit<CreateOrganizeJobRequestInput, keyof JobOwner | "ttl"> => ({
52
- filterId: NO_ACTION,
53
- ...predicate,
54
- });
55
-
56
- /**
57
- * Write a Pending job row and hand it to the account-fanout worker. Every
58
- * back-apply goes through here: "all like these", a new filter's first pass,
59
- * and Run now on a saved filter.
60
- */
61
- export const queueOrganizeJob = async (
62
- client: Pick<RemitClient, "organizeJobRequest">,
63
- request: OrganizeJobRequest,
64
- ): Promise<OrganizeJobRequestItem> => {
65
- const { accountConfigId, userId } = request;
66
- const columns =
67
- "filterId" in request
68
- ? filterJobColumns(request.filterId)
69
- : predicateColumns(request.predicate);
70
- const job = await client.organizeJobRequest.create({
71
- accountConfigId,
72
- userId,
73
- ...columns,
74
- ttl: Math.floor(Date.now() / 1000) + ORGANIZE_JOB_TTL_SECONDS,
75
- });
76
-
77
- await sqsClient.send(
78
- new SendMessageCommand({
79
- QueueUrl: env.SQS_QUEUE_URL_ACCOUNT_FANOUT,
80
- MessageBody: JSON.stringify({
81
- type: "OrganizeJob",
82
- accountConfigId,
83
- organizeJobId: job.organizeJobId,
84
- }),
85
- }),
86
- );
87
-
88
- // biome-ignore lint/plugin/no-logger-info: a back-apply is an audit-grade signal
89
- logger.info(
90
- {
91
- accountConfigId,
92
- organizeJobId: job.organizeJobId,
93
- filterId: job.filterId,
94
- },
95
- "Organize back-apply job initiated",
96
- );
97
- return job;
98
- };