better-ship 0.10.0 → 0.12.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.
Files changed (77) hide show
  1. package/dist/{application-DYfcAy-Q.js → application-CIxo_BpK.js} +32 -3
  2. package/dist/application-CIxo_BpK.js.map +1 -0
  3. package/dist/application.d.ts +3 -3
  4. package/dist/application.js +2 -2
  5. package/dist/better-auth/apple-client-secret.d.ts +1 -1
  6. package/dist/better-auth/apple-client-secret.js +1 -1
  7. package/dist/better-auth/auth-client.js +1 -1
  8. package/dist/better-auth/rate-limit.d.ts +1 -1
  9. package/dist/better-auth/rate-limit.js +1 -1
  10. package/dist/better-auth.js +1 -1
  11. package/dist/cloudflare/auth-secondary-storage.d.ts +1 -1
  12. package/dist/cloudflare/auth-secondary-storage.js +1 -1
  13. package/dist/cloudflare/queue.d.ts +1 -1
  14. package/dist/cloudflare/queue.js +2 -2
  15. package/dist/cloudflare/scheduled.d.ts +1 -1
  16. package/dist/cloudflare/scheduled.js +1 -1
  17. package/dist/cloudflare.d.ts +3 -3
  18. package/dist/cloudflare.js +3 -3
  19. package/dist/{core-DC_4pOrI.js → core-DG_cC8YT.js} +110 -2
  20. package/dist/{core-DC_4pOrI.js.map → core-DG_cC8YT.js.map} +1 -1
  21. package/dist/core.d.ts +2 -2
  22. package/dist/core.js +2 -2
  23. package/dist/http.d.ts +19 -0
  24. package/dist/http.d.ts.map +1 -0
  25. package/dist/http.js +35 -0
  26. package/dist/http.js.map +1 -0
  27. package/dist/{index-6XMPJu7u.d.ts → index-Bl0K1RQW.d.ts} +90 -2
  28. package/dist/index-Bl0K1RQW.d.ts.map +1 -0
  29. package/dist/{index-_Cihfq1J.d.ts → index-DwWxIdQP.d.ts} +137 -55
  30. package/dist/index-DwWxIdQP.d.ts.map +1 -0
  31. package/dist/postgres.d.ts +3 -3
  32. package/dist/postgres.js +3 -3
  33. package/dist/resend.d.ts +73 -0
  34. package/dist/resend.d.ts.map +1 -0
  35. package/dist/resend.js +284 -0
  36. package/dist/resend.js.map +1 -0
  37. package/dist/tanstack/server.d.ts +8 -0
  38. package/dist/tanstack/server.d.ts.map +1 -0
  39. package/dist/tanstack/server.js +16 -0
  40. package/dist/tanstack/server.js.map +1 -0
  41. package/dist/tanstack.d.ts +1 -8
  42. package/dist/tanstack.d.ts.map +1 -1
  43. package/dist/tanstack.js +15 -21
  44. package/dist/tanstack.js.map +1 -1
  45. package/dist/{unit-of-work-context-BFCmU92Z.js → unit-of-work-context-BPE1In2f.js} +2 -2
  46. package/dist/{unit-of-work-context-BFCmU92Z.js.map → unit-of-work-context-BPE1In2f.js.map} +1 -1
  47. package/dist/{unit-of-work-context-BWTJfYPW.d.ts → unit-of-work-context-D5ytJIa-.d.ts} +3 -3
  48. package/dist/{unit-of-work-context-BWTJfYPW.d.ts.map → unit-of-work-context-D5ytJIa-.d.ts.map} +1 -1
  49. package/dist/webhook-verification-BQ9qZnVK.js +18 -0
  50. package/dist/webhook-verification-BQ9qZnVK.js.map +1 -0
  51. package/dist/webhook-verification-sZtwTI5n.d.ts +29 -0
  52. package/dist/webhook-verification-sZtwTI5n.d.ts.map +1 -0
  53. package/package.json +10 -1
  54. package/src/application/email-service.ts +57 -0
  55. package/src/application/index.ts +16 -0
  56. package/src/application/record-email-event.ts +53 -0
  57. package/src/core/email-address.ts +7 -0
  58. package/src/core/email-deliverability.ts +31 -0
  59. package/src/core/email-idempotency-key.ts +34 -0
  60. package/src/core/email-service.error.ts +51 -0
  61. package/src/core/index.ts +18 -0
  62. package/src/core/record-email-event.ts +23 -0
  63. package/src/infrastructure/http/index.ts +6 -0
  64. package/src/infrastructure/http/webhook-handler.ts +35 -0
  65. package/src/infrastructure/http/webhook-verification.ts +27 -0
  66. package/src/infrastructure/resend/email-event.ts +142 -0
  67. package/src/infrastructure/resend/index.ts +3 -0
  68. package/src/infrastructure/resend/resend-email-service.ts +125 -0
  69. package/src/infrastructure/resend/resend-webhook.ts +70 -0
  70. package/src/infrastructure/tanstack/auth.middleware.ts +9 -3
  71. package/src/infrastructure/tanstack/error.middleware.ts +1 -1
  72. package/src/infrastructure/tanstack/index.ts +0 -1
  73. package/src/infrastructure/tanstack/server.ts +7 -0
  74. package/src/infrastructure/tanstack/session-cookies.ts +6 -4
  75. package/dist/application-DYfcAy-Q.js.map +0 -1
  76. package/dist/index-6XMPJu7u.d.ts.map +0 -1
  77. package/dist/index-_Cihfq1J.d.ts.map +0 -1
