@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,43 @@
1
+ "use strict";
2
+ /**
3
+ * `@crowdsource.you/core/testing` — integrate against CrowdSource before a jury
4
+ * exists.
5
+ *
6
+ * ```ts
7
+ * import { createCrowdSourceSandbox } from '@crowdsource.you/core/testing';
8
+ * import { CrowdSource } from '@crowdsource.you/core';
9
+ *
10
+ * const sandbox = createCrowdSourceSandbox();
11
+ * const crowdsource = new CrowdSource({
12
+ * serviceKey: sandbox.serviceKey,
13
+ * baseUrl: sandbox.baseUrl,
14
+ * fetch: sandbox.fetch,
15
+ * });
16
+ *
17
+ * const { caseId } = await crowdsource.reports.create({ ... });
18
+ * const decision = sandbox.decide(caseId, { outcome: 'violation' });
19
+ * await sandbox.deliver('http://localhost:3000/webhooks/crowdsource', sandbox.eventFor(decision));
20
+ * ```
21
+ *
22
+ * The report goes through the real client, the sandbox applies the real rules
23
+ * (tenant from the credential, idempotency, 409 on a changed body, one case per
24
+ * reported version), and the webhook that comes back is genuinely signed — so
25
+ * the receiver being tested is the receiver that will run in production.
26
+ *
27
+ * The simulator can also deliver a stale, forged or tampered event on purpose.
28
+ * Asserting that a receiver REFUSES those is the half of a webhook test that
29
+ * actually proves something.
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.signWebhookDelivery = exports.WebhookSimulator = exports.createCrowdSourceSandbox = exports.CrowdSourceSandbox = exports.decisionFixture = exports.caseEnvelopeFixture = exports.caseDecidedEventFixture = void 0;
33
+ var fixtures_js_1 = require("./fixtures.js");
34
+ Object.defineProperty(exports, "caseDecidedEventFixture", { enumerable: true, get: function () { return fixtures_js_1.caseDecidedEventFixture; } });
35
+ Object.defineProperty(exports, "caseEnvelopeFixture", { enumerable: true, get: function () { return fixtures_js_1.caseEnvelopeFixture; } });
36
+ Object.defineProperty(exports, "decisionFixture", { enumerable: true, get: function () { return fixtures_js_1.decisionFixture; } });
37
+ var sandbox_js_1 = require("./sandbox.js");
38
+ Object.defineProperty(exports, "CrowdSourceSandbox", { enumerable: true, get: function () { return sandbox_js_1.CrowdSourceSandbox; } });
39
+ Object.defineProperty(exports, "createCrowdSourceSandbox", { enumerable: true, get: function () { return sandbox_js_1.createCrowdSourceSandbox; } });
40
+ var webhook_simulator_js_1 = require("./webhook-simulator.js");
41
+ Object.defineProperty(exports, "WebhookSimulator", { enumerable: true, get: function () { return webhook_simulator_js_1.WebhookSimulator; } });
42
+ Object.defineProperty(exports, "signWebhookDelivery", { enumerable: true, get: function () { return webhook_simulator_js_1.signWebhookDelivery; } });
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAEH,6CAIuB;AAHrB,sHAAA,uBAAuB,OAAA;AACvB,kHAAA,mBAAmB,OAAA;AACnB,8GAAA,eAAe,OAAA;AAQjB,2CAA4E;AAAnE,gHAAA,kBAAkB,OAAA;AAAE,sHAAA,wBAAwB,OAAA;AAQrD,+DAA+E;AAAtE,wHAAA,gBAAgB,OAAA;AAAE,2HAAA,mBAAmB,OAAA"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * An in-process CrowdSource, so an integration can be exercised end to end
3
+ * before a jury exists.
4
+ *
5
+ * The path an application actually cares about is: deliver a report → get a
6
+ * case → a decision is published → a signed webhook arrives → enforcement runs.
7
+ * Today the middle of that does not exist anywhere — sortition, review and
8
+ * consensus are not built, so nothing publishes a decision. Waiting for them
9
+ * means an integrator cannot write, or test, the half of their code that
10
+ * matters most: what they DO when a decision says `violation`.
11
+ *
12
+ * So this sandbox implements the two ends and lets the test drive the middle.
13
+ * It accepts reports over a `fetch` the real `@crowdsource.you/core` client can be
14
+ * pointed at — same transport, same idempotency, same 409, same tenant check —
15
+ * and `decide()` publishes a decision the way consensus eventually will, which
16
+ * `deliver()` then sends as a genuinely signed `case.decided`.
17
+ *
18
+ * **What it is and is not.** It is a faithful implementation of the RULES an
19
+ * integrator's code depends on: `applicationId` from the credential, an
20
+ * idempotency key that returns the same `reportId`, a 409 for a reused
21
+ * `externalReportId` with a changed body, and §7.3's "two reports about the same
22
+ * version of the same content are one case". It is NOT the service, it holds no
23
+ * state between processes, and where it and the backend ever disagree the
24
+ * backend is right. The deduplication projection below mirrors the backend's
25
+ * `modules/evidence/contentSnapshot.ts`; that logic belongs in the contracts
26
+ * package so both sides share one implementation, and until it moves this file
27
+ * is the second copy — which is a thing to know rather than a thing to rely on.
28
+ */
29
+ import { type CaseEnvelope, type Decision, type DecisionFinding, type DecisionOutcome, type DecisionRecommendedAction, type KnownWebhookEvent, type TaxonomyCode } from '@crowdsource.you/contracts';
30
+ import { type WebhookDeliveryResult } from './webhook-simulator.js';
31
+ export interface SandboxReport {
32
+ readonly reportId: string;
33
+ readonly externalReportId: string;
34
+ readonly caseId: string;
35
+ readonly idempotencyKey: string;
36
+ readonly envelope: CaseEnvelope;
37
+ /** `merged` when this report joined a case another report had already opened. */
38
+ readonly status: 'received' | 'merged';
39
+ readonly receivedAt: string;
40
+ /**
41
+ * The fingerprint of what was delivered — the backend calls it `payloadHash`
42
+ * and computes it over `{ externalReportId, envelope }`, deliberately without
43
+ * the idempotency key, so the same content re-sent under a fresh key is
44
+ * recognised as the same report rather than conflicting with it.
45
+ */
46
+ readonly payloadHash: string;
47
+ }
48
+ export interface SandboxCase {
49
+ readonly caseId: string;
50
+ readonly externalSubjectId: string;
51
+ readonly dedupKey: string;
52
+ readonly policy: CaseEnvelope['policy'];
53
+ /** The union of what every merged report alleged (§6.2). */
54
+ readonly allegationCodes: readonly TaxonomyCode[];
55
+ readonly reportIds: readonly string[];
56
+ readonly decisions: readonly Decision[];
57
+ }
58
+ export interface CrowdSourceSandboxOptions {
59
+ readonly applicationId?: string;
60
+ readonly organizationId?: string;
61
+ readonly webhookSecret?: string;
62
+ /** The origin the client believes it is calling. Never reached over a socket. */
63
+ readonly baseUrl?: string;
64
+ }
65
+ export interface SandboxDecisionInput {
66
+ readonly outcome?: DecisionOutcome;
67
+ readonly status?: Decision['status'];
68
+ readonly findings?: readonly DecisionFinding[];
69
+ readonly recommendedActions?: readonly DecisionRecommendedAction[];
70
+ readonly confidence?: number;
71
+ }
72
+ export declare class CrowdSourceSandbox {
73
+ readonly applicationId: string;
74
+ readonly organizationId: string;
75
+ readonly baseUrl: string;
76
+ readonly webhookSecret: string;
77
+ /** The one opaque string an integration configures. */
78
+ readonly serviceKey: string;
79
+ private readonly bearerToken;
80
+ private readonly reportsById;
81
+ private readonly reportsByIdempotencyKey;
82
+ private readonly reportsByExternalId;
83
+ private readonly casesById;
84
+ private readonly caseIdByDedupKey;
85
+ private readonly decisionsById;
86
+ constructor(options?: CrowdSourceSandboxOptions);
87
+ get reports(): readonly SandboxReport[];
88
+ get cases(): readonly SandboxCase[];
89
+ /** The `fetch` to hand `new CrowdSource({ fetch, serviceKey, baseUrl })`. */
90
+ readonly fetch: typeof globalThis.fetch;
91
+ /**
92
+ * Publishes a decision for a case, the way consensus eventually will.
93
+ *
94
+ * A published revision is immutable (Appendix F), so calling this twice
95
+ * produces revision 2 superseding revision 1 rather than editing anything.
96
+ */
97
+ decide(caseId: string, input?: SandboxDecisionInput): Decision;
98
+ /** The `case.decided` (or `decision.corrected`) event for a decision. */
99
+ eventFor(decision: Decision): KnownWebhookEvent;
100
+ /** Signs and POSTs an event to the integration's webhook endpoint. */
101
+ deliver(url: string, event: unknown): Promise<WebhookDeliveryResult>;
102
+ private createReport;
103
+ private receiptResponse;
104
+ private readReport;
105
+ private readCase;
106
+ private readDecision;
107
+ }
108
+ export declare function createCrowdSourceSandbox(options?: CrowdSourceSandboxOptions): CrowdSourceSandbox;
109
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/testing/sandbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,342 @@
1
+ "use strict";
2
+ /**
3
+ * An in-process CrowdSource, so an integration can be exercised end to end
4
+ * before a jury exists.
5
+ *
6
+ * The path an application actually cares about is: deliver a report → get a
7
+ * case → a decision is published → a signed webhook arrives → enforcement runs.
8
+ * Today the middle of that does not exist anywhere — sortition, review and
9
+ * consensus are not built, so nothing publishes a decision. Waiting for them
10
+ * means an integrator cannot write, or test, the half of their code that
11
+ * matters most: what they DO when a decision says `violation`.
12
+ *
13
+ * So this sandbox implements the two ends and lets the test drive the middle.
14
+ * It accepts reports over a `fetch` the real `@crowdsource.you/core` client can be
15
+ * pointed at — same transport, same idempotency, same 409, same tenant check —
16
+ * and `decide()` publishes a decision the way consensus eventually will, which
17
+ * `deliver()` then sends as a genuinely signed `case.decided`.
18
+ *
19
+ * **What it is and is not.** It is a faithful implementation of the RULES an
20
+ * integrator's code depends on: `applicationId` from the credential, an
21
+ * idempotency key that returns the same `reportId`, a 409 for a reused
22
+ * `externalReportId` with a changed body, and §7.3's "two reports about the same
23
+ * version of the same content are one case". It is NOT the service, it holds no
24
+ * state between processes, and where it and the backend ever disagree the
25
+ * backend is right. The deduplication projection below mirrors the backend's
26
+ * `modules/evidence/contentSnapshot.ts`; that logic belongs in the contracts
27
+ * package so both sides share one implementation, and until it moves this file
28
+ * is the second copy — which is a thing to know rather than a thing to rely on.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.CrowdSourceSandbox = void 0;
32
+ exports.createCrowdSourceSandbox = createCrowdSourceSandbox;
33
+ const node_crypto_1 = require("node:crypto");
34
+ const contracts_1 = require("@crowdsource.you/contracts");
35
+ const webhook_simulator_js_1 = require("./webhook-simulator.js");
36
+ const DEFAULT_BASE_URL = 'https://sandbox.crowdsource.test';
37
+ function publicId(prefix) {
38
+ return `${prefix}_${(0, node_crypto_1.randomUUID)().replace(/-/g, '')}`;
39
+ }
40
+ function sha256Hex(value) {
41
+ return (0, node_crypto_1.createHash)('sha256').update(value, 'utf8').digest('hex');
42
+ }
43
+ /**
44
+ * §7.3's `contentEnvelopeHash`, over the same projection the backend hashes.
45
+ *
46
+ * The three normalisations that make two reporters agree, restated so a reader
47
+ * does not have to open the backend to know what is excluded: order is removed
48
+ * where it is not meaning, proof-of-delivery fields are dropped from principal
49
+ * bindings, and nothing per-reporter is included at all — no
50
+ * `externalReportId`, no `source`, no `allegations`, no reporter binding, no
51
+ * `urgency`, no `metadata`.
52
+ */
53
+ function contentHashOf(envelope) {
54
+ const referencedPrincipals = new Set();
55
+ for (const resource of envelope.resources) {
56
+ if (resource.authorPrincipalRef !== undefined) {
57
+ referencedPrincipals.add(resource.authorPrincipalRef);
58
+ }
59
+ if (resource.type === 'listing' && resource.data.sellerRef !== undefined) {
60
+ referencedPrincipals.add(resource.data.sellerRef);
61
+ }
62
+ }
63
+ for (const relation of envelope.relations) {
64
+ if (contracts_1.PRINCIPAL_TARGETED_RELATION_TYPES.some((type) => type === relation.type)) {
65
+ referencedPrincipals.add(relation.to);
66
+ }
67
+ }
68
+ const snapshot = {
69
+ schemaVersion: envelope.schemaVersion,
70
+ subject: envelope.subject,
71
+ resources: [...envelope.resources].sort((left, right) => (left.id < right.id ? -1 : 1)),
72
+ relations: [...envelope.relations].sort((left, right) => `${left.from} ${left.type} ${left.to}` < `${right.from} ${right.type} ${right.to}` ? -1 : 1),
73
+ principals: envelope.principalBindings
74
+ .filter((binding) => referencedPrincipals.has(binding.principalRef))
75
+ .sort((left, right) => (left.principalRef < right.principalRef ? -1 : 1))
76
+ .map((binding) => ({
77
+ principalRef: binding.principalRef,
78
+ type: binding.type,
79
+ externalPrincipalId: binding.externalPrincipalId,
80
+ })),
81
+ };
82
+ return sha256Hex(JSON.stringify(snapshot));
83
+ }
84
+ function jsonResponse(status, body) {
85
+ return new Response(JSON.stringify(body), {
86
+ status,
87
+ headers: { 'content-type': 'application/json' },
88
+ });
89
+ }
90
+ function apiError(status, code, message) {
91
+ return jsonResponse(status, { error: { code, message } });
92
+ }
93
+ class CrowdSourceSandbox {
94
+ constructor(options = {}) {
95
+ this.reportsById = new Map();
96
+ this.reportsByIdempotencyKey = new Map();
97
+ this.reportsByExternalId = new Map();
98
+ this.casesById = new Map();
99
+ this.caseIdByDedupKey = new Map();
100
+ this.decisionsById = new Map();
101
+ /** The `fetch` to hand `new CrowdSource({ fetch, serviceKey, baseUrl })`. */
102
+ this.fetch = async (input, init) => {
103
+ const url = new URL(typeof input === 'string' ? input : input instanceof URL ? input.href : input.url);
104
+ const method = (init?.method ?? 'GET').toUpperCase();
105
+ const headers = new Headers(init?.headers);
106
+ if (headers.get('authorization') !== `Bearer ${this.bearerToken}`) {
107
+ return apiError(401, 'unauthorized', 'The service credential is missing or invalid.');
108
+ }
109
+ if (method === 'POST' && url.pathname === '/v1/reports') {
110
+ return this.createReport(headers, init?.body);
111
+ }
112
+ if (method === 'GET' && url.pathname.startsWith('/v1/reports/')) {
113
+ return this.readReport(decodeURIComponent(url.pathname.slice('/v1/reports/'.length)));
114
+ }
115
+ if (method === 'GET' && url.pathname.startsWith('/v1/cases/')) {
116
+ return this.readCase(decodeURIComponent(url.pathname.slice('/v1/cases/'.length)));
117
+ }
118
+ if (method === 'GET' && url.pathname.startsWith('/v1/decisions/')) {
119
+ return this.readDecision(decodeURIComponent(url.pathname.slice('/v1/decisions/'.length)));
120
+ }
121
+ /**
122
+ * Everything else, including the upload routes, answers 404 — the same thing
123
+ * the deployed backend does today. A sandbox that faked an endpoint the
124
+ * service does not serve would let an integration pass its tests and fail on
125
+ * its first real call.
126
+ */
127
+ return apiError(404, 'not_found', `The sandbox does not serve ${method} ${url.pathname}.`);
128
+ };
129
+ this.applicationId = options.applicationId ?? publicId('app');
130
+ this.organizationId = options.organizationId ?? publicId('org');
131
+ this.baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
132
+ this.webhookSecret = options.webhookSecret ?? (0, node_crypto_1.randomUUID)();
133
+ const credentialId = publicId('csk');
134
+ const secret = (0, node_crypto_1.randomUUID)().replace(/-/g, '');
135
+ this.bearerToken = `${credentialId}.${secret}`;
136
+ this.serviceKey = [this.applicationId, credentialId, secret].join(':');
137
+ }
138
+ get reports() {
139
+ return [...this.reportsById.values()];
140
+ }
141
+ get cases() {
142
+ return [...this.casesById.values()];
143
+ }
144
+ /**
145
+ * Publishes a decision for a case, the way consensus eventually will.
146
+ *
147
+ * A published revision is immutable (Appendix F), so calling this twice
148
+ * produces revision 2 superseding revision 1 rather than editing anything.
149
+ */
150
+ decide(caseId, input = {}) {
151
+ const stored = this.casesById.get(caseId);
152
+ if (!stored)
153
+ throw new Error(`The sandbox holds no case '${caseId}'.`);
154
+ const previous = stored.decisions.at(-1);
155
+ const revision = (previous?.revision ?? 0) + 1;
156
+ const outcome = input.outcome ?? 'violation';
157
+ const decision = contracts_1.DecisionSchema.parse({
158
+ id: publicId('dec'),
159
+ caseId,
160
+ revision,
161
+ status: input.status ?? 'final',
162
+ outcome,
163
+ contextSufficiency: outcome === 'insufficient_context' ? 'insufficient' : 'sufficient',
164
+ confidence: input.confidence ?? 1,
165
+ findings: input.findings ??
166
+ (outcome === 'violation'
167
+ ? [
168
+ {
169
+ code: stored.allegationCodes[0] ?? 'other.unclassifiable',
170
+ resourceIds: ['res_subject'],
171
+ severity: 'medium',
172
+ scope: 'application_local',
173
+ attribution: 'author',
174
+ },
175
+ ]
176
+ : []),
177
+ recommendedActions: input.recommendedActions ??
178
+ (outcome === 'violation' ? [{ action: 'remove_or_restrict' }] : [{ action: 'no_action' }]),
179
+ jury: { size: 3, decisiveVotes: 3, winningVotes: 3, agreement: 1, specialistPresent: false },
180
+ policyVersions: {
181
+ taxonomy: contracts_1.UNIVERSAL_TAXONOMY_VERSION,
182
+ application: stored.policy.version,
183
+ oxyConduct: stored.policy.version,
184
+ },
185
+ ...(previous === undefined ? {} : { supersedesDecisionId: previous.id }),
186
+ publishedAt: new Date().toISOString(),
187
+ });
188
+ this.decisionsById.set(decision.id, decision);
189
+ this.casesById.set(caseId, { ...stored, decisions: [...stored.decisions, decision] });
190
+ return decision;
191
+ }
192
+ /** The `case.decided` (or `decision.corrected`) event for a decision. */
193
+ eventFor(decision) {
194
+ return contracts_1.KnownWebhookEventSchema.parse({
195
+ id: publicId('evt'),
196
+ type: decision.revision === 1 ? 'case.decided' : 'decision.corrected',
197
+ createdAt: new Date().toISOString(),
198
+ organizationId: this.organizationId,
199
+ applicationId: this.applicationId,
200
+ data: { caseId: decision.caseId, decision },
201
+ });
202
+ }
203
+ /** Signs and POSTs an event to the integration's webhook endpoint. */
204
+ async deliver(url, event) {
205
+ return await new webhook_simulator_js_1.WebhookSimulator({ secret: this.webhookSecret, url }).deliver(event);
206
+ }
207
+ createReport(headers, body) {
208
+ const idempotencyKey = headers.get('idempotency-key');
209
+ if (idempotencyKey === null || idempotencyKey.length === 0) {
210
+ return apiError(400, 'invalid_request', 'The Idempotency-Key header is required.');
211
+ }
212
+ if (typeof body !== 'string') {
213
+ return apiError(400, 'invalid_request', 'The request body must be JSON.');
214
+ }
215
+ let payload;
216
+ try {
217
+ payload = JSON.parse(body);
218
+ }
219
+ catch {
220
+ return apiError(400, 'invalid_request', 'The request body must be JSON.');
221
+ }
222
+ const parsed = contracts_1.CreateReportRequestSchema.safeParse(payload);
223
+ if (!parsed.success) {
224
+ return apiError(422, 'unprocessable_envelope', `The envelope cannot be processed — ${parsed.error.issues
225
+ .map((issue) => `${issue.path.join('.') || '(root)'}: ${issue.message}`)
226
+ .join('; ')}`);
227
+ }
228
+ const { externalReportId, envelope } = parsed.data;
229
+ if (envelope.applicationId !== this.applicationId) {
230
+ return apiError(403, 'forbidden', 'The envelope names a different applicationId than the credential it was delivered with.');
231
+ }
232
+ const payloadHash = sha256Hex(JSON.stringify(parsed.data));
233
+ /**
234
+ * Appendix D, both halves. The same key with the same payload is the SAME
235
+ * report and returns the same `reportId`; the same `externalReportId` with a
236
+ * DIFFERENT payload is §10.5's 409 — the two cannot both be that report, and
237
+ * no retry resolves it.
238
+ */
239
+ const byKey = this.reportsByIdempotencyKey.get(idempotencyKey);
240
+ if (byKey !== undefined) {
241
+ if (byKey.payloadHash !== payloadHash) {
242
+ return apiError(409, 'conflict', 'This Idempotency-Key was already used with a different payload.');
243
+ }
244
+ return this.receiptResponse(byKey);
245
+ }
246
+ const byExternalId = this.reportsByExternalId.get(externalReportId);
247
+ if (byExternalId !== undefined) {
248
+ if (byExternalId.payloadHash !== payloadHash) {
249
+ return apiError(409, 'conflict', `externalReportId '${externalReportId}' was already delivered with different content.`);
250
+ }
251
+ return this.receiptResponse(byExternalId);
252
+ }
253
+ const dedupKey = sha256Hex([
254
+ this.applicationId,
255
+ envelope.subject.externalId,
256
+ contentHashOf(envelope),
257
+ `${envelope.policy.policySetId}@${envelope.policy.version}`,
258
+ ].join(':'));
259
+ const existingCaseId = this.caseIdByDedupKey.get(dedupKey);
260
+ const merged = existingCaseId !== undefined;
261
+ const caseId = existingCaseId ?? publicId('case');
262
+ const report = {
263
+ reportId: publicId('rpt'),
264
+ externalReportId,
265
+ caseId,
266
+ idempotencyKey,
267
+ envelope,
268
+ status: merged ? 'merged' : 'received',
269
+ receivedAt: new Date().toISOString(),
270
+ payloadHash,
271
+ };
272
+ this.reportsById.set(report.reportId, report);
273
+ this.reportsByIdempotencyKey.set(idempotencyKey, report);
274
+ this.reportsByExternalId.set(externalReportId, report);
275
+ const existingCase = this.casesById.get(caseId);
276
+ this.casesById.set(caseId, {
277
+ caseId,
278
+ externalSubjectId: envelope.subject.externalId,
279
+ dedupKey,
280
+ policy: envelope.policy,
281
+ allegationCodes: [
282
+ ...new Set([
283
+ ...(existingCase?.allegationCodes ?? []),
284
+ ...envelope.allegations.map((allegation) => allegation.code),
285
+ ]),
286
+ ],
287
+ reportIds: [...(existingCase?.reportIds ?? []), report.reportId],
288
+ decisions: existingCase?.decisions ?? [],
289
+ });
290
+ this.caseIdByDedupKey.set(dedupKey, caseId);
291
+ return this.receiptResponse(report);
292
+ }
293
+ receiptResponse(report) {
294
+ return jsonResponse(202, {
295
+ reportId: report.reportId,
296
+ caseId: report.caseId,
297
+ status: report.status,
298
+ merged: report.status === 'merged',
299
+ });
300
+ }
301
+ readReport(reportId) {
302
+ const report = this.reportsById.get(reportId);
303
+ if (!report)
304
+ return apiError(404, 'not_found', 'No such report.');
305
+ return jsonResponse(200, {
306
+ reportId: report.reportId,
307
+ externalReportId: report.externalReportId,
308
+ caseId: report.caseId,
309
+ status: report.status,
310
+ receivedAt: report.receivedAt,
311
+ });
312
+ }
313
+ readCase(caseId) {
314
+ const stored = this.casesById.get(caseId);
315
+ if (!stored)
316
+ return apiError(404, 'not_found', 'No such case.');
317
+ return jsonResponse(200, {
318
+ caseId: stored.caseId,
319
+ status: stored.decisions.length > 0 ? 'decided' : 'awaiting_review',
320
+ subject: { externalId: stored.externalSubjectId, type: 'social.post' },
321
+ policy: stored.policy,
322
+ taxonomyVersion: contracts_1.UNIVERSAL_TAXONOMY_VERSION,
323
+ allegationCodes: stored.allegationCodes,
324
+ reportCount: stored.reportIds.length,
325
+ sensitivityClass: 'standard',
326
+ currentRevision: stored.decisions.length,
327
+ createdAt: new Date(0).toISOString(),
328
+ updatedAt: new Date().toISOString(),
329
+ });
330
+ }
331
+ readDecision(decisionId) {
332
+ const decision = this.decisionsById.get(decisionId);
333
+ if (!decision)
334
+ return apiError(404, 'not_found', 'No such decision.');
335
+ return jsonResponse(200, decision);
336
+ }
337
+ }
338
+ exports.CrowdSourceSandbox = CrowdSourceSandbox;
339
+ function createCrowdSourceSandbox(options = {}) {
340
+ return new CrowdSourceSandbox(options);
341
+ }
342
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../src/testing/sandbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The webhook simulator — the producer side of §10.8.
3
+ *
4
+ * An integrator can point this at their own receiver and watch a real delivery
5
+ * arrive, signed exactly as CrowdSource signs one, without a jury having sat and
6
+ * without anything being enforced against a real user.
7
+ *
8
+ * It deliberately also makes it easy to send a delivery that is WRONG:
9
+ * `expired`, `wrongSecret`, `tampered` and a raw `signature` override are here
10
+ * so an integration test can assert that the receiver REFUSES those. A test
11
+ * suite that only ever sends valid deliveries proves the receiver can say yes.
12
+ * Proving it can say no is the part that matters, and it is the part nobody
13
+ * writes unless the tooling makes it a one-liner.
14
+ *
15
+ * Signing lives here rather than in `@crowdsource.you/core/express` because the two
16
+ * are opposite sides of the contract: the verifier must never import a signer
17
+ * that could be "corrected" until they agree with each other and both disagree
18
+ * with the service. What they share instead is
19
+ * `buildWebhookSignedPayload` from the contracts package, which is the one
20
+ * definition of what gets signed.
21
+ */
22
+ export interface SignedWebhookDelivery {
23
+ /** Headers as CrowdSource sends them, plus `content-type`. */
24
+ readonly headers: Readonly<Record<string, string>>;
25
+ /** The exact bytes that were signed. Send these, not a re-serialisation. */
26
+ readonly body: string;
27
+ }
28
+ export interface SignWebhookInput {
29
+ readonly secret: string;
30
+ /**
31
+ * The event to deliver. Typed as `unknown` on purpose: a simulator that only
32
+ * accepted `KnownWebhookEvent` could not send an event type from a future
33
+ * version of the service, which is precisely the case §10.11 asks every
34
+ * receiver to survive.
35
+ */
36
+ readonly event: unknown;
37
+ /** Unix seconds. Defaults to now. */
38
+ readonly timestampSeconds?: number;
39
+ /** Overrides the body sent, WITHOUT re-signing it. For tamper tests. */
40
+ readonly tamperedBody?: string;
41
+ /** Overrides the signature header verbatim. For forgery tests. */
42
+ readonly signature?: string;
43
+ /** Overrides the event id header, to test header/body disagreement. */
44
+ readonly eventId?: string;
45
+ }
46
+ /** Signs one delivery. */
47
+ export declare function signWebhookDelivery(input: SignWebhookInput): SignedWebhookDelivery;
48
+ export interface WebhookDeliveryResult {
49
+ readonly status: number;
50
+ readonly body: unknown;
51
+ }
52
+ export interface WebhookSimulatorOptions {
53
+ readonly secret: string;
54
+ /** The receiver's webhook URL. */
55
+ readonly url: string;
56
+ readonly fetch?: typeof globalThis.fetch;
57
+ }
58
+ /** How a delivery should be wrong, for a test that asserts a refusal. */
59
+ export interface WebhookDeliveryOverrides {
60
+ /** Signs with a timestamp far outside §10.8's five-minute window. */
61
+ readonly expired?: boolean;
62
+ /** Signs with a secret the receiver does not hold. */
63
+ readonly wrongSecret?: string;
64
+ /** Sends a body different from the one that was signed. */
65
+ readonly tamperedBody?: string;
66
+ readonly signature?: string;
67
+ readonly eventId?: string;
68
+ readonly timestampSeconds?: number;
69
+ }
70
+ export declare class WebhookSimulator {
71
+ private readonly options;
72
+ constructor(options: WebhookSimulatorOptions);
73
+ /** Signs a delivery without sending it. */
74
+ sign(event: unknown, overrides?: WebhookDeliveryOverrides): SignedWebhookDelivery;
75
+ /** Signs and POSTs a delivery to the receiver. */
76
+ deliver(event: unknown, overrides?: WebhookDeliveryOverrides): Promise<WebhookDeliveryResult>;
77
+ }
78
+ //# sourceMappingURL=webhook-simulator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-simulator.d.ts","sourceRoot":"","sources":["../../src/testing/webhook-simulator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAYH,MAAM,WAAW,qBAAqB;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,qCAAqC;IACrC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,wEAAwE;IACxE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAUD,0BAA0B;AAC1B,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,qBAAqB,CAsBlF;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAC1C;AAED,yEAAyE;AACzE,MAAM,WAAW,wBAAwB;IACvC,qEAAqE;IACrE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,2DAA2D;IAC3D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAKD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;gBAEtC,OAAO,EAAE,uBAAuB;IAI5C,2CAA2C;IAC3C,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,GAAE,wBAA6B,GAAG,qBAAqB;IAarF,kDAAkD;IAC5C,OAAO,CACX,KAAK,EAAE,OAAO,EACd,SAAS,GAAE,wBAA6B,GACvC,OAAO,CAAC,qBAAqB,CAAC;CAqBlC"}