@heavybit/shared-lib 2.1.0 → 2.7.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 (296) hide show
  1. package/README.md +163 -2
  2. package/dist/cjs/access/index.js +9 -1
  3. package/dist/cjs/access/index.js.map +1 -1
  4. package/dist/cjs/approvals/index.js +137 -0
  5. package/dist/cjs/approvals/index.js.map +1 -0
  6. package/dist/cjs/audit/index.js +74 -0
  7. package/dist/cjs/audit/index.js.map +1 -0
  8. package/dist/cjs/cache/index.js +90 -16
  9. package/dist/cjs/cache/index.js.map +1 -1
  10. package/dist/cjs/cache/keyspace.js +101 -0
  11. package/dist/cjs/cache/keyspace.js.map +1 -0
  12. package/dist/cjs/common/circuit-breaker.js +74 -0
  13. package/dist/cjs/common/circuit-breaker.js.map +1 -0
  14. package/dist/cjs/common/index.js +4 -0
  15. package/dist/cjs/common/index.js.map +1 -1
  16. package/dist/cjs/common/internal-client.js +177 -0
  17. package/dist/cjs/common/internal-client.js.map +1 -0
  18. package/dist/cjs/common/internal-constants.js +12 -0
  19. package/dist/cjs/common/internal-constants.js.map +1 -0
  20. package/dist/cjs/common/internal-service-auth.js +160 -0
  21. package/dist/cjs/common/internal-service-auth.js.map +1 -0
  22. package/dist/cjs/common/money.js +215 -0
  23. package/dist/cjs/common/money.js.map +1 -0
  24. package/dist/cjs/common/phone.js +41 -22
  25. package/dist/cjs/common/phone.js.map +1 -1
  26. package/dist/cjs/common/time.js +28 -2
  27. package/dist/cjs/common/time.js.map +1 -1
  28. package/dist/cjs/config/index.js +139 -19
  29. package/dist/cjs/config/index.js.map +1 -1
  30. package/dist/cjs/contracts/notification-dispatch.js +1 -1
  31. package/dist/cjs/database/db.js +37 -0
  32. package/dist/cjs/database/db.js.map +1 -0
  33. package/dist/cjs/database/index.js +22 -51
  34. package/dist/cjs/database/index.js.map +1 -1
  35. package/dist/cjs/database/pagination.js +88 -0
  36. package/dist/cjs/database/pagination.js.map +1 -0
  37. package/dist/cjs/database/pool.js +60 -0
  38. package/dist/cjs/database/pool.js.map +1 -0
  39. package/dist/cjs/database/scoped-repository.js +132 -0
  40. package/dist/cjs/database/scoped-repository.js.map +1 -0
  41. package/dist/cjs/database/scopes.js +166 -0
  42. package/dist/cjs/database/scopes.js.map +1 -0
  43. package/dist/cjs/database/table-names.js +14 -0
  44. package/dist/cjs/database/table-names.js.map +1 -0
  45. package/dist/cjs/database/tables.js +71 -0
  46. package/dist/cjs/database/tables.js.map +1 -0
  47. package/dist/cjs/express/auth.js +64 -50
  48. package/dist/cjs/express/auth.js.map +1 -1
  49. package/dist/cjs/express/index.js +2 -0
  50. package/dist/cjs/express/index.js.map +1 -1
  51. package/dist/cjs/express/internal-caller.js +52 -0
  52. package/dist/cjs/express/internal-caller.js.map +1 -0
  53. package/dist/cjs/express/step-up.js +41 -0
  54. package/dist/cjs/express/step-up.js.map +1 -0
  55. package/dist/cjs/http/index.js +3 -71
  56. package/dist/cjs/http/index.js.map +1 -1
  57. package/dist/cjs/identity/index.js +269 -67
  58. package/dist/cjs/identity/index.js.map +1 -1
  59. package/dist/cjs/identity/step-up.js +52 -0
  60. package/dist/cjs/identity/step-up.js.map +1 -0
  61. package/dist/cjs/index.js +6 -0
  62. package/dist/cjs/index.js.map +1 -1
  63. package/dist/cjs/knex/columns.js +36 -0
  64. package/dist/cjs/knex/columns.js.map +1 -0
  65. package/dist/cjs/knex/index.js +26 -43
  66. package/dist/cjs/knex/index.js.map +1 -1
  67. package/dist/cjs/knex/migration-helpers.js +57 -0
  68. package/dist/cjs/knex/migration-helpers.js.map +1 -0
  69. package/dist/cjs/logging/redact.js +6 -11
  70. package/dist/cjs/logging/redact.js.map +1 -1
  71. package/dist/cjs/messaging/base-consumer.js +36 -0
  72. package/dist/cjs/messaging/base-consumer.js.map +1 -0
  73. package/dist/cjs/messaging/consumer.js +142 -73
  74. package/dist/cjs/messaging/consumer.js.map +1 -1
  75. package/dist/cjs/messaging/envelope.js +53 -10
  76. package/dist/cjs/messaging/envelope.js.map +1 -1
  77. package/dist/cjs/messaging/idempotency.js +95 -8
  78. package/dist/cjs/messaging/idempotency.js.map +1 -1
  79. package/dist/cjs/messaging/index.js +2 -1
  80. package/dist/cjs/messaging/index.js.map +1 -1
  81. package/dist/cjs/messaging/outbox.js +97 -55
  82. package/dist/cjs/messaging/outbox.js.map +1 -1
  83. package/dist/cjs/messaging/publisher.js +86 -38
  84. package/dist/cjs/messaging/publisher.js.map +1 -1
  85. package/dist/cjs/messaging/staff-notifier.js +4 -4
  86. package/dist/cjs/messaging/staff-notifier.js.map +1 -1
  87. package/dist/cjs/messaging/topology.js +41 -5
  88. package/dist/cjs/messaging/topology.js.map +1 -1
  89. package/dist/cjs/rate-limit/index.js +57 -6
  90. package/dist/cjs/rate-limit/index.js.map +1 -1
  91. package/dist/cjs/rate-limit/keys.js +47 -0
  92. package/dist/cjs/rate-limit/keys.js.map +1 -0
  93. package/dist/cjs/socket/index.js +127 -16
  94. package/dist/cjs/socket/index.js.map +1 -1
  95. package/dist/cjs/socket/ticket.js +124 -0
  96. package/dist/cjs/socket/ticket.js.map +1 -0
  97. package/dist/cjs/testing/index.js +23 -14
  98. package/dist/cjs/testing/index.js.map +1 -1
  99. package/dist/esm/access/index.js +9 -1
  100. package/dist/esm/access/index.js.map +1 -1
  101. package/dist/esm/approvals/index.js +127 -0
  102. package/dist/esm/approvals/index.js.map +1 -0
  103. package/dist/esm/audit/index.js +70 -0
  104. package/dist/esm/audit/index.js.map +1 -0
  105. package/dist/esm/cache/index.js +70 -13
  106. package/dist/esm/cache/index.js.map +1 -1
  107. package/dist/esm/cache/keyspace.js +95 -0
  108. package/dist/esm/cache/keyspace.js.map +1 -0
  109. package/dist/esm/common/circuit-breaker.js +70 -0
  110. package/dist/esm/common/circuit-breaker.js.map +1 -0
  111. package/dist/esm/common/index.js +4 -0
  112. package/dist/esm/common/index.js.map +1 -1
  113. package/dist/esm/common/internal-client.js +173 -0
  114. package/dist/esm/common/internal-client.js.map +1 -0
  115. package/dist/esm/common/internal-constants.js +9 -0
  116. package/dist/esm/common/internal-constants.js.map +1 -0
  117. package/dist/esm/common/internal-service-auth.js +150 -0
  118. package/dist/esm/common/internal-service-auth.js.map +1 -0
  119. package/dist/esm/common/money.js +207 -0
  120. package/dist/esm/common/money.js.map +1 -0
  121. package/dist/esm/common/phone.js +41 -22
  122. package/dist/esm/common/phone.js.map +1 -1
  123. package/dist/esm/common/time.js +27 -2
  124. package/dist/esm/common/time.js.map +1 -1
  125. package/dist/esm/config/index.js +135 -18
  126. package/dist/esm/config/index.js.map +1 -1
  127. package/dist/esm/contracts/notification-dispatch.js +1 -1
  128. package/dist/esm/database/db.js +32 -0
  129. package/dist/esm/database/db.js.map +1 -0
  130. package/dist/esm/database/index.js +9 -45
  131. package/dist/esm/database/index.js.map +1 -1
  132. package/dist/esm/database/pagination.js +81 -0
  133. package/dist/esm/database/pagination.js.map +1 -0
  134. package/dist/esm/database/pool.js +51 -0
  135. package/dist/esm/database/pool.js.map +1 -0
  136. package/dist/esm/database/scoped-repository.js +128 -0
  137. package/dist/esm/database/scoped-repository.js.map +1 -0
  138. package/dist/esm/database/scopes.js +157 -0
  139. package/dist/esm/database/scopes.js.map +1 -0
  140. package/dist/esm/database/table-names.js +11 -0
  141. package/dist/esm/database/table-names.js.map +1 -0
  142. package/dist/esm/database/tables.js +61 -0
  143. package/dist/esm/database/tables.js.map +1 -0
  144. package/dist/esm/express/auth.js +62 -48
  145. package/dist/esm/express/auth.js.map +1 -1
  146. package/dist/esm/express/index.js +2 -0
  147. package/dist/esm/express/index.js.map +1 -1
  148. package/dist/esm/express/internal-caller.js +49 -0
  149. package/dist/esm/express/internal-caller.js.map +1 -0
  150. package/dist/esm/express/step-up.js +38 -0
  151. package/dist/esm/express/step-up.js.map +1 -0
  152. package/dist/esm/http/index.js +3 -70
  153. package/dist/esm/http/index.js.map +1 -1
  154. package/dist/esm/identity/index.js +246 -64
  155. package/dist/esm/identity/index.js.map +1 -1
  156. package/dist/esm/identity/step-up.js +47 -0
  157. package/dist/esm/identity/step-up.js.map +1 -0
  158. package/dist/esm/index.js +6 -0
  159. package/dist/esm/index.js.map +1 -1
  160. package/dist/esm/knex/columns.js +28 -0
  161. package/dist/esm/knex/columns.js.map +1 -0
  162. package/dist/esm/knex/index.js +8 -34
  163. package/dist/esm/knex/index.js.map +1 -1
  164. package/dist/esm/knex/migration-helpers.js +51 -0
  165. package/dist/esm/knex/migration-helpers.js.map +1 -0
  166. package/dist/esm/logging/redact.js +3 -7
  167. package/dist/esm/logging/redact.js.map +1 -1
  168. package/dist/esm/messaging/base-consumer.js +32 -0
  169. package/dist/esm/messaging/base-consumer.js.map +1 -0
  170. package/dist/esm/messaging/consumer.js +139 -71
  171. package/dist/esm/messaging/consumer.js.map +1 -1
  172. package/dist/esm/messaging/envelope.js +50 -10
  173. package/dist/esm/messaging/envelope.js.map +1 -1
  174. package/dist/esm/messaging/idempotency.js +91 -7
  175. package/dist/esm/messaging/idempotency.js.map +1 -1
  176. package/dist/esm/messaging/index.js +2 -1
  177. package/dist/esm/messaging/index.js.map +1 -1
  178. package/dist/esm/messaging/outbox.js +97 -54
  179. package/dist/esm/messaging/outbox.js.map +1 -1
  180. package/dist/esm/messaging/publisher.js +86 -38
  181. package/dist/esm/messaging/publisher.js.map +1 -1
  182. package/dist/esm/messaging/staff-notifier.js +4 -4
  183. package/dist/esm/messaging/staff-notifier.js.map +1 -1
  184. package/dist/esm/messaging/topology.js +39 -4
  185. package/dist/esm/messaging/topology.js.map +1 -1
  186. package/dist/esm/rate-limit/index.js +41 -6
  187. package/dist/esm/rate-limit/index.js.map +1 -1
  188. package/dist/esm/rate-limit/keys.js +43 -0
  189. package/dist/esm/rate-limit/keys.js.map +1 -0
  190. package/dist/esm/socket/index.js +112 -17
  191. package/dist/esm/socket/index.js.map +1 -1
  192. package/dist/esm/socket/ticket.js +117 -0
  193. package/dist/esm/socket/ticket.js.map +1 -0
  194. package/dist/esm/testing/index.js +24 -15
  195. package/dist/esm/testing/index.js.map +1 -1
  196. package/dist/types/access/index.d.ts +9 -3
  197. package/dist/types/access/index.d.ts.map +1 -1
  198. package/dist/types/approvals/index.d.ts +99 -0
  199. package/dist/types/approvals/index.d.ts.map +1 -0
  200. package/dist/types/audit/index.d.ts +82 -0
  201. package/dist/types/audit/index.d.ts.map +1 -0
  202. package/dist/types/cache/index.d.ts +64 -14
  203. package/dist/types/cache/index.d.ts.map +1 -1
  204. package/dist/types/cache/keyspace.d.ts +84 -0
  205. package/dist/types/cache/keyspace.d.ts.map +1 -0
  206. package/dist/types/common/circuit-breaker.d.ts +51 -0
  207. package/dist/types/common/circuit-breaker.d.ts.map +1 -0
  208. package/dist/types/common/index.d.ts +4 -0
  209. package/dist/types/common/index.d.ts.map +1 -1
  210. package/dist/types/common/internal-client.d.ts +116 -0
  211. package/dist/types/common/internal-client.d.ts.map +1 -0
  212. package/dist/types/common/internal-constants.d.ts +9 -0
  213. package/dist/types/common/internal-constants.d.ts.map +1 -0
  214. package/dist/types/common/internal-service-auth.d.ts +102 -0
  215. package/dist/types/common/internal-service-auth.d.ts.map +1 -0
  216. package/dist/types/common/money.d.ts +104 -0
  217. package/dist/types/common/money.d.ts.map +1 -0
  218. package/dist/types/common/phone.d.ts +25 -15
  219. package/dist/types/common/phone.d.ts.map +1 -1
  220. package/dist/types/common/time.d.ts +20 -2
  221. package/dist/types/common/time.d.ts.map +1 -1
  222. package/dist/types/config/index.d.ts +251 -27
  223. package/dist/types/config/index.d.ts.map +1 -1
  224. package/dist/types/contracts/notification-dispatch.d.ts +13 -13
  225. package/dist/types/database/db.d.ts +46 -0
  226. package/dist/types/database/db.d.ts.map +1 -0
  227. package/dist/types/database/index.d.ts +9 -22
  228. package/dist/types/database/index.d.ts.map +1 -1
  229. package/dist/types/database/pagination.d.ts +68 -0
  230. package/dist/types/database/pagination.d.ts.map +1 -0
  231. package/dist/types/database/pool.d.ts +39 -0
  232. package/dist/types/database/pool.d.ts.map +1 -0
  233. package/dist/types/database/scoped-repository.d.ts +92 -0
  234. package/dist/types/database/scoped-repository.d.ts.map +1 -0
  235. package/dist/types/database/scopes.d.ts +159 -0
  236. package/dist/types/database/scopes.d.ts.map +1 -0
  237. package/dist/types/database/table-names.d.ts +13 -0
  238. package/dist/types/database/table-names.d.ts.map +1 -0
  239. package/dist/types/database/tables.d.ts +748 -0
  240. package/dist/types/database/tables.d.ts.map +1 -0
  241. package/dist/types/express/auth.d.ts +40 -24
  242. package/dist/types/express/auth.d.ts.map +1 -1
  243. package/dist/types/express/index.d.ts +2 -0
  244. package/dist/types/express/index.d.ts.map +1 -1
  245. package/dist/types/express/internal-caller.d.ts +53 -0
  246. package/dist/types/express/internal-caller.d.ts.map +1 -0
  247. package/dist/types/express/step-up.d.ts +38 -0
  248. package/dist/types/express/step-up.d.ts.map +1 -0
  249. package/dist/types/http/index.d.ts +3 -31
  250. package/dist/types/http/index.d.ts.map +1 -1
  251. package/dist/types/identity/index.d.ts +159 -21
  252. package/dist/types/identity/index.d.ts.map +1 -1
  253. package/dist/types/identity/step-up.d.ts +41 -0
  254. package/dist/types/identity/step-up.d.ts.map +1 -0
  255. package/dist/types/index.d.ts +6 -0
  256. package/dist/types/index.d.ts.map +1 -1
  257. package/dist/types/knex/columns.d.ts +15 -0
  258. package/dist/types/knex/columns.d.ts.map +1 -0
  259. package/dist/types/knex/index.d.ts +3 -11
  260. package/dist/types/knex/index.d.ts.map +1 -1
  261. package/dist/types/knex/migration-helpers.d.ts +28 -0
  262. package/dist/types/knex/migration-helpers.d.ts.map +1 -0
  263. package/dist/types/logging/redact.d.ts +2 -2
  264. package/dist/types/logging/redact.d.ts.map +1 -1
  265. package/dist/types/messaging/base-consumer.d.ts +33 -0
  266. package/dist/types/messaging/base-consumer.d.ts.map +1 -0
  267. package/dist/types/messaging/consumer.d.ts +59 -21
  268. package/dist/types/messaging/consumer.d.ts.map +1 -1
  269. package/dist/types/messaging/envelope.d.ts +50 -11
  270. package/dist/types/messaging/envelope.d.ts.map +1 -1
  271. package/dist/types/messaging/idempotency.d.ts +60 -3
  272. package/dist/types/messaging/idempotency.d.ts.map +1 -1
  273. package/dist/types/messaging/index.d.ts +7 -0
  274. package/dist/types/messaging/index.d.ts.map +1 -1
  275. package/dist/types/messaging/outbox.d.ts +62 -22
  276. package/dist/types/messaging/outbox.d.ts.map +1 -1
  277. package/dist/types/messaging/publisher.d.ts +54 -21
  278. package/dist/types/messaging/publisher.d.ts.map +1 -1
  279. package/dist/types/messaging/staff-notifier.d.ts +9 -9
  280. package/dist/types/messaging/staff-notifier.d.ts.map +1 -1
  281. package/dist/types/messaging/staff-recipients.d.ts +1 -1
  282. package/dist/types/messaging/topology.d.ts +40 -8
  283. package/dist/types/messaging/topology.d.ts.map +1 -1
  284. package/dist/types/rate-limit/index.d.ts +40 -4
  285. package/dist/types/rate-limit/index.d.ts.map +1 -1
  286. package/dist/types/rate-limit/keys.d.ts +52 -0
  287. package/dist/types/rate-limit/keys.d.ts.map +1 -0
  288. package/dist/types/socket/index.d.ts +86 -2
  289. package/dist/types/socket/index.d.ts.map +1 -1
  290. package/dist/types/socket/ticket.d.ts +90 -0
  291. package/dist/types/socket/ticket.d.ts.map +1 -0
  292. package/dist/types/testing/index.d.ts +31 -10
  293. package/dist/types/testing/index.d.ts.map +1 -1
  294. package/dist/types/types/index.d.ts +26 -14
  295. package/dist/types/types/index.d.ts.map +1 -1
  296. package/package.json +3 -1