@@ -1,4 +1,4 @@
1
- import { G as DomainMessage, K as DomainQuery, U as DomainCommand, W as DomainEvent, kt as AppError, q as MessageId } from "./index-6XMPJu7u.js";
1
+ import { G as DomainMessage, K as DomainQuery, Rt as EmailIdempotencyKey, U as DomainCommand, Ut as AppError, Vt as EmailAddress, W as DomainEvent, jt as BounceType, kt as RecordEmailEventPayload, q as MessageId } from "./index-Bl0K1RQW.js";
2
2
  //#region src/application/handlers.d.ts
3
3
  /**
4
4
  * Handles one command with the dependency container and returns the caller's value.
@@ -13,6 +13,63 @@ type QueryHandler<TQuery extends DomainQuery, QueryDeps, Value> = (query: TQuery
13
13
  /** Completes one subscriber's work for an event. Failures throw. */
14
14
  type EventHandler<TEvent extends DomainEvent, Deps> = (event: TEvent, deps: Deps) => Promise<void>;
15
15
  //#endregion
16
+ //#region src/application/email-service.d.ts
17
+ /** A media type such as `application/pdf`. */
18
+ type MimeType = `${string}/${string}`;
19
+ /** A file to attach. Binary content is bytes; the adapter encodes it for the provider. */
20
+ type EmailAttachment = {
21
+ readonly filename: string;
22
+ readonly content: string | Uint8Array;
23
+ /** Optional MIME metadata; Resend accepts filename and content without it. */
24
+ readonly contentType?: MimeType;
25
+ };
26
+ /**
27
+ * One transactional email. Resend accepts 50 recipients, 75 tags with ASCII names and values,
28
+ * and 40 MB of attachments after base64. Sources: https://resend.com/docs/api-reference/emails/send-email
29
+ * and https://resend.com/docs/dashboard/emails/tags
30
+ */
31
+ type OutboundEmail = {
32
+ /** Override the default for this email, for example "Display Name <address@domain.com>". */
33
+ readonly from?: string;
34
+ readonly to: string | readonly string[];
35
+ readonly subject: string;
36
+ readonly html: string;
37
+ /** The stable identity of this send. Resend deduplicates it for 24 hours. */
38
+ readonly idempotencyKey: EmailIdempotencyKey;
39
+ /** Resend generates plain text from html when omitted. An empty string disables that generation. */
40
+ readonly text?: string;
41
+ readonly replyTo?: string;
42
+ /** Labels the provider stores with the email and returns on every delivery event. */
43
+ readonly tags?: Readonly<Record<string, string>>;
44
+ readonly attachments?: readonly EmailAttachment[];
45
+ };
46
+ /** The provider accepted the email and assigned its id. */
47
+ type EmailAccepted = {
48
+ readonly providerId: string;
49
+ };
50
+ /**
51
+ * The adapter supplies the default sender and maps provider errors. Each email can override the sender.
52
+ * A provider rejection throws `EmailServiceError`; unexpected SDK rejections propagate unchanged.
53
+ */
54
+ interface IEmailService {
55
+ /** Resolve after provider acceptance; this does not confirm delivery to the recipient. */
56
+ sendEmail(email: OutboundEmail): Promise<EmailAccepted>;
57
+ }
58
+ /** Records every send for a test. Provider ids count up as `recorded-1`, `recorded-2`. */
59
+ declare class RecordingEmailService implements IEmailService {
60
+ /** Sends in call order, including repeated idempotency keys. */
61
+ readonly sent: OutboundEmail[];
62
+ /** Record the send before returning its provider id. */
63
+ sendEmail(email: OutboundEmail): Promise<EmailAccepted>;
64
+ }
65
+ //#endregion
66
+ //#region src/application/event-collector.d.ts
67
+ /** Records events in the current unit of work; the unit of work collects a snapshot for outbox persistence. */
68
+ interface IEventCollector {
69
+ emit(event: DomainEvent): void;
70
+ collect(): ReadonlyArray<DomainEvent>;
71
+ }
72
+ //#endregion
16
73
  //#region src/application/message-store.d.ts
