@bumail/queue 0.9.0 → 0.11.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 (57) hide show
  1. package/README.md +30 -2
  2. package/dist/chunks/{index-9yp15arf.js → index-t6q7zxgj.js} +27 -4
  3. package/dist/chunks/index-t6q7zxgj.js.map +14 -0
  4. package/dist/contract/checks.d.ts +4 -0
  5. package/dist/contract/checks.d.ts.map +1 -1
  6. package/dist/contract/dsn-checks.d.ts.map +1 -1
  7. package/dist/contract/queue-store.d.ts +15 -7
  8. package/dist/contract/queue-store.d.ts.map +1 -1
  9. package/dist/contract/types.d.ts +5 -0
  10. package/dist/contract/types.d.ts.map +1 -1
  11. package/dist/dsn/delivered.d.ts +2 -16
  12. package/dist/dsn/delivered.d.ts.map +1 -1
  13. package/dist/dsn/failed.d.ts +29 -0
  14. package/dist/dsn/failed.d.ts.map +1 -0
  15. package/dist/dsn/for.d.ts +26 -0
  16. package/dist/dsn/for.d.ts.map +1 -0
  17. package/dist/dsn/policy.d.ts +2 -0
  18. package/dist/dsn/policy.d.ts.map +1 -1
  19. package/dist/errors.d.ts +3 -1
  20. package/dist/errors.d.ts.map +1 -1
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +99 -42
  24. package/dist/index.js.map +10 -8
  25. package/dist/memory/index.js +10 -6
  26. package/dist/memory/index.js.map +3 -3
  27. package/dist/memory/store.d.ts +2 -2
  28. package/dist/memory/store.d.ts.map +1 -1
  29. package/dist/postgres/complete.d.ts +13 -0
  30. package/dist/postgres/complete.d.ts.map +1 -0
  31. package/dist/postgres/index.js +85 -74
  32. package/dist/postgres/index.js.map +6 -5
  33. package/dist/postgres/store.d.ts +2 -2
  34. package/dist/postgres/store.d.ts.map +1 -1
  35. package/dist/queue/deliver.d.ts.map +1 -1
  36. package/dist/queue/notify.d.ts +35 -6
  37. package/dist/queue/notify.d.ts.map +1 -1
  38. package/dist/queue/settings/limits.d.ts.map +1 -1
  39. package/dist/redis/complete.d.ts +11 -0
  40. package/dist/redis/complete.d.ts.map +1 -0
  41. package/dist/redis/index.js +133 -110
  42. package/dist/redis/index.js.map +7 -6
  43. package/dist/redis/scripts.d.ts +8 -5
  44. package/dist/redis/scripts.d.ts.map +1 -1
  45. package/dist/redis/store.d.ts +2 -2
  46. package/dist/redis/store.d.ts.map +1 -1
  47. package/dist/sqlite/index.js +29 -20
  48. package/dist/sqlite/index.js.map +5 -4
  49. package/dist/sqlite/insert.d.ts +5 -0
  50. package/dist/sqlite/insert.d.ts.map +1 -0
  51. package/dist/sqlite/store.d.ts +2 -2
  52. package/dist/sqlite/store.d.ts.map +1 -1
  53. package/docs/guide.md +59 -0
  54. package/docs/roadmap.md +36 -10
  55. package/docs/troubleshooting.md +67 -3
  56. package/package.json +4 -4
  57. package/dist/chunks/index-9yp15arf.js.map +0 -14
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  QueueError2,
7
7
  invalid,
8
8
  checkOwner
9
- } from "./chunks/index-9yp15arf.js";
9
+ } from "./chunks/index-t6q7zxgj.js";
10
10
 
11
11
  // src/dsn/build.ts
