@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.
- package/README.md +571 -0
- package/dist/cases.d.ts +66 -0
- package/dist/cases.d.ts.map +1 -0
- package/dist/cases.js +79 -0
- package/dist/cases.js.map +1 -0
- package/dist/client.d.ts +90 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +125 -0
- package/dist/client.js.map +1 -0
- package/dist/communityNotes.d.ts +49 -0
- package/dist/communityNotes.d.ts.map +1 -0
- package/dist/communityNotes.js +104 -0
- package/dist/communityNotes.js.map +1 -0
- package/dist/credential.d.ts +58 -0
- package/dist/credential.d.ts.map +1 -0
- package/dist/credential.js +71 -0
- package/dist/credential.js.map +1 -0
- package/dist/defaults.d.ts +61 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +82 -0
- package/dist/defaults.js.map +1 -0
- package/dist/digest.d.ts +44 -0
- package/dist/digest.d.ts.map +1 -0
- package/dist/digest.js +79 -0
- package/dist/digest.js.map +1 -0
- package/dist/envelope.d.ts +143 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +258 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.d.ts +90 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +151 -0
- package/dist/errors.js.map +1 -0
- package/dist/esm/cases.js +74 -0
- package/dist/esm/cases.js.map +1 -0
- package/dist/esm/client.js +121 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/communityNotes.js +100 -0
- package/dist/esm/communityNotes.js.map +1 -0
- package/dist/esm/credential.js +67 -0
- package/dist/esm/credential.js.map +1 -0
- package/dist/esm/defaults.js +77 -0
- package/dist/esm/defaults.js.map +1 -0
- package/dist/esm/digest.js +74 -0
- package/dist/esm/digest.js.map +1 -0
- package/dist/esm/envelope.js +252 -0
- package/dist/esm/envelope.js.map +1 -0
- package/dist/esm/errors.js +140 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/express/index.js +29 -0
- package/dist/esm/express/index.js.map +1 -0
- package/dist/esm/express/middleware.js +293 -0
- package/dist/esm/express/middleware.js.map +1 -0
- package/dist/esm/express/store.js +69 -0
- package/dist/esm/express/store.js.map +1 -0
- package/dist/esm/express/verify.js +133 -0
- package/dist/esm/express/verify.js.map +1 -0
- package/dist/esm/index.js +57 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/outbox/client.js +43 -0
- package/dist/esm/outbox/client.js.map +1 -0
- package/dist/esm/outbox/decision.js +153 -0
- package/dist/esm/outbox/decision.js.map +1 -0
- package/dist/esm/outbox/delivery.js +129 -0
- package/dist/esm/outbox/delivery.js.map +1 -0
- package/dist/esm/outbox/enforcement/executor.js +219 -0
- package/dist/esm/outbox/enforcement/executor.js.map +1 -0
- package/dist/esm/outbox/enforcement/planner.js +326 -0
- package/dist/esm/outbox/enforcement/planner.js.map +1 -0
- package/dist/esm/outbox/evidence.js +173 -0
- package/dist/esm/outbox/evidence.js.map +1 -0
- package/dist/esm/outbox/inbound.js +87 -0
- package/dist/esm/outbox/inbound.js.map +1 -0
- package/dist/esm/outbox/index.js +71 -0
- package/dist/esm/outbox/index.js.map +1 -0
- package/dist/esm/outbox/intake.js +140 -0
- package/dist/esm/outbox/intake.js.map +1 -0
- package/dist/esm/outbox/integration.js +116 -0
- package/dist/esm/outbox/integration.js.map +1 -0
- package/dist/esm/outbox/outbox/dispatcher.js +114 -0
- package/dist/esm/outbox/outbox/dispatcher.js.map +1 -0
- package/dist/esm/outbox/outbox/service.js +271 -0
- package/dist/esm/outbox/outbox/service.js.map +1 -0
- package/dist/esm/outbox/postgres/index.js +41 -0
- package/dist/esm/outbox/postgres/index.js.map +1 -0
- package/dist/esm/outbox/postgres/registries.js +155 -0
- package/dist/esm/outbox/postgres/registries.js.map +1 -0
- package/dist/esm/outbox/postgres/reportColumns.js +191 -0
- package/dist/esm/outbox/postgres/reportColumns.js.map +1 -0
- package/dist/esm/outbox/postgres/store/enforcement.js +158 -0
- package/dist/esm/outbox/postgres/store/enforcement.js.map +1 -0
- package/dist/esm/outbox/postgres/store/events.js +101 -0
- package/dist/esm/outbox/postgres/store/events.js.map +1 -0
- package/dist/esm/outbox/postgres/store/index.js +59 -0
- package/dist/esm/outbox/postgres/store/index.js.map +1 -0
- package/dist/esm/outbox/postgres/store/outbox.js +207 -0
- package/dist/esm/outbox/postgres/store/outbox.js.map +1 -0
- package/dist/esm/outbox/postgres/store/reports.js +231 -0
- package/dist/esm/outbox/postgres/store/reports.js.map +1 -0
- package/dist/esm/outbox/postgres/store/transaction.js +29 -0
- package/dist/esm/outbox/postgres/store/transaction.js.map +1 -0
- package/dist/esm/outbox/postgres/tables.js +270 -0
- package/dist/esm/outbox/postgres/tables.js.map +1 -0
- package/dist/esm/outbox/reconciliation.js +157 -0
- package/dist/esm/outbox/reconciliation.js.map +1 -0
- package/dist/esm/outbox/reportStatus.js +22 -0
- package/dist/esm/outbox/reportStatus.js.map +1 -0
- package/dist/esm/outbox/retention.js +33 -0
- package/dist/esm/outbox/retention.js.map +1 -0
- package/dist/esm/outbox/store/types.js +30 -0
- package/dist/esm/outbox/store/types.js.map +1 -0
- package/dist/esm/outbox/types.js +23 -0
- package/dist/esm/outbox/types.js.map +1 -0
- package/dist/esm/outbox/webhook.js +145 -0
- package/dist/esm/outbox/webhook.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/reports.js +74 -0
- package/dist/esm/reports.js.map +1 -0
- package/dist/esm/testing/fixtures.js +132 -0
- package/dist/esm/testing/fixtures.js.map +1 -0
- package/dist/esm/testing/index.js +33 -0
- package/dist/esm/testing/index.js.map +1 -0
- package/dist/esm/testing/sandbox.js +337 -0
- package/dist/esm/testing/sandbox.js.map +1 -0
- package/dist/esm/testing/webhook-simulator.js +93 -0
- package/dist/esm/testing/webhook-simulator.js.map +1 -0
- package/dist/esm/transport.js +177 -0
- package/dist/esm/transport.js.map +1 -0
- package/dist/esm/webhookEndpoints.js +139 -0
- package/dist/esm/webhookEndpoints.js.map +1 -0
- package/dist/express/index.d.ts +32 -0
- package/dist/express/index.d.ts.map +1 -0
- package/dist/express/index.js +40 -0
- package/dist/express/index.js.map +1 -0
- package/dist/express/middleware.d.ts +97 -0
- package/dist/express/middleware.d.ts.map +1 -0
- package/dist/express/middleware.js +298 -0
- package/dist/express/middleware.js.map +1 -0
- package/dist/express/store.d.ts +45 -0
- package/dist/express/store.d.ts.map +1 -0
- package/dist/express/store.js +72 -0
- package/dist/express/store.js.map +1 -0
- package/dist/express/verify.d.ts +86 -0
- package/dist/express/verify.d.ts.map +1 -0
- package/dist/express/verify.js +138 -0
- package/dist/express/verify.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/outbox/client.d.ts +35 -0
- package/dist/outbox/client.d.ts.map +1 -0
- package/dist/outbox/client.js +46 -0
- package/dist/outbox/client.js.map +1 -0
- package/dist/outbox/decision.d.ts +36 -0
- package/dist/outbox/decision.d.ts.map +1 -0
- package/dist/outbox/decision.js +159 -0
- package/dist/outbox/decision.js.map +1 -0
- package/dist/outbox/delivery.d.ts +50 -0
- package/dist/outbox/delivery.d.ts.map +1 -0
- package/dist/outbox/delivery.js +135 -0
- package/dist/outbox/delivery.js.map +1 -0
- package/dist/outbox/enforcement/executor.d.ts +26 -0
- package/dist/outbox/enforcement/executor.d.ts.map +1 -0
- package/dist/outbox/enforcement/executor.js +222 -0
- package/dist/outbox/enforcement/executor.js.map +1 -0
- package/dist/outbox/enforcement/planner.d.ts +50 -0
- package/dist/outbox/enforcement/planner.d.ts.map +1 -0
- package/dist/outbox/enforcement/planner.js +333 -0
- package/dist/outbox/enforcement/planner.js.map +1 -0
- package/dist/outbox/evidence.d.ts +114 -0
- package/dist/outbox/evidence.d.ts.map +1 -0
- package/dist/outbox/evidence.js +181 -0
- package/dist/outbox/evidence.js.map +1 -0
- package/dist/outbox/inbound.d.ts +78 -0
- package/dist/outbox/inbound.d.ts.map +1 -0
- package/dist/outbox/inbound.js +91 -0
- package/dist/outbox/inbound.js.map +1 -0
- package/dist/outbox/index.d.ts +79 -0
- package/dist/outbox/index.d.ts.map +1 -0
- package/dist/outbox/index.js +98 -0
- package/dist/outbox/index.js.map +1 -0
- package/dist/outbox/intake.d.ts +62 -0
- package/dist/outbox/intake.d.ts.map +1 -0
- package/dist/outbox/intake.js +145 -0
- package/dist/outbox/intake.js.map +1 -0
- package/dist/outbox/integration.d.ts +62 -0
- package/dist/outbox/integration.d.ts.map +1 -0
- package/dist/outbox/integration.js +119 -0
- package/dist/outbox/integration.js.map +1 -0
- package/dist/outbox/outbox/dispatcher.d.ts +26 -0
- package/dist/outbox/outbox/dispatcher.d.ts.map +1 -0
- package/dist/outbox/outbox/dispatcher.js +119 -0
- package/dist/outbox/outbox/dispatcher.js.map +1 -0
- package/dist/outbox/outbox/service.d.ts +124 -0
- package/dist/outbox/outbox/service.d.ts.map +1 -0
- package/dist/outbox/outbox/service.js +279 -0
- package/dist/outbox/outbox/service.js.map +1 -0
- package/dist/outbox/postgres/index.d.ts +44 -0
- package/dist/outbox/postgres/index.d.ts.map +1 -0
- package/dist/outbox/postgres/index.js +51 -0
- package/dist/outbox/postgres/index.js.map +1 -0
- package/dist/outbox/postgres/registries.d.ts +69 -0
- package/dist/outbox/postgres/registries.d.ts.map +1 -0
- package/dist/outbox/postgres/registries.js +159 -0
- package/dist/outbox/postgres/registries.js.map +1 -0
- package/dist/outbox/postgres/reportColumns.d.ts +154 -0
- package/dist/outbox/postgres/reportColumns.d.ts.map +1 -0
- package/dist/outbox/postgres/reportColumns.js +195 -0
- package/dist/outbox/postgres/reportColumns.js.map +1 -0
- package/dist/outbox/postgres/store/enforcement.d.ts +33 -0
- package/dist/outbox/postgres/store/enforcement.d.ts.map +1 -0
- package/dist/outbox/postgres/store/enforcement.js +161 -0
- package/dist/outbox/postgres/store/enforcement.js.map +1 -0
- package/dist/outbox/postgres/store/events.d.ts +46 -0
- package/dist/outbox/postgres/store/events.d.ts.map +1 -0
- package/dist/outbox/postgres/store/events.js +104 -0
- package/dist/outbox/postgres/store/events.js.map +1 -0
- package/dist/outbox/postgres/store/index.d.ts +27 -0
- package/dist/outbox/postgres/store/index.d.ts.map +1 -0
- package/dist/outbox/postgres/store/index.js +62 -0
- package/dist/outbox/postgres/store/index.js.map +1 -0
- package/dist/outbox/postgres/store/outbox.d.ts +8 -0
- package/dist/outbox/postgres/store/outbox.d.ts.map +1 -0
- package/dist/outbox/postgres/store/outbox.js +210 -0
- package/dist/outbox/postgres/store/outbox.js.map +1 -0
- package/dist/outbox/postgres/store/reports.d.ts +69 -0
- package/dist/outbox/postgres/store/reports.d.ts.map +1 -0
- package/dist/outbox/postgres/store/reports.js +234 -0
- package/dist/outbox/postgres/store/reports.js.map +1 -0
- package/dist/outbox/postgres/store/transaction.d.ts +49 -0
- package/dist/outbox/postgres/store/transaction.d.ts.map +1 -0
- package/dist/outbox/postgres/store/transaction.js +32 -0
- package/dist/outbox/postgres/store/transaction.js.map +1 -0
- package/dist/outbox/postgres/tables.d.ts +737 -0
- package/dist/outbox/postgres/tables.d.ts.map +1 -0
- package/dist/outbox/postgres/tables.js +273 -0
- package/dist/outbox/postgres/tables.js.map +1 -0
- package/dist/outbox/reconciliation.d.ts +49 -0
- package/dist/outbox/reconciliation.d.ts.map +1 -0
- package/dist/outbox/reconciliation.js +162 -0
- package/dist/outbox/reconciliation.js.map +1 -0
- package/dist/outbox/reportStatus.d.ts +12 -0
- package/dist/outbox/reportStatus.d.ts.map +1 -0
- package/dist/outbox/reportStatus.js +25 -0
- package/dist/outbox/reportStatus.js.map +1 -0
- package/dist/outbox/retention.d.ts +33 -0
- package/dist/outbox/retention.d.ts.map +1 -0
- package/dist/outbox/retention.js +36 -0
- package/dist/outbox/retention.js.map +1 -0
- package/dist/outbox/store/types.d.ts +466 -0
- package/dist/outbox/store/types.d.ts.map +1 -0
- package/dist/outbox/store/types.js +31 -0
- package/dist/outbox/store/types.js.map +1 -0
- package/dist/outbox/types.d.ts +660 -0
- package/dist/outbox/types.d.ts.map +1 -0
- package/dist/outbox/types.js +24 -0
- package/dist/outbox/types.js.map +1 -0
- package/dist/outbox/webhook.d.ts +15 -0
- package/dist/outbox/webhook.d.ts.map +1 -0
- package/dist/outbox/webhook.js +148 -0
- package/dist/outbox/webhook.js.map +1 -0
- package/dist/reports.d.ts +73 -0
- package/dist/reports.d.ts.map +1 -0
- package/dist/reports.js +78 -0
- package/dist/reports.js.map +1 -0
- package/dist/testing/fixtures.d.ts +62 -0
- package/dist/testing/fixtures.d.ts.map +1 -0
- package/dist/testing/fixtures.js +137 -0
- package/dist/testing/fixtures.js.map +1 -0
- package/dist/testing/index.d.ts +36 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +43 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/sandbox.d.ts +109 -0
- package/dist/testing/sandbox.d.ts.map +1 -0
- package/dist/testing/sandbox.js +342 -0
- package/dist/testing/sandbox.js.map +1 -0
- package/dist/testing/webhook-simulator.d.ts +78 -0
- package/dist/testing/webhook-simulator.d.ts.map +1 -0
- package/dist/testing/webhook-simulator.js +98 -0
- package/dist/testing/webhook-simulator.js.map +1 -0
- package/dist/transport.d.ts +59 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +181 -0
- package/dist/transport.js.map +1 -0
- package/dist/webhookEndpoints.d.ts +147 -0
- package/dist/webhookEndpoints.d.ts.map +1 -0
- package/dist/webhookEndpoints.js +143 -0
- package/dist/webhookEndpoints.js.map +1 -0
- package/package.json +127 -0
- package/src/cases.ts +119 -0
- package/src/client.ts +202 -0
- package/src/communityNotes.ts +194 -0
- package/src/credential.ts +100 -0
- package/src/defaults.ts +87 -0
- package/src/digest.ts +91 -0
- package/src/envelope.ts +432 -0
- package/src/errors.ts +165 -0
- package/src/express/index.ts +43 -0
- package/src/express/middleware.ts +387 -0
- package/src/express/store.ts +85 -0
- package/src/express/verify.ts +166 -0
- package/src/index.ts +107 -0
- package/src/outbox/client.ts +75 -0
- package/src/outbox/decision.ts +209 -0
- package/src/outbox/delivery.ts +163 -0
- package/src/outbox/enforcement/executor.ts +288 -0
- package/src/outbox/enforcement/planner.ts +377 -0
- package/src/outbox/evidence.ts +231 -0
- package/src/outbox/inbound.ts +140 -0
- package/src/outbox/index.ts +160 -0
- package/src/outbox/intake.ts +175 -0
- package/src/outbox/integration.ts +205 -0
- package/src/outbox/outbox/dispatcher.ts +131 -0
- package/src/outbox/outbox/service.ts +444 -0
- package/src/outbox/postgres/index.ts +59 -0
- package/src/outbox/postgres/registries.ts +177 -0
- package/src/outbox/postgres/reportColumns.ts +281 -0
- package/src/outbox/postgres/store/enforcement.ts +186 -0
- package/src/outbox/postgres/store/events.ts +112 -0
- package/src/outbox/postgres/store/index.ts +72 -0
- package/src/outbox/postgres/store/outbox.ts +273 -0
- package/src/outbox/postgres/store/reports.ts +349 -0
- package/src/outbox/postgres/store/transaction.ts +64 -0
- package/src/outbox/postgres/tables.ts +339 -0
- package/src/outbox/reconciliation.ts +199 -0
- package/src/outbox/reportStatus.ts +24 -0
- package/src/outbox/retention.ts +34 -0
- package/src/outbox/store/types.ts +528 -0
- package/src/outbox/types.ts +753 -0
- package/src/outbox/webhook.ts +165 -0
- package/src/reports.ts +138 -0
- package/src/testing/fixtures.ts +183 -0
- package/src/testing/index.ts +57 -0
- package/src/testing/sandbox.ts +469 -0
- package/src/testing/webhook-simulator.ts +166 -0
- package/src/transport.ts +250 -0
- package/src/webhookEndpoints.ts +256 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ModerationTaxonomyError = exports.ModerationSubjectUnsupportedError = void 0;
|
|
4
|
+
exports.snapshotHash = snapshotHash;
|
|
5
|
+
exports.createSubjectRegistry = createSubjectRegistry;
|
|
6
|
+
exports.buildModerationReportInput = buildModerationReportInput;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
/**
|
|
9
|
+
* Turning a stored report into the thing the SDK delivers.
|
|
10
|
+
*
|
|
11
|
+
* This is NOT a case-envelope builder, and the difference matters enough to
|
|
12
|
+
* name: `@crowdsource.you/core` builds the Case Envelope, and it deliberately does
|
|
13
|
+
* not export the function that does it. What this module produces is the SDK's
|
|
14
|
+
* `ReportInput` — a description of the material — and the SDK derives the
|
|
15
|
+
* envelope from it: resource ids, relations, digests, pseudonymous principal
|
|
16
|
+
* refs, the identity binding proof, the pinned policy version, the privacy terms
|
|
17
|
+
* and the idempotency key.
|
|
18
|
+
*
|
|
19
|
+
* That is not a technicality. Those derived values are exactly what the case
|
|
20
|
+
* dedup key is computed over, so an application that composed its own envelope
|
|
21
|
+
* would be the reason two reporters about one object opened two cases — and "one
|
|
22
|
+
* penalty per incident" would fail in production with nothing failing in a test.
|
|
23
|
+
* Building the description and letting the SDK build the document is what keeps
|
|
24
|
+
* that property true for every application at once.
|
|
25
|
+
*
|
|
26
|
+
* So this module does three things and no more: ask the registry for a snapshot
|
|
27
|
+
* of the reported object, translate the reporter's categories into allegations,
|
|
28
|
+
* and assemble both with the report's own identity.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* The material could not be described, because nothing can describe it.
|
|
32
|
+
*
|
|
33
|
+
* This is a DEFECT, not a state, and it should be unreachable. A report whose
|
|
34
|
+
* type has no subject provider never gets a delivery event in the first place —
|
|
35
|
+
* intake decides that from the same registry this module reads — so an event
|
|
36
|
+
* that arrives here has been created by something that bypassed intake, or by a
|
|
37
|
+
* deployment where a provider was removed while its reports were still in
|
|
38
|
+
* flight.
|
|
39
|
+
*
|
|
40
|
+
* `retryable: false` therefore dead-letters the outbox event, so the
|
|
41
|
+
* reconciliation sweep counts it and a human looks. The alternative — writing a
|
|
42
|
+
* local state — would file a genuine defect in the one place that looks
|
|
43
|
+
* identical to the deliberate local-only reports, and nothing would ever alert
|
|
44
|
+
* on it.
|
|
45
|
+
*
|
|
46
|
+
* Separate from "the object is gone": a deleted object is a fact about the world
|
|
47
|
+
* and closes the report normally.
|
|
48
|
+
*/
|
|
49
|
+
class ModerationSubjectUnsupportedError extends Error {
|
|
50
|
+
constructor(reportedType) {
|
|
51
|
+
super(`No moderation subject provider is registered for '${reportedType}'.`);
|
|
52
|
+
this.retryable = false;
|
|
53
|
+
this.name = 'ModerationSubjectUnsupportedError';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.ModerationSubjectUnsupportedError = ModerationSubjectUnsupportedError;
|
|
57
|
+
/**
|
|
58
|
+
* The application's taxonomy produced no allegation for a report.
|
|
59
|
+
*
|
|
60
|
+
* Not retryable and never silently defaulted. A report with no allegation is not
|
|
61
|
+
* a report, and substituting `other.unclassifiable` here would put a claim in
|
|
62
|
+
* front of a jury that no reporter made and that no version of the mapping
|
|
63
|
+
* records — the taxonomy is the application's to fix, and dead-lettering is what
|
|
64
|
+
* makes somebody fix it.
|
|
65
|
+
*/
|
|
66
|
+
class ModerationTaxonomyError extends Error {
|
|
67
|
+
constructor(categories) {
|
|
68
|
+
super(`The report taxonomy produced no allegation for categories [${categories.join(', ')}]. ` +
|
|
69
|
+
'Map every category to a code; use `other.unclassifiable` for one the universal taxonomy cannot name.');
|
|
70
|
+
this.retryable = false;
|
|
71
|
+
this.name = 'ModerationTaxonomyError';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.ModerationTaxonomyError = ModerationTaxonomyError;
|
|
75
|
+
/**
|
|
76
|
+
* SHA-256 of the snapshot, stored on the report.
|
|
77
|
+
*
|
|
78
|
+
* Taken over the described MATERIAL, not over the whole `ReportInput`: the
|
|
79
|
+
* report id, the reporter and the allegations are properties of the report, and
|
|
80
|
+
* including them would mean two people reporting identical content produced
|
|
81
|
+
* different hashes — which is the opposite of what this hash is for. Key order
|
|
82
|
+
* is fixed by the literal below rather than by `Object.keys`, so the digest is
|
|
83
|
+
* stable across Node versions and across a refactor that reorders a field.
|
|
84
|
+
*/
|
|
85
|
+
function snapshotHash(snapshot) {
|
|
86
|
+
const canonical = JSON.stringify({
|
|
87
|
+
subject: {
|
|
88
|
+
externalId: snapshot.subject.externalId,
|
|
89
|
+
type: snapshot.subject.type,
|
|
90
|
+
author: snapshot.subject.author?.oxyUserId ?? null,
|
|
91
|
+
},
|
|
92
|
+
content: snapshot.content,
|
|
93
|
+
attachments: snapshot.attachments ?? [],
|
|
94
|
+
context: snapshot.context ?? [],
|
|
95
|
+
});
|
|
96
|
+
return `sha256:${(0, crypto_1.createHash)('sha256').update(canonical, 'utf8').digest('hex')}`;
|
|
97
|
+
}
|
|
98
|
+
function createSubjectRegistry(providers) {
|
|
99
|
+
const byType = new Map();
|
|
100
|
+
for (const provider of providers) {
|
|
101
|
+
const existing = byType.get(provider.reportedType);
|
|
102
|
+
if (existing) {
|
|
103
|
+
/**
|
|
104
|
+
* Two providers for one noun is not a merge to resolve quietly: whichever
|
|
105
|
+
* one lost would describe nothing, and the subject type a jury reasons
|
|
106
|
+
* about would depend on array order.
|
|
107
|
+
*/
|
|
108
|
+
throw new Error(`Two moderation subject providers are registered for '${provider.reportedType}' ` +
|
|
109
|
+
`('${existing.subjectType}' and '${provider.subjectType}'). A reported type has one provider.`);
|
|
110
|
+
}
|
|
111
|
+
byType.set(provider.reportedType, provider);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
providerFor: (reportedType) => byType.get(reportedType),
|
|
115
|
+
deliverableTypes: () => Array.from(byType.keys()),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Describe a stored report for delivery, or report why it cannot be described.
|
|
120
|
+
*
|
|
121
|
+
* Returns `null` when the reported object no longer exists. That is not an error
|
|
122
|
+
* either: content deleted between the report and its delivery is ordinary, and
|
|
123
|
+
* evidence stays available through retention on CrowdSource's side — but an
|
|
124
|
+
* object the application never got to snapshot has no evidence to keep.
|
|
125
|
+
*/
|
|
126
|
+
async function buildModerationReportInput(input) {
|
|
127
|
+
const { report, registry, taxonomy } = input;
|
|
128
|
+
const provider = registry.providerFor(report.reportedType);
|
|
129
|
+
if (!provider)
|
|
130
|
+
throw new ModerationSubjectUnsupportedError(report.reportedType);
|
|
131
|
+
const snapshot = await provider.snapshot(report.reportedId);
|
|
132
|
+
if (!snapshot)
|
|
133
|
+
return null;
|
|
134
|
+
const allegationCodes = taxonomy.allegationsFor(report.categories);
|
|
135
|
+
if (allegationCodes.length === 0)
|
|
136
|
+
throw new ModerationTaxonomyError(report.categories);
|
|
137
|
+
const details = report.details?.trim();
|
|
138
|
+
return {
|
|
139
|
+
reportInput: {
|
|
140
|
+
externalReportId: report.id,
|
|
141
|
+
subject: snapshot.subject,
|
|
142
|
+
content: snapshot.content,
|
|
143
|
+
...(snapshot.attachments === undefined ? {} : { attachments: snapshot.attachments }),
|
|
144
|
+
...(snapshot.context === undefined ? {} : { context: snapshot.context }),
|
|
145
|
+
/**
|
|
146
|
+
* The reporter's own words ride on the FIRST allegation only.
|
|
147
|
+
*
|
|
148
|
+
* Repeating one free-text field across every code would say the reporter
|
|
149
|
+
* wrote it about each of them separately, and details are the reporter's
|
|
150
|
+
* claim and never evidence for it.
|
|
151
|
+
*/
|
|
152
|
+
allegations: allegationCodes.map((code, index) => index === 0 && details ? { code, details } : { code }),
|
|
153
|
+
/**
|
|
154
|
+
* The Oxy subject IS the binding proof. Reporters are stored as Oxy user
|
|
155
|
+
* ids, so there is no separate binding step to implement here.
|
|
156
|
+
*/
|
|
157
|
+
reportedBy: { oxyUserId: report.reporter },
|
|
158
|
+
/**
|
|
159
|
+
* The moment the USER reported it — the local report's own timestamp, not
|
|
160
|
+
* the moment of delivery. Any value invented per attempt would make every
|
|
161
|
+
* retry from the outbox a permanent 409.
|
|
162
|
+
*/
|
|
163
|
+
submittedAt: report.createdAt,
|
|
164
|
+
metadata: {
|
|
165
|
+
/**
|
|
166
|
+
* The application's own entries FIRST, so the two below cannot be
|
|
167
|
+
* shadowed. An adopter that names `taxonomyVersion` or `categories` gets
|
|
168
|
+
* them overwritten rather than winning — a case has to be readable back
|
|
169
|
+
* against the mapping that produced it, and that is not negotiable per
|
|
170
|
+
* application.
|
|
171
|
+
*/
|
|
172
|
+
...taxonomy.metadata,
|
|
173
|
+
/** So a case can be read back against the mapping that produced it. */
|
|
174
|
+
taxonomyVersion: taxonomy.version,
|
|
175
|
+
categories: [...report.categories].sort().join(','),
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
snapshotHash: snapshotHash(snapshot),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=evidence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evidence.js","sourceRoot":"","sources":["../../src/outbox/evidence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ProcessedEventStore } from '../express/index.js';
|
|
2
|
+
import { type OutboxService } from './outbox/service.js';
|
|
3
|
+
import type { ModerationEventStore, ModerationTransactionRunner } from './store/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* What happens between "a signed decision arrived" and "2xx".
|
|
6
|
+
*
|
|
7
|
+
* The receiver's contract is to answer quickly and queue the processing. So
|
|
8
|
+
* exactly two writes happen here, in ONE transaction — the event's audit row is
|
|
9
|
+
* completed and a durable `decision.apply` event is created — and the dispatcher
|
|
10
|
+
* does the rest.
|
|
11
|
+
*
|
|
12
|
+
* The transaction is what makes the dedupe safe. The middleware has already
|
|
13
|
+
* claimed the event id by inserting the row (see {@link createProcessedEventStore});
|
|
14
|
+
* if completing that row and queueing the work were two operations, a crash
|
|
15
|
+
* between them would leave an event that is permanently deduplicated with no
|
|
16
|
+
* work queued — a decision silently lost, with a row that says it arrived.
|
|
17
|
+
* Committing both together means the only two possible outcomes are "recorded
|
|
18
|
+
* and queued" or "neither", and "neither" releases the claim and gets
|
|
19
|
+
* redelivered.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The webhook dedupe store, backed by the moderation event log.
|
|
23
|
+
*
|
|
24
|
+
* `@crowdsource.you/core/express` defaults to an in-process store and says exactly
|
|
25
|
+
* when that is not enough: two instances behind a load balancer each keep their
|
|
26
|
+
* own, so a redelivery landing on the other instance is not deduplicated. Every
|
|
27
|
+
* Oxy backend runs several tasks behind one load balancer, so this is that case.
|
|
28
|
+
*
|
|
29
|
+
* The claim/release contract is the SDK's, and it is the right one. A row
|
|
30
|
+
* inserted BEFORE the handler runs means a concurrent redelivery cannot also run
|
|
31
|
+
* it; deleting that row when the handler THROWS means the sender's retry
|
|
32
|
+
* schedule can still deliver the event later. Recording the id only after
|
|
33
|
+
* success would let two copies run at once; recording it before and never
|
|
34
|
+
* releasing would make a transient failure permanent and lose a decision
|
|
35
|
+
* silently.
|
|
36
|
+
*
|
|
37
|
+
* The retention window is computed HERE rather than in a store, so both backends
|
|
38
|
+
* keep an audit row for the same length of time.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createProcessedEventStore<TTx>(events: ModerationEventStore<TTx>): ProcessedEventStore;
|
|
41
|
+
export interface RecordDecisionEventInput {
|
|
42
|
+
eventId: string;
|
|
43
|
+
type: string;
|
|
44
|
+
caseId: string;
|
|
45
|
+
/**
|
|
46
|
+
* The decision as delivered.
|
|
47
|
+
*
|
|
48
|
+
* `unknown`, deliberately. It is stored whole and parsed against the published
|
|
49
|
+
* contract by the worker that acts on it — these payloads are deliberately
|
|
50
|
+
* loose, and validating here would mean an event whose shape this deployment
|
|
51
|
+
* does not recognise yet is refused at the door and retried until it
|
|
52
|
+
* dead-letters, instead of being kept until the code catches up.
|
|
53
|
+
*/
|
|
54
|
+
decision: unknown;
|
|
55
|
+
}
|
|
56
|
+
export interface InboundService {
|
|
57
|
+
/** Record a decision-bearing event and queue its application. */
|
|
58
|
+
recordDecisionEvent(input: RecordDecisionEventInput): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Record an event there is nothing to do about.
|
|
61
|
+
*
|
|
62
|
+
* `case.created`, `case.escalated`, `case.closed` and any type a newer
|
|
63
|
+
* CrowdSource introduces. No outbox row, because no work — but the row is
|
|
64
|
+
* kept, because "did CrowdSource tell us about this case, and when" is the
|
|
65
|
+
* first question asked when a report looks stuck, and it has to be answerable.
|
|
66
|
+
*/
|
|
67
|
+
recordIgnoredEvent(input: {
|
|
68
|
+
eventId: string;
|
|
69
|
+
type: string;
|
|
70
|
+
caseId?: string;
|
|
71
|
+
}): Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
export declare function createInboundService<TTx>(input: {
|
|
74
|
+
transaction: ModerationTransactionRunner<TTx>;
|
|
75
|
+
events: ModerationEventStore<TTx>;
|
|
76
|
+
outbox: OutboxService<TTx>;
|
|
77
|
+
}): InboundService;
|
|
78
|
+
//# sourceMappingURL=inbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../src/outbox/inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAE1F;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAC3C,MAAM,EAAE,oBAAoB,CAAC,GAAG,CAAC,GAChC,mBAAmB,CAerB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,kBAAkB,CAAC,KAAK,EAAE;QACxB,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,KAAK,EAAE;IAC/C,WAAW,EAAE,2BAA2B,CAAC,GAAG,CAAC,CAAC;IAC9C,MAAM,EAAE,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;CAC5B,GAAG,cAAc,CAwCjB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProcessedEventStore = createProcessedEventStore;
|
|
4
|
+
exports.createInboundService = createInboundService;
|
|
5
|
+
const service_js_1 = require("./outbox/service.js");
|
|
6
|
+
const retention_js_1 = require("./retention.js");
|
|
7
|
+
/**
|
|
8
|
+
* What happens between "a signed decision arrived" and "2xx".
|
|
9
|
+
*
|
|
10
|
+
* The receiver's contract is to answer quickly and queue the processing. So
|
|
11
|
+
* exactly two writes happen here, in ONE transaction — the event's audit row is
|
|
12
|
+
* completed and a durable `decision.apply` event is created — and the dispatcher
|
|
13
|
+
* does the rest.
|
|
14
|
+
*
|
|
15
|
+
* The transaction is what makes the dedupe safe. The middleware has already
|
|
16
|
+
* claimed the event id by inserting the row (see {@link createProcessedEventStore});
|
|
17
|
+
* if completing that row and queueing the work were two operations, a crash
|
|
18
|
+
* between them would leave an event that is permanently deduplicated with no
|
|
19
|
+
* work queued — a decision silently lost, with a row that says it arrived.
|
|
20
|
+
* Committing both together means the only two possible outcomes are "recorded
|
|
21
|
+
* and queued" or "neither", and "neither" releases the claim and gets
|
|
22
|
+
* redelivered.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The webhook dedupe store, backed by the moderation event log.
|
|
26
|
+
*
|
|
27
|
+
* `@crowdsource.you/core/express` defaults to an in-process store and says exactly
|
|
28
|
+
* when that is not enough: two instances behind a load balancer each keep their
|
|
29
|
+
* own, so a redelivery landing on the other instance is not deduplicated. Every
|
|
30
|
+
* Oxy backend runs several tasks behind one load balancer, so this is that case.
|
|
31
|
+
*
|
|
32
|
+
* The claim/release contract is the SDK's, and it is the right one. A row
|
|
33
|
+
* inserted BEFORE the handler runs means a concurrent redelivery cannot also run
|
|
34
|
+
* it; deleting that row when the handler THROWS means the sender's retry
|
|
35
|
+
* schedule can still deliver the event later. Recording the id only after
|
|
36
|
+
* success would let two copies run at once; recording it before and never
|
|
37
|
+
* releasing would make a transient failure permanent and lose a decision
|
|
38
|
+
* silently.
|
|
39
|
+
*
|
|
40
|
+
* The retention window is computed HERE rather than in a store, so both backends
|
|
41
|
+
* keep an audit row for the same length of time.
|
|
42
|
+
*/
|
|
43
|
+
function createProcessedEventStore(events) {
|
|
44
|
+
return {
|
|
45
|
+
async claim(eventId) {
|
|
46
|
+
const now = new Date();
|
|
47
|
+
return await events.claim({
|
|
48
|
+
eventId,
|
|
49
|
+
receivedAt: now,
|
|
50
|
+
expiresAt: new Date(now.getTime() + retention_js_1.MODERATION_EVENT_RETENTION_SECONDS * 1000),
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
async release(eventId) {
|
|
54
|
+
await events.release(eventId);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function createInboundService(input) {
|
|
59
|
+
return {
|
|
60
|
+
async recordDecisionEvent(event) {
|
|
61
|
+
await input.transaction.run(async (tx) => {
|
|
62
|
+
const now = new Date();
|
|
63
|
+
await input.events.markQueued({
|
|
64
|
+
eventId: event.eventId,
|
|
65
|
+
type: event.type,
|
|
66
|
+
caseId: event.caseId,
|
|
67
|
+
payload: { caseId: event.caseId, decision: event.decision },
|
|
68
|
+
now,
|
|
69
|
+
}, tx);
|
|
70
|
+
await input.outbox.enqueue({
|
|
71
|
+
eventId: (0, service_js_1.decisionApplyEventId)(event.eventId),
|
|
72
|
+
kind: 'decision.apply',
|
|
73
|
+
payload: {
|
|
74
|
+
eventId: event.eventId,
|
|
75
|
+
caseId: event.caseId,
|
|
76
|
+
decision: event.decision,
|
|
77
|
+
},
|
|
78
|
+
}, tx);
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
async recordIgnoredEvent(event) {
|
|
82
|
+
await input.events.markIgnored({
|
|
83
|
+
eventId: event.eventId,
|
|
84
|
+
type: event.type,
|
|
85
|
+
...(event.caseId === undefined ? {} : { caseId: event.caseId }),
|
|
86
|
+
now: new Date(),
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=inbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.js","sourceRoot":"","sources":["../../src/outbox/inbound.ts"],"names":[],"mappings":";;AA0CA,8DAiBC;AAoCD,oDA4CC;AA1ID,oDAA+E;AAC/E,iDAAoE;AAGpE;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAgB,yBAAyB,CACvC,MAAiC;IAEjC,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,OAAe;YACzB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,OAAO,MAAM,MAAM,CAAC,KAAK,CAAC;gBACxB,OAAO;gBACP,UAAU,EAAE,GAAG;gBACf,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,iDAAkC,GAAG,IAAK,CAAC;aAChF,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,OAAO,CAAC,OAAe;YAC3B,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC;KACF,CAAC;AACJ,CAAC;AAoCD,SAAgB,oBAAoB,CAAM,KAIzC;IACC,OAAO;QACL,KAAK,CAAC,mBAAmB,CAAC,KAAK;YAC7B,MAAM,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;gBACvC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,CAC3B;oBACE,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,MAAM,EAAE,KAAK,CAAC,MAAM;oBACpB,OAAO,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;oBAC3D,GAAG;iBACJ,EACD,EAAE,CACH,CAAC;gBAEF,MAAM,KAAK,CAAC,MAAM,CAAC,OAAO,CACxB;oBACE,OAAO,EAAE,IAAA,iCAAoB,EAAC,KAAK,CAAC,OAAO,CAAC;oBAC5C,IAAI,EAAE,gBAAgB;oBACtB,OAAO,EAAE;wBACP,OAAO,EAAE,KAAK,CAAC,OAAO;wBACtB,MAAM,EAAE,KAAK,CAAC,MAAM;wBACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;qBACzB;iBACF,EACD,EAAE,CACH,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,kBAAkB,CAAC,KAAK;YAC5B,MAAM,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC;gBAC7B,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC/D,GAAG,EAAE,IAAI,IAAI,EAAE;aAChB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@crowdsource.you/core/outbox` — the application half of a CrowdSource integration.
|
|
3
|
+
*
|
|
4
|
+
* An application that adopts CrowdSource has to solve the same six problems
|
|
5
|
+
* every other application does: store a report and its promise of delivery
|
|
6
|
+
* atomically, deliver it with retries and a dead-letter path, receive signed
|
|
7
|
+
* decisions without a body parser destroying the signature, deduplicate
|
|
8
|
+
* redeliveries across several tasks, apply a decision without a stale revision
|
|
9
|
+
* overwriting a fresh one, and carry out consequences exactly once and
|
|
10
|
+
* reversibly. None of that has anything to do with what the application's
|
|
11
|
+
* objects are.
|
|
12
|
+
*
|
|
13
|
+
* So all of it is here, and an application supplies four things: its subjects,
|
|
14
|
+
* its category mapping, its enforcement tables, and a STORE built by the
|
|
15
|
+
* PostgreSQL store factory.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { createModerationIntegration } from '@crowdsource.you/core/outbox';
|
|
19
|
+
* import { postgresModerationStore } from '@crowdsource.you/core/outbox/postgres';
|
|
20
|
+
*
|
|
21
|
+
* const moderation = createModerationIntegration({
|
|
22
|
+
* store,
|
|
23
|
+
* crowdSource: { enabled: true, serviceKey, webhookSecret, enforcementMode: 'observe' },
|
|
24
|
+
* subjects: [listingSubjectProvider(), reviewSubjectProvider()],
|
|
25
|
+
* taxonomy: { version: '2026.07', allegationsFor },
|
|
26
|
+
* enforcement: commerceEnforcement,
|
|
27
|
+
* logger,
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Indexes before the first write: the unique ones ARE the exactly-once
|
|
31
|
+
* // mechanism, and an index that does not exist yet refuses nothing.
|
|
32
|
+
* await store.ensureSchema();
|
|
33
|
+
*
|
|
34
|
+
* // BEFORE express.json() — the signature covers the bytes that arrived.
|
|
35
|
+
* app.use('/webhooks', moderation.webhookRouter());
|
|
36
|
+
* app.use(express.json());
|
|
37
|
+
*
|
|
38
|
+
* moderation.dispatcher.start();
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Two invariants are ENFORCED here rather than documented, because both fail
|
|
42
|
+
* silently and neither shows up in a test that only asserts the happy path:
|
|
43
|
+
*
|
|
44
|
+
* 1. Nothing can be enqueued that is not already recorded in the outbox, in the
|
|
45
|
+
* same transaction. {@link ModerationOutboxTransactionError} is thrown by the
|
|
46
|
+
* only writer of that collection when the transaction it was handed is not
|
|
47
|
+
* open.
|
|
48
|
+
* 2. The webhook receiver reads raw bytes. Mounted after a JSON parser it
|
|
49
|
+
* refuses rather than verifying a signature over a re-serialisation.
|
|
50
|
+
*
|
|
51
|
+
* Types come from `@crowdsource.you/contracts` and `@crowdsource.you/core`. This
|
|
52
|
+
* package re-exports none of them: a `Decision` or a `TaxonomyCode` has exactly
|
|
53
|
+
* one definition.
|
|
54
|
+
*/
|
|
55
|
+
export { createModerationIntegration } from './integration.js';
|
|
56
|
+
export type { ModerationIntegration } from './integration.js';
|
|
57
|
+
/**
|
|
58
|
+
* The retention windows are storage-independent policy, so they stay here while
|
|
59
|
+
* PostgreSQL-specific tables and stores live behind the `/postgres` subpath.
|
|
60
|
+
*/
|
|
61
|
+
export { MODERATION_EVENT_RETENTION_SECONDS, MODERATION_OUTBOX_RETENTION_SECONDS, } from './retention.js';
|
|
62
|
+
export { ModerationOutboxTransactionError, decisionApplyEventId, isRetryableDeliveryError, reportSubmitEventId, } from './outbox/service.js';
|
|
63
|
+
export type { ModerationOutboxFailure, ModerationOutboxHandler, OutboxDrain, OutboxService, } from './outbox/service.js';
|
|
64
|
+
export type { ModerationEnforcementInsert, ModerationEnforcementKey, ModerationEnforcementStore, ModerationEventStore, ModerationOutboxStore, ModerationReportDecisionUpdate, ModerationReportInsert, ModerationReportRef, ModerationReportStore, ModerationStore, ModerationTransactionRunner, } from './store/types.js';
|
|
65
|
+
export { ModerationOutboxDispatcher } from './outbox/dispatcher.js';
|
|
66
|
+
export { ModerationReconciliationJob } from './reconciliation.js';
|
|
67
|
+
export type { ReconcileModerationReports } from './reconciliation.js';
|
|
68
|
+
export { DuplicateReportError } from './intake.js';
|
|
69
|
+
export { CrowdSourceUnavailableError, ModerationDeliveryRejectedError, } from './delivery.js';
|
|
70
|
+
export { ModerationDecisionDeferredError, ModerationDecisionRejectedError, } from './decision.js';
|
|
71
|
+
export { ModerationSubjectUnsupportedError, ModerationTaxonomyError, createSubjectRegistry, snapshotHash, } from './evidence.js';
|
|
72
|
+
export type { ModerationReportInput, SubjectRegistry } from './evidence.js';
|
|
73
|
+
export { ModerationRestoreDirectionError, assertRestoreDirection, planEnforcement, primaryAction, } from './enforcement/planner.js';
|
|
74
|
+
export type { EnforcementExecutor } from './enforcement/executor.js';
|
|
75
|
+
export { localStatusForDecision } from './reportStatus.js';
|
|
76
|
+
export { createProcessedEventStore } from './inbound.js';
|
|
77
|
+
export type { InboundService, RecordDecisionEventInput } from './inbound.js';
|
|
78
|
+
export type { CreateReportInput, CreateReportResult, CrowdSourceConnectionConfig, EnforcementEffect, EnforcementOutcome, EnforcementPreviousState, EnforcementSubject, ModerationContextResource, ModerationDispatchResult, ModerationEnforcementConfig, ModerationEnforcementMode, ModerationIntegrationConfig, ModerationLocalStatus, ModerationLogger, ModerationMetrics, ModerationOutboxEvent, ModerationOutboxKind, ModerationOutboxPayload, ModerationOutboxStatus, ModerationReconciliationResult, ModerationReportFields, ModerationResource, ModerationSubjectProvider, ModerationSubjectSnapshot, ModerationTaxonomy, PlannedEnforcementAction, ReportDecisionExtraFields, } from './types.js';
|
|
79
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/outbox/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAC/D,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE9D;;;GAGG;AACH,OAAO,EACL,kCAAkC,EAClC,mCAAmC,GACpC,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,gCAAgC,EAChC,oBAAoB,EACpB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,uBAAuB,EACvB,uBAAuB,EACvB,WAAW,EACX,aAAa,GACd,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,2BAA2B,EAC3B,wBAAwB,EACxB,0BAA0B,EAC1B,oBAAoB,EACpB,qBAAqB,EACrB,8BAA8B,EAC9B,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AACpE,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,YAAY,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EACL,2BAA2B,EAC3B,+BAA+B,GAChC,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,+BAA+B,EAC/B,+BAA+B,GAChC,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,iCAAiC,EACjC,uBAAuB,EACvB,qBAAqB,EACrB,YAAY,GACb,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAE5E,OAAO,EACL,+BAA+B,EAC/B,sBAAsB,EACtB,eAAe,EACf,aAAa,GACd,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AACzD,YAAY,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAE7E,YAAY,EACV,iBAAiB,EACjB,kBAAkB,EAClB,2BAA2B,EAC3B,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACxB,2BAA2B,EAC3B,yBAAyB,EACzB,2BAA2B,EAC3B,qBAAqB,EACrB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,8BAA8B,EAC9B,sBAAsB,EACtB,kBAAkB,EAClB,yBAAyB,EACzB,yBAAyB,EACzB,kBAAkB,EAClB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `@crowdsource.you/core/outbox` — the application half of a CrowdSource integration.
|
|
4
|
+
*
|
|
5
|
+
* An application that adopts CrowdSource has to solve the same six problems
|
|
6
|
+
* every other application does: store a report and its promise of delivery
|
|
7
|
+
* atomically, deliver it with retries and a dead-letter path, receive signed
|
|
8
|
+
* decisions without a body parser destroying the signature, deduplicate
|
|
9
|
+
* redeliveries across several tasks, apply a decision without a stale revision
|
|
10
|
+
* overwriting a fresh one, and carry out consequences exactly once and
|
|
11
|
+
* reversibly. None of that has anything to do with what the application's
|
|
12
|
+
* objects are.
|
|
13
|
+
*
|
|
14
|
+
* So all of it is here, and an application supplies four things: its subjects,
|
|
15
|
+
* its category mapping, its enforcement tables, and a STORE built by the
|
|
16
|
+
* PostgreSQL store factory.
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { createModerationIntegration } from '@crowdsource.you/core/outbox';
|
|
20
|
+
* import { postgresModerationStore } from '@crowdsource.you/core/outbox/postgres';
|
|
21
|
+
*
|
|
22
|
+
* const moderation = createModerationIntegration({
|
|
23
|
+
* store,
|
|
24
|
+
* crowdSource: { enabled: true, serviceKey, webhookSecret, enforcementMode: 'observe' },
|
|
25
|
+
* subjects: [listingSubjectProvider(), reviewSubjectProvider()],
|
|
26
|
+
* taxonomy: { version: '2026.07', allegationsFor },
|
|
27
|
+
* enforcement: commerceEnforcement,
|
|
28
|
+
* logger,
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Indexes before the first write: the unique ones ARE the exactly-once
|
|
32
|
+
* // mechanism, and an index that does not exist yet refuses nothing.
|
|
33
|
+
* await store.ensureSchema();
|
|
34
|
+
*
|
|
35
|
+
* // BEFORE express.json() — the signature covers the bytes that arrived.
|
|
36
|
+
* app.use('/webhooks', moderation.webhookRouter());
|
|
37
|
+
* app.use(express.json());
|
|
38
|
+
*
|
|
39
|
+
* moderation.dispatcher.start();
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* Two invariants are ENFORCED here rather than documented, because both fail
|
|
43
|
+
* silently and neither shows up in a test that only asserts the happy path:
|
|
44
|
+
*
|
|
45
|
+
* 1. Nothing can be enqueued that is not already recorded in the outbox, in the
|
|
46
|
+
* same transaction. {@link ModerationOutboxTransactionError} is thrown by the
|
|
47
|
+
* only writer of that collection when the transaction it was handed is not
|
|
48
|
+
* open.
|
|
49
|
+
* 2. The webhook receiver reads raw bytes. Mounted after a JSON parser it
|
|
50
|
+
* refuses rather than verifying a signature over a re-serialisation.
|
|
51
|
+
*
|
|
52
|
+
* Types come from `@crowdsource.you/contracts` and `@crowdsource.you/core`. This
|
|
53
|
+
* package re-exports none of them: a `Decision` or a `TaxonomyCode` has exactly
|
|
54
|
+
* one definition.
|
|
55
|
+
*/
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
exports.createProcessedEventStore = exports.localStatusForDecision = exports.primaryAction = exports.planEnforcement = exports.assertRestoreDirection = exports.ModerationRestoreDirectionError = exports.snapshotHash = exports.createSubjectRegistry = exports.ModerationTaxonomyError = exports.ModerationSubjectUnsupportedError = exports.ModerationDecisionRejectedError = exports.ModerationDecisionDeferredError = exports.ModerationDeliveryRejectedError = exports.CrowdSourceUnavailableError = exports.DuplicateReportError = exports.ModerationReconciliationJob = exports.ModerationOutboxDispatcher = exports.reportSubmitEventId = exports.isRetryableDeliveryError = exports.decisionApplyEventId = exports.ModerationOutboxTransactionError = exports.MODERATION_OUTBOX_RETENTION_SECONDS = exports.MODERATION_EVENT_RETENTION_SECONDS = exports.createModerationIntegration = void 0;
|
|
58
|
+
var integration_js_1 = require("./integration.js");
|
|
59
|
+
Object.defineProperty(exports, "createModerationIntegration", { enumerable: true, get: function () { return integration_js_1.createModerationIntegration; } });
|
|
60
|
+
/**
|
|
61
|
+
* The retention windows are storage-independent policy, so they stay here while
|
|
62
|
+
* PostgreSQL-specific tables and stores live behind the `/postgres` subpath.
|
|
63
|
+
*/
|
|
64
|
+
var retention_js_1 = require("./retention.js");
|
|
65
|
+
Object.defineProperty(exports, "MODERATION_EVENT_RETENTION_SECONDS", { enumerable: true, get: function () { return retention_js_1.MODERATION_EVENT_RETENTION_SECONDS; } });
|
|
66
|
+
Object.defineProperty(exports, "MODERATION_OUTBOX_RETENTION_SECONDS", { enumerable: true, get: function () { return retention_js_1.MODERATION_OUTBOX_RETENTION_SECONDS; } });
|
|
67
|
+
var service_js_1 = require("./outbox/service.js");
|
|
68
|
+
Object.defineProperty(exports, "ModerationOutboxTransactionError", { enumerable: true, get: function () { return service_js_1.ModerationOutboxTransactionError; } });
|
|
69
|
+
Object.defineProperty(exports, "decisionApplyEventId", { enumerable: true, get: function () { return service_js_1.decisionApplyEventId; } });
|
|
70
|
+
Object.defineProperty(exports, "isRetryableDeliveryError", { enumerable: true, get: function () { return service_js_1.isRetryableDeliveryError; } });
|
|
71
|
+
Object.defineProperty(exports, "reportSubmitEventId", { enumerable: true, get: function () { return service_js_1.reportSubmitEventId; } });
|
|
72
|
+
var dispatcher_js_1 = require("./outbox/dispatcher.js");
|
|
73
|
+
Object.defineProperty(exports, "ModerationOutboxDispatcher", { enumerable: true, get: function () { return dispatcher_js_1.ModerationOutboxDispatcher; } });
|
|
74
|
+
var reconciliation_js_1 = require("./reconciliation.js");
|
|
75
|
+
Object.defineProperty(exports, "ModerationReconciliationJob", { enumerable: true, get: function () { return reconciliation_js_1.ModerationReconciliationJob; } });
|
|
76
|
+
var intake_js_1 = require("./intake.js");
|
|
77
|
+
Object.defineProperty(exports, "DuplicateReportError", { enumerable: true, get: function () { return intake_js_1.DuplicateReportError; } });
|
|
78
|
+
var delivery_js_1 = require("./delivery.js");
|
|
79
|
+
Object.defineProperty(exports, "CrowdSourceUnavailableError", { enumerable: true, get: function () { return delivery_js_1.CrowdSourceUnavailableError; } });
|
|
80
|
+
Object.defineProperty(exports, "ModerationDeliveryRejectedError", { enumerable: true, get: function () { return delivery_js_1.ModerationDeliveryRejectedError; } });
|
|
81
|
+
var decision_js_1 = require("./decision.js");
|
|
82
|
+
Object.defineProperty(exports, "ModerationDecisionDeferredError", { enumerable: true, get: function () { return decision_js_1.ModerationDecisionDeferredError; } });
|
|
83
|
+
Object.defineProperty(exports, "ModerationDecisionRejectedError", { enumerable: true, get: function () { return decision_js_1.ModerationDecisionRejectedError; } });
|
|
84
|
+
var evidence_js_1 = require("./evidence.js");
|
|
85
|
+
Object.defineProperty(exports, "ModerationSubjectUnsupportedError", { enumerable: true, get: function () { return evidence_js_1.ModerationSubjectUnsupportedError; } });
|
|
86
|
+
Object.defineProperty(exports, "ModerationTaxonomyError", { enumerable: true, get: function () { return evidence_js_1.ModerationTaxonomyError; } });
|
|
87
|
+
Object.defineProperty(exports, "createSubjectRegistry", { enumerable: true, get: function () { return evidence_js_1.createSubjectRegistry; } });
|
|
88
|
+
Object.defineProperty(exports, "snapshotHash", { enumerable: true, get: function () { return evidence_js_1.snapshotHash; } });
|
|
89
|
+
var planner_js_1 = require("./enforcement/planner.js");
|
|
90
|
+
Object.defineProperty(exports, "ModerationRestoreDirectionError", { enumerable: true, get: function () { return planner_js_1.ModerationRestoreDirectionError; } });
|
|
91
|
+
Object.defineProperty(exports, "assertRestoreDirection", { enumerable: true, get: function () { return planner_js_1.assertRestoreDirection; } });
|
|
92
|
+
Object.defineProperty(exports, "planEnforcement", { enumerable: true, get: function () { return planner_js_1.planEnforcement; } });
|
|
93
|
+
Object.defineProperty(exports, "primaryAction", { enumerable: true, get: function () { return planner_js_1.primaryAction; } });
|
|
94
|
+
var reportStatus_js_1 = require("./reportStatus.js");
|
|
95
|
+
Object.defineProperty(exports, "localStatusForDecision", { enumerable: true, get: function () { return reportStatus_js_1.localStatusForDecision; } });
|
|
96
|
+
var inbound_js_1 = require("./inbound.js");
|
|
97
|
+
Object.defineProperty(exports, "createProcessedEventStore", { enumerable: true, get: function () { return inbound_js_1.createProcessedEventStore; } });
|
|
98
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/outbox/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;;;AAEH,mDAA+D;AAAtD,6HAAA,2BAA2B,OAAA;AAGpC;;;GAGG;AACH,+CAGwB;AAFtB,kIAAA,kCAAkC,OAAA;AAClC,mIAAA,mCAAmC,OAAA;AAGrC,kDAK6B;AAJ3B,8HAAA,gCAAgC,OAAA;AAChC,kHAAA,oBAAoB,OAAA;AACpB,sHAAA,wBAAwB,OAAA;AACxB,iHAAA,mBAAmB,OAAA;AAuBrB,wDAAoE;AAA3D,2HAAA,0BAA0B,OAAA;AACnC,yDAAkE;AAAzD,gIAAA,2BAA2B,OAAA;AAGpC,yCAAmD;AAA1C,iHAAA,oBAAoB,OAAA;AAE7B,6CAGuB;AAFrB,0HAAA,2BAA2B,OAAA;AAC3B,8HAAA,+BAA+B,OAAA;AAGjC,6CAGuB;AAFrB,8HAAA,+BAA+B,OAAA;AAC/B,8HAAA,+BAA+B,OAAA;AAGjC,6CAKuB;AAJrB,gIAAA,iCAAiC,OAAA;AACjC,sHAAA,uBAAuB,OAAA;AACvB,oHAAA,qBAAqB,OAAA;AACrB,2GAAA,YAAY,OAAA;AAId,uDAKkC;AAJhC,6HAAA,+BAA+B,OAAA;AAC/B,oHAAA,sBAAsB,OAAA;AACtB,6GAAA,eAAe,OAAA;AACf,2GAAA,aAAa,OAAA;AAIf,qDAA2D;AAAlD,yHAAA,sBAAsB,OAAA;AAE/B,2CAAyD;AAAhD,uHAAA,yBAAyB,OAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { SubjectRegistry } from './evidence.js';
|
|
2
|
+
import { type OutboxService } from './outbox/service.js';
|
|
3
|
+
import type { ModerationReportStore, ModerationTransactionRunner } from './store/types.js';
|
|
4
|
+
import type { CreateReportInput, CreateReportResult, ModerationReportFields } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Storing a report and, when there is somewhere to send it, the promise to
|
|
7
|
+
* deliver it — in one operation.
|
|
8
|
+
*
|
|
9
|
+
* This is the only thing in the integration that a user waits for. A 201 from an
|
|
10
|
+
* application's report route means the report row and its outbox event committed
|
|
11
|
+
* together. It does NOT mean CrowdSource accepted anything — CrowdSource may be
|
|
12
|
+
* unreachable, mid-deploy or not yet configured, and the reporter is told their
|
|
13
|
+
* report was received either way, because it was.
|
|
14
|
+
*
|
|
15
|
+
* The transaction is the whole mechanism. Two writes outside one would give two
|
|
16
|
+
* failure modes that are both silent: a report with no delivery event (the
|
|
17
|
+
* report exists, nothing will ever send it, and nobody finds out until somebody
|
|
18
|
+
* asks why a case never opened) or a delivery event with no report (a delivery
|
|
19
|
+
* worker looking up an id that was rolled back). Neither surfaces as an error at
|
|
20
|
+
* the moment it happens, which is exactly why this has to be atomic rather than
|
|
21
|
+
* carefully ordered.
|
|
22
|
+
*
|
|
23
|
+
* The one report with NO delivery event is the one whose type has no subject
|
|
24
|
+
* provider, and that is a different claim entirely: not "delivery failed" but
|
|
25
|
+
* "there was never a route out of this application for this kind of object".
|
|
26
|
+
* Those two must not be conflated, which is why they are different `localStatus`
|
|
27
|
+
* values and why the absent route is written down as a reason rather than
|
|
28
|
+
* inferred from a missing row.
|
|
29
|
+
*/
|
|
30
|
+
export declare class DuplicateReportError<TReport> extends Error {
|
|
31
|
+
readonly existing: TReport;
|
|
32
|
+
constructor(existing: TReport);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Store the report, and queue its delivery in the same transaction.
|
|
36
|
+
*
|
|
37
|
+
* Delivery is queued when — and only when — the reported type has a subject
|
|
38
|
+
* provider. A type without one is stored at `received` with the reason recorded,
|
|
39
|
+
* which is the behaviour the application had before CrowdSource existed: the
|
|
40
|
+
* report is a receipt and a local record, and nothing else ever happens to it.
|
|
41
|
+
*
|
|
42
|
+
* That branch is the reason the two writes stay in one transaction rather than
|
|
43
|
+
* being ordered carefully. The condition is read BEFORE the transaction body
|
|
44
|
+
* decides anything, so `localStatus` and the presence of an outbox row are
|
|
45
|
+
* decided together from one fact — a report can never commit as `queued` with
|
|
46
|
+
* nothing to deliver it, nor as `received` with a delivery event that will try
|
|
47
|
+
* anyway.
|
|
48
|
+
*
|
|
49
|
+
* Intake deliberately does not read whether the integration is enabled. A report
|
|
50
|
+
* taken while it is off still gets its delivery event, so turning the flag on
|
|
51
|
+
* delivers the backlog instead of stranding it — the dispatcher is what is
|
|
52
|
+
* gated, not the durable record. Nothing here is conditional on a third party's
|
|
53
|
+
* state; only on whether this application knows how to describe the object at
|
|
54
|
+
* all.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createIntake<TReport extends ModerationReportFields, TTx>(input: {
|
|
57
|
+
transaction: ModerationTransactionRunner<TTx>;
|
|
58
|
+
reports: ModerationReportStore<TReport, TTx>;
|
|
59
|
+
registry: SubjectRegistry;
|
|
60
|
+
outbox: OutboxService<TTx>;
|
|
61
|
+
}): (report: CreateReportInput) => Promise<CreateReportResult<TReport>>;
|
|
62
|
+
//# sourceMappingURL=intake.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intake.d.ts","sourceRoot":"","sources":["../../src/outbox/intake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,KAAK,EACV,qBAAqB,EACrB,2BAA2B,EAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,qBAAa,oBAAoB,CAAC,OAAO,CAAE,SAAQ,KAAK;IACtD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;gBAEf,QAAQ,EAAE,OAAO;CAK9B;AAqDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,OAAO,SAAS,sBAAsB,EAAE,GAAG,EAAE,KAAK,EAAE;IAC/E,WAAW,EAAE,2BAA2B,CAAC,GAAG,CAAC,CAAC;IAC9C,OAAO,EAAE,qBAAqB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7C,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC;CAC5B,GAAG,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAgDtE"}
|