@remit/backend 0.0.144 → 0.0.146

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 (91) hide show
  1. package/dist/src/api.d.ts +0 -2
  2. package/dist/src/api.js +9 -80
  3. package/dist/src/auth.d.ts +13 -19
  4. package/dist/src/auth.js +51 -43
  5. package/dist/src/config/api-config.d.ts +20 -0
  6. package/dist/src/config/api-config.js +1 -0
  7. package/dist/src/config/from-env.d.ts +2 -0
  8. package/dist/src/config/from-env.js +65 -0
  9. package/dist/src/config/msoauth-secrets-manager.d.ts +2 -0
  10. package/dist/src/config/msoauth-secrets-manager.js +28 -0
  11. package/dist/src/config/msoauth.d.ts +18 -29
  12. package/dist/src/config/msoauth.js +4 -28
  13. package/dist/src/derive/contentSignature.d.ts +3 -4
  14. package/dist/src/derive/contentSignature.js +4 -13
  15. package/dist/src/derive/contentUrl.d.ts +0 -13
  16. package/dist/src/derive/contentUrl.js +0 -19
  17. package/dist/src/handlers/account-oauth.d.ts +9 -3
  18. package/dist/src/handlers/account-oauth.js +197 -250
  19. package/dist/src/handlers/account.d.ts +13 -4
  20. package/dist/src/handlers/account.js +26 -39
  21. package/dist/src/handlers/address.d.ts +11 -3
  22. package/dist/src/handlers/address.js +11 -19
  23. package/dist/src/handlers/calendar-event.js +18 -26
  24. package/dist/src/handlers/calendar-feed.d.ts +2 -2
  25. package/dist/src/handlers/calendar-feed.js +24 -32
  26. package/dist/src/handlers/calendar-suggestion.js +18 -26
  27. package/dist/src/handlers/calendar.d.ts +1 -1
  28. package/dist/src/handlers/calendar.js +15 -22
  29. package/dist/src/handlers/config.d.ts +16 -6
  30. package/dist/src/handlers/config.js +23 -35
  31. package/dist/src/handlers/filter.d.ts +16 -3
  32. package/dist/src/handlers/filter.js +17 -32
  33. package/dist/src/handlers/folder-role.js +3 -6
  34. package/dist/src/handlers/index.d.ts +187 -2
  35. package/dist/src/handlers/index.js +6 -4
  36. package/dist/src/handlers/label.js +15 -22
  37. package/dist/src/handlers/mailbox.d.ts +20 -4
  38. package/dist/src/handlers/mailbox.js +18 -26
  39. package/dist/src/handlers/me.d.ts +10 -2
  40. package/dist/src/handlers/me.js +26 -47
  41. package/dist/src/handlers/message.d.ts +78 -6
  42. package/dist/src/handlers/message.js +34 -46
  43. package/dist/src/handlers/organize.d.ts +10 -4
  44. package/dist/src/handlers/organize.js +11 -20
  45. package/dist/src/handlers/outbox-attachment.d.ts +6 -5
  46. package/dist/src/handlers/outbox-attachment.js +11 -15
  47. package/dist/src/handlers/outbox.d.ts +25 -4
  48. package/dist/src/handlers/outbox.js +27 -35
  49. package/dist/src/handlers/sync.d.ts +19 -6
  50. package/dist/src/handlers/sync.js +13 -22
  51. package/dist/src/handlers/system-update.js +14 -27
  52. package/dist/src/handlers/thread.js +9 -14
  53. package/dist/src/handlers/unified-threads.js +3 -6
  54. package/dist/src/index.d.ts +4 -1
  55. package/dist/src/index.js +18 -2
  56. package/dist/src/jwt-auth.d.ts +9 -12
  57. package/dist/src/jwt-auth.js +19 -71
  58. package/dist/src/lambda.d.ts +13 -0
  59. package/dist/src/lambda.js +33 -0
  60. package/dist/src/request.d.ts +8 -2
  61. package/dist/src/request.js +16 -6
  62. package/dist/src/response.d.ts +20 -3
  63. package/dist/src/response.js +15 -22
  64. package/dist/src/serve.d.ts +31 -0
  65. package/dist/src/serve.js +117 -0
  66. package/dist/src/service/account-deletion.d.ts +2 -4
  67. package/dist/src/service/account-deletion.js +3 -9
  68. package/dist/src/service/compose-sqlite.d.ts +2 -1
  69. package/dist/src/service/compose-sqlite.js +5 -3
  70. package/dist/src/service/create-remit-client.d.ts +25 -11
  71. package/dist/src/service/create-remit-client.js +97 -116
  72. package/dist/src/service/data-client.js +3 -5
  73. package/dist/src/service/export-identity.d.ts +2 -1
  74. package/dist/src/service/export-identity.js +4 -6
  75. package/dist/src/service/fire-and-forget.js +1 -2
  76. package/dist/src/service/organize.d.ts +7 -10
  77. package/dist/src/service/organize.js +10 -27
  78. package/dist/src/service/queues.d.ts +12 -0
  79. package/dist/src/service/queues.js +1 -0
  80. package/dist/src/service/shared-deps.d.ts +6 -0
  81. package/dist/src/service/shared-deps.js +26 -0
  82. package/dist/src/service/trigger-sync.d.ts +11 -5
  83. package/dist/src/service/trigger-sync.js +10 -18
  84. package/dist/src/types.d.ts +24 -1
  85. package/package.json +24 -8
  86. package/dist/src/data-backend.d.ts +0 -12
  87. package/dist/src/data-backend.js +0 -12
  88. package/dist/src/request-context.d.ts +0 -9
  89. package/dist/src/request-context.js +0 -22
  90. package/dist/src/service/sqs.d.ts +0 -1
  91. package/dist/src/service/sqs.js +0 -3