17
74
  /**
18
75
  * Stores processed messages and outgoing events in the same transaction as application writes.
@@ -25,6 +82,83 @@ interface IMessageStore {
25
82
  persistEvents(events: ReadonlyArray<DomainEvent>): Promise<void>;
26
83
  }
27
84
  //#endregion
85
+ //#region src/application/outbox-relay.d.ts
86
+ /**
87
+ * Delivers committed outbox rows. The unit of work only wakes it after a commit that wrote
88
+ * events; the app decides what a wake is: `waitUntil`, a Durable Object alarm, nothing.
89
+ * The sweep is the source of truth, so a lost wake loses no events.
90
+ */
91
+ interface IOutboxRelay {
92
+ wake(): void;
93
+ }
94
+ //#endregion
95
+ //#region src/application/transaction.d.ts
96
+ /**
97
+ * Supplies `Tx` to participating repositories and resolves after their writes commit. Failure rolls back or discards writes.
98
+ * Nested calls reject with `UnitOfWorkAlreadyActiveError` before opening another boundary.
99
+ * Independent concurrent calls are allowed.
100
+ */
101
+ interface ITransaction<Tx> {
102
+ transaction<T>(work: (tx: Tx) => Promise<T>): Promise<T>;
103
+ }
104
+ //#endregion
105
+ //#region src/application/unit-of-work.d.ts
106
+ /**
107
+ * Commits the processed-message record, repository writes, and emitted events together before resolving.
108
+ * An existing record rejects with `MessageAlreadyProcessedError`; external effects must run through the outbox after commit.
109
+ * Nested calls reject with `UnitOfWorkAlreadyActiveError`; independent concurrent calls are allowed.
110
+ */
111
+ interface IUnitOfWork<Repos> {
112
+ run<T>(messageId: MessageId, work: (repos: Repos) => Promise<T>): Promise<T>;
113
+ }
114
+ type UnitOfWorkDeps<Tx, Repos> = {
115
+ readonly transaction: ITransaction<Tx>;
116
+ /** The processed-messages and outbox repository for this transaction. */
117
+ readonly messageStore: (tx: Tx) => IMessageStore;
118
+ /** Repositories participate in the current transaction. */
119
+ readonly createRepositories: (tx: Tx) => Repos;
120
+ readonly eventCollector: IEventCollector;
121
+ readonly relay: IOutboxRelay;
122
+ };
123
+ /** The unit of work: the boundary, the repositories, the events. Every database call is a port. */
124
+ declare class UnitOfWork<Tx, Repos> implements IUnitOfWork<Repos> {
125
+ private readonly deps;
126
+ constructor(deps: UnitOfWorkDeps<Tx, Repos>);
127
+ run<T>(messageId: MessageId, work: (repos: Repos) => Promise<T>): Promise<T>;
128
+ }
129
+ //#endregion
130
+ //#region src/application/record-email-event.d.ts
131
+ /** What a bounce records for one address. */
132
+ type EmailBounceRecord = {
133
+ readonly type: BounceType;
134
+ readonly subType: string | null;
135
+ };
136
+ /**
137
+ * The application's deliverability state per address. An implementation keeps the higher bounce
138
+ * severity with `shouldUpdateBounce`, and treats an unknown address as nothing to record.
139
+ */
140
+ interface IEmailDeliverabilityStore {
141
+ /** Keep the higher severity atomically. Equal severity permits replacement; the store owns timestamp ordering. */
142
+ recordBounce(address: EmailAddress, bounce: EmailBounceRecord): Promise<void>;
143
+ /** Record a complaint without clearing existing suppression state. */
144
+ recordComplaint(address: EmailAddress): Promise<void>;
145
+ }
146
+ /** The repositories the handler needs from the unit of work. */
147
+ type RecordEmailEventRepos = {
148
+ readonly emailDeliverability: IEmailDeliverabilityStore;
149
+ };
150
+ /** The container slice the handler reads. */
151
+ type RecordEmailEventDeps = {
152
+ readonly uow: IUnitOfWork<RecordEmailEventRepos>;
153
+ };
154
+ /** The command, under whatever name the application registered the payload. */
155
+ type RecordEmailEventCommand = DomainCommand & RecordEmailEventPayload;
156
+ /**
157
+ * Record a bounce or a complaint for every recipient inside the command's unit of work.
158
+ * The command id is the provider's event id, so a redelivered webhook is `MessageAlreadyProcessedError`.
159
+ */
160
+ declare function recordEmailEvent(command: RecordEmailEventCommand, deps: RecordEmailEventDeps): Promise<void>;
161
+ //#endregion
28
162
  //#region src/application/registry.d.ts
29
163
  /** One slot per command. `unknown` keeps the slot covariant; `MessageResult` recovers the value. */
30
164
  type CommandRegistry<TCommand extends DomainCommand, Deps> = { readonly [M in TCommand as M['name']]: CommandHandler<M, Deps, unknown>; };
@@ -94,58 +228,6 @@ declare class MessageBus<Deps, TRegistry extends Handlers<Deps>> implements IMes
94
228
  private handleEvent;
95
229
  }
96
230
  //#endregion
