@crowdsource.you/core 1.2.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 (339) hide show
  1. package/README.md +571 -0
  2. package/dist/cases.d.ts +66 -0
  3. package/dist/cases.d.ts.map +1 -0
  4. package/dist/cases.js +79 -0
  5. package/dist/cases.js.map +1 -0
  6. package/dist/client.d.ts +90 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +125 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/communityNotes.d.ts +49 -0
  11. package/dist/communityNotes.d.ts.map +1 -0
  12. package/dist/communityNotes.js +104 -0
  13. package/dist/communityNotes.js.map +1 -0
  14. package/dist/credential.d.ts +58 -0
  15. package/dist/credential.d.ts.map +1 -0
  16. package/dist/credential.js +71 -0
  17. package/dist/credential.js.map +1 -0
  18. package/dist/defaults.d.ts +61 -0
  19. package/dist/defaults.d.ts.map +1 -0
  20. package/dist/defaults.js +82 -0
  21. package/dist/defaults.js.map +1 -0
  22. package/dist/digest.d.ts +44 -0
  23. package/dist/digest.d.ts.map +1 -0
  24. package/dist/digest.js +79 -0
  25. package/dist/digest.js.map +1 -0
  26. package/dist/envelope.d.ts +143 -0
  27. package/dist/envelope.d.ts.map +1 -0
  28. package/dist/envelope.js +258 -0
  29. package/dist/envelope.js.map +1 -0
  30. package/dist/errors.d.ts +90 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/errors.js +151 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/esm/cases.js +74 -0
  35. package/dist/esm/cases.js.map +1 -0
  36. package/dist/esm/client.js +121 -0
  37. package/dist/esm/client.js.map +1 -0
  38. package/dist/esm/communityNotes.js +100 -0
  39. package/dist/esm/communityNotes.js.map +1 -0
  40. package/dist/esm/credential.js +67 -0
  41. package/dist/esm/credential.js.map +1 -0
  42. package/dist/esm/defaults.js +77 -0
  43. package/dist/esm/defaults.js.map +1 -0
  44. package/dist/esm/digest.js +74 -0
  45. package/dist/esm/digest.js.map +1 -0
  46. package/dist/esm/envelope.js +252 -0
  47. package/dist/esm/envelope.js.map +1 -0
  48. package/dist/esm/errors.js +140 -0
  49. package/dist/esm/errors.js.map +1 -0
  50. package/dist/esm/express/index.js +29 -0
  51. package/dist/esm/express/index.js.map +1 -0
  52. package/dist/esm/express/middleware.js +293 -0
  53. package/dist/esm/express/middleware.js.map +1 -0
  54. package/dist/esm/express/store.js +69 -0
  55. package/dist/esm/express/store.js.map +1 -0
  56. package/dist/esm/express/verify.js +133 -0
  57. package/dist/esm/express/verify.js.map +1 -0
  58. package/dist/esm/index.js +57 -0
  59. package/dist/esm/index.js.map +1 -0
  60. package/dist/esm/outbox/client.js +43 -0
  61. package/dist/esm/outbox/client.js.map +1 -0
  62. package/dist/esm/outbox/decision.js +153 -0
  63. package/dist/esm/outbox/decision.js.map +1 -0
  64. package/dist/esm/outbox/delivery.js +129 -0
  65. package/dist/esm/outbox/delivery.js.map +1 -0
  66. package/dist/esm/outbox/enforcement/executor.js +219 -0
  67. package/dist/esm/outbox/enforcement/executor.js.map +1 -0
  68. package/dist/esm/outbox/enforcement/planner.js +326 -0
  69. package/dist/esm/outbox/enforcement/planner.js.map +1 -0
  70. package/dist/esm/outbox/evidence.js +173 -0
  71. package/dist/esm/outbox/evidence.js.map +1 -0
  72. package/dist/esm/outbox/inbound.js +87 -0
  73. package/dist/esm/outbox/inbound.js.map +1 -0
  74. package/dist/esm/outbox/index.js +71 -0
  75. package/dist/esm/outbox/index.js.map +1 -0
  76. package/dist/esm/outbox/intake.js +140 -0
  77. package/dist/esm/outbox/intake.js.map +1 -0
  78. package/dist/esm/outbox/integration.js +116 -0
  79. package/dist/esm/outbox/integration.js.map +1 -0
  80. package/dist/esm/outbox/outbox/dispatcher.js +114 -0
  81. package/dist/esm/outbox/outbox/dispatcher.js.map +1 -0
  82. package/dist/esm/outbox/outbox/service.js +271 -0
  83. package/dist/esm/outbox/outbox/service.js.map +1 -0
  84. package/dist/esm/outbox/postgres/index.js +41 -0
  85. package/dist/esm/outbox/postgres/index.js.map +1 -0
  86. package/dist/esm/outbox/postgres/registries.js +155 -0
  87. package/dist/esm/outbox/postgres/registries.js.map +1 -0
  88. package/dist/esm/outbox/postgres/reportColumns.js +191 -0
  89. package/dist/esm/outbox/postgres/reportColumns.js.map +1 -0
  90. package/dist/esm/outbox/postgres/store/enforcement.js +158 -0
  91. package/dist/esm/outbox/postgres/store/enforcement.js.map +1 -0
  92. package/dist/esm/outbox/postgres/store/events.js +101 -0
  93. package/dist/esm/outbox/postgres/store/events.js.map +1 -0
  94. package/dist/esm/outbox/postgres/store/index.js +59 -0
  95. package/dist/esm/outbox/postgres/store/index.js.map +1 -0
  96. package/dist/esm/outbox/postgres/store/outbox.js +207 -0
  97. package/dist/esm/outbox/postgres/store/outbox.js.map +1 -0
  98. package/dist/esm/outbox/postgres/store/reports.js +231 -0
  99. package/dist/esm/outbox/postgres/store/reports.js.map +1 -0
  100. package/dist/esm/outbox/postgres/store/transaction.js +29 -0
  101. package/dist/esm/outbox/postgres/store/transaction.js.map +1 -0
  102. package/dist/esm/outbox/postgres/tables.js +270 -0
  103. package/dist/esm/outbox/postgres/tables.js.map +1 -0
  104. package/dist/esm/outbox/reconciliation.js +157 -0
  105. package/dist/esm/outbox/reconciliation.js.map +1 -0
  106. package/dist/esm/outbox/reportStatus.js +22 -0
  107. package/dist/esm/outbox/reportStatus.js.map +1 -0
  108. package/dist/esm/outbox/retention.js +33 -0
  109. package/dist/esm/outbox/retention.js.map +1 -0
  110. package/dist/esm/outbox/store/types.js +30 -0
  111. package/dist/esm/outbox/store/types.js.map +1 -0
  112. package/dist/esm/outbox/types.js +23 -0
  113. package/dist/esm/outbox/types.js.map +1 -0
  114. package/dist/esm/outbox/webhook.js +145 -0
  115. package/dist/esm/outbox/webhook.js.map +1 -0
  116. package/dist/esm/package.json +1 -0
  117. package/dist/esm/reports.js +74 -0
  118. package/dist/esm/reports.js.map +1 -0
  119. package/dist/esm/testing/fixtures.js +132 -0
  120. package/dist/esm/testing/fixtures.js.map +1 -0
  121. package/dist/esm/testing/index.js +33 -0
  122. package/dist/esm/testing/index.js.map +1 -0
  123. package/dist/esm/testing/sandbox.js +337 -0
  124. package/dist/esm/testing/sandbox.js.map +1 -0
  125. package/dist/esm/testing/webhook-simulator.js +93 -0
  126. package/dist/esm/testing/webhook-simulator.js.map +1 -0
  127. package/dist/esm/transport.js +177 -0
  128. package/dist/esm/transport.js.map +1 -0
  129. package/dist/esm/webhookEndpoints.js +139 -0
  130. package/dist/esm/webhookEndpoints.js.map +1 -0
  131. package/dist/express/index.d.ts +32 -0
  132. package/dist/express/index.d.ts.map +1 -0
  133. package/dist/express/index.js +40 -0
  134. package/dist/express/index.js.map +1 -0
  135. package/dist/express/middleware.d.ts +97 -0
  136. package/dist/express/middleware.d.ts.map +1 -0
  137. package/dist/express/middleware.js +298 -0
  138. package/dist/express/middleware.js.map +1 -0
  139. package/dist/express/store.d.ts +45 -0
  140. package/dist/express/store.d.ts.map +1 -0
  141. package/dist/express/store.js +72 -0
  142. package/dist/express/store.js.map +1 -0
  143. package/dist/express/verify.d.ts +86 -0
  144. package/dist/express/verify.d.ts.map +1 -0
  145. package/dist/express/verify.js +138 -0
  146. package/dist/express/verify.js.map +1 -0
  147. package/dist/index.d.ts +67 -0
  148. package/dist/index.d.ts.map +1 -0
  149. package/dist/index.js +89 -0
  150. package/dist/index.js.map +1 -0
  151. package/dist/outbox/client.d.ts +35 -0
  152. package/dist/outbox/client.d.ts.map +1 -0
  153. package/dist/outbox/client.js +46 -0
  154. package/dist/outbox/client.js.map +1 -0
  155. package/dist/outbox/decision.d.ts +36 -0
  156. package/dist/outbox/decision.d.ts.map +1 -0
  157. package/dist/outbox/decision.js +159 -0
  158. package/dist/outbox/decision.js.map +1 -0
  159. package/dist/outbox/delivery.d.ts +50 -0
  160. package/dist/outbox/delivery.d.ts.map +1 -0
  161. package/dist/outbox/delivery.js +135 -0
  162. package/dist/outbox/delivery.js.map +1 -0
  163. package/dist/outbox/enforcement/executor.d.ts +26 -0
  164. package/dist/outbox/enforcement/executor.d.ts.map +1 -0
  165. package/dist/outbox/enforcement/executor.js +222 -0
  166. package/dist/outbox/enforcement/executor.js.map +1 -0
  167. package/dist/outbox/enforcement/planner.d.ts +50 -0
  168. package/dist/outbox/enforcement/planner.d.ts.map +1 -0
  169. package/dist/outbox/enforcement/planner.js +333 -0
  170. package/dist/outbox/enforcement/planner.js.map +1 -0
  171. package/dist/outbox/evidence.d.ts +114 -0
  172. package/dist/outbox/evidence.d.ts.map +1 -0
  173. package/dist/outbox/evidence.js +181 -0
  174. package/dist/outbox/evidence.js.map +1 -0
  175. package/dist/outbox/inbound.d.ts +78 -0
  176. package/dist/outbox/inbound.d.ts.map +1 -0
  177. package/dist/outbox/inbound.js +91 -0
  178. package/dist/outbox/inbound.js.map +1 -0
  179. package/dist/outbox/index.d.ts +79 -0
  180. package/dist/outbox/index.d.ts.map +1 -0
  181. package/dist/outbox/index.js +98 -0
  182. package/dist/outbox/index.js.map +1 -0
  183. package/dist/outbox/intake.d.ts +62 -0
  184. package/dist/outbox/intake.d.ts.map +1 -0
  185. package/dist/outbox/intake.js +145 -0
  186. package/dist/outbox/intake.js.map +1 -0
  187. package/dist/outbox/integration.d.ts +62 -0
  188. package/dist/outbox/integration.d.ts.map +1 -0
  189. package/dist/outbox/integration.js +119 -0
  190. package/dist/outbox/integration.js.map +1 -0
  191. package/dist/outbox/outbox/dispatcher.d.ts +26 -0
  192. package/dist/outbox/outbox/dispatcher.d.ts.map +1 -0
  193. package/dist/outbox/outbox/dispatcher.js +119 -0
  194. package/dist/outbox/outbox/dispatcher.js.map +1 -0
  195. package/dist/outbox/outbox/service.d.ts +124 -0
  196. package/dist/outbox/outbox/service.d.ts.map +1 -0
  197. package/dist/outbox/outbox/service.js +279 -0
  198. package/dist/outbox/outbox/service.js.map +1 -0
  199. package/dist/outbox/postgres/index.d.ts +44 -0
  200. package/dist/outbox/postgres/index.d.ts.map +1 -0
  201. package/dist/outbox/postgres/index.js +51 -0
  202. package/dist/outbox/postgres/index.js.map +1 -0
  203. package/dist/outbox/postgres/registries.d.ts +69 -0
  204. package/dist/outbox/postgres/registries.d.ts.map +1 -0
  205. package/dist/outbox/postgres/registries.js +159 -0
  206. package/dist/outbox/postgres/registries.js.map +1 -0
  207. package/dist/outbox/postgres/reportColumns.d.ts +154 -0
  208. package/dist/outbox/postgres/reportColumns.d.ts.map +1 -0
  209. package/dist/outbox/postgres/reportColumns.js +195 -0
  210. package/dist/outbox/postgres/reportColumns.js.map +1 -0
  211. package/dist/outbox/postgres/store/enforcement.d.ts +33 -0
  212. package/dist/outbox/postgres/store/enforcement.d.ts.map +1 -0
  213. package/dist/outbox/postgres/store/enforcement.js +161 -0
  214. package/dist/outbox/postgres/store/enforcement.js.map +1 -0
  215. package/dist/outbox/postgres/store/events.d.ts +46 -0
  216. package/dist/outbox/postgres/store/events.d.ts.map +1 -0
  217. package/dist/outbox/postgres/store/events.js +104 -0
  218. package/dist/outbox/postgres/store/events.js.map +1 -0
  219. package/dist/outbox/postgres/store/index.d.ts +27 -0
  220. package/dist/outbox/postgres/store/index.d.ts.map +1 -0
  221. package/dist/outbox/postgres/store/index.js +62 -0
  222. package/dist/outbox/postgres/store/index.js.map +1 -0
  223. package/dist/outbox/postgres/store/outbox.d.ts +8 -0
  224. package/dist/outbox/postgres/store/outbox.d.ts.map +1 -0
  225. package/dist/outbox/postgres/store/outbox.js +210 -0
  226. package/dist/outbox/postgres/store/outbox.js.map +1 -0
  227. package/dist/outbox/postgres/store/reports.d.ts +69 -0
  228. package/dist/outbox/postgres/store/reports.d.ts.map +1 -0
  229. package/dist/outbox/postgres/store/reports.js +234 -0
  230. package/dist/outbox/postgres/store/reports.js.map +1 -0
  231. package/dist/outbox/postgres/store/transaction.d.ts +49 -0
  232. package/dist/outbox/postgres/store/transaction.d.ts.map +1 -0
  233. package/dist/outbox/postgres/store/transaction.js +32 -0
  234. package/dist/outbox/postgres/store/transaction.js.map +1 -0
  235. package/dist/outbox/postgres/tables.d.ts +737 -0
  236. package/dist/outbox/postgres/tables.d.ts.map +1 -0
  237. package/dist/outbox/postgres/tables.js +273 -0
  238. package/dist/outbox/postgres/tables.js.map +1 -0
  239. package/dist/outbox/reconciliation.d.ts +49 -0
  240. package/dist/outbox/reconciliation.d.ts.map +1 -0
  241. package/dist/outbox/reconciliation.js +162 -0
  242. package/dist/outbox/reconciliation.js.map +1 -0
  243. package/dist/outbox/reportStatus.d.ts +12 -0
  244. package/dist/outbox/reportStatus.d.ts.map +1 -0
  245. package/dist/outbox/reportStatus.js +25 -0
  246. package/dist/outbox/reportStatus.js.map +1 -0
  247. package/dist/outbox/retention.d.ts +33 -0
  248. package/dist/outbox/retention.d.ts.map +1 -0
  249. package/dist/outbox/retention.js +36 -0
  250. package/dist/outbox/retention.js.map +1 -0
  251. package/dist/outbox/store/types.d.ts +466 -0
  252. package/dist/outbox/store/types.d.ts.map +1 -0
  253. package/dist/outbox/store/types.js +31 -0
  254. package/dist/outbox/store/types.js.map +1 -0
  255. package/dist/outbox/types.d.ts +660 -0
  256. package/dist/outbox/types.d.ts.map +1 -0
  257. package/dist/outbox/types.js +24 -0
  258. package/dist/outbox/types.js.map +1 -0
  259. package/dist/outbox/webhook.d.ts +15 -0
  260. package/dist/outbox/webhook.d.ts.map +1 -0
  261. package/dist/outbox/webhook.js +148 -0
  262. package/dist/outbox/webhook.js.map +1 -0
  263. package/dist/reports.d.ts +73 -0
  264. package/dist/reports.d.ts.map +1 -0
  265. package/dist/reports.js +78 -0
  266. package/dist/reports.js.map +1 -0
  267. package/dist/testing/fixtures.d.ts +62 -0
  268. package/dist/testing/fixtures.d.ts.map +1 -0
  269. package/dist/testing/fixtures.js +137 -0
  270. package/dist/testing/fixtures.js.map +1 -0
  271. package/dist/testing/index.d.ts +36 -0
  272. package/dist/testing/index.d.ts.map +1 -0
  273. package/dist/testing/index.js +43 -0
  274. package/dist/testing/index.js.map +1 -0
  275. package/dist/testing/sandbox.d.ts +109 -0
  276. package/dist/testing/sandbox.d.ts.map +1 -0
  277. package/dist/testing/sandbox.js +342 -0
  278. package/dist/testing/sandbox.js.map +1 -0
  279. package/dist/testing/webhook-simulator.d.ts +78 -0
  280. package/dist/testing/webhook-simulator.d.ts.map +1 -0
  281. package/dist/testing/webhook-simulator.js +98 -0
  282. package/dist/testing/webhook-simulator.js.map +1 -0
  283. package/dist/transport.d.ts +59 -0
  284. package/dist/transport.d.ts.map +1 -0
  285. package/dist/transport.js +181 -0
  286. package/dist/transport.js.map +1 -0
  287. package/dist/webhookEndpoints.d.ts +147 -0
  288. package/dist/webhookEndpoints.d.ts.map +1 -0
  289. package/dist/webhookEndpoints.js +143 -0
  290. package/dist/webhookEndpoints.js.map +1 -0
  291. package/package.json +127 -0
  292. package/src/cases.ts +119 -0
  293. package/src/client.ts +202 -0
  294. package/src/communityNotes.ts +194 -0
  295. package/src/credential.ts +100 -0
  296. package/src/defaults.ts +87 -0
  297. package/src/digest.ts +91 -0
  298. package/src/envelope.ts +432 -0
  299. package/src/errors.ts +165 -0
  300. package/src/express/index.ts +43 -0
  301. package/src/express/middleware.ts +387 -0
  302. package/src/express/store.ts +85 -0
  303. package/src/express/verify.ts +166 -0
  304. package/src/index.ts +107 -0
  305. package/src/outbox/client.ts +75 -0
  306. package/src/outbox/decision.ts +209 -0
  307. package/src/outbox/delivery.ts +163 -0
  308. package/src/outbox/enforcement/executor.ts +288 -0
  309. package/src/outbox/enforcement/planner.ts +377 -0
  310. package/src/outbox/evidence.ts +231 -0
  311. package/src/outbox/inbound.ts +140 -0
  312. package/src/outbox/index.ts +160 -0
  313. package/src/outbox/intake.ts +175 -0
  314. package/src/outbox/integration.ts +205 -0
  315. package/src/outbox/outbox/dispatcher.ts +131 -0
  316. package/src/outbox/outbox/service.ts +444 -0
  317. package/src/outbox/postgres/index.ts +59 -0
  318. package/src/outbox/postgres/registries.ts +177 -0
  319. package/src/outbox/postgres/reportColumns.ts +281 -0
  320. package/src/outbox/postgres/store/enforcement.ts +186 -0
  321. package/src/outbox/postgres/store/events.ts +112 -0
  322. package/src/outbox/postgres/store/index.ts +72 -0
  323. package/src/outbox/postgres/store/outbox.ts +273 -0
  324. package/src/outbox/postgres/store/reports.ts +349 -0
  325. package/src/outbox/postgres/store/transaction.ts +64 -0
  326. package/src/outbox/postgres/tables.ts +339 -0
  327. package/src/outbox/reconciliation.ts +199 -0
  328. package/src/outbox/reportStatus.ts +24 -0
  329. package/src/outbox/retention.ts +34 -0
  330. package/src/outbox/store/types.ts +528 -0
  331. package/src/outbox/types.ts +753 -0
  332. package/src/outbox/webhook.ts +165 -0
  333. package/src/reports.ts +138 -0
  334. package/src/testing/fixtures.ts +183 -0
  335. package/src/testing/index.ts +57 -0
  336. package/src/testing/sandbox.ts +469 -0
  337. package/src/testing/webhook-simulator.ts +166 -0
  338. package/src/transport.ts +250 -0
  339. package/src/webhookEndpoints.ts +256 -0
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DuplicateReportError = void 0;
4
+ exports.createIntake = createIntake;
5
+ const service_js_1 = require("./outbox/service.js");
6
+ /**
7
+ * Storing a report and, when there is somewhere to send it, the promise to
8
+ * deliver it — in one operation.
9
+ *
10
+ * This is the only thing in the integration that a user waits for. A 201 from an
11
+ * application's report route means the report row and its outbox event committed
12
+ * together. It does NOT mean CrowdSource accepted anything — CrowdSource may be
13
+ * unreachable, mid-deploy or not yet configured, and the reporter is told their
14
+ * report was received either way, because it was.
15
+ *
16
+ * The transaction is the whole mechanism. Two writes outside one would give two
17
+ * failure modes that are both silent: a report with no delivery event (the
18
+ * report exists, nothing will ever send it, and nobody finds out until somebody
19
+ * asks why a case never opened) or a delivery event with no report (a delivery
20
+ * worker looking up an id that was rolled back). Neither surfaces as an error at
21
+ * the moment it happens, which is exactly why this has to be atomic rather than
22
+ * carefully ordered.
23
+ *
24
+ * The one report with NO delivery event is the one whose type has no subject
25
+ * provider, and that is a different claim entirely: not "delivery failed" but
26
+ * "there was never a route out of this application for this kind of object".
27
+ * Those two must not be conflated, which is why they are different `localStatus`
28
+ * values and why the absent route is written down as a reason rather than
29
+ * inferred from a missing row.
30
+ */
31
+ class DuplicateReportError extends Error {
32
+ constructor(existing) {
33
+ super('This item has already been reported by this reporter.');
34
+ this.name = 'DuplicateReportError';
35
+ this.existing = existing;
36
+ }
37
+ }
38
+ exports.DuplicateReportError = DuplicateReportError;
39
+ /**
40
+ * Refuses an identifier that is not a string, before it reaches the store.
41
+ *
42
+ * The input type says these are strings, but a type is erased at runtime and a
43
+ * truthiness check passes anything non-empty — including an object. What that
44
+ * costs depends on the backend, and the difference is worth stating rather than
45
+ * flattening: on Mongo a `{ $ne: null }` becomes a query OPERATOR, so the
46
+ * duplicate lookup matches an UNRELATED report and answers "you already reported
47
+ * this" about somebody else's row; on Postgres a bound parameter cannot become
48
+ * an operator, so that particular failure class does not exist there.
49
+ *
50
+ * The guard is not Mongo's, though. A non-string still reaches the insert and
51
+ * stores something that is not an id where an id belongs, on any backend — and
52
+ * the check lives here rather than at an application's route because this
53
+ * function is exported: a queue worker, a reconciliation script or a future
54
+ * admin path is under no obligation to have passed a route's validation, and a
55
+ * guard that only exists at one caller is a guard that holds until the second
56
+ * one arrives.
57
+ */
58
+ function requireIdentifier(value, field) {
59
+ /**
60
+ * `trim()` and not `length === 0`, because `' '` is not an identifier.
61
+ *
62
+ * A whitespace-only reporter or reported id is not a string somebody meant:
63
+ * it comes from a form field, a trimmed-elsewhere value, or a client that
64
+ * sends `' '` for absent. Stored, it is an id that matches nothing and reads
65
+ * as present — the duplicate check finds no earlier report, the delivery
66
+ * carries it as `externalReportId`, and nothing fails until a human asks why
67
+ * a case names nobody.
68
+ */
69
+ if (typeof value !== 'string' || value.trim() === '') {
70
+ throw new TypeError(`createReport: ${field} must be a non-empty string.`);
71
+ }
72
+ return value;
73
+ }
74
+ /**
75
+ * Why a report is not going anywhere, in words an operator can read.
76
+ *
77
+ * Stored on the row rather than left to be inferred from a missing outbox event.
78
+ * A missing row is also what a lost write looks like, and the two need to be
79
+ * distinguishable months later without re-deriving which types had providers at
80
+ * the time. Bounded by the schema's 300-character limit.
81
+ */
82
+ function localOnlyReason(reportedType) {
83
+ return (`There is no moderation subject provider for '${reportedType}', so this report is ` +
84
+ 'recorded locally and is not sent for community review.');
85
+ }
86
+ /**
87
+ * Store the report, and queue its delivery in the same transaction.
88
+ *
89
+ * Delivery is queued when — and only when — the reported type has a subject
90
+ * provider. A type without one is stored at `received` with the reason recorded,
91
+ * which is the behaviour the application had before CrowdSource existed: the
92
+ * report is a receipt and a local record, and nothing else ever happens to it.
93
+ *
94
+ * That branch is the reason the two writes stay in one transaction rather than
95
+ * being ordered carefully. The condition is read BEFORE the transaction body
96
+ * decides anything, so `localStatus` and the presence of an outbox row are
97
+ * decided together from one fact — a report can never commit as `queued` with
98
+ * nothing to deliver it, nor as `received` with a delivery event that will try
99
+ * anyway.
100
+ *
101
+ * Intake deliberately does not read whether the integration is enabled. A report
102
+ * taken while it is off still gets its delivery event, so turning the flag on
103
+ * delivers the backlog instead of stranding it — the dispatcher is what is
104
+ * gated, not the durable record. Nothing here is conditional on a third party's
105
+ * state; only on whether this application knows how to describe the object at
106
+ * all.
107
+ */
108
+ function createIntake(input) {
109
+ return async (report) => {
110
+ const reporter = requireIdentifier(report.reporter, 'reporter');
111
+ const reportedId = requireIdentifier(report.reportedId, 'reportedId');
112
+ const reportedType = requireIdentifier(report.reportedType, 'reportedType');
113
+ if (!Array.isArray(report.categories) || report.categories.length === 0) {
114
+ throw new TypeError('createReport: categories must be a non-empty array.');
115
+ }
116
+ for (const category of report.categories) {
117
+ requireIdentifier(category, 'categories[]');
118
+ }
119
+ const deliverable = input.registry.providerFor(reportedType) !== undefined;
120
+ return await input.transaction.run(async (tx) => {
121
+ const existing = await input.reports.findDuplicate({ reporter, reportedId, reportedType }, tx);
122
+ if (existing)
123
+ throw new DuplicateReportError(existing);
124
+ const created = await input.reports.insert({
125
+ reportedType,
126
+ reportedId,
127
+ reporter,
128
+ categories: [...report.categories],
129
+ ...(report.details === undefined ? {} : { details: report.details }),
130
+ localStatus: deliverable ? 'queued' : 'received',
131
+ ...(deliverable ? {} : { localStatusReason: localOnlyReason(reportedType) }),
132
+ ...(report.extra === undefined ? {} : { extra: report.extra }),
133
+ }, tx);
134
+ if (!deliverable)
135
+ return { report: created };
136
+ const outboxEventId = await input.outbox.enqueue({
137
+ eventId: (0, service_js_1.reportSubmitEventId)(created.id),
138
+ kind: 'report.submit',
139
+ payload: { reportId: created.id },
140
+ }, tx);
141
+ return { report: created, outboxEventId };
142
+ });
143
+ };
144
+ }
145
+ //# sourceMappingURL=intake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intake.js","sourceRoot":"","sources":["../../src/outbox/intake.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { Router } from 'express';
2
+ import { type CrowdSourceClientProvider } from './client.js';
3
+ import { type EnforcementExecutor } from './enforcement/executor.js';
4
+ import { type SubjectRegistry } from './evidence.js';
5
+ import { ModerationOutboxDispatcher } from './outbox/dispatcher.js';
6
+ import { ModerationReconciliationJob, type ReconcileModerationReports } from './reconciliation.js';
7
+ import type { CreateReportInput, CreateReportResult, ModerationIntegrationConfig, ModerationReportFields } from './types.js';
8
+ /**
9
+ * Everything wired together, from one object.
10
+ *
11
+ * A factory rather than a set of module-level singletons, and that is not
12
+ * stylistic: a module-level client, dispatcher or registry cannot be built
13
+ * twice, which makes two integrations in one test process impossible and makes
14
+ * test isolation depend on module-registry surgery. The store is built OUTSIDE
15
+ * and passed in, so the same wiring serves either backend and neither is
16
+ * reachable from here.
17
+ *
18
+ * `TTx` is deliberately absent from the returned interface. It is inferred from
19
+ * the config's store, used only inside this factory, and never surfaces — so a
20
+ * caller holding a `ModerationIntegration` cannot tell which backend built it,
21
+ * which is what lets one test suite run against both.
22
+ */
23
+ export interface ModerationIntegration<TReport extends ModerationReportFields, TAction extends string> {
24
+ /**
25
+ * Store a report and, when there is somewhere to send it, the promise to
26
+ * deliver it — in ONE transaction.
27
+ *
28
+ * Throws `DuplicateReportError` when this reporter already reported this
29
+ * object, and `TypeError` for an identifier that is not a non-empty string.
30
+ */
31
+ createReport(input: CreateReportInput): Promise<CreateReportResult<TReport>>;
32
+ /**
33
+ * The webhook receiver. **Mount this BEFORE `express.json()`** — the signature
34
+ * covers the bytes that arrived, and a parser destroys them.
35
+ *
36
+ * Returns an empty router when no webhook secret is configured: an
37
+ * unconfigured deployment 404s, which is indistinguishable from not having the
38
+ * feature.
39
+ */
40
+ webhookRouter(options?: {
41
+ path?: string;
42
+ }): Router;
43
+ /**
44
+ * The outbox loop. Safe on every task — every event is claimed under a lease
45
+ * with an owner check, so N tasks share the work.
46
+ */
47
+ readonly dispatcher: ModerationOutboxDispatcher;
48
+ /**
49
+ * The reconciliation sweep on a timer. Start it from a LEADER-elected
50
+ * scheduler only.
51
+ */
52
+ readonly reconciliationJob: ModerationReconciliationJob;
53
+ /** One reconciliation sweep, on demand. */
54
+ readonly reconcile: ReconcileModerationReports;
55
+ /** The reported types that have a subject provider, so a test can pin the set. */
56
+ deliverableTypes(): string[];
57
+ readonly registry: SubjectRegistry;
58
+ readonly enforcement: EnforcementExecutor<TAction>;
59
+ readonly client: CrowdSourceClientProvider;
60
+ }
61
+ export declare function createModerationIntegration<TReport extends ModerationReportFields, TAction extends string, TTx>(config: ModerationIntegrationConfig<TReport, TAction, TTx>): ModerationIntegration<TReport, TAction>;
62
+ //# sourceMappingURL=integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../src/outbox/integration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAwB,KAAK,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAGnF,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAEhG,OAAO,EAAyB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAE5E,OAAO,EAAE,0BAA0B,EAAsB,MAAM,wBAAwB,CAAC;AAGxF,OAAO,EACL,2BAA2B,EAE3B,KAAK,0BAA0B,EAChC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,2BAA2B,EAC3B,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,qBAAqB,CACpC,OAAO,SAAS,sBAAsB,EACtC,OAAO,SAAS,MAAM;IAEtB;;;;;;OAMG;IACH,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;IAE7E;;;;;;;OAOG;IACH,aAAa,CAAC,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC;IAEnD;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;IAEhD;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,EAAE,2BAA2B,CAAC;IAExD,2CAA2C;IAC3C,QAAQ,CAAC,SAAS,EAAE,0BAA0B,CAAC;IAE/C,kFAAkF;IAClF,gBAAgB,IAAI,MAAM,EAAE,CAAC;IAE7B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,yBAAyB,CAAC;CAC5C;AAED,wBAAgB,2BAA2B,CACzC,OAAO,SAAS,sBAAsB,EACtC,OAAO,SAAS,MAAM,EACtB,GAAG,EAEH,MAAM,EAAE,2BAA2B,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,GACzD,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAiHzC"}
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createModerationIntegration = createModerationIntegration;
4
+ const client_js_1 = require("./client.js");
5
+ const decision_js_1 = require("./decision.js");
6
+ const delivery_js_1 = require("./delivery.js");
7
+ const executor_js_1 = require("./enforcement/executor.js");
8
+ const planner_js_1 = require("./enforcement/planner.js");
9
+ const evidence_js_1 = require("./evidence.js");
10
+ const inbound_js_1 = require("./inbound.js");
11
+ const dispatcher_js_1 = require("./outbox/dispatcher.js");
12
+ const service_js_1 = require("./outbox/service.js");
13
+ const intake_js_1 = require("./intake.js");
14
+ const reconciliation_js_1 = require("./reconciliation.js");
15
+ const webhook_js_1 = require("./webhook.js");
16
+ function createModerationIntegration(config) {
17
+ /**
18
+ * Refuse an inverted `restoreAction` before anything is wired. It cannot be
19
+ * caught by the type — both directions are `TAction[]` — and it does not fail
20
+ * at runtime; it applies a punishment on an accepted appeal.
21
+ */
22
+ (0, planner_js_1.assertRestoreDirection)(config.enforcement);
23
+ const store = config.store;
24
+ const outbox = (0, service_js_1.createOutboxService)({ store: store.outbox, logger: config.logger });
25
+ const registry = (0, evidence_js_1.createSubjectRegistry)(config.subjects);
26
+ const client = (0, client_js_1.createClientProvider)({
27
+ config: config.crowdSource,
28
+ logger: config.logger,
29
+ });
30
+ const enforcement = (0, executor_js_1.createEnforcementExecutor)({
31
+ enforcement: store.enforcement,
32
+ config: config.enforcement,
33
+ defaultMode: config.crowdSource.enforcementMode,
34
+ logger: config.logger,
35
+ ...(config.metrics === undefined ? {} : { metrics: config.metrics }),
36
+ });
37
+ const deliverReport = (0, delivery_js_1.createDeliveryWorker)({
38
+ reports: store.reports,
39
+ registry,
40
+ taxonomy: config.taxonomy,
41
+ client,
42
+ logger: config.logger,
43
+ ...(config.metrics === undefined ? {} : { metrics: config.metrics }),
44
+ });
45
+ const applyDecision = (0, decision_js_1.createDecisionWorker)({
46
+ reports: store.reports,
47
+ executor: enforcement,
48
+ enforcement: config.enforcement,
49
+ logger: config.logger,
50
+ ...(config.reportDecisionExtraFields === undefined
51
+ ? {}
52
+ : { reportDecisionExtraFields: config.reportDecisionExtraFields }),
53
+ });
54
+ const inbound = (0, inbound_js_1.createInboundService)({
55
+ transaction: store.transaction,
56
+ events: store.events,
57
+ outbox,
58
+ });
59
+ const reconcile = (0, reconciliation_js_1.createReconciliation)({
60
+ transaction: store.transaction,
61
+ reports: store.reports,
62
+ outbox,
63
+ logger: config.logger,
64
+ ...(config.crowdSource.staleSubmittedHours === undefined
65
+ ? {}
66
+ : { staleSubmittedHours: config.crowdSource.staleSubmittedHours }),
67
+ });
68
+ const dispatcher = new dispatcher_js_1.ModerationOutboxDispatcher({
69
+ outbox,
70
+ handler: (0, dispatcher_js_1.createOutboxRouter)({ deliverReport, applyDecision }),
71
+ logger: config.logger,
72
+ enabled: config.crowdSource.enabled,
73
+ ...(config.crowdSource.outboxPollIntervalMs === undefined
74
+ ? {}
75
+ : { pollIntervalMs: config.crowdSource.outboxPollIntervalMs }),
76
+ ...(config.crowdSource.outboxBatchSize === undefined
77
+ ? {}
78
+ : { batchSize: config.crowdSource.outboxBatchSize }),
79
+ });
80
+ const reconciliationJob = new reconciliation_js_1.ModerationReconciliationJob({
81
+ reconcile,
82
+ logger: config.logger,
83
+ enabled: config.crowdSource.enabled,
84
+ ...(config.crowdSource.reconciliationIntervalMs === undefined
85
+ ? {}
86
+ : { intervalMs: config.crowdSource.reconciliationIntervalMs }),
87
+ });
88
+ return {
89
+ createReport: (0, intake_js_1.createIntake)({
90
+ transaction: store.transaction,
91
+ reports: store.reports,
92
+ registry,
93
+ outbox,
94
+ }),
95
+ webhookRouter(options = {}) {
96
+ return (0, webhook_js_1.createWebhookRouter)({
97
+ inbound,
98
+ store: (0, inbound_js_1.createProcessedEventStore)(store.events),
99
+ ...(config.crowdSource.webhookSecret === undefined
100
+ ? {}
101
+ : { secret: config.crowdSource.webhookSecret }),
102
+ ...(config.crowdSource.webhookPreviousSecret === undefined
103
+ ? {}
104
+ : { previousSecret: config.crowdSource.webhookPreviousSecret }),
105
+ logger: config.logger,
106
+ ...(config.metrics === undefined ? {} : { metrics: config.metrics }),
107
+ ...(options.path === undefined ? {} : { path: options.path }),
108
+ });
109
+ },
110
+ dispatcher,
111
+ reconciliationJob,
112
+ reconcile,
113
+ deliverableTypes: () => registry.deliverableTypes(),
114
+ registry,
115
+ enforcement,
116
+ client,
117
+ };
118
+ }
119
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.js","sourceRoot":"","sources":["../../src/outbox/integration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { ModerationLogger, ModerationOutboxEvent } from '../types.js';
2
+ import type { ModerationOutboxHandler, OutboxDrain } from './service.js';
3
+ export declare class ModerationOutboxDispatcher {
4
+ private readonly options;
5
+ private timer;
6
+ private inFlight;
7
+ private abortController;
8
+ private running;
9
+ constructor(options: {
10
+ outbox: OutboxDrain;
11
+ handler: ModerationOutboxHandler;
12
+ logger: ModerationLogger;
13
+ enabled: boolean;
14
+ pollIntervalMs?: number;
15
+ batchSize?: number;
16
+ });
17
+ start(): void;
18
+ stop(): Promise<void>;
19
+ private tick;
20
+ }
21
+ /** Route an event to the worker that owns its kind. */
22
+ export declare function createOutboxRouter(handlers: {
23
+ deliverReport: (event: ModerationOutboxEvent) => Promise<void>;
24
+ applyDecision: (event: ModerationOutboxEvent) => Promise<void>;
25
+ }): ModerationOutboxHandler;
26
+ //# sourceMappingURL=dispatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../../src/outbox/outbox/dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC3E,OAAO,KAAK,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAwBzE,qBAAa,0BAA0B;IAOnC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAN1B,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,eAAe,CAAgC;IACvD,OAAO,CAAC,OAAO,CAAS;gBAGL,OAAO,EAAE;QACxB,MAAM,EAAE,WAAW,CAAC;QACpB,OAAO,EAAE,uBAAuB,CAAC;QACjC,MAAM,EAAE,gBAAgB,CAAC;QACzB,OAAO,EAAE,OAAO,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;IAGH,KAAK,IAAI,IAAI;IAsBP,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAcb,IAAI;CAmCnB;AAED,uDAAuD;AACvD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE;IAC3C,aAAa,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,aAAa,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE,GAAG,uBAAuB,CAW1B"}
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ModerationOutboxDispatcher = void 0;
4
+ exports.createOutboxRouter = createOutboxRouter;
5
+ /**
6
+ * The loop that drains the moderation outbox.
7
+ *
8
+ * A bounded interval, one batch in flight at a time, an abort signal that stops
9
+ * claiming new work but lets the event already being handled reach a durable
10
+ * state.
11
+ *
12
+ * It is NOT leader-gated, and that is a property of the claim rather than an
13
+ * oversight. Every event is taken under a lease with an owner check, so N tasks
14
+ * draining the same collection simply share the work — and a task dying
15
+ * mid-delivery has its lease expire and its event reclaimed, which a single
16
+ * leader would not give us.
17
+ *
18
+ * `enabled` gates the LOOP, never the durable record. Reports taken while the
19
+ * integration is off keep their outbox rows and deliver when it is switched on;
20
+ * running the loop instead would count attempts against a deployment that has
21
+ * nowhere to send anything and dead-letter the backlog it was supposed to
22
+ * preserve.
23
+ */
24
+ const DEFAULT_POLL_INTERVAL_MS = 5000;
25
+ class ModerationOutboxDispatcher {
26
+ constructor(options) {
27
+ this.options = options;
28
+ this.timer = null;
29
+ this.inFlight = null;
30
+ this.abortController = null;
31
+ this.running = false;
32
+ }
33
+ start() {
34
+ if (this.running)
35
+ return;
36
+ if (!this.options.enabled) {
37
+ this.options.logger.info('[CrowdSource] outbox dispatcher not started: the integration is disabled');
38
+ return;
39
+ }
40
+ const intervalMs = this.options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
41
+ this.running = true;
42
+ this.abortController = new AbortController();
43
+ void this.tick();
44
+ this.timer = setInterval(() => {
45
+ void this.tick();
46
+ }, intervalMs);
47
+ this.timer.unref?.();
48
+ this.options.logger.info('[CrowdSource] outbox dispatcher started', {
49
+ intervalMs,
50
+ batchSize: this.options.batchSize,
51
+ });
52
+ }
53
+ async stop() {
54
+ this.running = false;
55
+ const controller = this.abortController;
56
+ controller?.abort();
57
+ if (this.timer) {
58
+ clearInterval(this.timer);
59
+ this.timer = null;
60
+ }
61
+ await this.inFlight;
62
+ if (this.abortController === controller) {
63
+ this.abortController = null;
64
+ }
65
+ }
66
+ async tick() {
67
+ if (!this.running)
68
+ return;
69
+ if (this.inFlight)
70
+ return this.inFlight;
71
+ const work = this.options.outbox
72
+ .dispatch({
73
+ handler: this.options.handler,
74
+ ...(this.options.batchSize === undefined
75
+ ? {}
76
+ : { batchSize: this.options.batchSize }),
77
+ ...(this.abortController === null
78
+ ? {}
79
+ : { signal: this.abortController.signal }),
80
+ })
81
+ .then(({ processed, failed, deadLettered }) => {
82
+ if (processed > 0 || failed > 0) {
83
+ this.options.logger.info('[CrowdSource] outbox batch complete', {
84
+ processed,
85
+ failed,
86
+ deadLettered,
87
+ });
88
+ }
89
+ })
90
+ .catch((error) => {
91
+ // Claim/database failures happen outside the per-event retry block. Keep
92
+ // the interval alive and avoid an unhandled rejection.
93
+ this.options.logger.error('[CrowdSource] outbox tick failed', {
94
+ error: error instanceof Error ? error.message : String(error),
95
+ });
96
+ })
97
+ .finally(() => {
98
+ if (this.inFlight === work)
99
+ this.inFlight = null;
100
+ });
101
+ this.inFlight = work;
102
+ return work;
103
+ }
104
+ }
105
+ exports.ModerationOutboxDispatcher = ModerationOutboxDispatcher;
106
+ /** Route an event to the worker that owns its kind. */
107
+ function createOutboxRouter(handlers) {
108
+ return async (event) => {
109
+ switch (event.kind) {
110
+ case 'report.submit':
111
+ await handlers.deliverReport(event);
112
+ return;
113
+ case 'decision.apply':
114
+ await handlers.applyDecision(event);
115
+ return;
116
+ }
117
+ };
118
+ }
119
+ //# sourceMappingURL=dispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../../../src/outbox/outbox/dispatcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,124 @@
1
+ import type { ModerationOutboxStore } from '../store/types.js';
2
+ import type { ModerationDispatchResult, ModerationLogger, ModerationOutboxEvent, ModerationOutboxKind, ModerationOutboxPayload, ModerationOutboxStatus } from '../types.js';
3
+ /**
4
+ * The event id for delivering a report.
5
+ *
6
+ * Derived from the report, not from the request: a transaction retry or two
7
+ * concurrent duplicate submissions upsert the SAME event rather than queueing
8
+ * two deliveries. There is exactly one delivery event per report for the life of
9
+ * the report, which is also what makes the CrowdSource-side idempotency key
10
+ * stable.
11
+ */
12
+ export declare function reportSubmitEventId(reportId: string): string;
13
+ /**
14
+ * The event id for applying an inbound decision.
15
+ *
16
+ * The webhook event id is the key, so a redelivery of the same event can never
17
+ * queue the work twice even if the dedupe claim were somehow released.
18
+ */
19
+ export declare function decisionApplyEventId(eventId: string): string;
20
+ /**
21
+ * Raised when an outbox event is written outside a transaction.
22
+ *
23
+ * Never expected at runtime. It exists so the invariant is ENFORCED rather than
24
+ * reviewed. It is thrown by the STORE — only a backend knows what an open
25
+ * transaction looks like — and it lives here so that every backend throws the
26
+ * same error for the same mistake. See {@link ModerationOutboxStore.enqueue}.
27
+ */
28
+ export declare class ModerationOutboxTransactionError extends Error {
29
+ constructor(eventId: string);
30
+ }
31
+ /**
32
+ * A failure that says whether trying the same payload again could ever work.
33
+ *
34
+ * Every error `@crowdsource.you/core` throws carries `retryable`, which is the only
35
+ * thing a delivery worker needs from it. Anything else — a bug in this code, a
36
+ * Mongo error — is treated as retryable, because assuming a defect is permanent
37
+ * is how a recoverable outage becomes lost moderation work.
38
+ */
39
+ export declare function isRetryableDeliveryError(error: unknown): boolean;
40
+ export interface ModerationOutboxFailure {
41
+ released: boolean;
42
+ deadLettered: boolean;
43
+ }
44
+ export type ModerationOutboxHandler = (event: ModerationOutboxEvent) => Promise<void>;
45
+ /**
46
+ * Draining the outbox: the one operation that never touches a transaction.
47
+ *
48
+ * Split out so {@link ModerationOutboxDispatcher} — which only ever asks for a
49
+ * batch — does not carry a backend's transaction type through a class that has
50
+ * no use for it.
51
+ */
52
+ export interface OutboxDrain {
53
+ /** Drain up to `batchSize` due events. Bounded, at-least-once, lease-protected. */
54
+ dispatch(options: {
55
+ handler: ModerationOutboxHandler;
56
+ leaseOwner?: string;
57
+ batchSize?: number;
58
+ leaseMs?: number;
59
+ signal?: AbortSignal;
60
+ }): Promise<ModerationDispatchResult>;
61
+ }
62
+ export interface OutboxService<TTx> extends OutboxDrain {
63
+ /**
64
+ * Write the event in the CALLER's transaction.
65
+ *
66
+ * The transaction is required, not optional. This is the whole point of the
67
+ * collection: the domain write and this row commit together or not at all. An
68
+ * overload that let a caller enqueue outside a transaction would be the one
69
+ * line that quietly reintroduces "the report was answered 201 and then
70
+ * vanished".
71
+ *
72
+ * The type makes the transaction mandatory; the STORE makes it mandatory that
73
+ * the transaction is ACTUALLY OPEN, by throwing
74
+ * {@link ModerationOutboxTransactionError}. A required parameter is satisfied
75
+ * by any handle — including a bare Mongo `startSession()` nobody opened a
76
+ * transaction on, which type-checks perfectly and commits the row on its own.
77
+ * That is the shape of the mistake worth catching: it looks exactly like
78
+ * correct code, it passes any test that only asserts the row exists, and it
79
+ * fails as lost moderation work with no trace on the day something restarts
80
+ * between the two writes.
81
+ *
82
+ * This function is also the ONLY writer of that table — the dispatcher claims
83
+ * existing rows and never creates one — so there is no second queue that can
84
+ * drift out of sync with the outbox. A job here is never the only evidence
85
+ * that work exists, because the row IS the job.
86
+ */
87
+ enqueue(input: {
88
+ eventId: string;
89
+ kind: ModerationOutboxKind;
90
+ payload: ModerationOutboxPayload;
91
+ }, tx: TTx): Promise<string>;
92
+ /**
93
+ * Atomically claim one due event. An expired `processing` lease is
94
+ * reclaimable, so a dead worker cannot strand moderation work forever.
95
+ */
96
+ claim(options: {
97
+ leaseOwner: string;
98
+ eventId?: string;
99
+ now?: Date;
100
+ leaseMs?: number;
101
+ }): Promise<ModerationOutboxEvent | null>;
102
+ /** Complete only the lease this dispatcher currently owns. */
103
+ complete(eventId: string, leaseOwner: string, now?: Date): Promise<boolean>;
104
+ /** Extend only a live lease still owned by this dispatcher. */
105
+ renew(eventId: string, leaseOwner: string, leaseMs: number, now?: Date): Promise<ModerationOutboxFailure['released']>;
106
+ /**
107
+ * Release a failed claim, with backoff — or stop.
108
+ *
109
+ * Stopping is not an optimisation. A 409 means this `externalReportId` already
110
+ * exists at CrowdSource with a different body, and no number of retries turns
111
+ * two payloads into one report; a 422 means the envelope is not processable.
112
+ * Both need the payload to change, so they become `dead_letter` immediately
113
+ * and stay visible with their error rather than accumulating attempts nobody
114
+ * reads.
115
+ */
116
+ fail(event: Pick<ModerationOutboxEvent, 'id' | 'attempts'>, leaseOwner: string, error: unknown, now?: Date): Promise<ModerationOutboxFailure>;
117
+ /** The status of one event, or `null`. Read by reconciliation. */
118
+ statusOf(eventId: string): Promise<ModerationOutboxStatus | null>;
119
+ }
120
+ export declare function createOutboxService<TTx>(input: {
121
+ store: ModerationOutboxStore<TTx>;
122
+ logger: ModerationLogger;
123
+ }): OutboxService<TTx>;
124
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/outbox/outbox/service.ts"],"names":[],"mappings":"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"}