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.
Files changed (75) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +94 -41
  3. package/dist/adapters/bun.js +2 -183
  4. package/dist/adapters/bun.js.map +2 -2
  5. package/dist/adapters/cf.js +2 -77
  6. package/dist/adapters/cf.js.map +2 -2
  7. package/dist/adapters/deno.js +2 -72
  8. package/dist/adapters/deno.js.map +2 -2
  9. package/dist/adapters/node.js +2 -180
  10. package/dist/adapters/node.js.map +2 -2
  11. package/dist/auth/oauth2.js +2 -13
  12. package/dist/auth/oauth2.js.map +1 -1
  13. package/dist/chunk-2kcwa9gt.js +4 -0
  14. package/dist/chunk-2kcwa9gt.js.map +11 -0
  15. package/dist/chunk-2t6hjer3.js +5 -0
  16. package/dist/chunk-2t6hjer3.js.map +10 -0
  17. package/dist/chunk-6yggz45h.js +5 -0
  18. package/dist/{chunk-794hc3m4.js.map → chunk-6yggz45h.js.map} +2 -2
  19. package/dist/chunk-dgkh77yp.js +4 -0
  20. package/dist/{chunk-ym3zzv8b.js.map → chunk-dgkh77yp.js.map} +2 -2
  21. package/dist/chunk-jfs80vhp.js +3 -0
  22. package/dist/{chunk-7fqv71z1.js.map → chunk-jfs80vhp.js.map} +2 -2
  23. package/dist/chunk-sqn04kae.js +4 -0
  24. package/dist/{chunk-v0bahtg2.js.map → chunk-sqn04kae.js.map} +1 -1
  25. package/dist/chunk-va2awz12.js +4 -0
  26. package/dist/{chunk-f4c9ttmr.js.map → chunk-va2awz12.js.map} +2 -2
  27. package/dist/chunk-wgtbr6ge.js +13 -0
  28. package/dist/{chunk-tymfm441.js.map → chunk-wgtbr6ge.js.map} +2 -2
  29. package/dist/core/smtp.js +2 -31
  30. package/dist/core/smtp.js.map +1 -1
  31. package/dist/core/types.d.ts +7 -0
  32. package/dist/detect.d.ts +2 -0
  33. package/dist/detect.js +2 -180
  34. package/dist/detect.js.map +4 -5
  35. package/dist/dkim.d.ts +21 -0
  36. package/dist/dkim.js +9 -0
  37. package/dist/dkim.js.map +10 -0
  38. package/dist/index.d.ts +74 -16
  39. package/dist/index.js +85 -14
  40. package/dist/mailer.d.ts +16 -0
  41. package/dist/mailer.js +3 -0
  42. package/dist/mailer.js.map +9 -0
  43. package/dist/plugins/template.js +2 -28
  44. package/dist/plugins/template.js.map +2 -2
  45. package/dist/pool/pool.js +2 -16
  46. package/dist/pool/pool.js.map +5 -3
  47. package/dist/transports/brevo.js +2 -115
  48. package/dist/transports/brevo.js.map +2 -2
  49. package/dist/transports/mailgun.js +2 -119
  50. package/dist/transports/mailgun.js.map +2 -2
  51. package/dist/transports/postmark.js +2 -113
  52. package/dist/transports/postmark.js.map +2 -2
  53. package/dist/transports/preview.js +2 -72
  54. package/dist/transports/preview.js.map +2 -2
  55. package/dist/transports/resend.js +2 -109
  56. package/dist/transports/resend.js.map +2 -2
  57. package/dist/transports/retry.js +2 -78
  58. package/dist/transports/retry.js.map +2 -2
  59. package/dist/transports/sendgrid.js +2 -132
  60. package/dist/transports/sendgrid.js.map +2 -2
  61. package/dist/transports/ses.js +5 -251
  62. package/dist/transports/ses.js.map +2 -2
  63. package/dist/transports/smtp.js +2 -26
  64. package/dist/transports/smtp.js.map +1 -1
  65. package/package.json +17 -6
  66. package/dist/chunk-794hc3m4.js +0 -105
  67. package/dist/chunk-7fqv71z1.js +0 -251
  68. package/dist/chunk-f4c9ttmr.js +0 -154
  69. package/dist/chunk-mp5c9bfd.js +0 -270
  70. package/dist/chunk-mp5c9bfd.js.map +0 -11
  71. package/dist/chunk-tymfm441.js +0 -405
  72. package/dist/chunk-v0bahtg2.js +0 -6
  73. package/dist/chunk-x3szga4k.js +0 -367
  74. package/dist/chunk-x3szga4k.js.map +0 -11
  75. 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, tree-shakes to ~6KB.
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 | ~6 KB core |
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 { createMailer, type MailOptions } from "sently";
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
- ## Tree-Shaking
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
- Each import path is a separate build entry point:
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
- vs. full Nodemailer: ~220KB
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
- Only code you import is bundled. Adapters and transports you never import are never included.
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
@@ -1,184 +1,3 @@
1
- import"../chunk-v0bahtg2.js";
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
- // src/adapters/bun.ts
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
@@ -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": "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",
8
- "debugId": "55F886DB16D90CED64756E2164756E21",
7
+ "mappings": "6BAgBA,wBACA,wBAGA,SAAS,CAA8B,CAAC,EAAuB,CAC7D,GAAI,EAAI,qBAAuB,GAC7B,QAAQ,KACN,uGAEF,EAmBG,MAAM,CAAoC,CAEvC,OAA4C,KAE5C,QAEA,WAAa,GAEJ,kBAEA,WAGjB,WAAW,CAAC,EAA6B,CAAC,EAAG,CAC3C,GAAI,OAAO,IAAQ,IACjB,MAAU,MAAM,qCAAqC,EAEvD,KAAK,QAAU,EAAQ,QAAU,GACjC,KAAK,kBAAoB,EAAQ,mBAAqB,MACtD,KAAK,WAAa,EAAQ,KAAO,CAAC,KAIhC,OAAM,EAAY,CACpB,OAAO,KAAK,WAIV,UAAS,EAAY,CACvB,OAAO,KAAK,gBAIR,QAAO,CAAC,EAAc,EAA6B,CACvD,GAAI,KAAK,QACP,MAAM,KAAK,WAAW,EAAM,CAAI,EAEhC,WAAM,KAAK,aAAa,EAAM,CAAI,EAEpC,KAAK,WAAa,QAId,SAAQ,CAAC,EAAqC,CAClD,GAAI,CAAC,KAAK,QAAU,KAAK,QACvB,MAAU,MAAM,4CAA4C,EAG9D,IAAM,EAAQ,KAAK,OACb,EAAS,IAAK,KAAK,cAAe,CAAQ,EAChD,EAA+B,CAAM,EAErC,MAAM,IAAI,QAAc,CAAC,EAAS,IAAW,CAC3C,IAAM,EAAY,EAAI,QAAQ,CAC5B,OAAQ,EACR,WAAY,EAAO,WACnB,mBAAoB,EAAO,oBAAsB,GACjD,WAAY,EAAO,UACrB,CAAC,EAED,EAAU,KAAK,gBAAiB,IAAM,CACpC,KAAK,OAAS,EACd,KAAK,QAAU,GACf,EAAQ,EACT,EACD,EAAU,KAAK,QAAS,CAAM,EAC/B,OAIG,MAAK,CAAC,EAAiC,CAC3C,GAAI,CAAC,KAAK,OACR,MAAU,MAAM,sBAAsB,EAGxC,MAAM,IAAI,QAAc,CAAC,EAAS,IAAW,CAC3C,KAAK,QAAQ,MAAM,OAAO,KAAK,CAAI,EAAG,CAAC,IAAkC,CACvE,GAAI,EACF,EAAO,CAAG,EAEV,OAAQ,EAEX,EACF,QAII,IAAI,EAA8C,CACvD,GAAI,CAAC,KAAK,OACR,MAAU,MAAM,sBAAsB,EAGxC,IAAM,EAAS,KAAK,OACd,EAAsB,CAAC,EACzB,EAAoE,KACpE,EAAO,GACP,EAAsB,KAEpB,EAAS,CAAC,IAAwB,CACtC,IAAM,EAAO,IAAI,WAAW,CAAK,EACjC,GAAI,EACF,EAAY,CAAE,MAAO,EAAM,KAAM,EAAM,CAAC,EACxC,EAAc,KAEd,OAAM,KAAK,CAAI,GAIb,EAAU,CAAC,IAAqB,CAGpC,GAFA,EAAQ,EACR,EAAO,GACH,EACF,EAAY,CAAE,MAAO,OAAoC,KAAM,EAAK,CAAC,EACrE,EAAc,MAIZ,EAAU,IAAY,CAE1B,GADA,EAAO,GACH,EACF,EAAY,CAAE,MAAO,OAAoC,KAAM,EAAK,CAAC,EACrE,EAAc,MAIlB,EAAO,GAAG,OAAQ,CAAM,EACxB,EAAO,GAAG,QAAS,CAAO,EAC1B,EAAO,GAAG,QAAS,CAAO,EAE1B,GAAI,CACF,MAAO,CAAC,GAAQ,EAAM,OAAS,EAAG,CAChC,GAAI,EACF,MAAM,EAER,GAAI,EAAM,OAAS,EAAG,CACpB,MAAM,EAAM,MAAM,EAClB,SAEF,GAAI,EACF,MAEF,IAAM,EAAQ,MAAM,IAAI,QAAoC,CAAC,IAAY,CACvE,EAAc,EACf,EACD,GAAI,EAAM,KACR,MAEF,MAAM,EAAM,cAEd,CACA,EAAO,IAAI,OAAQ,CAAM,EACzB,EAAO,IAAI,QAAS,CAAO,EAC3B,EAAO,IAAI,QAAS,CAAO,QAKzB,MAAK,EAAkB,CAC3B,GAAI,CAAC,KAAK,OACR,OAGF,MAAM,IAAI,QAAc,CAAC,IAAY,CACnC,KAAK,QAAQ,IAAI,IAAM,EAAQ,CAAC,EACjC,EACD,KAAK,OAAS,KACd,KAAK,WAAa,GAIZ,YAAY,CAAC,EAAc,EAA6B,CAC9D,OAAO,IAAI,QAAQ,CAAC,EAAS,IAAW,CACtC,IAAM,EAAS,EAAI,QAAQ,CAAE,OAAM,MAAK,EAAG,IAAM,EAAQ,CAAC,EAC1D,EAAO,WAAW,KAAK,iBAAiB,EACxC,EAAO,KAAK,UAAW,IAAM,CAC3B,EAAO,QAAQ,EACf,EAAW,MAAM,oBAAoB,CAAC,EACvC,EACD,EAAO,KAAK,QAAS,CAAM,EAC3B,KAAK,OAAS,EACf,EAIK,UAAU,CAAC,EAAc,EAA6B,CAE5D,OADA,EAA+B,KAAK,UAAU,EACvC,IAAI,QAAQ,CAAC,EAAS,IAAW,CACtC,IAAM,EAAS,EAAI,QACjB,CACE,OACA,OACA,WAAY,KAAK,WAAW,YAAc,EAC1C,mBAAoB,KAAK,WAAW,oBAAsB,GAC1D,WAAY,KAAK,WAAW,UAC9B,EACA,IAAM,EAAQ,CAChB,EACA,EAAO,WAAW,KAAK,iBAAiB,EACxC,EAAO,KAAK,UAAW,IAAM,CAC3B,EAAO,QAAQ,EACf,EAAW,MAAM,oBAAoB,CAAC,EACvC,EACD,EAAO,KAAK,QAAS,CAAM,EAC3B,KAAK,OAAS,EACf,EAEL",
8
+ "debugId": "94AAFD676203266764756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -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
- // src/adapters/cf.ts
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
@@ -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": ";;;;;AAiDO,MAAM,kBAA2C;AAAA,EAE9C,SAA0B;AAAA,EAE1B,SAAyD;AAAA,EAEzD;AAAA,EAEA,aAAa;AAAA,EAEJ;AAAA,EAEA;AAAA,EAGjB,WAAW,CAAC,UAAoC,CAAC,GAAG;AAAA,IAClD,KAAK,UAAU,QAAQ,UAAU;AAAA,IACjC,KAAK,YAAY,QAAQ,UAAU;AAAA,IACnC,KAAK,WAAW,QAAQ,YAAY,CAAC,KAAK;AAAA;AAAA,MAIxC,MAAM,GAAY;AAAA,IACpB,OAAO,KAAK;AAAA;AAAA,MAIV,SAAS,GAAY;AAAA,IACvB,OAAO,KAAK;AAAA;AAAA,OAIR,QAAO,CAAC,MAAc,MAA6B;AAAA,IACvD,QAAQ,YAAa,MAAa;AAAA,IAElC,MAAM,kBAAkB,KAAK,YAAY,OAAO,KAAK,WAAW,aAAa;AAAA,IAC7E,KAAK,SAAS,QAAQ,EAAE,UAAU,MAAM,KAAK,GAAG,EAAE,gBAAgB,CAAC;AAAA,IACnE,KAAK,SAAS,KAAK,OAAO,SAAS,UAAU;AAAA,IAC7C,KAAK,aAAa;AAAA,IAClB,KAAK,UAAU,oBAAoB;AAAA;AAAA,OAI/B,SAAQ,CAAC,UAAsC;AAAA,IACnD,IAAI,CAAC,KAAK,UAAU,KAAK,SAAS;AAAA,MAChC,MAAM,IAAI,MAAM,4CAA4C;AAAA,IAC9D;AAAA,IAEA,MAAM,KAAK,QAAQ,MAAM;AAAA,IACzB,KAAK,SAAS,KAAK,OAAO,SAAS;AAAA,IACnC,KAAK,SAAS,KAAK,OAAO,SAAS,UAAU;AAAA,IAC7C,KAAK,UAAU;AAAA;AAAA,OAIX,MAAK,CAAC,MAAiC;AAAA,IAC3C,IAAI,CAAC,KAAK,QAAQ;AAAA,MAChB,MAAM,IAAI,MAAM,sBAAsB;AAAA,IACxC;AAAA,IACA,MAAM,KAAK,OAAO,MAAM,IAAI;AAAA;AAAA,SAIvB,IAAI,GAA8C;AAAA,IACvD,IAAI,CAAC,KAAK,QAAQ;AAAA,MAChB,MAAM,IAAI,MAAM,sBAAsB;AAAA,IACxC;AAAA,IAEA,MAAM,SAAS,KAAK,OAAO,SAAS,UAAU;AAAA,IAC9C,IAAI;AAAA,MACF,OAAO,MAAM;AAAA,QACX,QAAQ,OAAO,SAAS,MAAM,OAAO,KAAK;AAAA,QAC1C,IAAI,MAAM;AAAA,UACR;AAAA,QACF;AAAA,QACA,IAAI,OAAO;AAAA,UACT,MAAM;AAAA,QACR;AAAA,MACF;AAAA,cACA;AAAA,MACA,OAAO,YAAY;AAAA;AAAA;AAAA,OAKjB,MAAK,GAAkB;AAAA,IAC3B,MAAM,KAAK,QAAQ,MAAM;AAAA,IACzB,MAAM,KAAK,QAAQ,MAAM;AAAA,IACzB,KAAK,SAAS;AAAA,IACd,KAAK,SAAS;AAAA,IACd,KAAK,aAAa;AAAA;AAEtB;",
8
- "debugId": "035AC57F8987550264756E2164756E21",
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
  }