@@ -1,32 +1,89 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MemoryIdempotencyStore = exports.RedisIdempotencyStore = void 0;
3
+ exports.StoreDeduper = exports.MemoryIdempotencyStore = exports.RedisIdempotencyStore = exports.ProcessedEventsDeduper = void 0;
4
+ exports.isDuplicateKeyError = isDuplicateKeyError;
5
+ const errors_js_1 = require("../common/errors.js");
6
+ const id_js_1 = require("../common/id.js");
7
+ const tables_js_1 = require("../database/tables.js");
8
+ /** Thrown inside the ledger transaction to roll back when the event is already recorded. */
9
+ class AlreadyProcessed extends Error {
10
+ }
11
+ const DUPLICATE_KEY = 'ER_DUP_ENTRY';
12
+ /** True for a MySQL duplicate-key error, also when Drizzle wraps it in `cause`. */
13
+ function isDuplicateKeyError(error) {
14
+ for (let e = error, depth = 0; e && depth < 5; e = e.cause, depth += 1) {
15
+ if (e.code === DUPLICATE_KEY || e.errno === 1062)
16
+ return true;
17
+ }
18
+ return false;
19
+ }
20
+ /**
21
+ * Database ledger (`processed_events`, unique `(consumer, event_id)`): the ledger row and the handler's
22
+ * writes commit in one transaction, so an event is recorded as processed if and only if its effects
23
+ * commit. A concurrent duplicate blocks on the unique key until the first commits, then is skipped.
24
+ *
25
+ * @example const dedupe = new ProcessedEventsDeduper(db); // handler gets `tx` and writes through it
26
+ */
27
+ class ProcessedEventsDeduper {
28
+ db;
29
+ table;
30
+ constructor(db, table = tables_js_1.processedEvents) {
31
+ this.db = db;
32
+ this.table = table;
33
+ }
34
+ async run(consumer, eventId, work) {
35
+ try {
36
+ await this.db.transaction(async (tx) => {
37
+ try {
38
+ await tx.insert(this.table).values({ id: (0, id_js_1.newId)(), consumer, eventId });
39
+ }
40
+ catch (error) {
41
+ if (isDuplicateKeyError(error))
42
+ throw new AlreadyProcessed();
43
+ throw error;
44
+ }
45
+ await work(tx);
46
+ });
47
+ return 'processed';
48
+ }
49
+ catch (error) {
50
+ if (error instanceof AlreadyProcessed)
51
+ return 'duplicate';
52
+ throw error;
53
+ }
54
+ }
55
+ }
56
+ exports.ProcessedEventsDeduper = ProcessedEventsDeduper;
4
57
  const PROCESSING = 'processing';