12
12
  import {
@@ -306,31 +306,48 @@ function asked(kind, notify) {
306
306
  return notify.includes(event[kind]);
307
307
  }
308
308
  var returnOf = (ret, fallback) => ret === "HDRS" ? "headers" : ret === "FULL" ? "full" : fallback;
309
+ var DEFAULT_MAX_RETURN = 64 * 1024;
309
310
 
310
- // src/dsn/delivered.ts
311
- function deliveredDsn(input) {
312
- const wanted = input.recipients.filter((r) => asked("delivered", r.notify));
313
- if (input.to === "" || wanted.length === 0)
311
+ // src/dsn/for.ts
312
+ function dsnFor(kind, input, recipients, returnContent) {
313
+ if (input.to === "" || recipients.length === 0)
314
314
  return;
315
- const date = input.date ?? new Date;
316
315
  return buildDsn({
317
- kind: "delivered",
316
+ kind,
318
317
  reportingMta: input.reportingMta,
319
318
  from: input.from,
320
319
  to: input.to,
321
320
  arrival: input.arrival,
322
- date,
321
+ date: input.date ?? new Date,
323
322
  ...input.envid === undefined ? {} : { envid: input.envid },
324
- recipients: wanted.map((r) => ({
325
- address: r.address,
326
- lastAttempt: date,
327
- ...r.orcpt ? { orcpt: r.orcpt } : {}
328
- })),
323
+ recipients,
329
324
  ...input.original ? { original: input.original } : {},
330
- returnContent: "headers",
331
- maxReturn: input.maxReturn ?? 64 * 1024
325
+ returnContent,
326
+ maxReturn: input.maxReturn ?? DEFAULT_MAX_RETURN
332
327
  });
333
328
  }
329
+
330
+ // src/dsn/delivered.ts
331
+ function deliveredDsn(input) {
332
+ const date = input.date ?? new Date;
333
+ const wanted = input.recipients.filter((r) => asked("delivered", r.notify));
334
+ return dsnFor("delivered", { ...input, date }, wanted.map((r) => ({
335
+ address: r.address,
336
+ lastAttempt: date,
337
+ ...r.orcpt ? { orcpt: r.orcpt } : {}
338
+ })), "headers");
339
+ }
340
+ // src/dsn/failed.ts
341
+ function failedDsn(input) {
342
+ const date = input.date ?? new Date;
343
+ const wanted = input.recipients.filter((r) => asked("failed", r.notify));
344
+ return dsnFor("failed", { ...input, date }, wanted.map((r) => ({
345
+ address: r.address,
346
+ reply: r.reply,
347
+ lastAttempt: date,
348
+ ...r.orcpt ? { orcpt: r.orcpt } : {}
349
+ })), returnOf(input.ret, "headers"));
350
+ }
334
351
  // src/queue/envelope.ts
335
352
  import { isMailbox } from "@bumail/smtp/client";
336
353
  var ADDRESS = /^[^\s<>]{1,64}@[^\s<>@]{1,253}$/u;
@@ -504,7 +521,7 @@ function limitsOf(options) {
504
521
  maxRecipients: numberOf("limits.maxRecipients", l.maxRecipients, 100, 1),
505
522
  maxItems: l.maxItems === undefined ? undefined : numberOf("limits.maxItems", l.maxItems, 0, 1),
506
523
  maxReplyText: numberOf("limits.maxReplyText", l.maxReplyText, 512, 64, 900),
507
- maxDsnReturn: numberOf("limits.maxDsnReturn", l.maxDsnReturn, 64 * 1024, 0)
524
+ maxDsnReturn: numberOf("limits.maxDsnReturn", l.maxDsnReturn, DEFAULT_MAX_RETURN, 0)
508
525
  };
509
526
  }
510
527
 
@@ -921,12 +938,12 @@ function reported(r, kind) {
921
938
  text: [reply.text, ...said].filter((t) => t !== "").join(" ")
922
939
  };
923
940
  }
