sently 0.4.4 → 0.4.6
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 +18 -0
- package/dist/adapters/bun.d.ts +10 -0
- package/dist/adapters/bun.js.map +2 -2
- package/dist/adapters/cf.d.ts +9 -0
- package/dist/adapters/cf.js.map +2 -2
- package/dist/adapters/deno.d.ts +7 -0
- package/dist/adapters/deno.js.map +2 -2
- package/dist/adapters/node.d.ts +10 -0
- package/dist/adapters/node.js.map +2 -2
- package/dist/auth/oauth2.d.ts +7 -0
- package/dist/chunk-7fqv71z1.js.map +2 -2
- package/dist/chunk-mp5c9bfd.js.map +3 -3
- package/dist/chunk-tymfm441.js.map +2 -2
- package/dist/chunk-x3szga4k.js.map +2 -2
- package/dist/chunk-ym3zzv8b.js.map +2 -2
- package/dist/core/mime.d.ts +4 -0
- package/dist/core/sigv4.d.ts +10 -0
- package/dist/core/smtp.d.ts +5 -0
- package/dist/core/types.d.ts +64 -0
- package/dist/pool/connection.d.ts +4 -0
- package/dist/pool/pool.d.ts +45 -0
- package/dist/transports/brevo.d.ts +1 -0
- package/dist/transports/brevo.js.map +2 -2
- package/dist/transports/mailgun.d.ts +3 -0
- package/dist/transports/mailgun.js.map +2 -2
- package/dist/transports/postmark.d.ts +2 -0
- package/dist/transports/postmark.js.map +2 -2
- package/dist/transports/preview.d.ts +3 -0
- package/dist/transports/preview.js.map +2 -2
- package/dist/transports/resend.d.ts +4 -0
- package/dist/transports/resend.js.map +2 -2
- package/dist/transports/retry.d.ts +12 -1
- package/dist/transports/retry.js.map +2 -2
- package/dist/transports/sendgrid.d.ts +2 -0
- package/dist/transports/sendgrid.js.map +2 -2
- package/dist/transports/ses.d.ts +5 -0
- package/dist/transports/ses.js.map +3 -3
- package/dist/transports/smtp.d.ts +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.6] — 2026-05-30
|
|
4
|
+
|
|
5
|
+
### Documentation
|
|
6
|
+
|
|
7
|
+
- Documented all remaining exported symbols (class members, interface
|
|
8
|
+
properties, and internal helpers) for 100% JSR symbol documentation
|
|
9
|
+
coverage across adapters, transports, pool, OAuth2, MIME, SigV4, and
|
|
10
|
+
SMTP protocol types
|
|
11
|
+
|
|
12
|
+
## [0.4.5] — 2026-05-30
|
|
13
|
+
|
|
14
|
+
### Documentation
|
|
15
|
+
|
|
16
|
+
- Added `@module` documentation to the `./pool` entrypoint
|
|
17
|
+
- Documented exported interface properties and public API methods across
|
|
18
|
+
core types, adapters, and transports to meet JSR symbol documentation
|
|
19
|
+
requirements (80%+ threshold)
|
|
20
|
+
|
|
3
21
|
## [0.4.4] — 2026-05-30
|
|
4
22
|
|
|
5
23
|
### Security
|
package/dist/adapters/bun.d.ts
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import type { SocketAdapter, TLSOptions } from "../core/types.js";
|
|
2
2
|
/** Configuration options for {@link BunAdapter}. */
|
|
3
3
|
export interface BunAdapterOptions {
|
|
4
|
+
/** Use implicit TLS on connect (port 465). Default: false. */
|
|
4
5
|
secure?: boolean;
|
|
6
|
+
/** Socket connect timeout in milliseconds. Default: 30_000. */
|
|
5
7
|
connectionTimeout?: number;
|
|
8
|
+
/** TLS options passed to node:tls (Bun Node compat layer). */
|
|
6
9
|
tls?: TLSOptions;
|
|
7
10
|
}
|
|
8
11
|
/**
|
|
9
12
|
* Bun socket adapter using node:net and node:tls (Node compat layer).
|
|
10
13
|
*/
|
|
11
14
|
export declare class BunAdapter implements SocketAdapter {
|
|
15
|
+
/** Underlying TCP or TLS socket (Node compat layer). */
|
|
12
16
|
private socket;
|
|
17
|
+
/** Whether the connection is currently encrypted. */
|
|
13
18
|
private _secure;
|
|
19
|
+
/** Whether the socket is connected. */
|
|
14
20
|
private _connected;
|
|
21
|
+
/** Socket connect timeout in milliseconds. */
|
|
15
22
|
private readonly connectionTimeout;
|
|
23
|
+
/** TLS options for direct TLS and STARTTLS upgrades. */
|
|
16
24
|
private readonly tlsOptions;
|
|
17
25
|
/** Creates a Bun socket adapter (requires the Bun runtime). */
|
|
18
26
|
constructor(options?: BunAdapterOptions);
|
|
@@ -30,6 +38,8 @@ export declare class BunAdapter implements SocketAdapter {
|
|
|
30
38
|
read(): AsyncGenerator<Uint8Array, void, unknown>;
|
|
31
39
|
/** Closes the socket connection. */
|
|
32
40
|
close(): Promise<void>;
|
|
41
|
+
/** Opens a plain TCP connection to the SMTP server. */
|
|
33
42
|
private connectPlain;
|
|
43
|
+
/** Opens a direct TLS connection to the SMTP server. */
|
|
34
44
|
private connectTls;
|
|
35
45
|
}
|
package/dist/adapters/bun.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/adapters/bun.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
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 secure?: boolean;\n connectionTimeout?: number;\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 private socket: net.Socket | tls.TLSSocket | null = null;\n private _secure: boolean;\n private _connected = false;\n private readonly connectionTimeout: number;\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 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 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"
|
|
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": ";;;AAgBA;AACA;AAGA,SAAS,8BAA8B,CAAC,MAAuB;AAAA,EAC7D,IAAI,KAAI,uBAAuB,OAAO;AAAA,IACpC,QAAQ,KACN,wDACE,oDACJ;AAAA,EACF;AAAA;AAAA;
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "55F886DB16D90CED64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/adapters/cf.d.ts
CHANGED
|
@@ -17,8 +17,11 @@
|
|
|
17
17
|
import type { SocketAdapter, TLSOptions } from "../core/types.js";
|
|
18
18
|
/** Configuration options for {@link CloudflareAdapter}. */
|
|
19
19
|
export interface CloudflareAdapterOptions {
|
|
20
|
+
/** Use implicit TLS on connect (secureTransport: "on"). Default: false. */
|
|
20
21
|
secure?: boolean;
|
|
22
|
+
/** Enable STARTTLS upgrade after plain connect. Default: true when not secure. */
|
|
21
23
|
starttls?: boolean;
|
|
24
|
+
/** Reserved for future TLS tuning (Workers sockets API). */
|
|
22
25
|
tls?: TLSOptions;
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
@@ -30,11 +33,17 @@ export interface CloudflareAdapterOptions {
|
|
|
30
33
|
* - No DNS MX lookup — explicit SMTP relay host required
|
|
31
34
|
*/
|
|
32
35
|
export declare class CloudflareAdapter implements SocketAdapter {
|
|
36
|
+
/** Underlying Cloudflare Workers socket. */
|
|
33
37
|
private socket;
|
|
38
|
+
/** Writable stream writer for outbound bytes. */
|
|
34
39
|
private writer;
|
|
40
|
+
/** Whether the connection is currently encrypted. */
|
|
35
41
|
private _secure;
|
|
42
|
+
/** Whether the socket is connected. */
|
|
36
43
|
private _connected;
|
|
44
|
+
/** Whether implicit TLS is used on connect (port 465 style). */
|
|
37
45
|
private readonly directTls;
|
|
46
|
+
/** Whether STARTTLS upgrade is enabled after plain connect. */
|
|
38
47
|
private readonly starttls;
|
|
39
48
|
/** Creates a Cloudflare Workers socket adapter. */
|
|
40
49
|
constructor(options?: CloudflareAdapterOptions);
|
package/dist/adapters/cf.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/adapters/cf.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
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 secure?: boolean;\n starttls?: boolean;\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 private socket: CFSocket | null = null;\n private writer: WritableStreamDefaultWriter<Uint8Array> | null = null;\n private _secure: boolean;\n private _connected = false;\n private readonly directTls: boolean;\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"
|
|
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": ";;;;;
|
|
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
8
|
"debugId": "035AC57F8987550264756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/adapters/deno.d.ts
CHANGED
|
@@ -17,17 +17,24 @@
|
|
|
17
17
|
import type { SocketAdapter, TLSOptions } from "../core/types.js";
|
|
18
18
|
/** Configuration options for {@link DenoAdapter}. */
|
|
19
19
|
export interface DenoAdapterOptions {
|
|
20
|
+
/** Use implicit TLS on connect (port 465). Default: false. */
|
|
20
21
|
secure?: boolean;
|
|
22
|
+
/** Socket connect timeout in milliseconds. Default: 30_000. */
|
|
21
23
|
connectionTimeout?: number;
|
|
24
|
+
/** TLS options passed to Deno.startTls / Deno.connectTls. */
|
|
22
25
|
tls?: TLSOptions;
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
25
28
|
* Deno socket adapter using Deno.connect / Deno.startTls.
|
|
26
29
|
*/
|
|
27
30
|
export declare class DenoAdapter implements SocketAdapter {
|
|
31
|
+
/** Active Deno TCP or TLS connection. */
|
|
28
32
|
private conn;
|
|
33
|
+
/** Whether the connection is currently encrypted. */
|
|
29
34
|
private _secure;
|
|
35
|
+
/** Whether the socket is connected. */
|
|
30
36
|
private _connected;
|
|
37
|
+
/** TLS options for direct TLS and STARTTLS upgrades. */
|
|
31
38
|
private readonly tlsOptions;
|
|
32
39
|
/** Creates a Deno socket adapter (requires the Deno runtime). */
|
|
33
40
|
constructor(options?: DenoAdapterOptions);
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/adapters/deno.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Deno socket adapter for SMTP connections via Deno.connect and Deno.startTls.\n *\n * @example\n * ```ts\n * import { DenoAdapter } from \"sently/adapters/deno\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * host: \"smtp.example.com\",\n * adapter: new DenoAdapter(),\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * });\n * ```\n */\nimport type { SocketAdapter, TLSOptions } from \"../core/types.js\";\n\ndeclare const Deno: {\n connect(options: { hostname: string; port: number }): Promise<DenoTcpConn>;\n connectTls(options: {\n hostname: string;\n port: number;\n [key: string]: unknown;\n }): Promise<DenoTlsConn>;\n startTls(conn: DenoTcpConn, options?: { hostname?: string }): Promise<DenoTlsConn>;\n};\n\ninterface DenoConn {\n read(p: Uint8Array): Promise<number | null>;\n write(p: Uint8Array): Promise<number>;\n close(): void;\n}\n\ninterface DenoTcpConn extends DenoConn {}\ninterface DenoTlsConn extends DenoConn {}\n\n/** Configuration options for {@link DenoAdapter}. */\nexport interface DenoAdapterOptions {\n secure?: boolean;\n connectionTimeout?: number;\n tls?: TLSOptions;\n}\n\n/**\n * Deno socket adapter using Deno.connect / Deno.startTls.\n */\nexport class DenoAdapter implements SocketAdapter {\n private conn: DenoConn | null = null;\n private _secure: boolean;\n private _connected = false;\n private readonly tlsOptions: TLSOptions;\n\n /** Creates a Deno socket adapter (requires the Deno runtime). */\n constructor(options: DenoAdapterOptions = {}) {\n if (typeof Deno === \"undefined\") {\n throw new Error(\"DenoAdapter requires the Deno runtime\");\n }\n this._secure = options.secure ?? false;\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 this.conn = await Deno.connectTls({\n hostname: host,\n port,\n ...(this.tlsOptions.servername ? { servername: this.tlsOptions.servername } : {}),\n });\n } else {\n this.conn = await Deno.connect({ hostname: 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.conn || this._secure) {\n throw new Error(\"Cannot STARTTLS: no plain connection available\");\n }\n\n const merged = { ...this.tlsOptions, ...options };\n this.conn = await Deno.startTls(this.conn as DenoTcpConn, {\n ...(merged.servername ? { hostname: merged.servername } : {}),\n });\n this._secure = true;\n }\n\n /** Writes raw bytes to the socket. */\n async write(data: Uint8Array): Promise<void> {\n if (!this.conn) {\n throw new Error(\"Socket not connected\");\n }\n await this.conn.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.conn) {\n throw new Error(\"Socket not connected\");\n }\n\n const buffer = new Uint8Array(8192);\n while (true) {\n const n = await this.conn.read(buffer);\n if (n === null) {\n break;\n }\n yield buffer.slice(0, n);\n }\n }\n\n /** Closes the socket connection. */\n async close(): Promise<void> {\n this.conn?.close();\n this.conn = null;\n this._connected = false;\n }\n}\n"
|
|
5
|
+
"/**\n * @module\n * Deno socket adapter for SMTP connections via Deno.connect and Deno.startTls.\n *\n * @example\n * ```ts\n * import { DenoAdapter } from \"sently/adapters/deno\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * host: \"smtp.example.com\",\n * adapter: new DenoAdapter(),\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * });\n * ```\n */\nimport type { SocketAdapter, TLSOptions } from \"../core/types.js\";\n\ndeclare const Deno: {\n connect(options: { hostname: string; port: number }): Promise<DenoTcpConn>;\n connectTls(options: {\n hostname: string;\n port: number;\n [key: string]: unknown;\n }): Promise<DenoTlsConn>;\n startTls(conn: DenoTcpConn, options?: { hostname?: string }): Promise<DenoTlsConn>;\n};\n\ninterface DenoConn {\n read(p: Uint8Array): Promise<number | null>;\n write(p: Uint8Array): Promise<number>;\n close(): void;\n}\n\ninterface DenoTcpConn extends DenoConn {}\ninterface DenoTlsConn extends DenoConn {}\n\n/** Configuration options for {@link DenoAdapter}. */\nexport interface DenoAdapterOptions {\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 Deno.startTls / Deno.connectTls. */\n tls?: TLSOptions;\n}\n\n/**\n * Deno socket adapter using Deno.connect / Deno.startTls.\n */\nexport class DenoAdapter implements SocketAdapter {\n /** Active Deno TCP or TLS connection. */\n private conn: DenoConn | null = null;\n /** Whether the connection is currently encrypted. */\n private _secure: boolean;\n /** Whether the socket is connected. */\n private _connected = false;\n /** TLS options for direct TLS and STARTTLS upgrades. */\n private readonly tlsOptions: TLSOptions;\n\n /** Creates a Deno socket adapter (requires the Deno runtime). */\n constructor(options: DenoAdapterOptions = {}) {\n if (typeof Deno === \"undefined\") {\n throw new Error(\"DenoAdapter requires the Deno runtime\");\n }\n this._secure = options.secure ?? false;\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 this.conn = await Deno.connectTls({\n hostname: host,\n port,\n ...(this.tlsOptions.servername ? { servername: this.tlsOptions.servername } : {}),\n });\n } else {\n this.conn = await Deno.connect({ hostname: 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.conn || this._secure) {\n throw new Error(\"Cannot STARTTLS: no plain connection available\");\n }\n\n const merged = { ...this.tlsOptions, ...options };\n this.conn = await Deno.startTls(this.conn as DenoTcpConn, {\n ...(merged.servername ? { hostname: merged.servername } : {}),\n });\n this._secure = true;\n }\n\n /** Writes raw bytes to the socket. */\n async write(data: Uint8Array): Promise<void> {\n if (!this.conn) {\n throw new Error(\"Socket not connected\");\n }\n await this.conn.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.conn) {\n throw new Error(\"Socket not connected\");\n }\n\n const buffer = new Uint8Array(8192);\n while (true) {\n const n = await this.conn.read(buffer);\n if (n === null) {\n break;\n }\n yield buffer.slice(0, n);\n }\n }\n\n /** Closes the socket connection. */\n async close(): Promise<void> {\n this.conn?.close();\n this.conn = null;\n this._connected = false;\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;
|
|
7
|
+
"mappings": ";;;AAkDO,MAAM,YAAqC;AAAA,EAExC,OAAwB;AAAA,EAExB;AAAA,EAEA,aAAa;AAAA,EAEJ;AAAA,EAGjB,WAAW,CAAC,UAA8B,CAAC,GAAG;AAAA,IAC5C,IAAI,OAAO,SAAS,aAAa;AAAA,MAC/B,MAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAAA,IACA,KAAK,UAAU,QAAQ,UAAU;AAAA,IACjC,KAAK,aAAa,QAAQ,OAAO,CAAC;AAAA;AAAA,MAIhC,MAAM,GAAY;AAAA,IACpB,OAAO,KAAK;AAAA;AAAA,MAIV,SAAS,GAAY;AAAA,IACvB,OAAO,KAAK;AAAA;AAAA,OAIR,QAAO,CAAC,MAAc,MAA6B;AAAA,IACvD,IAAI,KAAK,SAAS;AAAA,MAChB,KAAK,OAAO,MAAM,KAAK,WAAW;AAAA,QAChC,UAAU;AAAA,QACV;AAAA,WACI,KAAK,WAAW,aAAa,EAAE,YAAY,KAAK,WAAW,WAAW,IAAI,CAAC;AAAA,MACjF,CAAC;AAAA,IACH,EAAO;AAAA,MACL,KAAK,OAAO,MAAM,KAAK,QAAQ,EAAE,UAAU,MAAM,KAAK,CAAC;AAAA;AAAA,IAEzD,KAAK,aAAa;AAAA;AAAA,OAId,SAAQ,CAAC,SAAqC;AAAA,IAClD,IAAI,CAAC,KAAK,QAAQ,KAAK,SAAS;AAAA,MAC9B,MAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AAAA,IAEA,MAAM,SAAS,KAAK,KAAK,eAAe,QAAQ;AAAA,IAChD,KAAK,OAAO,MAAM,KAAK,SAAS,KAAK,MAAqB;AAAA,SACpD,OAAO,aAAa,EAAE,UAAU,OAAO,WAAW,IAAI,CAAC;AAAA,IAC7D,CAAC;AAAA,IACD,KAAK,UAAU;AAAA;AAAA,OAIX,MAAK,CAAC,MAAiC;AAAA,IAC3C,IAAI,CAAC,KAAK,MAAM;AAAA,MACd,MAAM,IAAI,MAAM,sBAAsB;AAAA,IACxC;AAAA,IACA,MAAM,KAAK,KAAK,MAAM,IAAI;AAAA;AAAA,SAIrB,IAAI,GAA8C;AAAA,IACvD,IAAI,CAAC,KAAK,MAAM;AAAA,MACd,MAAM,IAAI,MAAM,sBAAsB;AAAA,IACxC;AAAA,IAEA,MAAM,SAAS,IAAI,WAAW,IAAI;AAAA,IAClC,OAAO,MAAM;AAAA,MACX,MAAM,IAAI,MAAM,KAAK,KAAK,KAAK,MAAM;AAAA,MACrC,IAAI,MAAM,MAAM;AAAA,QACd;AAAA,MACF;AAAA,MACA,MAAM,OAAO,MAAM,GAAG,CAAC;AAAA,IACzB;AAAA;AAAA,OAII,MAAK,GAAkB;AAAA,IAC3B,KAAK,MAAM,MAAM;AAAA,IACjB,KAAK,OAAO;AAAA,IACZ,KAAK,aAAa;AAAA;AAEtB;",
|
|
8
8
|
"debugId": "871B95B1C9304E4564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/adapters/node.d.ts
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import type { SocketAdapter, TLSOptions } from "../core/types.js";
|
|
2
2
|
/** Configuration options for {@link NodeAdapter}. */
|
|
3
3
|
export interface NodeAdapterOptions {
|
|
4
|
+
/** Use implicit TLS on connect (port 465). Default: false. */
|
|
4
5
|
secure?: boolean;
|
|
6
|
+
/** Socket connect timeout in milliseconds. Default: 30_000. */
|
|
5
7
|
connectionTimeout?: number;
|
|
8
|
+
/** TLS options passed to node:tls. */
|
|
6
9
|
tls?: TLSOptions;
|
|
7
10
|
}
|
|
8
11
|
/**
|
|
9
12
|
* Node.js socket adapter using node:net and node:tls.
|
|
10
13
|
*/
|
|
11
14
|
export declare class NodeAdapter implements SocketAdapter {
|
|
15
|
+
/** Underlying Node.js TCP or TLS socket. */
|
|
12
16
|
private socket;
|
|
17
|
+
/** Whether the connection is currently encrypted. */
|
|
13
18
|
private _secure;
|
|
19
|
+
/** Whether the socket is connected. */
|
|
14
20
|
private _connected;
|
|
21
|
+
/** Socket connect timeout in milliseconds. */
|
|
15
22
|
private readonly connectionTimeout;
|
|
23
|
+
/** TLS options for direct TLS and STARTTLS upgrades. */
|
|
16
24
|
private readonly tlsOptions;
|
|
17
25
|
/** Creates a Node.js socket adapter. */
|
|
18
26
|
constructor(options?: NodeAdapterOptions);
|
|
@@ -30,6 +38,8 @@ export declare class NodeAdapter implements SocketAdapter {
|
|
|
30
38
|
read(): AsyncGenerator<Uint8Array, void, unknown>;
|
|
31
39
|
/** Closes the socket connection. */
|
|
32
40
|
close(): Promise<void>;
|
|
41
|
+
/** Opens a plain TCP connection to the SMTP server. */
|
|
33
42
|
private connectPlain;
|
|
43
|
+
/** Opens a direct TLS connection to the SMTP server. */
|
|
34
44
|
private connectTls;
|
|
35
45
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/adapters/node.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * Node.js socket adapter for SMTP connections via node:net and node:tls.\n *\n * @example\n * ```ts\n * import { NodeAdapter } from \"sently/adapters/node\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * host: \"smtp.example.com\",\n * adapter: new NodeAdapter(),\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\n/** Configuration options for {@link NodeAdapter}. */\nexport interface NodeAdapterOptions {\n secure?: boolean;\n connectionTimeout?: number;\n tls?: TLSOptions;\n}\n\n/**\n * Node.js socket adapter using node:net and node:tls.\n */\nexport class NodeAdapter implements SocketAdapter {\n private socket: net.Socket | tls.TLSSocket | null = null;\n private _secure: boolean;\n private _connected = false;\n private readonly connectionTimeout: number;\n private readonly tlsOptions: TLSOptions;\n\n /** Creates a Node.js socket adapter. */\n constructor(options: NodeAdapterOptions = {}) {\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 if (resolveNext) {\n resolveNext({ value: undefined as unknown as Uint8Array, done: true });\n resolveNext = null;\n }\n done = true;\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 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 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"
|
|
5
|
+
"/**\n * @module\n * Node.js socket adapter for SMTP connections via node:net and node:tls.\n *\n * @example\n * ```ts\n * import { NodeAdapter } from \"sently/adapters/node\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * host: \"smtp.example.com\",\n * adapter: new NodeAdapter(),\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\n/** Configuration options for {@link NodeAdapter}. */\nexport interface NodeAdapterOptions {\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. */\n tls?: TLSOptions;\n}\n\n/**\n * Node.js socket adapter using node:net and node:tls.\n */\nexport class NodeAdapter implements SocketAdapter {\n /** Underlying Node.js TCP or TLS socket. */\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 Node.js socket adapter. */\n constructor(options: NodeAdapterOptions = {}) {\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 if (resolveNext) {\n resolveNext({ value: undefined as unknown as Uint8Array, done: true });\n resolveNext = null;\n }\n done = true;\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": ";;;AAgBA;AACA;AAGA,SAAS,8BAA8B,CAAC,MAAuB;AAAA,EAC7D,IAAI,KAAI,uBAAuB,OAAO;AAAA,IACpC,QAAQ,KACN,wDACE,oDACJ;AAAA,EACF;AAAA;AAAA;
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "ECC754A0A0C9909F64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/auth/oauth2.d.ts
CHANGED
|
@@ -5,17 +5,24 @@ export declare const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
|
5
5
|
export declare const MICROSOFT_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
|
|
6
6
|
/** OAuth2 token endpoint response shape. */
|
|
7
7
|
export interface TokenResponse {
|
|
8
|
+
/** Bearer access token for API or SMTP XOAUTH2. */
|
|
8
9
|
access_token: string;
|
|
10
|
+
/** Token lifetime in seconds. */
|
|
9
11
|
expires_in: number;
|
|
12
|
+
/** Token type (typically `"Bearer"`). */
|
|
10
13
|
token_type: string;
|
|
11
14
|
}
|
|
12
15
|
/**
|
|
13
16
|
* OAuth2 client with in-memory token cache and automatic refresh.
|
|
14
17
|
*/
|
|
15
18
|
export declare class OAuth2Client {
|
|
19
|
+
/** OAuth2 configuration supplied at construction. */
|
|
16
20
|
private readonly config;
|
|
21
|
+
/** Cached access token, or null before the first fetch. */
|
|
17
22
|
private cachedToken;
|
|
23
|
+
/** Expiry timestamp (ms) for the cached access token. */
|
|
18
24
|
private expiresAt;
|
|
25
|
+
/** In-flight refresh promise used to deduplicate concurrent refreshes. */
|
|
19
26
|
private refreshPromise;
|
|
20
27
|
/** Creates an OAuth2 client from configuration. */
|
|
21
28
|
constructor(config: OAuth2Config);
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/transports/smtp.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * @module\n * SMTP transport — orchestrates socket adapter, MIME builder, and protocol logic.\n *\n * @example\n * ```ts\n * import { SMTPTransport } from \"sently/transports/smtp\";\n * import { NodeAdapter } from \"sently/adapters/node\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new SMTPTransport({\n * host: \"smtp.example.com\",\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * adapter: new NodeAdapter(),\n * }),\n * });\n * ```\n */\nimport { OAuth2Client } from \"../auth/oauth2.js\";\nimport { buildMIME, type MIMEBuildResult } from \"../core/mime.js\";\nimport type { SMTPResponse } from \"../core/smtp.js\";\nimport {\n accumulateResponse,\n assertResponse,\n computeCRAMMD5,\n encodeAuthLoginPass,\n encodeAuthLoginUser,\n encodeCommand,\n encodeLine,\n parseEHLO,\n parseResponse,\n SMTPError,\n selectAuthMethod,\n} from \"../core/smtp.js\";\nimport type {\n MailOptions,\n SendResult,\n SMTPConfig,\n SocketAdapter,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/**\n * SMTP transport orchestrating adapter, MIME builder, and protocol logic.\n */\nexport class SMTPTransport implements Transport {\n private readonly config: ResolvedSMTPConfig;\n private adapter: SocketAdapter | null = null;\n\n /** Creates an SMTP transport with the given configuration. */\n constructor(config: SMTPConfig) {\n this.config = resolveSMTPConfig(config);\n }\n\n /** Sends an email via SMTP using the configured adapter. */\n async send(options: MailOptions): Promise<SendResult> {\n const resolvedOptions = {\n ...options,\n attachments: await resolveAttachments(options.attachments),\n };\n const mime = await buildMIME(resolvedOptions, this.config.dkim);\n const adapter = await this.getAdapter();\n\n const host = this.config.direct\n ? await resolveMX(mime.envelope.from.split(\"@\")[1] ?? this.config.host)\n : this.config.host;\n\n await adapter.connect(host, this.config.port);\n this.adapter = adapter;\n\n try {\n await openSMTPSession(adapter, this.config);\n return await deliverSMTPMessage(adapter, mime);\n } finally {\n await closeSMTPSession(adapter);\n this.adapter = null;\n }\n }\n\n /** Verifies SMTP connectivity and authentication without sending mail. */\n async verify(): Promise<VerifyResult> {\n try {\n const adapter = await this.getAdapter();\n await adapter.connect(this.config.host, this.config.port);\n\n try {\n await openSMTPSession(adapter, this.config);\n return { ok: true, provider: \"smtp\" };\n } finally {\n await closeSMTPSession(adapter);\n }\n } catch (err) {\n return {\n ok: false,\n provider: \"smtp\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /** Closes the underlying socket adapter if connected. */\n async close(): Promise<void> {\n if (this.adapter) {\n await this.adapter.close();\n this.adapter = null;\n }\n }\n\n private async getAdapter(): Promise<SocketAdapter> {\n if (!this.config.adapter) {\n throw new SMTPError(\"No socket adapter configured\", 0, \"CONNECT\", \"\");\n }\n return this.config.adapter;\n }\n}\n\n/** Resolved SMTP transport configuration with defaults applied. */\nexport interface ResolvedSMTPConfig {\n host: string;\n port: number;\n secure: boolean;\n auth?: SMTPConfig[\"auth\"];\n requireTLS?: boolean;\n tls?: SMTPConfig[\"tls\"];\n dkim?: SMTPConfig[\"dkim\"];\n connectionTimeout?: number;\n greetingTimeout?: number;\n socketTimeout?: number;\n direct?: boolean;\n adapter?: SocketAdapter;\n}\n\n/** Apply defaults to SMTP configuration. */\nexport function resolveSMTPConfig(config: SMTPConfig): ResolvedSMTPConfig {\n const secure = config.secure ?? false;\n return {\n host: config.host,\n port: config.port ?? (secure ? 465 : 587),\n secure,\n ...(config.auth !== undefined ? { auth: config.auth } : {}),\n ...(config.requireTLS !== undefined ? { requireTLS: config.requireTLS } : {}),\n ...(config.dkim !== undefined ? { dkim: config.dkim } : {}),\n ...(config.tls !== undefined ? { tls: config.tls } : {}),\n ...(config.connectionTimeout !== undefined\n ? { connectionTimeout: config.connectionTimeout }\n : {}),\n ...(config.greetingTimeout !== undefined ? { greetingTimeout: config.greetingTimeout } : {}),\n ...(config.socketTimeout !== undefined ? { socketTimeout: config.socketTimeout } : {}),\n ...(config.direct !== undefined ? { direct: config.direct } : {}),\n ...(config.adapter !== undefined ? { adapter: config.adapter } : {}),\n };\n}\n\n/**\n * Connect greeting, EHLO, optional STARTTLS, and AUTH on an open adapter.\n */\nexport async function openSMTPSession(\n adapter: SocketAdapter,\n config: ResolvedSMTPConfig,\n): Promise<void> {\n const greeting = await readSMTPResponse(adapter);\n assertResponse(greeting, [220], \"greeting\");\n\n let capabilities = await ehlo(adapter, config.host);\n const supportsStartTls = capabilities.some((cap) => cap.toUpperCase() === \"STARTTLS\");\n if (!config.secure && !adapter.secure && supportsStartTls) {\n await sendRaw(adapter, encodeCommand({ type: \"STARTTLS\" }));\n const starttlsResp = await readSMTPResponse(adapter);\n assertResponse(starttlsResp, [220], \"STARTTLS\");\n await adapter.startTLS(config.tls);\n capabilities = await ehlo(adapter, config.host);\n }\n\n if (config.auth) {\n const tlsRequired = config.requireTLS ?? true;\n const encrypted = adapter.secure || config.secure;\n if (tlsRequired && !encrypted) {\n throw new SMTPError(\n \"Refusing to authenticate over unencrypted connection. \" +\n \"Set requireTLS: false to disable this check (not recommended).\",\n 0,\n \"AUTH\",\n \"\",\n );\n }\n await authenticate(adapter, config.auth, capabilities);\n }\n}\n\n/**\n * MAIL FROM, RCPT TO, and DATA for a built MIME message on an authenticated session.\n */\nexport async function deliverSMTPMessage(\n adapter: SocketAdapter,\n mime: MIMEBuildResult,\n): Promise<SendResult> {\n await sendCommand(adapter, { type: \"MAIL_FROM\", address: mime.envelope.from });\n const mailResp = await readSMTPResponse(adapter);\n assertResponse(mailResp, [250], \"MAIL FROM\");\n\n const accepted: string[] = [];\n const rejected: string[] = [];\n\n for (const recipient of mime.envelope.to) {\n await sendRaw(adapter, encodeCommand({ type: \"RCPT_TO\", address: recipient }));\n const rcptResp = await readSMTPResponse(adapter);\n if (rcptResp.isSuccess) {\n accepted.push(recipient);\n } else {\n rejected.push(recipient);\n }\n }\n\n await sendCommand(adapter, { type: \"DATA\" });\n const dataResp = await readSMTPResponse(adapter);\n assertResponse(dataResp, [354], \"DATA\");\n\n let finalResp: SMTPResponse;\n try {\n await sendRaw(adapter, encodeCommand({ type: \"DATA_BODY\", content: mime.raw }));\n finalResp = await readSMTPResponse(adapter);\n } catch (err) {\n await sendRaw(adapter, encodeCommand({ type: \"DATA_BODY\", content: mime.raw }));\n finalResp = await readSMTPResponse(adapter);\n if (finalResp.isError) {\n throw err;\n }\n }\n assertResponse(finalResp, [250], \"DATA end\");\n\n return {\n messageId: mime.messageId,\n accepted,\n rejected,\n response: finalResp.message,\n envelope: mime.envelope,\n };\n}\n\n/**\n * QUIT and close an SMTP session adapter.\n */\nexport async function closeSMTPSession(adapter: SocketAdapter): Promise<void> {\n try {\n await sendCommand(adapter, { type: \"QUIT\" });\n await readSMTPResponse(adapter);\n } catch {\n // ignore errors during shutdown\n } finally {\n await adapter.close();\n }\n}\n\nasync function ehlo(adapter: SocketAdapter, host: string): Promise<string[]> {\n await sendCommand(adapter, { type: \"EHLO\", domain: host });\n const response = await readSMTPResponse(adapter);\n assertResponse(response, [250], \"EHLO\");\n return parseEHLO(response);\n}\n\nasync function authenticate(\n adapter: SocketAdapter,\n auth: NonNullable<SMTPConfig[\"auth\"]>,\n capabilities: string[],\n): Promise<void> {\n if (auth.type === \"OAUTH2\" && auth.oauth2) {\n const client = new OAuth2Client(auth.oauth2);\n const xoauth2 = await client.buildXOAUTH2();\n await sendCommand(adapter, { type: \"AUTH_XOAUTH2\", xoauth2String: xoauth2 });\n let resp = await readSMTPResponse(adapter);\n if (resp.code === 334) {\n await sendRaw(adapter, encodeLine(\"\"));\n resp = await readSMTPResponse(adapter);\n }\n assertResponse(resp, [235], \"AUTH XOAUTH2\");\n return;\n }\n\n const method = auth.type ?? selectAuthMethod(capabilities);\n\n if (method === \"CRAM-MD5\") {\n const pass = requirePassword(auth, \"CRAM-MD5\");\n await sendCommand(adapter, { type: \"AUTH_CRAM_MD5_INIT\" });\n let resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH CRAM-MD5\");\n const challenge = resp.message.trim();\n const response = await computeCRAMMD5(challenge, auth.user, pass);\n await sendCommand(adapter, { type: \"AUTH_CRAM_MD5_RESPONSE\", response });\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH CRAM-MD5 response\");\n return;\n }\n\n if (method === \"PLAIN\") {\n const pass = requirePassword(auth, \"PLAIN\");\n await sendRaw(adapter, encodeCommand({ type: \"AUTH_PLAIN\", user: auth.user, pass }));\n const resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH PLAIN\");\n return;\n }\n\n const pass = requirePassword(auth, \"LOGIN\");\n await sendRaw(adapter, encodeCommand({ type: \"AUTH_LOGIN\", user: auth.user, pass }));\n let resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH LOGIN\");\n\n await sendRaw(adapter, encodeAuthLoginUser(auth.user));\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH LOGIN user\");\n\n await sendRaw(adapter, encodeAuthLoginPass(pass));\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH LOGIN pass\");\n}\n\nfunction requirePassword(auth: NonNullable<SMTPConfig[\"auth\"]>, method: string): string {\n if (!auth.pass) {\n throw new SMTPError(`Password required for ${method} authentication`, 0, `AUTH ${method}`, \"\");\n }\n return auth.pass;\n}\n\nasync function sendCommand(\n adapter: SocketAdapter,\n command: Parameters<typeof encodeCommand>[0],\n): Promise<void> {\n await sendRaw(adapter, encodeCommand(command));\n}\n\nasync function sendRaw(adapter: SocketAdapter, data: Uint8Array): Promise<void> {\n await adapter.write(data);\n}\n\n/** Reads and parses a complete SMTP response from the adapter. */\nasync function readSMTPResponse(adapter: SocketAdapter): Promise<SMTPResponse> {\n const chunks: Uint8Array[] = [];\n for await (const chunk of adapter.read()) {\n chunks.push(chunk);\n const complete = accumulateResponse(chunks);\n if (complete) {\n return parseResponse(complete);\n }\n }\n throw new SMTPError(\"Connection closed while reading SMTP response\", 0, \"READ\", \"\");\n}\n\nasync function resolveMX(domain: string): Promise<string> {\n const dns = await import(\"node:dns/promises\");\n const records = await dns.resolveMx(domain);\n if (records.length === 0) {\n throw new SMTPError(`No MX records for ${domain}`, 0, \"MX\", \"\");\n }\n records.sort((a: { priority: number }, b: { priority: number }) => a.priority - b.priority);\n return records[0]?.exchange ?? domain;\n}\n\n/** @internal Test helper for raw line writes. */\nexport { encodeLine, readSMTPResponse };\n"
|
|
5
|
+
"/**\n * @module\n * SMTP transport — orchestrates socket adapter, MIME builder, and protocol logic.\n *\n * @example\n * ```ts\n * import { SMTPTransport } from \"sently/transports/smtp\";\n * import { NodeAdapter } from \"sently/adapters/node\";\n * import { createMailer } from \"sently\";\n *\n * const mailer = await createMailer({\n * transport: new SMTPTransport({\n * host: \"smtp.example.com\",\n * auth: { user: \"you@example.com\", pass: \"secret\" },\n * adapter: new NodeAdapter(),\n * }),\n * });\n * ```\n */\nimport { OAuth2Client } from \"../auth/oauth2.js\";\nimport { buildMIME, type MIMEBuildResult } from \"../core/mime.js\";\nimport type { SMTPResponse } from \"../core/smtp.js\";\nimport {\n accumulateResponse,\n assertResponse,\n computeCRAMMD5,\n encodeAuthLoginPass,\n encodeAuthLoginUser,\n encodeCommand,\n encodeLine,\n parseEHLO,\n parseResponse,\n SMTPError,\n selectAuthMethod,\n} from \"../core/smtp.js\";\nimport type {\n MailOptions,\n SendResult,\n SMTPConfig,\n SocketAdapter,\n Transport,\n VerifyResult,\n} from \"../core/types.js\";\nimport { resolveAttachments } from \"./resolve-attachments.js\";\n\n/**\n * SMTP transport orchestrating adapter, MIME builder, and protocol logic.\n */\nexport class SMTPTransport implements Transport {\n /** Resolved SMTP configuration with defaults applied. */\n private readonly config: ResolvedSMTPConfig;\n /** Active socket adapter for the current session, if any. */\n private adapter: SocketAdapter | null = null;\n\n /** Creates an SMTP transport with the given configuration. */\n constructor(config: SMTPConfig) {\n this.config = resolveSMTPConfig(config);\n }\n\n /** Sends an email via SMTP using the configured adapter. */\n async send(options: MailOptions): Promise<SendResult> {\n const resolvedOptions = {\n ...options,\n attachments: await resolveAttachments(options.attachments),\n };\n const mime = await buildMIME(resolvedOptions, this.config.dkim);\n const adapter = await this.getAdapter();\n\n const host = this.config.direct\n ? await resolveMX(mime.envelope.from.split(\"@\")[1] ?? this.config.host)\n : this.config.host;\n\n await adapter.connect(host, this.config.port);\n this.adapter = adapter;\n\n try {\n await openSMTPSession(adapter, this.config);\n return await deliverSMTPMessage(adapter, mime);\n } finally {\n await closeSMTPSession(adapter);\n this.adapter = null;\n }\n }\n\n /** Verifies SMTP connectivity and authentication without sending mail. */\n async verify(): Promise<VerifyResult> {\n try {\n const adapter = await this.getAdapter();\n await adapter.connect(this.config.host, this.config.port);\n\n try {\n await openSMTPSession(adapter, this.config);\n return { ok: true, provider: \"smtp\" };\n } finally {\n await closeSMTPSession(adapter);\n }\n } catch (err) {\n return {\n ok: false,\n provider: \"smtp\",\n message: err instanceof Error ? err.message : String(err),\n };\n }\n }\n\n /** Closes the underlying socket adapter if connected. */\n async close(): Promise<void> {\n if (this.adapter) {\n await this.adapter.close();\n this.adapter = null;\n }\n }\n\n /** Returns the configured socket adapter or throws if none is set. */\n private async getAdapter(): Promise<SocketAdapter> {\n if (!this.config.adapter) {\n throw new SMTPError(\"No socket adapter configured\", 0, \"CONNECT\", \"\");\n }\n return this.config.adapter;\n }\n}\n\n/** Resolved SMTP transport configuration with defaults applied. */\nexport interface ResolvedSMTPConfig {\n /** SMTP server hostname. */\n host: string;\n /** SMTP port with secure/default applied. */\n port: number;\n /** Whether implicit TLS is used. */\n secure: boolean;\n /** Resolved authentication credentials, if any. */\n auth?: SMTPConfig[\"auth\"];\n /** Whether AUTH requires an encrypted connection. */\n requireTLS?: boolean;\n /** TLS options for STARTTLS and direct TLS. */\n tls?: SMTPConfig[\"tls\"];\n /** DKIM signing configuration. */\n dkim?: SMTPConfig[\"dkim\"];\n /** Socket connect timeout in milliseconds. */\n connectionTimeout?: number;\n /** Timeout waiting for the SMTP greeting in milliseconds. */\n greetingTimeout?: number;\n /** Idle socket timeout in milliseconds. */\n socketTimeout?: number;\n /** Direct-to-MX delivery mode. */\n direct?: boolean;\n /** Runtime socket adapter. */\n adapter?: SocketAdapter;\n}\n\n/** Apply defaults to SMTP configuration. */\nexport function resolveSMTPConfig(config: SMTPConfig): ResolvedSMTPConfig {\n const secure = config.secure ?? false;\n return {\n host: config.host,\n port: config.port ?? (secure ? 465 : 587),\n secure,\n ...(config.auth !== undefined ? { auth: config.auth } : {}),\n ...(config.requireTLS !== undefined ? { requireTLS: config.requireTLS } : {}),\n ...(config.dkim !== undefined ? { dkim: config.dkim } : {}),\n ...(config.tls !== undefined ? { tls: config.tls } : {}),\n ...(config.connectionTimeout !== undefined\n ? { connectionTimeout: config.connectionTimeout }\n : {}),\n ...(config.greetingTimeout !== undefined ? { greetingTimeout: config.greetingTimeout } : {}),\n ...(config.socketTimeout !== undefined ? { socketTimeout: config.socketTimeout } : {}),\n ...(config.direct !== undefined ? { direct: config.direct } : {}),\n ...(config.adapter !== undefined ? { adapter: config.adapter } : {}),\n };\n}\n\n/**\n * Connect greeting, EHLO, optional STARTTLS, and AUTH on an open adapter.\n */\nexport async function openSMTPSession(\n adapter: SocketAdapter,\n config: ResolvedSMTPConfig,\n): Promise<void> {\n const greeting = await readSMTPResponse(adapter);\n assertResponse(greeting, [220], \"greeting\");\n\n let capabilities = await ehlo(adapter, config.host);\n const supportsStartTls = capabilities.some((cap) => cap.toUpperCase() === \"STARTTLS\");\n if (!config.secure && !adapter.secure && supportsStartTls) {\n await sendRaw(adapter, encodeCommand({ type: \"STARTTLS\" }));\n const starttlsResp = await readSMTPResponse(adapter);\n assertResponse(starttlsResp, [220], \"STARTTLS\");\n await adapter.startTLS(config.tls);\n capabilities = await ehlo(adapter, config.host);\n }\n\n if (config.auth) {\n const tlsRequired = config.requireTLS ?? true;\n const encrypted = adapter.secure || config.secure;\n if (tlsRequired && !encrypted) {\n throw new SMTPError(\n \"Refusing to authenticate over unencrypted connection. \" +\n \"Set requireTLS: false to disable this check (not recommended).\",\n 0,\n \"AUTH\",\n \"\",\n );\n }\n await authenticate(adapter, config.auth, capabilities);\n }\n}\n\n/**\n * MAIL FROM, RCPT TO, and DATA for a built MIME message on an authenticated session.\n */\nexport async function deliverSMTPMessage(\n adapter: SocketAdapter,\n mime: MIMEBuildResult,\n): Promise<SendResult> {\n await sendCommand(adapter, { type: \"MAIL_FROM\", address: mime.envelope.from });\n const mailResp = await readSMTPResponse(adapter);\n assertResponse(mailResp, [250], \"MAIL FROM\");\n\n const accepted: string[] = [];\n const rejected: string[] = [];\n\n for (const recipient of mime.envelope.to) {\n await sendRaw(adapter, encodeCommand({ type: \"RCPT_TO\", address: recipient }));\n const rcptResp = await readSMTPResponse(adapter);\n if (rcptResp.isSuccess) {\n accepted.push(recipient);\n } else {\n rejected.push(recipient);\n }\n }\n\n await sendCommand(adapter, { type: \"DATA\" });\n const dataResp = await readSMTPResponse(adapter);\n assertResponse(dataResp, [354], \"DATA\");\n\n let finalResp: SMTPResponse;\n try {\n await sendRaw(adapter, encodeCommand({ type: \"DATA_BODY\", content: mime.raw }));\n finalResp = await readSMTPResponse(adapter);\n } catch (err) {\n await sendRaw(adapter, encodeCommand({ type: \"DATA_BODY\", content: mime.raw }));\n finalResp = await readSMTPResponse(adapter);\n if (finalResp.isError) {\n throw err;\n }\n }\n assertResponse(finalResp, [250], \"DATA end\");\n\n return {\n messageId: mime.messageId,\n accepted,\n rejected,\n response: finalResp.message,\n envelope: mime.envelope,\n };\n}\n\n/**\n * QUIT and close an SMTP session adapter.\n */\nexport async function closeSMTPSession(adapter: SocketAdapter): Promise<void> {\n try {\n await sendCommand(adapter, { type: \"QUIT\" });\n await readSMTPResponse(adapter);\n } catch {\n // ignore errors during shutdown\n } finally {\n await adapter.close();\n }\n}\n\nasync function ehlo(adapter: SocketAdapter, host: string): Promise<string[]> {\n await sendCommand(adapter, { type: \"EHLO\", domain: host });\n const response = await readSMTPResponse(adapter);\n assertResponse(response, [250], \"EHLO\");\n return parseEHLO(response);\n}\n\nasync function authenticate(\n adapter: SocketAdapter,\n auth: NonNullable<SMTPConfig[\"auth\"]>,\n capabilities: string[],\n): Promise<void> {\n if (auth.type === \"OAUTH2\" && auth.oauth2) {\n const client = new OAuth2Client(auth.oauth2);\n const xoauth2 = await client.buildXOAUTH2();\n await sendCommand(adapter, { type: \"AUTH_XOAUTH2\", xoauth2String: xoauth2 });\n let resp = await readSMTPResponse(adapter);\n if (resp.code === 334) {\n await sendRaw(adapter, encodeLine(\"\"));\n resp = await readSMTPResponse(adapter);\n }\n assertResponse(resp, [235], \"AUTH XOAUTH2\");\n return;\n }\n\n const method = auth.type ?? selectAuthMethod(capabilities);\n\n if (method === \"CRAM-MD5\") {\n const pass = requirePassword(auth, \"CRAM-MD5\");\n await sendCommand(adapter, { type: \"AUTH_CRAM_MD5_INIT\" });\n let resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH CRAM-MD5\");\n const challenge = resp.message.trim();\n const response = await computeCRAMMD5(challenge, auth.user, pass);\n await sendCommand(adapter, { type: \"AUTH_CRAM_MD5_RESPONSE\", response });\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH CRAM-MD5 response\");\n return;\n }\n\n if (method === \"PLAIN\") {\n const pass = requirePassword(auth, \"PLAIN\");\n await sendRaw(adapter, encodeCommand({ type: \"AUTH_PLAIN\", user: auth.user, pass }));\n const resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH PLAIN\");\n return;\n }\n\n const pass = requirePassword(auth, \"LOGIN\");\n await sendRaw(adapter, encodeCommand({ type: \"AUTH_LOGIN\", user: auth.user, pass }));\n let resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH LOGIN\");\n\n await sendRaw(adapter, encodeAuthLoginUser(auth.user));\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [334], \"AUTH LOGIN user\");\n\n await sendRaw(adapter, encodeAuthLoginPass(pass));\n resp = await readSMTPResponse(adapter);\n assertResponse(resp, [235], \"AUTH LOGIN pass\");\n}\n\nfunction requirePassword(auth: NonNullable<SMTPConfig[\"auth\"]>, method: string): string {\n if (!auth.pass) {\n throw new SMTPError(`Password required for ${method} authentication`, 0, `AUTH ${method}`, \"\");\n }\n return auth.pass;\n}\n\nasync function sendCommand(\n adapter: SocketAdapter,\n command: Parameters<typeof encodeCommand>[0],\n): Promise<void> {\n await sendRaw(adapter, encodeCommand(command));\n}\n\nasync function sendRaw(adapter: SocketAdapter, data: Uint8Array): Promise<void> {\n await adapter.write(data);\n}\n\n/** Reads and parses a complete SMTP response from the adapter. */\nasync function readSMTPResponse(adapter: SocketAdapter): Promise<SMTPResponse> {\n const chunks: Uint8Array[] = [];\n for await (const chunk of adapter.read()) {\n chunks.push(chunk);\n const complete = accumulateResponse(chunks);\n if (complete) {\n return parseResponse(complete);\n }\n }\n throw new SMTPError(\"Connection closed while reading SMTP response\", 0, \"READ\", \"\");\n}\n\nasync function resolveMX(domain: string): Promise<string> {\n const dns = await import(\"node:dns/promises\");\n const records = await dns.resolveMx(domain);\n if (records.length === 0) {\n throw new SMTPError(`No MX records for ${domain}`, 0, \"MX\", \"\");\n }\n records.sort((a: { priority: number }, b: { priority: number }) => a.priority - b.priority);\n return records[0]?.exchange ?? domain;\n}\n\n/** @internal Test helper for raw line writes. */\nexport { encodeLine, readSMTPResponse };\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDO,MAAM,cAAmC;AAAA,
|
|
7
|
+
"mappings": "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",
|
|
8
8
|
"debugId": "736A9106E3164FC464756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/pool/connection.ts", "../src/pool/pool.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { buildMIME } from \"../core/mime.js\";\nimport type { MailOptions, SendResult, SMTPConfig } from \"../core/types.js\";\nimport { resolveAttachments } from \"../transports/resolve-attachments.js\";\nimport {\n closeSMTPSession,\n deliverSMTPMessage,\n openSMTPSession,\n resolveSMTPConfig,\n} from \"../transports/smtp.js\";\n\n/** A single pooled SMTP connection with a persistent session. */\nexport interface PooledConnection {\n /** Send one message over this connection. */\n send(options: MailOptions): Promise<SendResult>;\n /** Whether this connection is idle and available for work. */\n readonly idle: boolean;\n /** Number of messages sent on this connection. */\n readonly messageCount: number;\n /** Whether this connection can accept more messages before recycle. */\n readonly usable: boolean;\n /** Close the connection and end the SMTP session. */\n close(): Promise<void>;\n}\n\n/** Options for creating a pooled connection. */\nexport interface PooledConnectionOptions {\n config: SMTPConfig;\n maxMessages: number;\n connectHost: string;\n createAdapter: () => Promise<import(\"../core/types.js\").SocketAdapter>;\n}\n\n/**\n * Create a pooled SMTP connection with an open authenticated session.\n */\nexport async function createPooledConnection(\n options: PooledConnectionOptions,\n): Promise<PooledConnection> {\n const config = resolveSMTPConfig(options.config);\n const adapter = await options.createAdapter();\n await adapter.connect(options.connectHost, config.port);\n await openSMTPSession(adapter, config);\n\n let messageCount = 0;\n let idle = true;\n let sendChain: Promise<void> = Promise.resolve();\n\n const maxMessages = options.maxMessages;\n\n return {\n get idle(): boolean {\n return idle;\n },\n get messageCount(): number {\n return messageCount;\n },\n get usable(): boolean {\n return messageCount < maxMessages;\n },\n\n async send(mailOptions: MailOptions): Promise<SendResult> {\n const run = async (): Promise<SendResult> => {\n idle = false;\n try {\n const resolvedOptions = {\n ...mailOptions,\n attachments: await resolveAttachments(mailOptions.attachments),\n };\n const mime = await buildMIME(resolvedOptions, config.dkim);\n const result = await deliverSMTPMessage(adapter, mime);\n messageCount += 1;\n return result;\n } finally {\n idle = true;\n }\n };\n\n const resultPromise = sendChain.then(run);\n sendChain = resultPromise.then(\n () => undefined,\n () => undefined,\n );\n return resultPromise;\n },\n\n async close(): Promise<void> {\n await sendChain;\n await closeSMTPSession(adapter);\n },\n };\n}\n",
|
|
6
|
-
"import 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 * Token bucket rate limiter with lazy refill on acquire.\n */\nclass RateLimiter {\n private tokens: number;\n private lastRefill: number;\n private waiters: Array<() => void> = [];\n\n constructor(\n private readonly rateDelta: number,\n private readonly rateLimit: number,\n private readonly now: () => number = Date.now,\n ) {\n this.tokens = rateDelta;\n this.lastRefill = now();\n }\n\n /** Wait until a token is available, then consume one. */\n async acquire(): Promise<void> {\n for (;;) {\n this.refill();\n if (this.tokens > 0) {\n this.tokens -= 1;\n return;\n }\n await new Promise<void>((resolve) => {\n this.waiters.push(resolve);\n });\n }\n }\n\n /** Wake waiters after the clock advances (for testing). */\n notify(): void {\n this.refill();\n }\n\n private refill(): void {\n const t = this.now();\n const elapsed = t - this.lastRefill;\n if (elapsed >= this.rateLimit) {\n const periods = Math.floor(elapsed / this.rateLimit);\n this.tokens = Math.min(this.rateDelta, this.tokens + periods * this.rateDelta);\n this.lastRefill += periods * this.rateLimit;\n while (this.tokens > 0 && this.waiters.length > 0) {\n this.tokens -= 1;\n const next = this.waiters.shift();\n next?.();\n }\n }\n }\n}\n\n/**\n * SMTP connection pool with optional rate limiting.\n */\nexport class SMTPPool implements Transport {\n private readonly config: SMTPConfig & PoolConfig;\n private readonly maxConnections: number;\n private readonly maxMessages: number;\n private readonly createAdapterFn: () => Promise<SocketAdapter>;\n private readonly rateLimiter: RateLimiter | null;\n private readonly connections: PooledConnection[] = [];\n private readonly queue: QueueEntry[] = [];\n private draining = false;\n private closed = false;\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 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 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 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 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 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 };\n"
|
|
5
|
+
"import { buildMIME } from \"../core/mime.js\";\nimport type { MailOptions, SendResult, SMTPConfig } from \"../core/types.js\";\nimport { resolveAttachments } from \"../transports/resolve-attachments.js\";\nimport {\n closeSMTPSession,\n deliverSMTPMessage,\n openSMTPSession,\n resolveSMTPConfig,\n} from \"../transports/smtp.js\";\n\n/** A single pooled SMTP connection with a persistent session. */\nexport interface PooledConnection {\n /** Send one message over this connection. */\n send(options: MailOptions): Promise<SendResult>;\n /** Whether this connection is idle and available for work. */\n readonly idle: boolean;\n /** Number of messages sent on this connection. */\n readonly messageCount: number;\n /** Whether this connection can accept more messages before recycle. */\n readonly usable: boolean;\n /** Close the connection and end the SMTP session. */\n close(): Promise<void>;\n}\n\n/** Options for creating a pooled connection. */\nexport interface PooledConnectionOptions {\n /** SMTP configuration for the pooled session. */\n config: SMTPConfig;\n /** Maximum messages before this connection is recycled. */\n maxMessages: number;\n /** Hostname to connect to (may differ from config.host for direct MX). */\n connectHost: string;\n /** Factory that creates the socket adapter for this connection. */\n createAdapter: () => Promise<import(\"../core/types.js\").SocketAdapter>;\n}\n\n/**\n * Create a pooled SMTP connection with an open authenticated session.\n */\nexport async function createPooledConnection(\n options: PooledConnectionOptions,\n): Promise<PooledConnection> {\n const config = resolveSMTPConfig(options.config);\n const adapter = await options.createAdapter();\n await adapter.connect(options.connectHost, config.port);\n await openSMTPSession(adapter, config);\n\n let messageCount = 0;\n let idle = true;\n let sendChain: Promise<void> = Promise.resolve();\n\n const maxMessages = options.maxMessages;\n\n return {\n get idle(): boolean {\n return idle;\n },\n get messageCount(): number {\n return messageCount;\n },\n get usable(): boolean {\n return messageCount < maxMessages;\n },\n\n async send(mailOptions: MailOptions): Promise<SendResult> {\n const run = async (): Promise<SendResult> => {\n idle = false;\n try {\n const resolvedOptions = {\n ...mailOptions,\n attachments: await resolveAttachments(mailOptions.attachments),\n };\n const mime = await buildMIME(resolvedOptions, config.dkim);\n const result = await deliverSMTPMessage(adapter, mime);\n messageCount += 1;\n return result;\n } finally {\n idle = true;\n }\n };\n\n const resultPromise = sendChain.then(run);\n sendChain = resultPromise.then(\n () => undefined,\n () => undefined,\n );\n return resultPromise;\n },\n\n async close(): Promise<void> {\n await sendChain;\n await closeSMTPSession(adapter);\n },\n };\n}\n",
|
|
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 */\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 * Token bucket rate limiter with lazy refill on acquire.\n */\nclass RateLimiter {\n /** Remaining tokens in the current rate-limit window. */\n private tokens: number;\n /** Timestamp (ms) of the last token refill. */\n private lastRefill: number;\n /** Resolvers waiting for a token when the bucket is empty. */\n private waiters: Array<() => void> = [];\n\n /** Creates a rate limiter with the given burst size and window duration. */\n constructor(\n private readonly rateDelta: number,\n private readonly rateLimit: number,\n private readonly now: () => number = Date.now,\n ) {\n this.tokens = rateDelta;\n this.lastRefill = now();\n }\n\n /** Wait until a token is available, then consume one. */\n async acquire(): Promise<void> {\n for (;;) {\n this.refill();\n if (this.tokens > 0) {\n this.tokens -= 1;\n return;\n }\n await new Promise<void>((resolve) => {\n this.waiters.push(resolve);\n });\n }\n }\n\n /** Wake waiters after the clock advances (for testing). */\n notify(): void {\n this.refill();\n }\n\n /** Refills tokens based on elapsed time and wakes waiting acquirers. */\n private refill(): void {\n const t = this.now();\n const elapsed = t - this.lastRefill;\n if (elapsed >= this.rateLimit) {\n const periods = Math.floor(elapsed / this.rateLimit);\n this.tokens = Math.min(this.rateDelta, this.tokens + periods * this.rateDelta);\n this.lastRefill += periods * this.rateLimit;\n while (this.tokens > 0 && this.waiters.length > 0) {\n this.tokens -= 1;\n const next = this.waiters.shift();\n next?.();\n }\n }\n }\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 };\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": ";;;;;;;;;;;;;;
|
|
8
|
+
"mappings": "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",
|
|
9
9
|
"debugId": "D47B35696A356F3464756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|