@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
package/dist/cases.js ADDED
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ /**
3
+ * `GET /v1/cases/{id}` and `GET /v1/decisions/{id}` (§10.2).
4
+ *
5
+ * Both are look-ups by an id CrowdSource gave the application. There is no case
6
+ * search and there will not be one: "nobody chooses the case they review" is an
7
+ * invariant, and a list endpoint on the application API would be the first step
8
+ * towards a queue somebody browses.
9
+ *
10
+ * `decisions.get` is written against the route §10.2 defines. **The backend does
11
+ * not serve it yet** — nothing publishes decisions, because sortition, review
12
+ * and consensus are not built — so it answers 404 today. It is here because a
13
+ * client of a documented endpoint is not a stub, and because the webhook path
14
+ * (`case.decided`) is how an application learns about a decision anyway; this is
15
+ * the read-back for one it already has an id for.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.Decisions = exports.Cases = void 0;
19
+ const contracts_1 = require("@crowdsource.you/contracts");
20
+ const zod_1 = require("zod");
21
+ const errors_js_1 = require("./errors.js");
22
+ const CaseViewSchema = zod_1.z.looseObject({
23
+ caseId: zod_1.z.string(),
24
+ status: zod_1.z.string(),
25
+ subject: zod_1.z.looseObject({ externalId: zod_1.z.string(), type: zod_1.z.string() }),
26
+ policy: zod_1.z.looseObject({ policySetId: zod_1.z.string(), version: zod_1.z.string() }),
27
+ taxonomyVersion: zod_1.z.string(),
28
+ allegationCodes: zod_1.z.array(zod_1.z.string()),
29
+ reportCount: zod_1.z.number(),
30
+ sensitivityClass: zod_1.z.string(),
31
+ currentRevision: zod_1.z.number(),
32
+ createdAt: zod_1.z.string(),
33
+ updatedAt: zod_1.z.string(),
34
+ });
35
+ class Cases {
36
+ constructor(transport) {
37
+ this.transport = transport;
38
+ }
39
+ async get(caseId, options = {}) {
40
+ const response = await this.transport.request({
41
+ method: 'GET',
42
+ path: `/v1/cases/${encodeURIComponent(caseId)}`,
43
+ signal: options.signal,
44
+ });
45
+ const parsed = CaseViewSchema.safeParse(response);
46
+ if (!parsed.success) {
47
+ throw new errors_js_1.CrowdSourceTransportError('CrowdSource answered with a case this client does not recognise.', { retryable: false, cause: parsed.error });
48
+ }
49
+ return parsed.data;
50
+ }
51
+ }
52
+ exports.Cases = Cases;
53
+ class Decisions {
54
+ constructor(transport) {
55
+ this.transport = transport;
56
+ }
57
+ /**
58
+ * Reads one immutable revision of a decision.
59
+ *
60
+ * A published revision is never edited (Appendix F); a later revision
61
+ * supersedes it and carries `supersedesDecisionId`. So a decision read twice
62
+ * is byte-identical, and an application that cached one never needs to
63
+ * invalidate it — it needs to notice a `decision.corrected` webhook.
64
+ */
65
+ async get(decisionId, options = {}) {
66
+ const response = await this.transport.request({
67
+ method: 'GET',
68
+ path: `/v1/decisions/${encodeURIComponent(decisionId)}`,
69
+ signal: options.signal,
70
+ });
71
+ const parsed = contracts_1.DecisionSchema.safeParse(response);
72
+ if (!parsed.success) {
73
+ throw new errors_js_1.CrowdSourceTransportError('CrowdSource answered with a decision this client does not recognise.', { retryable: false, cause: parsed.error });
74
+ }
75
+ return parsed.data;
76
+ }
77
+ }
78
+ exports.Decisions = Decisions;
79
+ //# sourceMappingURL=cases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cases.js","sourceRoot":"","sources":["../src/cases.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;AAEH,0DAA8F;AAC9F,6BAAwB;AAExB,2CAAwD;AAyBxD,MAAM,cAAc,GAAG,OAAC,CAAC,WAAW,CAAC;IACnC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,OAAO,EAAE,OAAC,CAAC,WAAW,CAAC,EAAE,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACpE,MAAM,EAAE,OAAC,CAAC,WAAW,CAAC,EAAE,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACvE,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE;IAC3B,eAAe,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC;IACpC,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE;IACvB,gBAAgB,EAAE,OAAC,CAAC,MAAM,EAAE;IAC5B,eAAe,EAAE,OAAC,CAAC,MAAM,EAAE;IAC3B,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;IACrB,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAC;AAMH,MAAa,KAAK;IAGhB,YAAY,SAAoB;QAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,MAAc,EAAE,UAAuB,EAAE;QACjD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAU;YACrD,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,aAAa,kBAAkB,CAAC,MAAM,CAAC,EAAE;YAC/C,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,qCAAyB,CACjC,kEAAkE,EAClE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAC1C,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF;AAvBD,sBAuBC;AAED,MAAa,SAAS;IAGpB,YAAY,SAAoB;QAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,GAAG,CAAC,UAAkB,EAAE,UAAuB,EAAE;QACrD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAAU;YACrD,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,iBAAiB,kBAAkB,CAAC,UAAU,CAAC,EAAE;YACvD,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,0BAAc,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,qCAAyB,CACjC,sEAAsE,EACtE,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAC1C,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF;AA/BD,8BA+BC"}
@@ -0,0 +1,90 @@
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
+ import { Cases, Decisions } from './cases.js';
23
+ import { CommunityNotes } from './communityNotes.js';
24
+ import { Reports } from './reports.js';
25
+ import { type FetchLike } from './transport.js';
26
+ import { WebhookEndpoints } from './webhookEndpoints.js';
27
+ /** The environment variable a zero-configuration integration reads. */
28
+ export declare const SERVICE_KEY_ENV_VAR = "CROWDSOURCE_SERVICE_KEY";
29
+ /** Overrides the service host. Set only when pointing at a local backend. */
30
+ export declare const BASE_URL_ENV_VAR = "CROWDSOURCE_BASE_URL";
31
+ export interface CrowdSourceOptions {
32
+ /**
33
+ * The service key CrowdSource issued, as one opaque string. Defaults to
34
+ * `process.env.CROWDSOURCE_SERVICE_KEY`.
35
+ *
36
+ * There is no `applicationId` option here or anywhere else. The application a
37
+ * report belongs to is read off this credential — see `credential.ts`.
38
+ */
39
+ readonly serviceKey?: string;
40
+ /**
41
+ * A first-party Oxy service authenticating with NO CrowdSource credential.
42
+ *
43
+ * Return a current Oxy service token; it is asked for once per request
44
+ * attempt, so returning a cached token and refreshing it when it expires is
45
+ * the expected shape (`oxyServices.getServiceToken()` does exactly that).
46
+ *
47
+ * With this set, `serviceKey` is neither needed nor read. CrowdSource resolves
48
+ * the tenant from the Oxy application the token names, so nothing here has to
49
+ * be issued, stored or rotated by a person. Third parties keep the service
50
+ * key: they run where Oxy cannot vouch for them.
51
+ */
52
+ readonly oxyToken?: () => string | Promise<string>;
53
+ readonly baseUrl?: string;
54
+ /** Per-attempt deadline. Default 10s. */
55
+ readonly timeoutMs?: number;
56
+ /** Attempts per call including the first, for retryable failures. Default 3. */
57
+ readonly maxAttempts?: number;
58
+ /**
59
+ * Marks reports as coming from the application's own pre-production (§5.1
60
+ * `source.environment`). CrowdSource has one deployment; this is a property of
61
+ * the report, not a different host to talk to.
62
+ *
63
+ * Every report from a sandbox client must carry `submittedAt`, because the
64
+ * environment travels inside `source` and `source` cannot be composed without
65
+ * one — see `ReportInput.submittedAt` for why inventing that timestamp would
66
+ * turn every retry into a 409.
67
+ */
68
+ readonly sandbox?: boolean;
69
+ /** Injected for tests and for the in-process sandbox. Defaults to global `fetch`. */
70
+ readonly fetch?: FetchLike;
71
+ }
72
+ export declare class CrowdSource {
73
+ /**
74
+ * The application this client acts as.
75
+ *
76
+ * Read off the credential when there is one. With an Oxy token there is
77
+ * nothing to read it off — the mapping lives in CrowdSource — so it resolves
78
+ * on first use from `GET /v1/applications/me` and is remembered.
79
+ */
80
+ readonly applicationId: string | Promise<string>;
81
+ readonly reports: Reports;
82
+ readonly cases: Cases;
83
+ readonly decisions: Decisions;
84
+ /** Where decisions get delivered, and the secret that signs them (§10.2). */
85
+ readonly webhookEndpoints: WebhookEndpoints;
86
+ /** Community notes: write, withdraw, draw to rate, rate, and the reads. */
87
+ readonly communityNotes: CommunityNotes;
88
+ constructor(options?: CrowdSourceOptions);
89
+ }
90
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIrD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAE7D,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;OASG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC;CAC5B;AAED,qBAAa,WAAW;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEjD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;gBAE5B,OAAO,GAAE,kBAAuB;CA4C7C"}
package/dist/client.js ADDED
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ /**
3
+ * The client.
4
+ *
5
+ * The whole design target is the length of the smallest integration:
6
+ *
7
+ * const crowdsource = new CrowdSource();
8
+ * await crowdsource.reports.create({ ... });
9
+ *
10
+ * Everything else has a default that is correct for the overwhelming majority of
11
+ * integrators and a way to override it for the rest. The service key comes from
12
+ * the environment, the base URL is the one deployment CrowdSource has, and the
13
+ * envelope, the policy version, the digests, the principal refs and the
14
+ * idempotency key are all composed rather than configured.
15
+ *
16
+ * This client is SERVER-SIDE ONLY. A service credential is the tenant's identity
17
+ * for its whole moderation stream; shipping one to a browser or a mobile bundle
18
+ * hands every user of the application the ability to file reports as the
19
+ * application, read its cases and exhaust its quota. The package depends on
20
+ * `node:crypto` and does not build for a browser, which is the intended
21
+ * outcome rather than a limitation to work around.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.CrowdSource = exports.BASE_URL_ENV_VAR = exports.SERVICE_KEY_ENV_VAR = void 0;
25
+ const cases_js_1 = require("./cases.js");
26
+ const communityNotes_js_1 = require("./communityNotes.js");
27
+ const credential_js_1 = require("./credential.js");
28
+ const defaults_js_1 = require("./defaults.js");
29
+ const errors_js_1 = require("./errors.js");
30
+ const reports_js_1 = require("./reports.js");
31
+ const transport_js_1 = require("./transport.js");
32
+ const webhookEndpoints_js_1 = require("./webhookEndpoints.js");
33
+ /** The environment variable a zero-configuration integration reads. */
34
+ exports.SERVICE_KEY_ENV_VAR = 'CROWDSOURCE_SERVICE_KEY';
35
+ /** Overrides the service host. Set only when pointing at a local backend. */
36
+ exports.BASE_URL_ENV_VAR = 'CROWDSOURCE_BASE_URL';
37
+ class CrowdSource {
38
+ constructor(options = {}) {
39
+ /**
40
+ * Two ways to be an application, and exactly one of them is configured.
41
+ *
42
+ * The Oxy path is checked first so that a deployment which has BOTH — during
43
+ * the migration off shared secrets — uses the identity it can prove rather
44
+ * than the secret it still happens to hold. Removing the key is then the
45
+ * cleanup, not the cutover.
46
+ */
47
+ const credential = options.oxyToken
48
+ ? null
49
+ : (0, credential_js_1.parseServiceKey)(options.serviceKey ?? process.env[exports.SERVICE_KEY_ENV_VAR] ?? '');
50
+ const baseUrl = normalisedBaseUrl(options.baseUrl ?? process.env[exports.BASE_URL_ENV_VAR] ?? defaults_js_1.DEFAULT_BASE_URL);
51
+ const timeoutMs = options.timeoutMs ?? transport_js_1.DEFAULT_TIMEOUT_MS;
52
+ const fetchImpl = options.fetch ?? globalThis.fetch;
53
+ if (typeof fetchImpl !== 'function') {
54
+ throw new errors_js_1.CrowdSourceConfigurationError('This runtime has no global fetch. Pass one as the `fetch` option.');
55
+ }
56
+ const oxyToken = options.oxyToken;
57
+ const transport = new transport_js_1.Transport({
58
+ baseUrl,
59
+ bearerToken: credential ? () => credential.bearerToken : () => oxyToken(),
60
+ timeoutMs,
61
+ maxAttempts: options.maxAttempts ?? transport_js_1.DEFAULT_MAX_ATTEMPTS,
62
+ fetch: fetchImpl,
63
+ });
64
+ this.applicationId = credential ? credential.applicationId : lazyApplicationId(transport);
65
+ this.reports = new reports_js_1.Reports({
66
+ transport,
67
+ applicationId: this.applicationId,
68
+ environment: options.sandbox === true ? 'sandbox' : 'production',
69
+ });
70
+ this.cases = new cases_js_1.Cases(transport);
71
+ this.decisions = new cases_js_1.Decisions(transport);
72
+ this.webhookEndpoints = new webhookEndpoints_js_1.WebhookEndpoints(transport);
73
+ this.communityNotes = new communityNotes_js_1.CommunityNotes(transport);
74
+ }
75
+ }
76
+ exports.CrowdSource = CrowdSource;
77
+ /**
78
+ * Asks CrowdSource which application this client is, once.
79
+ *
80
+ * Only the Oxy-token path needs this: a service key carries the id inside it.
81
+ * The promise is created at construction and awaited wherever the id is used,
82
+ * so the lookup happens at most once per client and never blocks a caller that
83
+ * does not need it (community notes never do; a report does, because its
84
+ * envelope names the application and the server refuses one that disagrees).
85
+ *
86
+ * A failure is not swallowed into a placeholder id. An envelope carrying the
87
+ * wrong application is refused by the server anyway, and a client that invented
88
+ * one would turn a clear "we could not identify you" into a confusing 403 on
89
+ * every report.
90
+ */
91
+ function lazyApplicationId(transport) {
92
+ let pending = null;
93
+ const resolve = () => (pending ?? (pending = askApplicationId(transport)));
94
+ // A thenable rather than a promise: nothing is requested until somebody
95
+ // awaits it, so a client that only reads community notes — which never name
96
+ // an application — makes no identity call at all. Awaiting it twice still
97
+ // makes one.
98
+ return { then: (onFulfilled, onRejected) => resolve().then(onFulfilled, onRejected) };
99
+ }
100
+ function askApplicationId(transport) {
101
+ return transport
102
+ .request({ method: 'GET', path: '/v1/applications/me' })
103
+ .then((body) => {
104
+ const applicationId = body?.applicationId;
105
+ if (typeof applicationId !== 'string' || applicationId.length === 0) {
106
+ throw new errors_js_1.CrowdSourceConfigurationError('CrowdSource did not name the application this token belongs to.');
107
+ }
108
+ return applicationId;
109
+ });
110
+ }
111
+ function normalisedBaseUrl(value) {
112
+ let parsed;
113
+ try {
114
+ parsed = new URL(value);
115
+ }
116
+ catch {
117
+ throw new errors_js_1.CrowdSourceConfigurationError(`'${value}' is not a usable CrowdSource base URL.`);
118
+ }
119
+ if (parsed.protocol !== 'https:' && parsed.hostname !== 'localhost') {
120
+ throw new errors_js_1.CrowdSourceConfigurationError('A CrowdSource base URL must be https. A service credential sent in clear is a credential you have to rotate.');
121
+ }
122
+ // Trailing slashes are stripped so `${baseUrl}/v1/reports` never doubles up.
123
+ return parsed.origin + parsed.pathname.replace(/\/+$/, '');
124
+ }
125
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { type CommunityNote, type CommunityNoteAssignment, type CommunityNoteAssignmentRequest, type CommunityNoteRatedList, type CommunityNoteRating, type CommunityNoteRatingSubmission, type CommunityNoteSubmission } from '@crowdsource.you/contracts';
2
+ import type { Transport } from './transport.js';
3
+ /**
4
+ * Community notes (`docs/architecture/community-notes.md`): reader-written context
5
+ * under the application's subjects, shown once raters of different viewpoints
6
+ * agree it is helpful.
7
+ *
8
+ * The application acts for its users, naming each by its own opaque principal id.
9
+ * Every write is retry-safe with no key from the caller where one can be derived
10
+ * from what the write IS — one note per writer per subject, one rating per rater
11
+ * per note — and the derived keys hash the principal ids, so an application's
12
+ * user ids never travel in a header a proxy might log. Drawing notes to rate has
13
+ * no such identity (two draws for one rater are two different requests), so that
14
+ * one method takes the key from the caller.
15
+ */
16
+ export interface CommunityNoteRequestOptions {
17
+ readonly idempotencyKey?: string;
18
+ readonly signal?: AbortSignal;
19
+ }
20
+ export interface CommunityNoteReadOptions {
21
+ readonly signal?: AbortSignal;
22
+ }
23
+ export declare class CommunityNotes {
24
+ private readonly transport;
25
+ constructor(transport: Transport);
26
+ /** Writes a note. A retry of the same writer's note on the same subject returns it. */
27
+ write(submission: CommunityNoteSubmission, options?: CommunityNoteRequestOptions): Promise<CommunityNote>;
28
+ /** Withdraws a note. Only its writer may; anyone else's note answers 404. */
29
+ withdraw(noteId: string, authorPrincipalId: string, options?: CommunityNoteRequestOptions): Promise<CommunityNote>;
30
+ /**
31
+ * Draws notes for one rater to rate — the ONLY way to be allowed to rate one.
32
+ *
33
+ * `idempotencyKey` is required: a retry with the same key returns the same
34
+ * batch, and a new key is a new draw.
35
+ */
36
+ drawToRate(request: CommunityNoteAssignmentRequest, options: CommunityNoteRequestOptions & {
37
+ readonly idempotencyKey: string;
38
+ }): Promise<CommunityNoteAssignment[]>;
39
+ /** Rates a note assigned to the rater. Final: a second rating is refused. */
40
+ rate(noteId: string, submission: CommunityNoteRatingSubmission, options?: CommunityNoteRequestOptions): Promise<CommunityNoteRating>;
41
+ /** The shown note, if any, for each of up to 50 subjects. */
42
+ shown(externalSubjectIds: readonly string[], options?: CommunityNoteReadOptions): Promise<CommunityNote[]>;
43
+ /** A writer's own notes, newest first. */
44
+ writtenBy(authorPrincipalId: string, options?: CommunityNoteReadOptions): Promise<CommunityNote[]>;
45
+ /** A rater's own ratings, each with the note it rated, newest first. */
46
+ ratedBy(raterPrincipalId: string, options?: CommunityNoteReadOptions): Promise<CommunityNoteRatedList['ratings']>;
47
+ private parse;
48
+ }
49
+ //# sourceMappingURL=communityNotes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"communityNotes.d.ts","sourceRoot":"","sources":["../src/communityNotes.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,8BAA8B,EACnC,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,6BAA6B,EAClC,KAAK,uBAAuB,EAC7B,MAAM,4BAA4B,CAAC;AAKpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAOD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAE1B,SAAS,EAAE,SAAS;IAIhC,uFAAuF;IACjF,KAAK,CAAC,UAAU,EAAE,uBAAuB,EAAE,OAAO,GAAE,2BAAgC,GAAG,OAAO,CAAC,aAAa,CAAC;IAgBnH,6EAA6E;IACvE,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,aAAa,CAAC;IAczB;;;;;OAKG;IACG,UAAU,CACd,OAAO,EAAE,8BAA8B,EACvC,OAAO,EAAE,2BAA2B,GAAG;QAAE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GACzE,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAerC,6EAA6E;IACvE,IAAI,CACR,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,6BAA6B,EACzC,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,mBAAmB,CAAC;IAe/B,6DAA6D;IACvD,KAAK,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAepH,0CAA0C;IACpC,SAAS,CAAC,iBAAiB,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAa5G,wEAAwE;IAClE,OAAO,CACX,gBAAgB,EAAE,MAAM,EACxB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAa7C,OAAO,CAAC,KAAK;CAUd"}
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CommunityNotes = void 0;
4
+ const contracts_1 = require("@crowdsource.you/contracts");
5
+ const digest_js_1 = require("./digest.js");
6
+ const errors_js_1 = require("./errors.js");
7
+ /** A stable, header-safe digest of the parts that identify a write. */
8
+ function digestOf(...parts) {
9
+ return (0, digest_js_1.sha256Digest)(JSON.stringify(parts)).replace('sha256:', '');
10
+ }
11
+ class CommunityNotes {
12
+ constructor(transport) {
13
+ this.transport = transport;
14
+ }
15
+ /** Writes a note. A retry of the same writer's note on the same subject returns it. */
16
+ async write(submission, options = {}) {
17
+ return this.parse(contracts_1.CommunityNoteSchema, await this.transport.request({
18
+ method: 'POST',
19
+ path: '/v1/community-notes',
20
+ body: submission,
21
+ idempotencyKey: options.idempotencyKey ??
22
+ `community-note.${digestOf(submission.externalSubjectId, submission.authorPrincipalId)}`,
23
+ signal: options.signal,
24
+ }), 'a community note');
25
+ }
26
+ /** Withdraws a note. Only its writer may; anyone else's note answers 404. */
27
+ async withdraw(noteId, authorPrincipalId, options = {}) {
28
+ return this.parse(contracts_1.CommunityNoteSchema, await this.transport.request({
29
+ method: 'POST',
30
+ path: `/v1/community-notes/${encodeURIComponent(noteId)}/withdraw`,
31
+ body: { authorPrincipalId },
32
+ idempotencyKey: options.idempotencyKey ?? `community-note-withdrawal.${digestOf(noteId)}`,
33
+ signal: options.signal,
34
+ }), 'a community note');
35
+ }
36
+ /**
37
+ * Draws notes for one rater to rate — the ONLY way to be allowed to rate one.
38
+ *
39
+ * `idempotencyKey` is required: a retry with the same key returns the same
40
+ * batch, and a new key is a new draw.
41
+ */
42
+ async drawToRate(request, options) {
43
+ const batch = this.parse(contracts_1.CommunityNoteAssignmentBatchSchema, await this.transport.request({
44
+ method: 'POST',
45
+ path: '/v1/community-notes/assignments',
46
+ body: request,
47
+ idempotencyKey: options.idempotencyKey,
48
+ signal: options.signal,
49
+ }), 'a batch of community note assignments');
50
+ return batch.assignments;
51
+ }
52
+ /** Rates a note assigned to the rater. Final: a second rating is refused. */
53
+ async rate(noteId, submission, options = {}) {
54
+ return this.parse(contracts_1.CommunityNoteRatingSchema, await this.transport.request({
55
+ method: 'POST',
56
+ path: `/v1/community-notes/${encodeURIComponent(noteId)}/ratings`,
57
+ body: submission,
58
+ idempotencyKey: options.idempotencyKey ?? `community-note-rating.${digestOf(noteId, submission.raterPrincipalId)}`,
59
+ signal: options.signal,
60
+ }), 'a community note rating');
61
+ }
62
+ /** The shown note, if any, for each of up to 50 subjects. */
63
+ async shown(externalSubjectIds, options = {}) {
64
+ const subjects = [...new Set(externalSubjectIds)];
65
+ if (subjects.length === 0)
66
+ return [];
67
+ const response = this.parse(contracts_1.ShownCommunityNotesSchema, await this.transport.request({
68
+ method: 'GET',
69
+ path: `/v1/community-notes/shown?subjects=${subjects.map(encodeURIComponent).join(',')}`,
70
+ signal: options.signal,
71
+ }), 'a shown community notes lookup');
72
+ return response.notes;
73
+ }
74
+ /** A writer's own notes, newest first. */
75
+ async writtenBy(authorPrincipalId, options = {}) {
76
+ const response = this.parse(contracts_1.CommunityNoteListSchema, await this.transport.request({
77
+ method: 'GET',
78
+ path: `/v1/community-notes/principals/${encodeURIComponent(authorPrincipalId)}/notes`,
79
+ signal: options.signal,
80
+ }), 'a list of community notes');
81
+ return response.notes;
82
+ }
83
+ /** A rater's own ratings, each with the note it rated, newest first. */
84
+ async ratedBy(raterPrincipalId, options = {}) {
85
+ const response = this.parse(contracts_1.CommunityNoteRatedListSchema, await this.transport.request({
86
+ method: 'GET',
87
+ path: `/v1/community-notes/principals/${encodeURIComponent(raterPrincipalId)}/ratings`,
88
+ signal: options.signal,
89
+ }), 'a list of community note ratings');
90
+ return response.ratings;
91
+ }
92
+ parse(schema, response, what) {
93
+ const parsed = schema.safeParse(response);
94
+ if (!parsed.success) {
95
+ throw new errors_js_1.CrowdSourceTransportError(`CrowdSource answered with ${what} this client does not recognise.`, {
96
+ retryable: false,
97
+ cause: parsed.error,
98
+ });
99
+ }
100
+ return parsed.data;
101
+ }
102
+ }
103
+ exports.CommunityNotes = CommunityNotes;
104
+ //# sourceMappingURL=communityNotes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"communityNotes.js","sourceRoot":"","sources":["../src/communityNotes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The service key, and the only place `applicationId` may come from.
3
+ *
4
+ * Appendix F: "`applicationId` comes from the credential, never from the request
5
+ * body." The backend already honours that — it derives the tenant from the
6
+ * presented token and 403s an envelope naming a different application. But the
7
+ * Case Envelope contract still REQUIRES `applicationId` on the wire, so a client
8
+ * has to put one there, and the question this module answers is where it gets it
9
+ * from without ever asking the integrator.
10
+ *
11
+ * The answer is that it comes out of the credential, literally. CrowdSource
12
+ * issues three values together (`provisioning.service.ts`
13
+ * `IssuedCredential`): the application the credential belongs to, the credential
14
+ * id, and the secret. Joined into ONE opaque string, they are a single
15
+ * environment variable an integrator pastes and never reads — and the
16
+ * `applicationId` in an envelope is then a value the client READ OFF THE
17
+ * CREDENTIAL, not a parameter anyone can pass. There is no `applicationId`
18
+ * option on the client, none on any method, and none an envelope input can
19
+ * carry; supplying one is not "discouraged", it is unexpressible.
20
+ *
21
+ * `:` is the separator because the contract's identifier grammar excludes it and
22
+ * credential secrets are base64url, so the split is unambiguous for every value
23
+ * CrowdSource can issue. A `.` separator would not be: `IdentifierSchema` allows
24
+ * dots inside an id.
25
+ *
26
+ * NOTE FOR THE CONSOLE: `issueApplicationCredential` returns the three values
27
+ * separately and the bearer token as `<credentialId>.<secret>`. Whatever surface
28
+ * shows an integrator their key must show `formatServiceKey(issued)` — the
29
+ * composite below — or the integrator has two values to configure instead of
30
+ * one, and the client has no way to know its own application.
31
+ */
32
+ /** The credential CrowdSource issues, parsed. */
33
+ export interface ServiceCredential {
34
+ /** The application this credential belongs to. The tenant, and its only source. */
35
+ readonly applicationId: string;
36
+ readonly credentialId: string;
37
+ /**
38
+ * The bearer token the API authenticates — `<credentialId>.<secret>`, exactly
39
+ * what `credential.service.ts` parses. The secret is never held on its own,
40
+ * so nothing in this client can log or serialise it by touching a field.
41
+ */
42
+ readonly bearerToken: string;
43
+ }
44
+ /** The three values `issueApplicationCredential` returns, as one opaque key. */
45
+ export declare function formatServiceKey(issued: {
46
+ applicationId: string;
47
+ credentialId: string;
48
+ secret: string;
49
+ }): string;
50
+ /**
51
+ * Parses a service key.
52
+ *
53
+ * Every rejection names which part is wrong and none of them echoes the secret:
54
+ * a configuration error is read from a log, and a log line is the second most
55
+ * common way a credential leaks after a screenshot.
56
+ */
57
+ export declare function parseServiceKey(serviceKey: string): ServiceCredential;
58
+ //# sourceMappingURL=credential.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential.d.ts","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAQH,iDAAiD;AACjD,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAET;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,CAgCrE"}
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ /**
3
+ * The service key, and the only place `applicationId` may come from.
4
+ *
5
+ * Appendix F: "`applicationId` comes from the credential, never from the request
6
+ * body." The backend already honours that — it derives the tenant from the
7
+ * presented token and 403s an envelope naming a different application. But the
8
+ * Case Envelope contract still REQUIRES `applicationId` on the wire, so a client
9
+ * has to put one there, and the question this module answers is where it gets it
10
+ * from without ever asking the integrator.
11
+ *
12
+ * The answer is that it comes out of the credential, literally. CrowdSource
13
+ * issues three values together (`provisioning.service.ts`
14
+ * `IssuedCredential`): the application the credential belongs to, the credential
15
+ * id, and the secret. Joined into ONE opaque string, they are a single
16
+ * environment variable an integrator pastes and never reads — and the
17
+ * `applicationId` in an envelope is then a value the client READ OFF THE
18
+ * CREDENTIAL, not a parameter anyone can pass. There is no `applicationId`
19
+ * option on the client, none on any method, and none an envelope input can
20
+ * carry; supplying one is not "discouraged", it is unexpressible.
21
+ *
22
+ * `:` is the separator because the contract's identifier grammar excludes it and
23
+ * credential secrets are base64url, so the split is unambiguous for every value
24
+ * CrowdSource can issue. A `.` separator would not be: `IdentifierSchema` allows
25
+ * dots inside an id.
26
+ *
27
+ * NOTE FOR THE CONSOLE: `issueApplicationCredential` returns the three values
28
+ * separately and the bearer token as `<credentialId>.<secret>`. Whatever surface
29
+ * shows an integrator their key must show `formatServiceKey(issued)` — the
30
+ * composite below — or the integrator has two values to configure instead of
31
+ * one, and the client has no way to know its own application.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.formatServiceKey = formatServiceKey;
35
+ exports.parseServiceKey = parseServiceKey;
36
+ const contracts_1 = require("@crowdsource.you/contracts");
37
+ const errors_js_1 = require("./errors.js");
38
+ const SERVICE_KEY_SEPARATOR = ':';
39
+ /** The three values `issueApplicationCredential` returns, as one opaque key. */
40
+ function formatServiceKey(issued) {
41
+ return [issued.applicationId, issued.credentialId, issued.secret].join(SERVICE_KEY_SEPARATOR);
42
+ }
43
+ /**
44
+ * Parses a service key.
45
+ *
46
+ * Every rejection names which part is wrong and none of them echoes the secret:
47
+ * a configuration error is read from a log, and a log line is the second most
48
+ * common way a credential leaks after a screenshot.
49
+ */
50
+ function parseServiceKey(serviceKey) {
51
+ const trimmed = serviceKey.trim();
52
+ if (!trimmed) {
53
+ throw new errors_js_1.CrowdSourceConfigurationError('The CrowdSource service key is empty.');
54
+ }
55
+ const parts = trimmed.split(SERVICE_KEY_SEPARATOR);
56
+ if (parts.length !== 3) {
57
+ throw new errors_js_1.CrowdSourceConfigurationError(`A CrowdSource service key is three colon-separated parts (applicationId:credentialId:secret); this one has ${parts.length}.`);
58
+ }
59
+ const [applicationId, credentialId, secret] = parts;
60
+ if (!contracts_1.IdentifierSchema.safeParse(applicationId).success) {
61
+ throw new errors_js_1.CrowdSourceConfigurationError('The first part of a CrowdSource service key must be the applicationId it was issued for.');
62
+ }
63
+ if (!contracts_1.IdentifierSchema.safeParse(credentialId).success) {
64
+ throw new errors_js_1.CrowdSourceConfigurationError('The second part of a CrowdSource service key must be the credentialId it was issued for.');
65
+ }
66
+ if (!secret) {
67
+ throw new errors_js_1.CrowdSourceConfigurationError('The third part of a CrowdSource service key must be the credential secret.');
68
+ }
69
+ return { applicationId, credentialId, bearerToken: `${credentialId}.${secret}` };
70
+ }
71
+ //# sourceMappingURL=credential.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential.js","sourceRoot":"","sources":["../src/credential.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;AAsBH,4CAMC;AASD,0CAgCC;AAnED,0DAA8D;AAE9D,2CAA4D;AAE5D,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAelC,gFAAgF;AAChF,SAAgB,gBAAgB,CAAC,MAIhC;IACC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAChG,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,UAAkB;IAChD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,yCAA6B,CAAC,uCAAuC,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACnD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,yCAA6B,CACrC,8GAA8G,KAAK,CAAC,MAAM,GAAG,CAC9H,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,aAAa,EAAE,YAAY,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAEpD,IAAI,CAAC,4BAAgB,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC;QACvD,MAAM,IAAI,yCAA6B,CACrC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,4BAAgB,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC;QACtD,MAAM,IAAI,yCAA6B,CACrC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,yCAA6B,CACrC,4EAA4E,CAC7E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,YAAY,IAAI,MAAM,EAAE,EAAE,CAAC;AACnF,CAAC"}