@bumail/smtp 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +105 -7
  2. package/dist/chunks/{index-3423jxf9.js → index-n4xwep5m.js} +10 -4
  3. package/dist/chunks/index-n4xwep5m.js.map +14 -0
  4. package/dist/client/index.js +1 -1
  5. package/dist/errors.d.ts +3 -1
  6. package/dist/errors.d.ts.map +1 -1
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +724 -126
  10. package/dist/index.js.map +19 -9
  11. package/dist/protocol/command.d.ts +4 -2
  12. package/dist/protocol/command.d.ts.map +1 -1
  13. package/dist/protocol/path.d.ts +13 -2
  14. package/dist/protocol/path.d.ts.map +1 -1
  15. package/dist/server/admission.d.ts +29 -0
  16. package/dist/server/admission.d.ts.map +1 -0
  17. package/dist/server/client-key.d.ts +15 -0
  18. package/dist/server/client-key.d.ts.map +1 -0
  19. package/dist/server/listener.d.ts +46 -0
  20. package/dist/server/listener.d.ts.map +1 -0
  21. package/dist/server/options.d.ts +45 -1
  22. package/dist/server/options.d.ts.map +1 -1
  23. package/dist/server/proxy/address.d.ts +17 -0
  24. package/dist/server/proxy/address.d.ts.map +1 -0
  25. package/dist/server/proxy/header.d.ts +26 -0
  26. package/dist/server/proxy/header.d.ts.map +1 -0
  27. package/dist/server/proxy/reader.d.ts +15 -0
  28. package/dist/server/proxy/reader.d.ts.map +1 -0
  29. package/dist/server/proxy/tls.d.ts +50 -0
  30. package/dist/server/proxy/tls.d.ts.map +1 -0
  31. package/dist/server/proxy/trusted.d.ts +9 -0
  32. package/dist/server/proxy/trusted.d.ts.map +1 -0
  33. package/dist/server/raw-socket.d.ts +9 -0
  34. package/dist/server/raw-socket.d.ts.map +1 -0
  35. package/dist/server/received.d.ts +2 -0
  36. package/dist/server/received.d.ts.map +1 -1
  37. package/dist/server/server.d.ts.map +1 -1
  38. package/dist/server/settings.d.ts +4 -0
  39. package/dist/server/settings.d.ts.map +1 -1
  40. package/dist/server/slots.d.ts +14 -0
  41. package/dist/server/slots.d.ts.map +1 -0
  42. package/dist/server/tls-context.d.ts +16 -0
  43. package/dist/server/tls-context.d.ts.map +1 -0
  44. package/dist/server/transaction.d.ts.map +1 -1
  45. package/dist/server/transport.d.ts +2 -2
  46. package/dist/server/transport.d.ts.map +1 -1
  47. package/docs/README.md +2 -2
  48. package/docs/guide.md +472 -12
  49. package/docs/roadmap.md +57 -0
  50. package/docs/troubleshooting.md +406 -4
  51. package/package.json +2 -2
  52. package/dist/chunks/index-3423jxf9.js.map +0 -14