@@ -1,9 +1,6 @@
1
1
  import { BadRequestError, NotFoundError } from "@remit/data-ports/errors";
2
2
  import { logger } from "@remit/logger";
3
3
  import { ownedAccount, queueOrganizeJob, } from "@remit/mailbox-service";
4
- import { env } from "expect-env";
5
- import { getAccountConfigIdFromEvent, getSubFromEvent } from "../auth.js";
6
- import { getClient } from "../service/data-client.js";
7
4
  import { buildOrganizeMatchDeps, matchOrganize, ORGANIZE_MATCH_LIMIT, organizePredicateRejection, } from "../service/organize.js";
8
5
  import { unwrap } from "../service/refusal.js";
9
6
  /**
@@ -55,20 +52,16 @@ const requestedJob = async (client, owner, input) => {
55
52
  return { ...owner, predicate };
56
53
  };
57
54
  export const OrganizeOperations = {
58
- OrganizeOperations_createOrganizeJob: async (context, ...args) => {
59
- const event = args[0];
60
- const accountConfigId = getAccountConfigIdFromEvent(event);
55
+ OrganizeOperations_createOrganizeJob: async (context, ctx) => {
56
+ const accountConfigId = ctx.identity.accountConfigId;
61
57
  const { accountId } = context.request.params;
62
58
  const input = context.request.requestBody;
63
- const userId = getSubFromEvent(event);
64
- if (!userId) {
65
- throw new Error("Missing Cognito `sub`: cannot attribute an organize job to a user");
66
- }
67
- const client = await getClient();
59
+ const userId = ctx.identity.sub;
60
+ const client = ctx.client;
68
61
  await assertAccount(client, accountId, accountConfigId, "act");
69
62
  const job = await queueOrganizeJob({
70
63
  organizeJobRequest: client.organizeJobRequest,
71
- sqsQueueUrl: env.SQS_QUEUE_URL_ACCOUNT_FANOUT,
64
+ queue: client.queues.accountFanout,
72
65
  logger,
73
66
  }, await requestedJob(client, { accountConfigId, userId }, input));
74
67
  return {
@@ -77,12 +70,11 @@ export const OrganizeOperations = {
77
70
  state: job.state,
78
71
  };
79
72
  },
80
- OrganizeOperations_previewOrganize: async (context, ...args) => {
81
- const event = args[0];
82
- const accountConfigId = getAccountConfigIdFromEvent(event);
73
+ OrganizeOperations_previewOrganize: async (context, ctx) => {
74
+ const accountConfigId = ctx.identity.accountConfigId;
83
75
  const { accountId } = context.request.params;
84
76
  const input = context.request.requestBody;
85
- const client = await getClient();
77
+ const client = ctx.client;
86
78
  await assertAccount(client, accountId, accountConfigId, "read");
87
79
  if (input.filterId) {
88
80
  throw new BadRequestError("Preview takes a rule, not a saved filter. Run the filter to apply it.");
@@ -97,11 +89,10 @@ export const OrganizeOperations = {
97
89
  },
98
90
  };
99
91
  export const OrganizeJobDetailOperations = {
100
- OrganizeJobDetailOperations_getOrganizeJob: async (context, ...args) => {
101
- const event = args[0];
102
- const accountConfigId = getAccountConfigIdFromEvent(event);
92
+ OrganizeJobDetailOperations_getOrganizeJob: async (context, ctx) => {
93
+ const accountConfigId = ctx.identity.accountConfigId;
103
94
  const { accountId, organizeJobId } = context.request.params;
104
- const client = await getClient();
95
+ const client = ctx.client;
105
96
  await assertAccount(client, accountId, accountConfigId, "read");
106
97
  const job = await client.organizeJobRequest.get(organizeJobId);
107
98
  if (job.accountConfigId !== accountConfigId) {
@@ -1,8 +1,10 @@
1
1
  import type { MintOutboxAttachmentResponse, OutboxAttachmentRejection, OutboxAttachmentResponse } from "@remit/api-openapi-types";
2
2
  import type { OutboxAttachmentItem } from "@remit/data-ports";
3
3
  import type { Context } from "openapi-backend";
4
- export declare const toOutboxAttachmentResponse: (item: OutboxAttachmentItem) => OutboxAttachmentResponse;
5
- interface RejectionResponse {
4
+ import type { ApiConfig } from "../config/api-config.js";
5
+ import type { HandlerContext } from "../types.js";
6
+ export declare const toOutboxAttachmentResponse: (config: ApiConfig) => (item: OutboxAttachmentItem) => OutboxAttachmentResponse;
7
+ export interface RejectionResponse {
6
8
  statusCode: number;
7
9
  body: OutboxAttachmentRejection;
8
10
  }
@@ -12,12 +14,11 @@ interface RejectionResponse {
12
14
  * lets the upload live on its own fleet and keeps this operation under any
13
15
  * payload ceiling the REST tier has.
14
16
  */
