@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,121 @@
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 { parseServiceKey } from './credential.js';
25
+ import { DEFAULT_BASE_URL } from './defaults.js';
26
+ import { CrowdSourceConfigurationError } from './errors.js';
27
+ import { Reports } from './reports.js';
28
+ import { DEFAULT_MAX_ATTEMPTS, DEFAULT_TIMEOUT_MS, Transport, } from './transport.js';
29
+ import { WebhookEndpoints } from './webhookEndpoints.js';
30
+ /** The environment variable a zero-configuration integration reads. */
31
+ export const SERVICE_KEY_ENV_VAR = 'CROWDSOURCE_SERVICE_KEY';
32
+ /** Overrides the service host. Set only when pointing at a local backend. */
33
+ export const BASE_URL_ENV_VAR = 'CROWDSOURCE_BASE_URL';
34
+ export class CrowdSource {
35
+ constructor(options = {}) {
36
+ /**
37
+ * Two ways to be an application, and exactly one of them is configured.
38
+ *
39
+ * The Oxy path is checked first so that a deployment which has BOTH — during
40
+ * the migration off shared secrets — uses the identity it can prove rather
41
+ * than the secret it still happens to hold. Removing the key is then the
42
+ * cleanup, not the cutover.
43
+ */
44
+ const credential = options.oxyToken
45
+ ? null
46
+ : parseServiceKey(options.serviceKey ?? process.env[SERVICE_KEY_ENV_VAR] ?? '');
47
+ const baseUrl = normalisedBaseUrl(options.baseUrl ?? process.env[BASE_URL_ENV_VAR] ?? DEFAULT_BASE_URL);
48
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
49
+ const fetchImpl = options.fetch ?? globalThis.fetch;
50
+ if (typeof fetchImpl !== 'function') {
51
+ throw new CrowdSourceConfigurationError('This runtime has no global fetch. Pass one as the `fetch` option.');
52
+ }
53
+ const oxyToken = options.oxyToken;
54
+ const transport = new Transport({
55
+ baseUrl,
56
+ bearerToken: credential ? () => credential.bearerToken : () => oxyToken(),
57
+ timeoutMs,
58
+ maxAttempts: options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
59
+ fetch: fetchImpl,
60
+ });
61
+ this.applicationId = credential ? credential.applicationId : lazyApplicationId(transport);
62
+ this.reports = new Reports({
63
+ transport,
64
+ applicationId: this.applicationId,
65
+ environment: options.sandbox === true ? 'sandbox' : 'production',
66
+ });
67
+ this.cases = new Cases(transport);
68
+ this.decisions = new Decisions(transport);
69
+ this.webhookEndpoints = new WebhookEndpoints(transport);
70
+ this.communityNotes = new CommunityNotes(transport);
71
+ }
72
+ }
73
+ /**
74
+ * Asks CrowdSource which application this client is, once.
75
+ *
76
+ * Only the Oxy-token path needs this: a service key carries the id inside it.
77
+ * The promise is created at construction and awaited wherever the id is used,
78
+ * so the lookup happens at most once per client and never blocks a caller that
79
+ * does not need it (community notes never do; a report does, because its
80
+ * envelope names the application and the server refuses one that disagrees).
81
+ *
82
+ * A failure is not swallowed into a placeholder id. An envelope carrying the
83
+ * wrong application is refused by the server anyway, and a client that invented
84
+ * one would turn a clear "we could not identify you" into a confusing 403 on
85
+ * every report.
86
+ */
87
+ function lazyApplicationId(transport) {
88
+ let pending = null;
89
+ const resolve = () => (pending ?? (pending = askApplicationId(transport)));
90
+ // A thenable rather than a promise: nothing is requested until somebody
91
+ // awaits it, so a client that only reads community notes — which never name
92
+ // an application — makes no identity call at all. Awaiting it twice still
93
+ // makes one.
94
+ return { then: (onFulfilled, onRejected) => resolve().then(onFulfilled, onRejected) };
95
+ }
96
+ function askApplicationId(transport) {
97
+ return transport
98
+ .request({ method: 'GET', path: '/v1/applications/me' })
99
+ .then((body) => {
100
+ const applicationId = body?.applicationId;
101
+ if (typeof applicationId !== 'string' || applicationId.length === 0) {
102
+ throw new CrowdSourceConfigurationError('CrowdSource did not name the application this token belongs to.');
103
+ }
104
+ return applicationId;
105
+ });
106
+ }
107
+ function normalisedBaseUrl(value) {
108
+ let parsed;
109
+ try {
110
+ parsed = new URL(value);
111
+ }
112
+ catch {
113
+ throw new CrowdSourceConfigurationError(`'${value}' is not a usable CrowdSource base URL.`);
114
+ }
115
+ if (parsed.protocol !== 'https:' && parsed.hostname !== 'localhost') {
116
+ throw new CrowdSourceConfigurationError('A CrowdSource base URL must be https. A service credential sent in clear is a credential you have to rotate.');
117
+ }
118
+ // Trailing slashes are stripped so `${baseUrl}/v1/reports` never doubles up.
119
+ return parsed.origin + parsed.pathname.replace(/\/+$/, '');
120
+ }
121
+ //# 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,100 @@
1
+ import { CommunityNoteAssignmentBatchSchema, CommunityNoteListSchema, CommunityNoteRatedListSchema, CommunityNoteRatingSchema, CommunityNoteSchema, ShownCommunityNotesSchema, } from '@crowdsource.you/contracts';
2
+ import { sha256Digest } from './digest.js';
3
+ import { CrowdSourceTransportError } from './errors.js';
4
+ /** A stable, header-safe digest of the parts that identify a write. */
5
+ function digestOf(...parts) {
6
+ return sha256Digest(JSON.stringify(parts)).replace('sha256:', '');
7
+ }
8
+ export class CommunityNotes {
9
+ constructor(transport) {
10
+ this.transport = transport;
11
+ }
12
+ /** Writes a note. A retry of the same writer's note on the same subject returns it. */
13
+ async write(submission, options = {}) {
14
+ return this.parse(CommunityNoteSchema, await this.transport.request({
15
+ method: 'POST',
16
+ path: '/v1/community-notes',
17
+ body: submission,
18
+ idempotencyKey: options.idempotencyKey ??
19
+ `community-note.${digestOf(submission.externalSubjectId, submission.authorPrincipalId)}`,
20
+ signal: options.signal,
21
+ }), 'a community note');
22
+ }
23
+ /** Withdraws a note. Only its writer may; anyone else's note answers 404. */
24
+ async withdraw(noteId, authorPrincipalId, options = {}) {
25
+ return this.parse(CommunityNoteSchema, await this.transport.request({
26
+ method: 'POST',
27
+ path: `/v1/community-notes/${encodeURIComponent(noteId)}/withdraw`,
28
+ body: { authorPrincipalId },
29
+ idempotencyKey: options.idempotencyKey ?? `community-note-withdrawal.${digestOf(noteId)}`,
30
+ signal: options.signal,
31
+ }), 'a community note');
32
+ }
33
+ /**
34
+ * Draws notes for one rater to rate — the ONLY way to be allowed to rate one.
35
+ *
36
+ * `idempotencyKey` is required: a retry with the same key returns the same
37
+ * batch, and a new key is a new draw.
38
+ */
39
+ async drawToRate(request, options) {
40
+ const batch = this.parse(CommunityNoteAssignmentBatchSchema, await this.transport.request({
41
+ method: 'POST',
42
+ path: '/v1/community-notes/assignments',
43
+ body: request,
44
+ idempotencyKey: options.idempotencyKey,
45
+ signal: options.signal,
46
+ }), 'a batch of community note assignments');
47
+ return batch.assignments;
48
+ }
49
+ /** Rates a note assigned to the rater. Final: a second rating is refused. */
50
+ async rate(noteId, submission, options = {}) {
51
+ return this.parse(CommunityNoteRatingSchema, await this.transport.request({
52
+ method: 'POST',
53
+ path: `/v1/community-notes/${encodeURIComponent(noteId)}/ratings`,
54
+ body: submission,
55
+ idempotencyKey: options.idempotencyKey ?? `community-note-rating.${digestOf(noteId, submission.raterPrincipalId)}`,
56
+ signal: options.signal,
57
+ }), 'a community note rating');
58
+ }
59
+ /** The shown note, if any, for each of up to 50 subjects. */
60
+ async shown(externalSubjectIds, options = {}) {
61
+ const subjects = [...new Set(externalSubjectIds)];
62
+ if (subjects.length === 0)
63
+ return [];
64
+ const response = this.parse(ShownCommunityNotesSchema, await this.transport.request({
65
+ method: 'GET',
66
+ path: `/v1/community-notes/shown?subjects=${subjects.map(encodeURIComponent).join(',')}`,
67
+ signal: options.signal,
68
+ }), 'a shown community notes lookup');
69
+ return response.notes;
70
+ }
71
+ /** A writer's own notes, newest first. */
72
+ async writtenBy(authorPrincipalId, options = {}) {
73
+ const response = this.parse(CommunityNoteListSchema, await this.transport.request({
74
+ method: 'GET',
75
+ path: `/v1/community-notes/principals/${encodeURIComponent(authorPrincipalId)}/notes`,
76
+ signal: options.signal,
77
+ }), 'a list of community notes');
78
+ return response.notes;
79
+ }
80
+ /** A rater's own ratings, each with the note it rated, newest first. */
81
+ async ratedBy(raterPrincipalId, options = {}) {
82
+ const response = this.parse(CommunityNoteRatedListSchema, await this.transport.request({
83
+ method: 'GET',
84
+ path: `/v1/community-notes/principals/${encodeURIComponent(raterPrincipalId)}/ratings`,
85
+ signal: options.signal,
86
+ }), 'a list of community note ratings');
87
+ return response.ratings;
88
+ }
89
+ parse(schema, response, what) {
90
+ const parsed = schema.safeParse(response);
91
+ if (!parsed.success) {
92
+ throw new CrowdSourceTransportError(`CrowdSource answered with ${what} this client does not recognise.`, {
93
+ retryable: false,
94
+ cause: parsed.error,
95
+ });
96
+ }
97
+ return parsed.data;
98
+ }
99
+ }
100
+ //# 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,67 @@
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
+ import { IdentifierSchema } from '@crowdsource.you/contracts';
33
+ import { CrowdSourceConfigurationError } from './errors.js';
34
+ const SERVICE_KEY_SEPARATOR = ':';
35
+ /** The three values `issueApplicationCredential` returns, as one opaque key. */
36
+ export function formatServiceKey(issued) {
37
+ return [issued.applicationId, issued.credentialId, issued.secret].join(SERVICE_KEY_SEPARATOR);
38
+ }
39
+ /**
40
+ * Parses a service key.
41
+ *
42
+ * Every rejection names which part is wrong and none of them echoes the secret:
43
+ * a configuration error is read from a log, and a log line is the second most
44
+ * common way a credential leaks after a screenshot.
45
+ */
46
+ export function parseServiceKey(serviceKey) {
47
+ const trimmed = serviceKey.trim();
48
+ if (!trimmed) {
49
+ throw new CrowdSourceConfigurationError('The CrowdSource service key is empty.');
50
+ }
51
+ const parts = trimmed.split(SERVICE_KEY_SEPARATOR);
52
+ if (parts.length !== 3) {
53
+ throw new CrowdSourceConfigurationError(`A CrowdSource service key is three colon-separated parts (applicationId:credentialId:secret); this one has ${parts.length}.`);
54
+ }
55
+ const [applicationId, credentialId, secret] = parts;
56
+ if (!IdentifierSchema.safeParse(applicationId).success) {
57
+ throw new CrowdSourceConfigurationError('The first part of a CrowdSource service key must be the applicationId it was issued for.');
58
+ }
59
+ if (!IdentifierSchema.safeParse(credentialId).success) {
60
+ throw new CrowdSourceConfigurationError('The second part of a CrowdSource service key must be the credentialId it was issued for.');
61
+ }
62
+ if (!secret) {
63
+ throw new CrowdSourceConfigurationError('The third part of a CrowdSource service key must be the credential secret.');
64
+ }
65
+ return { applicationId, credentialId, bearerToken: `${credentialId}.${secret}` };
66
+ }
67
+ //# sourceMappingURL=credential.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential.js","sourceRoot":"","sources":["../../src/credential.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAE9D,OAAO,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAelC,gFAAgF;AAChF,MAAM,UAAU,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,MAAM,UAAU,eAAe,CAAC,UAAkB;IAChD,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,6BAA6B,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,6BAA6B,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,gBAAgB,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC;QACvD,MAAM,IAAI,6BAA6B,CACrC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC;QACtD,MAAM,IAAI,6BAA6B,CACrC,0FAA0F,CAC3F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,6BAA6B,CACrC,4EAA4E,CAC7E,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,GAAG,YAAY,IAAI,MAAM,EAAE,EAAE,CAAC;AACnF,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * What an integrator does not have to declare, and why each default is safe to
3
+ * apply without being asked.
4
+ *
5
+ * The product requirement is near-zero configuration: an application should be
6
+ * able to report a piece of content with a credential and the object itself. The
7
+ * plan's requirement pulling the other way is §6.4 — every decision records the
8
+ * policy version it was decided under, and a policy update must never silently
9
+ * change what a past decision meant.
10
+ *
11
+ * Both hold only if the default is a PINNED VERSION rather than "whatever is
12
+ * current". `DEFAULT_POLICY` below names a specific immutable published version.
13
+ * When CrowdSource publishes a newer baseline, this constant changes in a
14
+ * release of this package and integrators adopt it by upgrading — which is
15
+ * visible in a lockfile, reviewable in a diff, and dated. The alternative
16
+ * (resolving "latest" server-side) would move the policy version under an
17
+ * application that never changed a line, and would silently split
18
+ * `caseDedupKey` — §7.3 makes the policy version part of that key, so the day it
19
+ * moved, two reports about one post would open two cases.
20
+ */
21
+ /** Where the service lives. One deployment; there is no sandbox host. */
22
+ export const DEFAULT_BASE_URL = 'https://api.crowdsource.oxy.so';
23
+ /**
24
+ * The policy set a report is evaluated under when the application declares
25
+ * none: the baseline CrowdSource ships over its own universal taxonomy (§6.3).
26
+ *
27
+ * Must stay equal to `BASELINE_POLICY_SET_ID` / `BASELINE_POLICY_VERSION` in the
28
+ * backend's `modules/policy/policyBaseline.ts`, which is the registry that
29
+ * resolves it. A drift is not a type error and not a test failure anywhere else:
30
+ * it is a 422 on every zero-config report, in production, at the moment of
31
+ * upgrade. `__tests__/defaults.test.ts` reads that file and asserts the pair.
32
+ */
33
+ export const DEFAULT_POLICY = Object.freeze({
34
+ policySetId: 'crowdsource.baseline',
35
+ version: '2026.07',
36
+ });
37
+ /**
38
+ * §13.6's default retention: 30 days after a final decision, configurable by
39
+ * policy.
40
+ */
41
+ export const DEFAULT_RETENTION_DAYS = 30;
42
+ /**
43
+ * Allegation codes that must never reach a community jury (§7.5).
44
+ *
45
+ * §7.5 routes child sexual abuse and potentially illegal material to a
46
+ * specialist team under legal protocol, and non-consensual intimate material to
47
+ * specialist review. An integrator who never read §7.5 still gets that: an
48
+ * envelope alleging one of these is composed with `allowCommunityReview: false`,
49
+ * and an application that explicitly asks for `true` alongside one of them is
50
+ * REFUSED rather than quietly corrected — the belief that this material can be
51
+ * community-reviewed is the defect, and silently fixing the field would leave
52
+ * the belief in place.
53
+ *
54
+ * This is a floor, never a ceiling. Triage may route a case away from the
55
+ * community for reasons this list cannot see; nothing here can route one
56
+ * towards it.
57
+ */
58
+ export const COMMUNITY_REVIEW_FORBIDDEN_ALLEGATIONS = Object.freeze([
59
+ 'child_safety.sexualization',
60
+ 'child_safety.grooming',
61
+ 'child_safety.exploitation',
62
+ 'sexual_content.non_consensual',
63
+ 'sexual_content.exploitation',
64
+ ]);
65
+ const FORBIDDEN_SET = new Set(COMMUNITY_REVIEW_FORBIDDEN_ALLEGATIONS);
66
+ /** The allegations in `codes` that §7.5 keeps away from a community jury. */
67
+ export function allegationsForbiddingCommunityReview(codes) {
68
+ return codes.filter((code) => FORBIDDEN_SET.has(code));
69
+ }
70
+ /** The privacy terms of a report that declares none. */
71
+ export function defaultPrivacy(allegationCodes) {
72
+ return {
73
+ retentionDays: DEFAULT_RETENTION_DAYS,
74
+ allowCommunityReview: allegationsForbiddingCommunityReview(allegationCodes).length === 0,
75
+ };
76
+ }
77
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,yEAAyE;AACzE,MAAM,CAAC,MAAM,gBAAgB,GAAG,gCAAgC,CAAC;AAEjE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAkB,MAAM,CAAC,MAAM,CAAC;IACzD,WAAW,EAAE,sBAAsB;IACnC,OAAO,EAAE,SAAS;CACnB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAEzC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,sCAAsC,GAA4B,MAAM,CAAC,MAAM,CAAC;IAC3F,4BAA4B;IAC5B,uBAAuB;IACvB,2BAA2B;IAC3B,+BAA+B;IAC/B,6BAA6B;CAC9B,CAAC,CAAC;AAEH,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC,sCAAsC,CAAC,CAAC;AAE3F,6EAA6E;AAC7E,MAAM,UAAU,oCAAoC,CAClD,KAA8B;IAE9B,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,cAAc,CAAC,eAAwC;IACrE,OAAO;QACL,aAAa,EAAE,sBAAsB;QACrC,oBAAoB,EAAE,oCAAoC,CAAC,eAAe,CAAC,CAAC,MAAM,KAAK,CAAC;KACzF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Resource digests (§5.2 `sha256`, §5.6 "SHA 256 of each resource").
3
+ *
4
+ * Every inline resource in an envelope must carry a digest of "the exact
5
+ * representation reviewed", and asking an integrator to compute one is asking
6
+ * them to get canonical serialisation right — which they will do differently
7
+ * from the next integrator, and differently again from themselves next quarter.
8
+ * So the client computes it, from one rule applied to every resource type:
9
+ *
10
+ * sha256:<hex of SHA-256 over canonicalJson({ type, data }) in UTF-8>
11
+ *
12
+ * `type` is inside the digest so a `text` resource and a `metadata` resource
13
+ * carrying identical fields do not produce the same digest. `custom` hashes its
14
+ * registered `schemaId` and `payload` for the same reason.
15
+ *
16
+ * Determinism is not a nicety here, it is the deduplication invariant. §7.3
17
+ * requires two people reporting the same version of the same post to land on ONE
18
+ * case, and the server's `caseDedupKey` is computed over a projection of the
19
+ * envelope that INCLUDES every resource verbatim, digest and all. A digest that
20
+ * varied with key order — or with which reporter sent it — would give the same
21
+ * post two cases, and "one penalty per incident" would be broken by a JSON
22
+ * serialiser.
23
+ *
24
+ * Asset-backed resources are the exception the contract already makes: their
25
+ * bytes live outside the envelope and `asset.sha256` is the digest of those
26
+ * bytes, so nothing here computes one for them.
27
+ */
28
+ import { createHash } from 'node:crypto';
29
+ /**
30
+ * JSON with object keys in a fixed order and `undefined` members dropped.
31
+ *
32
+ * Array order is PRESERVED. In a conversation's `messageResourceIds` the order
33
+ * is the meaning, and the backend's own canonicaliser makes the same choice for
34
+ * the same reason.
35
+ */
36
+ export function canonicalJson(value) {
37
+ if (value === null || typeof value === 'boolean' || typeof value === 'string') {
38
+ return JSON.stringify(value);
39
+ }
40
+ if (typeof value === 'number') {
41
+ if (!Number.isFinite(value)) {
42
+ throw new TypeError('A resource cannot carry a non-finite number: it has no JSON form.');
43
+ }
44
+ return JSON.stringify(value);
45
+ }
46
+ if (isCanonicalArray(value)) {
47
+ return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`;
48
+ }
49
+ const members = Object.keys(value)
50
+ .sort()
51
+ .flatMap((key) => {
52
+ const member = value[key];
53
+ return member === undefined ? [] : [`${JSON.stringify(key)}:${canonicalJson(member)}`];
54
+ });
55
+ return `{${members.join(',')}}`;
56
+ }
57
+ /**
58
+ * A written-out predicate rather than a bare `Array.isArray`, because
59
+ * `Array.isArray` narrows `readonly T[]` in its TRUE branch only — the false
60
+ * branch keeps the array in the union and the object branch below stops
61
+ * type-checking.
62
+ */
63
+ function isCanonicalArray(value) {
64
+ return Array.isArray(value);
65
+ }
66
+ /** `sha256:<64 lowercase hex>` — the only digest form the contract accepts. */
67
+ export function sha256Digest(bytes) {
68
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
69
+ }
70
+ /** The digest of an inline resource's reviewable representation. */
71
+ export function resourceDigest(reviewable) {
72
+ return sha256Digest(Buffer.from(canonicalJson(reviewable), 'utf8'));
73
+ }
74
+ //# sourceMappingURL=digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/digest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAWzC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,KAAqB;IACjD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9E,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,SAAS,CAAC,mEAAmE,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IACrE,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;SAC/B,IAAI,EAAE;SACN,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QACf,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1B,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC,CAAC,CAAC;IAEL,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAClC,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,KAAqB;IAC7C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,KAA0B;IACrD,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACtE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,cAAc,CAAC,UAA0B;IACvD,OAAO,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACtE,CAAC"}