package/README.md CHANGED
@@ -105,6 +105,11 @@ const server = createSmtpServer({
105
105
  await server.listen({ port: 465 });
106
106
  ```
107
107
 
108
+ A client has `handshakeTimeout` seconds (default 10) to complete its TLS
109
+ handshake, and holds a slot of `maxConnections` and
110
+ `maxConnectionsPerClient` meanwhile: a socket that never sends its
111
+ ClientHello is closed, without a word.
112
+
108
113
  ## Hooks for policy
109
114
 
110
115
  A hook returns nothing to accept, or a `reply(code, status, text)` with a
@@ -126,7 +131,9 @@ const server = createSmtpServer({
126
131
  onMailFrom: (path) =>
127
132
  path.domain === 'spam.example' ? reply(550, '5.7.1', 'Sender refused') : undefined,
128
133
  onRcptTo: (path) =>
129
- mailboxes.has(path.address.toLowerCase()) ? undefined : reply(550, '5.1.1', 'No such user here'),
134
+ path.postmaster || mailboxes.has(path.address.toLowerCase())
135
+ ? undefined
136
+ : reply(550, '5.1.1', 'No such user here'),
130
137
  async onData(message) {
131
138
  await Bun.write(`spool/${message.id}.eml`, await new Response(message.content).bytes());
132
139
  },
@@ -139,6 +146,33 @@ await server.listen({ port: 25 });
139
146
  The server's own checks run first: `onRcptTo` is never asked about a
140
147
  recipient the server already refused, relaying included.
141
148
 
149
+ `RCPT TO:<postmaster>`, with no domain and in any case, is taken as the
150
+ server's own postmaster (RFC 5321 §4.5.1): it is not relaying, so it needs
151
+ no AUTH, and `localDomains` is not asked. `onRcptTo` gets a `Path` whose
152
+ `postmaster` is `true`, `address` and `local` `'postmaster'` and `domain`
153
+ `''`, and the envelope lists the recipient as `'postmaster'`, with no `@`.
154
+ Route it to a mailbox of your own:
155
+
156
+ ```ts
157
+ import { createSmtpServer } from '@bumail/smtp';
158
+
159
+ const postmaster = 'postmaster@example.com';
160
+
161
+ const server = createSmtpServer({
162
+ hostname: 'mx.example.com',
163
+ localDomains: ['example.com'],
164
+ async onData(message) {
165
+ const recipients = message.envelope.to.map((to) => (to === 'postmaster' ? postmaster : to));
166
+ const bytes = await new Response(message.content).bytes();
167
+ for (const to of recipients) await Bun.write(`spool/${to}/${message.id}.eml`, bytes);
168
+ },
169
+ });
170
+ ```
171
+
172
+ `<postmaster@domain>` is an ordinary path, checked as any other. The
173
+ [guide](https://github.com/softistx/bumail/blob/develop/packages/smtp/docs/guide.md#rcpt-topostmaster)
174
+ has the details.
175
+
142
176
  ## Limits
143
177
 
144
178
  Every limit has a default; each must be a positive integer —
@@ -154,6 +188,8 @@ const server = createSmtpServer({
154
188
  maxMessageSize: 10 * 1024 * 1024, // bytes, announced as SIZE; default 25 MiB → 552 5.3.4
155
189
  maxRecipients: 50, // per message; default 100 → 452 4.5.3
156
190
  maxConnections: 200, // at once; default 1000 → 421 4.3.2
191
+ maxConnectionsPerClient: 5, // at once from one IPv4 address or IPv6 /64; default 10 → 421 4.7.0
192
+ handshakeTimeout: 10, // seconds to complete an implicit TLS handshake, or a proxy's PROXY header; default 10 → closed
157
193
  maxErrors: 5, // failed commands before hanging up; default 10 → 421 4.7.0
158
194
  timeout: 120, // idle seconds, from the client's last byte or the 220; default 300 → 421 4.4.2
159
195
  hookTimeout: 20, // seconds a hook has to settle; default 60 → 451 4.3.0
@@ -168,6 +204,38 @@ console.log(`listening on ${port}, ${server.connections} open`);
168
204
  // later: server.stop(true) hangs up on every client too
169
205
  ```
170
206
 
207
+ ## Behind a TCP proxy
208
+
209
+ Behind a TCP proxy such as a Traefik TCP router or HAProxy, every client
210
+ has the proxy's address. `proxyProtocol` reads the PROXY protocol,
211
+ versions 1 and 2, from the proxies it lists, so the server sees the
212
+ client's address again: in `session.remoteAddress`, every hook, the
213
+ Received field and `maxConnectionsPerClient`.
214
+
215
+ ```ts
216
+ import { createSmtpServer } from '@bumail/smtp';
217
+
218
+ const server = createSmtpServer({
219
+ hostname: 'mx.example.com',
220
+ localDomains: ['example.com'],
221
+ // The proxy's own address, or its network: never a range clients connect from.
222
+ proxyProtocol: { trusted: ['10.0.0.5'] },
223
+ onConnect: (session) => console.log('client', session.remoteAddress),
224
+ async onData(message) {
225
+ await Bun.write(`spool/${message.id}.eml`, await new Response(message.content).bytes());
226
+ },
227
+ });
228
+
229
+ await server.listen({ port: 25 });
230
+ ```
231
+
232
+ A listed peer must send its header first, within `handshakeTimeout`
233
+ (default 10 seconds), or it is reset without a word. A peer not listed is
234
+ served as without the option, and a header it sends is just bad input.
235
+ It works on 25 and 587 with STARTTLS, and on 465 with `implicitTls`. The
236
+ [guide](https://github.com/softistx/bumail/blob/develop/packages/smtp/docs/guide.md#running-behind-a-tcp-proxy)
237
+ has a Traefik configuration for all three.
238
+
171
239
  ## Send mail: to a smarthost, a submission server or Mailpit
172
240
 
173
241
  `sendMail(message, options)` from `@bumail/smtp/client` delivers one
@@ -286,6 +354,34 @@ host; a 5xx stops. A null MX (RFC 7505) fails at once with `NULL_MX`.
286
354
  back for that many seconds once `onConnect` accepted, so a sender that
287
355
  does not wait gives itself away, as Postfix's postscreen does. A refusal
288
356
  from `onConnect` goes out at once, delay or not.
357
+ - **One client cannot take every slot.** `maxConnectionsPerClient`
358
+ (default 10) bounds the connections one client holds at once, so a
359
+ single host cannot fill `maxConnections`. A client is an IPv4 address,
360
+ or an IPv6 address by its /64; an IPv4-mapped or NAT64 address counts as
361
+ the IPv4 address inside it, so a dual-stack listener counts an IPv4
362
+ client once. One more gets `421 4.7.0 <hostname> Too many connections
363
+ from your address, try later` and is closed, before `onConnect`. Every
364
+ close frees the slot: QUIT, a hang-up, an error, the idle time,
365
+ `stop(true)`. Group addresses the same way in your own policy with
366
+ `clientKey`:
367
+
368
+ ```ts
369
+ import { clientKey } from '@bumail/smtp';
370
+
371
+ clientKey('::ffff:192.0.2.1'); // '192.0.2.1'
372
+ clientKey('2001:db8::1'); // '2001:db8:0:0::/64': four groups, zeros written out
373
+ ```
374
+ - **A TLS handshake is bounded.** On implicit TLS a socket is counted from
375
+ the TCP connection on, before its handshake, and closed past
376
+ `handshakeTimeout` (default 10 seconds) if the handshake has not
377
+ completed; `onConnect` and the greeting come once it has. A STARTTLS
378
+ handshake is bounded by `timeout`.
379
+ - **A PROXY header is trusted only from the peers `proxyProtocol` lists.**
380
+ Any of them can claim any client address, so list the proxies' own
381
+ addresses only, never a range clients connect from. From anyone else,
382
+ the header never sets an address. A listed peer that sends no valid
383
+ header within `handshakeTimeout` is reset, and holds no slot of
384
+ `maxConnections` or `maxConnectionsPerClient` while it waits.
289
385
  - **Bounded memory.** The server holds 64 KiB of a client's input and of a
290
386
  message at most, and stops reading the client past that; replies to a
291
387
  client that reads slowly wait in the server, and none is lost while the
@@ -362,9 +458,10 @@ host; a 5xx stops. A null MX (RFC 7505) fails at once with `NULL_MX`.
362
458
  | export | |
363
459
  | --- | --- |
364
460
  | `createSmtpServer(options)` | the server; throws an `SmtpError` (`INVALID_OPTION`) on a bad option |
365
- | `SmtpServer` | `listen({ port, hostname? })` (once; again throws `ALREADY_LISTENING`), `stop(closeConnections?)` (`true` hangs up on every client, after STARTTLS too), `connections` |
366
- | `SmtpServerOptions` | `hostname`, `mode`, `localDomains`, `tls`, `implicitTls`, `authenticate`, the limits, `hookTimeout`, `greetingDelay`, `onError`, and the hooks |
367
- | `SmtpError`, `SmtpErrorCode` | `code`: `INVALID_OPTION`, `ALREADY_LISTENING`, and what a content stream or `onError` can get: `MESSAGE_TOO_BIG`, `BARE_LINE_BREAK`, `CONNECTION_LOST`, `HOOK_TIMEOUT`, `INVALID_HOOK_REPLY`, `MESSAGE_NOT_READ`; `sendMail`'s are listed below. `temporary`, `reply` and `rejected` are set for `sendMail`'s errors |
461
+ | `SmtpServer` | `listen({ port, hostname? })` (once; again, even before the first resolved, throws `ALREADY_LISTENING`; on implicit TLS, a key or certificate it cannot read or use throws `INVALID_OPTION`), `stop(closeConnections?)` (`true` hangs up on every client, after STARTTLS too), `connections` |
462
+ | `SmtpServerOptions` | `hostname`, `mode`, `localDomains`, `tls`, `implicitTls`, `authenticate`, the limits (`maxConnectionsPerClient` among them), `hookTimeout`, `handshakeTimeout`, `greetingDelay`, `proxyProtocol`, `onError`, and the hooks |
463
+ | `ProxyProtocolOptions` | `trusted`: the proxies' IPv4 and IPv6 addresses and CIDRs, the only peers whose PROXY header (v1 or v2) is read |
464
+ | `SmtpError`, `SmtpErrorCode` | `code`: `INVALID_OPTION`, `ALREADY_LISTENING`, `STOPPED` (a `stop()` came before `listen` resolved), and what a content stream or `onError` can get: `MESSAGE_TOO_BIG`, `BARE_LINE_BREAK`, `CONNECTION_LOST`, `HOOK_TIMEOUT`, `INVALID_HOOK_REPLY`, `MESSAGE_NOT_READ`; `sendMail`'s are listed below. `temporary`, `reply` and `rejected` are set for `sendMail`'s errors |
368
465
  | `SmtpHooks` | `onConnect`, `onMailFrom`, `onRcptTo`, `onData` |
369
466
  | `HookResult` | what a hook returns: `undefined` to accept, a `Reply` to refuse |
370
467
  | `Session` | `id`, `remoteAddress`, `secure`, `helo`, `esmtp`, `user`, and `data` for your own state |
@@ -373,9 +470,10 @@ host; a 5xx stops. A null MX (RFC 7505) fails at once with `NULL_MX`.
373
470
  | `Credentials` | what `authenticate` receives: `mechanism`, `username`, `password`, `authorizationId?` |
374
471
  | `reply(code, status, text)`, `Reply` | a reply, for a hook to refuse with |
375
472
  | `formatReply(reply, enhanced?)` | a reply as sent on the wire, CRLF included |
376
- | `parsePath(text, allowNull, sourceRoute?)`, `Path`, `SourceRoute` | `<local@domain>` (RFC 5321 §4.1.2) to `{ address, local, domain }`; a source route `@a,@b:` is dropped (`'discard'`, the default) or refused (`'refuse'`); a control character (C0, DEL or C1), `>`, U+2028 or U+2029, Unicode format character (`\p{Cf}`) or lone surrogate anywhere, or an IPv4 literal octet above 255, refuses the path |
473
+ | `parsePath(text, allowNull, sourceRoute?)`, `Path`, `SourceRoute` | `<local@domain>` (RFC 5321 §4.1.2) to `{ address, local, domain }` (and `postmaster: true` for `RCPT TO:<postmaster>`, which `parsePathCommand` gives); a source route `@a,@b:` is dropped (`'discard'`, the default) or refused (`'refuse'`); a control character (C0, DEL or C1), `>`, U+2028 or U+2029, Unicode format character (`\p{Cf}`) or lone surrogate anywhere, or an IPv4 literal octet above 255, refuses the path |
377
474
  | `parseCommand(line)`, `Command` | a command line to `{ verb, argument }` |
378
- | `parsePathCommand(argument, 'FROM' \| 'TO')`, `PathCommand` | the argument of MAIL or RCPT to `{ path, parameters }` |
475
+ | `parsePathCommand(argument, 'FROM' \| 'TO')`, `PathCommand` | the argument of MAIL or RCPT to `{ path, parameters }`; `TO:` also takes `<postmaster>` with no domain, in any case (RFC 5321 §4.1.1.3) |
476
+ | `clientKey(address)` | which client a remote address counts as, for a limit per client: an IPv4 address as it is, an IPv4-mapped or NAT64 address as its IPv4 address, any other IPv6 address as its /64, its first four groups written out, zeros included (`'2001:db8:1:2::/64'`, `'2001:db8:0:0::/64'` for `2001:db8::1`); `undefined` for what is not an IP address |
379
477
  | `DataReader`, `DataChunk` | reads DATA: dot-unstuffing, the terminator, bare CR and LF counted |
380
478
  | `decodePlain(response)`, `decodeLoginStep(response)` | SASL PLAIN (RFC 4616) and LOGIN responses to `Credentials` or text |
381
479
 
@@ -401,6 +499,6 @@ host; a 5xx stops. A null MX (RFC 7505) fails at once with `NULL_MX`.
401
499
  These pages ship in the package, under `docs/`.
402
500
 
403
501
  - [Index](https://github.com/softistx/bumail/blob/develop/packages/smtp/docs/README.md): the pages, and when to read each.
404
- - [Guide](https://github.com/softistx/bumail/blob/develop/packages/smtp/docs/guide.md): the session and every reply, hooks and their order, `Session.data`, what `onData` receives, TLS, sending mail with the client and trying it with Mailpit, the RFCs implemented and what is not.
502
+ - [Guide](https://github.com/softistx/bumail/blob/develop/packages/smtp/docs/guide.md): the session and every reply, hooks and their order, `Session.data`, what `onData` receives, TLS, running behind a TCP proxy, sending mail with the client and trying it with Mailpit, the RFCs implemented and what is not.
405
503
  - [Troubleshooting](https://github.com/softistx/bumail/blob/develop/packages/smtp/docs/troubleshooting.md): every error, the replies a client reports, and every error `sendMail` rejects with.
406
504
  - [Roadmap](https://github.com/softistx/bumail/blob/develop/packages/smtp/docs/roadmap.md): what is coming, and what is not planned.
@@ -15,7 +15,7 @@ class SmtpError2 extends Error {
15
15
  reply;
16
16
  rejected;
17
17
  constructor(code, message, details = {}) {
18
- super(message);
18
+ super(message, details.cause === undefined ? undefined : { cause: details.cause });
19
19
  this.code = code;
20
20
  this.temporary = details.temporary ?? (details.reply ? details.reply.code < 500 : TEMPORARY.has(code));
21
21
  if (details.reply)
@@ -26,6 +26,12 @@ class SmtpError2 extends Error {
26
26
  }
27
27
 
28
28
  // src/protocol/path.ts
29
+ var POSTMASTER = Object.freeze({
30
+ address: "postmaster",
31
+ local: "postmaster",
32
+ domain: "",
33
+ postmaster: true
34
+ });
29
35
  var ATOM = /^[A-Za-z0-9!#$%&'*+\-/=?^_`{|}~\u0080-\u{10ffff}]+$/u;
30
36
  var LABEL = /^(?:[A-Za-z0-9\u0080-\u{10ffff}](?:[A-Za-z0-9\-\u0080-\u{10ffff}]*[A-Za-z0-9\u0080-\u{10ffff}])?)$/u;
31
37
  var INVISIBLE = /[\u0080-\u009f\u2028\u2029\p{Cf}\p{Cs}]/u;
@@ -243,7 +249,7 @@ function concat(a, b) {
243
249
  return out;
244
250
  }
245
251
 
246
- export { SmtpError2, isHelloName, parsePath2, decodePlain2, decodeLoginStep2, encodePlain, encodeLoginStep, MAX_LINE, LineSplitter, Outgoing };
252
+ export { SmtpError2, POSTMASTER, isHelloName, parsePath2, decodePlain2, decodeLoginStep2, encodePlain, encodeLoginStep, MAX_LINE, LineSplitter, Outgoing };
247
253
 
248
- //# debugId=10D3BC4806ADB37C64756E2164756E21
249
- //# sourceMappingURL=index-3423jxf9.js.map
254
+ //# debugId=C64F62A0D2105F3A64756E2164756E21
255
+ //# sourceMappingURL=index-n4xwep5m.js.map
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors.ts", "../src/protocol/path.ts", "../src/protocol/sasl.ts", "../src/io/outgoing.ts", "../src/protocol/lines.ts"],
4
+ "sourcesContent": [
5
+ "import type { Reply } from './protocol/reply';\n\n/** Why the server refused an option, why a message stream ended in error, or why `sendMail` failed. */\nexport type SmtpErrorCode =\n\t| 'INVALID_OPTION'\n\t| 'ALREADY_LISTENING'\n\t| 'STOPPED'\n\t| 'MESSAGE_TOO_BIG'\n\t| 'BARE_LINE_BREAK'\n\t| 'CONNECTION_LOST'\n\t| 'HOOK_TIMEOUT'\n\t| 'INVALID_HOOK_REPLY'\n\t| 'MESSAGE_NOT_READ'\n\t| 'CONNECTION_FAILED'\n\t| 'TIMEOUT'\n\t| 'BAD_REPLY'\n\t| 'REFUSED'\n\t| 'RECIPIENTS_REFUSED'\n\t| 'TLS_UNAVAILABLE'\n\t| 'TLS_FAILED'\n\t| 'AUTH_UNAVAILABLE'\n\t| 'EXTENSION_MISSING'\n\t| 'NULL_MX'\n\t| 'DNS_FAILED';\n\n/** One recipient and the server's reply to its `RCPT TO`. */\nexport interface RecipientReply {\n\treadonly recipient: string;\n\treadonly reply: Reply;\n}\n\n/** What `sendMail` adds to an error: whether to try again, and the reply behind it. */\nexport interface SmtpErrorDetails {\n\t/** Trying again later may succeed: a 4xx, the network, a timeout. */\n\treadonly temporary?: boolean;\n\t/** The server's reply that failed the delivery, enhanced status code included. */\n\treadonly reply?: Reply;\n\t/** With `RECIPIENTS_REFUSED`: each recipient, and why it was refused. */\n\treadonly rejected?: readonly RecipientReply[];\n\t/** The error behind this one, as `Error`'s `cause`. */\n\treadonly cause?: unknown;\n}\n\n/** The codes that are temporary unless a reply or `details` says otherwise. */\nconst TEMPORARY = new Set<SmtpErrorCode>([\n\t'CONNECTION_LOST',\n\t'CONNECTION_FAILED',\n\t'TIMEOUT',\n\t'BAD_REPLY',\n\t'TLS_UNAVAILABLE',\n\t'TLS_FAILED',\n]);\n\n/**\n * Thrown by `createSmtpServer` and `listen` for a wrong option or call; the\n * error a message's `content` stream ends with when the message must not be\n * delivered; what `onError` is given for a hook that broke a rule; and what\n * `sendMail` rejects with. `temporary` tells a failure worth trying again\n * later (a 4xx, the network, a timeout) from a permanent one (a 5xx, a null\n * MX, a message the server can never take).\n */\nexport class SmtpError extends Error {\n\toverride readonly name = 'SmtpError';\n\treadonly code: SmtpErrorCode;\n\t/** Trying again later may succeed. Meaningful for `sendMail`'s errors. */\n\treadonly temporary: boolean;\n\t/** The server reply behind a `sendMail` failure, enhanced status code included. */\n\treadonly reply?: Reply;\n\t/** With `RECIPIENTS_REFUSED`: each recipient, and the reply that refused it. */\n\treadonly rejected?: readonly RecipientReply[];\n\n\tconstructor(\n\t\tcode: SmtpErrorCode,\n\t\tmessage: string,\n\t\tdetails: SmtpErrorDetails = {},\n\t) {\n\t\tsuper(\n\t\t\tmessage,\n\t\t\tdetails.cause === undefined ? undefined : { cause: details.cause },\n\t\t);\n\t\tthis.code = code;\n\t\tthis.temporary =\n\t\t\tdetails.temporary ??\n\t\t\t(details.reply ? details.reply.code < 500 : TEMPORARY.has(code));\n\t\tif (details.reply) this.reply = details.reply;\n\t\tif (details.rejected) this.rejected = details.rejected;\n\t}\n}\n",
6
+ "/** An envelope address, as `MAIL FROM` and `RCPT TO` give it. */\nexport interface Path {\n\t/**\n\t * `local@domain`; `''` for the null reverse-path `<>`; `'postmaster'`\n\t * for `RCPT TO:<postmaster>`, which has no domain.\n\t */\n\treadonly address: string;\n\treadonly local: string;\n\t/** Lower case; `''` for `<>` and for `<postmaster>`. */\n\treadonly domain: string;\n\t/**\n\t * `true` for `RCPT TO:<postmaster>`, in any case, with no domain (RFC\n\t * 5321 §4.5.1): the postmaster of this server, whatever domains it\n\t * hosts. Absent for every other path, `<postmaster@domain>` included.\n\t */\n\treadonly postmaster?: true;\n}\n\n/** The bare `<postmaster>` path `RCPT TO` takes (RFC 5321 §4.1.1.3, §4.5.1). */\nexport const POSTMASTER: Path = Object.freeze({\n\taddress: 'postmaster',\n\tlocal: 'postmaster',\n\tdomain: '',\n\tpostmaster: true,\n});\n\nconst ATOM = /^[A-Za-z0-9!#$%&'*+\\-/=?^_`{|}~\\u0080-\\u{10ffff}]+$/u;\nconst LABEL =\n\t/^(?:[A-Za-z0-9\\u0080-\\u{10ffff}](?:[A-Za-z0-9\\-\\u0080-\\u{10ffff}]*[A-Za-z0-9\\u0080-\\u{10ffff}])?)$/u;\n\n/**\n * C1 controls, Unicode format characters (zero-width, bidi, BOM), the\n * line and paragraph separators, and lone surrogates: none has a place in\n * an address, and each would land as it is in the Received field and the\n * app's records — a lone surrogate as U+FFFD, once encoded for the wire.\n */\nconst INVISIBLE = /[\\u0080-\\u009f\\u2028\\u2029\\p{Cf}\\p{Cs}]/u;\n\n/**\n * C0 controls (CR, LF and NUL among them), DEL and `>`, anywhere in the\n * path, quoted or not: a CR or an LF would end the command line and start\n * another, and a `>` would end the path where a reader of the line stops.\n */\nfunction hasControl(text: string): boolean {\n\tfor (let i = 0; i < text.length; i++) {\n\t\tconst code = text.charCodeAt(i);\n\t\tif (code < 0x20 || code === 0x7f || code === 0x3e) return true;\n\t}\n\treturn false;\n}\n\n/** One hop of a source route, `@domain`, and what follows it: `,` or `:`. */\nconst HOP = /^@(\\[[^\\]]*\\]|[^,:@[\\]]*)([,:])/;\n\nfunction isDotString(text: string): boolean {\n\treturn text.split('.').every((atom) => ATOM.test(atom));\n}\n\nfunction isQuotedString(text: string): boolean {\n\treturn /^\"(?:[\\x20\\x21\\x23-\\x5b\\x5d-\\x7e\\u0080-\\u{10ffff}]|\\\\[\\x20-\\x7e])*\"$/u.test(\n\t\ttext,\n\t);\n}\n\n/**\n * An address literal (§4.1.3): `[IPv6:…]`, its tag in any case (a quoted\n * string in RFC 5234 is), or `[a.b.c.d]` with each octet at most 255.\n */\nexport function isAddressLiteral(text: string): boolean {\n\tif (/^\\[IPv6:[0-9a-f:.]+\\]$/i.test(text)) return true;\n\tconst ipv4 = /^\\[(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\]$/.exec(text);\n\treturn ipv4?.slice(1).every((octet) => Number(octet) <= 255) === true;\n}\n\n/**\n * The argument of EHLO or HELO (RFC 5321 §4.1.1.1): a host name of\n * letters, digits and hyphens, or an address literal.\n */\nexport function isHelloName(text: string): boolean {\n\treturn (\n\t\t/^[A-Za-z0-9-]+(?:\\.[A-Za-z0-9-]+)*$/.test(text) || isAddressLiteral(text)\n\t);\n}\n\nfunction isDomain(domain: string): boolean {\n\tif (isAddressLiteral(domain)) return true;\n\treturn (\n\t\tdomain.length <= 255 &&\n\t\tdomain.split('.').every((label) => label.length <= 63 && LABEL.test(label))\n\t);\n}\n\n/**\n * What `parsePath` does with a source route (`<@a,@b:local@domain>`, RFC 5321\n * Appendix C): a server accepts and discards it (`'discard'`), a client\n * refuses it, as §4.1.1.3 says clients should not send one (`'refuse'`).\n */\nexport type SourceRoute = 'discard' | 'refuse';\n\n/**\n * The mailbox after a source route, each of whose hops must be\n * `@domain` with a valid domain; `undefined` for any other route.\n */\nfunction afterRoute(inner: string): string | undefined {\n\tlet rest = inner;\n\tfor (;;) {\n\t\tconst hop = HOP.exec(rest);\n\t\tif (!hop || !isDomain(hop[1] as string)) return undefined;\n\t\trest = rest.slice(hop[0].length);\n\t\tif (hop[2] === ':') return rest;\n\t}\n}\n\n/**\n * Parses the `<path>` of RFC 5321 §4.1.2: `<local@domain>`, or `<>` when\n * `allowNull`. A source route (`<@a,@b:local@domain>`, §C) is dropped when\n * `sourceRoute` is `'discard'`, the default, provided each hop is\n * `@domain`; with `'refuse'` the path is not one. A C0 control (CR, LF,\n * NUL…), DEL or `>` anywhere refuses the path, as do C1 controls,\n * U+2028 and U+2029, Unicode format characters and lone surrogates. An IPv4 address literal\n * takes octets up to 255.\n * Non-ASCII is allowed: whether the session may use it is SMTPUTF8's\n * question (RFC 6531), not the grammar's. `undefined` when it is not a path.\n *\n * The local part is kept as written, quotes included: `\"v@x.example\"` and\n * `v%x.example` are local parts of the domain after the last `@`, not\n * addresses elsewhere. Code that delivers must not split on the first `@`.\n */\nexport function parsePath(\n\ttext: string,\n\tallowNull: boolean,\n\tsourceRoute: SourceRoute = 'discard',\n): Path | undefined {\n\tif (!text.startsWith('<') || !text.endsWith('>')) return undefined;\n\tlet inner: string | undefined = text.slice(1, -1);\n\tif (hasControl(inner) || INVISIBLE.test(inner)) return undefined;\n\tif (inner === '')\n\t\treturn allowNull ? { address: '', local: '', domain: '' } : undefined;\n\tif (inner.startsWith('@')) {\n\t\tif (sourceRoute === 'refuse') return undefined;\n\t\tinner = afterRoute(inner);\n\t\tif (inner === undefined) return undefined;\n\t}\n\tconst at = inner.lastIndexOf('@');\n\tif (at <= 0) return undefined;\n\tconst local = inner.slice(0, at);\n\tconst domain = inner.slice(at + 1);\n\tif (local.length > 64 || !(isDotString(local) || isQuotedString(local)))\n\t\treturn undefined;\n\tif (!isDomain(domain)) return undefined;\n\treturn {\n\t\taddress: `${local}@${domain.toLowerCase()}`,\n\t\tlocal,\n\t\tdomain: domain.toLowerCase(),\n\t};\n}\n",
7
+ "/** Credentials a client gave with `AUTH`. */\nexport interface Credentials {\n\treadonly mechanism: 'PLAIN' | 'LOGIN';\n\treadonly username: string;\n\treadonly password: string;\n\t/**\n\t * PLAIN's authorization identity, when it differs from the username.\n\t * The server refuses such credentials before `authenticate` is asked.\n\t */\n\treadonly authorizationId?: string;\n}\n\nfunction base64ToText(text: string): string | undefined {\n\tif (!/^[A-Za-z0-9+/]*={0,2}$/.test(text) || text.length % 4 !== 0)\n\t\treturn undefined;\n\ttry {\n\t\treturn new TextDecoder('utf-8', { fatal: true }).decode(\n\t\t\tUint8Array.fromBase64(text),\n\t\t);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** PLAIN (RFC 4616): `authzid NUL authcid NUL passwd`, in base64. */\nexport function decodePlain(response: string): Credentials | undefined {\n\tconst text = base64ToText(response);\n\tif (text === undefined) return undefined;\n\tconst parts = text.split('\\0');\n\tif (parts.length !== 3) return undefined;\n\tconst [authorizationId = '', username = '', password = ''] = parts;\n\tif (username === '' || password === '') return undefined;\n\treturn {\n\t\tmechanism: 'PLAIN',\n\t\tusername,\n\t\tpassword,\n\t\t...(authorizationId === '' || authorizationId === username\n\t\t\t? {}\n\t\t\t: { authorizationId }),\n\t};\n}\n\n/** One step of LOGIN: a base64 line to its text. */\nexport function decodeLoginStep(response: string): string | undefined {\n\treturn base64ToText(response);\n}\n\nconst base64 = (text: string) => new TextEncoder().encode(text).toBase64();\n\n/** A client's PLAIN response (RFC 4616): `NUL username NUL password`, in base64, no authorization identity. */\nexport function encodePlain(username: string, password: string): string {\n\treturn base64(`\\0${username}\\0${password}`);\n}\n\n/** One step of LOGIN, the username or the password, in base64. */\nexport function encodeLoginStep(text: string): string {\n\treturn base64(text);\n}\n",
8
+ "/**\n * What waits to leave a Bun socket. Bun's sockets do not buffer: `write`\n * takes what the kernel takes and says how much. The rest waits here for\n * the socket's `drain` event, so nothing written is lost to a peer that\n * reads slowly. The server's transport and the client's socket share it.\n */\nexport class Outgoing {\n\treadonly #write: (bytes: Uint8Array) => number;\n\t#queue: Uint8Array[] = [];\n\t#waiters: (() => void)[] = [];\n\n\tconstructor(write: (bytes: Uint8Array) => number) {\n\t\tthis.#write = write;\n\t}\n\n\t/** Nothing waits to leave. */\n\tget empty(): boolean {\n\t\treturn this.#queue.length === 0;\n\t}\n\n\t/** Writes what the socket takes now, and keeps the rest for `drain`. */\n\twrite(bytes: Uint8Array): void {\n\t\tif (this.#queue.length > 0) {\n\t\t\tthis.#queue.push(bytes);\n\t\t\treturn;\n\t\t}\n\t\tconst written = this.#write(bytes);\n\t\tif (written < bytes.length) {\n\t\t\tthis.#queue.push(bytes.subarray(Math.max(written, 0)));\n\t\t}\n\t}\n\n\t/** The socket's `drain`: it can take more. True once everything has left. */\n\tdrain(): boolean {\n\t\twhile (this.#queue.length > 0) {\n\t\t\tconst bytes = this.#queue[0] as Uint8Array;\n\t\t\tconst written = this.#write(bytes);\n\t\t\tif (written < bytes.length) {\n\t\t\t\tthis.#queue[0] = bytes.subarray(Math.max(written, 0));\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthis.#queue.shift();\n\t\t}\n\t\tfor (const wake of this.#waiters.splice(0)) wake();\n\t\treturn true;\n\t}\n\n\t/** Resolves once everything written has left, or the socket closed. */\n\tdrained(): Promise<void> {\n\t\tif (this.#queue.length === 0) return Promise.resolve();\n\t\treturn new Promise((wake) => this.#waiters.push(wake));\n\t}\n\n\t/** The socket closed: nothing more will leave. */\n\tclear(): void {\n\t\tthis.#queue = [];\n\t\tfor (const wake of this.#waiters.splice(0)) wake();\n\t}\n}\n",
9
+ "/**\n * The longest line taken, CRLF included: a command (RFC 5321 §4.5.3.1.4\n * asks for 512 at least) or a reply line (§4.5.3.1.5, 512 too).\n */\nexport const MAX_LINE = 2048;\n\n/** One line from the peer, or a line that was too long and was skipped. */\nexport type LineEvent = { line: string } | { tooLong: true };\n\n/**\n * Cuts a peer's bytes into lines — a client's commands, a server's\n * replies — ending at LF with or without CR. It holds one partial line at most: past `MAX_LINE`, the line is\n * skipped to its end and reported once.\n */\nexport class LineSplitter {\n\t#held: Uint8Array = new Uint8Array(0);\n\t#skipping = false;\n\t#rest: Uint8Array = new Uint8Array(0);\n\n\t/** Starts on a chunk; read its lines with `next`. */\n\tpush(chunk: Uint8Array): void {\n\t\tthis.#rest = this.#held.length > 0 ? concat(this.#held, chunk) : chunk;\n\t\tthis.#held = new Uint8Array(0);\n\t}\n\n\t/** The next line of the current chunk, or `undefined` when it has no more. */\n\tnext(): LineEvent | undefined {\n\t\tfor (;;) {\n\t\t\tconst data = this.#rest;\n\t\t\tconst lf = data.indexOf(0x0a);\n\t\t\tif (lf < 0) {\n\t\t\t\tthis.#rest = new Uint8Array(0);\n\t\t\t\tif (this.#skipping) return undefined;\n\t\t\t\tif (data.length > MAX_LINE) {\n\t\t\t\t\tthis.#skipping = true;\n\t\t\t\t\treturn { tooLong: true };\n\t\t\t\t}\n\t\t\t\tthis.#held = data.slice();\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\tconst end = lf > 0 && data[lf - 1] === 0x0d ? lf - 1 : lf;\n\t\t\tthis.#rest = data.subarray(lf + 1);\n\t\t\tif (this.#skipping) {\n\t\t\t\tthis.#skipping = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (end > MAX_LINE) return { tooLong: true };\n\t\t\treturn { line: new TextDecoder().decode(data.subarray(0, end)) };\n\t\t}\n\t}\n\n\t/** Bytes are held or left: a line begun, or lines not yet read. */\n\tget holding(): boolean {\n\t\treturn this.#held.length > 0 || this.#rest.length > 0;\n\t}\n\n\t/** What is left of the current chunk: message content pipelined after DATA. */\n\ttakeRest(): Uint8Array {\n\t\tconst rest = this.#rest;\n\t\tthis.#rest = new Uint8Array(0);\n\t\treturn rest;\n\t}\n\n\t/** Forgets everything held: after STARTTLS, or after a refused DATA. */\n\tclear(): void {\n\t\tthis.#held = new Uint8Array(0);\n\t\tthis.#rest = new Uint8Array(0);\n\t\tthis.#skipping = false;\n\t}\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n\tconst out = new Uint8Array(a.length + b.length);\n\tout.set(a, 0);\n\tout.set(b, a.length);\n\treturn out;\n}\n"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "C64F62A0D2105F3A64756E2164756E21",
13
+ "names": []
14
+ }
@@ -7,7 +7,7 @@ import {
7
7
  MAX_LINE,
8
8
  LineSplitter,
9
9
  Outgoing
10
- } from "../chunks/index-3423jxf9.js";
10
+ } from "../chunks/index-n4xwep5m.js";
11
11
  // src/client/address.ts
12
12
  function isMailbox(address) {
13
13
  return typeof address === "string" && parsePath2(`<${address}>`, false, "refuse") !== undefined;
package/dist/errors.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Reply } from './protocol/reply';
2
2
  /** Why the server refused an option, why a message stream ended in error, or why `sendMail` failed. */
3
- export type SmtpErrorCode = 'INVALID_OPTION' | 'ALREADY_LISTENING' | 'MESSAGE_TOO_BIG' | 'BARE_LINE_BREAK' | 'CONNECTION_LOST' | 'HOOK_TIMEOUT' | 'INVALID_HOOK_REPLY' | 'MESSAGE_NOT_READ' | 'CONNECTION_FAILED' | 'TIMEOUT' | 'BAD_REPLY' | 'REFUSED' | 'RECIPIENTS_REFUSED' | 'TLS_UNAVAILABLE' | 'TLS_FAILED' | 'AUTH_UNAVAILABLE' | 'EXTENSION_MISSING' | 'NULL_MX' | 'DNS_FAILED';
3
+ export type SmtpErrorCode = 'INVALID_OPTION' | 'ALREADY_LISTENING' | 'STOPPED' | 'MESSAGE_TOO_BIG' | 'BARE_LINE_BREAK' | 'CONNECTION_LOST' | 'HOOK_TIMEOUT' | 'INVALID_HOOK_REPLY' | 'MESSAGE_NOT_READ' | 'CONNECTION_FAILED' | 'TIMEOUT' | 'BAD_REPLY' | 'REFUSED' | 'RECIPIENTS_REFUSED' | 'TLS_UNAVAILABLE' | 'TLS_FAILED' | 'AUTH_UNAVAILABLE' | 'EXTENSION_MISSING' | 'NULL_MX' | 'DNS_FAILED';
4
4
  /** One recipient and the server's reply to its `RCPT TO`. */
5
5
  export interface RecipientReply {
6
6
  readonly recipient: string;
@@ -14,6 +14,8 @@ export interface SmtpErrorDetails {
14
14
  readonly reply?: Reply;
15
15
  /** With `RECIPIENTS_REFUSED`: each recipient, and why it was refused. */
16
16
  readonly rejected?: readonly RecipientReply[];
17
+ /** The error behind this one, as `Error`'s `cause`. */
18
+ readonly cause?: unknown;
17
19
  }
18
20
  /**
19
21
  * Thrown by `createSmtpServer` and `listen` for a wrong option or call; the
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,uGAAuG;AACvG,MAAM,MAAM,aAAa,GACtB,gBAAgB,GAChB,mBAAmB,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,cAAc,GACd,oBAAoB,GACpB,kBAAkB,GAClB,mBAAmB,GACnB,SAAS,GACT,WAAW,GACX,SAAS,GACT,oBAAoB,GACpB,iBAAiB,GACjB,YAAY,GACZ,kBAAkB,GAClB,mBAAmB,GACnB,SAAS,GACT,YAAY,CAAC;AAEhB,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACtB;AAED,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C;AAYD;;;;;;;GAOG;AACH,qBAAa,SAAU,SAAQ,KAAK;IACnC,SAAkB,IAAI,eAAe;IACrC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;gBAG7C,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,gBAAqB;CAU/B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,uGAAuG;AACvG,MAAM,MAAM,aAAa,GACtB,gBAAgB,GAChB,mBAAmB,GACnB,SAAS,GACT,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,cAAc,GACd,oBAAoB,GACpB,kBAAkB,GAClB,mBAAmB,GACnB,SAAS,GACT,WAAW,GACX,SAAS,GACT,oBAAoB,GACpB,iBAAiB,GACjB,YAAY,GACZ,kBAAkB,GAClB,mBAAmB,GACnB,SAAS,GACT,YAAY,CAAC;AAEhB,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;CACtB;AAED,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC9C,uDAAuD;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CACzB;AAYD;;;;;;;GAOG;AACH,qBAAa,SAAU,SAAQ,KAAK;IACnC,SAAkB,IAAI,eAAe;IACrC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;gBAG7C,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,gBAAqB;CAa/B"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { type DataChunk, DataReader } from './protocol/data';
4
4
  export { type Path, parsePath, type SourceRoute } from './protocol/path';
5
5
  export { formatReply, type Reply, reply } from './protocol/reply';
6
6
  export { type Credentials, decodeLoginStep, decodePlain, } from './protocol/sasl';
7
- export type { Envelope, HookResult, ReceivedMessage, Session, SmtpHooks, SmtpServerOptions, TlsOptions, } from './server/options';
7
+ export { clientKey } from './server/client-key';
8
+ export type { Envelope, HookResult, ProxyProtocolOptions, ReceivedMessage, Session, SmtpHooks, SmtpServerOptions, TlsOptions, } from './server/options';
8
9
  export { createSmtpServer, type SmtpServer } from './server/server';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,YAAY,EACZ,gBAAgB,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,KAAK,IAAI,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EACN,KAAK,WAAW,EAChB,eAAe,EACf,WAAW,GACX,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACX,QAAQ,EACR,UAAU,EACV,eAAe,EACf,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,UAAU,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,YAAY,EACZ,gBAAgB,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,KAAK,IAAI,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EACN,KAAK,WAAW,EAChB,eAAe,EACf,WAAW,GACX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,YAAY,EACX,QAAQ,EACR,UAAU,EACV,oBAAoB,EACpB,eAAe,EACf,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,UAAU,GACV,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC"}