@teamscala/service-boot 0.23.404 → 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 +26 -1
- package/src/fn-routers/agreement-completion-email.ts +2 -3
- package/src/fn-routers/agreement-send-router.ts +2 -3
- package/src/fn-routers/proposal-send-router.ts +2 -3
- package/src/fn-routers/public-assessment-router.test.ts +4 -4
- package/src/fn-routers/public-assessment-router.ts +2 -3
- package/src/fn-routers/resend-client.ts +38 -0
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"name": "@teamscala/service-boot",
|
|
8
8
|
"type": "module",
|
|
9
|
-
"version": "0.23.
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
49
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
+
}
|