@teamscala/service-boot 0.23.403 → 0.23.405

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/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.403",
9
+ "version": "0.23.405",
10
10
  "sideEffects": false,
11
11
  "imports": {
12
12
  "#service-boot/*": "./src/*",
@@ -42,6 +42,7 @@
42
42
  "@teamscala/publish-registry": "*",
43
43
  "@teamscala/realtime-utils": "*",
44
44
  "@teamscala/resolver-engine": "*",
45
+ "@teamscala/safe-fallback": "*",
45
46
  "@teamscala/timing": "*",
46
47
  "@teamscala/tool-executor": "*",
47
48
  "@teamscala/watchdog": "*",
@@ -373,6 +374,14 @@
373
374
  "types": "./src/fn-routers/public-gpt-session-router.d.ts",
374
375
  "default": "./src/fn-routers/public-gpt-session-router.ts"
375
376
  },
377
+ "./fn-routers/resend-client": {
378
+ "types": "./src/fn-routers/resend-client.d.ts",
379
+ "default": "./src/fn-routers/resend-client.ts"
380
+ },
381
+ "./fn-routers/resend-client.ts": {
382
+ "types": "./src/fn-routers/resend-client.d.ts",
383
+ "default": "./src/fn-routers/resend-client.ts"
384
+ },
376
385
  "./fn-routers/sign-in-flow-router": {
377
386
  "types": "./src/fn-routers/sign-in-flow-router.d.ts",
378
387
  "default": "./src/fn-routers/sign-in-flow-router.ts"
@@ -421,6 +430,14 @@
421
430
  "types": "./src/handlers/bun-stable-watch.d.ts",
422
431
  "default": "./src/handlers/bun-stable-watch.ts"
423
432
  },
433
+ "./handlers/messaging-send-reconcile-logic": {
434
+ "types": "./src/handlers/messaging-send-reconcile-logic.d.ts",
435
+ "default": "./src/handlers/messaging-send-reconcile-logic.ts"
436
+ },
437
+ "./handlers/messaging-send-reconcile-logic.ts": {
438
+ "types": "./src/handlers/messaging-send-reconcile-logic.d.ts",
439
+ "default": "./src/handlers/messaging-send-reconcile-logic.ts"
440
+ },
424
441
  "./handlers/messaging-send-recording-logic": {
425
442
  "types": "./src/handlers/messaging-send-recording-logic.d.ts",
426
443
  "default": "./src/handlers/messaging-send-recording-logic.ts"
@@ -437,6 +454,14 @@
437
454
  "types": "./src/handlers/messaging-send-recording-watch.d.ts",
438
455
  "default": "./src/handlers/messaging-send-recording-watch.ts"
439
456
  },
457
+ "./handlers/messaging-send-sweep-selection": {
458
+ "types": "./src/handlers/messaging-send-sweep-selection.d.ts",
459
+ "default": "./src/handlers/messaging-send-sweep-selection.ts"
460
+ },
461
+ "./handlers/messaging-send-sweep-selection.ts": {
462
+ "types": "./src/handlers/messaging-send-sweep-selection.d.ts",
463
+ "default": "./src/handlers/messaging-send-sweep-selection.ts"
464
+ },
440
465
  "./handlers/pane-outbox-drain": {
441
466
  "types": "./src/handlers/pane-outbox-drain.d.ts",
442
467
  "default": "./src/handlers/pane-outbox-drain.ts"
@@ -17,7 +17,7 @@ import { getServerClient } from "@teamscala/orpc/server-client-registry";
17
17
  import type { PrismaClient } from "@teamscala/db/client";
18
18
  import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
19
19
  import { decryptSecretConfig } from "@teamscala/encryption/crypto/secret-config";
20
- import { sendEmail, initResendClient } from "@teamscala/api-resend/send-email";
20
+ import { sendResendEmail } from "./resend-client.ts";
21
21
  import { applyTemplateVars, lookupTemplate } from "@teamscala/email/templates/lookup";
22
22
  import { getAppLogger } from "@teamscala/logger/app-loggers";
23
23
  import { signAgreementAccessToken } from "@teamscala/document-signing/agreement-access-token";
@@ -64,7 +64,6 @@ export async function sendAgreementCompletionEmails(
64
64
  log.warn("[completion-email] Resend token unavailable — skipping");
65
65
  return;
66
66
  }
67
- initResendClient(decrypted.token);
68
67
  const from = await getDefaultEmailSender();
69
68
 
70
69
  const base = baseUrl.replace(/\/+$/, "");
@@ -90,7 +89,7 @@ export async function sendAgreementCompletionEmails(
90
89
  agreementName: agreement.name,
91
90
  url,
92
91
  });