97
- //#region src/application/outbox-relay.d.ts
98
- /**
99
- * Delivers committed outbox rows. The unit of work only wakes it after a commit that wrote
100
- * events; the app decides what a wake is: `waitUntil`, a Durable Object alarm, nothing.
101
- * The sweep is the source of truth, so a lost wake loses no events.
102
- */
103
- interface IOutboxRelay {
104
- wake(): void;
105
- }
106
- //#endregion
107
- //#region src/application/transaction.d.ts
108
- /**
109
- * Supplies `Tx` to participating repositories and resolves after their writes commit. Failure rolls back or discards writes.
110
- * Nested calls reject with `UnitOfWorkAlreadyActiveError` before opening another boundary.
111
- * Independent concurrent calls are allowed.
112
- */
113
- interface ITransaction<Tx> {
114
- transaction<T>(work: (tx: Tx) => Promise<T>): Promise<T>;
115
- }
116
- //#endregion
117
- //#region src/application/event-collector.d.ts
118
- /** Records events in the current unit of work; the unit of work collects a snapshot for outbox persistence. */
119
- interface IEventCollector {
120
- emit(event: DomainEvent): void;
121
- collect(): ReadonlyArray<DomainEvent>;
122
- }
123
- //#endregion
124
- //#region src/application/unit-of-work.d.ts
125
- /**
126
- * Commits the processed-message record, repository writes, and emitted events together before resolving.
127
- * An existing record rejects with `MessageAlreadyProcessedError`; external effects must run through the outbox after commit.
128
- * Nested calls reject with `UnitOfWorkAlreadyActiveError`; independent concurrent calls are allowed.
129
- */
130
- interface IUnitOfWork<Repos> {
131
- run<T>(messageId: MessageId, work: (repos: Repos) => Promise<T>): Promise<T>;
132
- }
133
- type UnitOfWorkDeps<Tx, Repos> = {
134
- readonly transaction: ITransaction<Tx>;
135
- /** The processed-messages and outbox repository for this transaction. */
136
- readonly messageStore: (tx: Tx) => IMessageStore;
137
- /** Repositories participate in the current transaction. */
138
- readonly createRepositories: (tx: Tx) => Repos;
139
- readonly eventCollector: IEventCollector;
140
- readonly relay: IOutboxRelay;
141
- };
142
- /** The unit of work: the boundary, the repositories, the events. Every database call is a port. */
143
- declare class UnitOfWork<Tx, Repos> implements IUnitOfWork<Repos> {
144
- private readonly deps;
145
- constructor(deps: UnitOfWorkDeps<Tx, Repos>);
146
- run<T>(messageId: MessageId, work: (repos: Repos) => Promise<T>): Promise<T>;
147
- }
148
- //#endregion
149
231
  //#region src/application/message-already-processed.error.d.ts
150
232
  /** The message's effects already committed. A caller can skip duplicate delivery without repeating those effects. */