5
58
  const DONE = 'done';
59
+ /** Redis claim store; every key comes from the service keyspace (`{prefix}idem:{key}`). */
6
60
  class RedisIdempotencyStore {
7
61
  redis;
8
- prefix;
9
- constructor(redis, prefix = 'idem:') {
62
+ keyspace;
63
+ constructor(redis, keyspace) {
10
64
  this.redis = redis;
11
- this.prefix = prefix;
65
+ this.keyspace = keyspace;
66
+ }
67
+ key(key) {
68
+ return this.keyspace.key('idem', key);
12
69
  }
13
70
  async begin(key, leaseSec) {
14
- const k = this.prefix + key;
71
+ const k = this.key(key);
15
72
  const set = await this.redis.set(k, PROCESSING, 'EX', leaseSec, 'NX');
16
73
  if (set === 'OK')
17
74
  return 'claimed';
18
75
  return (await this.redis.get(k)) === DONE ? 'done' : 'in-flight';
19
76
  }
20
77
  async complete(key, ttlSec) {
21
- await this.redis.set(this.prefix + key, DONE, 'EX', ttlSec);
78
+ await this.redis.set(this.key(key), DONE, 'EX', ttlSec);
22
79
  }
23
80
  async abandon(key) {
24
- const k = this.prefix + key;
25
81
  // Only release our own lease — never erase a completed marker.
26
- await this.redis.eval("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) end return 0", 1, k, PROCESSING);
82
+ await this.redis.eval("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) end return 0", 1, this.key(key), PROCESSING);
27
83
  }
28
84
  }
29
85
  exports.RedisIdempotencyStore = RedisIdempotencyStore;
86
+ /** In-process claim store for tests and single-process tools. */
30
87
  class MemoryIdempotencyStore {
31
88
  entries = new Map();
32
89
  read(key) {
@@ -54,4 +111,34 @@ class MemoryIdempotencyStore {
54
111
  }
55
112
  }
56
113
  exports.MemoryIdempotencyStore = MemoryIdempotencyStore;
114
+ /**
115
+ * {@link EventDeduper} over an {@link IdempotencyStore}. A duplicate still in flight elsewhere throws a
116
+ * retryable 503, so the consumer retries it later instead of acking it unprocessed.
117
+ */
118
+ class StoreDeduper {
119
+ store;
120
+ options;
121
+ constructor(store, options = {}) {
122
+ this.store = store;
123
+ this.options = options;
124
+ }
125
+ async run(consumer, eventId, work) {
126
+ const key = `${consumer}:${eventId}`;
127
+ const claim = await this.store.begin(key, this.options.leaseSec ?? 300);
128
+ if (claim === 'done')
129
+ return 'duplicate';
130
+ if (claim === 'in-flight')
131
+ throw new errors_js_1.ServiceUnavailableError('Duplicate message in flight', { code: 'DUPLICATE_IN_FLIGHT' });
132
+ try {
133
+ await work(undefined);
134
+ }
135
+ catch (error) {
136
+ await this.store.abandon(key).catch(() => undefined);
137
+ throw error;
138
+ }
139
+ await this.store.complete(key, this.options.ttlSec ?? 7 * 86_400).catch(() => undefined);
140
+ return 'processed';
141
+ }
142
+ }
143
+ exports.StoreDeduper = StoreDeduper;
57
144
  //# sourceMappingURL=idempotency.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../../src/messaging/idempotency.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../../src/messaging/idempotency.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,7 @@ __exportStar(require("./topology.js"), exports);
27
27
  __exportStar(require("./envelope.js"), exports);
28
28
  __exportStar(require("./publisher.js"), exports);
29
29
  __exportStar(require("./consumer.js"), exports);
30
+ __exportStar(require("./base-consumer.js"), exports);
30
31
  __exportStar(require("./idempotency.js"), exports);
31
32
  __exportStar(require("./rpc.js"), exports);
32
33
  __exportStar(require("./outbox.js"), exports);
@@ -39,7 +40,7 @@ function createMessaging(options) {
39
40
  const topology = (0, topology_js_1.createTopology)({ app: options.app, prefix: options.prefix });
40
41
  const connection = new connection_js_1.AmqpConnection({ url: options.url, connectionName: `${options.service}@${options.prefix}`, logger });
41
42
  const publisher = new publisher_js_1.MessagePublisher({ connection, topology, source: options.service, logger });
42
- const consumer = new consumer_js_1.MessageConsumer(connection, topology, logger);
43
+ const consumer = new consumer_js_1.MessageConsumer(connection, topology, options.service, logger);
43
44
  const rpcServer = new rpc_js_1.RpcServer(connection, topology, logger);
44
45
  const rpcClient = new rpc_js_1.RpcClient(connection, topology, options.service, logger);
45
46
  const monitor = new monitor_js_1.QueueMonitor(connection, () => consumer.deadLetterQueues, logger);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/messaging/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AA2CA,0CA0BC;AArED,kDAAmD;AAEnD,mDAAiD;AACjD,+CAAgD;AAChD,6CAA4C;AAC5C,iDAAkD;AAClD,qCAAgD;AAChD,+CAA8D;AAE9D,kDAAgC;AAChC,gDAA8B;AAC9B,gDAA8B;AAC9B,iDAA+B;AAC/B,gDAA8B;AAC9B,mDAAiC;AACjC,2CAAyB;AACzB,8CAA4B;AAC5B,+CAA6B;AAC7B,sDAAoC;AACpC,wDAAsC;AAuBtC,0FAA0F;AAC1F,SAAgB,eAAe,CAAC,OAAyB;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,uBAAY,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAA,4BAAc,EAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,MAAM,UAAU,GAAG,IAAI,8BAAc,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5H,MAAM,SAAS,GAAG,IAAI,+BAAgB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAClG,MAAM,QAAQ,GAAG,IAAI,6BAAe,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,IAAI,kBAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,kBAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC/E,MAAM,OAAO,GAAG,IAAI,yBAAY,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAEtF,OAAO;QACL,QAAQ;QACR,UAAU;QACV,SAAS;QACT,QAAQ;QACR,SAAS;QACT,SAAS;QACT,OAAO;QACP,KAAK,CAAC,KAAK;YACT,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC;YACvB,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC;YACvB,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE;KAChC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/messaging/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAkDA,0CA0BC;AA5ED,kDAAmD;AAEnD,mDAAiD;AACjD,+CAAgD;AAChD,6CAA4C;AAC5C,iDAAkD;AAClD,qCAAgD;AAChD,+CAA8D;AAE9D,kDAAgC;AAChC,gDAA8B;AAC9B,gDAA8B;AAC9B,iDAA+B;AAC/B,gDAA8B;AAC9B,qDAAmC;AACnC,mDAAiC;AACjC,2CAAyB;AACzB,8CAA4B;AAC5B,+CAA6B;AAC7B,sDAAoC;AACpC,wDAAsC;AA6BtC,0FAA0F;AAC1F,SAAgB,eAAe,CAAC,OAAyB;IACvD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,uBAAY,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAA,4BAAc,EAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9E,MAAM,UAAU,GAAG,IAAI,8BAAc,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5H,MAAM,SAAS,GAAG,IAAI,+BAAgB,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAClG,MAAM,QAAQ,GAAG,IAAI,6BAAe,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACpF,MAAM,SAAS,GAAG,IAAI,kBAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,kBAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC/E,MAAM,OAAO,GAAG,IAAI,yBAAY,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAEtF,OAAO;QACL,QAAQ;QACR,UAAU;QACV,SAAS;QACT,QAAQ;QACR,SAAS;QACT,SAAS;QACT,OAAO;QACP,KAAK,CAAC,KAAK;YACT,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC;YACvB,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC;YACvB,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;QAC7B,CAAC;QACD,KAAK,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE;KAChC,CAAC;AACJ,CAAC"}
@@ -1,56 +1,78 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OutboxRelay = exports.DEFAULT_OUTBOX_TABLE = void 0;
4
- exports.outboxRoutingKey = outboxRoutingKey;
5
4
  exports.enqueueOutbox = enqueueOutbox;
5
+ const drizzle_orm_1 = require("drizzle-orm");
6
+ const table_names_js_1 = require("../database/table-names.js");
7
+ Object.defineProperty(exports, "DEFAULT_OUTBOX_TABLE", { enumerable: true, get: function () { return table_names_js_1.DEFAULT_OUTBOX_TABLE; } });
8
+ const tables_js_1 = require("../database/tables.js");
6
9
  const index_js_1 = require("../logging/index.js");
10
+ const envelope_js_1 = require("./envelope.js");
7
11
  const publisher_js_1 = require("./publisher.js");
8
- exports.DEFAULT_OUTBOX_TABLE = 'event_outbox';
9
- const COMMAND_PREFIX = 'command:';
10
- /** Outbox `routing_key` column value: the event type, or `command:<name>` for commands. */
11
- function outboxRoutingKey(envelope, kind = 'event') {
12
- return kind === 'command' ? `${COMMAND_PREFIX}${envelope.type}` : envelope.type;
13
- }
14
12
  /**
15
- * Transactional outbox: write the domain change and the event in one DB transaction, then a
16
- * relay publishes pending rows with broker confirms. Closes the "row committed, event lost"
17
- * gap of publish-after-commit. Consumers must be idempotent (they are: see IdempotencyStore).
13
+ * Transactional outbox: stages a message in the caller's transaction, so it is published if and only if
14
+ * the domain change commits. The {@link OutboxRelay} publishes it later with broker confirms. The
15
+ * correlation id (explicit, else the request context's) is stored with the row and sent as a header.
18
16
  *
19
- * Table (see `createOutboxTable` in `@heavybit/shared-lib/knex`):
20
- * id CHAR(26) PK = eventId, routing_key, payload JSON, attempts, last_error, created_at, published_at
17
+ * @param tx The caller's open Drizzle transaction (or db handle for a single-statement change).
18
+ * @param routingKey Event or command name.
19
+ * @param data Payload fields.
20
+ * @param options Exchange, envelope meta and table.
21
+ * @returns The staged envelope.
22
+ * @example await db.transaction(async (tx) => { await repo.insert(ctx, row, { tx }); await enqueueOutbox(tx, 'thing.created', { thingId: row.id }); });
21
23
  */
22
- async function enqueueOutbox(db, envelope, options = {}) {
23
- await db(options.table ?? exports.DEFAULT_OUTBOX_TABLE).insert({
24
- id: envelope.eventId,
25
- routing_key: outboxRoutingKey(envelope, options.kind),
26
- payload: JSON.stringify(envelope),
24
+ async function enqueueOutbox(tx, routingKey, data, options = {}) {
25
+ const table = options.table ?? tables_js_1.outbox;
26
+ const envelope = (0, envelope_js_1.buildEnvelope)(routingKey, data, options);
27
+ await tx.insert(table).values({
28
+ id: envelope.data.eventId,
29
+ routingKey,
30
+ exchange: options.exchange ?? 'events',
31
+ payload: envelope,
32
+ correlationId: (0, envelope_js_1.outgoingCorrelationId)(options) ?? null,
27
33
  attempts: 0,
28
- created_at: new Date(),
29
34
  });
35
+ return envelope;
30
36
  }
37
+ /**
38
+ * Publishes pending outbox rows in id (= time) order. Each batch locks its rows with
39
+ * `FOR UPDATE SKIP LOCKED`, so several replicas can relay at once without double-publishing, and marks a
40
+ * row published only after the broker confirms it. Consumers still dedupe: a crash between confirm and
41
+ * commit re-sends the row.
42
+ *
43
+ * Failures: an unroutable command stays pending and the batch continues; a broker error stops the batch
44
+ * (keeping order) and the poll interval backs off exponentially until a batch succeeds.
45
+ */
31
46
  class OutboxRelay {
32
47
  options;
33
48
  timer = null;
34
49
  running = null;
35
50
  rerun = false;
51
+ failures = 0;
36
52
  logger;
37
53
  table;
38
54
  constructor(options) {
39
55
  this.options = options;
40
56
  this.logger = options.logger ?? index_js_1.silentLogger;
41
- this.table = options.table ?? exports.DEFAULT_OUTBOX_TABLE;
57
+ this.table = options.table ?? tables_js_1.outbox;
42
58
  }
59
+ /** Starts polling. */
43
60
  start() {
44
61
  if (this.timer)
45
62
  return;
46
- this.timer = setInterval(() => { void this.trigger(); }, this.options.intervalMs ?? 2000);
47
- void this.trigger();
63
+ this.schedule(0);
48
64
  }
65
+ /** Stops polling (an in-flight batch finishes). */
49
66
  stop() {
50
67
  if (this.timer)
51
- clearInterval(this.timer);
68
+ clearTimeout(this.timer);
52
69
  this.timer = null;
53
70
  }
71
+ /** Delay before the next poll: the interval, doubled per consecutive failed batch, capped. */
72
+ nextDelayMs() {
73
+ const interval = this.options.intervalMs ?? 2000;
74
+ return Math.min(this.options.maxBackoffMs ?? 60_000, interval * 2 ** this.failures);
75
+ }
54
76
  /** Publishes pending rows now. Concurrent triggers coalesce into one follow-up run. */
55
77
  async trigger() {
56
78
  if (this.running) {
@@ -68,68 +90,88 @@ class OutboxRelay {
68
90
  }
69
91
  } while (this.rerun);
70
92
  }
71
- /** One batch. Returns how many rows were published. */
93
+ /** One batch. */
72
94
  async flush() {
73
- const { knex, bus } = this.options;
74
- let published = 0;
95
+ const metrics = { published: 0, failed: 0, lagMs: 0 };
96
+ let brokerDown = false;
75
97
  try {
76
- await knex.transaction(async (trx) => {
77
- // SKIP LOCKED lets several replicas relay concurrently without double-publishing.
78
- const rows = await trx(this.table)
79
- .select('id', 'routing_key', 'payload', 'attempts')
80
- .whereNull('published_at')
81
- .orderBy('id', 'asc')
82
- .limit(this.options.batchSize ?? 100)
83
- .forUpdate()
84
- .skipLocked();
98
+ await this.options.db.transaction(async (tx) => {
99
+ const rows = await this.lockPending(tx);
100
+ if (rows[0])
101
+ metrics.lagMs = Math.max(0, Date.now() - new Date(rows[0].createdAt).getTime());
85
102
  for (const row of rows) {
86
- const envelope = (typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload);
87
103
  try {
88
- if (row.routing_key.startsWith(COMMAND_PREFIX))
89
- await bus.sendEnvelope(envelope);
90
- else
91
- await bus.publishEnvelope(envelope);
92
- await trx(this.table).where({ id: row.id }).update({ published_at: new Date(), last_error: null });
93
- published += 1;
104
+ await this.publish(row);
105
+ await tx.update(this.table).set({ publishedAt: new Date(), lastError: null }).where((0, drizzle_orm_1.eq)(this.table.id, row.id));
106
+ metrics.published += 1;
94
107
  }
95
108
  catch (error) {
96
- await trx(this.table).where({ id: row.id }).update({ attempts: knex.raw('attempts + 1'), last_error: String(error.message).slice(0, 500) });
97
- // Unroutable: only this command's owner is missing. Keep the row for the next tick and carry on,
98
- // so one undeclared queue never holds back every other event behind it.
109
+ metrics.failed += 1;
110
+ await tx.update(this.table)
111
+ .set({ attempts: (0, drizzle_orm_1.sql) `${this.table.attempts} + 1`, lastError: String(error?.message ?? error).slice(0, 500) })
112
+ .where((0, drizzle_orm_1.eq)(this.table.id, row.id));
113
+ // Unroutable: only this command's owner is missing. Keep the row and carry on, so one
114
+ // undeclared queue never holds back every other message behind it.
99
115
  if ((0, publisher_js_1.isUnroutableError)(error)) {
100
116
  this.reportUnroutable(row, error);
101
117
  continue;
102
118
  }
103
- // Broker down: stop this batch; the next tick retries in order.
119
+ // Broker down: stop this batch; the next poll retries in order.
120
+ brokerDown = true;
104
121
  break;
105
122
  }
106
123
  }
107
124
  });
108
125
  }
109
126
  catch (error) {
127
+ brokerDown = true;
110
128
  this.logger.error('Outbox relay batch failed', { error: error.message });
111
129
  }
112
- if (published)
113
- this.logger.debug('Outbox relayed', { published });
114
- return published;
130
+ this.failures = brokerDown ? Math.min(this.failures + 1, 16) : 0;
131
+ if (metrics.published)
132
+ this.logger.debug('Outbox relayed', { published: metrics.published });
133
+ this.options.onMetrics?.(metrics);
134
+ return metrics;
135
+ }
136
+ /** Deletes published rows older than `olderThanDays`; returns how many were removed. */
137
+ async prune(olderThanDays = 7) {
138
+ const cutoff = new Date(Date.now() - olderThanDays * 86_400_000);
139
+ const [result] = await this.options.db.delete(this.table)
140
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.isNotNull)(this.table.publishedAt), (0, drizzle_orm_1.lt)(this.table.publishedAt, cutoff)));
141
+ return result?.affectedRows ?? 0;
142
+ }
143
+ lockPending(tx) {
144
+ return tx.select().from(this.table)
145
+ .where((0, drizzle_orm_1.isNull)(this.table.publishedAt))
146
+ .orderBy((0, drizzle_orm_1.asc)(this.table.id))
147
+ .limit(this.options.batchSize ?? 100)
148
+ .for('update', { skipLocked: true });
149
+ }
150
+ publish(row) {
151
+ const envelope = (typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload);
152
+ const options = row.correlationId ? { correlationId: row.correlationId } : {};
153
+ return row.exchange === 'commands'
154
+ ? this.options.bus.sendEnvelope(envelope, options)
155
+ : this.options.bus.publishEnvelope(envelope, options);
156
+ }
157
+ schedule(delayMs) {
158
+ this.timer = setTimeout(() => {
159
+ void this.trigger().finally(() => { if (this.timer)
160
+ this.schedule(this.nextDelayMs()); });
161
+ }, delayMs);
115
162
  }
116
163
  /**
117
164
  * Logged on the 1st, 2nd, 4th, 8th… failure of a row (the attempt count before this one is in `attempts`),
118
- * so a queue that stays missing for hours is still visible without a line every tick.
165
+ * so a queue that stays missing for hours is still visible without a line every poll.
119
166
  */
120
167
  reportUnroutable(row, error) {
121
168
  const attempt = Number(row.attempts ?? 0) + 1;
122
169
  if ((attempt & (attempt - 1)) !== 0)
123
170
  return;
124
171
  this.logger.error('Outbox command is unroutable: its consumer queue is not declared; will retry', {
125
- eventId: row.id, routingKey: row.routing_key, target: error.routingKey, attempt,
172
+ eventId: row.id, routingKey: row.routingKey, target: error.routingKey, attempt,
126
173
  });
127
174
  }
128
- /** Deletes published rows older than `olderThanDays`. */
129
- async prune(olderThanDays = 7) {
130
- const cutoff = new Date(Date.now() - olderThanDays * 86_400_000);
131
- return this.options.knex(this.table).whereNotNull('published_at').andWhere('published_at', '<', cutoff).delete();
132
- }
133
175
  }
134
176
  exports.OutboxRelay = OutboxRelay;
135
177
  //# sourceMappingURL=outbox.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../../src/messaging/outbox.ts"],"names":[],"mappings":";;;AAYA,4CAEC;AAUD,sCAQC;AA/BD,kDAAmD;AAEnD,iDAAgH;AAEnG,QAAA,oBAAoB,GAAG,cAAc,CAAC;AAInD,MAAM,cAAc,GAAG,UAAU,CAAC;AAElC,2FAA2F;AAC3F,SAAgB,gBAAgB,CAAC,QAAqC,EAAE,OAAmB,OAAO;IAChG,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,cAAc,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;AAClF,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,aAAa,CAAC,EAA2B,EAAE,QAAuB,EAAE,UAAiD,EAAE;IAC3I,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,4BAAoB,CAAC,CAAC,MAAM,CAAC;QACrD,EAAE,EAAE,QAAQ,CAAC,OAAO;QACpB,WAAW,EAAE,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC;QACrD,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;QACjC,QAAQ,EAAE,CAAC;QACX,UAAU,EAAE,IAAI,IAAI,EAAE;KACvB,CAAC,CAAC;AACL,CAAC;AAYD,MAAa,WAAW;IAOO;IANrB,KAAK,GAA0C,IAAI,CAAC;IACpD,OAAO,GAA2B,IAAI,CAAC;IACvC,KAAK,GAAG,KAAK,CAAC;IACL,MAAM,CAAS;IACf,KAAK,CAAS;IAE/B,YAA6B,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;QACtD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,uBAAY,CAAC;QAC7C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,4BAAoB,CAAC;IACrD,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC;QAC1F,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK;YAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,uFAAuF;IACvF,KAAK,CAAC,OAAO;QACX,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,OAAO;QACT,CAAC;QACD,GAAG,CAAC;YACF,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,OAAO,CAAC;YACrB,CAAC;oBAAS,CAAC;gBACT,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;YACtB,CAAC;QACH,CAAC,QAAQ,IAAI,CAAC,KAAK,EAAE;IACvB,CAAC;IAED,uDAAuD;IACvD,KAAK,CAAC,KAAK;QACT,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QACnC,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBACnC,kFAAkF;gBAClF,MAAM,IAAI,GAA2F,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;qBACvH,MAAM,CAAC,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,CAAC;qBAClD,SAAS,CAAC,cAAc,CAAC;qBACzB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;qBACpB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,GAAG,CAAC;qBACpC,SAAS,EAAE;qBACX,UAAU,EAAE,CAAC;gBAChB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;oBACvB,MAAM,QAAQ,GAAG,CAAC,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAkB,CAAC;oBAC5G,IAAI,CAAC;wBACH,IAAI,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,cAAc,CAAC;4BAAE,MAAM,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;;4BAC5E,MAAM,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;wBACzC,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,IAAI,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;wBACnG,SAAS,IAAI,CAAC,CAAC;oBACjB,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,UAAU,EAAE,MAAM,CAAE,KAAe,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;wBACvJ,iGAAiG;wBACjG,wEAAwE;wBACxE,IAAI,IAAA,gCAAiB,EAAC,KAAK,CAAC,EAAE,CAAC;4BAC7B,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;4BAClC,SAAS;wBACX,CAAC;wBACD,gEAAgE;wBAChE,MAAM;oBACR,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;QACtF,CAAC;QACD,IAAI,SAAS;YAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAClE,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;OAGG;IACK,gBAAgB,CAAC,GAA2D,EAAE,KAA6B;QACjH,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;YAAE,OAAO;QAC5C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,8EAA8E,EAAE;YAChG,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,OAAO;SAChF,CAAC,CAAC;IACL,CAAC;IAED,yDAAyD;IACzD,KAAK,CAAC,KAAK,CAAC,aAAa,GAAG,CAAC;QAC3B,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,GAAG,UAAU,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;IACnH,CAAC;CACF;AAlGD,kCAkGC"}
1
+ {"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../../src/messaging/outbox.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.NoopMessageBus = exports.MessagePublisher = exports.UnroutableMessageError = void 0;
3
+ exports.NoopMessageBus = exports.MessagePublisher = exports.MAX_DELAY_MS = exports.UnroutableMessageError = void 0;
4
4
  exports.isUnroutableError = isUnroutableError;
5
5
  const errors_js_1 = require("../common/errors.js");
6
6
  const id_js_1 = require("../common/id.js");
7
7
  const index_js_1 = require("../logging/index.js");
8
8
  const envelope_js_1 = require("./envelope.js");
9
+ const topology_js_1 = require("./topology.js");
9
10
  /**
10
11
  * The broker accepted a `mandatory` message but no queue was bound to receive it (the consumer has never
11
12
  * started, or its binding is wrong). Retryable: the message is not lost — the caller (or the outbox relay)
@@ -21,16 +22,20 @@ class UnroutableMessageError extends errors_js_1.ServiceUnavailableError {
21
22
  }
22
23
  }
23
24
  exports.UnroutableMessageError = UnroutableMessageError;
25
+ /** True when `error` is an {@link UnroutableMessageError} (retryable; the target queue is missing). */
24
26
  function isUnroutableError(error) {
25
27
  return error instanceof UnroutableMessageError;
26
28
  }
27
- const envelopeProps = (envelope, mandatory) => ({
28
- mandatory, messageId: envelope.eventId, correlationId: envelope.correlationId, type: envelope.type,
29
+ /** Largest delay `scheduleDelayed` accepts (AMQP per-message TTL is a 32-bit value). */
30
+ exports.MAX_DELAY_MS = 2 ** 31 - 1;
31
+ const envelopeProps = (envelope, mandatory, options) => ({
32
+ mandatory, messageId: envelope.data.eventId, correlationId: (0, envelope_js_1.outgoingCorrelationId)(options), type: envelope.routingKey,
29
33
  });
30
34
  /**
31
35
  * Event and command publisher on a confirm channel: `publish()` resolves only once the broker
32
36
  * has persisted the message, and rejects with 503 when the broker is unreachable so callers
33
- * (or the outbox relay) can retry.
37
+ * (or the outbox relay) can retry. Every message is persistent JSON with `messageId` = event id and the
38
+ * correlation id in the `x-correlation-id` header.
34
39
  *
35
40
  * Commands and direct queue sends are `mandatory`: a command has exactly one owner, so one that no
36
41
  * queue receives is a fault, not a no-op. The broker returns it (`basic.return`, always before its
@@ -43,6 +48,8 @@ class MessagePublisher {
43
48
  logger;
44
49
  /** Message ids the broker returned as unroutable, awaiting their confirm. */
45
50
  returned = new Set();
51
+ /** Delay queues already declared, per live channel (a reconnect gets a new channel). */
52
+ declaredDelayQueues = new WeakMap();
46
53
  constructor(options) {
47
54
  this.options = options;
48
55
  this.logger = options.logger ?? index_js_1.silentLogger;
@@ -59,46 +66,86 @@ class MessagePublisher {
59
66
  });
60
67
  }, { confirm: true });
61
68
  }
62
- async publish(type, data, meta = {}) {
63
- const envelope = (0, envelope_js_1.buildEnvelope)(type, data, this.options.source, meta);
64
- await this.publishEnvelope(envelope);
69
+ async publish(routingKey, data, meta = {}) {
70
+ const envelope = (0, envelope_js_1.buildEnvelope)(routingKey, data, meta);
71
+ await this.publishEnvelope(envelope, meta);
65
72
  return envelope;
66
73
  }
67
- async publishEnvelope(envelope) {
68
- await this.write(this.options.topology.eventsExchange, envelope.type, envelope, envelopeProps(envelope, false));
74
+ async publishEnvelope(envelope, options) {
75
+ await this.write(this.options.topology.eventsExchange, envelope.routingKey, envelope, envelopeProps(envelope, false, options));
69
76
  }
70
77
  async send(command, data, meta = {}) {
71
- const envelope = (0, envelope_js_1.buildEnvelope)(command, data, this.options.source, meta);
72
- await this.sendEnvelope(envelope);
78
+ const envelope = (0, envelope_js_1.buildEnvelope)(command, data, meta);
79
+ await this.sendEnvelope(envelope, meta);
73
80
  return envelope;
74
81
  }
75
- async sendEnvelope(envelope) {
76
- await this.write(this.options.topology.commandsExchange, this.options.topology.queue(envelope.type), envelope, envelopeProps(envelope, true));
82
+ async sendEnvelope(envelope, options) {
83
+ const { topology } = this.options;
84
+ await this.write(topology.commandsExchange, topology.queue(envelope.routingKey), envelope, envelopeProps(envelope, true, options));
77
85
  }
78
86
  /**
79
87
  * Sends `payload` as-is (no envelope) to `queue` through the default exchange. The queue is the
80
88
  * consumer's to declare, so it is not asserted here; a missing queue rejects as unroutable.
81
89
  */
82
90
  async sendToQueue(queue, payload, options = {}) {
83
- await this.write('', queue, payload, { mandatory: true, messageId: options.messageId ?? (0, id_js_1.newId)(), correlationId: options.correlationId });
91
+ await this.write('', queue, payload, { mandatory: true, messageId: options.messageId ?? (0, id_js_1.newId)(), correlationId: (0, envelope_js_1.outgoingCorrelationId)(options) });
84
92
  }
85
- write(exchange, routingKey, body, props) {
93
+ /**
94
+ * Parks `payload` in a delay queue with a per-message TTL; when it expires the broker dead-letters it
95
+ * into `queue` (MSG-4). Delay queues are bucketed by power of two (see `delayQueueName`). The target
96
+ * queue must exist when the TTL fires, or the broker drops the message.
97
+ *
98
+ * @throws RangeError when `delayMs` is not an integer in 1 … {@link MAX_DELAY_MS}.
99
+ */
100
+ async scheduleDelayed(queue, payload, delayMs, options = {}) {
101
+ if (!Number.isInteger(delayMs) || delayMs < 1 || delayMs > exports.MAX_DELAY_MS) {
102
+ throw new RangeError('delayMs must be an integer between 1 and 2^31-1');
103
+ }
104
+ const delayQueue = await this.declareDelayQueue(queue, delayMs);
105
+ await this.write('', delayQueue, payload, {
106
+ mandatory: true, messageId: options.messageId ?? (0, id_js_1.newId)(), correlationId: (0, envelope_js_1.outgoingCorrelationId)(options), expiration: String(delayMs),
107
+ });
108
+ }
109
+ async declareDelayQueue(queue, delayMs) {
110
+ const channel = this.channel();
111
+ const name = (0, topology_js_1.delayQueueName)(queue, delayMs);
112
+ const declared = this.declaredDelayQueues.get(channel) ?? new Set();
113
+ if (!declared.has(name)) {
114
+ await channel.assertQueue(name, { durable: true, arguments: { 'x-dead-letter-exchange': '', 'x-dead-letter-routing-key': queue } });
115
+ declared.add(name);
116
+ this.declaredDelayQueues.set(channel, declared);
117
+ }
118
+ return name;
119
+ }
120
+ channel() {
86
121
  const channel = this.handle?.current();
87
- if (!channel) {
88
- return Promise.reject(new errors_js_1.ServiceUnavailableError('Message broker unavailable'));
122
+ if (!channel)
123
+ throw new errors_js_1.ServiceUnavailableError('Message broker unavailable');
124
+ return channel;
125
+ }
126
+ write(exchange, routingKey, body, props) {
127
+ let channel;
128
+ try {
129
+ channel = this.channel();
130
+ }
131
+ catch (error) {
132
+ return Promise.reject(error);
89
133
  }
90
- const { messageId } = props;
134
+ const { messageId, correlationId } = props;
135
+ const publishOptions = {
136
+ persistent: true,
137
+ mandatory: props.mandatory,
138
+ contentType: 'application/json',
139
+ messageId,
140
+ correlationId,
141
+ type: props.type,
142
+ expiration: props.expiration,
143
+ timestamp: Math.floor(Date.now() / 1000),
144
+ appId: this.options.source,
145
+ headers: correlationId ? { [envelope_js_1.CORRELATION_HEADER]: correlationId } : {},
146
+ };
91
147
  return new Promise((resolve, reject) => {
92
- channel.publish(exchange, routingKey, Buffer.from(JSON.stringify(body)), {
93
- persistent: true,
94
- mandatory: props.mandatory,
95
- contentType: 'application/json',
96
- messageId,
97
- correlationId: props.correlationId,
98
- type: props.type,
99
- timestamp: Math.floor(Date.now() / 1000),
100
- appId: this.options.source,
101
- }, (error) => {
148
+ channel.publish(exchange, routingKey, Buffer.from(JSON.stringify(body)), publishOptions, (error) => {
102
149
  // The broker sends `basic.return` before the confirm, so a returned message is already recorded here.
103
150
  const unroutable = this.returned.delete(messageId);
104
151
  if (error) {
@@ -119,31 +166,32 @@ class MessagePublisher {
119
166
  exports.MessagePublisher = MessagePublisher;
120
167
  /** Stand-in when messaging is disabled (local tooling, tests): logs and drops. */
121
168
  class NoopMessageBus {
122
- source;
123
169
  logger;
124
- constructor(source, logger = index_js_1.silentLogger) {
125
- this.source = source;
170
+ constructor(logger = index_js_1.silentLogger) {
126
171
  this.logger = logger;
127
172
  }
128
- async publish(type, data, meta = {}) {
129
- const envelope = (0, envelope_js_1.buildEnvelope)(type, data, this.source, meta);
130
- this.logger.debug('Messaging disabled; event dropped', { type, eventId: envelope.eventId });
173
+ async publish(routingKey, data, meta = {}) {
174
+ const envelope = (0, envelope_js_1.buildEnvelope)(routingKey, data, meta);
175
+ this.logger.debug('Messaging disabled; event dropped', { routingKey, eventId: envelope.data.eventId });
131
176
  return envelope;
132
177
  }
133
178
  async publishEnvelope(envelope) {
134
- this.logger.debug('Messaging disabled; event dropped', { type: envelope.type, eventId: envelope.eventId });
179
+ this.logger.debug('Messaging disabled; event dropped', { routingKey: envelope.routingKey, eventId: envelope.data.eventId });
135
180
  }
136
181
  async send(command, data, meta = {}) {
137
- const envelope = (0, envelope_js_1.buildEnvelope)(command, data, this.source, meta);
138
- this.logger.debug('Messaging disabled; command dropped', { command, eventId: envelope.eventId });
182
+ const envelope = (0, envelope_js_1.buildEnvelope)(command, data, meta);
183
+ this.logger.debug('Messaging disabled; command dropped', { command, eventId: envelope.data.eventId });
139
184
  return envelope;
140
185
  }
141
186
  async sendEnvelope(envelope) {
142
- this.logger.debug('Messaging disabled; command dropped', { command: envelope.type, eventId: envelope.eventId });
187
+ this.logger.debug('Messaging disabled; command dropped', { command: envelope.routingKey, eventId: envelope.data.eventId });
143
188
  }
144
189
  async sendToQueue(queue) {
145
190
  this.logger.debug('Messaging disabled; queue message dropped', { queue });
146
191
  }
192
+ async scheduleDelayed(queue, _payload, delayMs) {
193
+ this.logger.debug('Messaging disabled; delayed message dropped', { queue, delayMs });
194
+ }
147
195
  }
148
196
  exports.NoopMessageBus = NoopMessageBus;
149
197
  //# sourceMappingURL=publisher.js.map