better-ship 0.19.0 → 0.21.0
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/{application-Dg1jIKPe.js → application-D5tNECiC.js} +2 -2
- package/dist/application-D5tNECiC.js.map +1 -0
- package/dist/application.d.ts +2 -2
- package/dist/application.js +1 -1
- package/dist/better-auth/apple-client-secret.js +1 -1
- package/dist/better-auth/auth-client.js +1 -1
- package/dist/better-auth/rate-limit.js +1 -1
- package/dist/better-auth.js +1 -1
- package/dist/billing/client.js +1 -1
- package/dist/cloudflare/auth-secondary-storage.js +1 -1
- package/dist/cloudflare/durable-object-client.js +1 -1
- package/dist/cloudflare/durable-object-errors.js +1 -1
- package/dist/cloudflare/kv-client.js +1 -1
- package/dist/cloudflare/kv-errors.js +1 -1
- package/dist/cloudflare/queue.d.ts +1 -1
- package/dist/cloudflare/queue.js +2 -2
- package/dist/cloudflare/r2-client.js +1 -1
- package/dist/cloudflare/r2-errors.js +1 -1
- package/dist/cloudflare/scheduled.js +1 -1
- package/dist/cloudflare.d.ts +2 -2
- package/dist/cloudflare.js +3 -3
- package/dist/{core-BT1dSEQI.js → core-BIas38R0.js} +2 -2
- package/dist/core-BIas38R0.js.map +1 -0
- package/dist/core.js +1 -1
- package/dist/http.js +2 -2
- package/dist/{index-CYHXOH08.d.ts → index-tNZHSCpJ.d.ts} +6 -2
- package/dist/index-tNZHSCpJ.d.ts.map +1 -0
- package/dist/{outbox-repository-options-B8TnAE1h.js → outbox-repository-options-D0R8z1yS.js} +2 -2
- package/dist/{outbox-repository-options-B8TnAE1h.js.map → outbox-repository-options-D0R8z1yS.js.map} +1 -1
- package/dist/{outbox-repository-options-DjBgFhMi.d.ts → outbox-repository-options-_B8l8l6l.d.ts} +2 -2
- package/dist/{outbox-repository-options-DjBgFhMi.d.ts.map → outbox-repository-options-_B8l8l6l.d.ts.map} +1 -1
- package/dist/postgres.d.ts +2 -2
- package/dist/postgres.js +3 -3
- package/dist/posthog/client.d.ts +9 -8
- package/dist/posthog/client.d.ts.map +1 -1
- package/dist/posthog/client.js.map +1 -1
- package/dist/posthog/metrics.d.ts +1 -1
- package/dist/posthog/server.d.ts +1 -1
- package/dist/posthog/server.js +1 -1
- package/dist/resend.d.ts +14 -16
- package/dist/resend.d.ts.map +1 -1
- package/dist/resend.js +42 -19
- package/dist/resend.js.map +1 -1
- package/dist/sentry/client.js +1 -1
- package/dist/sentry/server.d.ts +1 -1
- package/dist/stripe/payment-service.error.js +1 -1
- package/dist/stripe/payment-webhook.js +1 -1
- package/dist/tanstack.js +1 -1
- package/dist/testing/outbox-repository.d.ts +1 -1
- package/dist/{webhook-verification-qSFXv5H3.js → webhook-verification-CEUARY9X.js} +2 -2
- package/dist/{webhook-verification-qSFXv5H3.js.map → webhook-verification-CEUARY9X.js.map} +1 -1
- package/package.json +1 -1
- package/src/application/email-service.ts +3 -0
- package/src/application/index.ts +1 -0
- package/src/core/email-service.error.ts +1 -1
- package/src/infrastructure/posthog/client.ts +10 -6
- package/src/infrastructure/resend/email-event.ts +3 -0
- package/src/infrastructure/resend/index.ts +2 -2
- package/src/infrastructure/resend/resend-email-service.ts +39 -17
- package/src/infrastructure/resend/resend-webhook.ts +15 -16
- package/dist/application-Dg1jIKPe.js.map +0 -1
- package/dist/core-BT1dSEQI.js.map +0 -1
- package/dist/index-CYHXOH08.d.ts.map +0 -1
package/dist/posthog/client.d.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { PostHog, PostHogConfig } from "posthog-js";
|
|
2
2
|
import { PostHogProvider, usePostHog } from "@posthog/react";
|
|
3
3
|
//#region src/infrastructure/posthog/client.d.ts
|
|
4
|
+
/** Application properties and native SDK options for browser initialization. */
|
|
5
|
+
export interface PostHogClientOptionsConfig {
|
|
6
|
+
readonly environment: string;
|
|
7
|
+
readonly source: string;
|
|
8
|
+
readonly options?: Partial<PostHogConfig>;
|
|
9
|
+
}
|
|
4
10
|
/** The application controls enablement; disabled initialization needs no project key. */
|
|
5
11
|
export type PostHogClientConfig = {
|
|
6
12
|
readonly enabled: false;
|
|
7
|
-
} | {
|
|
13
|
+
} | (PostHogClientOptionsConfig & {
|
|
8
14
|
readonly enabled: true;
|
|
9
15
|
readonly apiKey: string;
|
|
10
|
-
|
|
11
|
-
readonly source: string;
|
|
12
|
-
readonly options?: Partial<PostHogConfig>;
|
|
13
|
-
};
|
|
16
|
+
});
|
|
14
17
|
/** Browser options from Typist and Porte; SDK option names and application overrides stay intact. */
|
|
15
|
-
export declare function createPostHogClientOptions(config:
|
|
16
|
-
enabled: true;
|
|
17
|
-
}>): Partial<PostHogConfig>;
|
|
18
|
+
export declare function createPostHogClientOptions(config: PostHogClientOptionsConfig): Partial<PostHogConfig>;
|
|
18
19
|
/** Initialize once in the browser entrypoint; pass the returned SDK client to PostHogProvider. */
|
|
19
20
|
export declare function initializePostHog(config: PostHogClientConfig): PostHog;
|
|
20
21
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","names":[],"sources":["../../src/infrastructure/posthog/client.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"client.d.ts","names":[],"sources":["../../src/infrastructure/posthog/client.ts"],"mappings":";;;;iBAGiB;WACN;WACA;WACA,UAAU,QAAQ;;;YAIjB;WACG;KACV;WACU;WACA;;;wBAIC,2BACd,QAAQ,6BACP,QAAQ;;wBAkBK,kBAAkB,QAAQ,sBAAsB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../../src/infrastructure/posthog/client.ts"],"sourcesContent":["import posthog, { type PostHog, type PostHogConfig } from 'posthog-js'\n\n/** The application controls enablement; disabled initialization needs no project key. */\nexport type PostHogClientConfig =\n | { readonly enabled: false }\n | {\n readonly enabled: true\n readonly apiKey: string\n
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../../src/infrastructure/posthog/client.ts"],"sourcesContent":["import posthog, { type PostHog, type PostHogConfig } from 'posthog-js'\n\n/** Application properties and native SDK options for browser initialization. */\nexport interface PostHogClientOptionsConfig {\n readonly environment: string\n readonly source: string\n readonly options?: Partial<PostHogConfig>\n}\n\n/** The application controls enablement; disabled initialization needs no project key. */\nexport type PostHogClientConfig =\n | { readonly enabled: false }\n | (PostHogClientOptionsConfig & {\n readonly enabled: true\n readonly apiKey: string\n })\n\n/** Browser options from Typist and Porte; SDK option names and application overrides stay intact. */\nexport function createPostHogClientOptions(\n config: PostHogClientOptionsConfig,\n): Partial<PostHogConfig> {\n return {\n api_host: '/relay',\n ui_host: 'https://us.posthog.com',\n person_profiles: 'identified_only',\n defaults: '2026-08-29',\n disable_surveys: true,\n advanced_disable_feature_flags: true,\n capture_performance: { web_vitals: false },\n ...config.options,\n loaded: (client) => {\n client.register({ environment: config.environment, source: config.source })\n config.options?.loaded?.(client)\n },\n }\n}\n\n/** Initialize once in the browser entrypoint; pass the returned SDK client to PostHogProvider. */\nexport function initializePostHog(config: PostHogClientConfig): PostHog {\n if (!config.enabled) return posthog\n posthog.init(config.apiKey, createPostHogClientOptions(config))\n return posthog\n}\n\nexport { PostHogProvider, usePostHog } from '@posthog/react'\n"],"mappings":";;;;;AAkBA,SAAgB,2BACd,QACwB;CACxB,OAAO;EACL,UAAU;EACV,SAAS;EACT,iBAAiB;EACjB,UAAU;EACV,iBAAiB;EACjB,gCAAgC;EAChC,qBAAqB,EAAE,YAAY,MAAM;EACzC,GAAG,OAAO;EACV,SAAS,WAAW;GAClB,OAAO,SAAS;IAAE,aAAa,OAAO;IAAa,QAAQ,OAAO;GAAO,CAAC;GAC1E,OAAO,SAAS,SAAS,MAAM;EACjC;CACF;AACF;;AAGA,SAAgB,kBAAkB,QAAsC;CACtE,IAAI,CAAC,OAAO,SAAS,OAAO;CAC5B,QAAQ,KAAK,OAAO,QAAQ,2BAA2B,MAAM,CAAC;CAC9D,OAAO;AACT"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { J as
|
|
1
|
+
import { J as MetricOptions, Y as MetricsService } from "../index-tNZHSCpJ.js";
|
|
2
2
|
import { PostHog } from "posthog-node";
|
|
3
3
|
//#region src/infrastructure/posthog/metrics.d.ts
|
|
4
4
|
/** PostHog Metrics with Worker lifetime management, ported from Typist's metrics-service.ts. */
|
package/dist/posthog/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Gt as AnalyticsEventIdentity, Kt as AnalyticsEventInput, qt as AnalyticsProperties } from "../index-DUFS0SJ_.js";
|
|
2
|
-
import {
|
|
2
|
+
import { Z as AnalyticsService } from "../index-tNZHSCpJ.js";
|
|
3
3
|
import { PostHogMetricsService } from "./metrics.js";
|
|
4
4
|
import { PostHog, PostHogOptions } from "posthog-node";
|
|
5
5
|
//#region src/infrastructure/posthog/server.d.ts
|
package/dist/posthog/server.js
CHANGED
package/dist/resend.d.ts
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import { jt as BounceType } from "./index-DUFS0SJ_.js";
|
|
2
|
-
import { B as OutboundEmail, I as EmailAccepted, R as IEmailService } from "./index-
|
|
2
|
+
import { B as OutboundEmail, I as EmailAccepted, R as IEmailService, V as ReceivedEmail } from "./index-tNZHSCpJ.js";
|
|
3
3
|
import { n as WebhookVerifier } from "./webhook-verification-D1c7BY4q.js";
|
|
4
|
-
import
|
|
4
|
+
import "resend";
|
|
5
5
|
//#region src/infrastructure/resend/resend-email-service.d.ts
|
|
6
|
-
/** The SDK operations required by the adapter; a test factory can return a plain object. */
|
|
7
|
-
type ResendEmailClient = {
|
|
8
|
-
readonly emails: Pick<Resend['emails'], 'send'>;
|
|
9
|
-
};
|
|
10
6
|
/**
|
|
11
7
|
* Send without package retries or logs. Ported from Suparelay's resend-email-service.ts.
|
|
12
8
|
* The Resend SDK itself logs API errors outside production.
|
|
13
9
|
*/
|
|
14
10
|
export declare class ResendEmailService implements IEmailService {
|
|
15
|
-
private readonly
|
|
11
|
+
private readonly apiKey;
|
|
16
12
|
private readonly fromAddress;
|
|
17
|
-
/** Load the SDK on the first
|
|
13
|
+
/** Load the SDK on the first email operation. Constructing the service does not load it. */
|
|
18
14
|
static create(apiKey: string, fromAddress: string): ResendEmailService;
|
|
19
15
|
private client;
|
|
20
|
-
/**
|
|
21
|
-
constructor(
|
|
16
|
+
/** Store configuration. Load the SDK and create its client on the first email operation. */
|
|
17
|
+
constructor(apiKey: string, fromAddress: string);
|
|
22
18
|
/** Resolve after acceptance. Provider failures throw EmailServiceError; SDK defects retain their identity. */
|
|
23
19
|
sendEmail(email: OutboundEmail): Promise<EmailAccepted>;
|
|
20
|
+
/** Retrieve headers by the received email's provider ID. Provider failures throw EmailServiceError. */
|
|
21
|
+
getReceivedEmail(emailId: string): Promise<ReceivedEmail>;
|
|
22
|
+
private getClient;
|
|
24
23
|
}
|
|
25
24
|
//#endregion
|
|
26
25
|
//#region src/infrastructure/resend/email-event.d.ts
|
|
@@ -30,6 +29,7 @@ type EmailEventType = (typeof SIMPLE_EMAIL_TYPES)[number] | 'email.bounced' | 'e
|
|
|
30
29
|
type EmailEventBase = {
|
|
31
30
|
readonly id: string;
|
|
32
31
|
readonly emailId: string;
|
|
32
|
+
readonly messageId: string;
|
|
33
33
|
readonly recipients: readonly string[];
|
|
34
34
|
readonly subject: string;
|
|
35
35
|
readonly tags: Readonly<Record<string, string>>;
|
|
@@ -54,20 +54,18 @@ type EmailEvent = (EmailEventBase & {
|
|
|
54
54
|
});
|
|
55
55
|
//#endregion
|
|
56
56
|
//#region src/infrastructure/resend/resend-webhook.d.ts
|
|
57
|
-
/** The SDK's local signature verifier. It makes no API request. */
|
|
58
|
-
type ResendWebhookClient = Pick<Resend['webhooks'], 'verify'>;
|
|
59
57
|
/** Verify Svix signatures and map Resend email events. Ported from Typist's Resend route. */
|
|
60
58
|
export declare class ResendWebhook implements WebhookVerifier<EmailEvent> {
|
|
61
59
|
private readonly webhookSecret;
|
|
62
|
-
private readonly verifier;
|
|
63
60
|
/** Load the SDK on the first verification. Only the webhook secret is required. */
|
|
64
61
|
static create(webhookSecret: string): ResendWebhook;
|
|
65
62
|
private client;
|
|
66
|
-
/**
|
|
67
|
-
constructor(webhookSecret: string
|
|
63
|
+
/** Store the webhook secret. Load the SDK on the first verification. */
|
|
64
|
+
constructor(webhookSecret: string);
|
|
68
65
|
/** Verify the raw body and Svix headers. Invalid requests throw typed errors; other failures propagate. */
|
|
69
66
|
verify(body: string, headers: Headers): Promise<EmailEvent | null>;
|
|
67
|
+
private getClient;
|
|
70
68
|
}
|
|
71
69
|
//#endregion
|
|
72
|
-
export type { EmailEvent, EmailEventType
|
|
70
|
+
export type { EmailEvent, EmailEventType };
|
|
73
71
|
//# sourceMappingURL=resend.d.ts.map
|
package/dist/resend.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resend.d.ts","names":[],"sources":["../src/infrastructure/resend/resend-email-service.ts","../src/infrastructure/resend/email-event.ts","../src/infrastructure/resend/resend-webhook.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"resend.d.ts","names":[],"sources":["../src/infrastructure/resend/resend-email-service.ts","../src/infrastructure/resend/email-event.ts","../src/infrastructure/resend/resend-webhook.ts"],"mappings":";;;;;;;;;qBA+Ea,8BAA8B;mBAUtB;mBACA;;SATZ,OAAO,gBAAgB,sBAAsB;UAI5C;;EAGR,YACmB,gBACA;;EAIb,UAAU,OAAO,gBAAgB,QAAQ;;EA+BzC,iBAAiB,kBAAkB,QAAQ;UAgBnC;;;;cCxIV;;KAYM,yBACA;KAiDP;WACM;WACA;WACA;WACA;WACA;WACA,MAAM,SAAS;WACf;;;KAIC,cACP;WACU;WACA;aACE,MAAM;aACN;aACA;;MAGZ;WAA4B;WAA+B;MAC3D;WAA4B;WAAgC;MAC5D;WAA4B,cAAc;;;;;qBC7ElC,yBAAyB,gBAAgB;mBASvB;;SAPtB,OAAO,wBAAwB;UAI9B;;EAGR,YAA6B;;EAGvB,OAAO,cAAc,SAAS,UAAU,QAAQ;UA0BxC"}
|
package/dist/resend.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $ as EmailAddressSchema, J as parseBounceType, X as EmailServiceError } from "./core-
|
|
2
|
-
import { t as WebhookVerificationError } from "./webhook-verification-
|
|
1
|
+
import { $ as EmailAddressSchema, J as parseBounceType, X as EmailServiceError } from "./core-BIas38R0.js";
|
|
2
|
+
import { t as WebhookVerificationError } from "./webhook-verification-CEUARY9X.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { Buffer } from "node:buffer";
|
|
5
5
|
|
|
@@ -61,22 +61,21 @@ function toAttachment(attachment) {
|
|
|
61
61
|
* The Resend SDK itself logs API errors outside production.
|
|
62
62
|
*/
|
|
63
63
|
var ResendEmailService = class ResendEmailService {
|
|
64
|
-
|
|
64
|
+
apiKey;
|
|
65
65
|
fromAddress;
|
|
66
|
-
/** Load the SDK on the first
|
|
66
|
+
/** Load the SDK on the first email operation. Constructing the service does not load it. */
|
|
67
67
|
static create(apiKey, fromAddress) {
|
|
68
|
-
return new ResendEmailService(
|
|
68
|
+
return new ResendEmailService(apiKey, fromAddress);
|
|
69
69
|
}
|
|
70
70
|
client;
|
|
71
|
-
/**
|
|
72
|
-
constructor(
|
|
73
|
-
this.
|
|
71
|
+
/** Store configuration. Load the SDK and create its client on the first email operation. */
|
|
72
|
+
constructor(apiKey, fromAddress) {
|
|
73
|
+
this.apiKey = apiKey;
|
|
74
74
|
this.fromAddress = fromAddress;
|
|
75
75
|
}
|
|
76
76
|
/** Resolve after acceptance. Provider failures throw EmailServiceError; SDK defects retain their identity. */
|
|
77
77
|
async sendEmail(email) {
|
|
78
|
-
|
|
79
|
-
const client = await this.client;
|
|
78
|
+
const client = await this.getClient();
|
|
80
79
|
const payload = {
|
|
81
80
|
from: email.from ?? this.fromAddress,
|
|
82
81
|
to: [email.to].flat(),
|
|
@@ -101,6 +100,26 @@ var ResendEmailService = class ResendEmailService {
|
|
|
101
100
|
});
|
|
102
101
|
return { providerId: result.data.id };
|
|
103
102
|
}
|
|
103
|
+
/** Retrieve headers by the received email's provider ID. Provider failures throw EmailServiceError. */
|
|
104
|
+
async getReceivedEmail(emailId) {
|
|
105
|
+
const result = await (await this.getClient()).emails.receiving.get(emailId);
|
|
106
|
+
if (result.error !== null) throw new EmailServiceError({
|
|
107
|
+
reason: reasonFromResend(result.error),
|
|
108
|
+
provider: "resend",
|
|
109
|
+
providerCode: result.error.name,
|
|
110
|
+
statusCode: result.error.statusCode,
|
|
111
|
+
retryAfter: retryAfterSeconds(result.headers?.["retry-after"]),
|
|
112
|
+
cause: result.error
|
|
113
|
+
});
|
|
114
|
+
return { headers: result.data.headers ?? {} };
|
|
115
|
+
}
|
|
116
|
+
async getClient() {
|
|
117
|
+
if (this.client === void 0) {
|
|
118
|
+
const { Resend } = await import("resend");
|
|
119
|
+
this.client ??= new Resend(this.apiKey);
|
|
120
|
+
}
|
|
121
|
+
return this.client;
|
|
122
|
+
}
|
|
104
123
|
};
|
|
105
124
|
|
|
106
125
|
//#endregion
|
|
@@ -130,6 +149,7 @@ const EMAIL_TYPES = {
|
|
|
130
149
|
};
|
|
131
150
|
const BaseDataSchema = z.object({
|
|
132
151
|
email_id: z.string().min(1),
|
|
152
|
+
message_id: z.string().min(1),
|
|
133
153
|
to: z.array(EmailAddressSchema).min(1),
|
|
134
154
|
subject: z.string(),
|
|
135
155
|
tags: z.record(z.string(), z.string()).default({})
|
|
@@ -168,6 +188,7 @@ function parseEmailEvent(payload, id) {
|
|
|
168
188
|
const base = {
|
|
169
189
|
id,
|
|
170
190
|
emailId: event.data.email_id,
|
|
191
|
+
messageId: event.data.message_id,
|
|
171
192
|
recipients: event.data.to,
|
|
172
193
|
subject: event.data.subject,
|
|
173
194
|
tags: event.data.tags,
|
|
@@ -206,19 +227,14 @@ const SignatureErrorSchema = z.instanceof(Error).refine((error) => error.name ==
|
|
|
206
227
|
/** Verify Svix signatures and map Resend email events. Ported from Typist's Resend route. */
|
|
207
228
|
var ResendWebhook = class ResendWebhook {
|
|
208
229
|
webhookSecret;
|
|
209
|
-
verifier;
|
|
210
230
|
/** Load the SDK on the first verification. Only the webhook secret is required. */
|
|
211
231
|
static create(webhookSecret) {
|
|
212
|
-
return new ResendWebhook(webhookSecret
|
|
213
|
-
const { Resend } = await import("resend");
|
|
214
|
-
return new Resend("re_webhook_verification_only").webhooks;
|
|
215
|
-
});
|
|
232
|
+
return new ResendWebhook(webhookSecret);
|
|
216
233
|
}
|
|
217
234
|
client;
|
|
218
|
-
/**
|
|
219
|
-
constructor(webhookSecret
|
|
235
|
+
/** Store the webhook secret. Load the SDK on the first verification. */
|
|
236
|
+
constructor(webhookSecret) {
|
|
220
237
|
this.webhookSecret = webhookSecret;
|
|
221
|
-
this.verifier = verifier;
|
|
222
238
|
}
|
|
223
239
|
/** Verify the raw body and Svix headers. Invalid requests throw typed errors; other failures propagate. */
|
|
224
240
|
async verify(body, headers) {
|
|
@@ -226,7 +242,7 @@ var ResendWebhook = class ResendWebhook {
|
|
|
226
242
|
const timestamp = headers.get("svix-timestamp");
|
|
227
243
|
const signature = headers.get("svix-signature");
|
|
228
244
|
if (!id || !timestamp || !signature) throw new WebhookVerificationError("missing_headers");
|
|
229
|
-
const client = await
|
|
245
|
+
const client = await this.getClient();
|
|
230
246
|
let payload;
|
|
231
247
|
try {
|
|
232
248
|
payload = client.verify({
|
|
@@ -250,6 +266,13 @@ var ResendWebhook = class ResendWebhook {
|
|
|
250
266
|
throw cause;
|
|
251
267
|
}
|
|
252
268
|
}
|
|
269
|
+
async getClient() {
|
|
270
|
+
if (this.client === void 0) {
|
|
271
|
+
const { Resend } = await import("resend");
|
|
272
|
+
this.client ??= new Resend("re_webhook_verification_only").webhooks;
|
|
273
|
+
}
|
|
274
|
+
return this.client;
|
|
275
|
+
}
|
|
253
276
|
};
|
|
254
277
|
|
|
255
278
|
//#endregion
|
package/dist/resend.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resend.js","names":[],"sources":["../src/infrastructure/resend/resend-email-service.ts","../src/infrastructure/resend/email-event.ts","../src/infrastructure/resend/resend-webhook.ts"],"sourcesContent":["import { Buffer } from 'node:buffer'\n\nimport type { Attachment, CreateEmailOptions, ErrorResponse, Resend } from 'resend'\n\nimport type { EmailAccepted, EmailAttachment, IEmailService, OutboundEmail } from '@/application'\nimport { EmailServiceError, type EmailSendFailureReason } from '@/core'\n\n// SDK additions must receive an explicit classification before the package builds.\nconst SDK_REASONS = {\n invalid_idempotency_key: 'invalid_request',\n validation_error: 'invalid_request',\n missing_api_key: 'configuration',\n restricted_api_key: 'configuration',\n invalid_api_key: 'configuration',\n not_found: 'invalid_request',\n method_not_allowed: 'invalid_request',\n invalid_idempotent_request: 'invalid_request',\n concurrent_idempotent_requests: 'idempotency_conflict',\n invalid_attachment: 'invalid_request',\n invalid_from_address: 'invalid_request',\n invalid_access: 'configuration',\n invalid_parameter: 'invalid_request',\n invalid_region: 'invalid_request',\n missing_required_field: 'invalid_request',\n monthly_quota_exceeded: 'quota_exceeded',\n daily_quota_exceeded: 'quota_exceeded',\n rate_limit_exceeded: 'rate_limited',\n security_error: 'configuration',\n application_error: 'provider_error',\n internal_server_error: 'provider_error',\n} satisfies Record<ErrorResponse['name'], EmailSendFailureReason>\n\n// Resend documents these codes before its SDK includes them in ErrorResponse.\nconst DOCUMENTED_REASONS = new Map<string, EmailSendFailureReason>([\n ['email_above_quota', 'quota_exceeded'],\n ['invalid_permission', 'configuration'],\n ['suspended_api_key', 'configuration'],\n ['resource_locked', 'idempotency_conflict'],\n ['service_unavailable', 'provider_error'],\n ['missing_required_parameter', 'invalid_request'],\n])\n\nfunction reasonFromResend(error: ErrorResponse): EmailSendFailureReason {\n if (Object.hasOwn(SDK_REASONS, error.name)) return SDK_REASONS[error.name]\n const reason = DOCUMENTED_REASONS.get(error.name)\n if (reason !== undefined) return reason\n if (error.statusCode === 429) return 'rate_limited'\n if (error.statusCode !== null && error.statusCode >= 400 && error.statusCode < 500)\n return 'invalid_request'\n return 'provider_error'\n}\n\nfunction retryAfterSeconds(value: string | undefined): number | null {\n if (value === undefined || !/^\\d+$/.test(value)) return null\n const seconds = Number(value)\n return Number.isSafeInteger(seconds) ? seconds : null\n}\n\nfunction toAttachment(attachment: EmailAttachment): Attachment {\n const result: Attachment = {\n filename: attachment.filename,\n content: ArrayBuffer.isView(attachment.content)\n ? Buffer.from(attachment.content)\n : attachment.content,\n }\n if (attachment.contentType !== undefined) result.contentType = attachment.contentType\n return result\n}\n\n/** The SDK operations required by the adapter; a test factory can return a plain object. */\nexport type ResendEmailClient = { readonly emails: Pick<Resend['emails'], 'send'> }\n\n/**\n * Send without package retries or logs. Ported from Suparelay's resend-email-service.ts.\n * The Resend SDK itself logs API errors outside production.\n */\nexport class ResendEmailService implements IEmailService {\n /** Load the SDK on the first send. Constructing the service does not load it. */\n static create(apiKey: string, fromAddress: string): ResendEmailService {\n return new ResendEmailService(\n () => import('resend').then(({ Resend }) => new Resend(apiKey)),\n fromAddress,\n )\n }\n\n private client: Promise<ResendEmailClient> | undefined\n\n /** Supply an async factory. The service calls it once, on the first send, and keeps its promise. */\n constructor(\n private readonly createClient: () => Promise<ResendEmailClient>,\n private readonly fromAddress: string,\n ) {}\n\n /** Resolve after acceptance. Provider failures throw EmailServiceError; SDK defects retain their identity. */\n async sendEmail(email: OutboundEmail): Promise<EmailAccepted> {\n if (this.client === undefined) {\n // Cache synchronous factory throws as rejections too, so later sends cannot initialize again.\n this.client = Promise.resolve().then(this.createClient)\n }\n const client = await this.client\n const payload: CreateEmailOptions = {\n from: email.from ?? this.fromAddress,\n to: [email.to].flat(),\n subject: email.subject,\n html: email.html,\n }\n if (email.text !== undefined) payload.text = email.text\n if (email.replyTo !== undefined) payload.replyTo = email.replyTo\n if (email.tags !== undefined)\n payload.tags = Object.entries(email.tags).map(([name, value]) => ({ name, value }))\n if (email.attachments !== undefined) payload.attachments = email.attachments.map(toAttachment)\n const result = await client.emails.send(\n payload,\n email.idempotencyKey === undefined ? undefined : { idempotencyKey: email.idempotencyKey },\n )\n if (result.error !== null) {\n throw new EmailServiceError({\n reason: reasonFromResend(result.error),\n provider: 'resend',\n providerCode: result.error.name,\n statusCode: result.error.statusCode,\n retryAfter: retryAfterSeconds(result.headers?.['retry-after']),\n cause: result.error,\n })\n }\n return { providerId: result.data.id }\n }\n}\n","import type { WebhookEventPayload } from 'resend'\nimport { z } from 'zod'\n\nimport { EmailAddressSchema, parseBounceType, type BounceType } from '@/core'\n\nconst SIMPLE_EMAIL_TYPES = [\n 'email.sent',\n 'email.scheduled',\n 'email.delivered',\n 'email.delivery_delayed',\n 'email.complained',\n 'email.opened',\n 'email.received',\n 'email.suppressed',\n] as const\n\n/** Resend email event names supported by this module. */\nexport type EmailEventType =\n | (typeof SIMPLE_EMAIL_TYPES)[number]\n | 'email.bounced'\n | 'email.failed'\n | 'email.clicked'\n\nconst EMAIL_TYPES = {\n 'email.sent': true,\n 'email.scheduled': true,\n 'email.delivered': true,\n 'email.delivery_delayed': true,\n 'email.complained': true,\n 'email.opened': true,\n 'email.received': true,\n 'email.suppressed': true,\n 'email.bounced': true,\n 'email.failed': true,\n 'email.clicked': true,\n} satisfies Record<Extract<WebhookEventPayload['type'], `email.${string}`>, true>\n\nconst BaseDataSchema = z.object({\n email_id: z.string().min(1),\n to: z.array(EmailAddressSchema).min(1),\n subject: z.string(),\n tags: z.record(z.string(), z.string()).default({}),\n})\n\nconst EmailPayloadSchema = z.discriminatedUnion('type', [\n z.object({ type: z.enum(SIMPLE_EMAIL_TYPES), data: BaseDataSchema }),\n z.object({\n type: z.literal('email.bounced'),\n data: BaseDataSchema.extend({\n bounce: z.object({\n type: z.string().optional(),\n subType: z.string().nullable().optional(),\n message: z.string(),\n }),\n }),\n }),\n z.object({\n type: z.literal('email.failed'),\n data: BaseDataSchema.extend({ failed: z.object({ reason: z.string() }) }),\n }),\n z.object({\n type: z.literal('email.clicked'),\n data: BaseDataSchema.extend({ click: z.object({ link: z.string() }) }),\n }),\n])\n\ntype EmailEventBase = {\n readonly id: string\n readonly emailId: string\n readonly recipients: readonly string[]\n readonly subject: string\n readonly tags: Readonly<Record<string, string>>\n readonly occurredAt: string\n}\n\n/** A verified event. `id` is the Svix identity; use `resend:event:${id}` as the command id. */\nexport type EmailEvent =\n | (EmailEventBase & {\n readonly type: 'email.bounced'\n readonly bounce: {\n readonly type: BounceType\n readonly subType: string | null\n readonly message: string\n }\n })\n | (EmailEventBase & { readonly type: 'email.failed'; readonly reason: string })\n | (EmailEventBase & { readonly type: 'email.clicked'; readonly link: string })\n | (EmailEventBase & { readonly type: (typeof SIMPLE_EMAIL_TYPES)[number] })\n\nconst EventHeaderSchema = z.object({\n type: z.string(),\n created_at: z.iso.datetime({ offset: true }),\n})\n\n/** Validate signed provider data before projecting it into the public event contract. */\nexport function parseEmailEvent(payload: WebhookEventPayload, id: string): EmailEvent | null {\n const header = EventHeaderSchema.parse(payload)\n if (!Object.hasOwn(EMAIL_TYPES, header.type)) return null\n const event = EmailPayloadSchema.parse(payload)\n const base: EmailEventBase = {\n id,\n emailId: event.data.email_id,\n recipients: event.data.to,\n subject: event.data.subject,\n tags: event.data.tags,\n occurredAt: header.created_at,\n }\n switch (event.type) {\n case 'email.bounced':\n return {\n ...base,\n type: event.type,\n bounce: {\n type: parseBounceType(event.data.bounce.type),\n subType: event.data.bounce.subType ?? null,\n message: event.data.bounce.message,\n },\n }\n case 'email.failed':\n return { ...base, type: event.type, reason: event.data.failed.reason }\n case 'email.clicked':\n return { ...base, type: event.type, link: event.data.click.link }\n default:\n return { ...base, type: event.type }\n }\n}\n","import type { Resend } from 'resend'\nimport { z } from 'zod'\n\nimport { WebhookVerificationError, type WebhookVerifier } from '../http/webhook-verification.ts'\nimport { parseEmailEvent, type EmailEvent } from './email-event.ts'\n\n// Separate standardwebhooks versions have different prototypes; its error name remains the same.\nconst SignatureErrorSchema = z\n .instanceof(Error)\n .refine((error) => error.name === 'WebhookVerificationError')\n\n/** The SDK's local signature verifier. It makes no API request. */\nexport type ResendWebhookClient = Pick<Resend['webhooks'], 'verify'>\n\n/** Verify Svix signatures and map Resend email events. Ported from Typist's Resend route. */\nexport class ResendWebhook implements WebhookVerifier<EmailEvent> {\n /** Load the SDK on the first verification. Only the webhook secret is required. */\n static create(webhookSecret: string): ResendWebhook {\n return new ResendWebhook(webhookSecret, async () => {\n const { Resend } = await import('resend')\n // The SDK requires a nonempty key even though its local verifier never uses that key.\n return new Resend('re_webhook_verification_only').webhooks\n })\n }\n\n private client: Promise<ResendWebhookClient> | undefined\n\n /** Inject the local SDK verifier when testing. */\n constructor(\n private readonly webhookSecret: string,\n private readonly verifier: () => Promise<ResendWebhookClient>,\n ) {}\n\n /** Verify the raw body and Svix headers. Invalid requests throw typed errors; other failures propagate. */\n async verify(body: string, headers: Headers): Promise<EmailEvent | null> {\n const id = headers.get('svix-id')\n const timestamp = headers.get('svix-timestamp')\n const signature = headers.get('svix-signature')\n if (!id || !timestamp || !signature) throw new WebhookVerificationError('missing_headers')\n const client = await (this.client ??= this.verifier())\n let payload: ReturnType<ResendWebhookClient['verify']>\n try {\n payload = client.verify({\n payload: body,\n headers: { id, timestamp, signature },\n webhookSecret: this.webhookSecret,\n })\n } catch (cause) {\n if (cause instanceof SyntaxError) throw new WebhookVerificationError('invalid_payload', cause)\n if (!SignatureErrorSchema.safeParse(cause).success) throw cause\n throw new WebhookVerificationError('invalid_signature', cause)\n }\n try {\n return parseEmailEvent(payload, id)\n } catch (cause) {\n if (cause instanceof z.ZodError) throw new WebhookVerificationError('invalid_payload', cause)\n throw cause\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resend.js","names":[],"sources":["../src/infrastructure/resend/resend-email-service.ts","../src/infrastructure/resend/email-event.ts","../src/infrastructure/resend/resend-webhook.ts"],"sourcesContent":["import { Buffer } from 'node:buffer'\n\nimport type { Attachment, CreateEmailOptions, ErrorResponse, Resend } from 'resend'\n\nimport type {\n EmailAccepted,\n EmailAttachment,\n IEmailService,\n OutboundEmail,\n ReceivedEmail,\n} from '@/application'\nimport { EmailServiceError, type EmailSendFailureReason } from '@/core'\n\n// SDK additions must receive an explicit classification before the package builds.\nconst SDK_REASONS = {\n invalid_idempotency_key: 'invalid_request',\n validation_error: 'invalid_request',\n missing_api_key: 'configuration',\n restricted_api_key: 'configuration',\n invalid_api_key: 'configuration',\n not_found: 'invalid_request',\n method_not_allowed: 'invalid_request',\n invalid_idempotent_request: 'invalid_request',\n concurrent_idempotent_requests: 'idempotency_conflict',\n invalid_attachment: 'invalid_request',\n invalid_from_address: 'invalid_request',\n invalid_access: 'configuration',\n invalid_parameter: 'invalid_request',\n invalid_region: 'invalid_request',\n missing_required_field: 'invalid_request',\n monthly_quota_exceeded: 'quota_exceeded',\n daily_quota_exceeded: 'quota_exceeded',\n rate_limit_exceeded: 'rate_limited',\n security_error: 'configuration',\n application_error: 'provider_error',\n internal_server_error: 'provider_error',\n} satisfies Record<ErrorResponse['name'], EmailSendFailureReason>\n\n// Resend documents these codes before its SDK includes them in ErrorResponse.\nconst DOCUMENTED_REASONS = new Map<string, EmailSendFailureReason>([\n ['email_above_quota', 'quota_exceeded'],\n ['invalid_permission', 'configuration'],\n ['suspended_api_key', 'configuration'],\n ['resource_locked', 'idempotency_conflict'],\n ['service_unavailable', 'provider_error'],\n ['missing_required_parameter', 'invalid_request'],\n])\n\nfunction reasonFromResend(error: ErrorResponse): EmailSendFailureReason {\n if (Object.hasOwn(SDK_REASONS, error.name)) return SDK_REASONS[error.name]\n const reason = DOCUMENTED_REASONS.get(error.name)\n if (reason !== undefined) return reason\n if (error.statusCode === 429) return 'rate_limited'\n if (error.statusCode !== null && error.statusCode >= 400 && error.statusCode < 500)\n return 'invalid_request'\n return 'provider_error'\n}\n\nfunction retryAfterSeconds(value: string | undefined): number | null {\n if (value === undefined || !/^\\d+$/.test(value)) return null\n const seconds = Number(value)\n return Number.isSafeInteger(seconds) ? seconds : null\n}\n\nfunction toAttachment(attachment: EmailAttachment): Attachment {\n const result: Attachment = {\n filename: attachment.filename,\n content: ArrayBuffer.isView(attachment.content)\n ? Buffer.from(attachment.content)\n : attachment.content,\n }\n if (attachment.contentType !== undefined) result.contentType = attachment.contentType\n return result\n}\n\n/**\n * Send without package retries or logs. Ported from Suparelay's resend-email-service.ts.\n * The Resend SDK itself logs API errors outside production.\n */\nexport class ResendEmailService implements IEmailService {\n /** Load the SDK on the first email operation. Constructing the service does not load it. */\n static create(apiKey: string, fromAddress: string): ResendEmailService {\n return new ResendEmailService(apiKey, fromAddress)\n }\n\n private client: Resend | undefined\n\n /** Store configuration. Load the SDK and create its client on the first email operation. */\n constructor(\n private readonly apiKey: string,\n private readonly fromAddress: string,\n ) {}\n\n /** Resolve after acceptance. Provider failures throw EmailServiceError; SDK defects retain their identity. */\n async sendEmail(email: OutboundEmail): Promise<EmailAccepted> {\n const client = await this.getClient()\n const payload: CreateEmailOptions = {\n from: email.from ?? this.fromAddress,\n to: [email.to].flat(),\n subject: email.subject,\n html: email.html,\n }\n if (email.text !== undefined) payload.text = email.text\n if (email.replyTo !== undefined) payload.replyTo = email.replyTo\n if (email.tags !== undefined)\n payload.tags = Object.entries(email.tags).map(([name, value]) => ({ name, value }))\n if (email.attachments !== undefined) payload.attachments = email.attachments.map(toAttachment)\n const result = await client.emails.send(\n payload,\n email.idempotencyKey === undefined ? undefined : { idempotencyKey: email.idempotencyKey },\n )\n if (result.error !== null) {\n throw new EmailServiceError({\n reason: reasonFromResend(result.error),\n provider: 'resend',\n providerCode: result.error.name,\n statusCode: result.error.statusCode,\n retryAfter: retryAfterSeconds(result.headers?.['retry-after']),\n cause: result.error,\n })\n }\n return { providerId: result.data.id }\n }\n\n /** Retrieve headers by the received email's provider ID. Provider failures throw EmailServiceError. */\n async getReceivedEmail(emailId: string): Promise<ReceivedEmail> {\n const client = await this.getClient()\n const result = await client.emails.receiving.get(emailId)\n if (result.error !== null) {\n throw new EmailServiceError({\n reason: reasonFromResend(result.error),\n provider: 'resend',\n providerCode: result.error.name,\n statusCode: result.error.statusCode,\n retryAfter: retryAfterSeconds(result.headers?.['retry-after']),\n cause: result.error,\n })\n }\n return { headers: result.data.headers ?? {} }\n }\n\n private async getClient(): Promise<Resend> {\n if (this.client === undefined) {\n const { Resend } = await import('resend')\n // Recheck after the import so concurrent calls share one client.\n this.client ??= new Resend(this.apiKey)\n }\n return this.client\n }\n}\n","import type { WebhookEventPayload } from 'resend'\nimport { z } from 'zod'\n\nimport { EmailAddressSchema, parseBounceType, type BounceType } from '@/core'\n\nconst SIMPLE_EMAIL_TYPES = [\n 'email.sent',\n 'email.scheduled',\n 'email.delivered',\n 'email.delivery_delayed',\n 'email.complained',\n 'email.opened',\n 'email.received',\n 'email.suppressed',\n] as const\n\n/** Resend email event names supported by this module. */\nexport type EmailEventType =\n | (typeof SIMPLE_EMAIL_TYPES)[number]\n | 'email.bounced'\n | 'email.failed'\n | 'email.clicked'\n\nconst EMAIL_TYPES = {\n 'email.sent': true,\n 'email.scheduled': true,\n 'email.delivered': true,\n 'email.delivery_delayed': true,\n 'email.complained': true,\n 'email.opened': true,\n 'email.received': true,\n 'email.suppressed': true,\n 'email.bounced': true,\n 'email.failed': true,\n 'email.clicked': true,\n} satisfies Record<Extract<WebhookEventPayload['type'], `email.${string}`>, true>\n\nconst BaseDataSchema = z.object({\n email_id: z.string().min(1),\n message_id: z.string().min(1),\n to: z.array(EmailAddressSchema).min(1),\n subject: z.string(),\n tags: z.record(z.string(), z.string()).default({}),\n})\n\nconst EmailPayloadSchema = z.discriminatedUnion('type', [\n z.object({ type: z.enum(SIMPLE_EMAIL_TYPES), data: BaseDataSchema }),\n z.object({\n type: z.literal('email.bounced'),\n data: BaseDataSchema.extend({\n bounce: z.object({\n type: z.string().optional(),\n subType: z.string().nullable().optional(),\n message: z.string(),\n }),\n }),\n }),\n z.object({\n type: z.literal('email.failed'),\n data: BaseDataSchema.extend({ failed: z.object({ reason: z.string() }) }),\n }),\n z.object({\n type: z.literal('email.clicked'),\n data: BaseDataSchema.extend({ click: z.object({ link: z.string() }) }),\n }),\n])\n\ntype EmailEventBase = {\n readonly id: string\n readonly emailId: string\n readonly messageId: string\n readonly recipients: readonly string[]\n readonly subject: string\n readonly tags: Readonly<Record<string, string>>\n readonly occurredAt: string\n}\n\n/** A verified event. `id` is the Svix identity; use `resend:event:${id}` as the command id. */\nexport type EmailEvent =\n | (EmailEventBase & {\n readonly type: 'email.bounced'\n readonly bounce: {\n readonly type: BounceType\n readonly subType: string | null\n readonly message: string\n }\n })\n | (EmailEventBase & { readonly type: 'email.failed'; readonly reason: string })\n | (EmailEventBase & { readonly type: 'email.clicked'; readonly link: string })\n | (EmailEventBase & { readonly type: (typeof SIMPLE_EMAIL_TYPES)[number] })\n\nconst EventHeaderSchema = z.object({\n type: z.string(),\n created_at: z.iso.datetime({ offset: true }),\n})\n\n/** Validate signed provider data before projecting it into the public event contract. */\nexport function parseEmailEvent(payload: WebhookEventPayload, id: string): EmailEvent | null {\n const header = EventHeaderSchema.parse(payload)\n if (!Object.hasOwn(EMAIL_TYPES, header.type)) return null\n const event = EmailPayloadSchema.parse(payload)\n const base: EmailEventBase = {\n id,\n emailId: event.data.email_id,\n messageId: event.data.message_id,\n recipients: event.data.to,\n subject: event.data.subject,\n tags: event.data.tags,\n occurredAt: header.created_at,\n }\n switch (event.type) {\n case 'email.bounced':\n return {\n ...base,\n type: event.type,\n bounce: {\n type: parseBounceType(event.data.bounce.type),\n subType: event.data.bounce.subType ?? null,\n message: event.data.bounce.message,\n },\n }\n case 'email.failed':\n return { ...base, type: event.type, reason: event.data.failed.reason }\n case 'email.clicked':\n return { ...base, type: event.type, link: event.data.click.link }\n default:\n return { ...base, type: event.type }\n }\n}\n","import type { Resend } from 'resend'\nimport { z } from 'zod'\n\nimport { WebhookVerificationError, type WebhookVerifier } from '../http/webhook-verification.ts'\nimport { parseEmailEvent, type EmailEvent } from './email-event.ts'\n\n// Separate standardwebhooks versions have different prototypes; its error name remains the same.\nconst SignatureErrorSchema = z\n .instanceof(Error)\n .refine((error) => error.name === 'WebhookVerificationError')\n\n/** Verify Svix signatures and map Resend email events. Ported from Typist's Resend route. */\nexport class ResendWebhook implements WebhookVerifier<EmailEvent> {\n /** Load the SDK on the first verification. Only the webhook secret is required. */\n static create(webhookSecret: string): ResendWebhook {\n return new ResendWebhook(webhookSecret)\n }\n\n private client: Resend['webhooks'] | undefined\n\n /** Store the webhook secret. Load the SDK on the first verification. */\n constructor(private readonly webhookSecret: string) {}\n\n /** Verify the raw body and Svix headers. Invalid requests throw typed errors; other failures propagate. */\n async verify(body: string, headers: Headers): Promise<EmailEvent | null> {\n const id = headers.get('svix-id')\n const timestamp = headers.get('svix-timestamp')\n const signature = headers.get('svix-signature')\n if (!id || !timestamp || !signature) throw new WebhookVerificationError('missing_headers')\n const client = await this.getClient()\n let payload: ReturnType<Resend['webhooks']['verify']>\n try {\n payload = client.verify({\n payload: body,\n headers: { id, timestamp, signature },\n webhookSecret: this.webhookSecret,\n })\n } catch (cause) {\n if (cause instanceof SyntaxError) throw new WebhookVerificationError('invalid_payload', cause)\n if (!SignatureErrorSchema.safeParse(cause).success) throw cause\n throw new WebhookVerificationError('invalid_signature', cause)\n }\n try {\n return parseEmailEvent(payload, id)\n } catch (cause) {\n if (cause instanceof z.ZodError) throw new WebhookVerificationError('invalid_payload', cause)\n throw cause\n }\n }\n\n private async getClient(): Promise<Resend['webhooks']> {\n if (this.client === undefined) {\n const { Resend } = await import('resend')\n // The SDK requires a nonempty key even though its local verifier never uses that key.\n this.client ??= new Resend('re_webhook_verification_only').webhooks\n }\n return this.client\n }\n}\n"],"mappings":"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"}
|
package/dist/sentry/client.js
CHANGED
package/dist/sentry/server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { u as LoggerErrorHook } from "../index-DUFS0SJ_.js";
|
|
2
|
-
import {
|
|
2
|
+
import { G as ObservabilityUser, U as ObservabilityScope, W as ObservabilityService } from "../index-tNZHSCpJ.js";
|
|
3
3
|
import * as Sentry from "@sentry/cloudflare";
|
|
4
4
|
//#region src/infrastructure/sentry/server.d.ts
|
|
5
5
|
/** Application-owned deployment settings with the full Cloudflare SDK option contract. */
|
package/dist/tanstack.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { H as StaleSessionError, R as ValidationError, W as AuthenticationError, nt as AppError, r as createLogger } from "./core-
|
|
1
|
+
import { H as StaleSessionError, R as ValidationError, W as AuthenticationError, nt as AppError, r as createLogger } from "./core-BIas38R0.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { getReasonPhrase } from "http-status-codes";
|
|
4
4
|
import { isNotFound, isRedirect, redirect } from "@tanstack/react-router";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { W as DomainEvent } from "../index-DUFS0SJ_.js";
|
|
2
|
-
import { A as IOutboxRepository } from "../index-
|
|
2
|
+
import { A as IOutboxRepository } from "../index-tNZHSCpJ.js";
|
|
3
3
|
//#region src/infrastructure/testing/outbox-repository-contract.d.ts
|
|
4
4
|
/** Stored fields observed by the adapter contract, independent of a database library. */
|
|
5
5
|
export type OutboxContractRow = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { nt as AppError } from "./core-
|
|
1
|
+
import { nt as AppError } from "./core-BIas38R0.js";
|
|
2
2
|
|
|
3
3
|
//#region src/infrastructure/http/webhook-verification.ts
|
|
4
4
|
/** A webhook cannot be trusted or parsed. The HTTP handler logs the rejection once. */
|
|
@@ -15,4 +15,4 @@ var WebhookVerificationError = class extends AppError {
|
|
|
15
15
|
|
|
16
16
|
//#endregion
|
|
17
17
|
export { WebhookVerificationError as t };
|
|
18
|
-
//# sourceMappingURL=webhook-verification-
|
|
18
|
+
//# sourceMappingURL=webhook-verification-CEUARY9X.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-verification-
|
|
1
|
+
{"version":3,"file":"webhook-verification-CEUARY9X.js","names":[],"sources":["../src/infrastructure/http/webhook-verification.ts"],"sourcesContent":["import { AppError } from '@/core'\n\n/** A webhook cannot be trusted or parsed. The HTTP handler logs the rejection once. */\nexport class WebhookVerificationError extends AppError {\n readonly _tag = 'WebhookVerificationError'\n readonly classification = 'terminal'\n\n /** Keep secrets and payload details out of the public message. */\n constructor(\n readonly reason: 'missing_headers' | 'invalid_signature' | 'invalid_payload',\n cause?: unknown,\n ) {\n super(`Webhook rejected: ${reason}`, { cause })\n }\n}\n\n/** The provider owns signature verification, timestamp tolerance, and payload parsing. */\nexport interface WebhookVerifier<Verified> {\n /**\n * Verify the exact body before parsing it. Return null for an ignored webhook.\n * @throws WebhookVerificationError when the request cannot be trusted or parsed.\n */\n verify(body: string, headers: Headers): Promise<Verified | null>\n}\n"],"mappings":";;;;AAGA,IAAa,2BAAb,cAA8C,SAAS;CAM1C;CALX,AAAS,OAAO;CAChB,AAAS,iBAAiB;;CAG1B,YACE,AAAS,QACT,OACA;EACA,MAAM,qBAAqB,UAAU,EAAE,MAAM,CAAC;EAHrC;CAIX;AACF"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://www.schemastore.org/package.json",
|
|
3
3
|
"name": "better-ship",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.21.0",
|
|
5
5
|
"description": "Foundation for applications on Cloudflare Workers: logger, errors, message bus, infrastructure adapters, and UI primitives",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -32,6 +32,9 @@ export type OutboundEmail = {
|
|
|
32
32
|
readonly attachments?: readonly EmailAttachment[]
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/** Received email headers, without the body or attachments. */
|
|
36
|
+
export type ReceivedEmail = { readonly headers: Readonly<Record<string, string>> }
|
|
37
|
+
|
|
35
38
|
/** The provider accepted the email and assigned its id. */
|
|
36
39
|
export type EmailAccepted = { readonly providerId: string }
|
|
37
40
|
|
package/src/application/index.ts
CHANGED
|
@@ -40,7 +40,7 @@ export class EmailServiceError extends AppError {
|
|
|
40
40
|
readonly retryAfter: number | null
|
|
41
41
|
readonly cause: unknown
|
|
42
42
|
}) {
|
|
43
|
-
super(`Email
|
|
43
|
+
super(`Email request failed: ${args.provider} ${args.providerCode}`, { cause: args.cause })
|
|
44
44
|
this.reason = args.reason
|
|
45
45
|
this.classification = CLASSIFICATION_BY_REASON[args.reason]
|
|
46
46
|
this.provider = args.provider
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import posthog, { type PostHog, type PostHogConfig } from 'posthog-js'
|
|
2
2
|
|
|
3
|
+
/** Application properties and native SDK options for browser initialization. */
|
|
4
|
+
export interface PostHogClientOptionsConfig {
|
|
5
|
+
readonly environment: string
|
|
6
|
+
readonly source: string
|
|
7
|
+
readonly options?: Partial<PostHogConfig>
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
/** The application controls enablement; disabled initialization needs no project key. */
|
|
4
11
|
export type PostHogClientConfig =
|
|
5
12
|
| { readonly enabled: false }
|
|
6
|
-
| {
|
|
13
|
+
| (PostHogClientOptionsConfig & {
|
|
7
14
|
readonly enabled: true
|
|
8
15
|
readonly apiKey: string
|
|
9
|
-
|
|
10
|
-
readonly source: string
|
|
11
|
-
readonly options?: Partial<PostHogConfig>
|
|
12
|
-
}
|
|
16
|
+
})
|
|
13
17
|
|
|
14
18
|
/** Browser options from Typist and Porte; SDK option names and application overrides stay intact. */
|
|
15
19
|
export function createPostHogClientOptions(
|
|
16
|
-
config:
|
|
20
|
+
config: PostHogClientOptionsConfig,
|
|
17
21
|
): Partial<PostHogConfig> {
|
|
18
22
|
return {
|
|
19
23
|
api_host: '/relay',
|
|
@@ -37,6 +37,7 @@ const EMAIL_TYPES = {
|
|
|
37
37
|
|
|
38
38
|
const BaseDataSchema = z.object({
|
|
39
39
|
email_id: z.string().min(1),
|
|
40
|
+
message_id: z.string().min(1),
|
|
40
41
|
to: z.array(EmailAddressSchema).min(1),
|
|
41
42
|
subject: z.string(),
|
|
42
43
|
tags: z.record(z.string(), z.string()).default({}),
|
|
@@ -67,6 +68,7 @@ const EmailPayloadSchema = z.discriminatedUnion('type', [
|
|
|
67
68
|
type EmailEventBase = {
|
|
68
69
|
readonly id: string
|
|
69
70
|
readonly emailId: string
|
|
71
|
+
readonly messageId: string
|
|
70
72
|
readonly recipients: readonly string[]
|
|
71
73
|
readonly subject: string
|
|
72
74
|
readonly tags: Readonly<Record<string, string>>
|
|
@@ -100,6 +102,7 @@ export function parseEmailEvent(payload: WebhookEventPayload, id: string): Email
|
|
|
100
102
|
const base: EmailEventBase = {
|
|
101
103
|
id,
|
|
102
104
|
emailId: event.data.email_id,
|
|
105
|
+
messageId: event.data.message_id,
|
|
103
106
|
recipients: event.data.to,
|
|
104
107
|
subject: event.data.subject,
|
|
105
108
|
tags: event.data.tags,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { ResendEmailService
|
|
2
|
-
export { ResendWebhook
|
|
1
|
+
export { ResendEmailService } from './resend-email-service.ts'
|
|
2
|
+
export { ResendWebhook } from './resend-webhook.ts'
|
|
3
3
|
export type { EmailEvent, EmailEventType } from './email-event.ts'
|
|
@@ -2,7 +2,13 @@ import { Buffer } from 'node:buffer'
|
|
|
2
2
|
|
|
3
3
|
import type { Attachment, CreateEmailOptions, ErrorResponse, Resend } from 'resend'
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
EmailAccepted,
|
|
7
|
+
EmailAttachment,
|
|
8
|
+
IEmailService,
|
|
9
|
+
OutboundEmail,
|
|
10
|
+
ReceivedEmail,
|
|
11
|
+
} from '@/application'
|
|
6
12
|
import { EmailServiceError, type EmailSendFailureReason } from '@/core'
|
|
7
13
|
|
|
8
14
|
// SDK additions must receive an explicit classification before the package builds.
|
|
@@ -67,37 +73,27 @@ function toAttachment(attachment: EmailAttachment): Attachment {
|
|
|
67
73
|
return result
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
/** The SDK operations required by the adapter; a test factory can return a plain object. */
|
|
71
|
-
export type ResendEmailClient = { readonly emails: Pick<Resend['emails'], 'send'> }
|
|
72
|
-
|
|
73
76
|
/**
|
|
74
77
|
* Send without package retries or logs. Ported from Suparelay's resend-email-service.ts.
|
|
75
78
|
* The Resend SDK itself logs API errors outside production.
|
|
76
79
|
*/
|
|
77
80
|
export class ResendEmailService implements IEmailService {
|
|
78
|
-
/** Load the SDK on the first
|
|
81
|
+
/** Load the SDK on the first email operation. Constructing the service does not load it. */
|
|
79
82
|
static create(apiKey: string, fromAddress: string): ResendEmailService {
|
|
80
|
-
return new ResendEmailService(
|
|
81
|
-
() => import('resend').then(({ Resend }) => new Resend(apiKey)),
|
|
82
|
-
fromAddress,
|
|
83
|
-
)
|
|
83
|
+
return new ResendEmailService(apiKey, fromAddress)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
private client:
|
|
86
|
+
private client: Resend | undefined
|
|
87
87
|
|
|
88
|
-
/**
|
|
88
|
+
/** Store configuration. Load the SDK and create its client on the first email operation. */
|
|
89
89
|
constructor(
|
|
90
|
-
private readonly
|
|
90
|
+
private readonly apiKey: string,
|
|
91
91
|
private readonly fromAddress: string,
|
|
92
92
|
) {}
|
|
93
93
|
|
|
94
94
|
/** Resolve after acceptance. Provider failures throw EmailServiceError; SDK defects retain their identity. */
|
|
95
95
|
async sendEmail(email: OutboundEmail): Promise<EmailAccepted> {
|
|
96
|
-
|
|
97
|
-
// Cache synchronous factory throws as rejections too, so later sends cannot initialize again.
|
|
98
|
-
this.client = Promise.resolve().then(this.createClient)
|
|
99
|
-
}
|
|
100
|
-
const client = await this.client
|
|
96
|
+
const client = await this.getClient()
|
|
101
97
|
const payload: CreateEmailOptions = {
|
|
102
98
|
from: email.from ?? this.fromAddress,
|
|
103
99
|
to: [email.to].flat(),
|
|
@@ -125,4 +121,30 @@ export class ResendEmailService implements IEmailService {
|
|
|
125
121
|
}
|
|
126
122
|
return { providerId: result.data.id }
|
|
127
123
|
}
|
|
124
|
+
|
|
125
|
+
/** Retrieve headers by the received email's provider ID. Provider failures throw EmailServiceError. */
|
|
126
|
+
async getReceivedEmail(emailId: string): Promise<ReceivedEmail> {
|
|
127
|
+
const client = await this.getClient()
|
|
128
|
+
const result = await client.emails.receiving.get(emailId)
|
|
129
|
+
if (result.error !== null) {
|
|
130
|
+
throw new EmailServiceError({
|
|
131
|
+
reason: reasonFromResend(result.error),
|
|
132
|
+
provider: 'resend',
|
|
133
|
+
providerCode: result.error.name,
|
|
134
|
+
statusCode: result.error.statusCode,
|
|
135
|
+
retryAfter: retryAfterSeconds(result.headers?.['retry-after']),
|
|
136
|
+
cause: result.error,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
return { headers: result.data.headers ?? {} }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private async getClient(): Promise<Resend> {
|
|
143
|
+
if (this.client === undefined) {
|
|
144
|
+
const { Resend } = await import('resend')
|
|
145
|
+
// Recheck after the import so concurrent calls share one client.
|
|
146
|
+
this.client ??= new Resend(this.apiKey)
|
|
147
|
+
}
|
|
148
|
+
return this.client
|
|
149
|
+
}
|
|
128
150
|
}
|