@bumail/queue 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/guide.md CHANGED
@@ -98,6 +98,7 @@ recipient's outcome is recorded with the reply that decided it.
98
98
  | `sendMail` refuses the route's options (`INVALID_OPTION`) | `deferred`, as `4.3.5` | tried again later, and an `error` event: the configuration is yours to fix |
99
99
  | a recipient a store holds that `sendMail` would refuse | `failed`, as `5.1.3` | final, alone; a DSN |
100
100
  | a sender a store holds that `sendMail` would refuse | `failed`, as `5.1.7` | final, every recipient, with no session; a DSN to that sender, which itself fails as `5.1.3` |
101
+ | a message the store lost ([below](#a-message-the-store-lost)) | `failed`, as `5.3.0` | final, every pending recipient, with no session; a DSN without the original, and a `MESSAGE_UNREADABLE` error |
101
102
 
102
103
  An error is read by its `name` (`SmtpError`) and `code`, not by its
103
104
  class: an app with a second copy of `@bumail/smtp` installed, or a
@@ -160,6 +161,40 @@ seconds (RFC 5321 §4.5.3.2's by default) and the whole session's (1800
160
161
  seconds, DNS included). `createQueue` checks each one: above 0, and at
161
162
  most 2147483 seconds, the longest a timer waits.
162
163
 
164
+ ### A message the store lost
165
+
166
+ An item whose message the store no longer gives — a Redis key evicted
167
+ or deleted, a row removed by hand — cannot be sent. Rather than leave it
168
+ leased, claimed again at every lease and holding its `limits.maxItems`
169
+ place, the attempt fails every recipient still pending, at once:
170
+
171
+ - the failures are recorded, and the attempt counts; then
172
+ - the `error` event gets a `QueueError` of code `MESSAGE_UNREADABLE`,
173
+ `The message of … is unreadable: the store holds the item but not its
174
+ message, so every pending recipient failed`, with the item's `id`;
175
+ - each recipient's `failed` event follows, its `reply` `{ status:
176
+ '5.3.0', text: 'Message unreadable: the queue store holds the item but
177
+ not its message' }`, with no `code`, since no server answered;
178
+ - the failure DSN goes to the sender as usual — never about a message
179
+ from `<>` — with no third part, since there is nothing to return;
180
+ - the item leaves the queue. Recipients already delivered keep their
181
+ state, and are not told again.
182
+
183
+ In that order: the record, the `error`, the `failed` events, the `dsn`
184
+ event. Nothing is told before the record: when the lease was lost
185
+ meanwhile, the attempt reports `LEASE_LOST` only, and the worker that
186
+ holds the item next tells the failures; when `complete` throws, the
187
+ `error` event gets that error alone; when the item was cancelled
188
+ meanwhile, its lease still held, nothing is told at all — no session
189
+ ran, and nothing was recorded. An item that is gone along with its
190
+ message (cancelled, or finished by another worker) is left alone, as
191
+ before.
192
+
193
+ A store keeps an item from being claimed before its message can be read
194
+ — the message written first, or both in one step — so a message missing
195
+ at a claim is always damage, never an add still under way. Every store
196
+ here adds both in one step.
197
+
163
198
  ## Retries
164
199
 
165
200
  ```ts
@@ -196,7 +231,9 @@ final, and the item's `attempts` counts them.
196
231
  The queue writes DSNs (RFC 3464) as a `multipart/report`
197
232
  (`report-type=delivery-status`, RFC 6522) of three parts: text for a
198
233
  person, the `message/delivery-status` fields a program reads, and the
199
- original's header fields.
234
+ original's header fields. The third is left out, as RFC 6522 §3
235
+ allows, for [a message the store lost](#a-message-the-store-lost): the
236
+ text then says it could not be read.
200
237
 
201
238
  ```
202
239
  Reporting-MTA: dns; mail.example.net
@@ -309,11 +346,19 @@ off(); // stops listening
309
346
  | `deferred` | the same, and `nextAttemptAt` |
310
347
  | `failed` | the same as `delivered` |
311
348
  | `dsn` | `{ kind: 'delayed' \| 'failed', id, of, to, recipients }`: `id` is the DSN's own item, `of` the item it reports on |
312
- | `error` | `{ error, id? }`: a store that failed (a lease renewal included), a DSN that could not be enqueued, a lease lost, a route `sendMail` refused (`INVALID_OPTION`) |
313
-
314
- Events come once the outcome is recorded. A listener that throws is
315
- ignored. `reply.code` is absent when no server answered: a connection
316
- error, a timeout, the DNS.
349
+ | `error` | `{ error, id? }`: a store that failed (a lease renewal included), a DSN that could not be enqueued, a lease lost (`LEASE_LOST`), a message the store lost (`MESSAGE_UNREADABLE`), a route `sendMail` refused (`INVALID_OPTION`) |
350
+
351
+ Events about an outcome come once it is recorded, in this order: a
352
+ `MESSAGE_UNREADABLE` error when the store lost the message, each
353
+ recipient's `delivered`, `deferred` or `failed`, then the `dsn` events.
354
+ An item cancelled while it was delivered, its lease still held, has
355
+ what its sessions did told, with no DSN, though nothing is recorded;
356
+ for a message the store lost, no session ran, and nothing is told.
357
+ Other errors are told as they happen: a route `sendMail` refused during
358
+ its session, a lease lost or a store that failed in place of the
359
+ outcome, and a DSN that could not be enqueued, during the `dsn` step.
360
+ A listener that throws is ignored. `reply.code` is absent
361
+ when no server answered: a connection error, a timeout, the DNS.
317
362
 
318
363
  ## Admin
319
364
 
@@ -413,8 +458,23 @@ a store on the same database, and they share one queue.
413
458
  - It is typed by its shape, `PostgresClient` (`unsafe`, `begin` and
414
459
  `close`), so the declarations need nothing of `@types/bun`; a `Bun.SQL`
415
460
  client fits it.
461
+ - **Writes run at `READ COMMITTED`**, whatever the client's sessions
462
+ default to: each transaction that writes — a claim, a renewal, an
463
+ outcome, an add, a reschedule, a cancel, a migration — starts with
464
+ `SET TRANSACTION ISOLATION LEVEL READ COMMITTED, READ WRITE`. A client
465
+ shared with code that sets `default_transaction_isolation` to
466
+ `repeatable read` or `serializable` (`new Bun.SQL({ url, connection: {
467
+ default_transaction_isolation: 'serializable' } })`) still serves the
468
+ queue: the claim's `FOR UPDATE SKIP LOCKED` and the `maxItems` lock
469
+ need that level, and under a stricter one concurrent writes would fail
470
+ with SQLSTATE 40001. Reads are one statement each, at the client's
471
+ level.
416
472
  - **Nothing connects at `open`**: a wrong option is refused there, and a
417
- database out of reach on the first call.
473
+ database out of reach on the first call. A URL is never repeated in an
474
+ error, and the password — the URL's, or a given `Bun.SQL`'s — is
475
+ masked should the database's reason name it: always where it stands
476
+ in a URL (`:…@`), and alone, elsewhere, when it is 4 characters or
477
+ more (masking a shorter one everywhere would garble the reason).
418
478
 
419
479
  ### The schema
420
480
 
@@ -431,7 +491,10 @@ They are the `bun:sqlite` store's tables, column for column. The prefix
431
491
  is lowercase letters, digits and underscores, starting with a letter or
432
492
  an underscore, 40 characters at most: it is written into the statements,
433
493
  never bound. Give each queue its own prefix to keep several in one
434
- database. A `bytea` holds 1 GB at most, far above `limits.maxMessageSize`
494
+ database, and never share a prefix with another package's or
495
+ application's tables: the store cannot tell another `<prefix>schema` or
496
+ `<prefix>messages` from its own, and would read that version as the
497
+ queue's. A `bytea` holds 1 GB at most, far above `limits.maxMessageSize`
435
498
  (25 MiB by default).
436
499
 
437
500
  ### Migrations
@@ -490,8 +553,9 @@ RETURNING …
490
553
 
491
554
  ### Pool size
492
555
 
493
- Each call holds a connection for one statement, `complete` and an add
494
- with `limits.maxItems` for one short transaction. A worker has at most
556
+ A read holds a connection for one statement, and each write for one
557
+ short transaction: the isolation level, then its statement or two, then
558
+ the commit. A worker has at most
495
559
  `concurrency` items (20) in flight, each mostly waiting on a remote SMTP
496
560
  server, not the database: `Bun.SQL`'s default pool of 10 connections is
497
561
  enough for one, and a call waits for a free connection rather than fail.
@@ -543,6 +607,9 @@ exactly once, against `redis:7`.
543
607
  and a claim or an outcome is told on `error` (see Troubleshooting).
544
608
  - One client is one connection, pipelined: every call of one store goes
545
609
  through it, so there is no pool to size.
610
+ - A URL is never repeated in an error, and its password is masked should
611
+ Redis's or Bun's reason name it, as on PostgreSQL. A given client's
612
+ password is not known to the store, and not masked.
546
613
 
547
614
  ### The keys
548
615
 
@@ -626,7 +693,9 @@ instance between its steps.
626
693
  `noeviction` it refuses the write (`OOM command not allowed`), and
627
694
  `enqueue` rejects with that `RedisError`. An item whose hash is gone
628
695
  all the same (evicted, or deleted by hand) is dropped by the next
629
- claim that meets it, its message with it, and never leased. Bound the queue with `limits.maxItems` and
696
+ claim that meets it, its message with it, and never leased. An item
697
+ whose message alone is gone fails its pending recipients at once
698
+ ([a message the store lost](#a-message-the-store-lost)). Bound the queue with `limits.maxItems` and
630
699
  `limits.maxMessageSize`, and size `maxmemory` for both.
631
700
  - **An ACL user** needs the commands the store sends and the ones its
632
701
  scripts call, on its prefix's keys (`~<keyPrefix>*`; here the
@@ -716,8 +785,15 @@ points at it, and `mxPort` its port.
716
785
  A store implements `QueueStore`, throws `QueueError` with the contract's
717
786
  codes, and keeps its promises:
718
787
 
788
+ - `add` never makes an item claimable before its message is readable:
789
+ write the message first, or both in one step.
719
790
  - `claim` is atomic: two claimers, in any process, never get the same
720
791
  item; an item whose lease expired is claimable again.
792
+ - `readMessage` gives `undefined` once the item is gone, and also when
793
+ the store still holds the item but has lost its message: the queue
794
+ then fails the item's pending recipients as `5.3.0`
795
+ ([a message the store lost](#a-message-the-store-lost)), so never
796
+ throw for a missing message.
721
797
  - `complete` applies the outcome, the schedule and the count, and lets go
722
798
  of the lease, in one step and only for the lease's owner; a final
723
799
  status is never changed; an item whose every recipient is final is
package/docs/roadmap.md CHANGED
@@ -7,7 +7,7 @@ only number on it.
7
7
  ## Now
8
8
 
9
9
  Nothing in progress: the MongoDB store, first under Next, is the next
10
- one taken. The Redis store is merged (see Shipped).
10
+ one taken. The Redis store shipped in 0.3.0 (see Shipped).
11
11
 
12
12
  ## Next
13
13
 
@@ -65,6 +65,25 @@ Then:
65
65
 
66
66
  ### Unreleased — merged, not yet published
67
67
 
68
+ - **The PostgreSQL store's writes run at `READ COMMITTED`** — whatever
69
+ the client's sessions default to, so a client shared with code that
70
+ defaults to `repeatable read` or `serializable` serves the queue:
71
+ concurrent claims, outcomes, adds with `maxItems` and migrations no
72
+ longer fail with SQLSTATE 40001, and `maxItems` holds under them.
73
+ - **A message the store lost no longer loops** — an item whose message
74
+ is gone (a Redis key evicted or deleted, a row removed by hand) fails
75
+ its pending recipients at once as `5.3.0`, `Message unreadable`, says
76
+ so on the `error` event (`MESSAGE_UNREADABLE`), sends its failure DSN
77
+ without the original and leaves the queue, rather than be claimed
78
+ again at every lease, forever, holding its `maxItems` place.
79
+ - **A set-up error never shows the password** — `The PostgreSQL queue
80
+ cannot be set up` and `The Redis queue cannot be set up` mask it
81
+ should the database's reason repeat it; and a password under 4
82
+ characters is masked only where a URL holds it, so the rest of the
83
+ reason stays readable.
84
+
85
+ ### 0.3.0
86
+
68
87
  - **A Redis store**, `@bumail/queue/redis`, on Bun's own `Bun.redis`,
69
88
  for instances of a server on several machines sharing one queue:
70
89
  `RedisQueueStore.open({ client })` takes a `Bun.RedisClient`, or
@@ -36,10 +36,12 @@ parts shown as … vary.
36
36
  - [`QueueError: The lease on … was lost while it was delivered`](#queueerror-the-lease-on--was-lost-while-it-was-delivered)
37
37
  - [`QueueError: The lease on … expired before its outcome was recorded, and the item is gone: lost to another worker that finished it, the message then sent twice, or cancelled`](#queueerror-the-lease-on--expired-before-its-outcome-was-recorded-and-the-item-is-gone-lost-to-another-worker-that-finished-it-the-message-then-sent-twice-or-cancelled)
38
38
  - [`QueueError: The lease on … was lost while it was delivered, and the item is gone: finished by the worker that took it, the message then sent twice, or cancelled`](#queueerror-the-lease-on--was-lost-while-it-was-delivered-and-the-item-is-gone-finished-by-the-worker-that-took-it-the-message-then-sent-twice-or-cancelled)
39
+ - [`QueueError: The message of … is unreadable: the store holds the item but not its message, so every pending recipient failed`](#queueerror-the-message-of--is-unreadable-the-store-holds-the-item-but-not-its-message-so-every-pending-recipient-failed)
39
40
  - [`SmtpError: sendMail(): …` (`INVALID_OPTION`), with recipients deferred as `4.3.5`](#smtperror-sendmail--invalid_option-with-recipients-deferred-as-435)
40
41
  - [`SQLiteError: database is locked`, or another store error, during a delivery](#sqliteerror-database-is-locked-or-another-store-error-during-a-delivery)
41
42
  - [`PostgresError: …`, or a connection error, during a delivery](#postgreserror--or-a-connection-error-during-a-delivery)
42
43
  - [`RedisError: …` during a delivery: `Connection closed`, `OOM command not allowed …`, `READONLY …`](#rediserror--during-a-delivery-connection-closed-oom-command-not-allowed--readonly-)
44
+ - [`PostgresError: could not serialize access due to concurrent update` (SQLSTATE `40001`)](#postgreserror-could-not-serialize-access-due-to-concurrent-update-sqlstate-40001)
43
45
 
44
46
  **The `bun:sqlite` store** (`@bumail/queue/sqlite`)
45
47
 
@@ -87,6 +89,7 @@ parts shown as … vary.
87
89
  - [Every delivery fails at once with `5.7.1`, or with a reply naming your IP or EHLO name](#every-delivery-fails-at-once-with-571-or-with-a-reply-naming-your-ip-or-ehlo-name)
88
90
  - [A recipient fails with `5.1.3`, `The address is not one SMTP can carry`](#a-recipient-fails-with-513-the-address-is-not-one-smtp-can-carry)
89
91
  - [Every recipient fails with `5.1.7`, `The sender's address is not one SMTP can carry`](#every-recipient-fails-with-517-the-senders-address-is-not-one-smtp-can-carry)
92
+ - [Every pending recipient fails with `5.3.0`, `Message unreadable: the queue store holds the item but not its message`](#every-pending-recipient-fails-with-530-message-unreadable-the-queue-store-holds-the-item-but-not-its-message)
90
93
  - [`retryNow` returns `false` for an item that is in the queue](#retrynow-returns-false-for-an-item-that-is-in-the-queue)
91
94
 
92
95
  ## Enqueuing
@@ -323,6 +326,29 @@ the store keeps no record of which happened, so the message names both.
323
326
  **Fix:** a longer `leaseMs`, find what stalled the process, and keep the
324
327
  machines on NTP.
325
328
 
329
+ ### `QueueError: The message of … is unreadable: the store holds the item but not its message, so every pending recipient failed`
330
+
331
+ **Code:** `MESSAGE_UNREADABLE`, with the item's `id`.
332
+ **When:** a worker claimed an item, the store gave no message for it
333
+ while it still held the item, and the failures were then recorded: it
334
+ comes after the record, before the `failed` events. With the lease lost
335
+ before the record, only `LEASE_LOST` is told, and the next worker tells
336
+ this; with the item cancelled meanwhile, nothing is.
337
+ **Why:** the message is gone and the item is not: on Redis, its
338
+ `<prefix>message:<id>` key evicted under a `maxmemory-policy` other than
339
+ `noeviction`, or deleted; on any store, a message row or key removed by
340
+ hand, or a store of your own that loses it. Nothing can be sent, so
341
+ every recipient still pending fails as `5.3.0` (see
342
+ [below](#every-pending-recipient-fails-with-530-message-unreadable-the-queue-store-holds-the-item-but-not-its-message)),
343
+ the failure DSN goes to the sender without the original, and the item
344
+ leaves the queue. Before this, such an item stayed leased and was
345
+ claimed again at every lease, forever, with no event.
346
+ **Fix:** find what removed the message. On Redis, set
347
+ `maxmemory-policy noeviction` and size `maxmemory` for the queue (the
348
+ guide's [Redis](guide.md#redis)); never delete a queue's keys or rows by
349
+ hand, `cancel(id)` drops an item with its message. The sender has the
350
+ DSN and may send again.
351
+
326
352
  ### `SmtpError: sendMail(): …` (`INVALID_OPTION`), with recipients deferred as `4.3.5`
327
353
 
328
354
  **When:** `sendMail` refused the options of a session — the route, the
@@ -387,6 +413,26 @@ your provider's endpoint or Sentinel's, which follows a failover). For
387
413
  `maxmemory-policy noeviction` (the guide's [Redis](guide.md#redis)), or
388
414
  Redis drops queue items to make room instead of refusing.
389
415
 
416
+ ### `PostgresError: could not serialize access due to concurrent update` (SQLSTATE `40001`)
417
+
418
+ **When:** on `@bumail/queue/postgres` 0.3.0 or earlier — a claim, an
419
+ outcome, an `enqueue` with `limits.maxItems` or a migration rejecting, or
420
+ told on `error`, with `could not serialize access due to concurrent
421
+ update` or `… due to read/write dependencies among transactions`, while
422
+ several workers run — or `limits.maxItems` letting more items in than
423
+ its limit.
424
+ **Why:** the client's sessions default to `repeatable read` or
425
+ `serializable` (`connection: { default_transaction_isolation }`, or the
426
+ role's or database's own default). The claim's `FOR UPDATE SKIP LOCKED`
427
+ and the `maxItems` advisory lock need `READ COMMITTED`: under a stricter
428
+ level, a row another worker changed since the transaction's snapshot
429
+ fails, and the count taken after the lock reads a snapshot from before
430
+ it.
431
+ **Fix:** upgrade: every transaction the store writes in now starts with
432
+ `SET TRANSACTION ISOLATION LEVEL READ COMMITTED`, whatever the client's
433
+ default. The error from a transaction of your own on the same client is
434
+ yours to retry.
435
+
390
436
  ## The `bun:sqlite` store
391
437
 
392
438
  ### `QueueError: A SQLite queue store needs a directory`
@@ -447,7 +493,8 @@ PostgresQueueStore.open({ sql: new Bun.SQL({ url, max: 10 }) });
447
493
  **Why:** the prefix is written into every statement, never bound as a
448
494
  value, so anything that is not a plain name is refused — and 40
449
495
  characters keep the longest name built on it within PostgreSQL's 63.
450
- **Fix:** `tablePrefix: 'mail_queue_'`. To put the tables in another
496
+ **Fix:** `tablePrefix: 'mail_queue_'`, a prefix of the queue's own: never
497
+ one another package's or application's tables use. To put the tables in another
451
498
  schema, set the connection's `search_path` rather than a dotted prefix.
452
499
 
453
500
  ### `QueueError: The URL in sql cannot be opened: …`
@@ -457,7 +504,10 @@ schema, set the connection's `search_path` rather than a dotted prefix.
457
504
  connecting, such as a `sslmode` it does not know. The rest is Bun's
458
505
  reason (`The argument 'sslmode' must be one of: disable, allow, prefer,
459
506
  require, verify-ca, verify-full. Received '…'`); the URL is never
460
- repeated, and the password is masked should the reason name it.
507
+ repeated, and the password is masked should the reason name it: always
508
+ where a URL holds it (`:…@`), and alone, elsewhere, when it is 4
509
+ characters or more — masking `x` everywhere would turn "execute" into
510
+ "e…ecute".
461
511
  **Fix:** correct the parameter: `?sslmode=require`, or `verify-full` to
462
512
  check the server's certificate.
463
513
 
@@ -467,7 +517,10 @@ check the server's certificate.
467
517
  **When:** the first call on a store, or `migrate()`, when the tables
468
518
  cannot be made or brought up to date. The rest of the message is the
469
519
  database's: `Failed to connect`, `password authentication failed for
470
- user "…"`, `permission denied for schema public`.
520
+ user "…"`, `permission denied for schema public`. The password — the
521
+ URL's, or a given `Bun.SQL`'s — is masked should the reason name it, as
522
+ for `The URL in sql cannot be opened`: a `…` where you expect a role or
523
+ a database name is one that equals the password.
471
524
  **Why:** the database is out of reach, the credentials are wrong, or the
472
525
  tables are missing or behind and the role may not create them: a role
473
526
  without `CREATE` can use tables that are current, never make them.
@@ -516,7 +569,8 @@ database its own prefix.
516
569
  **When:** `RedisQueueStore.open` with a URL `Bun.RedisClient` refuses
517
570
  before connecting, such as a database that is not a number (`Invalid
518
571
  database number in Redis URL: "…"`). The URL is never repeated, and the
519
- password is masked should the reason name it.
572
+ password is masked should the reason name it, as on PostgreSQL: always
573
+ in a URL, and alone from 4 characters.
520
574
  **Fix:** correct the URL: the database is the path, `/0` to `/15` on a
521
575
  default Redis.
522
576
 
@@ -528,7 +582,8 @@ its keys (`<prefix>schema`), writing it on a new queue. The rest of the
528
582
  message is Redis's or Bun's: `Max reconnection attempts reached` (out of
529
583
  reach), `WRONGPASS invalid username-password pair or user is disabled.`,
530
584
  `ERR DB index is out of range`, `NOPERM …` (an ACL user without
531
- `EVALSHA`, `EVAL` or the keys).
585
+ `EVALSHA`, `EVAL` or the keys). The URL's password is masked should the
586
+ reason repeat it; a given client's is not known to the store.
532
587
  **Why:** the server is out of reach, the credentials or the database are
533
588
  wrong, or the user may not run scripts. With `Bun.RedisClient`'s
534
589
  defaults an unreachable server takes about half a minute of reconnecting
@@ -697,6 +752,22 @@ other.
697
752
  `@bumail/smtp/client`'s `isMailbox` (or let it be `''`) before a store of
698
753
  your own keeps it.
699
754
 
755
+ ### Every pending recipient fails with `5.3.0`, `Message unreadable: the queue store holds the item but not its message`
756
+
757
+ **When:** the store held the item but gave no message for it, and the
758
+ failures were recorded: the `error` event says
759
+ [`The message of … is unreadable`](#queueerror-the-message-of--is-unreadable-the-store-holds-the-item-but-not-its-message-so-every-pending-recipient-failed)
760
+ for the same item, just before these `failed` events. An item cancelled
761
+ before the record tells no `5.3.0` failure; with the lease lost, this
762
+ attempt tells none and the next worker tells them.
763
+ **Why:** no session can send a message that cannot be read, and an
764
+ attempt left without an outcome would be claimed again at every lease,
765
+ forever, keeping its `maxItems` place. Recipients already delivered keep
766
+ their state; the others fail in one attempt, with one failure DSN —
767
+ none for a message from `<>` — that returns nothing of the original.
768
+ **Fix:** as for that error: find what removed the message, and on Redis
769
+ keep `maxmemory-policy noeviction`.
770
+
700
771
  ### `retryNow` returns `false` for an item that is in the queue
701
772
 
702
773
  **Why:** a worker is delivering it: its lease is held. That attempt's
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bumail/queue",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "The outbound mail queue of a mail server: every recipient's state, retries with back-off, delivery status notifications (RFC 3464), leases for several workers, and a memory, a bun:sqlite, a PostgreSQL (Bun.sql) and a Redis (Bun.redis) store, delivering through @bumail/smtp/client",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,18 +0,0 @@
1
- // src/masked.ts
2
- function masked(text, raw) {
3
- let decoded = raw;
4
- try {
5
- decoded = decodeURIComponent(raw);
6
- } catch {}
7
- let out = text;
8
- for (const password of [raw, decoded]) {
9
- if (password !== "")
10
- out = out.replaceAll(password, "…");
11
- }
12
- return out;
13
- }
14
-
15
- export { masked };
16
-
17
- //# debugId=64BFD310901EABF064756E2164756E21
18
- //# sourceMappingURL=index-y3n1w2az.js.map
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/masked.ts"],
4
- "sourcesContent": [
5
- "/**\n * `text` with a URL's password masked, as written in the URL and decoded,\n * if it decodes: what a store opening a URL repeats of the client's\n * reason, which may name it.\n */\nexport function masked(text: string, raw: string): string {\n\tlet decoded = raw;\n\ttry {\n\t\tdecoded = decodeURIComponent(raw);\n\t} catch {\n\t\t// Not percent-encoding a client reads either: the raw form is what it would repeat.\n\t}\n\tlet out = text;\n\tfor (const password of [raw, decoded]) {\n\t\tif (password !== '') out = out.replaceAll(password, '…');\n\t}\n\treturn out;\n}\n"
6
- ],
7
- "mappings": ";AAKO,SAAS,MAAM,CAAC,MAAc,KAAqB;AAAA,EACzD,IAAI,UAAU;AAAA,EACd,IAAI;AAAA,IACH,UAAU,mBAAmB,GAAG;AAAA,IAC/B,MAAM;AAAA,EAGR,IAAI,MAAM;AAAA,EACV,WAAW,YAAY,CAAC,KAAK,OAAO,GAAG;AAAA,IACtC,IAAI,aAAa;AAAA,MAAI,MAAM,IAAI,WAAW,UAAU,GAAG;AAAA,EACxD;AAAA,EACA,OAAO;AAAA;",
8
- "debugId": "64BFD310901EABF064756E2164756E21",
9
- "names": []
10
- }