@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 @@
1
+ {"version":3,"file":"webhookEndpoints.js","sourceRoot":"","sources":["../src/webhookEndpoints.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAEH,6BAAwB;AAExB,2CAA2C;AAC3C,2CAAwD;AAoExD;;;;;;;;;;;;GAYG;AACH,SAAS,0BAA0B,CAAC,GAAW;IAC7C,OAAO,oBAAoB,IAAA,wBAAY,EAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC;AACxE,CAAC;AAyBD,MAAM,mBAAmB,GAAG,OAAC,CAAC,WAAW,CAAC;IACxC,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE;IACnB,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;IACjB,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE;CAC5B,CAAC,CAAC;AAEH,MAAM,qBAAqB,GAAG,OAAC,CAAC,WAAW,CAAC;IAC1C,iBAAiB,EAAE,OAAC,CAAC,MAAM,EAAE;IAC7B,GAAG,EAAE,OAAC,CAAC,MAAM,EAAE;IACf,UAAU,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;IAC/B,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,cAAc,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IACpC,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;IACrB,MAAM,EAAE,mBAAmB,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC;AAEH,MAAM,0BAA0B,GAAG,OAAC,CAAC,WAAW,CAAC;IAC/C,iBAAiB,EAAE,OAAC,CAAC,MAAM,EAAE;IAC7B,MAAM,EAAE,mBAAmB;IAC3B,cAAc,EAAE,OAAC;SACd,WAAW,CAAC,EAAE,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,CAAC;SAC3D,OAAO,EAAE;CACb,CAAC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAa,gBAAgB;IAG3B,YAAY,SAAoB;QAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,CACZ,KAAmC,EACnC,UAAyC,EAAE;QAE3C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAU;YACrD,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,uBAAuB;YAC7B,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,EAAE;YAC3D,cAAc,EAAE,OAAO,CAAC,cAAc,IAAI,0BAA0B,CAAC,KAAK,CAAC,GAAG,CAAC;YAC/E,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACzD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,qCAAyB,CACjC,sGAAsG,EACtG,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAC1C,CAAC;QACJ,CAAC;QAED,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;QACxD,OAAO;YACL,GAAG,IAAI;YACP,cAAc,EAAE,cAAc,IAAI,IAAI;YACtC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;SAC5C,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,YAAY,CAChB,iBAAyB,EACzB,UAA+B,EAAE;QAEjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAU;YACrD,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,yBAAyB,kBAAkB,CAAC,iBAAiB,CAAC,gBAAgB;YACpF,IAAI,EACF,OAAO,CAAC,cAAc,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE;YACxF,cAAc,EACZ,OAAO,CAAC,cAAc;gBACtB,oBAAoB,IAAA,wBAAY,EAAC,iBAAiB,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS;YACrF,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,0BAA0B,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,qCAAyB,CACjC,iGAAiG,EACjG,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAC1C,CAAC;QACJ,CAAC;QAED,OAAO;YACL,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,iBAAiB;YAChD,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;YAC1B,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,cAAc,IAAI,IAAI;SACnD,CAAC;IACJ,CAAC;CACF;AA5ED,4CA4EC"}
package/package.json ADDED
@@ -0,0 +1,127 @@
1
+ {
2
+ "name": "@crowdsource.you/core",
3
+ "version": "1.2.0",
4
+ "description": "The CrowdSource integration: the API client at the root, and the Express webhook receiver, the transactional outbox and the test sandbox behind subpaths",
5
+ "type": "commonjs",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/esm/index.js",
12
+ "require": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./express": {
16
+ "types": "./dist/express/index.d.ts",
17
+ "import": "./dist/esm/express/index.js",
18
+ "require": "./dist/express/index.js",
19
+ "default": "./dist/express/index.js"
20
+ },
21
+ "./outbox": {
22
+ "types": "./dist/outbox/index.d.ts",
23
+ "import": "./dist/esm/outbox/index.js",
24
+ "require": "./dist/outbox/index.js",
25
+ "default": "./dist/outbox/index.js"
26
+ },
27
+ "./outbox/postgres": {
28
+ "types": "./dist/outbox/postgres/index.d.ts",
29
+ "import": "./dist/esm/outbox/postgres/index.js",
30
+ "require": "./dist/outbox/postgres/index.js",
31
+ "default": "./dist/outbox/postgres/index.js"
32
+ },
33
+ "./testing": {
34
+ "types": "./dist/testing/index.d.ts",
35
+ "import": "./dist/esm/testing/index.js",
36
+ "require": "./dist/testing/index.js",
37
+ "default": "./dist/testing/index.js"
38
+ },
39
+ "./package.json": "./package.json"
40
+ },
41
+ "typesVersions": {
42
+ "*": {
43
+ "express": ["./dist/express/index.d.ts"],
44
+ "outbox": ["./dist/outbox/index.d.ts"],
45
+ "outbox/postgres": ["./dist/outbox/postgres/index.d.ts"],
46
+ "testing": ["./dist/testing/index.d.ts"]
47
+ }
48
+ },
49
+ "scripts": {
50
+ "build": "tsc && tsc -p tsconfig.esm.json && bun ../../scripts/write-esm-marker.mjs",
51
+ "dev": "tsc --watch",
52
+ "clean": "rm -rf dist *.tsbuildinfo",
53
+ "db:generate": "drizzle-kit generate",
54
+ "lint": "tsc --noEmit --noUnusedLocals --noUnusedParameters && tsc -p tsconfig.test.json",
55
+ "test": "bun run test:unit && bun run test:express && bun run test:testing && bun run test:outbox && bun run test:invariants && bun run test:table-type",
56
+ "test:unit": "node ../../node_modules/vitest/vitest.mjs run",
57
+ "test:express": "node ../../node_modules/vitest/vitest.mjs run -c vitest.express.config.ts",
58
+ "test:outbox": "node ../../node_modules/vitest/vitest.mjs run -c vitest.outbox.config.ts",
59
+ "test:testing": "node ../../node_modules/vitest/vitest.mjs run -c vitest.testing.config.ts",
60
+ "test:invariants": "bun ./scripts/test-invariants.mjs",
61
+ "test:table-type": "bun ./scripts/test-report-table-type.mjs",
62
+ "test:watch": "node ../../node_modules/vitest/vitest.mjs",
63
+ "prepublishOnly": "bun run build"
64
+ },
65
+ "keywords": [
66
+ "crowdsource",
67
+ "moderation",
68
+ "sdk",
69
+ "outbox",
70
+ "webhooks",
71
+ "express",
72
+ "postgresql"
73
+ ],
74
+ "author": "OxyHQ",
75
+ "license": "MIT",
76
+ "repository": {
77
+ "type": "git",
78
+ "url": "https://github.com/OxyHQ/CrowdSource",
79
+ "directory": "packages/core"
80
+ },
81
+ "homepage": "https://github.com/OxyHQ/CrowdSource/tree/main/packages/core#readme",
82
+ "bugs": "https://github.com/OxyHQ/CrowdSource/issues",
83
+ "publishConfig": {
84
+ "access": "public"
85
+ },
86
+ "dependencies": {
87
+ "zod": "^4.6.5"
88
+ },
89
+ "devDependencies": {
90
+ "@crowdsource.you/contracts": "1.1.0",
91
+ "@oxy.so/db": "^1.0.0",
92
+ "@types/express": "^5.0.6",
93
+ "@types/node": "^25.9.5",
94
+ "drizzle-kit": "^0.31.10",
95
+ "drizzle-orm": "^0.45.2",
96
+ "express": "^5.2.1",
97
+ "postgres": "^3.4.9",
98
+ "typescript": "^5.9.3",
99
+ "vitest": "^4.1.10"
100
+ },
101
+ "files": [
102
+ "dist/**/*",
103
+ "src/**/*",
104
+ "!src/**/__tests__/**"
105
+ ],
106
+ "peerDependencies": {
107
+ "@crowdsource.you/contracts": "^1.1.0",
108
+ "@oxy.so/db": "^1.0.0",
109
+ "drizzle-orm": "^0.45.2",
110
+ "express": ">=4.18.0 <6",
111
+ "postgres": "^3.4.9"
112
+ },
113
+ "peerDependenciesMeta": {
114
+ "@oxy.so/db": {
115
+ "optional": true
116
+ },
117
+ "drizzle-orm": {
118
+ "optional": true
119
+ },
120
+ "express": {
121
+ "optional": true
122
+ },
123
+ "postgres": {
124
+ "optional": true
125
+ }
126
+ }
127
+ }
package/src/cases.ts ADDED
@@ -0,0 +1,119 @@
1
+ /**
2
+ * `GET /v1/cases/{id}` and `GET /v1/decisions/{id}` (§10.2).
3
+ *
4
+ * Both are look-ups by an id CrowdSource gave the application. There is no case
5
+ * search and there will not be one: "nobody chooses the case they review" is an
6
+ * invariant, and a list endpoint on the application API would be the first step
7
+ * towards a queue somebody browses.
8
+ *
9
+ * `decisions.get` is written against the route §10.2 defines. **The backend does
10
+ * not serve it yet** — nothing publishes decisions, because sortition, review
11
+ * and consensus are not built — so it answers 404 today. It is here because a
12
+ * client of a documented endpoint is not a stub, and because the webhook path
13
+ * (`case.decided`) is how an application learns about a decision anyway; this is
14
+ * the read-back for one it already has an id for.
15
+ */
16
+
17
+ import { DecisionSchema, type Decision, type TaxonomyCode } from '@crowdsource.you/contracts';
18
+ import { z } from 'zod';
19
+
20
+ import { CrowdSourceTransportError } from './errors.js';
21
+ import type { Transport } from './transport.js';
22
+
23
+ /**
24
+ * The projection §10.2 returns for a case.
25
+ *
26
+ * Deliberately not the case document. Priority score, review pool and reporter
27
+ * fingerprints never leave the service — an application that could read its own
28
+ * cases' priority could learn which signals move it.
29
+ */
30
+ export interface CaseView {
31
+ readonly caseId: string;
32
+ readonly status: string;
33
+ readonly subject: { readonly externalId: string; readonly type: string };
34
+ readonly policy: { readonly policySetId: string; readonly version: string };
35
+ readonly taxonomyVersion: string;
36
+ readonly allegationCodes: readonly (TaxonomyCode | (string & {}))[];
37
+ /** How many reports merged into this case. §7.3: many reports, one case. */
38
+ readonly reportCount: number;
39
+ readonly sensitivityClass: string;
40
+ readonly currentRevision: number;
41
+ readonly createdAt: string;
42
+ readonly updatedAt: string;
43
+ }
44
+
45
+ const CaseViewSchema = z.looseObject({
46
+ caseId: z.string(),
47
+ status: z.string(),
48
+ subject: z.looseObject({ externalId: z.string(), type: z.string() }),
49
+ policy: z.looseObject({ policySetId: z.string(), version: z.string() }),
50
+ taxonomyVersion: z.string(),
51
+ allegationCodes: z.array(z.string()),
52
+ reportCount: z.number(),
53
+ sensitivityClass: z.string(),
54
+ currentRevision: z.number(),
55
+ createdAt: z.string(),
56
+ updatedAt: z.string(),
57
+ });
58
+
59
+ export interface ReadOptions {
60
+ readonly signal?: AbortSignal;
61
+ }
62
+
63
+ export class Cases {
64
+ private readonly transport: Transport;
65
+
66
+ constructor(transport: Transport) {
67
+ this.transport = transport;
68
+ }
69
+
70
+ async get(caseId: string, options: ReadOptions = {}): Promise<CaseView> {
71
+ const response = await this.transport.request<unknown>({
72
+ method: 'GET',
73
+ path: `/v1/cases/${encodeURIComponent(caseId)}`,
74
+ signal: options.signal,
75
+ });
76
+
77
+ const parsed = CaseViewSchema.safeParse(response);
78
+ if (!parsed.success) {
79
+ throw new CrowdSourceTransportError(
80
+ 'CrowdSource answered with a case this client does not recognise.',
81
+ { retryable: false, cause: parsed.error },
82
+ );
83
+ }
84
+ return parsed.data;
85
+ }
86
+ }
87
+
88
+ export class Decisions {
89
+ private readonly transport: Transport;
90
+
91
+ constructor(transport: Transport) {
92
+ this.transport = transport;
93
+ }
94
+
95
+ /**
96
+ * Reads one immutable revision of a decision.
97
+ *
98
+ * A published revision is never edited (Appendix F); a later revision
99
+ * supersedes it and carries `supersedesDecisionId`. So a decision read twice
100
+ * is byte-identical, and an application that cached one never needs to
101
+ * invalidate it — it needs to notice a `decision.corrected` webhook.
102
+ */
103
+ async get(decisionId: string, options: ReadOptions = {}): Promise<Decision> {
104
+ const response = await this.transport.request<unknown>({
105
+ method: 'GET',
106
+ path: `/v1/decisions/${encodeURIComponent(decisionId)}`,
107
+ signal: options.signal,
108
+ });
109
+
110
+ const parsed = DecisionSchema.safeParse(response);
111
+ if (!parsed.success) {
112
+ throw new CrowdSourceTransportError(
113
+ 'CrowdSource answered with a decision this client does not recognise.',
114
+ { retryable: false, cause: parsed.error },
115
+ );
116
+ }
117
+ return parsed.data;
118
+ }
119
+ }
package/src/client.ts ADDED
@@ -0,0 +1,202 @@
1
+ /**
2
+ * The client.
3
+ *
4
+ * The whole design target is the length of the smallest integration:
5
+ *
6
+ * const crowdsource = new CrowdSource();
7
+ * await crowdsource.reports.create({ ... });
8
+ *
9
+ * Everything else has a default that is correct for the overwhelming majority of
10
+ * integrators and a way to override it for the rest. The service key comes from
11
+ * the environment, the base URL is the one deployment CrowdSource has, and the
12
+ * envelope, the policy version, the digests, the principal refs and the
13
+ * idempotency key are all composed rather than configured.
14
+ *
15
+ * This client is SERVER-SIDE ONLY. A service credential is the tenant's identity
16
+ * for its whole moderation stream; shipping one to a browser or a mobile bundle
17
+ * hands every user of the application the ability to file reports as the
18
+ * application, read its cases and exhaust its quota. The package depends on
19
+ * `node:crypto` and does not build for a browser, which is the intended
20
+ * outcome rather than a limitation to work around.
21
+ */
22
+
23
+ import { Cases, Decisions } from './cases.js';
24
+ import { CommunityNotes } from './communityNotes.js';
25
+ import { parseServiceKey, type ServiceCredential } from './credential.js';
26
+ import { DEFAULT_BASE_URL } from './defaults.js';
27
+ import { CrowdSourceConfigurationError } from './errors.js';
28
+ import { Reports } from './reports.js';
29
+ import {
30
+ DEFAULT_MAX_ATTEMPTS,
31
+ DEFAULT_TIMEOUT_MS,
32
+ Transport,
33
+ type FetchLike,
34
+ } from './transport.js';
35
+ import { WebhookEndpoints } from './webhookEndpoints.js';
36
+
37
+ /** The environment variable a zero-configuration integration reads. */
38
+ export const SERVICE_KEY_ENV_VAR = 'CROWDSOURCE_SERVICE_KEY';
39
+
40
+ /** Overrides the service host. Set only when pointing at a local backend. */
41
+ export const BASE_URL_ENV_VAR = 'CROWDSOURCE_BASE_URL';
42
+
43
+ export interface CrowdSourceOptions {
44
+ /**
45
+ * The service key CrowdSource issued, as one opaque string. Defaults to
46
+ * `process.env.CROWDSOURCE_SERVICE_KEY`.
47
+ *
48
+ * There is no `applicationId` option here or anywhere else. The application a
49
+ * report belongs to is read off this credential — see `credential.ts`.
50
+ */
51
+ readonly serviceKey?: string;
52
+ /**
53
+ * A first-party Oxy service authenticating with NO CrowdSource credential.
54
+ *
55
+ * Return a current Oxy service token; it is asked for once per request
56
+ * attempt, so returning a cached token and refreshing it when it expires is
57
+ * the expected shape (`oxyServices.getServiceToken()` does exactly that).
58
+ *
59
+ * With this set, `serviceKey` is neither needed nor read. CrowdSource resolves
60
+ * the tenant from the Oxy application the token names, so nothing here has to
61
+ * be issued, stored or rotated by a person. Third parties keep the service
62
+ * key: they run where Oxy cannot vouch for them.
63
+ */
64
+ readonly oxyToken?: () => string | Promise<string>;
65
+ readonly baseUrl?: string;
66
+ /** Per-attempt deadline. Default 10s. */
67
+ readonly timeoutMs?: number;
68
+ /** Attempts per call including the first, for retryable failures. Default 3. */
69
+ readonly maxAttempts?: number;
70
+ /**
71
+ * Marks reports as coming from the application's own pre-production (§5.1
72
+ * `source.environment`). CrowdSource has one deployment; this is a property of
73
+ * the report, not a different host to talk to.
74
+ *
75
+ * Every report from a sandbox client must carry `submittedAt`, because the
76
+ * environment travels inside `source` and `source` cannot be composed without
77
+ * one — see `ReportInput.submittedAt` for why inventing that timestamp would
78
+ * turn every retry into a 409.
79
+ */
80
+ readonly sandbox?: boolean;
81
+ /** Injected for tests and for the in-process sandbox. Defaults to global `fetch`. */
82
+ readonly fetch?: FetchLike;
83
+ }
84
+
85
+ export class CrowdSource {
86
+ /**
87
+ * The application this client acts as.
88
+ *
89
+ * Read off the credential when there is one. With an Oxy token there is
90
+ * nothing to read it off — the mapping lives in CrowdSource — so it resolves
91
+ * on first use from `GET /v1/applications/me` and is remembered.
92
+ */
93
+ readonly applicationId: string | Promise<string>;
94
+
95
+ readonly reports: Reports;
96
+ readonly cases: Cases;
97
+ readonly decisions: Decisions;
98
+ /** Where decisions get delivered, and the secret that signs them (§10.2). */
99
+ readonly webhookEndpoints: WebhookEndpoints;
100
+ /** Community notes: write, withdraw, draw to rate, rate, and the reads. */
101
+ readonly communityNotes: CommunityNotes;
102
+
103
+ constructor(options: CrowdSourceOptions = {}) {
104
+ /**
105
+ * Two ways to be an application, and exactly one of them is configured.
106
+ *
107
+ * The Oxy path is checked first so that a deployment which has BOTH — during
108
+ * the migration off shared secrets — uses the identity it can prove rather
109
+ * than the secret it still happens to hold. Removing the key is then the
110
+ * cleanup, not the cutover.
111
+ */
112
+ const credential: ServiceCredential | null = options.oxyToken
113
+ ? null
114
+ : parseServiceKey(options.serviceKey ?? process.env[SERVICE_KEY_ENV_VAR] ?? '');
115
+
116
+ const baseUrl = normalisedBaseUrl(
117
+ options.baseUrl ?? process.env[BASE_URL_ENV_VAR] ?? DEFAULT_BASE_URL,
118
+ );
119
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
120
+ const fetchImpl = options.fetch ?? globalThis.fetch;
121
+ if (typeof fetchImpl !== 'function') {
122
+ throw new CrowdSourceConfigurationError(
123
+ 'This runtime has no global fetch. Pass one as the `fetch` option.',
124
+ );
125
+ }
126
+
127
+ const oxyToken = options.oxyToken;
128
+ const transport = new Transport({
129
+ baseUrl,
130
+ bearerToken: credential ? () => credential.bearerToken : () => oxyToken!(),
131
+ timeoutMs,
132
+ maxAttempts: options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
133
+ fetch: fetchImpl,
134
+ });
135
+
136
+ this.applicationId = credential ? credential.applicationId : lazyApplicationId(transport);
137
+ this.reports = new Reports({
138
+ transport,
139
+ applicationId: this.applicationId,
140
+ environment: options.sandbox === true ? 'sandbox' : 'production',
141
+ });
142
+ this.cases = new Cases(transport);
143
+ this.decisions = new Decisions(transport);
144
+ this.webhookEndpoints = new WebhookEndpoints(transport);
145
+ this.communityNotes = new CommunityNotes(transport);
146
+ }
147
+ }
148
+
149
+
150
+ /**
151
+ * Asks CrowdSource which application this client is, once.
152
+ *
153
+ * Only the Oxy-token path needs this: a service key carries the id inside it.
154
+ * The promise is created at construction and awaited wherever the id is used,
155
+ * so the lookup happens at most once per client and never blocks a caller that
156
+ * does not need it (community notes never do; a report does, because its
157
+ * envelope names the application and the server refuses one that disagrees).
158
+ *
159
+ * A failure is not swallowed into a placeholder id. An envelope carrying the
160
+ * wrong application is refused by the server anyway, and a client that invented
161
+ * one would turn a clear "we could not identify you" into a confusing 403 on
162
+ * every report.
163
+ */
164
+ function lazyApplicationId(transport: Transport): Promise<string> {
165
+ let pending: Promise<string> | null = null;
166
+ const resolve = () => (pending ??= askApplicationId(transport));
167
+ // A thenable rather than a promise: nothing is requested until somebody
168
+ // awaits it, so a client that only reads community notes — which never name
169
+ // an application — makes no identity call at all. Awaiting it twice still
170
+ // makes one.
171
+ return { then: (onFulfilled, onRejected) => resolve().then(onFulfilled, onRejected) } as Promise<string>;
172
+ }
173
+
174
+ function askApplicationId(transport: Transport): Promise<string> {
175
+ return transport
176
+ .request<{ applicationId?: unknown }>({ method: 'GET', path: '/v1/applications/me' })
177
+ .then((body) => {
178
+ const applicationId = body?.applicationId;
179
+ if (typeof applicationId !== 'string' || applicationId.length === 0) {
180
+ throw new CrowdSourceConfigurationError(
181
+ 'CrowdSource did not name the application this token belongs to.',
182
+ );
183
+ }
184
+ return applicationId;
185
+ });
186
+ }
187
+
188
+ function normalisedBaseUrl(value: string): string {
189
+ let parsed: URL;
190
+ try {
191
+ parsed = new URL(value);
192
+ } catch {
193
+ throw new CrowdSourceConfigurationError(`'${value}' is not a usable CrowdSource base URL.`);
194
+ }
195
+ if (parsed.protocol !== 'https:' && parsed.hostname !== 'localhost') {
196
+ throw new CrowdSourceConfigurationError(
197
+ 'A CrowdSource base URL must be https. A service credential sent in clear is a credential you have to rotate.',
198
+ );
199
+ }
200
+ // Trailing slashes are stripped so `${baseUrl}/v1/reports` never doubles up.
201
+ return parsed.origin + parsed.pathname.replace(/\/+$/, '');
202
+ }
@@ -0,0 +1,194 @@
1
+ import {
2
+ CommunityNoteAssignmentBatchSchema,
3
+ CommunityNoteListSchema,
4
+ CommunityNoteRatedListSchema,
5
+ CommunityNoteRatingSchema,
6
+ CommunityNoteSchema,
7
+ ShownCommunityNotesSchema,
8
+ type CommunityNote,
9
+ type CommunityNoteAssignment,
10
+ type CommunityNoteAssignmentRequest,
11
+ type CommunityNoteRatedList,
12
+ type CommunityNoteRating,
13
+ type CommunityNoteRatingSubmission,
14
+ type CommunityNoteSubmission,
15
+ } from '@crowdsource.you/contracts';
16
+ import type { z } from 'zod';
17
+
18
+ import { sha256Digest } from './digest.js';
19
+ import { CrowdSourceTransportError } from './errors.js';
20
+ import type { Transport } from './transport.js';
21
+
22
+ /**
23
+ * Community notes (`docs/architecture/community-notes.md`): reader-written context
24
+ * under the application's subjects, shown once raters of different viewpoints
25
+ * agree it is helpful.
26
+ *
27
+ * The application acts for its users, naming each by its own opaque principal id.
28
+ * Every write is retry-safe with no key from the caller where one can be derived
29
+ * from what the write IS — one note per writer per subject, one rating per rater
30
+ * per note — and the derived keys hash the principal ids, so an application's
31
+ * user ids never travel in a header a proxy might log. Drawing notes to rate has
32
+ * no such identity (two draws for one rater are two different requests), so that
33
+ * one method takes the key from the caller.
34
+ */
35
+
36
+ export interface CommunityNoteRequestOptions {
37
+ readonly idempotencyKey?: string;
38
+ readonly signal?: AbortSignal;
39
+ }
40
+
41
+ export interface CommunityNoteReadOptions {
42
+ readonly signal?: AbortSignal;
43
+ }
44
+
45
+ /** A stable, header-safe digest of the parts that identify a write. */
46
+ function digestOf(...parts: readonly string[]): string {
47
+ return sha256Digest(JSON.stringify(parts)).replace('sha256:', '');
48
+ }
49
+
50
+ export class CommunityNotes {
51
+ private readonly transport: Transport;
52
+
53
+ constructor(transport: Transport) {
54
+ this.transport = transport;
55
+ }
56
+
57
+ /** Writes a note. A retry of the same writer's note on the same subject returns it. */
58
+ async write(submission: CommunityNoteSubmission, options: CommunityNoteRequestOptions = {}): Promise<CommunityNote> {
59
+ return this.parse(
60
+ CommunityNoteSchema,
61
+ await this.transport.request<unknown>({
62
+ method: 'POST',
63
+ path: '/v1/community-notes',
64
+ body: submission,
65
+ idempotencyKey:
66
+ options.idempotencyKey ??
67
+ `community-note.${digestOf(submission.externalSubjectId, submission.authorPrincipalId)}`,
68
+ signal: options.signal,
69
+ }),
70
+ 'a community note',
71
+ );
72
+ }
73
+
74
+ /** Withdraws a note. Only its writer may; anyone else's note answers 404. */
75
+ async withdraw(
76
+ noteId: string,
77
+ authorPrincipalId: string,
78
+ options: CommunityNoteRequestOptions = {},
79
+ ): Promise<CommunityNote> {
80
+ return this.parse(
81
+ CommunityNoteSchema,
82
+ await this.transport.request<unknown>({
83
+ method: 'POST',
84
+ path: `/v1/community-notes/${encodeURIComponent(noteId)}/withdraw`,
85
+ body: { authorPrincipalId },
86
+ idempotencyKey: options.idempotencyKey ?? `community-note-withdrawal.${digestOf(noteId)}`,
87
+ signal: options.signal,
88
+ }),
89
+ 'a community note',
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Draws notes for one rater to rate — the ONLY way to be allowed to rate one.
95
+ *
96
+ * `idempotencyKey` is required: a retry with the same key returns the same
97
+ * batch, and a new key is a new draw.
98
+ */
99
+ async drawToRate(
100
+ request: CommunityNoteAssignmentRequest,
101
+ options: CommunityNoteRequestOptions & { readonly idempotencyKey: string },
102
+ ): Promise<CommunityNoteAssignment[]> {
103
+ const batch = this.parse(
104
+ CommunityNoteAssignmentBatchSchema,
105
+ await this.transport.request<unknown>({
106
+ method: 'POST',
107
+ path: '/v1/community-notes/assignments',
108
+ body: request,
109
+ idempotencyKey: options.idempotencyKey,
110
+ signal: options.signal,
111
+ }),
112
+ 'a batch of community note assignments',
113
+ );
114
+ return batch.assignments;
115
+ }
116
+
117
+ /** Rates a note assigned to the rater. Final: a second rating is refused. */
118
+ async rate(
119
+ noteId: string,
120
+ submission: CommunityNoteRatingSubmission,
121
+ options: CommunityNoteRequestOptions = {},
122
+ ): Promise<CommunityNoteRating> {
123
+ return this.parse(
124
+ CommunityNoteRatingSchema,
125
+ await this.transport.request<unknown>({
126
+ method: 'POST',
127
+ path: `/v1/community-notes/${encodeURIComponent(noteId)}/ratings`,
128
+ body: submission,
129
+ idempotencyKey:
130
+ options.idempotencyKey ?? `community-note-rating.${digestOf(noteId, submission.raterPrincipalId)}`,
131
+ signal: options.signal,
132
+ }),
133
+ 'a community note rating',
134
+ );
135
+ }
136
+
137
+ /** The shown note, if any, for each of up to 50 subjects. */
138
+ async shown(externalSubjectIds: readonly string[], options: CommunityNoteReadOptions = {}): Promise<CommunityNote[]> {
139
+ const subjects = [...new Set(externalSubjectIds)];
140
+ if (subjects.length === 0) return [];
141
+ const response = this.parse(
142
+ ShownCommunityNotesSchema,
143
+ await this.transport.request<unknown>({
144
+ method: 'GET',
145
+ path: `/v1/community-notes/shown?subjects=${subjects.map(encodeURIComponent).join(',')}`,
146
+ signal: options.signal,
147
+ }),
148
+ 'a shown community notes lookup',
149
+ );
150
+ return response.notes;
151
+ }
152
+
153
+ /** A writer's own notes, newest first. */
154
+ async writtenBy(authorPrincipalId: string, options: CommunityNoteReadOptions = {}): Promise<CommunityNote[]> {
155
+ const response = this.parse(
156
+ CommunityNoteListSchema,
157
+ await this.transport.request<unknown>({
158
+ method: 'GET',
159
+ path: `/v1/community-notes/principals/${encodeURIComponent(authorPrincipalId)}/notes`,
160
+ signal: options.signal,
161
+ }),
162
+ 'a list of community notes',
163
+ );
164
+ return response.notes;
165
+ }
166
+
167
+ /** A rater's own ratings, each with the note it rated, newest first. */
168
+ async ratedBy(
169
+ raterPrincipalId: string,
170
+ options: CommunityNoteReadOptions = {},
171
+ ): Promise<CommunityNoteRatedList['ratings']> {
172
+ const response = this.parse(
173
+ CommunityNoteRatedListSchema,
174
+ await this.transport.request<unknown>({
175
+ method: 'GET',
176
+ path: `/v1/community-notes/principals/${encodeURIComponent(raterPrincipalId)}/ratings`,
177
+ signal: options.signal,
178
+ }),
179
+ 'a list of community note ratings',
180
+ );
181
+ return response.ratings;
182
+ }
183
+
184
+ private parse<S extends z.ZodType>(schema: S, response: unknown, what: string): z.infer<S> {
185
+ const parsed = schema.safeParse(response);
186
+ if (!parsed.success) {
187
+ throw new CrowdSourceTransportError(`CrowdSource answered with ${what} this client does not recognise.`, {
188
+ retryable: false,
189
+ cause: parsed.error,
190
+ });
191
+ }
192
+ return parsed.data;
193
+ }
194
+ }