@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,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.postgresEventStore = postgresEventStore;
4
+ const drizzle_orm_1 = require("drizzle-orm");
5
+ /**
6
+ * The inbound webhook event log, in Postgres.
7
+ *
8
+ * ## The one place this backend is structurally better rather than equivalent
9
+ *
10
+ * Everything else in this port aims for parity. This claim does not: it removes a
11
+ * failure mode instead of handling it, and the difference is worth being precise
12
+ * about, because "better" is otherwise just an adjective.
13
+ *
14
+ * **Mongo's claim throws and catches.** It inserts, and reads `code === 11000` to
15
+ * tell "somebody else has this event" from a real fault; everything that is not
16
+ * 11000 is rethrown, so a lost connection or a failover answers non-2xx and the
17
+ * event stays on the sender's retry schedule. That property is correct — and it
18
+ * lives in a PREDICATE. `catch { return false }` is one keystroke away, it
19
+ * type-checks, and it turns a connection failure into "already processed": the
20
+ * receiver answers 200 and a decision is retired that nobody ever handled. The
21
+ * only thing standing between those two behaviours is a conditional somebody
22
+ * could widen, and a test can only catch that by injecting a driver failure.
23
+ *
24
+ * **Postgres's claim does not throw at all.** `ON CONFLICT DO NOTHING` plus
25
+ * `RETURNING` makes a duplicate a ROW COUNT rather than an error: one row means
26
+ * this call took the claim, zero means somebody else holds it. So there is no
27
+ * catch block here — and therefore
28
+ *
29
+ * - no predicate to widen,
30
+ * - no code path that can convert a fault into a negative answer,
31
+ * - and the "rethrow everything else" guarantee holds by the ABSENCE of code
32
+ * rather than by the presence of correct code.
33
+ *
34
+ * That is the mechanism: the Mongo version is a property of code that exists and
35
+ * can be edited wrongly; this one is a property of code that does not exist. Only
36
+ * the second cannot be broken by a well-meaning change.
37
+ *
38
+ * The consequence for the mutation suite, stated because its absence would
39
+ * otherwise look like a gap: there is nothing to DELETE on the insert side, so no
40
+ * mutation can attack it. The one that exists attacks the READ — `rows.length === 1`
41
+ * collapsing to `true`, which would hand the same event to two handlers.
42
+ */
43
+ function postgresEventStore(input) {
44
+ const { db } = input;
45
+ const events = input.tables.events;
46
+ return {
47
+ async claim({ eventId, receivedAt, expiresAt }) {
48
+ /**
49
+ * The insert IS the claim, and a lost race is zero rows rather than an
50
+ * exception. `created_at`/`updated_at` are left to their column defaults:
51
+ * unlike the outbox, nothing here needs a repeated write to be provably a
52
+ * no-op, so the database's clock is the simpler authority.
53
+ */
54
+ const rows = await db
55
+ .insert(events)
56
+ .values({ id: eventId, state: 'claimed', receivedAt, expiresAt })
57
+ .onConflictDoNothing({ target: events.id })
58
+ .returning({ id: events.id });
59
+ return rows.length === 1;
60
+ },
61
+ /** Give the claim back so a redelivery can be processed. */
62
+ async release(eventId) {
63
+ await db.delete(events).where((0, drizzle_orm_1.eq)(events.id, eventId));
64
+ },
65
+ async markQueued({ eventId, type, caseId, payload, now }, tx) {
66
+ /**
67
+ * On the CALLER's transaction, which is the whole point: this row's
68
+ * completion and the outbox row that carries the work commit together, or a
69
+ * crash between them leaves an event permanently deduplicated with no work
70
+ * queued — a decision silently lost, with a row saying it arrived.
71
+ */
72
+ await tx
73
+ .update(events)
74
+ .set({
75
+ type,
76
+ caseId,
77
+ payload,
78
+ state: 'queued',
79
+ queuedAt: now,
80
+ updatedAt: now,
81
+ })
82
+ .where((0, drizzle_orm_1.eq)(events.id, eventId));
83
+ },
84
+ async markIgnored({ eventId, type, caseId, now }) {
85
+ await db
86
+ .update(events)
87
+ .set({
88
+ type,
89
+ /**
90
+ * Absent means LEAVE IT, not write something. An event type carrying no
91
+ * case id is ordinary — `case.created` before a case is linked — and
92
+ * `caseId: String(caseId)` would store the four characters `null` or
93
+ * the nine characters `undefined`, which then reads as a case id
94
+ * everywhere downstream. Postgres keeps the column NULL.
95
+ */
96
+ ...(caseId === undefined ? {} : { caseId }),
97
+ state: 'ignored',
98
+ updatedAt: now,
99
+ })
100
+ .where((0, drizzle_orm_1.eq)(events.id, eventId));
101
+ },
102
+ };
103
+ }
104
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/events.ts"],"names":[],"mappings":";;AA2CA,gDAoEC;AA/GD,6CAAiC;AAKjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,SAAgB,kBAAkB,CAAC,KAGlC;IACC,MAAM,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC;IACrB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;IAEnC,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE;YAC5C;;;;;eAKG;YACH,MAAM,IAAI,GAAG,MAAM,EAAE;iBAClB,MAAM,CAAC,MAAM,CAAC;iBACd,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;iBAChE,mBAAmB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;iBAC1C,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;YAEhC,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC;QAC3B,CAAC;QAED,4DAA4D;QAC5D,KAAK,CAAC,OAAO,CAAC,OAAO;YACnB,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAA,gBAAE,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QACxD,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;YAC1D;;;;;eAKG;YACH,MAAM,EAAE;iBACL,MAAM,CAAC,MAAM,CAAC;iBACd,GAAG,CAAC;gBACH,IAAI;gBACJ,MAAM;gBACN,OAAO;gBACP,KAAK,EAAE,QAAQ;gBACf,QAAQ,EAAE,GAAG;gBACb,SAAS,EAAE,GAAG;aACf,CAAC;iBACD,KAAK,CAAC,IAAA,gBAAE,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QACnC,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;YAC9C,MAAM,EAAE;iBACL,MAAM,CAAC,MAAM,CAAC;iBACd,GAAG,CAAC;gBACH,IAAI;gBACJ;;;;;;mBAMG;gBACH,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;gBAC3C,KAAK,EAAE,SAAS;gBAChB,SAAS,EAAE,GAAG;aACf,CAAC;iBACD,KAAK,CAAC,IAAA,gBAAE,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QACnC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { ModerationStore } from '../../store/types.js';
2
+ import type { ModerationReportFields } from '../../types.js';
3
+ import type { ModerationReportTable } from '../reportColumns.js';
4
+ import type { ModerationTables } from '../tables.js';
5
+ import { type ModerationPgHandle } from './transaction.js';
6
+ /**
7
+ * Everything this package writes, in Postgres.
8
+ *
9
+ * One factory rather than five, because the five members must share one handle: a
10
+ * report and its outbox row commit in the SAME transaction, and a store assembled
11
+ * from two handles would type-check perfectly and quietly lose that.
12
+ *
13
+ * Unlike the Mongo factory, this one CREATES NOTHING. There is no schema to
14
+ * register: the adopter's own migration created the tables, generated from the
15
+ * definitions this package exports, in the adopter's own journal. That asymmetry
16
+ * is deliberate and is the reason no migrations folder ships here — two journals
17
+ * against one `drizzle.__drizzle_migrations` table interleave, and the loser is
18
+ * skipped in silence with exit 0.
19
+ */
20
+ export declare function postgresModerationStore<TReport extends ModerationReportFields>(input: {
21
+ db: ModerationPgHandle;
22
+ /** The application's own report table, built from `moderationReportColumns`. */
23
+ reportTable: ModerationReportTable;
24
+ /** The three tables this package owns, from `moderationTables`. */
25
+ tables: ModerationTables;
26
+ }): ModerationStore<TReport, ModerationPgHandle>;
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKrD,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,sBAAsB,EAAE,KAAK,EAAE;IACrF,EAAE,EAAE,kBAAkB,CAAC;IACvB,gFAAgF;IAChF,WAAW,EAAE,qBAAqB,CAAC;IACnC,mEAAmE;IACnE,MAAM,EAAE,gBAAgB,CAAC;CAC1B,GAAG,eAAe,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAuC/C"}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.postgresModerationStore = postgresModerationStore;
4
+ const drizzle_orm_1 = require("drizzle-orm");
5
+ const enforcement_js_1 = require("./enforcement.js");
6
+ const events_js_1 = require("./events.js");
7
+ const outbox_js_1 = require("./outbox.js");
8
+ const reports_js_1 = require("./reports.js");
9
+ const transaction_js_1 = require("./transaction.js");
10
+ /**
11
+ * Everything this package writes, in Postgres.
12
+ *
13
+ * One factory rather than five, because the five members must share one handle: a
14
+ * report and its outbox row commit in the SAME transaction, and a store assembled
15
+ * from two handles would type-check perfectly and quietly lose that.
16
+ *
17
+ * Unlike the Mongo factory, this one CREATES NOTHING. There is no schema to
18
+ * register: the adopter's own migration created the tables, generated from the
19
+ * definitions this package exports, in the adopter's own journal. That asymmetry
20
+ * is deliberate and is the reason no migrations folder ships here — two journals
21
+ * against one `drizzle.__drizzle_migrations` table interleave, and the loser is
22
+ * skipped in silence with exit 0.
23
+ */
24
+ function postgresModerationStore(input) {
25
+ const { db, reportTable, tables } = input;
26
+ return {
27
+ transaction: (0, transaction_js_1.postgresTransactionRunner)(db),
28
+ outbox: (0, outbox_js_1.postgresOutboxStore)({ db, tables }),
29
+ events: (0, events_js_1.postgresEventStore)({ db, tables }),
30
+ enforcement: (0, enforcement_js_1.postgresEnforcementStore)({ db, tables }),
31
+ reports: (0, reports_js_1.postgresReportStore)({ db, reportTable }),
32
+ /**
33
+ * ASSERTS the schema, rather than creating it.
34
+ *
35
+ * The Mongo half calls `init()` and builds indexes; there is nothing here to
36
+ * build, so what remains is worth doing for its own sake: confirming the four
37
+ * tables this package queries exist BEFORE the first report is filed rather
38
+ * than at the first delivery.
39
+ *
40
+ * A missing table raises `42P01 undefined_table` and names it. `limit(0)`
41
+ * means Postgres parses and plans each statement — which is what proves the
42
+ * relation resolves — without reading a row.
43
+ *
44
+ * It cannot check COLUMNS. An adopter whose migration is a version behind
45
+ * gets a compile error from `ModerationReportTable` for a missing column and
46
+ * a `42703` from the query for a stale migration; both are loud, and neither
47
+ * is this function's job.
48
+ */
49
+ async ensureSchema() {
50
+ const present = [
51
+ tables.outbox,
52
+ tables.events,
53
+ tables.enforcements,
54
+ reportTable,
55
+ ];
56
+ for (const table of present) {
57
+ await db.select({ resolves: (0, drizzle_orm_1.sql) `1` }).from(table).limit(0);
58
+ }
59
+ },
60
+ };
61
+ }
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/index.ts"],"names":[],"mappings":";;AA0BA,0DA6CC;AAvED,6CAAkC;AAMlC,qDAA4D;AAC5D,2CAAiD;AACjD,2CAAkD;AAClD,6CAAmD;AACnD,qDAAsF;AAEtF;;;;;;;;;;;;;GAaG;AACH,SAAgB,uBAAuB,CAAyC,KAM/E;IACC,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAE1C,OAAO;QACL,WAAW,EAAE,IAAA,0CAAyB,EAAC,EAAE,CAAC;QAC1C,MAAM,EAAE,IAAA,+BAAmB,EAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;QAC3C,MAAM,EAAE,IAAA,8BAAkB,EAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;QAC1C,WAAW,EAAE,IAAA,yCAAwB,EAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;QACrD,OAAO,EAAE,IAAA,gCAAmB,EAAU,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC;QAE1D;;;;;;;;;;;;;;;;WAgBG;QACH,KAAK,CAAC,YAAY;YAChB,MAAM,OAAO,GAAuB;gBAClC,MAAM,CAAC,MAAM;gBACb,MAAM,CAAC,MAAM;gBACb,MAAM,CAAC,YAAY;gBACnB,WAAW;aACZ,CAAC;YACF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAA,iBAAG,EAAA,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { ModerationOutboxStore } from '../../store/types.js';
2
+ import type { ModerationTables } from '../tables.js';
3
+ import type { ModerationPgHandle } from './transaction.js';
4
+ export declare function postgresOutboxStore(input: {
5
+ db: ModerationPgHandle;
6
+ tables: ModerationTables;
7
+ }): ModerationOutboxStore<ModerationPgHandle>;
8
+ //# sourceMappingURL=outbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox.d.ts","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/outbox.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAqC3D,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,EAAE,EAAE,kBAAkB,CAAC;IACvB,MAAM,EAAE,gBAAgB,CAAC;CAC1B,GAAG,qBAAqB,CAAC,kBAAkB,CAAC,CAkO5C"}
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.postgresOutboxStore = postgresOutboxStore;
4
+ const drizzle_orm_1 = require("drizzle-orm");
5
+ const pg_core_1 = require("drizzle-orm/pg-core");
6
+ const service_js_1 = require("../../outbox/service.js");
7
+ function postgresOutboxStore(input) {
8
+ const { db } = input;
9
+ const outbox = input.tables.outbox;
10
+ /**
11
+ * Due work, as one predicate.
12
+ *
13
+ * Either `pending` and past its `available_at`, or `processing` with an EXPIRED
14
+ * lease — the second arm is what makes a dead worker's event reclaimable rather
15
+ * than stranded. The two arms have an index each
16
+ * (`moderation_outbox_due_idx`, `moderation_outbox_lease_idx`).
17
+ */
18
+ const dueOrExpired = (now) => (0, drizzle_orm_1.or)((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(outbox.status, 'pending'), (0, drizzle_orm_1.lte)(outbox.availableAt, now)), (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(outbox.status, 'processing'), (0, drizzle_orm_1.lte)(outbox.leaseUntil, now)));
19
+ /** The lease this caller claims to hold, still live. Shared by all three transitions. */
20
+ const heldLease = (eventId, leaseOwner, now) => (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(outbox.id, eventId), (0, drizzle_orm_1.eq)(outbox.status, 'processing'), (0, drizzle_orm_1.eq)(outbox.leaseOwner, leaseOwner), (0, drizzle_orm_1.gt)(outbox.leaseUntil, now));
21
+ const toEvent = (row) => ({
22
+ id: row.id,
23
+ kind: row.kind,
24
+ payload: row.payload,
25
+ attempts: row.attempts,
26
+ availableAt: row.availableAt,
27
+ // `null` is how Postgres stores "no lease"; the event type says absent.
28
+ ...(row.leaseOwner === null ? {} : { leaseOwner: row.leaseOwner }),
29
+ ...(row.leaseUntil === null ? {} : { leaseUntil: row.leaseUntil }),
30
+ expiresAt: row.expiresAt,
31
+ createdAt: row.createdAt,
32
+ });
33
+ return {
34
+ async enqueue(event, tx) {
35
+ /**
36
+ * The guard, and it is not the same mistake Mongo's guards against.
37
+ *
38
+ * Both `db` and `tx` are a `ModerationPgHandle`, so handing this the POOL
39
+ * handle type-checks perfectly — and then the row commits on its own
40
+ * connection, independently of the domain write it was supposed to be
41
+ * atomic with. That is "the report was answered 201 and never delivered",
42
+ * reached by a different route than a session nobody opened a transaction
43
+ * on, with the same silence.
44
+ *
45
+ * `PgTransaction` is a real runtime class in `drizzle-orm/pg-core`, so this
46
+ * is a genuine check rather than a duck-typed guess.
47
+ */
48
+ if (!(tx instanceof pg_core_1.PgTransaction)) {
49
+ throw new service_js_1.ModerationOutboxTransactionError(event.eventId);
50
+ }
51
+ /**
52
+ * `ON CONFLICT DO NOTHING`, never `DO UPDATE`.
53
+ *
54
+ * A repeated enqueue is ORDINARY — a transaction retry, two concurrent
55
+ * duplicate submissions, a reconciliation sweep re-deriving an event — and
56
+ * the dispatcher is concurrently taking, renewing and completing leases on
57
+ * these same rows. `DO UPDATE` would make each repeat a real write, which
58
+ * conflicts with a live lease update and aborts the enclosing transaction:
59
+ * the exact defect the Mongo store's `timestamps: false` exists to prevent,
60
+ * reintroduced in a dialect where nothing forces it on you.
61
+ *
62
+ * One behavioural difference from Mongo, and Postgres has the better end of
63
+ * it: if a CONCURRENT UNCOMMITTED transaction holds this same key, Postgres
64
+ * WAITS for it and then proceeds (finding the row committed, and doing
65
+ * nothing), where Mongo raises `WriteConflict` (code 112) and aborts the
66
+ * enclosing transaction. Waiting is the outcome a caller wants.
67
+ *
68
+ * `created_at` and `updated_at` are written explicitly from the caller's
69
+ * clock rather than left to their defaults, so both backends stamp a row
70
+ * from one instant — and so a test can assert that a repeat changed
71
+ * NOTHING, which is a stronger claim than "no duplicate row".
72
+ */
73
+ await tx
74
+ .insert(outbox)
75
+ .values({
76
+ id: event.eventId,
77
+ kind: event.kind,
78
+ payload: event.payload,
79
+ status: 'pending',
80
+ attempts: 0,
81
+ availableAt: event.availableAt,
82
+ expiresAt: event.expiresAt,
83
+ createdAt: event.now,
84
+ updatedAt: event.now,
85
+ })
86
+ .onConflictDoNothing({ target: outbox.id });
87
+ },
88
+ async claim({ leaseOwner, leaseUntil, now, eventId }) {
89
+ /**
90
+ * `SKIP LOCKED` is load-bearing, not tuning.
91
+ *
92
+ * Without it, under READ COMMITTED the sub-select is evaluated once: the
93
+ * loser blocks on the head row and then returns ZERO rows. `claim` answers
94
+ * `null`, `dispatch` breaks out of its batch, and a deployment running N
95
+ * tasks silently drains at 1/N the rate with nothing failing anywhere.
96
+ *
97
+ * The `FOR UPDATE` lives in the SUB-SELECT because that is what locks the
98
+ * one row this claim intends to take, before the UPDATE touches it — the
99
+ * documented Postgres idiom for a work queue.
100
+ */
101
+ const due = db
102
+ .select({ id: outbox.id })
103
+ .from(outbox)
104
+ .where(eventId === undefined
105
+ ? dueOrExpired(now)
106
+ : (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(outbox.id, eventId), dueOrExpired(now)))
107
+ // Oldest first, so a backlog drains in the order it was filed rather than
108
+ // in whatever order the storage engine finds convenient.
109
+ .orderBy((0, drizzle_orm_1.asc)(outbox.createdAt))
110
+ .limit(1)
111
+ .for('update', { skipLocked: true });
112
+ const rows = await db
113
+ .update(outbox)
114
+ .set({
115
+ status: 'processing',
116
+ leaseOwner,
117
+ leaseUntil,
118
+ // The COLUMN is interpolated, not a value: `attempts` is incremented by
119
+ // the database so two claimers cannot both read 3 and both write 4.
120
+ attempts: (0, drizzle_orm_1.sql) `${outbox.attempts} + 1`,
121
+ // The previous attempt's error, cleared with the claim so a stale
122
+ // message can never be read as this attempt's. `null` CLEARS in
123
+ // drizzle; `undefined` would leave it alone.
124
+ lastError: null,
125
+ updatedAt: now,
126
+ })
127
+ .where((0, drizzle_orm_1.inArray)(outbox.id, due))
128
+ .returning({
129
+ id: outbox.id,
130
+ kind: outbox.kind,
131
+ payload: outbox.payload,
132
+ attempts: outbox.attempts,
133
+ availableAt: outbox.availableAt,
134
+ leaseOwner: outbox.leaseOwner,
135
+ leaseUntil: outbox.leaseUntil,
136
+ expiresAt: outbox.expiresAt,
137
+ createdAt: outbox.createdAt,
138
+ });
139
+ const [row] = rows;
140
+ return row === undefined ? null : toEvent(row);
141
+ },
142
+ /**
143
+ * ## Why all three transitions read `RETURNING`, and what that collapses
144
+ *
145
+ * Mongo's `complete` and `fail` answer `modifiedCount === 1` while its
146
+ * `renew` answers `matchedCount === 1`. `RETURNING` counts MATCHED rows, so
147
+ * this store answers the `matchedCount` question in all three places.
148
+ *
149
+ * That is equivalent HERE, and the argument is worth writing down because it
150
+ * is an argument rather than a test: the WHERE clause requires
151
+ * `status = 'processing'`, and `complete` and `fail` both write a different
152
+ * status, so a matched row is always a modified row. `renew` writes only
153
+ * `lease_until`/`updated_at` and Mongo already used `matchedCount` for it —
154
+ * a renewal that lands on the values already stored is still a lease this
155
+ * caller holds, and reporting it as lost would make a dispatcher abandon an
156
+ * event it still owns.
157
+ *
158
+ * If a later transition ever stops changing `status`, this equivalence stops
159
+ * holding and the difference becomes silent.
160
+ */
161
+ async complete({ eventId, leaseOwner, now }) {
162
+ const rows = await db
163
+ .update(outbox)
164
+ .set({
165
+ status: 'processed',
166
+ processedAt: now,
167
+ updatedAt: now,
168
+ leaseOwner: null,
169
+ leaseUntil: null,
170
+ lastError: null,
171
+ })
172
+ .where(heldLease(eventId, leaseOwner, now))
173
+ .returning({ id: outbox.id });
174
+ return rows.length === 1;
175
+ },
176
+ async renew({ eventId, leaseOwner, leaseUntil, now }) {
177
+ const rows = await db
178
+ .update(outbox)
179
+ .set({ leaseUntil, updatedAt: now })
180
+ .where(heldLease(eventId, leaseOwner, now))
181
+ .returning({ id: outbox.id });
182
+ return rows.length === 1;
183
+ },
184
+ async fail({ eventId, leaseOwner, status, availableAt, lastError, now }) {
185
+ const rows = await db
186
+ .update(outbox)
187
+ .set({
188
+ status,
189
+ availableAt,
190
+ lastError,
191
+ updatedAt: now,
192
+ leaseOwner: null,
193
+ leaseUntil: null,
194
+ })
195
+ .where(heldLease(eventId, leaseOwner, now))
196
+ .returning({ id: outbox.id });
197
+ return rows.length === 1;
198
+ },
199
+ async statusOf(eventId) {
200
+ const rows = await db
201
+ .select({ status: outbox.status })
202
+ .from(outbox)
203
+ .where((0, drizzle_orm_1.eq)(outbox.id, eventId))
204
+ .limit(1);
205
+ const [row] = rows;
206
+ return row === undefined ? null : row.status;
207
+ },
208
+ };
209
+ }
210
+ //# sourceMappingURL=outbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/outbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import type { ModerationReportStore } from '../../store/types.js';
2
+ import type { ModerationReportFields } from '../../types.js';
3
+ import type { ModerationReportTable } from '../reportColumns.js';
4
+ import type { ModerationPgHandle } from './transaction.js';
5
+ /**
6
+ * The application's own report table, in Postgres.
7
+ *
8
+ * The only store that takes a table it did not define, which is what makes the
9
+ * structural type worth having: an adopter who forgets a column this package
10
+ * queries gets a COMPILE ERROR at the `postgresModerationStore(...)` call rather
11
+ * than a runtime failure on the first delivery.
12
+ *
13
+ * `scripts/test-report-table-type.mjs` proves that gate in both directions and
14
+ * records exactly what it does not catch — a column present under the right name
15
+ * but the wrong TYPE passes, because every member of `ModerationReportTable` is a
16
+ * bare `PgColumn`. The DDL and the schema tests are what cover that half.
17
+ *
18
+ * ## Two Mongo hazards that do not exist here
19
+ *
20
+ * **A malformed id is not an error.** `id` is `text`, so an id nothing could have
21
+ * generated simply matches no rows and `findById` answers `null` — which is
22
+ * exactly what the delivery worker already does with "the report is gone".
23
+ * Mongoose raises a `CastError` for the same input and its store has to catch it.
24
+ * There is nothing to catch here, and adding a branch for `22P02` would be
25
+ * writing a handler for an error this column cannot raise.
26
+ *
27
+ * **A bound parameter cannot become a query operator.** `requireIdentifier` in
28
+ * `intake.ts` still runs, and still should — a non-string corrupts data on any
29
+ * backend, and the function is exported for callers with no route validation. But
30
+ * the specific failure it was written for, `{ $ne: null }` arriving as a value and
31
+ * matching an unrelated report, is a Mongo shape: here a parameter is a parameter.
32
+ *
33
+ * ## One thing that is worse, stated plainly
34
+ *
35
+ * Every read hands the caller `TReport`, and no type can prove that: the adopter
36
+ * owns the type AND the table, and the two are only connected by their names. See
37
+ * {@link asReport}.
38
+ */
39
+ /**
40
+ * The fields `ModerationReportFields` declares OPTIONAL, computed from the type.
41
+ *
42
+ * Postgres stores an absent value as NULL; Mongo omits the field. Both are the
43
+ * same claim — "this never happened" — and one suite has to be able to assert it
44
+ * once, so the package's own optional fields come back ABSENT from either
45
+ * backend. Two `describe.each` pairs failed on exactly this before it was
46
+ * settled, both reading `expected null to be undefined`, and neither was about
47
+ * behaviour.
48
+ *
49
+ * Only the fields the PORT owns are normalised. An adopter's own nullable column
50
+ * is theirs: `extra` goes in untouched and comes back untouched.
51
+ */
52
+ type OptionalReportField = {
53
+ [K in keyof ModerationReportFields]-?: undefined extends ModerationReportFields[K] ? K : never;
54
+ }[keyof ModerationReportFields];
55
+ declare const OPTIONAL_REPORT_FIELDS: readonly ["details", "localStatusReason", "crowdSourceReportId", "crowdSourceCaseId", "crowdSourceMerged", "contentSnapshotHash", "submittedAt", "lastDeliveryError", "decisionId", "decisionRevision", "decisionOutcome", "decisionStatus", "decidedAt", "enforcedAction", "enforcedAt"];
56
+ /** `T` when it is `never`, and a compile error naming the field when it is not. */
57
+ type AssertNever<T extends never> = T;
58
+ /**
59
+ * The exhaustiveness gate. `satisfies` above refuses a field that is not optional;
60
+ * THIS refuses an optional field that is missing from the list — the direction
61
+ * that would otherwise ship as a backend difference nobody looked for.
62
+ */
63
+ export type UncoveredOptionalReportField = AssertNever<Exclude<OptionalReportField, (typeof OPTIONAL_REPORT_FIELDS)[number]>>;
64
+ export declare function postgresReportStore<TReport extends ModerationReportFields>(input: {
65
+ db: ModerationPgHandle;
66
+ reportTable: ModerationReportTable;
67
+ }): ModerationReportStore<TReport, ModerationPgHandle>;
68
+ export {};
69
+ //# sourceMappingURL=reports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/reports.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH;;;;;;;;;;;;GAYG;AACH,KAAK,mBAAmB,GAAG;KACxB,CAAC,IAAI,MAAM,sBAAsB,CAAC,CAAC,GAAG,SAAS,SAAS,sBAAsB,CAAC,CAAC,CAAC,GAC9E,CAAC,GACD,KAAK;CACV,CAAC,MAAM,sBAAsB,CAAC,CAAC;AAEhC,QAAA,MAAM,sBAAsB,2RAgBuB,CAAC;AAEpD,mFAAmF;AACnF,KAAK,WAAW,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC;AAEtC;;;;GAIG;AACH,MAAM,MAAM,4BAA4B,GAAG,WAAW,CACpD,OAAO,CAAC,mBAAmB,EAAE,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC,CACtE,CAAC;AA+BF,wBAAgB,mBAAmB,CAAC,OAAO,SAAS,sBAAsB,EAAE,KAAK,EAAE;IACjF,EAAE,EAAE,kBAAkB,CAAC;IACvB,WAAW,EAAE,qBAAqB,CAAC;CACpC,GAAG,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAwMrD"}