151
233
  declare class MessageAlreadyProcessedError extends AppError {
@@ -167,5 +249,5 @@ declare class UnitOfWorkAlreadyActiveError extends AppError {
167
249
  constructor();
168
250
  }
169
251
  //#endregion
170
- export { RegistryMessage as _, UnitOfWorkDeps as a, EventHandler as b, IOutboxRelay as c, CommandRegistry as d, EventRegistry as f, QueryRegistry as g, MessageResult as h, UnitOfWork as i, IMessageBus as l, MessageRegistry as m, MessageAlreadyProcessedError as n, IEventCollector as o, Handlers as p, IUnitOfWork as r, ITransaction as s, UnitOfWorkAlreadyActiveError as t, MessageBus as u, IMessageStore as v, QueryHandler as x, CommandHandler as y };
171
- //# sourceMappingURL=index-_Cihfq1J.d.ts.map
252
+ export { RecordingEmailService as A, IMessageStore as C, IEmailService as D, EmailAttachment as E, EventHandler as M, QueryHandler as N, MimeType as O, IOutboxRelay as S, EmailAccepted as T, recordEmailEvent as _, CommandRegistry as a, UnitOfWorkDeps as b, MessageRegistry as c, RegistryMessage as d, EmailBounceRecord as f, RecordEmailEventRepos as g, RecordEmailEventDeps as h, MessageBus as i, CommandHandler as j, OutboundEmail as k, MessageResult as l, RecordEmailEventCommand as m, MessageAlreadyProcessedError as n, EventRegistry as o, IEmailDeliverabilityStore as p, IMessageBus as r, Handlers as s, UnitOfWorkAlreadyActiveError as t, QueryRegistry as u, IUnitOfWork as v, IEventCollector as w, ITransaction as x, UnitOfWork as y };
253
+ //# sourceMappingURL=index-DwWxIdQP.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-DwWxIdQP.d.ts","names":[],"sources":["../src/application/handlers.ts","../src/application/email-service.ts","../src/application/event-collector.ts","../src/application/message-store.ts","../src/application/outbox-relay.ts","../src/application/transaction.ts","../src/application/unit-of-work.ts","../src/application/record-email-event.ts","../src/application/registry.ts","../src/application/message-bus.ts","../src/application/message-already-processed.error.ts","../src/application/unit-of-work.errors.ts"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { W as DomainEvent, nt as DatabaseFailure, q as MessageId, tt as DatabaseError } from "./index-6XMPJu7u.js";
2
- import { s as ITransaction, v as IMessageStore } from "./index-_Cihfq1J.js";
3
- import { n as DrizzleSchema, t as eventCollector } from "./unit-of-work-context-BWTJfYPW.js";
1
+ import { W as DomainEvent, nt as DatabaseFailure, q as MessageId, tt as DatabaseError } from "./index-Bl0K1RQW.js";
2
+ import { C as IMessageStore, x as ITransaction } from "./index-DwWxIdQP.js";
3
+ import { n as DrizzleSchema, t as eventCollector } from "./unit-of-work-context-D5ytJIa-.js";
4
4
  import { ExtractTablesWithRelations } from "drizzle-orm";
5
5
  import { PostgresJsDatabase, PostgresJsTransaction, drizzle } from "drizzle-orm/postgres-js";
6
6
  //#region src/infrastructure/postgres/postgres-database-types.d.ts
package/dist/postgres.js CHANGED
@@ -1,6 +1,6 @@
1
- import { E as DatabaseError, U as AppError } from "./core-DC_4pOrI.js";
2
- import { i as MessageAlreadyProcessedError } from "./application-DYfcAy-Q.js";
3
- import { n as eventCollector, r as runWithUnitOfWorkContext } from "./unit-of-work-context-BFCmU92Z.js";
1
+ import { E as DatabaseError, Q as AppError } from "./core-DG_cC8YT.js";
2
+ import { i as MessageAlreadyProcessedError } from "./application-CIxo_BpK.js";
3
+ import { n as eventCollector, r as runWithUnitOfWorkContext } from "./unit-of-work-context-BPE1In2f.js";
4
4
  import { DrizzleQueryError } from "drizzle-orm/errors";
5
5
  import { isNull } from "drizzle-orm";
6
6
  import { index, integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";
@@ -0,0 +1,73 @@
1
+ import { jt as BounceType } from "./index-Bl0K1RQW.js";
2
+ import { D as IEmailService, T as EmailAccepted, k as OutboundEmail } from "./index-DwWxIdQP.js";
3
+ import { r as WebhookVerifier, t as WebhookVerification } from "./webhook-verification-sZtwTI5n.js";
4
+ import { Resend } from "resend";
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
+ /**
11
+ * Send without package retries or logs. Ported from Suparelay's resend-email-service.ts.
12
+ * The Resend SDK itself logs API errors outside production.
13
+ */
14
+ export declare class ResendEmailService implements IEmailService {
15
+ private readonly createClient;
16
+ private readonly fromAddress;
17
+ /** Load the SDK on the first send. Constructing the service does not load it. */
18
+ static create(apiKey: string, fromAddress: string): ResendEmailService;
19
+ private client;
20
+ /** Supply an async factory. The service calls it once, on the first send, and keeps its promise. */
21
+ constructor(createClient: () => Promise<ResendEmailClient>, fromAddress: string);
22
+ /** Resolve after acceptance. Provider failures throw EmailServiceError; SDK defects retain their identity. */
23
+ sendEmail(email: OutboundEmail): Promise<EmailAccepted>;
24
+ }
25
+ //#endregion
26
+ //#region src/infrastructure/resend/email-event.d.ts
27
+ declare const SIMPLE_EMAIL_TYPES: readonly ['email.sent', 'email.scheduled', 'email.delivered', 'email.delivery_delayed', 'email.complained', 'email.opened', 'email.received', 'email.suppressed'];
28
+ /** Resend email event names supported by this module. */
29
+ type EmailEventType = (typeof SIMPLE_EMAIL_TYPES)[number] | 'email.bounced' | 'email.failed' | 'email.clicked';
30
+ type EmailEventBase = {
31
+ readonly id: string;
32
+ readonly emailId: string;
33
+ readonly recipients: readonly string[];
34
+ readonly subject: string;
35
+ readonly tags: Readonly<Record<string, string>>;
36
+ readonly occurredAt: string;
37
+ };
38
+ /** A verified event. `id` is the Svix identity; use `resend:event:${id}` as the command id. */
39
+ type EmailEvent = (EmailEventBase & {
40
+ readonly type: 'email.bounced';
41
+ readonly bounce: {
42
+ readonly type: BounceType;
43
+ readonly subType: string | null;
44
+ readonly message: string;
45
+ };
46
+ }) | (EmailEventBase & {
47
+ readonly type: 'email.failed';
48
+ readonly reason: string;
49
+ }) | (EmailEventBase & {
50
+ readonly type: 'email.clicked';
51
+ readonly link: string;
52
+ }) | (EmailEventBase & {
53
+ readonly type: (typeof SIMPLE_EMAIL_TYPES)[number];
54
+ });
55
+ //#endregion
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
+ /** Verify Svix signatures and map Resend email events. Ported from Typist's Resend route. */
60
+ export declare class ResendWebhook implements WebhookVerifier<EmailEvent> {
61
+ private readonly webhookSecret;
62
+ private readonly verifier;
63
+ /** Load the SDK on the first verification. Only the webhook secret is required. */
64
+ static create(webhookSecret: string): ResendWebhook;
65
+ private client;
66
+ /** Inject the local SDK verifier when testing. */
67
+ constructor(webhookSecret: string, verifier: () => Promise<ResendWebhookClient>);
68
+ /** Verify the raw body and Svix headers. Invalid requests return rejected; configuration and SDK defects propagate. */
69
+ verify(body: string, headers: Headers): Promise<WebhookVerification<EmailEvent>>;
70
+ }
71
+ //#endregion
72
+ export type { EmailEvent, EmailEventType, ResendEmailClient, ResendWebhookClient };
73
+ //# sourceMappingURL=resend.d.ts.map
@@ -0,0 +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":";;;;;;KAsEY;WAA+B,QAAQ,KAAK;;;;;;qBAM3C,8BAA8B;mBAatB;mBACA;;SAZZ,OAAO,gBAAgB,sBAAsB;UAO5C;;EAGR,YACmB,oBAAoB,QAAQ,oBAC5B;;EAIb,UAAU,OAAO,gBAAgB,QAAQ;;;;cCzF3C;;KAYM,yBACA;KAgDP;WACM;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;;;;;KCvEnC,sBAAsB,KAAK;;qBAG1B,yBAAyB,gBAAgB;mBAcjC;mBACA;;SAbZ,OAAO,wBAAwB;UAQ9B;;EAGR,YACmB,uBACA,gBAAgB,QAAQ;;EAIrC,OAAO,cAAc,SAAS,UAAU,QAAQ,oBAAoB"}
package/dist/resend.js ADDED
@@ -0,0 +1,284 @@
1
+ import { J as EmailServiceError, K as parseBounceType, Z as EmailAddressSchema } from "./core-DG_cC8YT.js";
2
+ import { t as WebhookVerificationError } from "./webhook-verification-BQ9qZnVK.js";
3
+ import { z } from "zod";
4
+ import { Buffer } from "node:buffer";
5
+
6
+ //#region src/infrastructure/resend/resend-email-service.ts
7
+ const SDK_REASONS = {
8
+ invalid_idempotency_key: "invalid_request",
9
+ validation_error: "invalid_request",
10
+ missing_api_key: "configuration",
11
+ restricted_api_key: "configuration",
12
+ invalid_api_key: "configuration",
13
+ not_found: "invalid_request",
14
+ method_not_allowed: "invalid_request",
15
+ invalid_idempotent_request: "invalid_request",
16
+ concurrent_idempotent_requests: "idempotency_conflict",
17
+ invalid_attachment: "invalid_request",
18
+ invalid_from_address: "invalid_request",
19
+ invalid_access: "configuration",
20
+ invalid_parameter: "invalid_request",
21
+ invalid_region: "invalid_request",
22
+ missing_required_field: "invalid_request",
23
+ monthly_quota_exceeded: "quota_exceeded",
24
+ daily_quota_exceeded: "quota_exceeded",
25
+ rate_limit_exceeded: "rate_limited",
26
+ security_error: "configuration",
27
+ application_error: "provider_error",
28
+ internal_server_error: "provider_error"
29
+ };
30
+ const DOCUMENTED_REASONS = /* @__PURE__ */ new Map([
31
+ ["email_above_quota", "quota_exceeded"],
32
+ ["invalid_permission", "configuration"],
33
+ ["suspended_api_key", "configuration"],
34
+ ["resource_locked", "idempotency_conflict"],
35
+ ["service_unavailable", "provider_error"],
36
+ ["missing_required_parameter", "invalid_request"]
37
+ ]);
38
+ function reasonFromResend(error) {
39
+ if (Object.hasOwn(SDK_REASONS, error.name)) return SDK_REASONS[error.name];
40
+ const reason = DOCUMENTED_REASONS.get(error.name);
41
+ if (reason !== void 0) return reason;
42
+ if (error.statusCode === 429) return "rate_limited";
43
+ if (error.statusCode !== null && error.statusCode >= 400 && error.statusCode < 500) return "invalid_request";
44
+ return "provider_error";
45
+ }
46
+ function retryAfterSeconds(value) {
47
+ if (value === void 0 || !/^\d+$/.test(value)) return null;
48
+ const seconds = Number(value);
49
+ return Number.isSafeInteger(seconds) ? seconds : null;
50
+ }
51
+ function toAttachment(attachment) {
52
+ const result = {
53
+ filename: attachment.filename,
54
+ content: ArrayBuffer.isView(attachment.content) ? Buffer.from(attachment.content) : attachment.content
55
+ };
56
+ if (attachment.contentType !== void 0) result.contentType = attachment.contentType;
57
+ return result;
58
+ }
59
+ /**
60
+ * Send without package retries or logs. Ported from Suparelay's resend-email-service.ts.
61
+ * The Resend SDK itself logs API errors outside production.
62
+ */
63
+ var ResendEmailService = class ResendEmailService {
64
+ createClient;
65
+ fromAddress;
66
+ /** Load the SDK on the first send. Constructing the service does not load it. */
67
+ static create(apiKey, fromAddress) {
68
+ return new ResendEmailService(() => import("resend").then(({ Resend }) => new Resend(apiKey)), fromAddress);
69
+ }
70
+ client;
71
+ /** Supply an async factory. The service calls it once, on the first send, and keeps its promise. */
72
+ constructor(createClient, fromAddress) {
73
+ this.createClient = createClient;
74
+ this.fromAddress = fromAddress;
75
+ }
76
+ /** Resolve after acceptance. Provider failures throw EmailServiceError; SDK defects retain their identity. */
77
+ async sendEmail(email) {
78
+ if (this.client === void 0) this.client = Promise.resolve().then(this.createClient);
79
+ const client = await this.client;
80
+ const payload = {
81
+ from: email.from ?? this.fromAddress,
82
+ to: [email.to].flat(),
83
+ subject: email.subject,
84
+ html: email.html
85
+ };
86
+ if (email.text !== void 0) payload.text = email.text;
87
+ if (email.replyTo !== void 0) payload.replyTo = email.replyTo;
88
+ if (email.tags !== void 0) payload.tags = Object.entries(email.tags).map(([name, value]) => ({
89
+ name,
90
+ value
91
+ }));
92
+ if (email.attachments !== void 0) payload.attachments = email.attachments.map(toAttachment);
93
+ const result = await client.emails.send(payload, { idempotencyKey: email.idempotencyKey });
94
+ if (result.error !== null) throw new EmailServiceError({
95
+ reason: reasonFromResend(result.error),
96
+ provider: "resend",
97
+ providerCode: result.error.name,
98
+ statusCode: result.error.statusCode,
99
+ retryAfter: retryAfterSeconds(result.headers?.["retry-after"]),
100
+ cause: result.error
101
+ });
102
+ return { providerId: result.data.id };
103
+ }
104
+ };
105
+
106
+ //#endregion
107
+ //#region src/infrastructure/resend/email-event.ts
108
+ const SIMPLE_EMAIL_TYPES = [
109
+ "email.sent",
110
+ "email.scheduled",
111
+ "email.delivered",
112
+ "email.delivery_delayed",
113
+ "email.complained",
114
+ "email.opened",
115
+ "email.received",
116
+ "email.suppressed"
117
+ ];
118
+ const EMAIL_TYPES = {
119
+ "email.sent": true,
120
+ "email.scheduled": true,
121
+ "email.delivered": true,
122
+ "email.delivery_delayed": true,
123
+ "email.complained": true,
124
+ "email.opened": true,
125
+ "email.received": true,
126
+ "email.suppressed": true,
127
+ "email.bounced": true,
128
+ "email.failed": true,
129
+ "email.clicked": true
130
+ };
131
+ const BaseDataSchema = z.object({
132
+ email_id: z.string().min(1),
133
+ to: z.array(EmailAddressSchema).min(1),
134
+ subject: z.string(),
135
+ tags: z.record(z.string(), z.string()).default({})
136
+ });
137
+ const EmailPayloadSchema = z.discriminatedUnion("type", [
138
+ z.object({
139
+ type: z.enum(SIMPLE_EMAIL_TYPES),
140
+ data: BaseDataSchema
141
+ }),
142
+ z.object({
143
+ type: z.literal("email.bounced"),
144
+ data: BaseDataSchema.extend({ bounce: z.object({
145
+ type: z.string().optional(),
146
+ subType: z.string().nullable().optional(),
147
+ message: z.string()
148
+ }) })
149
+ }),
150
+ z.object({
151
+ type: z.literal("email.failed"),
152
+ data: BaseDataSchema.extend({ failed: z.object({ reason: z.string() }) })
153
+ }),
154
+ z.object({
155
+ type: z.literal("email.clicked"),
156
+ data: BaseDataSchema.extend({ click: z.object({ link: z.string() }) })
157
+ })
158
+ ]);
159
+ const EventHeaderSchema = z.object({
160
+ type: z.string(),
161
+ created_at: z.iso.datetime({ offset: true })
162
+ });
163
+ /** Validate signed provider data before projecting it into the public event contract. */
164
+ function parseEmailEvent(payload, id) {
165
+ const header = EventHeaderSchema.safeParse(payload);
166
+ if (!header.success) return { outcome: "invalid" };
167
+ if (!Object.hasOwn(EMAIL_TYPES, header.data.type)) return {
168
+ outcome: "ignored",
169
+ type: header.data.type
170
+ };
171
+ const parsed = EmailPayloadSchema.safeParse(payload);
172
+ if (!parsed.success) return { outcome: "invalid" };
173
+ const event = parsed.data;
174
+ const base = {
175
+ id,
176
+ emailId: event.data.email_id,
177
+ recipients: event.data.to,
178
+ subject: event.data.subject,
179
+ tags: event.data.tags,
180
+ occurredAt: header.data.created_at
181
+ };
182
+ switch (event.type) {
183
+ case "email.bounced": return {
184
+ outcome: "event",
185
+ event: {
186
+ ...base,
187
+ type: event.type,
188
+ bounce: {
189
+ type: parseBounceType(event.data.bounce.type),
190
+ subType: event.data.bounce.subType ?? null,
191
+ message: event.data.bounce.message
192
+ }
193
+ }
194
+ };
195
+ case "email.failed": return {
196
+ outcome: "event",
197
+ event: {
198
+ ...base,
199
+ type: event.type,
200
+ reason: event.data.failed.reason
201
+ }
202
+ };
203
+ case "email.clicked": return {
204
+ outcome: "event",
205
+ event: {
206
+ ...base,
207
+ type: event.type,
208
+ link: event.data.click.link
209
+ }
210
+ };
211
+ default: return {
212
+ outcome: "event",
213
+ event: {
214
+ ...base,
215
+ type: event.type
216
+ }
217
+ };
218
+ }
219
+ }
220
+
221
+ //#endregion
222
+ //#region src/infrastructure/resend/resend-webhook.ts
223
+ const SignatureErrorSchema = z.instanceof(Error).refine((error) => error.name === "WebhookVerificationError");
224
+ /** Verify Svix signatures and map Resend email events. Ported from Typist's Resend route. */
225
+ var ResendWebhook = class ResendWebhook {
226
+ webhookSecret;
227
+ verifier;
228
+ /** Load the SDK on the first verification. Only the webhook secret is required. */
229
+ static create(webhookSecret) {
230
+ return new ResendWebhook(webhookSecret, async () => {
231
+ const { Resend } = await import("resend");
232
+ return new Resend("re_webhook_verification_only").webhooks;
233
+ });
234
+ }
235
+ client;
236
+ /** Inject the local SDK verifier when testing. */
237
+ constructor(webhookSecret, verifier) {
238
+ this.webhookSecret = webhookSecret;
239
+ this.verifier = verifier;
240
+ }
241
+ /** Verify the raw body and Svix headers. Invalid requests return rejected; configuration and SDK defects propagate. */
242
+ async verify(body, headers) {
243
+ const id = headers.get("svix-id");
244
+ const timestamp = headers.get("svix-timestamp");
245
+ const signature = headers.get("svix-signature");
246
+ if (!id || !timestamp || !signature) return {
247
+ outcome: "rejected",
248
+ error: new WebhookVerificationError("missing_headers")
249
+ };
250
+ const client = await (this.client ??= this.verifier());
251
+ let payload;
252
+ try {
253
+ payload = client.verify({
254
+ payload: body,
255
+ headers: {
256
+ id,
257
+ timestamp,
258
+ signature
259
+ },
260
+ webhookSecret: this.webhookSecret
261
+ });
262
+ } catch (cause) {
263
+ if (cause instanceof SyntaxError) return {
264
+ outcome: "rejected",
265
+ error: new WebhookVerificationError("invalid_payload", cause)
266
+ };
267
+ if (!SignatureErrorSchema.safeParse(cause).success) throw cause;
268
+ return {
269
+ outcome: "rejected",
270
+ error: new WebhookVerificationError("invalid_signature", cause)
271
+ };
272
+ }
273
+ const result = parseEmailEvent(payload, id);
274
+ if (result.outcome === "invalid") return {
275
+ outcome: "rejected",
276
+ error: new WebhookVerificationError("invalid_payload")
277
+ };
278
+ return result;
279
+ }
280
+ };
281
+
282
+ //#endregion
283
+ export { ResendEmailService, ResendWebhook };
284
+ //# sourceMappingURL=resend.js.map
@@ -0,0 +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(payload, { idempotencyKey: email.idempotencyKey })\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(\n payload: WebhookEventPayload,\n id: string,\n):\n | { readonly outcome: 'event'; readonly event: EmailEvent }\n | { readonly outcome: 'ignored'; readonly type: string }\n | { readonly outcome: 'invalid' } {\n const header = EventHeaderSchema.safeParse(payload)\n if (!header.success) return { outcome: 'invalid' }\n if (!Object.hasOwn(EMAIL_TYPES, header.data.type))\n return { outcome: 'ignored', type: header.data.type }\n const parsed = EmailPayloadSchema.safeParse(payload)\n if (!parsed.success) return { outcome: 'invalid' }\n const event = parsed.data\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.data.created_at,\n }\n switch (event.type) {\n case 'email.bounced':\n return {\n outcome: 'event',\n event: {\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 }\n case 'email.failed':\n return {\n outcome: 'event',\n event: { ...base, type: event.type, reason: event.data.failed.reason },\n }\n case 'email.clicked':\n return { outcome: 'event', event: { ...base, type: event.type, link: event.data.click.link } }\n default:\n return { outcome: 'event', event: { ...base, type: event.type } }\n }\n}\n","import type { Resend } from 'resend'\nimport { z } from 'zod'\n\nimport {\n WebhookVerificationError,\n type WebhookVerification,\n type WebhookVerifier,\n} 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 return rejected; configuration and SDK defects propagate. */\n async verify(body: string, headers: Headers): Promise<WebhookVerification<EmailEvent>> {\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)\n return { outcome: 'rejected', error: 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)\n return {\n outcome: 'rejected',\n error: new WebhookVerificationError('invalid_payload', cause),\n }\n if (!SignatureErrorSchema.safeParse(cause).success) throw cause\n return {\n outcome: 'rejected',\n error: new WebhookVerificationError('invalid_signature', cause),\n }\n }\n const result = parseEmailEvent(payload, id)\n if (result.outcome === 'invalid')\n return { outcome: 'rejected', error: new WebhookVerificationError('invalid_payload') }\n return result\n }\n}\n"],"mappings":"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"}