15
- export declare const mintOutboxAttachment: (context: Context, ...args: unknown[]) => Promise<MintOutboxAttachmentResponse | RejectionResponse>;
17
+ export declare const mintOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<MintOutboxAttachmentResponse | RejectionResponse>;
16
18
  /**
17
19
  * Confirm the upload landed. The size in the response is read back from
18
20
  * storage — on a deployment where the browser PUT straight to block storage
19
21
  * this call is the first the API hears of the bytes, and the client's word for
20
22
  * how many there are is not evidence.
21
23
  */
22
- export declare const completeOutboxAttachment: (context: Context, ...args: unknown[]) => Promise<OutboxAttachmentResponse | RejectionResponse>;
23
- export {};
24
+ export declare const completeOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<OutboxAttachmentResponse | RejectionResponse>;
@@ -1,9 +1,7 @@
1
1
  import { OutboxAttachmentRejectionReason } from "@remit/domain-enums";
2
2
  import { outboxAttachmentContentId } from "@remit/mailbox-service/outbox-attachment-content";
3
- import { getAccountConfigIdFromEvent } from "../auth.js";
4
3
  import { getContentSigner } from "../derive/contentSignature.js";
5
- import { buildStorageContentUrl, getContentDeliveryDomain, } from "../derive/contentUrl.js";
6
- import { getClient } from "../service/data-client.js";
4
+ import { buildStorageContentUrl } from "../derive/contentUrl.js";
7
5
  // Every error body the API emits carries a `code` (issue #371). A refusal here
8
6
  // is one kind, so the code is one value and `reason` says which refusal it was.
9
7
  const ATTACHMENT_REJECTED_CODE = "attachment_rejected";
@@ -12,7 +10,7 @@ const TOO_LARGE = new Set([
12
10
  OutboxAttachmentRejectionReason.MessageTooLarge,
13
11
  OutboxAttachmentRejectionReason.TooManyAttachments,
14
12
  ]);
