sently 0.4.6 → 0.4.7
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 +35 -0
- package/README.md +94 -41
- package/dist/adapters/bun.js +2 -183
- package/dist/adapters/bun.js.map +2 -2
- package/dist/adapters/cf.js +2 -77
- package/dist/adapters/cf.js.map +2 -2
- package/dist/adapters/deno.js +2 -72
- package/dist/adapters/deno.js.map +2 -2
- package/dist/adapters/node.js +2 -180
- package/dist/adapters/node.js.map +2 -2
- package/dist/auth/oauth2.js +2 -13
- package/dist/auth/oauth2.js.map +1 -1
- package/dist/chunk-2kcwa9gt.js +4 -0
- package/dist/chunk-2kcwa9gt.js.map +11 -0
- package/dist/chunk-2t6hjer3.js +5 -0
- package/dist/chunk-2t6hjer3.js.map +10 -0
- package/dist/chunk-6yggz45h.js +5 -0
- package/dist/{chunk-794hc3m4.js.map → chunk-6yggz45h.js.map} +2 -2
- package/dist/chunk-dgkh77yp.js +4 -0
- package/dist/{chunk-ym3zzv8b.js.map → chunk-dgkh77yp.js.map} +2 -2
- package/dist/chunk-jfs80vhp.js +3 -0
- package/dist/{chunk-7fqv71z1.js.map → chunk-jfs80vhp.js.map} +2 -2
- package/dist/chunk-sqn04kae.js +4 -0
- package/dist/{chunk-v0bahtg2.js.map → chunk-sqn04kae.js.map} +1 -1
- package/dist/chunk-va2awz12.js +4 -0
- package/dist/{chunk-f4c9ttmr.js.map → chunk-va2awz12.js.map} +2 -2
- package/dist/chunk-wgtbr6ge.js +13 -0
- package/dist/{chunk-tymfm441.js.map → chunk-wgtbr6ge.js.map} +2 -2
- package/dist/core/smtp.js +2 -31
- package/dist/core/smtp.js.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/detect.d.ts +2 -0
- package/dist/detect.js +2 -180
- package/dist/detect.js.map +4 -5
- package/dist/dkim.d.ts +21 -0
- package/dist/dkim.js +9 -0
- package/dist/dkim.js.map +10 -0
- package/dist/index.d.ts +74 -16
- package/dist/index.js +85 -14
- package/dist/mailer.d.ts +16 -0
- package/dist/mailer.js +3 -0
- package/dist/mailer.js.map +9 -0
- package/dist/plugins/template.js +2 -28
- package/dist/plugins/template.js.map +2 -2
- package/dist/pool/pool.js +2 -16
- package/dist/pool/pool.js.map +5 -3
- package/dist/transports/brevo.js +2 -115
- package/dist/transports/brevo.js.map +2 -2
- package/dist/transports/mailgun.js +2 -119
- package/dist/transports/mailgun.js.map +2 -2
- package/dist/transports/postmark.js +2 -113
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.js +2 -72
- package/dist/transports/preview.js.map +2 -2
- package/dist/transports/resend.js +2 -109
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/retry.js +2 -78
- package/dist/transports/retry.js.map +2 -2
- package/dist/transports/sendgrid.js +2 -132
- package/dist/transports/sendgrid.js.map +2 -2
- package/dist/transports/ses.js +5 -251
- package/dist/transports/ses.js.map +2 -2
- package/dist/transports/smtp.js +2 -26
- package/dist/transports/smtp.js.map +1 -1
- package/package.json +17 -6
- package/dist/chunk-794hc3m4.js +0 -105
- package/dist/chunk-7fqv71z1.js +0 -251
- package/dist/chunk-f4c9ttmr.js +0 -154
- package/dist/chunk-mp5c9bfd.js +0 -270
- package/dist/chunk-mp5c9bfd.js.map +0 -11
- package/dist/chunk-tymfm441.js +0 -405
- package/dist/chunk-v0bahtg2.js +0 -6
- package/dist/chunk-x3szga4k.js +0 -367
- package/dist/chunk-x3szga4k.js.map +0 -11
- package/dist/chunk-ym3zzv8b.js +0 -74
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.4.7] — 2026-05-30
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`sently/mailer` entry** — transport-only `createMailer` without SMTP code in the
|
|
10
|
+
bundle (~4.3 KB with HTTP transports vs ~14 KB from the main entry)
|
|
11
|
+
- **`sently/dkim` entry** — optional DKIM signing; MIME lazy-loads it only when
|
|
12
|
+
`dkim` config is passed (~1.7 KB)
|
|
13
|
+
- **`TransportMailerOptions`** type for the `sently/mailer` entry
|
|
14
|
+
- **Bundle size CI gate** — `bun run check:size` enforces gzip budgets in
|
|
15
|
+
`tools/bundle-size-budgets.json`
|
|
16
|
+
- **`bun run measure:size`** / **`measure:size:md`** — categorized bundle reports
|
|
17
|
+
for docs and CI
|
|
18
|
+
- **Deno and Cloudflare adapter smoke tests** in CI
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Lazy-load SMTP transport and pool from `createMailer` when using SMTP config
|
|
23
|
+
- Extract `MailerImpl` to `src/mailer.ts`; full `createMailer` in `detect.ts` delegates
|
|
24
|
+
to it for custom transports
|
|
25
|
+
- **`dist/index.js` generated from `src/index.ts`** via `scripts/generate-index-js.ts`
|
|
26
|
+
(no hand-maintained export list)
|
|
27
|
+
- **Minified `dist/` output** in production builds
|
|
28
|
+
- **Pinned devDependency versions** (Biome 2.4.16, TypeScript 6.0.3, `@types/node`
|
|
29
|
+
25.9.1, MCP SDK 1.29.0)
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
|
|
33
|
+
- README **Bundle Size** section — import-path guide, common stacks, per-subpath
|
|
34
|
+
tables, and HTTP stack breakdown
|
|
35
|
+
- Inline JSDoc on `src/index.ts` barrel re-exports for JSR symbol documentation
|
|
36
|
+
- Corrected bundle size figures (minified + gzip, one decimal KB)
|
|
37
|
+
|
|
3
38
|
## [0.4.6] — 2026-05-30
|
|
4
39
|
|
|
5
40
|
### Documentation
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# sently
|
|
2
2
|
|
|
3
3
|
> Nodemailer hasn't been updated in years, doesn't run on Bun or Deno, and ships at 220KB.
|
|
4
|
-
> sently is the modern replacement — same familiar API, runs everywhere,
|
|
4
|
+
> sently is the modern replacement — same familiar API, runs everywhere, HTTP stacks from ~4 KB via `sently/mailer`.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
7
|
bun add sently
|
|
@@ -20,7 +20,7 @@ bun add sently
|
|
|
20
20
|
|
|
21
21
|
| Feature | Nodemailer | sently |
|
|
22
22
|
|---------|-----------|--------|
|
|
23
|
-
| Bundle size | ~220 KB | ~
|
|
23
|
+
| Bundle size | ~220 KB always | ~4 KB HTTP · ~14 KB SMTP |
|
|
24
24
|
| Runtimes | Node.js only | Node, Bun, Deno, CF Workers |
|
|
25
25
|
| Module format | CommonJS | ESM only |
|
|
26
26
|
| Dependencies | 3 | 0 |
|
|
@@ -40,7 +40,8 @@ bun add sently
|
|
|
40
40
|
## The 30-second tour
|
|
41
41
|
|
|
42
42
|
```typescript
|
|
43
|
-
import {
|
|
43
|
+
import type { MailOptions } from "sently";
|
|
44
|
+
import { createMailer } from "sently/mailer";
|
|
44
45
|
import { ResendTransport } from "sently/transports/resend";
|
|
45
46
|
import { PreviewTransport } from "sently/transports/preview";
|
|
46
47
|
|
|
@@ -93,7 +94,8 @@ bunx jsr add @alialnaghmoush/sently
|
|
|
93
94
|
```
|
|
94
95
|
|
|
95
96
|
```typescript
|
|
96
|
-
import { createMailer } from "sently";
|
|
97
|
+
import { createMailer } from "sently/mailer"; // HTTP transports
|
|
98
|
+
import { createMailer as createSMTPMailer } from "sently"; // SMTP host/port config
|
|
97
99
|
```
|
|
98
100
|
|
|
99
101
|
---
|
|
@@ -125,7 +127,7 @@ await mailer.close();
|
|
|
125
127
|
### Resend HTTP transport (Vercel Edge compatible)
|
|
126
128
|
|
|
127
129
|
```typescript
|
|
128
|
-
import { createMailer } from "sently";
|
|
130
|
+
import { createMailer } from "sently/mailer";
|
|
129
131
|
import { ResendTransport } from "sently/transports/resend";
|
|
130
132
|
|
|
131
133
|
const mailer = await createMailer({
|
|
@@ -197,7 +199,7 @@ const mailer = await createMailer({
|
|
|
197
199
|
### SMTP
|
|
198
200
|
|
|
199
201
|
```typescript
|
|
200
|
-
import { createMailer } from "sently";
|
|
202
|
+
import { createMailer } from "sently/mailer";
|
|
201
203
|
import { SMTPTransport } from "sently/transports/smtp";
|
|
202
204
|
import { NodeAdapter } from "sently/adapters/node";
|
|
203
205
|
|
|
@@ -212,6 +214,8 @@ const mailer = await createMailer({ transport });
|
|
|
212
214
|
await mailer.verify(); // test connection + auth
|
|
213
215
|
```
|
|
214
216
|
|
|
217
|
+
Use `sently/mailer` instead of `sently` when passing `{ transport }` — keeps HTTP-only bundles ~4 KB smaller.
|
|
218
|
+
|
|
215
219
|
**AUTH methods:** XOAUTH2, CRAM-MD5, LOGIN, and PLAIN (auto-negotiated from EHLO unless `auth.type` is set).
|
|
216
220
|
|
|
217
221
|
**`requireTLS` (default `true` when `auth` is set):** sently refuses to send credentials over an unencrypted connection. If the link is not secured by direct TLS (`secure: true`) or a successful `STARTTLS` upgrade, authentication throws an `SMTPError` instead of leaking credentials — this defends against STARTTLS-stripping MITM attacks. Set `requireTLS: false` only if you fully trust the network (not recommended).
|
|
@@ -230,6 +234,8 @@ const mailer = await createMailer({
|
|
|
230
234
|
});
|
|
231
235
|
```
|
|
232
236
|
|
|
237
|
+
Pass `dkim` on SMTP config or use `signDKIM` from `sently/dkim` directly. MIME lazy-loads DKIM only when the option is set.
|
|
238
|
+
|
|
233
239
|
#### Gmail OAuth2 (XOAUTH2)
|
|
234
240
|
|
|
235
241
|
```typescript
|
|
@@ -282,6 +288,7 @@ const pool = new SMTPPool({
|
|
|
282
288
|
|
|
283
289
|
| Transport | Import path | Required config |
|
|
284
290
|
|-----------|-------------|-----------------|
|
|
291
|
+
| Mailer wrapper | `sently/mailer` | — (use with any transport below) |
|
|
285
292
|
| Resend | `sently/transports/resend` | `apiKey` |
|
|
286
293
|
| SendGrid | `sently/transports/sendgrid` | `apiKey` |
|
|
287
294
|
| Postmark | `sently/transports/postmark` | `serverToken` |
|
|
@@ -299,7 +306,7 @@ Write emails to disk during local development instead of sending them:
|
|
|
299
306
|
|
|
300
307
|
```typescript
|
|
301
308
|
import { PreviewTransport } from "sently/transports/preview";
|
|
302
|
-
import { createMailer } from "sently";
|
|
309
|
+
import { createMailer } from "sently/mailer";
|
|
303
310
|
|
|
304
311
|
const mailer = await createMailer({
|
|
305
312
|
transport: new PreviewTransport({
|
|
@@ -324,7 +331,7 @@ Wrap any transport with automatic retries and configurable backoff:
|
|
|
324
331
|
```typescript
|
|
325
332
|
import { RetryTransport } from "sently/transports/retry";
|
|
326
333
|
import { ResendTransport } from "sently/transports/resend";
|
|
327
|
-
import { createMailer } from "sently";
|
|
334
|
+
import { createMailer } from "sently/mailer";
|
|
328
335
|
|
|
329
336
|
const transport = new RetryTransport(
|
|
330
337
|
new ResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
|
|
@@ -380,6 +387,9 @@ const mailer = await createMailer({
|
|
|
380
387
|
Works with SMTP config or custom transports:
|
|
381
388
|
|
|
382
389
|
```typescript
|
|
390
|
+
import { createMailer } from "sently/mailer";
|
|
391
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
392
|
+
|
|
383
393
|
const mailer = await createMailer({
|
|
384
394
|
transport: new ResendTransport({ apiKey: "re_..." }),
|
|
385
395
|
plugins: [addFooter],
|
|
@@ -392,7 +402,7 @@ Render HTML from named templates with zero dependencies:
|
|
|
392
402
|
|
|
393
403
|
```typescript
|
|
394
404
|
import { templatePlugin, simpleEngine } from "sently/plugins/template";
|
|
395
|
-
import { createMailer } from "sently";
|
|
405
|
+
import { createMailer } from "sently/mailer";
|
|
396
406
|
import { ResendTransport } from "sently/transports/resend";
|
|
397
407
|
|
|
398
408
|
const mailer = await createMailer({
|
|
@@ -550,59 +560,102 @@ MIME attachment filenames and custom attachment headers are likewise sanitized a
|
|
|
550
560
|
|
|
551
561
|
---
|
|
552
562
|
|
|
553
|
-
##
|
|
563
|
+
## Bundle Size
|
|
564
|
+
|
|
565
|
+
All sizes are **minified + gzip**, measured by bundling each import path in isolation (`bun run measure:size`). CI enforces budgets on key entries (`bun run check:size`). Node built-ins and `cloudflare:sockets` are external — same as in your app bundle.
|
|
566
|
+
|
|
567
|
+
**Nodemailer ships ~220 KB** whether you use SMTP or an HTTP plugin. sently tree-shakes per subpath.
|
|
554
568
|
|
|
555
|
-
|
|
569
|
+
### Choosing an import path
|
|
556
570
|
|
|
571
|
+
| You send via… | Import | Why |
|
|
572
|
+
|---------------|--------|-----|
|
|
573
|
+
| Resend, SendGrid, Postmark, etc. | `sently/mailer` + `sently/transports/<provider>` | **~4 KB** — no SMTP code in the bundle |
|
|
574
|
+
| SMTP relay (`host` / `port`) | `sently` | **~14 KB** — includes MIME + SMTP stack |
|
|
575
|
+
| Raw transport, no plugins | `sently/transports/<provider>` only | **~4 KB** — skip `createMailer` wrapper |
|
|
576
|
+
| DKIM signing | `sently/dkim` or `dkim` option on send | **~2 KB** add-on, lazy-loaded by MIME |
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
// Recommended — HTTP API (~4.3 KB bundled)
|
|
580
|
+
import { createMailer } from "sently/mailer";
|
|
581
|
+
import { ResendTransport } from "sently/transports/resend";
|
|
582
|
+
|
|
583
|
+
// Avoid for HTTP-only apps — pulls SMTP into flat bundles (~14 KB)
|
|
584
|
+
import { createMailer } from "sently";
|
|
557
585
|
```
|
|
558
|
-
import { createMailer } from "sently"
|
|
559
|
-
+ import { ResendTransport } from "sently/transports/resend"
|
|
560
|
-
→ Bundle: core/mime (~8KB) + core/address (~2KB) + transports/resend (~2KB) ≈ ~12KB gzip
|
|
561
586
|
|
|
562
|
-
|
|
587
|
+
### Common stacks
|
|
588
|
+
|
|
589
|
+
| Use case | What you import | ~gzip |
|
|
590
|
+
|----------|-----------------|-------|
|
|
591
|
+
| HTTP — Resend | `sently/mailer` + `transports/resend` | **4.3 KB** |
|
|
592
|
+
| HTTP — SendGrid | `sently/mailer` + `transports/sendgrid` | **4.3 KB** |
|
|
593
|
+
| HTTP — transport only | `transports/resend` (call `.send()` directly) | **3.9 KB** |
|
|
594
|
+
| SMTP relay | `sently` + `{ host, port, auth }` | **13.6 KB** |
|
|
595
|
+
| SMTP + explicit adapter | `sently` + `adapters/node` | **13.6 KB** |
|
|
596
|
+
| Main entry + HTTP ⚠️ | `sently` + `transports/resend` | **14.1 KB** |
|
|
597
|
+
|
|
598
|
+
Adapters are **auto-selected at runtime** for SMTP unless you pass `adapter` explicitly. Only the adapter for your runtime is fetched (dynamic import).
|
|
599
|
+
|
|
600
|
+
### Core entries
|
|
601
|
+
|
|
602
|
+
| Export | ~gzip | Notes |
|
|
603
|
+
|--------|-------|-------|
|
|
604
|
+
| `sently/mailer` | 0.6 KB | `createMailer({ transport })` — plugins, `sendBulk`, `verify` |
|
|
605
|
+
| `sently` | 13.6 KB | Full `createMailer` — SMTP config + lazy SMTP chunks |
|
|
606
|
+
| `sently/dkim` | 1.7 KB | `signDKIM`, `importPrivateKey` — loaded when `dkim` option is set |
|
|
607
|
+
|
|
608
|
+
### Transports
|
|
609
|
+
|
|
610
|
+
| Export | ~gzip | Protocol |
|
|
611
|
+
|--------|-------|----------|
|
|
612
|
+
| `sently/transports/resend` | 3.9 KB | HTTP |
|
|
613
|
+
| `sently/transports/sendgrid` | 3.9 KB | HTTP |
|
|
614
|
+
| `sently/transports/postmark` | 3.9 KB | HTTP |
|
|
615
|
+
| `sently/transports/mailgun` | 4.0 KB | HTTP |
|
|
616
|
+
| `sently/transports/brevo` | 3.8 KB | HTTP |
|
|
617
|
+
| `sently/transports/ses` | 7.3 KB | HTTP (SigV4) |
|
|
618
|
+
| `sently/transports/smtp` | 10.0 KB | SMTP + MIME |
|
|
619
|
+
| `sently/transports/preview` | 6.4 KB | Dev disk preview |
|
|
620
|
+
|
|
621
|
+
HTTP transports share MIME/address parsing (~3.8 KB). SES is larger due to SigV4 signing.
|
|
622
|
+
|
|
623
|
+
### Adapters (SMTP socket layer)
|
|
624
|
+
|
|
625
|
+
| Export | ~gzip | Runtime |
|
|
626
|
+
|--------|-------|---------|
|
|
627
|
+
| `sently/adapters/node` | 1.2 KB | Node.js |
|
|
628
|
+
| `sently/adapters/bun` | 1.2 KB | Bun |
|
|
629
|
+
| `sently/adapters/deno` | 0.5 KB | Deno |
|
|
630
|
+
| `sently/adapters/cf` | 0.6 KB | Cloudflare Workers |
|
|
631
|
+
|
|
632
|
+
### What's inside an HTTP stack (~4.3 KB)
|
|
633
|
+
|
|
634
|
+
```
|
|
635
|
+
sently/mailer 0.6 KB createMailer wrapper, plugins, sendBulk
|
|
636
|
+
transports/resend 3.9 KB fetch client + MIME/address parsing
|
|
637
|
+
─────
|
|
638
|
+
total 4.3 KB vs Nodemailer ~220 KB
|
|
563
639
|
```
|
|
564
640
|
|
|
565
|
-
|
|
641
|
+
Regenerate tables after changes: `bun run measure:size` (full report) or `bun tools/measure-bundle-size.ts --markdown`.
|
|
566
642
|
|
|
567
643
|
---
|
|
568
644
|
|
|
569
645
|
## Migrating from Nodemailer
|
|
570
646
|
|
|
571
647
|
| Nodemailer | sently |
|
|
572
|
-
|
|
648
|
+
|------------|--------|
|
|
573
649
|
| `nodemailer.createTransport({...})` | `await createMailer({...})` |
|
|
574
650
|
| `transporter.sendMail(options)` | `mailer.send(options)` |
|
|
575
651
|
| `transporter.verify()` | `mailer.verify()` |
|
|
576
652
|
| `options.attachments[].path` | Same (Node/Bun/Deno); use `content` on edge |
|
|
577
|
-
| `import nodemailer from 'nodemailer'` | `import { createMailer } from 'sently'` |
|
|
653
|
+
| `import nodemailer from 'nodemailer'` | `import { createMailer } from 'sently/mailer'` (HTTP) or `'sently'` (SMTP) |
|
|
578
654
|
| CommonJS | ESM only |
|
|
579
655
|
| Node.js only | Node, Bun, Deno, CF Workers |
|
|
580
656
|
|
|
581
657
|
---
|
|
582
658
|
|
|
583
|
-
## Bundle Size
|
|
584
|
-
|
|
585
|
-
Approximate gzip sizes per subpath export:
|
|
586
|
-
|
|
587
|
-
| Export | ~gzip |
|
|
588
|
-
|--------|-------|
|
|
589
|
-
| `sently` | ~6 KB |
|
|
590
|
-
| `sently/transports/smtp` | ~10 KB |
|
|
591
|
-
| `sently/transports/resend` | ~2 KB |
|
|
592
|
-
| `sently/transports/sendgrid` | ~2 KB |
|
|
593
|
-
| `sently/transports/postmark` | ~2 KB |
|
|
594
|
-
| `sently/transports/mailgun` | ~3 KB |
|
|
595
|
-
| `sently/transports/ses` | ~5 KB |
|
|
596
|
-
| `sently/transports/brevo` | ~2 KB |
|
|
597
|
-
| `sently/adapters/node` | ~3 KB |
|
|
598
|
-
| `sently/adapters/bun` | ~3 KB |
|
|
599
|
-
| `sently/adapters/deno` | ~2 KB |
|
|
600
|
-
| `sently/adapters/cf` | ~2 KB |
|
|
601
|
-
|
|
602
|
-
> **Example:** Resend only = core (~6 KB) + transport (~2 KB) = **~8 KB total**. Nodemailer ships 220 KB regardless of which transport you use.
|
|
603
|
-
|
|
604
|
-
---
|
|
605
|
-
|
|
606
659
|
## TypeScript
|
|
607
660
|
|
|
608
661
|
```typescript
|
package/dist/adapters/bun.js
CHANGED
|
@@ -1,184 +1,3 @@
|
|
|
1
|
-
import"../chunk-
|
|
1
|
+
import"../chunk-sqn04kae.js";import W from"node:net";import Q from"node:tls";function V(f){if(f.rejectUnauthorized===!1)console.warn("[sently] TLS certificate verification is disabled. Never use rejectUnauthorized: false in production.")}class X{socket=null;_secure;_connected=!1;connectionTimeout;tlsOptions;constructor(f={}){if(typeof Bun>"u")throw Error("BunAdapter requires the Bun runtime");this._secure=f.secure??!1,this.connectionTimeout=f.connectionTimeout??30000,this.tlsOptions=f.tls??{}}get secure(){return this._secure}get connected(){return this._connected}async connect(f,B){if(this._secure)await this.connectTls(f,B);else await this.connectPlain(f,B);this._connected=!0}async startTLS(f){if(!this.socket||this._secure)throw Error("Cannot STARTTLS: no plain socket available");let B=this.socket,y={...this.tlsOptions,...f};V(y),await new Promise((F,A)=>{let G=Q.connect({socket:B,servername:y.servername,rejectUnauthorized:y.rejectUnauthorized??!0,minVersion:y.minVersion});G.once("secureConnect",()=>{this.socket=G,this._secure=!0,F()}),G.once("error",A)})}async write(f){if(!this.socket)throw Error("Socket not connected");await new Promise((B,y)=>{this.socket?.write(Buffer.from(f),(F)=>{if(F)y(F);else B()})})}async*read(){if(!this.socket)throw Error("Socket not connected");let f=this.socket,B=[],y=null,F=!1,A=null,G=(H)=>{let I=new Uint8Array(H);if(y)y({value:I,done:!1}),y=null;else B.push(I)},J=(H)=>{if(A=H,F=!0,y)y({value:void 0,done:!0}),y=null},K=()=>{if(F=!0,y)y({value:void 0,done:!0}),y=null};f.on("data",G),f.on("error",J),f.on("close",K);try{while(!F||B.length>0){if(A)throw A;if(B.length>0){yield B.shift();continue}if(F)break;let H=await new Promise((I)=>{y=I});if(H.done)break;yield H.value}}finally{f.off("data",G),f.off("error",J),f.off("close",K)}}async close(){if(!this.socket)return;await new Promise((f)=>{this.socket?.end(()=>f())}),this.socket=null,this._connected=!1}connectPlain(f,B){return new Promise((y,F)=>{let A=W.connect({host:f,port:B},()=>y());A.setTimeout(this.connectionTimeout),A.once("timeout",()=>{A.destroy(),F(Error("Connection timeout"))}),A.once("error",F),this.socket=A})}connectTls(f,B){return V(this.tlsOptions),new Promise((y,F)=>{let A=Q.connect({host:f,port:B,servername:this.tlsOptions.servername??f,rejectUnauthorized:this.tlsOptions.rejectUnauthorized??!0,minVersion:this.tlsOptions.minVersion},()=>y());A.setTimeout(this.connectionTimeout),A.once("timeout",()=>{A.destroy(),F(Error("Connection timeout"))}),A.once("error",F),this.socket=A})}}export{X as BunAdapter};
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
import net from "node:net";
|
|
5
|
-
import tls from "node:tls";
|
|
6
|
-
function warnRejectUnauthorizedDisabled(tls2) {
|
|
7
|
-
if (tls2.rejectUnauthorized === false) {
|
|
8
|
-
console.warn("[sently] TLS certificate verification is disabled. " + "Never use rejectUnauthorized: false in production.");
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
class BunAdapter {
|
|
13
|
-
socket = null;
|
|
14
|
-
_secure;
|
|
15
|
-
_connected = false;
|
|
16
|
-
connectionTimeout;
|
|
17
|
-
tlsOptions;
|
|
18
|
-
constructor(options = {}) {
|
|
19
|
-
if (typeof Bun === "undefined") {
|
|
20
|
-
throw new Error("BunAdapter requires the Bun runtime");
|
|
21
|
-
}
|
|
22
|
-
this._secure = options.secure ?? false;
|
|
23
|
-
this.connectionTimeout = options.connectionTimeout ?? 30000;
|
|
24
|
-
this.tlsOptions = options.tls ?? {};
|
|
25
|
-
}
|
|
26
|
-
get secure() {
|
|
27
|
-
return this._secure;
|
|
28
|
-
}
|
|
29
|
-
get connected() {
|
|
30
|
-
return this._connected;
|
|
31
|
-
}
|
|
32
|
-
async connect(host, port) {
|
|
33
|
-
if (this._secure) {
|
|
34
|
-
await this.connectTls(host, port);
|
|
35
|
-
} else {
|
|
36
|
-
await this.connectPlain(host, port);
|
|
37
|
-
}
|
|
38
|
-
this._connected = true;
|
|
39
|
-
}
|
|
40
|
-
async startTLS(options) {
|
|
41
|
-
if (!this.socket || this._secure) {
|
|
42
|
-
throw new Error("Cannot STARTTLS: no plain socket available");
|
|
43
|
-
}
|
|
44
|
-
const plain = this.socket;
|
|
45
|
-
const merged = { ...this.tlsOptions, ...options };
|
|
46
|
-
warnRejectUnauthorizedDisabled(merged);
|
|
47
|
-
await new Promise((resolve, reject) => {
|
|
48
|
-
const tlsSocket = tls.connect({
|
|
49
|
-
socket: plain,
|
|
50
|
-
servername: merged.servername,
|
|
51
|
-
rejectUnauthorized: merged.rejectUnauthorized ?? true,
|
|
52
|
-
minVersion: merged.minVersion
|
|
53
|
-
});
|
|
54
|
-
tlsSocket.once("secureConnect", () => {
|
|
55
|
-
this.socket = tlsSocket;
|
|
56
|
-
this._secure = true;
|
|
57
|
-
resolve();
|
|
58
|
-
});
|
|
59
|
-
tlsSocket.once("error", reject);
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
async write(data) {
|
|
63
|
-
if (!this.socket) {
|
|
64
|
-
throw new Error("Socket not connected");
|
|
65
|
-
}
|
|
66
|
-
await new Promise((resolve, reject) => {
|
|
67
|
-
this.socket?.write(Buffer.from(data), (err) => {
|
|
68
|
-
if (err) {
|
|
69
|
-
reject(err);
|
|
70
|
-
} else {
|
|
71
|
-
resolve();
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
async* read() {
|
|
77
|
-
if (!this.socket) {
|
|
78
|
-
throw new Error("Socket not connected");
|
|
79
|
-
}
|
|
80
|
-
const socket = this.socket;
|
|
81
|
-
const queue = [];
|
|
82
|
-
let resolveNext = null;
|
|
83
|
-
let done = false;
|
|
84
|
-
let error = null;
|
|
85
|
-
const onData = (chunk) => {
|
|
86
|
-
const data = new Uint8Array(chunk);
|
|
87
|
-
if (resolveNext) {
|
|
88
|
-
resolveNext({ value: data, done: false });
|
|
89
|
-
resolveNext = null;
|
|
90
|
-
} else {
|
|
91
|
-
queue.push(data);
|
|
92
|
-
}
|
|
93
|
-
};
|
|
94
|
-
const onError = (err) => {
|
|
95
|
-
error = err;
|
|
96
|
-
done = true;
|
|
97
|
-
if (resolveNext) {
|
|
98
|
-
resolveNext({ value: undefined, done: true });
|
|
99
|
-
resolveNext = null;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
const onClose = () => {
|
|
103
|
-
done = true;
|
|
104
|
-
if (resolveNext) {
|
|
105
|
-
resolveNext({ value: undefined, done: true });
|
|
106
|
-
resolveNext = null;
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
socket.on("data", onData);
|
|
110
|
-
socket.on("error", onError);
|
|
111
|
-
socket.on("close", onClose);
|
|
112
|
-
try {
|
|
113
|
-
while (!done || queue.length > 0) {
|
|
114
|
-
if (error) {
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
if (queue.length > 0) {
|
|
118
|
-
yield queue.shift();
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
if (done) {
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
const chunk = await new Promise((resolve) => {
|
|
125
|
-
resolveNext = resolve;
|
|
126
|
-
});
|
|
127
|
-
if (chunk.done) {
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
yield chunk.value;
|
|
131
|
-
}
|
|
132
|
-
} finally {
|
|
133
|
-
socket.off("data", onData);
|
|
134
|
-
socket.off("error", onError);
|
|
135
|
-
socket.off("close", onClose);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
async close() {
|
|
139
|
-
if (!this.socket) {
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
await new Promise((resolve) => {
|
|
143
|
-
this.socket?.end(() => resolve());
|
|
144
|
-
});
|
|
145
|
-
this.socket = null;
|
|
146
|
-
this._connected = false;
|
|
147
|
-
}
|
|
148
|
-
connectPlain(host, port) {
|
|
149
|
-
return new Promise((resolve, reject) => {
|
|
150
|
-
const socket = net.connect({ host, port }, () => resolve());
|
|
151
|
-
socket.setTimeout(this.connectionTimeout);
|
|
152
|
-
socket.once("timeout", () => {
|
|
153
|
-
socket.destroy();
|
|
154
|
-
reject(new Error("Connection timeout"));
|
|
155
|
-
});
|
|
156
|
-
socket.once("error", reject);
|
|
157
|
-
this.socket = socket;
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
connectTls(host, port) {
|
|
161
|
-
warnRejectUnauthorizedDisabled(this.tlsOptions);
|
|
162
|
-
return new Promise((resolve, reject) => {
|
|
163
|
-
const socket = tls.connect({
|
|
164
|
-
host,
|
|
165
|
-
port,
|
|
166
|
-
servername: this.tlsOptions.servername ?? host,
|
|
167
|
-
rejectUnauthorized: this.tlsOptions.rejectUnauthorized ?? true,
|
|
168
|
-
minVersion: this.tlsOptions.minVersion
|
|
169
|
-
}, () => resolve());
|
|
170
|
-
socket.setTimeout(this.connectionTimeout);
|
|
171
|
-
socket.once("timeout", () => {
|
|
172
|
-
socket.destroy();
|
|
173
|
-
reject(new Error("Connection timeout"));
|
|
174
|
-
});
|
|
175
|
-
socket.once("error", reject);
|
|
176
|
-
this.socket = socket;
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
export {
|
|
181
|
-
BunAdapter
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
//# debugId=55F886DB16D90CED64756E2164756E21
|
|
3
|
+
//# debugId=94AAFD676203266764756E2164756E21
|
package/dist/adapters/bun.js.map
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @module\n * Bun socket adapter for SMTP connections via the Node.js compatibility layer.\n *\n * @example\n * ```ts\n * import { BunAdapter } from \"sently/adapters/bun\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * host: \"smtp.example.com\",\n * adapter: new BunAdapter(),\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * });\n * ```\n */\nimport net from \"node:net\";\nimport tls from \"node:tls\";\nimport type { SocketAdapter, TLSOptions } from \"../core/types.js\";\n\nfunction warnRejectUnauthorizedDisabled(tls: TLSOptions): void {\n if (tls.rejectUnauthorized === false) {\n console.warn(\n \"[sently] TLS certificate verification is disabled. \" +\n \"Never use rejectUnauthorized: false in production.\",\n );\n }\n}\n\ndeclare const Bun: { version: string } | undefined;\n\n/** Configuration options for {@link BunAdapter}. */\nexport interface BunAdapterOptions {\n /** Use implicit TLS on connect (port 465). Default: false. */\n secure?: boolean;\n /** Socket connect timeout in milliseconds. Default: 30_000. */\n connectionTimeout?: number;\n /** TLS options passed to node:tls (Bun Node compat layer). */\n tls?: TLSOptions;\n}\n\n/**\n * Bun socket adapter using node:net and node:tls (Node compat layer).\n */\nexport class BunAdapter implements SocketAdapter {\n /** Underlying TCP or TLS socket (Node compat layer). */\n private socket: net.Socket | tls.TLSSocket | null = null;\n /** Whether the connection is currently encrypted. */\n private _secure: boolean;\n /** Whether the socket is connected. */\n private _connected = false;\n /** Socket connect timeout in milliseconds. */\n private readonly connectionTimeout: number;\n /** TLS options for direct TLS and STARTTLS upgrades. */\n private readonly tlsOptions: TLSOptions;\n\n /** Creates a Bun socket adapter (requires the Bun runtime). */\n constructor(options: BunAdapterOptions = {}) {\n if (typeof Bun === \"undefined\") {\n throw new Error(\"BunAdapter requires the Bun runtime\");\n }\n this._secure = options.secure ?? false;\n this.connectionTimeout = options.connectionTimeout ?? 30_000;\n this.tlsOptions = options.tls ?? {};\n }\n\n /** Whether the connection uses TLS. */\n get secure(): boolean {\n return this._secure;\n }\n\n /** Whether the socket is currently connected. */\n get connected(): boolean {\n return this._connected;\n }\n\n /** Opens a TCP or TLS connection to the given host and port. */\n async connect(host: string, port: number): Promise<void> {\n if (this._secure) {\n await this.connectTls(host, port);\n } else {\n await this.connectPlain(host, port);\n }\n this._connected = true;\n }\n\n /** Upgrades a plain connection to TLS via STARTTLS. */\n async startTLS(options?: TLSOptions): Promise<void> {\n if (!this.socket || this._secure) {\n throw new Error(\"Cannot STARTTLS: no plain socket available\");\n }\n\n const plain = this.socket;\n const merged = { ...this.tlsOptions, ...options };\n warnRejectUnauthorizedDisabled(merged);\n\n await new Promise<void>((resolve, reject) => {\n const tlsSocket = tls.connect({\n socket: plain,\n servername: merged.servername,\n rejectUnauthorized: merged.rejectUnauthorized ?? true,\n minVersion: merged.minVersion,\n });\n\n tlsSocket.once(\"secureConnect\", () => {\n this.socket = tlsSocket;\n this._secure = true;\n resolve();\n });\n tlsSocket.once(\"error\", reject);\n });\n }\n\n /** Writes raw bytes to the socket. */\n async write(data: Uint8Array): Promise<void> {\n if (!this.socket) {\n throw new Error(\"Socket not connected\");\n }\n\n await new Promise<void>((resolve, reject) => {\n this.socket?.write(Buffer.from(data), (err: Error | null | undefined) => {\n if (err) {\n reject(err);\n } else {\n resolve();\n }\n });\n });\n }\n\n /** Reads incoming socket data as an async iterable of byte chunks. */\n async *read(): AsyncGenerator<Uint8Array, void, unknown> {\n if (!this.socket) {\n throw new Error(\"Socket not connected\");\n }\n\n const socket = this.socket;\n const queue: Uint8Array[] = [];\n let resolveNext: ((value: IteratorResult<Uint8Array>) => void) | null = null;\n let done = false;\n let error: Error | null = null;\n\n const onData = (chunk: Buffer): void => {\n const data = new Uint8Array(chunk);\n if (resolveNext) {\n resolveNext({ value: data, done: false });\n resolveNext = null;\n } else {\n queue.push(data);\n }\n };\n\n const onError = (err: Error): void => {\n error = err;\n done = true;\n if (resolveNext) {\n resolveNext({ value: undefined as unknown as Uint8Array, done: true });\n resolveNext = null;\n }\n };\n\n const onClose = (): void => {\n done = true;\n if (resolveNext) {\n resolveNext({ value: undefined as unknown as Uint8Array, done: true });\n resolveNext = null;\n }\n };\n\n socket.on(\"data\", onData);\n socket.on(\"error\", onError);\n socket.on(\"close\", onClose);\n\n try {\n while (!done || queue.length > 0) {\n if (error) {\n throw error;\n }\n if (queue.length > 0) {\n yield queue.shift() as Uint8Array;\n continue;\n }\n if (done) {\n break;\n }\n const chunk = await new Promise<IteratorResult<Uint8Array>>((resolve) => {\n resolveNext = resolve;\n });\n if (chunk.done) {\n break;\n }\n yield chunk.value;\n }\n } finally {\n socket.off(\"data\", onData);\n socket.off(\"error\", onError);\n socket.off(\"close\", onClose);\n }\n }\n\n /** Closes the socket connection. */\n async close(): Promise<void> {\n if (!this.socket) {\n return;\n }\n\n await new Promise<void>((resolve) => {\n this.socket?.end(() => resolve());\n });\n this.socket = null;\n this._connected = false;\n }\n\n /** Opens a plain TCP connection to the SMTP server. */\n private connectPlain(host: string, port: number): Promise<void> {\n return new Promise((resolve, reject) => {\n const socket = net.connect({ host, port }, () => resolve());\n socket.setTimeout(this.connectionTimeout);\n socket.once(\"timeout\", () => {\n socket.destroy();\n reject(new Error(\"Connection timeout\"));\n });\n socket.once(\"error\", reject);\n this.socket = socket;\n });\n }\n\n /** Opens a direct TLS connection to the SMTP server. */\n private connectTls(host: string, port: number): Promise<void> {\n warnRejectUnauthorizedDisabled(this.tlsOptions);\n return new Promise((resolve, reject) => {\n const socket = tls.connect(\n {\n host,\n port,\n servername: this.tlsOptions.servername ?? host,\n rejectUnauthorized: this.tlsOptions.rejectUnauthorized ?? true,\n minVersion: this.tlsOptions.minVersion,\n },\n () => resolve(),\n );\n socket.setTimeout(this.connectionTimeout);\n socket.once(\"timeout\", () => {\n socket.destroy();\n reject(new Error(\"Connection timeout\"));\n });\n socket.once(\"error\", reject);\n this.socket = socket;\n });\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "94AAFD676203266764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/adapters/cf.js
CHANGED
|
@@ -1,78 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__require
|
|
3
|
-
} from "../chunk-v0bahtg2.js";
|
|
1
|
+
import{I as q}from"../chunk-sqn04kae.js";class w{socket=null;writer=null;_secure;_connected=!1;directTls;starttls;constructor(b={}){this._secure=b.secure??!1,this.directTls=b.secure??!1,this.starttls=b.starttls??!this.directTls}get secure(){return this._secure}get connected(){return this._connected}async connect(b,g){let{connect:j}=await import("cloudflare:sockets"),m=this.directTls?"on":this.starttls?"starttls":"off";this.socket=j({hostname:b,port:g},{secureTransport:m}),this.writer=this.socket.writable.getWriter(),this._connected=!0,this._secure=m==="on"}async startTLS(b){if(!this.socket||this._secure)throw Error("Cannot STARTTLS: no plain socket available");await this.writer?.close(),this.socket=this.socket.startTls(),this.writer=this.socket.writable.getWriter(),this._secure=!0}async write(b){if(!this.writer)throw Error("Socket not connected");await this.writer.write(b)}async*read(){if(!this.socket)throw Error("Socket not connected");let b=this.socket.readable.getReader();try{while(!0){let{value:g,done:j}=await b.read();if(j)break;if(g)yield g}}finally{b.releaseLock()}}async close(){await this.writer?.close(),await this.socket?.close(),this.writer=null,this.socket=null,this._connected=!1}}export{w as CloudflareAdapter};
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
class CloudflareAdapter {
|
|
7
|
-
socket = null;
|
|
8
|
-
writer = null;
|
|
9
|
-
_secure;
|
|
10
|
-
_connected = false;
|
|
11
|
-
directTls;
|
|
12
|
-
starttls;
|
|
13
|
-
constructor(options = {}) {
|
|
14
|
-
this._secure = options.secure ?? false;
|
|
15
|
-
this.directTls = options.secure ?? false;
|
|
16
|
-
this.starttls = options.starttls ?? !this.directTls;
|
|
17
|
-
}
|
|
18
|
-
get secure() {
|
|
19
|
-
return this._secure;
|
|
20
|
-
}
|
|
21
|
-
get connected() {
|
|
22
|
-
return this._connected;
|
|
23
|
-
}
|
|
24
|
-
async connect(host, port) {
|
|
25
|
-
const { connect } = await import("cloudflare:sockets");
|
|
26
|
-
const secureTransport = this.directTls ? "on" : this.starttls ? "starttls" : "off";
|
|
27
|
-
this.socket = connect({ hostname: host, port }, { secureTransport });
|
|
28
|
-
this.writer = this.socket.writable.getWriter();
|
|
29
|
-
this._connected = true;
|
|
30
|
-
this._secure = secureTransport === "on";
|
|
31
|
-
}
|
|
32
|
-
async startTLS(_options) {
|
|
33
|
-
if (!this.socket || this._secure) {
|
|
34
|
-
throw new Error("Cannot STARTTLS: no plain socket available");
|
|
35
|
-
}
|
|
36
|
-
await this.writer?.close();
|
|
37
|
-
this.socket = this.socket.startTls();
|
|
38
|
-
this.writer = this.socket.writable.getWriter();
|
|
39
|
-
this._secure = true;
|
|
40
|
-
}
|
|
41
|
-
async write(data) {
|
|
42
|
-
if (!this.writer) {
|
|
43
|
-
throw new Error("Socket not connected");
|
|
44
|
-
}
|
|
45
|
-
await this.writer.write(data);
|
|
46
|
-
}
|
|
47
|
-
async* read() {
|
|
48
|
-
if (!this.socket) {
|
|
49
|
-
throw new Error("Socket not connected");
|
|
50
|
-
}
|
|
51
|
-
const reader = this.socket.readable.getReader();
|
|
52
|
-
try {
|
|
53
|
-
while (true) {
|
|
54
|
-
const { value, done } = await reader.read();
|
|
55
|
-
if (done) {
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
if (value) {
|
|
59
|
-
yield value;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
} finally {
|
|
63
|
-
reader.releaseLock();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
async close() {
|
|
67
|
-
await this.writer?.close();
|
|
68
|
-
await this.socket?.close();
|
|
69
|
-
this.writer = null;
|
|
70
|
-
this.socket = null;
|
|
71
|
-
this._connected = false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
export {
|
|
75
|
-
CloudflareAdapter
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
//# debugId=035AC57F8987550264756E2164756E21
|
|
3
|
+
//# debugId=698255E287F8C83164756E2164756E21
|
package/dist/adapters/cf.js.map
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * @module\n * Cloudflare Workers socket adapter for SMTP via cloudflare:sockets.\n *\n * @example\n * ```ts\n * import { CloudflareAdapter } from \"sently/adapters/cf\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * host: \"smtp.example.com\",\n * adapter: new CloudflareAdapter(),\n * auth: { user: \"relay@example.com\", pass: \"secret\" },\n * });\n * ```\n */\nimport type { SocketAdapter, TLSOptions } from \"../core/types.js\";\n\ninterface CFSocket {\n readonly readable: ReadableStream<Uint8Array>;\n readonly writable: WritableStream<Uint8Array>;\n readonly closed: Promise<void>;\n close(): Promise<void>;\n startTls(): CFSocket;\n}\n\ntype CFConnect = (\n address: { hostname: string; port: number },\n options?: { secureTransport?: \"off\" | \"on\" | \"starttls\"; allowHalfOpen?: boolean },\n) => CFSocket;\n\n/** Configuration options for {@link CloudflareAdapter}. */\nexport interface CloudflareAdapterOptions {\n /** Use implicit TLS on connect (secureTransport: \"on\"). Default: false. */\n secure?: boolean;\n /** Enable STARTTLS upgrade after plain connect. Default: true when not secure. */\n starttls?: boolean;\n /** Reserved for future TLS tuning (Workers sockets API). */\n tls?: TLSOptions;\n}\n\n/**\n * Cloudflare Workers socket adapter via cloudflare:sockets.\n *\n * Limitations:\n * - No connection pooling (isolate lifecycle)\n * - No file system access for attachment.path\n * - No DNS MX lookup — explicit SMTP relay host required\n */\nexport class CloudflareAdapter implements SocketAdapter {\n /** Underlying Cloudflare Workers socket. */\n private socket: CFSocket | null = null;\n /** Writable stream writer for outbound bytes. */\n private writer: WritableStreamDefaultWriter<Uint8Array> | null = null;\n /** Whether the connection is currently encrypted. */\n private _secure: boolean;\n /** Whether the socket is connected. */\n private _connected = false;\n /** Whether implicit TLS is used on connect (port 465 style). */\n private readonly directTls: boolean;\n /** Whether STARTTLS upgrade is enabled after plain connect. */\n private readonly starttls: boolean;\n\n /** Creates a Cloudflare Workers socket adapter. */\n constructor(options: CloudflareAdapterOptions = {}) {\n this._secure = options.secure ?? false;\n this.directTls = options.secure ?? false;\n this.starttls = options.starttls ?? !this.directTls;\n }\n\n /** Whether the connection uses TLS. */\n get secure(): boolean {\n return this._secure;\n }\n\n /** Whether the socket is currently connected. */\n get connected(): boolean {\n return this._connected;\n }\n\n /** Opens a TCP or TLS connection to the given host and port. */\n async connect(host: string, port: number): Promise<void> {\n const { connect } = (await import(\"cloudflare:sockets\")) as { connect: CFConnect };\n\n const secureTransport = this.directTls ? \"on\" : this.starttls ? \"starttls\" : \"off\";\n this.socket = connect({ hostname: host, port }, { secureTransport });\n this.writer = this.socket.writable.getWriter();\n this._connected = true;\n this._secure = secureTransport === \"on\";\n }\n\n /** Upgrades a plain connection to TLS via STARTTLS. */\n async startTLS(_options?: TLSOptions): Promise<void> {\n if (!this.socket || this._secure) {\n throw new Error(\"Cannot STARTTLS: no plain socket available\");\n }\n\n await this.writer?.close();\n this.socket = this.socket.startTls();\n this.writer = this.socket.writable.getWriter();\n this._secure = true;\n }\n\n /** Writes raw bytes to the socket. */\n async write(data: Uint8Array): Promise<void> {\n if (!this.writer) {\n throw new Error(\"Socket not connected\");\n }\n await this.writer.write(data);\n }\n\n /** Reads incoming socket data as an async iterable of byte chunks. */\n async *read(): AsyncGenerator<Uint8Array, void, unknown> {\n if (!this.socket) {\n throw new Error(\"Socket not connected\");\n }\n\n const reader = this.socket.readable.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n break;\n }\n if (value) {\n yield value;\n }\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n /** Closes the socket connection. */\n async close(): Promise<void> {\n await this.writer?.close();\n await this.socket?.close();\n this.writer = null;\n this.socket = null;\n this._connected = false;\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "yCAiDO,MAAM,CAA2C,CAE9C,OAA0B,KAE1B,OAAyD,KAEzD,QAEA,WAAa,GAEJ,UAEA,SAGjB,WAAW,CAAC,EAAoC,CAAC,EAAG,CAClD,KAAK,QAAU,EAAQ,QAAU,GACjC,KAAK,UAAY,EAAQ,QAAU,GACnC,KAAK,SAAW,EAAQ,UAAY,CAAC,KAAK,aAIxC,OAAM,EAAY,CACpB,OAAO,KAAK,WAIV,UAAS,EAAY,CACvB,OAAO,KAAK,gBAIR,QAAO,CAAC,EAAc,EAA6B,CACvD,IAAQ,WAAa,KAAa,8BAE5B,EAAkB,KAAK,UAAY,KAAO,KAAK,SAAW,WAAa,MAC7E,KAAK,OAAS,EAAQ,CAAE,SAAU,EAAM,MAAK,EAAG,CAAE,iBAAgB,CAAC,EACnE,KAAK,OAAS,KAAK,OAAO,SAAS,UAAU,EAC7C,KAAK,WAAa,GAClB,KAAK,QAAU,IAAoB,UAI/B,SAAQ,CAAC,EAAsC,CACnD,GAAI,CAAC,KAAK,QAAU,KAAK,QACvB,MAAU,MAAM,4CAA4C,EAG9D,MAAM,KAAK,QAAQ,MAAM,EACzB,KAAK,OAAS,KAAK,OAAO,SAAS,EACnC,KAAK,OAAS,KAAK,OAAO,SAAS,UAAU,EAC7C,KAAK,QAAU,QAIX,MAAK,CAAC,EAAiC,CAC3C,GAAI,CAAC,KAAK,OACR,MAAU,MAAM,sBAAsB,EAExC,MAAM,KAAK,OAAO,MAAM,CAAI,QAIvB,IAAI,EAA8C,CACvD,GAAI,CAAC,KAAK,OACR,MAAU,MAAM,sBAAsB,EAGxC,IAAM,EAAS,KAAK,OAAO,SAAS,UAAU,EAC9C,GAAI,CACF,MAAO,GAAM,CACX,IAAQ,QAAO,QAAS,MAAM,EAAO,KAAK,EAC1C,GAAI,EACF,MAEF,GAAI,EACF,MAAM,UAGV,CACA,EAAO,YAAY,QAKjB,MAAK,EAAkB,CAC3B,MAAM,KAAK,QAAQ,MAAM,EACzB,MAAM,KAAK,QAAQ,MAAM,EACzB,KAAK,OAAS,KACd,KAAK,OAAS,KACd,KAAK,WAAa,GAEtB",
|
|
8
|
+
"debugId": "698255E287F8C83164756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|