sently 0.7.0 → 0.8.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/CHANGELOG.md +31 -0
- package/README.md +223 -18
- package/dist/adapters/bun.js +1 -1
- package/dist/adapters/cf.js +2 -2
- package/dist/adapters/cf.js.map +1 -1
- package/dist/adapters/deno.js +1 -1
- package/dist/adapters/node.js +1 -1
- package/dist/auth/oauth2.js +2 -2
- package/dist/auth/oauth2.js.map +1 -1
- package/dist/{chunk-d5rwm3br.js → chunk-5scdgffb.js} +2 -2
- package/dist/{chunk-d5rwm3br.js.map → chunk-5scdgffb.js.map} +1 -1
- package/dist/{chunk-a1c87hn1.js → chunk-6bkj4y4r.js} +2 -2
- package/dist/{chunk-a1c87hn1.js.map → chunk-6bkj4y4r.js.map} +1 -1
- package/dist/{chunk-cvrbfke9.js → chunk-8phpsns1.js} +3 -3
- package/dist/{chunk-cvrbfke9.js.map → chunk-8phpsns1.js.map} +1 -1
- package/dist/chunk-9zkv5azm.js +4 -0
- package/dist/chunk-9zkv5azm.js.map +10 -0
- package/dist/{chunk-qp4y0kp2.js → chunk-f3dz43cy.js} +2 -2
- package/dist/{chunk-qp4y0kp2.js.map → chunk-f3dz43cy.js.map} +1 -1
- package/dist/chunk-m07smapm.js +3 -0
- package/dist/chunk-m07smapm.js.map +10 -0
- package/dist/chunk-n5dan5bz.js +4 -0
- package/dist/chunk-n5dan5bz.js.map +10 -0
- package/dist/{chunk-keksa6fx.js → chunk-nbmq8ejv.js} +2 -2
- package/dist/{chunk-keksa6fx.js.map → chunk-nbmq8ejv.js.map} +1 -1
- package/dist/chunk-qh90yr6d.js +4 -0
- package/dist/{chunk-gh5n759p.js.map → chunk-qh90yr6d.js.map} +2 -2
- package/dist/{chunk-r7ee9tk7.js → chunk-vhxzhx8b.js} +3 -3
- package/dist/{chunk-r7ee9tk7.js.map → chunk-vhxzhx8b.js.map} +1 -1
- package/dist/{chunk-rm78s1ek.js → chunk-wbvhaqcp.js} +3 -3
- package/dist/{chunk-rm78s1ek.js.map → chunk-wbvhaqcp.js.map} +1 -1
- package/dist/{chunk-74rha3ac.js → chunk-wsymfbkn.js} +3 -3
- package/dist/{chunk-74rha3ac.js.map → chunk-wsymfbkn.js.map} +1 -1
- package/dist/chunk-yr56b0ts.js +4 -0
- package/dist/chunk-yr56b0ts.js.map +11 -0
- package/dist/{chunk-kebhy657.js → chunk-z62q7kqr.js} +2 -2
- package/dist/{chunk-kebhy657.js.map → chunk-z62q7kqr.js.map} +1 -1
- package/dist/{chunk-gkkv7yqm.js → chunk-zst8an2p.js} +3 -3
- package/dist/{chunk-gkkv7yqm.js.map → chunk-zst8an2p.js.map} +1 -1
- package/dist/core/errors.js +2 -2
- package/dist/core/errors.js.map +1 -1
- package/dist/core/provider-label.d.ts +6 -0
- package/dist/core/smtp.js +2 -2
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/types.d.ts +27 -4
- package/dist/detect.js +2 -2
- package/dist/detect.js.map +1 -1
- package/dist/dkim.js +2 -2
- package/dist/dkim.js.map +1 -1
- package/dist/idempotency.d.ts +1 -0
- package/dist/idempotency.js +2 -2
- package/dist/idempotency.js.map +3 -3
- package/dist/index.d.ts +49 -0
- package/dist/index.js +58 -0
- package/dist/mailer.js +2 -2
- package/dist/mailer.js.map +1 -1
- package/dist/observability/console.d.ts +12 -0
- package/dist/observability/console.js +3 -0
- package/dist/observability/console.js.map +10 -0
- package/dist/plugins/react.js +2 -2
- package/dist/plugins/react.js.map +1 -1
- package/dist/plugins/template.js +1 -1
- package/dist/pool/pool.js +2 -2
- package/dist/pool/pool.js.map +1 -1
- package/dist/smtp-mailer.js +2 -2
- package/dist/smtp-mailer.js.map +2 -2
- package/dist/transports/brevo.d.ts +1 -0
- package/dist/transports/brevo.js +2 -2
- package/dist/transports/brevo.js.map +3 -3
- package/dist/transports/cloudflare-email.d.ts +58 -0
- package/dist/transports/cloudflare-email.js +3 -0
- package/dist/transports/cloudflare-email.js.map +10 -0
- package/dist/transports/fallback.d.ts +100 -0
- package/dist/transports/fallback.js +3 -0
- package/dist/transports/fallback.js.map +9 -0
- package/dist/transports/loops.d.ts +32 -0
- package/dist/transports/loops.js +3 -0
- package/dist/transports/loops.js.map +10 -0
- package/dist/transports/mailersend.d.ts +27 -0
- package/dist/transports/mailersend.js +3 -0
- package/dist/transports/mailersend.js.map +10 -0
- package/dist/transports/mailgun.d.ts +1 -0
- package/dist/transports/mailgun.js +2 -2
- package/dist/transports/mailgun.js.map +3 -3
- package/dist/transports/mailtrap.d.ts +36 -0
- package/dist/transports/mailtrap.js +3 -0
- package/dist/transports/mailtrap.js.map +10 -0
- package/dist/transports/plunk.d.ts +29 -0
- package/dist/transports/plunk.js +3 -0
- package/dist/transports/plunk.js.map +10 -0
- package/dist/transports/postmark.d.ts +1 -0
- package/dist/transports/postmark.js +2 -2
- package/dist/transports/postmark.js.map +3 -3
- package/dist/transports/preview.d.ts +1 -0
- package/dist/transports/preview.js +2 -2
- package/dist/transports/preview.js.map +3 -3
- package/dist/transports/resend.d.ts +1 -0
- package/dist/transports/resend.js +2 -2
- package/dist/transports/resend.js.map +3 -3
- package/dist/transports/retry.d.ts +1 -0
- package/dist/transports/retry.js +2 -2
- package/dist/transports/retry.js.map +3 -3
- package/dist/transports/sendgrid.d.ts +1 -0
- package/dist/transports/sendgrid.js +2 -2
- package/dist/transports/sendgrid.js.map +3 -3
- package/dist/transports/ses.d.ts +1 -0
- package/dist/transports/ses.js +3 -3
- package/dist/transports/ses.js.map +3 -3
- package/dist/transports/smtp.d.ts +1 -0
- package/dist/transports/smtp.js +2 -2
- package/dist/transports/smtp.js.map +1 -1
- package/dist/transports/sparkpost.d.ts +30 -0
- package/dist/transports/sparkpost.js +3 -0
- package/dist/transports/sparkpost.js.map +10 -0
- package/dist/transports/weighted-fallback.d.ts +65 -0
- package/dist/transports/weighted-fallback.js +3 -0
- package/dist/transports/weighted-fallback.js.map +10 -0
- package/dist/webhooks/brevo.js +1 -1
- package/dist/webhooks/mailgun.js +1 -1
- package/dist/webhooks/postmark.js +1 -1
- package/dist/webhooks/resend.js +1 -1
- package/dist/webhooks/sendgrid.js +1 -1
- package/dist/webhooks/ses.js +1 -1
- package/dist/webhooks/timing-safe-equal.js +1 -1
- package/package.json +37 -1
- package/dist/chunk-4mvp3b85.js +0 -3
- package/dist/chunk-4mvp3b85.js.map +0 -10
- package/dist/chunk-gh5n759p.js +0 -4
- package/dist/chunk-ka6t8g77.js +0 -4
- package/dist/chunk-ka6t8g77.js.map +0 -11
package/dist/dkim.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{T as G,W as E}from"./chunk-5scdgffb.js";import"./chunk-nbmq8ejv.js";var j=`\r
|
|
2
2
|
`,C="from:to:subject:date:message-id:mime-version:content-type";function _(M,I){let x=F(M),K=[];for(let S of I){let Y=S.toLowerCase().trim(),z=x.get(Y);if(!z)continue;for(let D of z){let w=D.replace(/\r?\n/g,"").replace(/\s+/g," ").trim();K.push(`${Y}:${w}`)}}return K.length>0?`${K.join(j)}${j}`:""}function $(M){let x=M.replace(/\r\n/g,`
|
|
3
3
|
`).replace(/\r/g,`
|
|
4
4
|
`).split(`
|
|
@@ -6,4 +6,4 @@ import{P as G,S as E}from"./chunk-d5rwm3br.js";import"./chunk-keksa6fx.js";var j
|
|
|
6
6
|
\r
|
|
7
7
|
`),w=D>=0?z.slice(0,D):z,P=D>=0?z.slice(D+4):"",T=$(P),O=await k(E(T)),Q=x==="ed25519-sha256"?"ed25519-sha256":"rsa-sha256",X=Y.join(":"),Z=Math.floor(Date.now()/1000),W=["v=1",`a=${Q}`,"c=relaxed/relaxed",`d=${I.domainName}`,`s=${I.keySelector}`,`h=${X}`,`bh=${O}`,"b=",`t=${Z}`].join("; "),b="dkim-signature",H=`${w}${j}dkim-signature:${W}`,p=_(H,[...Y,"dkim-signature"]),A=await v(I.privateKey,x),R=E(p),U=await L(A,R,x),V=G(U).replace(/\r\n/g,"");return{header:`DKIM-Signature: v=1; a=${Q}; c=relaxed/relaxed; d=${I.domainName}; s=${I.keySelector}; h=${X}; bh=${O}; b=${V}; t=${Z}`}}function F(M){let I=new Map,x=M.split(/\r?\n/).filter((z)=>z.length>0),K="",S="",Y=()=>{if(!K)return;let z=K.toLowerCase(),D=I.get(z)??[];D.push(S),I.set(z,D),K="",S=""};for(let z of x){if(/^[ \t]/.test(z)&&K){S+=` ${z.trim()}`;continue}Y();let D=z.indexOf(":");if(D>0)K=z.slice(0,D).trim(),S=z.slice(D+1).trim()}return Y(),I}function B(M){let I=M.replace(/-----BEGIN[^-]+-----/g,"").replace(/-----END[^-]+-----/g,"").replace(/\s/g,""),x=atob(I),K=new Uint8Array(x.length);for(let S=0;S<x.length;S++)K[S]=x.charCodeAt(S);return K}function J(M){return M.buffer.slice(M.byteOffset,M.byteOffset+M.byteLength)}async function k(M){let I=await crypto.subtle.digest("SHA-256",J(M));return G(new Uint8Array(I)).replace(/\r\n/g,"")}async function L(M,I,x){let K=J(I);if(x==="ed25519-sha256"){let Y=await crypto.subtle.sign("Ed25519",M,K);return new Uint8Array(Y)}let S=await crypto.subtle.sign({name:"RSASSA-PKCS1-v1_5"},M,K);return new Uint8Array(S)}export{N as signDKIM,v as importPrivateKey,_ as canonicalizeHeadersRelaxed,$ as canonicalizeBodyRelaxed};
|
|
8
8
|
|
|
9
|
-
//# debugId=
|
|
9
|
+
//# debugId=7BA218874198F54764756E2164756E21
|
package/dist/dkim.js.map
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"/**\n * DKIM signing implementation (RFC 6376). Public entry: `sently/dkim`.\n */\nimport { encodeBase64, encodeUtf8 } from \"./base64.js\";\nimport type { DKIMConfig } from \"./types.js\";\n\nconst CRLF = \"\\r\\n\";\nconst DEFAULT_HEADER_FIELDS = \"from:to:subject:date:message-id:mime-version:content-type\";\n\n/** Result of DKIM signing — the header line to prepend. */\nexport interface DKIMSignResult {\n /** Complete DKIM-Signature header line (without trailing CRLF). */\n header: string;\n}\n\n/**\n * Canonicalize headers using the relaxed algorithm (RFC 6376 §3.4.2).\n */\nexport function canonicalizeHeadersRelaxed(headers: string, fieldNames: string[]): string {\n const parsed = parseHeaders(headers);\n const lines: string[] = [];\n\n for (const name of fieldNames) {\n const key = name.toLowerCase().trim();\n const values = parsed.get(key);\n if (!values) {\n continue;\n }\n for (const value of values) {\n const unfolded = value.replace(/\\r?\\n/g, \"\").replace(/\\s+/g, \" \").trim();\n lines.push(`${key}:${unfolded}`);\n }\n }\n\n return lines.length > 0 ? `${lines.join(CRLF)}${CRLF}` : \"\";\n}\n\n/**\n * Canonicalize body using the relaxed algorithm (RFC 6376 §3.4.4).\n */\nexport function canonicalizeBodyRelaxed(body: string): string {\n const normalized = body.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const lines = normalized\n .split(\"\\n\")\n .map((line) => line.replace(/[ \\t]+$/g, \"\").replace(/[ \\t]+/g, \" \"))\n .filter((line) => line.length > 0);\n if (lines.length === 0) {\n return CRLF;\n }\n return `${lines.join(CRLF)}${CRLF}`;\n}\n\n/**\n * Import a PEM-encoded private key into a Web Crypto CryptoKey.\n */\nexport async function importPrivateKey(\n pem: string,\n algorithm: \"rsa-sha256\" | \"ed25519-sha256\",\n): Promise<CryptoKey> {\n if (algorithm === \"ed25519-sha256\" && /OPENSSH PRIVATE KEY/i.test(pem)) {\n throw new Error(\n \"Ed25519 keys must be in PKCS#8 PEM format (-----BEGIN PRIVATE KEY-----). Convert with: openssl pkcs8 -topk8 -nocrypt -in key.pem -out key_pkcs8.pem\",\n );\n }\n\n const der = pemToDer(pem);\n\n const derBuffer = toArrayBuffer(der);\n\n try {\n if (algorithm === \"ed25519-sha256\") {\n return await crypto.subtle.importKey(\"pkcs8\", derBuffer, { name: \"Ed25519\" }, false, [\n \"sign\",\n ]);\n }\n return await crypto.subtle.importKey(\n \"pkcs8\",\n derBuffer,\n { name: \"RSASSA-PKCS1-v1_5\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n } catch (err) {\n if (algorithm === \"ed25519-sha256\" && err instanceof DOMException) {\n throw new Error(\"Ed25519 DKIM requires Node.js ≥ 18.4, Bun ≥ 1.0, or Cloudflare Workers\", {\n cause: err,\n });\n }\n throw err;\n }\n}\n\n/**\n * Sign a raw MIME message with DKIM.\n */\nexport async function signDKIM(\n rawMessage: Uint8Array,\n config: DKIMConfig,\n): Promise<DKIMSignResult> {\n const algorithm = config.algorithm ?? \"rsa-sha256\";\n const fieldList = (config.headerFieldNames ?? DEFAULT_HEADER_FIELDS)\n .split(\":\")\n .map((f) => f.trim().toLowerCase())\n .filter(Boolean);\n const skip = new Set(\n (config.skipFields ?? \"\")\n .split(\":\")\n .map((f) => f.trim().toLowerCase())\n .filter(Boolean),\n );\n const signFields = fieldList.filter((f) => !skip.has(f));\n\n const text = new TextDecoder().decode(rawMessage);\n const sep = text.indexOf(\"\\r\\n\\r\\n\");\n const headerPart = sep >= 0 ? text.slice(0, sep) : text;\n const bodyPart = sep >= 0 ? text.slice(sep + 4) : \"\";\n\n const bodyCanonical = canonicalizeBodyRelaxed(bodyPart);\n const bodyHash = await sha256Base64(encodeUtf8(bodyCanonical));\n\n const dkimAlgo = algorithm === \"ed25519-sha256\" ? \"ed25519-sha256\" : \"rsa-sha256\";\n const headerList = signFields.join(\":\");\n const timestamp = Math.floor(Date.now() / 1000);\n\n const dkimWithoutSig = [\n `v=1`,\n `a=${dkimAlgo}`,\n `c=relaxed/relaxed`,\n `d=${config.domainName}`,\n `s=${config.keySelector}`,\n `h=${headerList}`,\n `bh=${bodyHash}`,\n `b=`,\n `t=${timestamp}`,\n ].join(\"; \");\n\n const dkimHeaderName = \"dkim-signature\";\n const dkimHeaderValue = dkimWithoutSig;\n const headersWithDkim = `${headerPart}${CRLF}${dkimHeaderName}:${dkimHeaderValue}`;\n const canonical = canonicalizeHeadersRelaxed(headersWithDkim, [...signFields, dkimHeaderName]);\n\n const key = await importPrivateKey(config.privateKey, algorithm);\n const data = encodeUtf8(canonical);\n const signature = await signData(key, data, algorithm);\n const bValue = encodeBase64(signature).replace(/\\r\\n/g, \"\");\n\n const header = `DKIM-Signature: v=1; a=${dkimAlgo}; c=relaxed/relaxed; d=${config.domainName}; s=${config.keySelector}; h=${headerList}; bh=${bodyHash}; b=${bValue}; t=${timestamp}`;\n\n return { header };\n}\n\nfunction parseHeaders(headerBlock: string): Map<string, string[]> {\n const map = new Map<string, string[]>();\n const lines = headerBlock.split(/\\r?\\n/).filter((l) => l.length > 0);\n let currentName = \"\";\n let currentValue = \"\";\n\n const flush = (): void => {\n if (!currentName) {\n return;\n }\n const key = currentName.toLowerCase();\n const list = map.get(key) ?? [];\n list.push(currentValue);\n map.set(key, list);\n currentName = \"\";\n currentValue = \"\";\n };\n\n for (const line of lines) {\n if (/^[ \\t]/.test(line) && currentName) {\n currentValue += ` ${line.trim()}`;\n continue;\n }\n flush();\n const colon = line.indexOf(\":\");\n if (colon > 0) {\n currentName = line.slice(0, colon).trim();\n currentValue = line.slice(colon + 1).trim();\n }\n }\n flush();\n return map;\n}\n\nfunction pemToDer(pem: string): Uint8Array {\n const lines = pem\n .replace(/-----BEGIN[^-]+-----/g, \"\")\n .replace(/-----END[^-]+-----/g, \"\")\n .replace(/\\s/g, \"\");\n const binary = atob(lines);\n const der = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n der[i] = binary.charCodeAt(i);\n }\n return der;\n}\n\nfunction toArrayBuffer(bytes: Uint8Array): ArrayBuffer {\n return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;\n}\n\nasync function sha256Base64(data: Uint8Array): Promise<string> {\n const hash = await crypto.subtle.digest(\"SHA-256\", toArrayBuffer(data));\n return encodeBase64(new Uint8Array(hash)).replace(/\\r\\n/g, \"\");\n}\n\nasync function signData(\n key: CryptoKey,\n data: Uint8Array,\n algorithm: \"rsa-sha256\" | \"ed25519-sha256\",\n): Promise<Uint8Array> {\n const buf = toArrayBuffer(data);\n if (algorithm === \"ed25519-sha256\") {\n const sig = await crypto.subtle.sign(\"Ed25519\", key, buf);\n return new Uint8Array(sig);\n }\n const sig = await crypto.subtle.sign({ name: \"RSASSA-PKCS1-v1_5\" }, key, buf);\n return new Uint8Array(sig);\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "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",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "7BA218874198F54764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/idempotency.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export interface IdempotencyTransportOptions {
|
|
|
29
29
|
export declare class IdempotencyTransport implements Transport {
|
|
30
30
|
/** Transport that performs the actual send when the key is new. */
|
|
31
31
|
private readonly inner;
|
|
32
|
+
readonly provider = "idempotency";
|
|
32
33
|
private readonly store;
|
|
33
34
|
private readonly ttlMs;
|
|
34
35
|
/** Wraps an inner transport with idempotency deduplication. */
|
package/dist/idempotency.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{t as F}from"./chunk-f3dz43cy.js";import{D as C,F as B}from"./chunk-qh90yr6d.js";import"./chunk-5scdgffb.js";import"./chunk-nbmq8ejv.js";class G{entries=new Map;async has(q){let w=this.entries.get(q);if(w===void 0)return!1;if(w<=Date.now())return this.entries.delete(q),!1;return!0}async set(q,w){let z=w!==void 0?Date.now()+w:Number.POSITIVE_INFINITY;this.entries.set(q,z)}}function H(q){let w=C(q.from)[0],z=[...B(q.to),...q.cc?B(q.cc):[],...q.bcc?B(q.bcc):[]];return{messageId:q.messageId??"",accepted:B(q.to),rejected:[],response:"Deduped — idempotency key already recorded",envelope:{from:w?.address??"",to:z},deduped:!0}}class J{inner;provider="idempotency";store;ttlMs;constructor(q,w){this.inner=q;this.store=w?.store??new G,this.ttlMs=w?.ttlMs??86400000}async send(q){let w=F(q);if(w===void 0)return this.inner.send(q);if(await this.store.has(w))return H(q);let z=await this.inner.send(q);return await this.store.set(w,this.ttlMs),z}async sendBatch(q){if(this.inner.sendBatch)return this.inner.sendBatch(q);return Promise.all(q.map((w)=>this.send(w)))}async verify(){if(this.inner.verify)return this.inner.verify();return{ok:!0,provider:"idempotency"}}async close(){await this.inner.close?.()}}export{G as MemoryIdempotencyStore,J as IdempotencyTransport};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=35CA16D52F6C656B64756E2164756E21
|
package/dist/idempotency.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/idempotency.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * IdempotencyTransport — decorator that deduplicates sends on retry or replay.\n *\n * Wrap **outside** RetryTransport so all retry attempts share one key:\n *\n * ```ts\n * new IdempotencyTransport(new RetryTransport(inner))\n * ```\n *\n * For production, supply a shared store (Redis, Dragonfly, etc.).\n *\n * @example\n * ```ts\n * import { IdempotencyTransport, MemoryIdempotencyStore } from \"sently/idempotency\";\n * import { RetryTransport } from \"sently/transports/retry\";\n * import { ResendTransport } from \"sently/transports/resend\";\n *\n * const transport = new IdempotencyTransport(\n * new RetryTransport(new ResendTransport({ apiKey })),\n * { store: new MemoryIdempotencyStore(), ttlMs: 86_400_000 },\n * );\n * ```\n */\nimport { extractEmails, parseAddresses } from \"./core/address.js\";\nimport { resolveIdempotencyKey } from \"./core/idempotency-key.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"./core/types.js\";\n\n/** Key-value store for idempotency deduplication. */\nexport interface IdempotencyStore {\n /** Returns true when the key was already recorded. */\n has(key: string): Promise<boolean>;\n /** Record a key, optionally expiring after `ttlMs` milliseconds. */\n set(key: string, ttlMs?: number): Promise<void>;\n}\n\n/** In-memory idempotency store with optional TTL expiry. Suitable for single-process use. */\nexport class MemoryIdempotencyStore implements IdempotencyStore {\n private readonly entries = new Map<string, number>();\n\n /** Returns true when the key exists and has not expired. */\n async has(key: string): Promise<boolean> {\n const expiresAt = this.entries.get(key);\n if (expiresAt === undefined) {\n return false;\n }\n if (expiresAt <= Date.now()) {\n this.entries.delete(key);\n return false;\n }\n return true;\n }\n\n /** Record a key with optional TTL in milliseconds. */\n async set(key: string, ttlMs?: number): Promise<void> {\n const expiresAt = ttlMs !== undefined ? Date.now() + ttlMs : Number.POSITIVE_INFINITY;\n this.entries.set(key, expiresAt);\n }\n}\n\n/** Options for {@link IdempotencyTransport}. */\nexport interface IdempotencyTransportOptions {\n /** Store used to track sent keys. Defaults to {@link MemoryIdempotencyStore}. */\n store?: IdempotencyStore;\n /** TTL in milliseconds for recorded keys. Default: 24 hours. */\n ttlMs?: number;\n}\n\nfunction syntheticDedupedResult(options: MailOptions): SendResult {\n const from = parseAddresses(options.from)[0];\n const to = [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ];\n\n return {\n messageId: options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: \"Deduped — idempotency key already recorded\",\n envelope: {\n from: from?.address ?? \"\",\n to,\n },\n deduped: true,\n };\n}\n\n/**\n * Transport decorator that skips duplicate sends when an idempotency key\n * was already recorded. Checks the store once before delegating to the inner\n * transport, so retries inside RetryTransport reuse the same key automatically.\n */\nexport class IdempotencyTransport implements Transport {\n private readonly store: IdempotencyStore;\n private readonly ttlMs: number;\n\n /** Wraps an inner transport with idempotency deduplication. */\n constructor(\n /** Transport that performs the actual send when the key is new. */\n private readonly inner: Transport,\n options?: IdempotencyTransportOptions,\n ) {\n this.store = options?.store ?? new MemoryIdempotencyStore();\n this.ttlMs = options?.ttlMs ?? 86_400_000;\n }\n\n /** Sends once per idempotency key; returns a synthetic result on duplicate keys. */\n async send(options: MailOptions): Promise<SendResult> {\n const key = resolveIdempotencyKey(options);\n if (key === undefined) {\n return this.inner.send(options);\n }\n\n if (await this.store.has(key)) {\n return syntheticDedupedResult(options);\n }\n\n const result = await this.inner.send(options);\n await this.store.set(key, this.ttlMs);\n return result;\n }\n\n /** Delegates batch sends to the inner transport when available. */\n async sendBatch(messages: MailOptions[]): Promise<SendResult[]> {\n if (this.inner.sendBatch) {\n return this.inner.sendBatch(messages);\n }\n return Promise.all(messages.map((message) => this.send(message)));\n }\n\n /** Delegates to the inner transport verify or returns a default success result. */\n async verify(): Promise<VerifyResult> {\n if (this.inner.verify) {\n return this.inner.verify();\n }\n return { ok: true, provider: \"idempotency\" };\n }\n\n /** Delegates close to the inner transport if available. */\n async close(): Promise<void> {\n await this.inner.close?.();\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * IdempotencyTransport — decorator that deduplicates sends on retry or replay.\n *\n * Wrap **outside** RetryTransport so all retry attempts share one key:\n *\n * ```ts\n * new IdempotencyTransport(new RetryTransport(inner))\n * ```\n *\n * For production, supply a shared store (Redis, Dragonfly, etc.).\n *\n * @example\n * ```ts\n * import { IdempotencyTransport, MemoryIdempotencyStore } from \"sently/idempotency\";\n * import { RetryTransport } from \"sently/transports/retry\";\n * import { ResendTransport } from \"sently/transports/resend\";\n *\n * const transport = new IdempotencyTransport(\n * new RetryTransport(new ResendTransport({ apiKey })),\n * { store: new MemoryIdempotencyStore(), ttlMs: 86_400_000 },\n * );\n * ```\n */\nimport { extractEmails, parseAddresses } from \"./core/address.js\";\nimport { resolveIdempotencyKey } from \"./core/idempotency-key.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"./core/types.js\";\n\n/** Key-value store for idempotency deduplication. */\nexport interface IdempotencyStore {\n /** Returns true when the key was already recorded. */\n has(key: string): Promise<boolean>;\n /** Record a key, optionally expiring after `ttlMs` milliseconds. */\n set(key: string, ttlMs?: number): Promise<void>;\n}\n\n/** In-memory idempotency store with optional TTL expiry. Suitable for single-process use. */\nexport class MemoryIdempotencyStore implements IdempotencyStore {\n private readonly entries = new Map<string, number>();\n\n /** Returns true when the key exists and has not expired. */\n async has(key: string): Promise<boolean> {\n const expiresAt = this.entries.get(key);\n if (expiresAt === undefined) {\n return false;\n }\n if (expiresAt <= Date.now()) {\n this.entries.delete(key);\n return false;\n }\n return true;\n }\n\n /** Record a key with optional TTL in milliseconds. */\n async set(key: string, ttlMs?: number): Promise<void> {\n const expiresAt = ttlMs !== undefined ? Date.now() + ttlMs : Number.POSITIVE_INFINITY;\n this.entries.set(key, expiresAt);\n }\n}\n\n/** Options for {@link IdempotencyTransport}. */\nexport interface IdempotencyTransportOptions {\n /** Store used to track sent keys. Defaults to {@link MemoryIdempotencyStore}. */\n store?: IdempotencyStore;\n /** TTL in milliseconds for recorded keys. Default: 24 hours. */\n ttlMs?: number;\n}\n\nfunction syntheticDedupedResult(options: MailOptions): SendResult {\n const from = parseAddresses(options.from)[0];\n const to = [\n ...extractEmails(options.to),\n ...(options.cc ? extractEmails(options.cc) : []),\n ...(options.bcc ? extractEmails(options.bcc) : []),\n ];\n\n return {\n messageId: options.messageId ?? \"\",\n accepted: extractEmails(options.to),\n rejected: [],\n response: \"Deduped — idempotency key already recorded\",\n envelope: {\n from: from?.address ?? \"\",\n to,\n },\n deduped: true,\n };\n}\n\n/**\n * Transport decorator that skips duplicate sends when an idempotency key\n * was already recorded. Checks the store once before delegating to the inner\n * transport, so retries inside RetryTransport reuse the same key automatically.\n */\nexport class IdempotencyTransport implements Transport {\n readonly provider = \"idempotency\";\n\n private readonly store: IdempotencyStore;\n private readonly ttlMs: number;\n\n /** Wraps an inner transport with idempotency deduplication. */\n constructor(\n /** Transport that performs the actual send when the key is new. */\n private readonly inner: Transport,\n options?: IdempotencyTransportOptions,\n ) {\n this.store = options?.store ?? new MemoryIdempotencyStore();\n this.ttlMs = options?.ttlMs ?? 86_400_000;\n }\n\n /** Sends once per idempotency key; returns a synthetic result on duplicate keys. */\n async send(options: MailOptions): Promise<SendResult> {\n const key = resolveIdempotencyKey(options);\n if (key === undefined) {\n return this.inner.send(options);\n }\n\n if (await this.store.has(key)) {\n return syntheticDedupedResult(options);\n }\n\n const result = await this.inner.send(options);\n await this.store.set(key, this.ttlMs);\n return result;\n }\n\n /** Delegates batch sends to the inner transport when available. */\n async sendBatch(messages: MailOptions[]): Promise<SendResult[]> {\n if (this.inner.sendBatch) {\n return this.inner.sendBatch(messages);\n }\n return Promise.all(messages.map((message) => this.send(message)));\n }\n\n /** Delegates to the inner transport verify or returns a default success result. */\n async verify(): Promise<VerifyResult> {\n if (this.inner.verify) {\n return this.inner.verify();\n }\n return { ok: true, provider: \"idempotency\" };\n }\n\n /** Delegates close to the inner transport if available. */\n async close(): Promise<void> {\n await this.inner.close?.();\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "iKAqCO,AAAM,LAAmD,LAC7C,QAAU,IAAI,SAGzB,IAAG,CAAC,EAA+B,CACvC,IAAM,EAAY,KAAK,QAAQ,IAAI,CAAG,EACtC,GAAI,IAAc,OAChB,MAAO,GAET,GAAI,GAAa,KAAK,IAAI,EAExB,OADA,KAAK,QAAQ,OAAO,CAAG,EAChB,GAET,MAAO,QAIH,IAAG,CAAC,EAAa,EAA+B,CACpD,IAAM,EAAY,IAAU,OAAY,KAAK,IAAI,EAAI,EAAQ,OAAO,kBACpE,KAAK,QAAQ,IAAI,EAAK,CAAS,EAEnC,CAUA,SAAS,CAAsB,CAAC,EAAkC,CAChE,IAAM,EAAO,EAAe,EAAQ,IAAI,EAAE,GACpC,EAAK,CACT,GAAG,EAAc,EAAQ,EAAE,EAC3B,GAAI,EAAQ,GAAK,EAAc,EAAQ,EAAE,EAAI,CAAC,EAC9C,GAAI,EAAQ,IAAM,EAAc,EAAQ,GAAG,EAAI,CAAC,CAClD,EAEA,MAAO,CACL,UAAW,EAAQ,WAAa,GAChC,SAAU,EAAc,EAAQ,EAAE,EAClC,SAAU,CAAC,EACX,SAAU,6CACV,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,IACF,EACA,QAAS,EACX,EAQK,MAAM,CAA0C,CASlC,MARV,SAAW,cAEH,MACA,MAGjB,WAAW,CAEQ,EACjB,EACA,CAFiB,aAGjB,KAAK,MAAQ,GAAS,OAAS,IAAI,EACnC,KAAK,MAAQ,GAAS,OAAS,cAI3B,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAM,EAAsB,CAAO,EACzC,GAAI,IAAQ,OACV,OAAO,KAAK,MAAM,KAAK,CAAO,EAGhC,GAAI,MAAM,KAAK,MAAM,IAAI,CAAG,EAC1B,OAAO,EAAuB,CAAO,EAGvC,IAAM,EAAS,MAAM,KAAK,MAAM,KAAK,CAAO,EAE5C,OADA,MAAM,KAAK,MAAM,IAAI,EAAK,KAAK,KAAK,EAC7B,OAIH,UAAS,CAAC,EAAgD,CAC9D,GAAI,KAAK,MAAM,UACb,OAAO,KAAK,MAAM,UAAU,CAAQ,EAEtC,OAAO,QAAQ,IAAI,EAAS,IAAI,CAAC,IAAY,KAAK,KAAK,CAAO,CAAC,CAAC,OAI5D,OAAM,EAA0B,CACpC,GAAI,KAAK,MAAM,OACb,OAAO,KAAK,MAAM,OAAO,EAE3B,MAAO,CAAE,GAAI,GAAM,SAAU,aAAc,OAIvC,MAAK,EAAkB,CAC3B,MAAM,KAAK,MAAM,QAAQ,EAE7B",
|
|
8
|
+
"debugId": "35CA16D52F6C656B64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -45,5 +45,54 @@ export {
|
|
|
45
45
|
/** Create a mailer for custom transports (HTTP APIs, preview, retry). Prefer `sently/mailer` for smallest bundles. */
|
|
46
46
|
createMailer, } from "./mailer.js";
|
|
47
47
|
export {
|
|
48
|
+
/** Ready-made console logging hooks for mailer lifecycle events. */
|
|
49
|
+
consoleObserver, } from "./observability/console.js";
|
|
50
|
+
export {
|
|
48
51
|
/** Create a mailer from SMTP host/port config (pooling, adapters). Prefer `sently/smtp` for smallest SMTP bundles. */
|
|
49
52
|
createSMTPMailer, } from "./smtp-mailer.js";
|
|
53
|
+
export type { CloudflareEmailConfig, CloudflareEmailMessage, CloudflareSendEmailFn, } from "./transports/cloudflare-email.js";
|
|
54
|
+
export {
|
|
55
|
+
/** Error thrown when the Cloudflare Email binding rejects a send. */
|
|
56
|
+
CloudflareEmailError,
|
|
57
|
+
/** Cloudflare Workers `send_email` binding transport. */
|
|
58
|
+
CloudflareEmailTransport, } from "./transports/cloudflare-email.js";
|
|
59
|
+
export type { FallbackAttempt, FallbackOptions, FallbackProviderVerifyResult, FallbackVerifyAllResult, } from "./transports/fallback.js";
|
|
60
|
+
export {
|
|
61
|
+
/** Thrown when every transport in a fallback chain fails. */
|
|
62
|
+
FallbackError,
|
|
63
|
+
/** Provider failover decorator — routes through an ordered list of transports. */
|
|
64
|
+
FallbackTransport, } from "./transports/fallback.js";
|
|
65
|
+
export type { LoopsConfig } from "./transports/loops.js";
|
|
66
|
+
export {
|
|
67
|
+
/** Error thrown when the Loops API returns a non-success response. */
|
|
68
|
+
LoopsError,
|
|
69
|
+
/** Loops transactional HTTP API transport (template-first). */
|
|
70
|
+
LoopsTransport, } from "./transports/loops.js";
|
|
71
|
+
export type { MailerSendConfig } from "./transports/mailersend.js";
|
|
72
|
+
export {
|
|
73
|
+
/** Error thrown when the MailerSend API returns a non-success response. */
|
|
74
|
+
MailerSendError,
|
|
75
|
+
/** MailerSend HTTP API transport. */
|
|
76
|
+
MailerSendTransport, } from "./transports/mailersend.js";
|
|
77
|
+
export type { MailtrapConfig } from "./transports/mailtrap.js";
|
|
78
|
+
export {
|
|
79
|
+
/** Error thrown when the Mailtrap API returns a non-success response. */
|
|
80
|
+
MailtrapError,
|
|
81
|
+
/** Mailtrap HTTP API transport. */
|
|
82
|
+
MailtrapTransport, } from "./transports/mailtrap.js";
|
|
83
|
+
export type { PlunkConfig } from "./transports/plunk.js";
|
|
84
|
+
export {
|
|
85
|
+
/** Error thrown when the Plunk API returns a non-success response. */
|
|
86
|
+
PlunkError,
|
|
87
|
+
/** Plunk HTTP API transport. */
|
|
88
|
+
PlunkTransport, } from "./transports/plunk.js";
|
|
89
|
+
export type { SparkPostConfig } from "./transports/sparkpost.js";
|
|
90
|
+
export {
|
|
91
|
+
/** Error thrown when the SparkPost API returns a non-success response. */
|
|
92
|
+
SparkPostError,
|
|
93
|
+
/** SparkPost HTTP API transport. */
|
|
94
|
+
SparkPostTransport, } from "./transports/sparkpost.js";
|
|
95
|
+
export type { WeightedTransportEntry } from "./transports/weighted-fallback.js";
|
|
96
|
+
export {
|
|
97
|
+
/** Weighted provider routing with failover on error. */
|
|
98
|
+
WeightedFallbackTransport, } from "./transports/weighted-fallback.js";
|
package/dist/index.js
CHANGED
|
@@ -24,7 +24,65 @@ export {
|
|
|
24
24
|
|
|
25
25
|
createMailer,
|
|
26
26
|
} from "./mailer.js";
|
|
27
|
+
export {
|
|
28
|
+
|
|
29
|
+
consoleObserver,
|
|
30
|
+
} from "./observability/console.js";
|
|
27
31
|
export {
|
|
28
32
|
|
|
29
33
|
createSMTPMailer,
|
|
30
34
|
} from "./smtp-mailer.js";
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
|
|
38
|
+
CloudflareEmailError,
|
|
39
|
+
|
|
40
|
+
CloudflareEmailTransport,
|
|
41
|
+
} from "./transports/cloudflare-email.js";
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
|
|
45
|
+
FallbackError,
|
|
46
|
+
|
|
47
|
+
FallbackTransport,
|
|
48
|
+
} from "./transports/fallback.js";
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
|
|
52
|
+
LoopsError,
|
|
53
|
+
|
|
54
|
+
LoopsTransport,
|
|
55
|
+
} from "./transports/loops.js";
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
|
|
59
|
+
MailerSendError,
|
|
60
|
+
|
|
61
|
+
MailerSendTransport,
|
|
62
|
+
} from "./transports/mailersend.js";
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
|
|
66
|
+
MailtrapError,
|
|
67
|
+
|
|
68
|
+
MailtrapTransport,
|
|
69
|
+
} from "./transports/mailtrap.js";
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
|
|
73
|
+
PlunkError,
|
|
74
|
+
|
|
75
|
+
PlunkTransport,
|
|
76
|
+
} from "./transports/plunk.js";
|
|
77
|
+
|
|
78
|
+
export {
|
|
79
|
+
|
|
80
|
+
SparkPostError,
|
|
81
|
+
|
|
82
|
+
SparkPostTransport,
|
|
83
|
+
} from "./transports/sparkpost.js";
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
|
|
87
|
+
WeightedFallbackTransport,
|
|
88
|
+
} from "./transports/weighted-fallback.js";
|
package/dist/mailer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{y as a,z as b}from"./chunk-yr56b0ts.js";import"./chunk-9zkv5azm.js";import"./chunk-z62q7kqr.js";import"./chunk-qh90yr6d.js";import"./chunk-5scdgffb.js";import"./chunk-6bkj4y4r.js";import"./chunk-nbmq8ejv.js";export{a as createMailer,b as MailerImpl};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=2CD0C35CD0B1F6FF64756E2164756E21
|
package/dist/mailer.js.map
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Ready-made observability helpers for sently.
|
|
4
|
+
*/
|
|
5
|
+
import type { MailerHooks } from "../core/types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Returns {@link MailerHooks} that log lifecycle events to the console.
|
|
8
|
+
* For production, write your own hooks targeting your metrics/tracing system.
|
|
9
|
+
*
|
|
10
|
+
* @param prefix — log line prefix. Default: `'[sently]'`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function consoleObserver(prefix?: string): MailerHooks;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import"../chunk-nbmq8ejv.js";function E(A="[sently]"){return{onSend:({subject:q})=>{console.log(`${A} sending: ${q}`)},onSuccess:({subject:q},C,z)=>{let B=z!==void 0?` (${z.toFixed(0)}ms)`:"";console.log(`${A} sent: ${q}${B}`)},onError:({subject:q},C,z)=>{let B=z!==void 0?` (${z.toFixed(0)}ms)`:"";console.error(`${A} failed: ${q}${B}`,C)}}}export{E as consoleObserver};
|
|
2
|
+
|
|
3
|
+
//# debugId=0D8FEC4487F45FD464756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/observability/console.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module\n * Ready-made observability helpers for sently.\n */\nimport type { MailerHooks } from \"../core/types.js\";\n\n/**\n * Returns {@link MailerHooks} that log lifecycle events to the console.\n * For production, write your own hooks targeting your metrics/tracing system.\n *\n * @param prefix — log line prefix. Default: `'[sently]'`.\n */\nexport function consoleObserver(prefix = \"[sently]\"): MailerHooks {\n return {\n onSend: ({ subject }) => {\n console.log(`${prefix} sending: ${subject}`);\n },\n onSuccess: ({ subject }, _result, durationMs) => {\n const timing = durationMs !== undefined ? ` (${durationMs.toFixed(0)}ms)` : \"\";\n console.log(`${prefix} sent: ${subject}${timing}`);\n },\n onError: ({ subject }, error, durationMs) => {\n const timing = durationMs !== undefined ? ` (${durationMs.toFixed(0)}ms)` : \"\";\n console.error(`${prefix} failed: ${subject}${timing}`, error);\n },\n };\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "6BAYO,SAAS,CAAe,CAAC,EAAS,WAAyB,CAChE,MAAO,CACL,OAAQ,EAAG,aAAc,CACvB,QAAQ,IAAI,GAAG,cAAmB,GAAS,GAE7C,UAAW,EAAG,WAAW,EAAS,IAAe,CAC/C,IAAM,EAAS,IAAe,OAAY,KAAK,EAAW,QAAQ,CAAC,OAAS,GAC5E,QAAQ,IAAI,GAAG,WAAgB,IAAU,GAAQ,GAEnD,QAAS,EAAG,WAAW,EAAO,IAAe,CAC3C,IAAM,EAAS,IAAe,OAAY,KAAK,EAAW,QAAQ,CAAC,OAAS,GAC5E,QAAQ,MAAM,GAAG,aAAkB,IAAU,IAAU,CAAK,EAEhE",
|
|
8
|
+
"debugId": "0D8FEC4487F45FD464756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/plugins/react.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{_ as B}from"../chunk-nbmq8ejv.js";function F(){return async(j)=>{if(j.react===void 0||j.react===null)return j;let{render:k}=await import("@react-email/render"),q=j.html??await k(j.react,{pretty:!1}),z=j.text??await k(j.react,{plainText:!0}),{react:D,...A}=j;return{...A,html:q,text:z}}}export{F as reactPlugin};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=2453D5063490A51F64756E2164756E21
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
"/**\n * @module\n * React Email plugin for sently.\n * Renders a React element to HTML and plain text via `@react-email/render`.\n *\n * @example\n * ```ts\n * import { reactPlugin } from \"sently/react\";\n * import { WelcomeEmail } from \"./emails/welcome\";\n *\n * const mailer = await createMailer({\n * transport: new ResendTransport({ apiKey }),\n * plugins: [reactPlugin()],\n * });\n *\n * await mailer.send({\n * from: \"...\",\n * to: \"...\",\n * subject: \"Welcome!\",\n * react: WelcomeEmail({ name: \"Ali\" }),\n * });\n * ```\n */\nimport type { MailOptions, MailPlugin } from \"../core/types.js\";\n\n/** Mail options with a React element body (requires `react` peer). */\nexport interface ReactMailOptions extends Omit<MailOptions, \"react\"> {\n /** React element rendered to html + text by the react plugin. */\n react?: import(\"react\").ReactElement;\n}\n\n/**\n * Create a plugin that renders `options.react` to HTML and plain text.\n * Explicit `html` / `text` values always win. The `react` field is stripped\n * before options reach the transport.\n */\nexport function reactPlugin(): MailPlugin {\n return async (options: MailOptions): Promise<MailOptions> => {\n if (options.react === undefined || options.react === null) {\n return options;\n }\n\n const { render } = await import(\"@react-email/render\");\n\n const html =\n options.html ??\n (await render(options.react as Parameters<typeof render>[0], { pretty: false }));\n\n const text =\n options.text ??\n (await render(options.react as Parameters<typeof render>[0], { plainText: true }));\n\n const { react: _react, ...rest } = options;\n return { ...rest, html, text };\n };\n}\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": "yCAoCO,SAAS,CAAW,EAAe,CACxC,MAAO,OAAO,IAA+C,CAC3D,GAAI,EAAQ,QAAU,QAAa,EAAQ,QAAU,KACnD,OAAO,EAGT,IAAQ,UAAW,KAAa,+BAE1B,EACJ,EAAQ,MACP,MAAM,EAAO,EAAQ,MAAuC,CAAE,OAAQ,EAAM,CAAC,EAE1E,EACJ,EAAQ,MACP,MAAM,EAAO,EAAQ,MAAuC,CAAE,UAAW,EAAK,CAAC,GAE1E,MAAO,KAAW,GAAS,EACnC,MAAO,IAAK,EAAM,OAAM,MAAK",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "2453D5063490A51F64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/plugins/template.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"../chunk-
|
|
1
|
+
import"../chunk-nbmq8ejv.js";function E(q,j){return q.replace(/\{\{(\w+)\}\}/g,(z,A)=>{let w=j[A];return w!==void 0&&w!==null?String(w):""})}function F(q){return(j)=>{if(!j.template)return j;let z=q.templates[j.template];if(!z)throw Error(`sently: template "${j.template}" not found`);let A=q.engine(z,j.data??{}),{template:w,data:D,...B}=j;return{...B,html:A}}}export{F as templatePlugin,E as simpleEngine};
|
|
2
2
|
|
|
3
3
|
//# debugId=094B3D1AC9B011AB64756E2164756E21
|
package/dist/pool/pool.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{l as B,m as Q,n as U,o as V}from"../chunk-m07smapm.js";import"../chunk-wbvhaqcp.js";import{u as K}from"../chunk-zst8an2p.js";import{v as N}from"../chunk-vhxzhx8b.js";import{B as G}from"../chunk-z62q7kqr.js";import"../chunk-qh90yr6d.js";import"../chunk-8phpsns1.js";import"../chunk-5scdgffb.js";import"../chunk-6bkj4y4r.js";import"../chunk-nbmq8ejv.js";async function W(k){let q=B(k.config),w=await k.createAdapter();await w.connect(k.connectHost,q.port),await Q(w,q);let z=0,D=!0,F=Promise.resolve(),X=k.maxMessages;return{get idle(){return D},get messageCount(){return z},get usable(){return z<X},async send(H){let Y=async()=>{D=!1;try{let Z={...H,attachments:await N(H.attachments)},_=await K(Z,q.dkim),$=await U(w,_);return z+=1,$}finally{D=!0}},J=F.then(Y);return F=J.then(()=>{return},()=>{return}),J},async close(){await F,await V(w)}}}class b{config;maxConnections;maxMessages;createAdapterFn;rateLimiter;connections=[];queue=[];draining=!1;closed=!1;processChain=Promise.resolve();constructor(k,q){if(this.config=k,this.maxConnections=k.maxConnections??5,this.maxMessages=k.maxMessages??100,q?.createAdapter){let w=q.createAdapter;this.createAdapterFn=async()=>w()}else if(k.adapter)this.createAdapterFn=async()=>k.adapter;else throw Error("SMTPPool requires config.adapter or options.createAdapter");if(k.rateDelta!==void 0&&k.rateDelta>0)this.rateLimiter=new G(k.rateDelta,k.rateLimit??1000,q?.now);else this.rateLimiter=null}async send(k){if(this.draining)throw Error("SMTPPool is closing — no new messages accepted");if(this.closed)throw Error("SMTPPool is closed");if(this.rateLimiter)await this.rateLimiter.acquire();return new Promise((q,w)=>{this.queue.push({options:k,resolve:q,reject:w}),this.scheduleProcess()})}scheduleProcess(){this.processChain=this.processChain.then(()=>this.processQueue()).catch(()=>{return})}async verify(){try{let k=await this.spawnConnection();try{return{ok:!0,provider:"smtp-pool"}}finally{await k.close(),this.removeConnection(k)}}catch(k){return{ok:!1,provider:"smtp-pool",message:k instanceof Error?k.message:String(k)}}}async close(){this.draining=!0,await this.drainQueue(),await Promise.allSettled(this.connections.map((k)=>k.close())),this.connections.length=0,this.closed=!0}get connectionCount(){return this.connections.length}get queueSize(){return this.queue.length}async processQueue(){if(this.draining)return;while(this.queue.length>0){let k=this.connections.find((q)=>q.idle&&q.usable);if(k){let q=this.queue.shift();if(!q)break;try{let w=await k.send(q.options);if(q.resolve(w),!k.usable)await k.close(),this.removeConnection(k)}catch(w){q.reject(w),await k.close().catch(()=>{return}),this.removeConnection(k)}continue}if(this.connections.length<this.maxConnections){let q=this.queue.shift();if(!q)break;let w=await this.spawnConnection();try{let z=await w.send(q.options);if(q.resolve(z),!w.usable)await w.close(),this.removeConnection(w)}catch(z){q.reject(z),await w.close().catch(()=>{return}),this.removeConnection(w)}continue}break}}async spawnConnection(){let k=B(this.config),q=await W({config:this.config,maxMessages:this.maxMessages,connectHost:k.host,createAdapter:this.createAdapterFn});return this.connections.push(q),q}removeConnection(k){let q=this.connections.indexOf(k);if(q>=0)this.connections.splice(q,1)}async drainQueue(){while(this.queue.length>0||this.connections.some((k)=>!k.idle))if(await this.processQueue(),this.queue.length>0)await new Promise((k)=>setTimeout(k,10))}}export{b as SMTPPool,G as RateLimiter};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=EFD81E528113B8E564756E2164756E21
|
package/dist/pool/pool.js.map
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
"/**\n * @module\n * SMTP connection pool with optional rate limiting.\n *\n * @example\n * ```ts\n * import { SMTPPool } from \"sently/pool\";\n * import { NodeAdapter } from \"sently/adapters/node\";\n *\n * const pool = new SMTPPool({\n * host: \"smtp.example.com\",\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * adapter: new NodeAdapter(),\n * pool: true,\n * maxConnections: 5,\n * });\n *\n * await pool.send({\n * from: \"you@example.com\",\n * to: \"recipient@example.com\",\n * subject: \"Hello\",\n * text: \"Pooled send\",\n * });\n * ```\n */\n\nimport { RateLimiter } from \"../core/rate-limiter.js\";\nimport type {\n MailOptions,\n PoolConfig,\n SendResult,\n SMTPConfig,\n SocketAdapter,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveSMTPConfig } from \"../transports/smtp.js\";\nimport { createPooledConnection, type PooledConnection } from \"./connection.js\";\n\n/** Options for {@link SMTPPool}. */\nexport interface SMTPPoolOptions {\n /** Factory for a new socket adapter per pooled connection. */\n createAdapter?: () => Promise<SocketAdapter> | SocketAdapter;\n /** Injectable clock for rate limiting (testing). */\n now?: () => number;\n}\n\ninterface QueueEntry {\n options: MailOptions;\n resolve: (result: SendResult) => void;\n reject: (error: unknown) => void;\n}\n\n/**\n * SMTP connection pool with optional rate limiting.\n */\nexport class SMTPPool implements Transport {\n /** Resolved SMTP and pool configuration. */\n private readonly config: SMTPConfig & PoolConfig;\n /** Maximum simultaneous pooled connections. */\n private readonly maxConnections: number;\n /** Maximum messages per connection before recycle. */\n private readonly maxMessages: number;\n /** Factory that creates a socket adapter for each new connection. */\n private readonly createAdapterFn: () => Promise<SocketAdapter>;\n /** Optional token-bucket rate limiter, or null when disabled. */\n private readonly rateLimiter: RateLimiter | null;\n /** Active pooled SMTP connections. */\n private readonly connections: PooledConnection[] = [];\n /** Pending send operations waiting for a connection. */\n private readonly queue: QueueEntry[] = [];\n /** True while {@link close} is draining; rejects new sends. */\n private draining = false;\n /** True after the pool has fully closed. */\n private closed = false;\n /** Serializes queue processing to avoid concurrent drain races. */\n private processChain: Promise<void> = Promise.resolve();\n\n /** Creates an SMTP connection pool. */\n constructor(config: SMTPConfig & PoolConfig, options?: SMTPPoolOptions) {\n this.config = config;\n this.maxConnections = config.maxConnections ?? 5;\n this.maxMessages = config.maxMessages ?? 100;\n\n if (options?.createAdapter) {\n const factory = options.createAdapter;\n this.createAdapterFn = async () => factory();\n } else if (config.adapter) {\n this.createAdapterFn = async () => config.adapter as SocketAdapter;\n } else {\n throw new Error(\"SMTPPool requires config.adapter or options.createAdapter\");\n }\n\n if (config.rateDelta !== undefined && config.rateDelta > 0) {\n this.rateLimiter = new RateLimiter(config.rateDelta, config.rateLimit ?? 1000, options?.now);\n } else {\n this.rateLimiter = null;\n }\n }\n\n /** Sends a message through the pool. */\n async send(options: MailOptions): Promise<SendResult> {\n if (this.draining) {\n throw new Error(\"SMTPPool is closing — no new messages accepted\");\n }\n if (this.closed) {\n throw new Error(\"SMTPPool is closed\");\n }\n if (this.rateLimiter) {\n await this.rateLimiter.acquire();\n }\n return new Promise<SendResult>((resolve, reject) => {\n this.queue.push({ options, resolve, reject });\n this.scheduleProcess();\n });\n }\n\n /** Schedules asynchronous processing of the send queue. */\n private scheduleProcess(): void {\n this.processChain = this.processChain.then(() => this.processQueue()).catch(() => undefined);\n }\n\n /** Verifies connectivity using a temporary connection. */\n async verify(): Promise<VerifyResult> {\n try {\n const conn = await this.spawnConnection();\n try {\n return { ok: true, provider: \"smtp-pool\" };\n } finally {\n await conn.close();\n this.removeConnection(conn);\n }\n } catch (err) {\n return {\n ok: false,\n provider: \"smtp-pool\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /** Drains the queue and closes all connections. */\n async close(): Promise<void> {\n this.draining = true;\n await this.drainQueue();\n await Promise.allSettled(this.connections.map((c) => c.close()));\n this.connections.length = 0;\n this.closed = true;\n }\n\n /** Current number of open pooled connections. */\n get connectionCount(): number {\n return this.connections.length;\n }\n\n /** Number of messages waiting in the send queue. */\n get queueSize(): number {\n return this.queue.length;\n }\n\n /** Dispatches queued messages to idle or newly spawned connections. */\n private async processQueue(): Promise<void> {\n if (this.draining) {\n return;\n }\n\n while (this.queue.length > 0) {\n const idleConn = this.connections.find((c) => c.idle && c.usable);\n if (idleConn) {\n const entry = this.queue.shift();\n if (!entry) {\n break;\n }\n try {\n const result = await idleConn.send(entry.options);\n entry.resolve(result);\n if (!idleConn.usable) {\n await idleConn.close();\n this.removeConnection(idleConn);\n }\n } catch (err) {\n entry.reject(err);\n await idleConn.close().catch(() => undefined);\n this.removeConnection(idleConn);\n }\n continue;\n }\n\n if (this.connections.length < this.maxConnections) {\n const entry = this.queue.shift();\n if (!entry) {\n break;\n }\n const conn = await this.spawnConnection();\n try {\n const result = await conn.send(entry.options);\n entry.resolve(result);\n if (!conn.usable) {\n await conn.close();\n this.removeConnection(conn);\n }\n } catch (err) {\n entry.reject(err);\n await conn.close().catch(() => undefined);\n this.removeConnection(conn);\n }\n continue;\n }\n\n break;\n }\n }\n\n /** Opens a new authenticated pooled SMTP connection. */\n private async spawnConnection(): Promise<PooledConnection> {\n const resolved = resolveSMTPConfig(this.config);\n const conn = await createPooledConnection({\n config: this.config,\n maxMessages: this.maxMessages,\n connectHost: resolved.host,\n createAdapter: this.createAdapterFn,\n });\n this.connections.push(conn);\n return conn;\n }\n\n /** Removes a connection from the active pool list. */\n private removeConnection(conn: PooledConnection): void {\n const index = this.connections.indexOf(conn);\n if (index >= 0) {\n this.connections.splice(index, 1);\n }\n }\n\n /** Waits until the send queue and in-flight work are fully drained. */\n private async drainQueue(): Promise<void> {\n while (this.queue.length > 0 || this.connections.some((c) => !c.idle)) {\n await this.processQueue();\n if (this.queue.length > 0) {\n await new Promise((r) => setTimeout(r, 10));\n }\n }\n }\n}\n\n/** @internal Exposed for deterministic rate limiter tests. */\nexport { RateLimiter } from \"../core/rate-limiter.js\";\n"
|
|
7
7
|
],
|
|
8
8
|
"mappings": "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",
|
|
9
|
-
"debugId": "
|
|
9
|
+
"debugId": "EFD81E528113B8E564756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|
package/dist/smtp-mailer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{x as g}from"./chunk-wsymfbkn.js";import{z as k}from"./chunk-yr56b0ts.js";import"./chunk-9zkv5azm.js";import"./chunk-z62q7kqr.js";import"./chunk-qh90yr6d.js";import"./chunk-5scdgffb.js";import"./chunk-6bkj4y4r.js";import{_ as v}from"./chunk-nbmq8ejv.js";async function F(b){let q={...b.secure!==void 0?{secure:b.secure}:{},...b.connectionTimeout!==void 0?{connectionTimeout:b.connectionTimeout}:{},...b.tls!==void 0?{tls:b.tls}:{}};if(b.pool){let{SMTPPool:z}=await import("./pool/pool.js");return new k(new z(b,{createAdapter:async()=>b.adapter??await g(q)}),b.plugins,b.hooks)}let w=b.adapter??await g(q),{SMTPTransport:x}=await import("./transports/smtp.js");return new k(new x({...b,adapter:w}),b.plugins,b.hooks)}export{F as createSMTPMailer};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=837085A2C030C9F464756E2164756E21
|
package/dist/smtp-mailer.js.map
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @module\n * SMTP-capable mailer factory — host/port config, pooling, and runtime adapters.\n *\n * Import from `sently/smtp` or use {@link createSMTPMailer} from the main package.\n * For HTTP transports, use `sently/mailer` instead (~10 KB smaller in app bundles).\n *\n * @example\n * ```ts\n * import { createSMTPMailer } from \"sently/smtp\";\n *\n * const mailer = await createSMTPMailer({\n * host: \"smtp.example.com\",\n * port: 587,\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * });\n * ```\n */\nimport type { Mailer, SMTPMailerOptions } from \"./core/types.js\";\nimport { createDefaultAdapter } from \"./detect.js\";\nimport { MailerImpl } from \"./mailer.js\";\n\n/**\n * Create a mailer from SMTP relay options (host, port, auth, pool, adapter).\n */\nexport async function createSMTPMailer(options: SMTPMailerOptions): Promise<Mailer> {\n const adapterOptions = {\n ...(options.secure !== undefined ? { secure: options.secure } : {}),\n ...(options.connectionTimeout !== undefined\n ? { connectionTimeout: options.connectionTimeout }\n : {}),\n ...(options.tls !== undefined ? { tls: options.tls } : {}),\n };\n\n if (options.pool) {\n const { SMTPPool } = await import(\"./pool/pool.js\");\n return new MailerImpl(\n new SMTPPool(options, {\n createAdapter: async () => options.adapter ?? (await createDefaultAdapter(adapterOptions)),\n }),\n options.plugins,\n options.hooks,\n );\n }\n\n const adapter = options.adapter ?? (await createDefaultAdapter(adapterOptions));\n const { SMTPTransport } = await import(\"./transports/smtp.js\");\n\n return new MailerImpl(new SMTPTransport({ ...options, adapter }), options.plugins, options.hooks);\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "8SAyBA,SAAsB,LAAgB,LAAC,JAA6C,LAClF,FAAM,JAAiB,IACjB,EAAQ,SAAW,OAAY,CAAE,OAAQ,EAAQ,MAAO,EAAI,CAAC,KAC7D,EAAQ,oBAAsB,OAC9B,CAAE,kBAAmB,EAAQ,iBAAkB,EAC/C,CAAC,KACD,EAAQ,MAAQ,OAAY,CAAE,IAAK,EAAQ,GAAI,EAAI,CAAC,CAC1D,EAEA,GAAI,EAAQ,KAAM,CAChB,IAAQ,YAAa,KAAa,0BAClC,OAAO,IAAI,EACT,IAAI,EAAS,EAAS,CACpB,cAAe,SAAY,EAAQ,SAAY,MAAM,EAAqB,CAAc,CAC1F,CAAC,EACD,EAAQ,QACR,EAAQ,KACV,EAGF,IAAM,EAAU,EAAQ,SAAY,MAAM,EAAqB,CAAc,GACrE,iBAAkB,KAAa,gCAEvC,OAAO,IAAI,EAAW,IAAI,EAAc,IAAK,EAAS,SAAQ,CAAC,EAAG,EAAQ,QAAS,EAAQ,KAAK",
|
|
8
|
+
"debugId": "837085A2C030C9F464756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -10,6 +10,7 @@ export declare class BrevoError extends SentlyError {
|
|
|
10
10
|
* Brevo HTTP API transport.
|
|
11
11
|
*/
|
|
12
12
|
export declare class BrevoTransport implements Transport {
|
|
13
|
+
readonly provider = "brevo";
|
|
13
14
|
/** Brevo API key used for Authorization. */
|
|
14
15
|
private readonly apiKey;
|
|
15
16
|
/** Creates a Brevo transport with the given API key. */
|
package/dist/transports/brevo.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{v as R}from"../chunk-vhxzhx8b.js";import{D as G,F as M}from"../chunk-qh90yr6d.js";import{T as L}from"../chunk-5scdgffb.js";import{X as N,Y as Q}from"../chunk-6bkj4y4r.js";import"../chunk-nbmq8ejv.js";class U extends N{statusCode;constructor(q,w,D){super(q,Q(w),{statusCode:w,provider:"brevo"});this.statusCode=w;this.name="BrevoError",this.code=D}}function J(q){return G(q).map((w)=>({email:w.address,...w.name?{name:w.name}:{}}))}class X{provider="brevo";apiKey;constructor(q){this.apiKey=q.apiKey}async send(q){let w=await R(q.attachments),D=G(q.from)[0],W={sender:D?{email:D.address,...D.name?{name:D.name}:{}}:{email:""},to:J(q.to),subject:q.subject,...q.cc?{cc:J(q.cc)}:{},...q.bcc?{bcc:J(q.bcc)}:{},...q.replyTo?{replyTo:(()=>{let z=G(q.replyTo)[0];return z?{email:z.address,...z.name?{name:z.name}:{}}:void 0})()}:{},...q.html?{htmlContent:q.html}:{},...q.text?{textContent:q.text}:{},...w.length>0?{attachment:w.map((z)=>({name:z.filename,content:z.content instanceof Uint8Array?L(z.content).replace(/\r\n/g,""):z.content}))}:{}},H=await fetch("https://api.brevo.com/v3/smtp/email",{method:"POST",headers:{"api-key":this.apiKey,"Content-Type":"application/json"},body:JSON.stringify(W)}),F=await H.json();if(!H.ok)throw new U(F.message??"Brevo API error",H.status,F.code??"");let K=M(q.to);return{messageId:F.messageId??"",accepted:K,rejected:[],response:F.messageId??"sent",envelope:{from:D?.address??"",to:K}}}async verify(){try{let q=await fetch("https://api.brevo.com/v3/account",{headers:{"api-key":this.apiKey}}),w=await q.json();if(!q.ok)return{ok:!1,provider:"brevo",message:w.message??`HTTP ${q.status}`};return{ok:!0,provider:"brevo",...w.companyName?{message:w.companyName}:{}}}catch(q){return{ok:!1,provider:"brevo",message:q instanceof Error?q.message:String(q)}}}}export{X as BrevoTransport,U as BrevoError};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=6B0A1C06C762C9F964756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/brevo.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Brevo (formerly Sendinblue) HTTP transport for sently.\n *\n * @example\n * ```ts\n * import { BrevoTransport } from \"sently/transports/brevo\";\n * import { createMailer } from \"sently/mailer\";\n *\n * const mailer = await createMailer({\n * transport: new BrevoTransport({ apiKey: \"xkeysib-...\" }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type {\n BrevoConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Brevo API returns a non-success response. */\nexport class BrevoError extends SentlyError {\n /** Creates a Brevo API error with status code and error code. */\n constructor(\n message: string,\n public readonly statusCode: number,\n apiCode: string,\n ) {\n super(message, httpStatusToSentlyCode(statusCode), {\n statusCode,\n provider: \"brevo\",\n });\n this.name = \"BrevoError\";\n this.code = apiCode;\n }\n}\n\nfunction toAddressObjects(input: MailOptions[\"to\"]): Array<{ email: string; name?: string }> {\n return parseAddresses(input).map((addr) => ({\n email: addr.address,\n ...(addr.name ? { name: addr.name } : {}),\n }));\n}\n\n/**\n * Brevo HTTP API transport.\n */\nexport class BrevoTransport implements Transport {\n /** Brevo API key used for Authorization. */\n private readonly apiKey: string;\n\n /** Creates a Brevo transport with the given API key. */\n constructor(config: BrevoConfig) {\n this.apiKey = config.apiKey;\n }\n\n /** Sends an email via the Brevo HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const body: Record<string, unknown> = {\n sender: from\n ? { email: from.address, ...(from.name ? { name: from.name } : {}) }\n : { email: \"\" },\n to: toAddressObjects(options.to),\n subject: options.subject,\n ...(options.cc ? { cc: toAddressObjects(options.cc) } : {}),\n ...(options.bcc ? { bcc: toAddressObjects(options.bcc) } : {}),\n ...(options.replyTo\n ? {\n replyTo: (() => {\n const reply = parseAddresses(options.replyTo as MailOptions[\"to\"])[0];\n return reply\n ? { email: reply.address, ...(reply.name ? { name: reply.name } : {}) }\n : undefined;\n })(),\n }\n : {}),\n ...(options.html ? { htmlContent: options.html } : {}),\n ...(options.text ? { textContent: options.text } : {}),\n ...(attachments.length > 0\n ? {\n attachment: attachments.map((att) => ({\n name: att.filename,\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.brevo.com/v3/smtp/email\", {\n method: \"POST\",\n headers: {\n \"api-key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as {\n messageId?: string;\n message?: string;\n code?: string;\n };\n\n if (!response.ok) {\n throw new BrevoError(\n payload.message ?? \"Brevo API error\",\n response.status,\n payload.code ?? \"\",\n );\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.messageId ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.messageId ?? \"sent\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Brevo API key by fetching account info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.brevo.com/v3/account\", {\n headers: {\n \"api-key\": this.apiKey,\n },\n });\n\n const payload = (await response.json()) as { companyName?: string; message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"brevo\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"brevo\",\n ...(payload.companyName ? { message: payload.companyName } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"brevo\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * Brevo (formerly Sendinblue) HTTP transport for sently.\n *\n * @example\n * ```ts\n * import { BrevoTransport } from \"sently/transports/brevo\";\n * import { createMailer } from \"sently/mailer\";\n *\n * const mailer = await createMailer({\n * transport: new BrevoTransport({ apiKey: \"xkeysib-...\" }),\n * });\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { httpStatusToSentlyCode, SentlyError } from \"../core/errors.js\";\nimport type {\n BrevoConfig,\n MailOptions,\n SendResult,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Error thrown when the Brevo API returns a non-success response. */\nexport class BrevoError extends SentlyError {\n /** Creates a Brevo API error with status code and error code. */\n constructor(\n message: string,\n public readonly statusCode: number,\n apiCode: string,\n ) {\n super(message, httpStatusToSentlyCode(statusCode), {\n statusCode,\n provider: \"brevo\",\n });\n this.name = \"BrevoError\";\n this.code = apiCode;\n }\n}\n\nfunction toAddressObjects(input: MailOptions[\"to\"]): Array<{ email: string; name?: string }> {\n return parseAddresses(input).map((addr) => ({\n email: addr.address,\n ...(addr.name ? { name: addr.name } : {}),\n }));\n}\n\n/**\n * Brevo HTTP API transport.\n */\nexport class BrevoTransport implements Transport {\n readonly provider = \"brevo\";\n\n /** Brevo API key used for Authorization. */\n private readonly apiKey: string;\n\n /** Creates a Brevo transport with the given API key. */\n constructor(config: BrevoConfig) {\n this.apiKey = config.apiKey;\n }\n\n /** Sends an email via the Brevo HTTP API. */\n async send(options: MailOptions): Promise<SendResult> {\n const attachments = await resolveAttachments(options.attachments);\n const from = parseAddresses(options.from)[0];\n\n const body: Record<string, unknown> = {\n sender: from\n ? { email: from.address, ...(from.name ? { name: from.name } : {}) }\n : { email: \"\" },\n to: toAddressObjects(options.to),\n subject: options.subject,\n ...(options.cc ? { cc: toAddressObjects(options.cc) } : {}),\n ...(options.bcc ? { bcc: toAddressObjects(options.bcc) } : {}),\n ...(options.replyTo\n ? {\n replyTo: (() => {\n const reply = parseAddresses(options.replyTo as MailOptions[\"to\"])[0];\n return reply\n ? { email: reply.address, ...(reply.name ? { name: reply.name } : {}) }\n : undefined;\n })(),\n }\n : {}),\n ...(options.html ? { htmlContent: options.html } : {}),\n ...(options.text ? { textContent: options.text } : {}),\n ...(attachments.length > 0\n ? {\n attachment: attachments.map((att) => ({\n name: att.filename,\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : att.content,\n })),\n }\n : {}),\n };\n\n const response = await fetch(\"https://api.brevo.com/v3/smtp/email\", {\n method: \"POST\",\n headers: {\n \"api-key\": this.apiKey,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n });\n\n const payload = (await response.json()) as {\n messageId?: string;\n message?: string;\n code?: string;\n };\n\n if (!response.ok) {\n throw new BrevoError(\n payload.message ?? \"Brevo API error\",\n response.status,\n payload.code ?? \"\",\n );\n }\n\n const toEmails = extractEmails(options.to);\n return {\n messageId: payload.messageId ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: payload.messageId ?? \"sent\",\n envelope: {\n from: from?.address ?? \"\",\n to: toEmails,\n },\n };\n }\n\n /** Verifies the Brevo API key by fetching account info. */\n async verify(): Promise<VerifyResult> {\n try {\n const response = await fetch(\"https://api.brevo.com/v3/account\", {\n headers: {\n \"api-key\": this.apiKey,\n },\n });\n\n const payload = (await response.json()) as { companyName?: string; message?: string };\n\n if (!response.ok) {\n return {\n ok: false,\n provider: \"brevo\",\n message: payload.message ?? `HTTP ${response.status}`,\n };\n }\n\n return {\n ok: true,\n provider: \"brevo\",\n ...(payload.companyName ? { message: payload.companyName } : {}),\n };\n } catch (err) {\n return {\n ok: false,\n provider: \"brevo\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "mOA2BO,CAAM,KAAmB,JAAY,JAIxB,WAFlB,WAAW,CACT,EACgB,EAChB,EACA,CACA,MAAM,EAAS,EAAuB,CAAU,EAAG,CACjD,aACA,SAAU,OACZ,CAAC,EANe,kBAOhB,KAAK,KAAO,aACZ,KAAK,KAAO,EAEhB,CAEA,SAAS,CAAgB,CAAC,EAAmE,CAC3F,OAAO,EAAe,CAAK,EAAE,IAAI,CAAC,KAAU,CAC1C,MAAO,EAAK,WACR,EAAK,KAAO,CAAE,KAAM,EAAK,IAAK,EAAI,CAAC,CACzC,EAAE,EAMG,MAAM,CAAoC,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "mOA2BO,CAAM,KAAmB,JAAY,JAIxB,WAFlB,WAAW,CACT,EACgB,EAChB,EACA,CACA,MAAM,EAAS,EAAuB,CAAU,EAAG,CACjD,aACA,SAAU,OACZ,CAAC,EANe,kBAOhB,KAAK,KAAO,aACZ,KAAK,KAAO,EAEhB,CAEA,SAAS,CAAgB,CAAC,EAAmE,CAC3F,OAAO,EAAe,CAAK,EAAE,IAAI,CAAC,KAAU,CAC1C,MAAO,EAAK,WACR,EAAK,KAAO,CAAE,KAAM,EAAK,IAAK,EAAI,CAAC,CACzC,EAAE,EAMG,MAAM,CAAoC,CACtC,SAAW,QAGH,OAGjB,WAAW,CAAC,EAAqB,CAC/B,KAAK,OAAS,EAAO,YAIjB,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAc,MAAM,EAAmB,EAAQ,WAAW,EAC1D,EAAO,EAAe,EAAQ,IAAI,EAAE,GAEpC,EAAgC,CACpC,OAAQ,EACJ,CAAE,MAAO,EAAK,WAAa,EAAK,KAAO,CAAE,KAAM,EAAK,IAAK,EAAI,CAAC,CAAG,EACjE,CAAE,MAAO,EAAG,EAChB,GAAI,EAAiB,EAAQ,EAAE,EAC/B,QAAS,EAAQ,WACb,EAAQ,GAAK,CAAE,GAAI,EAAiB,EAAQ,EAAE,CAAE,EAAI,CAAC,KACrD,EAAQ,IAAM,CAAE,IAAK,EAAiB,EAAQ,GAAG,CAAE,EAAI,CAAC,KACxD,EAAQ,QACR,CACE,SAAU,IAAM,CACd,IAAM,EAAQ,EAAe,EAAQ,OAA4B,EAAE,GACnE,OAAO,EACH,CAAE,MAAO,EAAM,WAAa,EAAM,KAAO,CAAE,KAAM,EAAM,IAAK,EAAI,CAAC,CAAG,EACpE,SACH,CACL,EACA,CAAC,KACD,EAAQ,KAAO,CAAE,YAAa,EAAQ,IAAK,EAAI,CAAC,KAChD,EAAQ,KAAO,CAAE,YAAa,EAAQ,IAAK,EAAI,CAAC,KAChD,EAAY,OAAS,EACrB,CACE,WAAY,EAAY,IAAI,CAAC,KAAS,CACpC,KAAM,EAAI,SACV,QACE,EAAI,mBAAmB,WACnB,EAAa,EAAI,OAAO,EAAE,QAAQ,QAAS,EAAE,EAC7C,EAAI,OACZ,EAAE,CACJ,EACA,CAAC,CACP,EAEM,EAAW,MAAM,MAAM,sCAAuC,CAClE,OAAQ,OACR,QAAS,CACP,UAAW,KAAK,OAChB,eAAgB,kBAClB,EACA,KAAM,KAAK,UAAU,CAAI,CAC3B,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAMrC,GAAI,CAAC,EAAS,GACZ,MAAM,IAAI,EACR,EAAQ,SAAW,kBACnB,EAAS,OACT,EAAQ,MAAQ,EAClB,EAGF,IAAM,EAAW,EAAc,EAAQ,EAAE,EACzC,MAAO,CACL,UAAW,EAAQ,WAAa,GAChC,SAAU,EACV,SAAU,CAAC,EACX,SAAU,EAAQ,WAAa,OAC/B,SAAU,CACR,KAAM,GAAM,SAAW,GACvB,GAAI,CACN,CACF,OAII,OAAM,EAA0B,CACpC,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,mCAAoC,CAC/D,QAAS,CACP,UAAW,KAAK,MAClB,CACF,CAAC,EAEK,EAAW,MAAM,EAAS,KAAK,EAErC,GAAI,CAAC,EAAS,GACZ,MAAO,CACL,GAAI,GACJ,SAAU,QACV,QAAS,EAAQ,SAAW,QAAQ,EAAS,QAC/C,EAGF,MAAO,CACL,GAAI,GACJ,SAAU,WACN,EAAQ,YAAc,CAAE,QAAS,EAAQ,WAAY,EAAI,CAAC,CAChE,EACA,MAAO,EAAK,CACZ,MAAO,CACL,GAAI,GACJ,SAAU,QACV,QAAS,aAAe,MAAQ,EAAI,QAAU,OAAO,CAAG,CAC1D,GAGN",
|
|
8
|
+
"debugId": "6B0A1C06C762C9F964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { SentlyError } from "../core/errors.js";
|
|
2
|
+
import type { MailOptions, SendResult, Transport, VerifyResult } from "../core/types.js";
|
|
3
|
+
/** Message shape accepted by the Cloudflare Workers `send_email` binding. */
|
|
4
|
+
export interface CloudflareEmailMessage {
|
|
5
|
+
/** Envelope sender. */
|
|
6
|
+
from: string;
|
|
7
|
+
/** Primary recipient. */
|
|
8
|
+
to: string;
|
|
9
|
+
/** Message subject. */
|
|
10
|
+
subject?: string;
|
|
11
|
+
/** Reply-To address. */
|
|
12
|
+
reply_to?: string;
|
|
13
|
+
/** Carbon-copy recipients. */
|
|
14
|
+
cc?: string[];
|
|
15
|
+
/** Blind carbon-copy recipients. */
|
|
16
|
+
bcc?: string[];
|
|
17
|
+
/** Additional MIME headers. */
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
/** Message body parts. */
|
|
20
|
+
content: Array<{
|
|
21
|
+
type: string;
|
|
22
|
+
value: string;
|
|
23
|
+
}>;
|
|
24
|
+
/** Optional attachments (base64 content). */
|
|
25
|
+
attachments?: Array<{
|
|
26
|
+
disposition: "attachment" | "inline";
|
|
27
|
+
filename: string;
|
|
28
|
+
type: string;
|
|
29
|
+
content: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
/** Callable Workers `send_email` binding. */
|
|
33
|
+
export type CloudflareSendEmailFn = (message: CloudflareEmailMessage) => Promise<void>;
|
|
34
|
+
/** Cloudflare Workers Email binding configuration. */
|
|
35
|
+
export interface CloudflareEmailConfig {
|
|
36
|
+
/** The Workers `send_email` binding (e.g. `env.SEND_EMAIL`). */
|
|
37
|
+
sendEmail: CloudflareSendEmailFn;
|
|
38
|
+
}
|
|
39
|
+
/** Error thrown when the Cloudflare Email binding rejects a send. */
|
|
40
|
+
export declare class CloudflareEmailError extends SentlyError {
|
|
41
|
+
/** Creates a Cloudflare Email binding error. */
|
|
42
|
+
constructor(message: string, cause?: unknown);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Cloudflare Workers Email binding transport.
|
|
46
|
+
*/
|
|
47
|
+
export declare class CloudflareEmailTransport implements Transport {
|
|
48
|
+
readonly provider = "cloudflare-email";
|
|
49
|
+
private readonly sendEmail;
|
|
50
|
+
/** Creates a transport backed by the Workers `send_email` binding. */
|
|
51
|
+
constructor(config: CloudflareEmailConfig);
|
|
52
|
+
/** Sends an email via the Workers `send_email` binding. */
|
|
53
|
+
send(options: MailOptions): Promise<SendResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Confirms the binding is configured — no network validation is available.
|
|
56
|
+
*/
|
|
57
|
+
verify(): Promise<VerifyResult>;
|
|
58
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{v as O}from"../chunk-vhxzhx8b.js";import{D as M,F as G}from"../chunk-qh90yr6d.js";import{T as L}from"../chunk-5scdgffb.js";import{X as N}from"../chunk-6bkj4y4r.js";import"../chunk-nbmq8ejv.js";class I extends N{constructor(q,D){super(q,"PROVIDER_ERROR",{provider:"cloudflare-email",cause:D});this.name="CloudflareEmailError"}}class Q{provider="cloudflare-email";sendEmail;constructor(q){this.sendEmail=q.sendEmail}async send(q){let D=M(q.from)[0],H=G(q.to),J=H[0];if(!D||!J)throw new I("Cloudflare Email requires from and to addresses");let F=[];if(q.text!==void 0)F.push({type:"text/plain",value:q.text});if(q.html!==void 0)F.push({type:"text/html",value:q.html});if(F.length===0)F.push({type:"text/plain",value:""});let K=await O(q.attachments),P={from:D.address,to:J,subject:q.subject,content:F,...q.replyTo?{reply_to:G(q.replyTo)[0]}:{},...q.cc?{cc:G(q.cc)}:{},...q.bcc?{bcc:G(q.bcc)}:{},...q.headers?{headers:q.headers}:{},...K.length>0?{attachments:K.map((z)=>({disposition:z.inline?"inline":"attachment",filename:z.filename,type:z.contentType??"application/octet-stream",content:z.content instanceof Uint8Array?L(z.content).replace(/\r\n/g,""):String(z.content??"")}))}:{}};try{await this.sendEmail(P)}catch(z){throw new I(z instanceof Error?z.message:"Cloudflare Email send failed",z)}return{messageId:q.messageId??"",accepted:H,rejected:[],response:"sent via Cloudflare Email binding",envelope:{from:D.address,to:H}}}async verify(){return{ok:!0,provider:"cloudflare-email",message:"send_email binding configured (not validated at runtime)"}}}export{Q as CloudflareEmailTransport,I as CloudflareEmailError};
|
|
2
|
+
|
|
3
|
+
//# debugId=DB8ADE23940DCF7B64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/transports/cloudflare-email.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @module\n * Cloudflare Workers Email transport — wraps the `send_email` binding\n * configured in `wrangler.toml`, not a fetch-based HTTP API.\n *\n * @example\n * ```ts\n * // wrangler.toml: [[send_email]]\n * import { CloudflareEmailTransport } from \"sently/transports/cloudflare-email\";\n * import { createMailer } from \"sently/mailer\";\n *\n * export default {\n * async fetch(request, env) {\n * const mailer = await createMailer({\n * transport: new CloudflareEmailTransport({ sendEmail: env.SEND_EMAIL }),\n * });\n * await mailer.send({ from: \"...\", to: \"...\", subject: \"...\", text: \"...\" });\n * },\n * };\n * ```\n */\nimport { extractEmails, parseAddresses } from \"../core/address.js\";\nimport { encodeBase64 } from \"../core/base64.js\";\nimport { SentlyError } from \"../core/errors.js\";\nimport type { MailOptions, SendResult, Transport, VerifyResult } from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/** Message shape accepted by the Cloudflare Workers `send_email` binding. */\nexport interface CloudflareEmailMessage {\n /** Envelope sender. */\n from: string;\n /** Primary recipient. */\n to: string;\n /** Message subject. */\n subject?: string;\n /** Reply-To address. */\n reply_to?: string;\n /** Carbon-copy recipients. */\n cc?: string[];\n /** Blind carbon-copy recipients. */\n bcc?: string[];\n /** Additional MIME headers. */\n headers?: Record<string, string>;\n /** Message body parts. */\n content: Array<{ type: string; value: string }>;\n /** Optional attachments (base64 content). */\n attachments?: Array<{\n disposition: \"attachment\" | \"inline\";\n filename: string;\n type: string;\n content: string;\n }>;\n}\n\n/** Callable Workers `send_email` binding. */\nexport type CloudflareSendEmailFn = (message: CloudflareEmailMessage) => Promise<void>;\n\n/** Cloudflare Workers Email binding configuration. */\nexport interface CloudflareEmailConfig {\n /** The Workers `send_email` binding (e.g. `env.SEND_EMAIL`). */\n sendEmail: CloudflareSendEmailFn;\n}\n\n/** Error thrown when the Cloudflare Email binding rejects a send. */\nexport class CloudflareEmailError extends SentlyError {\n /** Creates a Cloudflare Email binding error. */\n constructor(message: string, cause?: unknown) {\n super(message, \"PROVIDER_ERROR\", { provider: \"cloudflare-email\", cause });\n this.name = \"CloudflareEmailError\";\n }\n}\n\n/**\n * Cloudflare Workers Email binding transport.\n */\nexport class CloudflareEmailTransport implements Transport {\n readonly provider = \"cloudflare-email\";\n\n private readonly sendEmail: CloudflareSendEmailFn;\n\n /** Creates a transport backed by the Workers `send_email` binding. */\n constructor(config: CloudflareEmailConfig) {\n this.sendEmail = config.sendEmail;\n }\n\n /** Sends an email via the Workers `send_email` binding. */\n async send(options: MailOptions): Promise<SendResult> {\n const from = parseAddresses(options.from)[0];\n const toEmails = extractEmails(options.to);\n const primaryTo = toEmails[0];\n\n if (!from || !primaryTo) {\n throw new CloudflareEmailError(\"Cloudflare Email requires from and to addresses\");\n }\n\n const content: CloudflareEmailMessage[\"content\"] = [];\n if (options.text !== undefined) {\n content.push({ type: \"text/plain\", value: options.text });\n }\n if (options.html !== undefined) {\n content.push({ type: \"text/html\", value: options.html });\n }\n if (content.length === 0) {\n content.push({ type: \"text/plain\", value: \"\" });\n }\n\n const attachments = await resolveAttachments(options.attachments);\n const message: CloudflareEmailMessage = {\n from: from.address,\n to: primaryTo,\n subject: options.subject,\n content,\n ...(options.replyTo ? { reply_to: extractEmails(options.replyTo)[0] } : {}),\n ...(options.cc ? { cc: extractEmails(options.cc) } : {}),\n ...(options.bcc ? { bcc: extractEmails(options.bcc) } : {}),\n ...(options.headers ? { headers: options.headers } : {}),\n ...(attachments.length > 0\n ? {\n attachments: attachments.map((att) => ({\n disposition: att.inline ? (\"inline\" as const) : (\"attachment\" as const),\n filename: att.filename,\n type: att.contentType ?? \"application/octet-stream\",\n content:\n att.content instanceof Uint8Array\n ? encodeBase64(att.content).replace(/\\r\\n/g, \"\")\n : String(att.content ?? \"\"),\n })),\n }\n : {}),\n };\n\n try {\n await this.sendEmail(message);\n } catch (err) {\n throw new CloudflareEmailError(\n err instanceof Error ? err.message : \"Cloudflare Email send failed\",\n err,\n );\n }\n\n return {\n messageId: options.messageId ?? \"\",\n accepted: toEmails,\n rejected: [],\n response: \"sent via Cloudflare Email binding\",\n envelope: {\n from: from.address,\n to: toEmails,\n },\n };\n }\n\n /**\n * Confirms the binding is configured — no network validation is available.\n */\n async verify(): Promise<VerifyResult> {\n return {\n ok: true,\n provider: \"cloudflare-email\",\n message: \"send_email binding configured (not validated at runtime)\",\n };\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "4NAgEO,CAAM,KAA6B,JAAY,JAEpD,WAAW,CAAC,EAAiB,EAAiB,CAC5C,MAAM,EAAS,iBAAkB,CAAE,SAAU,mBAAoB,OAAM,CAAC,EACxE,KAAK,KAAO,uBAEhB,CAKO,MAAM,CAA8C,CAChD,SAAW,mBAEH,UAGjB,WAAW,CAAC,EAA+B,CACzC,KAAK,UAAY,EAAO,eAIpB,KAAI,CAAC,EAA2C,CACpD,IAAM,EAAO,EAAe,EAAQ,IAAI,EAAE,GACpC,EAAW,EAAc,EAAQ,EAAE,EACnC,EAAY,EAAS,GAE3B,GAAI,CAAC,GAAQ,CAAC,EACZ,MAAM,IAAI,EAAqB,iDAAiD,EAGlF,IAAM,EAA6C,CAAC,EACpD,GAAI,EAAQ,OAAS,OACnB,EAAQ,KAAK,CAAE,KAAM,aAAc,MAAO,EAAQ,IAAK,CAAC,EAE1D,GAAI,EAAQ,OAAS,OACnB,EAAQ,KAAK,CAAE,KAAM,YAAa,MAAO,EAAQ,IAAK,CAAC,EAEzD,GAAI,EAAQ,SAAW,EACrB,EAAQ,KAAK,CAAE,KAAM,aAAc,MAAO,EAAG,CAAC,EAGhD,IAAM,EAAc,MAAM,EAAmB,EAAQ,WAAW,EAC1D,EAAkC,CACtC,KAAM,EAAK,QACX,GAAI,EACJ,QAAS,EAAQ,QACjB,aACI,EAAQ,QAAU,CAAE,SAAU,EAAc,EAAQ,OAAO,EAAE,EAAG,EAAI,CAAC,KACrE,EAAQ,GAAK,CAAE,GAAI,EAAc,EAAQ,EAAE,CAAE,EAAI,CAAC,KAClD,EAAQ,IAAM,CAAE,IAAK,EAAc,EAAQ,GAAG,CAAE,EAAI,CAAC,KACrD,EAAQ,QAAU,CAAE,QAAS,EAAQ,OAAQ,EAAI,CAAC,KAClD,EAAY,OAAS,EACrB,CACE,YAAa,EAAY,IAAI,CAAC,KAAS,CACrC,YAAa,EAAI,OAAU,SAAsB,aACjD,SAAU,EAAI,SACd,KAAM,EAAI,aAAe,2BACzB,QACE,EAAI,mBAAmB,WACnB,EAAa,EAAI,OAAO,EAAE,QAAQ,QAAS,EAAE,EAC7C,OAAO,EAAI,SAAW,EAAE,CAChC,EAAE,CACJ,EACA,CAAC,CACP,EAEA,GAAI,CACF,MAAM,KAAK,UAAU,CAAO,EAC5B,MAAO,EAAK,CACZ,MAAM,IAAI,EACR,aAAe,MAAQ,EAAI,QAAU,+BACrC,CACF,EAGF,MAAO,CACL,UAAW,EAAQ,WAAa,GAChC,SAAU,EACV,SAAU,CAAC,EACX,SAAU,oCACV,SAAU,CACR,KAAM,EAAK,QACX,GAAI,CACN,CACF,OAMI,OAAM,EAA0B,CACpC,MAAO,CACL,GAAI,GACJ,SAAU,mBACV,QAAS,0DACX,EAEJ",
|
|
8
|
+
"debugId": "DB8ADE23940DCF7B64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|