93
- const res = await sendEmail({
92
+ const res = await sendResendEmail(decrypted.token, {
94
93
  from,
95
94
  to: p.email,
96
95
  subject: tpl.subject,
@@ -29,7 +29,7 @@ import {
29
29
  decryptSecretConfig,
30
30
  type SecretConfigLike,
31
31
  } from "@teamscala/encryption/crypto/secret-config";
32
- import { sendEmail, initResendClient } from "@teamscala/api-resend/send-email";
32
+ import { sendResendEmail } from "./resend-client.ts";
33
33
  import { applyTemplateVars, lookupTemplate } from "@teamscala/email/templates/lookup";
34
34
  import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
35
35
  import { getAppLogger } from "@teamscala/logger/app-loggers";
@@ -232,7 +232,6 @@ async function runSend(
232
232
  if (!decrypted.token) {
233
233
  return { ok: false as const, error: "Resend API key missing token" };
234
234
  }
235
- initResendClient(decrypted.token);
236
235
 
237
236
  const sent: Array<{ email: string; ok: boolean; id?: string; error?: string }> =
238
237
  [];
@@ -265,7 +264,7 @@ async function runSend(
265
264
  url: recipientUrl,
266
265
  },
267
266
  );
268
- const res = await sendEmail({
267
+ const res = await sendResendEmail(decrypted.token, {
269
268
  from: sender.from,
270
269
  to: r.email,
271
270
  reply_to: sender.replyTo,
@@ -12,7 +12,7 @@
12
12
  import { os } from "@orpc/server";
13
13
  import * as v from "valibot";
14
14
  import { decryptSecretConfig } from "@teamscala/encryption/crypto/secret-config";
15
- import { sendEmail, initResendClient } from "@teamscala/api-resend/send-email";
15
+ import { sendResendEmail } from "./resend-client.ts";
16
16
  import { applyTemplateVars, lookupTemplate } from "@teamscala/email/templates/lookup";
17
17
  import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
18
18
  import { getAppLogger } from "@teamscala/logger/app-loggers";
@@ -155,7 +155,6 @@ export const proposalSendRouter = {
155
155
  if (!decrypted?.token) {
156
156
  return { ok: false as const, error: "Resend API key not configured" };
157
157
  }
158
- initResendClient(decrypted.token);
159
158
 
160
159
  const sender = await resolveSender(prisma, proposal.organisation_id);
161
160
  const token = signAccessToken(
@@ -180,7 +179,7 @@ export const proposalSendRouter = {
180
179
  proposalName: proposal.name,
181
180
  url,
182
181
  });
183
- const res = await sendEmail({
182
+ const res = await sendResendEmail(decrypted.token, {
184
183
  from: sender.from,
185
184
  to: email,
186
185
  reply_to: sender.replyTo,
@@ -45,12 +45,12 @@ mock.module("@teamscala/logger/app-loggers", () => ({
45
45
  getAppLogger: () => noopLogger,
46
46
  }));
47
47
 
48
- mock.module("@teamscala/api-resend/send-email", () => ({
49
- initResendClient: () => {},
50
- sendEmail: async (args: CapturedEmail) => {
48
+ mock.module("@teamscala/api-resend", () => ({
49
+ createResendAdapter: () => ({ bridged: true }),
50
+ sendEmail: async (_client: unknown, args: CapturedEmail) => {
51
51
  capturedEmail = args;
52
52
  emailResolve?.(args);
53
- return { success: true as const, id: "re_test_123" };
53
+ return { id: "re_test_123" };
54
54
  },
55
55
  }));
56
56
 
@@ -23,7 +23,7 @@
23
23
  import { os } from "@orpc/server";
24
24
  import * as v from "valibot";
25
25
  import { getAppLogger } from "@teamscala/logger/app-loggers";
26
- import { sendEmail, initResendClient } from "@teamscala/api-resend/send-email";
26
+ import { sendResendEmail } from "./resend-client.ts";
27
27
  import { applyTemplateVars, lookupTemplate } from "@teamscala/email/templates/lookup";
28
28
  import { resolveEmailSender } from "@teamscala/email/identity/resolve-email-sender";
29
29
  import { decryptSecretConfig } from "@teamscala/encryption/crypto/secret-config";
@@ -129,7 +129,6 @@ async function sendAuthorityReportEmail(opts: {
129
129
  log.warn("[authority-report-email] Resend token unavailable — skipping");
130
130
  return;
131
131
  }
132
- initResendClient(decrypted.token);
133
132
 
134
133
  const brandRow =
135
134
  opts.organisationId && opts.prisma
@@ -259,7 +258,7 @@ async function sendAuthorityReportEmail(opts: {
259
258
  });
260
259
 
261
260
  const sendOnce = async (fromHeader: string, replyToHeader: string | undefined) =>
262
- sendEmail({
261
+ sendResendEmail(decrypted.token, {
263
262
  from: fromHeader,
264
263
  to: opts.to,
265
264
  subject: tpl.subject,
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @system server-boot
3
+ * @status handwritten
4
+ * @edit edit directly
5
+ *
6
+ * The fn-router Resend send: a per-call adapter (org rest_client token)
7
+ * carrying the {success, id?, error?} shape the routers consume. Replaces
8
+ * the module-global @teamscala/api-resend/send-email wrapper a regen wiped;
9
+ * failure routes through safe-fallback (fail-open-noisy).
10
+ */
11
+ import { createResendAdapter, sendEmail } from "@teamscala/api-resend";
12
+ import { createSafeFallback } from "@teamscala/safe-fallback/create-safe-fallback";
13
+
14
+ /** The Resend send body (from/to/subject + html/text/reply_to) — forwarded
15
+ * to the adapter operation verbatim; nothing is assembled here. */
16
+ export type ResendSendBody = Parameters<typeof sendEmail>[1];
17
+
18
+ export interface ResendSendResult {
19
+ success: boolean;
20
+ id?: string;
21
+ error?: string;
22
+ }
23
+
24
+ const sendLayer = createSafeFallback<ResendSendResult>({
25
+ name: "service-boot:resend-send",
26
+ onFailure: "fail-open-noisy",
27
+ degradedResult: { success: false, error: "resend send layer degraded" },
28
+ failureBudget: 5,
29
+ });
30
+
31
+ export async function sendResendEmail(token: string, body: ResendSendBody): Promise<ResendSendResult> {
32
+ const result = await sendLayer.call(async () => {
33
+ const client = createResendAdapter({ apiKey: token });
34
+ const res = await sendEmail(client, body);
35
+ return { success: true, id: res.id } satisfies ResendSendResult;
36
+ });
37
+ return result;
38
+ }