924
- async function notify(ctx, item, message, kind, recipients, now) {
941
+ function dsnsFor(ctx, item, message, kind, recipients, now, failures) {
925
942
  const states = new Map(item.recipients.map((r) => [r.address, r]));
926
943
  const wanted = recipients.filter((r) => asked(kind, states.get(r.address)?.notify));
927
944
  if (item.from === "" || wanted.length === 0)
928
- return;
929
- const { settings, store, events } = ctx;
945
+ return [];
946
+ const { settings } = ctx;
930
947
  try {
931
948
  const lastAttempt = new Date(now);
932
949
  const dsn = buildDsn({
@@ -952,21 +969,61 @@ async function notify(ctx, item, message, kind, recipients, now) {
952
969
  returnContent: kind === "relayed" ? "headers" : returnOf(item.ret, settings.dsn.returnContent),
953
970
  maxReturn: settings.limits.maxDsnReturn
954
971
  });
955
- const added = await store.add({
956
- from: "",
957
- to: [item.from],
958
- message: dsn,
959
- createdAt: now
960
- });
972
+ return [
973
+ {
974
+ item: { from: "", to: [item.from], message: dsn, createdAt: now },
975
+ kind,
976
+ to: item.from,
977
+ recipients: wanted.map((r) => r.address)
978
+ }
979
+ ];
980
+ } catch (error) {
981
+ failures.push(error);
982
+ return [];
983
+ }
984
+ }
985
+ function dsnsOf(ctx, item, message, settled, now) {
986
+ const failures = [];
987
+ const build = (kind, recipients) => dsnsFor(ctx, item, message, kind, recipients, now, failures);
988
+ const pending = [
989
+ ...build("failed", settled.failed),
990
+ ...build("relayed", settled.delivered.filter((u) => u.dsnOffered === false)),
991
+ ...settled.delayDsn ? build("delayed", settled.deferred) : []
992
+ ];
993
+ return { pending, failures };
994
+ }
995
+ async function tellDsns(ctx, item, pending, completed) {
996
+ const { store, events } = ctx;
997
+ const stored = completed.dsns;
998
+ const outdated = !Array.isArray(stored);
999
+ if (outdated && pending.length > 0) {
1000
+ const error = new QueueError2("INVALID", "Queue store outdated: complete returned the item alone, not { item, dsns }; its DSNs are added one by one, outside the step that records the outcome");
1001
+ events.emit("error", { error, id: item.id });
1002
+ }
1003
+ for (const [index, dsn] of pending.entries()) {
1004
+ let id;
1005
+ if (outdated) {
1006
+ try {
1007
+ id = (await store.add(dsn.item)).id;
1008
+ } catch (error) {
1009
+ events.emit("error", { error, id: item.id });
1010
+ continue;
1011
+ }
1012
+ } else {
1013
+ id = stored[index]?.id;
1014
+ if (id === undefined) {
1015
+ const error = new QueueError2("INVALID", `The queue store stored ${stored.length} of the ${pending.length} DSNs of ${item.id}: a ${dsn.kind} DSN to ${dsn.to} is lost`);
1016
+ events.emit("error", { error, id: item.id });
1017
+ continue;
1018
+ }
1019
+ }
961
1020
  events.emit("dsn", {
962
- kind,
963
- id: added.id,
1021
+ kind: dsn.kind,
1022
+ id,
964
1023
  of: item.id,
965
- to: item.from,
966
- recipients: wanted.map((r) => r.address)
1024
+ to: dsn.to,
1025
+ recipients: dsn.recipients
967
1026
  });
968
- } catch (error) {
969
- events.emit("error", { error, id: item.id });
970
1027
  }
971
1028
  }
972
1029
 
@@ -1099,9 +1156,10 @@ async function record(ctx, item, updates, skipped, message, used) {
1099
1156
  const { settings, store, events } = ctx;
1100
1157
  const now = settings.now();
1101
1158
  const settled = settle(item, updates, skipped, now, settings);
1102
- const after = await store.complete(item.id, settings.owner, settled.result);
1159
+ const { pending, failures } = dsnsOf(ctx, item, message, settled, now);
1160
+ const completed = await store.complete(item.id, settings.owner, settled.result, pending.map((p) => p.item));
1103
1161
  ctx.recorded();
1104
- if (!after && await store.get(item.id) === undefined) {
1162
+ if (!completed && await store.get(item.id) === undefined) {
1105
1163
  const lapse = await ctx.leaseLapsed(now);
1106
1164
  if (lapse) {
1107
1165
  events.emit("error", { error: goneAfter(lapse, item.id), id: item.id });
@@ -1112,7 +1170,7 @@ async function record(ctx, item, updates, skipped, message, used) {
1112
1170
  emitOutcomes(events, item, settled, used);
1113
1171
  return;
1114
1172
  }
1115
- if (!after) {
1173
+ if (!completed) {
1116
1174
  const error = new QueueError2("LEASE_LOST", `The lease on ${item.id} was lost before its outcome was recorded; another worker will try it again`);
1117
1175
  events.emit("error", { error, id: item.id });
1118
1176
  return;
@@ -1121,12 +1179,9 @@ async function record(ctx, item, updates, skipped, message, used) {
1121
1179
  events.emit("error", { error: unreadableError(item.id), id: item.id });
1122
1180
  }
1123
1181
  emitOutcomes(events, item, settled, used);
1124
- await notify(ctx, item, message, "failed", settled.failed, now);
1125
- const relayed = settled.delivered.filter((u) => u.dsnOffered === false);
1126
- await notify(ctx, item, message, "relayed", relayed, now);
1127
- if (settled.delayDsn) {
1128
- await notify(ctx, item, message, "delayed", settled.deferred, now);
1129
- }
1182
+ for (const error of failures)
1183
+ events.emit("error", { error, id: item.id });
1184
+ await tellDsns(ctx, item, pending, completed);
1130
1185
  }
1131
1186
 
1132
1187
  // src/queue/lease.ts
@@ -1414,10 +1469,12 @@ function createQueue(options) {
1414
1469
  };
1415
1470
  }
1416
1471
  export {
1472
+ DEFAULT_MAX_RETURN,
1417
1473
  QueueError2 as QueueError,
1418
1474
  createQueue,
1419
- deliveredDsn
1475
+ deliveredDsn,
1476
+ failedDsn
1420
1477
  };
1421
1478
 
1422
- //# debugId=D24C84F35DEF0E8164756E2164756E21
1479
+ //# debugId=9190A6EB36D3FC0664756E2164756E21
1423
1480
  //# sourceMappingURL=index.js.map