@remit/backend 0.0.142 → 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 -521
- 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,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
|
-
});
|
package/src/service/filter.ts
DELETED
|
@@ -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
|
-
};
|