15
- export const toOutboxAttachmentResponse = (item) => ({
13
+ export const toOutboxAttachmentResponse = (config) => (item) => ({
16
14
  outboxAttachmentId: item.outboxAttachmentId,
17
15
  outboxMessageId: item.outboxMessageId,
18
16
  filename: item.filename,
@@ -21,9 +19,9 @@ export const toOutboxAttachmentResponse = (item) => ({
21
19
  state: item.state,
22
20
  contentId: outboxAttachmentContentId(item.outboxAttachmentId),
23
21
  contentUrl: buildStorageContentUrl({
24
- domain: getContentDeliveryDomain(),
22
+ domain: config.contentDeliveryDomain,
25
23
  storageKey: item.storageKey,
26
- sign: getContentSigner(),
24
+ sign: getContentSigner(config),
27
25
  }),
28
26
  });
29
27
  const refuse = (detail) => ({
@@ -42,12 +40,11 @@ const refuse = (detail) => ({
42
40
  * lets the upload live on its own fleet and keeps this operation under any
43
41
  * payload ceiling the REST tier has.
44
42
  */
45
- export const mintOutboxAttachment = async (context, ...args) => {
46
- const event = args[0];
47
- const accountConfigId = getAccountConfigIdFromEvent(event);
43
+ export const mintOutboxAttachment = async (context, ctx) => {
44
+ const accountConfigId = ctx.identity.accountConfigId;
48
45
  const { outboxMessageId } = context.request.params;
49
46
  const input = context.request.requestBody;
50
- const client = await getClient();
47
+ const client = ctx.client;
51
48
  const result = await client.outboxAttachment.mint({
52
49
  accountConfigId,
53
50
  outboxMessageId,
@@ -65,11 +62,10 @@ export const mintOutboxAttachment = async (context, ...args) => {
65
62
  * this call is the first the API hears of the bytes, and the client's word for
66
63
  * how many there are is not evidence.
67
64
  */
68
- export const completeOutboxAttachment = async (context, ...args) => {
69
- const event = args[0];
70
- const accountConfigId = getAccountConfigIdFromEvent(event);
65
+ export const completeOutboxAttachment = async (context, ctx) => {
66
+ const accountConfigId = ctx.identity.accountConfigId;
71
67
  const { outboxMessageId, outboxAttachmentId } = context.request.params;
72
- const client = await getClient();
68
+ const client = ctx.client;
73
69
  const result = await client.outboxAttachment.complete({
74
70
  accountConfigId,
75
71
  outboxMessageId,
@@ -77,5 +73,5 @@ export const completeOutboxAttachment = async (context, ...args) => {
77
73
  });
78
74
  if (result.outcome === "Rejected")
79
75
  return refuse(result.rejection);
80
- return toOutboxAttachmentResponse(result.attachment);
76
+ return toOutboxAttachmentResponse(ctx.config)(result.attachment);
81
77
  };
@@ -1,4 +1,25 @@
1
- import type { OperationHandler, OutboxAttachmentOperationIds, OutboxDetailOperationIds, OutboxOperationIds } from "../types.js";
2
- export declare const OutboxOperations: Record<OutboxOperationIds, OperationHandler<OutboxOperationIds>>;
3
- export declare const OutboxDetailOperations: Record<OutboxDetailOperationIds, OperationHandler<OutboxDetailOperationIds>>;
4
- export declare const OutboxAttachmentOperations: Record<OutboxAttachmentOperationIds, OperationHandler<OutboxAttachmentOperationIds>>;
1
+ import type { OutboxMessageResponse } from "@remit/api-openapi-types";
2
+ import type { Context } from "openapi-backend";
3
+ import type { HandlerContext } from "../types.js";
4
+ export declare const OutboxOperations: {
5
+ OutboxOperations_createOutboxMessage: (context: Context, ctx: HandlerContext<"smtp">) => Promise<OutboxMessageResponse>;
6
+ OutboxOperations_listOutboxMessages: (context: Context, ctx: HandlerContext) => Promise<{
7
+ items: never[];
8
+ continuationToken: null;
9
+ } | {
10
+ items: import("@remit/api-openapi-types").Components.Schemas.OutboxMessageResponse[];
11
+ continuationToken: string | undefined;
12
+ }>;
13
+ };
14
+ export declare const OutboxDetailOperations: {
15
+ OutboxDetailOperations_getOutboxMessage: (context: Context, ctx: HandlerContext) => Promise<OutboxMessageResponse>;
16
+ OutboxDetailOperations_updateOutboxMessage: (context: Context, ctx: HandlerContext<"smtp">) => Promise<OutboxMessageResponse>;
17
+ OutboxDetailOperations_deleteOutboxMessage: (context: Context, ctx: HandlerContext<"smtp">) => Promise<{
18
+ statusCode: number;
19
+ }>;
20
+ OutboxDetailOperations_sendOutboxMessage: (context: Context, ctx: HandlerContext<"smtp">) => Promise<OutboxMessageResponse>;
21
+ OutboxDetailOperations_mintOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<import("@remit/api-openapi-types").MintOutboxAttachmentResponse | import("./outbox-attachment.js").RejectionResponse>;
22
+ };
23
+ export declare const OutboxAttachmentOperations: {
24
+ OutboxAttachmentOperations_completeOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<import("@remit/api-openapi-types").OutboxAttachmentResponse | import("./outbox-attachment.js").RejectionResponse>;
25
+ };
@@ -1,9 +1,7 @@
1
1
  import { ForbiddenError } from "@remit/data-ports/errors";
2
- import { getAccountConfigIdFromEvent } from "../auth.js";
3
- import { getClient } from "../service/data-client.js";
4
2
  import { completeOutboxAttachment, mintOutboxAttachment, toOutboxAttachmentResponse, } from "./outbox-attachment.js";
5
- const toOutboxMessageResponse = (item, attachments = []) => ({
6
- attachments: attachments.map(toOutboxAttachmentResponse),
3
+ const toOutboxMessageResponse = (config, item, attachments = []) => ({
4
+ attachments: attachments.map(toOutboxAttachmentResponse(config)),
7
5
  outboxMessageId: item.outboxMessageId,
8
6
  accountId: item.accountId,
9
7
  fromAddress: item.fromAddress,
@@ -23,11 +21,10 @@ const toOutboxMessageResponse = (item, attachments = []) => ({
23
21
  updatedAt: item.updatedAt,
24
22
  });
25
23
  export const OutboxOperations = {
26
- OutboxOperations_createOutboxMessage: async (_context, ...args) => {
27
- const event = args[0];
28
- const accountConfigId = getAccountConfigIdFromEvent(event);
29
- const input = JSON.parse(event.body ?? "{}");
30
- const client = await getClient();
24
+ OutboxOperations_createOutboxMessage: async (context, ctx) => {
25
+ const accountConfigId = ctx.identity.accountConfigId;
26
+ const input = context.request.requestBody;
27
+ const client = ctx.client;
31
28
  const account = await client.account.get(input.accountId);
32
29
  if (account.accountConfigId !== accountConfigId) {
33
30
  throw new ForbiddenError(`Account ${input.accountId} not in account config`);
@@ -47,7 +44,7 @@ export const OutboxOperations = {
47
44
  inReplyTo: input.inReplyTo,
48
45
  references: input.references,
49
46
  });
50
- return toOutboxMessageResponse(outbox);
47
+ return toOutboxMessageResponse(ctx.config, outbox);
51
48
  }
52
49
  const outbox = await client.outboxQueue.createDraft({
53
50
  accountId: input.accountId,
@@ -62,41 +59,38 @@ export const OutboxOperations = {
62
59
  inReplyTo: input.inReplyTo,
63
60
  references: input.references,
64
61
  });
65
- return toOutboxMessageResponse(outbox);
62
+ return toOutboxMessageResponse(ctx.config, outbox);
66
63
  },
67
- OutboxOperations_listOutboxMessages: async (context, ...args) => {
68
- const event = args[0];
69
- const accountConfigId = getAccountConfigIdFromEvent(event);
64
+ OutboxOperations_listOutboxMessages: async (context, ctx) => {
65
+ const accountConfigId = ctx.identity.accountConfigId;
70
66
  const { continuationToken } = context.request.query;
71
- const client = await getClient();
67
+ const client = ctx.client;
72
68
  const accounts = await client.account.listAllByAccountConfig(accountConfigId);
73
69
  if (accounts.length === 0) {
74
70
  return { items: [], continuationToken: null };
75
71
  }
76
72
  const result = await client.outboxMessage.listByAccounts(accounts.map((account) => account.accountId), { continuationToken });
77
73
  return {
78
- items: result.items.map((item) => toOutboxMessageResponse(item)),
74
+ items: result.items.map((item) => toOutboxMessageResponse(ctx.config, item)),
79
75
  continuationToken: result.continuationToken,
80
76
  };
81
77
  },
82
78
  };
83
79
  export const OutboxDetailOperations = {
84
- OutboxDetailOperations_getOutboxMessage: async (context, ...args) => {
85
- const event = args[0];
86
- const accountConfigId = getAccountConfigIdFromEvent(event);
80
+ OutboxDetailOperations_getOutboxMessage: async (context, ctx) => {
81
+ const accountConfigId = ctx.identity.accountConfigId;
87
82
  const { outboxMessageId } = context.request.params;
88
- const client = await getClient();
83
+ const client = ctx.client;
89
84
  // Default mode "read": the scoped get refuses a foreign message with
90
85
  // NotFound (404, no existence leak) — no separate check needed.
91
86
  const outbox = await client.outboxMessage.get(accountConfigId, outboxMessageId);
92
- return toOutboxMessageResponse(outbox, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
87
+ return toOutboxMessageResponse(ctx.config, outbox, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
93
88
  },
94
- OutboxDetailOperations_updateOutboxMessage: async (context, ...args) => {
95
- const event = args[0];
96
- const accountConfigId = getAccountConfigIdFromEvent(event);
89
+ OutboxDetailOperations_updateOutboxMessage: async (context, ctx) => {
90
+ const accountConfigId = ctx.identity.accountConfigId;
97
91
  const { outboxMessageId } = context.request.params;
98
92
  const input = context.request.requestBody;
99
- const client = await getClient();
93
+ const client = ctx.client;
100
94
  // updateDraft's own scoped get uses mode "act": a foreign message is
101
95
  // denied with Forbidden (403), not feigned as NotFound.
102
96
  const updated = await client.outboxQueue.updateDraft(accountConfigId, outboxMessageId, {
@@ -115,27 +109,25 @@ export const OutboxDetailOperations = {
115
109
  if (input.attachmentIds !== undefined) {
116
110
  await client.outboxAttachment.retainOnly(accountConfigId, updated.accountId, outboxMessageId, input.attachmentIds);
117
111
  }
118
- return toOutboxMessageResponse(updated, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
112
+ return toOutboxMessageResponse(ctx.config, updated, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
119
113
  },
120
- OutboxDetailOperations_deleteOutboxMessage: async (context, ...args) => {
121
- const event = args[0];
122
- const accountConfigId = getAccountConfigIdFromEvent(event);
114
+ OutboxDetailOperations_deleteOutboxMessage: async (context, ctx) => {
115
+ const accountConfigId = ctx.identity.accountConfigId;
123
116
  const { outboxMessageId } = context.request.params;
124
- const client = await getClient();
117
+ const client = ctx.client;
125
118
  // deleteDraft's own scoped get uses mode "act": a foreign message is
126
119
  // denied with Forbidden (403), not feigned as NotFound.
127
120
  await client.outboxQueue.deleteDraft(accountConfigId, outboxMessageId);
128
121
  return { statusCode: 204 };
129
122
  },
130
- OutboxDetailOperations_sendOutboxMessage: async (context, ...args) => {
131
- const event = args[0];
132
- const accountConfigId = getAccountConfigIdFromEvent(event);
123
+ OutboxDetailOperations_sendOutboxMessage: async (context, ctx) => {
124
+ const accountConfigId = ctx.identity.accountConfigId;
133
125
  const { outboxMessageId } = context.request.params;
134
- const client = await getClient();
126
+ const client = ctx.client;
135
127
  // send's own scoped get uses mode "act": a foreign message is denied
136
128
  // with Forbidden (403), not feigned as NotFound.
137
129
  const sent = await client.outboxQueue.send(accountConfigId, outboxMessageId);
138
- return toOutboxMessageResponse(sent);
130
+ return toOutboxMessageResponse(ctx.config, sent);
139
131
  },
140
132
  OutboxDetailOperations_mintOutboxAttachment: mintOutboxAttachment,
141
133
  };
@@ -1,9 +1,9 @@
1
- import type { SQSClient } from "@aws-sdk/client-sqs";
1
+ import type { FifoQueueProducer } from "@remit/data-ports";
2
2
  import { type FireAndForgetLogger } from "../service/fire-and-forget.js";
3
- import type { OperationHandler, SyncOperationIds } from "../types.js";
3
+ import { type SyncMailboxesEvent } from "../service/trigger-sync.js";
4
+ import type { HandlerContext } from "../types.js";
4
5
  interface SyncTriggerDeps {
5
- sqsClient: SQSClient;
6
- queueUrl: string;
6
+ queue: FifoQueueProducer<SyncMailboxesEvent>;
7
7
  logger: FireAndForgetLogger & {
8
8
  info: (fields: Record<string, unknown>, message: string) => void;
9
9
  };
@@ -23,6 +23,19 @@ interface SyncTriggerDeps {
23
23
  * loudly with the alertable structured fields, and resolves to void. A genuine
24
24
  * account-lookup / ownership failure still propagates and 500s as normal.
25
25
  */
26
- export declare const triggerSyncSafe: (accountId: string, accountConfigId: string, deps?: SyncTriggerDeps) => Promise<void>;
27
- export declare const SyncOperations: Record<SyncOperationIds, OperationHandler<SyncOperationIds>>;
26
+ export declare const triggerSyncSafe: (accountId: string, accountConfigId: string, deps: SyncTriggerDeps) => Promise<void>;
27
+ export declare const SyncOperations: {
28
+ SyncOperations_triggerSync: (context: import("openapi-backend").Context<any, any, any, any, any, import("openapi-backend").Document>, ctx: HandlerContext<"sync">) => Promise<{
29
+ triggered: boolean;
30
+ message: string;
31
+ }>;
32
+ SyncOperations_getSyncStatus: (context: import("openapi-backend").Context<any, any, any, any, any, import("openapi-backend").Document>, ctx: HandlerContext) => Promise<{
33
+ accountId: string;
34
+ syncPhase: "error" | "idle" | "discovering_mailboxes" | "syncing_inbox" | "syncing_others" | "complete" | undefined;
35
+ mailboxCountTotal: number | undefined;
36
+ mailboxCountSynced: number | undefined;
37
+ lastSyncAt: number | undefined;
38
+ mailboxes: import("./sync-progress.js").MailboxSyncProgress[];
39
+ }>;
40
+ };
28
41
  export {};
@@ -1,18 +1,9 @@
1
1
  import { logger } from "@remit/logger";
2
2
  import { ownedAccount } from "@remit/mailbox-service";
3
- import { env } from "expect-env";
4
- import { getAccountConfigIdFromEvent } from "../auth.js";
5
- import { getClient } from "../service/data-client.js";
6
3
  import { fireAndForget, } from "../service/fire-and-forget.js";
7
4
  import { unwrap } from "../service/refusal.js";
8
- import { sqsClient } from "../service/sqs.js";
9
- import { triggerAccountSync } from "../service/trigger-sync.js";
5
+ import { triggerAccountSync, } from "../service/trigger-sync.js";
10
6
  import { toMailboxSyncProgress } from "./sync-progress.js";
11
- const defaultSyncTriggerDeps = () => ({
12
- sqsClient,
13
- queueUrl: env.SQS_QUEUE_URL,
14
- logger,
15
- });
16
7
  /**
17
8
  * Fire-and-forget enqueue for the POST /sync kick.
18
9
  *
@@ -28,11 +19,10 @@ const defaultSyncTriggerDeps = () => ({
28
19
  * loudly with the alertable structured fields, and resolves to void. A genuine
29
20
  * account-lookup / ownership failure still propagates and 500s as normal.
30
21
  */
31
- export const triggerSyncSafe = async (accountId, accountConfigId, deps = defaultSyncTriggerDeps()) => {
22
+ export const triggerSyncSafe = async (accountId, accountConfigId, deps) => {
32
23
  await fireAndForget(async () => {
33
24
  const { eventId } = await triggerAccountSync({
34
- sqsClient: deps.sqsClient,
35
- queueUrl: deps.queueUrl,
25
+ queue: deps.queue,
36
26
  accountId,
37
27
  // POST /sync asks for a sync of this account by name — the refresh
38
28
  // control, pull-to-refresh, and the client's automatic poll all land
@@ -49,23 +39,24 @@ export const triggerSyncSafe = async (accountId, accountConfigId, deps = default
49
39
  });
50
40
  };
51
41
  export const SyncOperations = {
52
- SyncOperations_triggerSync: async (context, ...args) => {
53
- const event = args[0];
54
- const accountConfigId = getAccountConfigIdFromEvent(event);
42
+ SyncOperations_triggerSync: async (context, ctx) => {
43
+ const accountConfigId = ctx.identity.accountConfigId;
55
44
  const { accountId } = context.request.params;
56
- const account = await (await getClient()).account.get(accountId);
45
+ const account = await ctx.client.account.get(accountId);
57
46
  unwrap(ownedAccount(account, accountConfigId, "act"));
58
- void triggerSyncSafe(account.accountId, accountConfigId);
47
+ void triggerSyncSafe(account.accountId, accountConfigId, {
48
+ queue: ctx.client.queues.sync,
49
+ logger,
50
+ });
59
51
  return {
60
52
  triggered: true,
61
53
  message: `Sync triggered for account ${accountId}`,
62
54
  };
63
55
  },
64
- SyncOperations_getSyncStatus: async (context, ...args) => {
65
- const event = args[0];
66
- const accountConfigId = getAccountConfigIdFromEvent(event);
56
+ SyncOperations_getSyncStatus: async (context, ctx) => {
57
+ const accountConfigId = ctx.identity.accountConfigId;
67
58
  const { accountId } = context.request.params;
68
- const client = await getClient();
59
+ const client = ctx.client;
69
60
  const account = await client.account.get(accountId);
70
61
  unwrap(ownedAccount(account, accountConfigId, "read"));
71
62
  // Collect all mailboxes (read-only — does not write or contend with sync lock)
@@ -2,15 +2,11 @@ import { randomUUID } from "node:crypto";
2
2
  import { readFileSync, renameSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { ConflictError, HTTPError } from "@remit/data-ports/errors";
5
- import { getSubFromEvent } from "../auth.js";
6
5
  import { defaultErrorCode } from "../error.js";
7
6
  class TargetVersionMismatchError extends HTTPError {
8
7
  name = "TargetVersionMismatchError";
9
8
  statusCode = 422;
10
9
  }
11
- const DEFAULT_CONTROL_DIR = "/data/control";
12
- const controlDir = () => process.env.REMIT_UPDATE_CONTROL_DIR ?? DEFAULT_CONTROL_DIR;
13
- const manifestUrl = () => process.env.REMIT_UPDATE_MANIFEST_URL;
14
10
  /**
15
11
  * The running version is a fact the updater owns: it reads the tag from `.env`
16
12
  * and writes it into `state.json`, which is the only honest source across an
@@ -33,24 +29,23 @@ const refuse = (statusCode, message) => ({
33
29
  body: { code: defaultErrorCode(statusCode), message },
34
30
  });
35
31
  const notFound = () => refuse(404, "Not found");
36
- const unauthorized = () => refuse(401, "Unauthorized");
37
32
  /**
38
33
  * The self-update seam is off unless a manifest URL is configured (RFC 037 D8),
39
34
  * which the hosted deployment leaves unset. Both endpoints answer 404 in that
40
35
  * case. Where it is on, any authenticated caller may use it — the account list
41
36
  * is the trust boundary of a self-hosted instance.
42
37
  */
43
- const guardManifestConfigured = () => manifestUrl() ? null : notFound();
38
+ const guardManifestConfigured = (config) => config.selfUpdate === "Enabled" ? null : notFound();
44
39
  /**
45
40
  * Read `state.json` off the control volume. A missing file is an instance that
46
41
  * has never checked or updated (RFC 037 D8), not an error, so it reads as
47
42
  * `null`; the caller renders the disabled/empty resource. Any other read or
48
43
  * parse failure propagates.
49
44
  */
50
- const readState = () => {
45
+ const readState = (controlDir) => {
51
46
  let raw;
52
47
  try {
53
- raw = readFileSync(join(controlDir(), "state.json"), "utf8");
48
+ raw = readFileSync(join(controlDir, "state.json"), "utf8");
54
49
  }
55
50
  catch (error) {
56
51
  if (error.code === "ENOENT")
@@ -78,8 +73,7 @@ const checkedVersion = (state) => state?.check.status === "ok" ? state.check.lat
78
73
  * so the backend never writes one. The temp file sits in the same directory as
79
74
  * the target for the rename to be atomic on one filesystem.
80
75
  */
81
- const writeRequest = (request) => {
82
- const dir = controlDir();
76
+ const writeRequest = (dir, request) => {
83
77
  const tmp = join(dir, `.request.json.${request.runId}.tmp`);
84
78
  writeFileSync(tmp, JSON.stringify(request), { mode: 0o644 });
85
79
  renameSync(tmp, join(dir, "request.json"));
@@ -91,8 +85,7 @@ const writeRequest = (request) => {
91
85
  * request.json it is written atomically and carries no authority: its presence
92
86
  * is the whole message, so it is empty.
93
87
  */
94
- const writeCheckRequest = () => {
95
- const dir = controlDir();
88
+ const writeCheckRequest = (dir) => {
96
89
  const tmp = join(dir, `.check-request.json.tmp`);
97
90
  writeFileSync(tmp, JSON.stringify({}), { mode: 0o644 });
98
91
  renameSync(tmp, join(dir, "check-request.json"));
@@ -123,13 +116,10 @@ const requestedResource = (state, runId, targetVersion, requestedAt) => {
123
116
  };
124
117
  };
125
118
  export const SystemOperations = {
126
- SystemOperations_getSystemUpdate: async (context, ...args) => {
127
- const offSurface = guardManifestConfigured();
119
+ SystemOperations_getSystemUpdate: async (context, ctx) => {
120
+ const offSurface = guardManifestConfigured(ctx.config);
128
121
  if (offSurface)
129
122
  return offSurface;
130
- const event = args[0];
131
- if (!getSubFromEvent(event))
132
- return unauthorized();
133
123
  // A refresh asks for a fresh answer, and only the updater can fetch one, so
134
124
  // the request goes on the control seam for its watch loop to pick up (#599).
135
125
  // The answer is the stored state as it stands, `lastCheckedAt` included: the
@@ -137,19 +127,16 @@ export const SystemOperations = {
137
127
  // response never has to claim a verdict the updater has not reached.
138
128
  const { refresh } = context.request.query;
139
129
  if (refresh === true)
140
- writeCheckRequest();
141
- return readState() ?? emptyResource();
130
+ writeCheckRequest(ctx.config.updateControlDir);
131
+ return readState(ctx.config.updateControlDir) ?? emptyResource();
142
132
  },
143
- SystemOperations_applySystemUpdate: async (context, ...args) => {
144
- const offSurface = guardManifestConfigured();
133
+ SystemOperations_applySystemUpdate: async (context, ctx) => {
134
+ const offSurface = guardManifestConfigured(ctx.config);
145
135
  if (offSurface)
146
136
  return offSurface;
147
- const event = args[0];
148
- const sub = getSubFromEvent(event);
149
- if (!sub)
150
- return unauthorized();
137
+ const sub = ctx.identity.sub;
151
138
  const { targetVersion } = context.request.requestBody;
152
- const state = readState();
139
+ const state = readState(ctx.config.updateControlDir);
153
140
  if (isRunInFlight(state?.run)) {
154
141
  throw new ConflictError("An update is already in progress.");
155
142
  }
@@ -158,7 +145,7 @@ export const SystemOperations = {
158
145
  }
159
146
  const runId = randomUUID();
160
147
  const requestedAt = new Date().toISOString();
161
- writeRequest({
148
+ writeRequest(ctx.config.updateControlDir, {
162
149
  runId,
163
150
  targetVersion,
164
151
  requestedAt,
@@ -1,9 +1,7 @@
1
1
  import { NotFoundError } from "@remit/data-ports/errors";
2
2
  import { isValidMessageId } from "@remit/data-ports/id";
3
- import { getAccountConfigIdFromEvent } from "../auth.js";
4
3
  import { enrichThreadRows, } from "../derive/enrichThreadRows.js";
5
4
  import { filterByOffRowCriteria, hasOffRowCriteria, } from "../derive/filterThreadCriteria.js";
6
- import { getClient } from "../service/data-client.js";
7
5
  const DEFAULT_THREADS_PAGE_SIZE = 50;
8
6
  // Project to fields used by ListThreadsResponse — keep in sync with handler mapping.
9
7
  // Includes ThreadMessage table key fields (accountConfigId, threadMessageId) and
@@ -183,12 +181,11 @@ export const dedupeThreadMessages = (rows) => {
183
181
  });
184
182
  };
185
183
  export const ThreadOperations = {
186
- ThreadOperations_listThreads: async (context, ...args) => {
187
- const event = args[0];
188
- const accountConfigId = getAccountConfigIdFromEvent(event);
184
+ ThreadOperations_listThreads: async (context, ctx) => {
185
+ const accountConfigId = ctx.identity.accountConfigId;
189
186
  const { mailboxId } = context.request.params;
190
187
  const { continuationToken, order } = context.request.query;
191
- const client = await getClient();
188
+ const client = ctx.client;
192
189
  const result = await client.threadMessage.listByMailbox(accountConfigId, mailboxId, buildListThreadsOptions({ continuationToken, order }));
193
190
  const items = await enrichThreadRows(result.items, client, accountConfigId);
194
191
  return {
@@ -196,12 +193,11 @@ export const ThreadOperations = {
196
193
  continuationToken: result.continuationToken,
197
194
  };
198
195
  },
199
- ThreadOperations_searchThreads: async (context, ...args) => {
200
- const event = args[0];
201
- const accountConfigId = getAccountConfigIdFromEvent(event);
196
+ ThreadOperations_searchThreads: async (context, ctx) => {
197
+ const accountConfigId = ctx.identity.accountConfigId;
202
198
  const { mailboxId } = context.request.params;
203
199
  const raw = context.request.query;
204
- return executeThreadSearch(await getClient(), accountConfigId, mailboxId, {
200
+ return executeThreadSearch(ctx.client, accountConfigId, mailboxId, {
205
201
  ...raw,
206
202
  senderTrust: toArray(raw.senderTrust),
207
203
  category: toArray(raw.category),
@@ -209,12 +205,11 @@ export const ThreadOperations = {
209
205
  },
210
206
  };
211
207
  export const ThreadDetailOperations = {
212
- ThreadDetailOperations_listThreadMessages: async (context, ...args) => {
213
- const event = args[0];
214
- const accountConfigId = getAccountConfigIdFromEvent(event);
208
+ ThreadDetailOperations_listThreadMessages: async (context, ctx) => {
209
+ const accountConfigId = ctx.identity.accountConfigId;
215
210
  const { threadId } = context.request.params;
216
211
  const { order } = context.request.query;
217
- const client = await getClient();
212
+ const client = ctx.client;
218
213
  const result = await client.threadMessage.listByThread(threadId, accountConfigId, buildListThreadMessagesOptions({ order }));
219
214
  // Defense-in-depth: the query is already scoped to accountConfigId; assert
220
215
  // no foreign row slips into the response. `read`-class → 404, no existence
@@ -1,9 +1,7 @@
1
1
  import { isVirtualCopyMailbox } from "@remit/data-ports/virtual-copy";
2
2
  import { MailboxSpecialUse } from "@remit/domain-enums";
3
3
  import pMap from "p-map";
4
- import { getAccountConfigIdFromEvent } from "../auth.js";
5
4
  import { enrichThreadRows, } from "../derive/enrichThreadRows.js";
6
- import { getClient } from "../service/data-client.js";
7
5
  import { groupAccountOverrides, groupMailboxOverrides, } from "./account-overrides.js";
8
6
  const DEFAULT_UNIFIED_THREADS_PAGE_SIZE = 50;
9
7
  const MAILBOX_LIST_CONCURRENCY = 5;
@@ -374,9 +372,8 @@ const toBoolean = (value) => {
374
372
  return undefined;
375
373
  };
376
374
  export const UnifiedThreadOperations = {
377
- UnifiedThreadOperations_listAllThreads: async (context, ...args) => {
378
- const event = args[0];
379
- const accountConfigId = getAccountConfigIdFromEvent(event);
375
+ UnifiedThreadOperations_listAllThreads: async (context, ctx) => {
376
+ const accountConfigId = ctx.identity.accountConfigId;
380
377
  const { continuationToken, order, limit, starred, query, from, subject, accountId, mailboxId, category, unread, attachments, muted, count, results, } = context.request.query;
381
378
  // Whitespace-only text is not a search: it would widen the scope to every
382
379
  // folder while matching nothing in particular. The two field parameters
@@ -384,7 +381,7 @@ export const UnifiedThreadOperations = {
384
381
  const searchText = query?.trim();
385
382
  const fromText = from?.trim();
386
383
  const subjectText = subject?.trim();
387
- return executeUnifiedThreadListing(await getClient(), accountConfigId, {
384
+ return executeUnifiedThreadListing(ctx.client, accountConfigId, {
388
385
  continuationToken,
389
386
  order,
390
387
  limit,
@@ -1,2 +1,5 @@
1
- export { api, handleApiEvent, OpenAPISpec } from "./api.js";
1
+ export { OpenAPISpec } from "./api.js";
2
+ export { configFromEnv } from "./config/from-env.js";
3
+ export { createApiEventHandler } from "./lambda.js";
4
+ export declare const handleApiEvent: import("./lambda.js").ApiEventHandler;
2
5
  export declare const handler: (event: import("aws-lambda").APIGatewayProxyEvent, context: import("aws-lambda").Context) => Promise<import("aws-lambda").APIGatewayProxyResult>;