@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,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.postgresEventStore = postgresEventStore;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
/**
|
|
6
|
+
* The inbound webhook event log, in Postgres.
|
|
7
|
+
*
|
|
8
|
+
* ## The one place this backend is structurally better rather than equivalent
|
|
9
|
+
*
|
|
10
|
+
* Everything else in this port aims for parity. This claim does not: it removes a
|
|
11
|
+
* failure mode instead of handling it, and the difference is worth being precise
|
|
12
|
+
* about, because "better" is otherwise just an adjective.
|
|
13
|
+
*
|
|
14
|
+
* **Mongo's claim throws and catches.** It inserts, and reads `code === 11000` to
|
|
15
|
+
* tell "somebody else has this event" from a real fault; everything that is not
|
|
16
|
+
* 11000 is rethrown, so a lost connection or a failover answers non-2xx and the
|
|
17
|
+
* event stays on the sender's retry schedule. That property is correct — and it
|
|
18
|
+
* lives in a PREDICATE. `catch { return false }` is one keystroke away, it
|
|
19
|
+
* type-checks, and it turns a connection failure into "already processed": the
|
|
20
|
+
* receiver answers 200 and a decision is retired that nobody ever handled. The
|
|
21
|
+
* only thing standing between those two behaviours is a conditional somebody
|
|
22
|
+
* could widen, and a test can only catch that by injecting a driver failure.
|
|
23
|
+
*
|
|
24
|
+
* **Postgres's claim does not throw at all.** `ON CONFLICT DO NOTHING` plus
|
|
25
|
+
* `RETURNING` makes a duplicate a ROW COUNT rather than an error: one row means
|
|
26
|
+
* this call took the claim, zero means somebody else holds it. So there is no
|
|
27
|
+
* catch block here — and therefore
|
|
28
|
+
*
|
|
29
|
+
* - no predicate to widen,
|
|
30
|
+
* - no code path that can convert a fault into a negative answer,
|
|
31
|
+
* - and the "rethrow everything else" guarantee holds by the ABSENCE of code
|
|
32
|
+
* rather than by the presence of correct code.
|
|
33
|
+
*
|
|
34
|
+
* That is the mechanism: the Mongo version is a property of code that exists and
|
|
35
|
+
* can be edited wrongly; this one is a property of code that does not exist. Only
|
|
36
|
+
* the second cannot be broken by a well-meaning change.
|
|
37
|
+
*
|
|
38
|
+
* The consequence for the mutation suite, stated because its absence would
|
|
39
|
+
* otherwise look like a gap: there is nothing to DELETE on the insert side, so no
|
|
40
|
+
* mutation can attack it. The one that exists attacks the READ — `rows.length === 1`
|
|
41
|
+
* collapsing to `true`, which would hand the same event to two handlers.
|
|
42
|
+
*/
|
|
43
|
+
function postgresEventStore(input) {
|
|
44
|
+
const { db } = input;
|
|
45
|
+
const events = input.tables.events;
|
|
46
|
+
return {
|
|
47
|
+
async claim({ eventId, receivedAt, expiresAt }) {
|
|
48
|
+
/**
|
|
49
|
+
* The insert IS the claim, and a lost race is zero rows rather than an
|
|
50
|
+
* exception. `created_at`/`updated_at` are left to their column defaults:
|
|
51
|
+
* unlike the outbox, nothing here needs a repeated write to be provably a
|
|
52
|
+
* no-op, so the database's clock is the simpler authority.
|
|
53
|
+
*/
|
|
54
|
+
const rows = await db
|
|
55
|
+
.insert(events)
|
|
56
|
+
.values({ id: eventId, state: 'claimed', receivedAt, expiresAt })
|
|
57
|
+
.onConflictDoNothing({ target: events.id })
|
|
58
|
+
.returning({ id: events.id });
|
|
59
|
+
return rows.length === 1;
|
|
60
|
+
},
|
|
61
|
+
/** Give the claim back so a redelivery can be processed. */
|
|
62
|
+
async release(eventId) {
|
|
63
|
+
await db.delete(events).where((0, drizzle_orm_1.eq)(events.id, eventId));
|
|
64
|
+
},
|
|
65
|
+
async markQueued({ eventId, type, caseId, payload, now }, tx) {
|
|
66
|
+
/**
|
|
67
|
+
* On the CALLER's transaction, which is the whole point: this row's
|
|
68
|
+
* completion and the outbox row that carries the work commit together, or a
|
|
69
|
+
* crash between them leaves an event permanently deduplicated with no work
|
|
70
|
+
* queued — a decision silently lost, with a row saying it arrived.
|
|
71
|
+
*/
|
|
72
|
+
await tx
|
|
73
|
+
.update(events)
|
|
74
|
+
.set({
|
|
75
|
+
type,
|
|
76
|
+
caseId,
|
|
77
|
+
payload,
|
|
78
|
+
state: 'queued',
|
|
79
|
+
queuedAt: now,
|
|
80
|
+
updatedAt: now,
|
|
81
|
+
})
|
|
82
|
+
.where((0, drizzle_orm_1.eq)(events.id, eventId));
|
|
83
|
+
},
|
|
84
|
+
async markIgnored({ eventId, type, caseId, now }) {
|
|
85
|
+
await db
|
|
86
|
+
.update(events)
|
|
87
|
+
.set({
|
|
88
|
+
type,
|
|
89
|
+
/**
|
|
90
|
+
* Absent means LEAVE IT, not write something. An event type carrying no
|
|
91
|
+
* case id is ordinary — `case.created` before a case is linked — and
|
|
92
|
+
* `caseId: String(caseId)` would store the four characters `null` or
|
|
93
|
+
* the nine characters `undefined`, which then reads as a case id
|
|
94
|
+
* everywhere downstream. Postgres keeps the column NULL.
|
|
95
|
+
*/
|
|
96
|
+
...(caseId === undefined ? {} : { caseId }),
|
|
97
|
+
state: 'ignored',
|
|
98
|
+
updatedAt: now,
|
|
99
|
+
})
|
|
100
|
+
.where((0, drizzle_orm_1.eq)(events.id, eventId));
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/events.ts"],"names":[],"mappings":";;AA2CA,gDAoEC;AA/GD,6CAAiC;AAKjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,SAAgB,kBAAkB,CAAC,KAGlC;IACC,MAAM,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC;IACrB,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;IAEnC,OAAO;QACL,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE;YAC5C;;;;;eAKG;YACH,MAAM,IAAI,GAAG,MAAM,EAAE;iBAClB,MAAM,CAAC,MAAM,CAAC;iBACd,MAAM,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;iBAChE,mBAAmB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;iBAC1C,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;YAEhC,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC;QAC3B,CAAC;QAED,4DAA4D;QAC5D,KAAK,CAAC,OAAO,CAAC,OAAO;YACnB,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAA,gBAAE,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QACxD,CAAC;QAED,KAAK,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;YAC1D;;;;;eAKG;YACH,MAAM,EAAE;iBACL,MAAM,CAAC,MAAM,CAAC;iBACd,GAAG,CAAC;gBACH,IAAI;gBACJ,MAAM;gBACN,OAAO;gBACP,KAAK,EAAE,QAAQ;gBACf,QAAQ,EAAE,GAAG;gBACb,SAAS,EAAE,GAAG;aACf,CAAC;iBACD,KAAK,CAAC,IAAA,gBAAE,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QACnC,CAAC;QAED,KAAK,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;YAC9C,MAAM,EAAE;iBACL,MAAM,CAAC,MAAM,CAAC;iBACd,GAAG,CAAC;gBACH,IAAI;gBACJ;;;;;;mBAMG;gBACH,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;gBAC3C,KAAK,EAAE,SAAS;gBAChB,SAAS,EAAE,GAAG;aACf,CAAC;iBACD,KAAK,CAAC,IAAA,gBAAE,EAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QACnC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ModerationStore } from '../../store/types.js';
|
|
2
|
+
import type { ModerationReportFields } from '../../types.js';
|
|
3
|
+
import type { ModerationReportTable } from '../reportColumns.js';
|
|
4
|
+
import type { ModerationTables } from '../tables.js';
|
|
5
|
+
import { type ModerationPgHandle } from './transaction.js';
|
|
6
|
+
/**
|
|
7
|
+
* Everything this package writes, in Postgres.
|
|
8
|
+
*
|
|
9
|
+
* One factory rather than five, because the five members must share one handle: a
|
|
10
|
+
* report and its outbox row commit in the SAME transaction, and a store assembled
|
|
11
|
+
* from two handles would type-check perfectly and quietly lose that.
|
|
12
|
+
*
|
|
13
|
+
* Unlike the Mongo factory, this one CREATES NOTHING. There is no schema to
|
|
14
|
+
* register: the adopter's own migration created the tables, generated from the
|
|
15
|
+
* definitions this package exports, in the adopter's own journal. That asymmetry
|
|
16
|
+
* is deliberate and is the reason no migrations folder ships here — two journals
|
|
17
|
+
* against one `drizzle.__drizzle_migrations` table interleave, and the loser is
|
|
18
|
+
* skipped in silence with exit 0.
|
|
19
|
+
*/
|
|
20
|
+
export declare function postgresModerationStore<TReport extends ModerationReportFields>(input: {
|
|
21
|
+
db: ModerationPgHandle;
|
|
22
|
+
/** The application's own report table, built from `moderationReportColumns`. */
|
|
23
|
+
reportTable: ModerationReportTable;
|
|
24
|
+
/** The three tables this package owns, from `moderationTables`. */
|
|
25
|
+
tables: ModerationTables;
|
|
26
|
+
}): ModerationStore<TReport, ModerationPgHandle>;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAKrD,OAAO,EAA6B,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,sBAAsB,EAAE,KAAK,EAAE;IACrF,EAAE,EAAE,kBAAkB,CAAC;IACvB,gFAAgF;IAChF,WAAW,EAAE,qBAAqB,CAAC;IACnC,mEAAmE;IACnE,MAAM,EAAE,gBAAgB,CAAC;CAC1B,GAAG,eAAe,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAuC/C"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.postgresModerationStore = postgresModerationStore;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
const enforcement_js_1 = require("./enforcement.js");
|
|
6
|
+
const events_js_1 = require("./events.js");
|
|
7
|
+
const outbox_js_1 = require("./outbox.js");
|
|
8
|
+
const reports_js_1 = require("./reports.js");
|
|
9
|
+
const transaction_js_1 = require("./transaction.js");
|
|
10
|
+
/**
|
|
11
|
+
* Everything this package writes, in Postgres.
|
|
12
|
+
*
|
|
13
|
+
* One factory rather than five, because the five members must share one handle: a
|
|
14
|
+
* report and its outbox row commit in the SAME transaction, and a store assembled
|
|
15
|
+
* from two handles would type-check perfectly and quietly lose that.
|
|
16
|
+
*
|
|
17
|
+
* Unlike the Mongo factory, this one CREATES NOTHING. There is no schema to
|
|
18
|
+
* register: the adopter's own migration created the tables, generated from the
|
|
19
|
+
* definitions this package exports, in the adopter's own journal. That asymmetry
|
|
20
|
+
* is deliberate and is the reason no migrations folder ships here — two journals
|
|
21
|
+
* against one `drizzle.__drizzle_migrations` table interleave, and the loser is
|
|
22
|
+
* skipped in silence with exit 0.
|
|
23
|
+
*/
|
|
24
|
+
function postgresModerationStore(input) {
|
|
25
|
+
const { db, reportTable, tables } = input;
|
|
26
|
+
return {
|
|
27
|
+
transaction: (0, transaction_js_1.postgresTransactionRunner)(db),
|
|
28
|
+
outbox: (0, outbox_js_1.postgresOutboxStore)({ db, tables }),
|
|
29
|
+
events: (0, events_js_1.postgresEventStore)({ db, tables }),
|
|
30
|
+
enforcement: (0, enforcement_js_1.postgresEnforcementStore)({ db, tables }),
|
|
31
|
+
reports: (0, reports_js_1.postgresReportStore)({ db, reportTable }),
|
|
32
|
+
/**
|
|
33
|
+
* ASSERTS the schema, rather than creating it.
|
|
34
|
+
*
|
|
35
|
+
* The Mongo half calls `init()` and builds indexes; there is nothing here to
|
|
36
|
+
* build, so what remains is worth doing for its own sake: confirming the four
|
|
37
|
+
* tables this package queries exist BEFORE the first report is filed rather
|
|
38
|
+
* than at the first delivery.
|
|
39
|
+
*
|
|
40
|
+
* A missing table raises `42P01 undefined_table` and names it. `limit(0)`
|
|
41
|
+
* means Postgres parses and plans each statement — which is what proves the
|
|
42
|
+
* relation resolves — without reading a row.
|
|
43
|
+
*
|
|
44
|
+
* It cannot check COLUMNS. An adopter whose migration is a version behind
|
|
45
|
+
* gets a compile error from `ModerationReportTable` for a missing column and
|
|
46
|
+
* a `42703` from the query for a stale migration; both are loud, and neither
|
|
47
|
+
* is this function's job.
|
|
48
|
+
*/
|
|
49
|
+
async ensureSchema() {
|
|
50
|
+
const present = [
|
|
51
|
+
tables.outbox,
|
|
52
|
+
tables.events,
|
|
53
|
+
tables.enforcements,
|
|
54
|
+
reportTable,
|
|
55
|
+
];
|
|
56
|
+
for (const table of present) {
|
|
57
|
+
await db.select({ resolves: (0, drizzle_orm_1.sql) `1` }).from(table).limit(0);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/index.ts"],"names":[],"mappings":";;AA0BA,0DA6CC;AAvED,6CAAkC;AAMlC,qDAA4D;AAC5D,2CAAiD;AACjD,2CAAkD;AAClD,6CAAmD;AACnD,qDAAsF;AAEtF;;;;;;;;;;;;;GAaG;AACH,SAAgB,uBAAuB,CAAyC,KAM/E;IACC,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAE1C,OAAO;QACL,WAAW,EAAE,IAAA,0CAAyB,EAAC,EAAE,CAAC;QAC1C,MAAM,EAAE,IAAA,+BAAmB,EAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;QAC3C,MAAM,EAAE,IAAA,8BAAkB,EAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;QAC1C,WAAW,EAAE,IAAA,yCAAwB,EAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;QACrD,OAAO,EAAE,IAAA,gCAAmB,EAAU,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC;QAE1D;;;;;;;;;;;;;;;;WAgBG;QACH,KAAK,CAAC,YAAY;YAChB,MAAM,OAAO,GAAuB;gBAClC,MAAM,CAAC,MAAM;gBACb,MAAM,CAAC,MAAM;gBACb,MAAM,CAAC,YAAY;gBACnB,WAAW;aACZ,CAAC;YACF,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAA,iBAAG,EAAA,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ModerationOutboxStore } from '../../store/types.js';
|
|
2
|
+
import type { ModerationTables } from '../tables.js';
|
|
3
|
+
import type { ModerationPgHandle } from './transaction.js';
|
|
4
|
+
export declare function postgresOutboxStore(input: {
|
|
5
|
+
db: ModerationPgHandle;
|
|
6
|
+
tables: ModerationTables;
|
|
7
|
+
}): ModerationOutboxStore<ModerationPgHandle>;
|
|
8
|
+
//# sourceMappingURL=outbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox.d.ts","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/outbox.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAqC3D,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,EAAE,EAAE,kBAAkB,CAAC;IACvB,MAAM,EAAE,gBAAgB,CAAC;CAC1B,GAAG,qBAAqB,CAAC,kBAAkB,CAAC,CAkO5C"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.postgresOutboxStore = postgresOutboxStore;
|
|
4
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
5
|
+
const pg_core_1 = require("drizzle-orm/pg-core");
|
|
6
|
+
const service_js_1 = require("../../outbox/service.js");
|
|
7
|
+
function postgresOutboxStore(input) {
|
|
8
|
+
const { db } = input;
|
|
9
|
+
const outbox = input.tables.outbox;
|
|
10
|
+
/**
|
|
11
|
+
* Due work, as one predicate.
|
|
12
|
+
*
|
|
13
|
+
* Either `pending` and past its `available_at`, or `processing` with an EXPIRED
|
|
14
|
+
* lease — the second arm is what makes a dead worker's event reclaimable rather
|
|
15
|
+
* than stranded. The two arms have an index each
|
|
16
|
+
* (`moderation_outbox_due_idx`, `moderation_outbox_lease_idx`).
|
|
17
|
+
*/
|
|
18
|
+
const dueOrExpired = (now) => (0, drizzle_orm_1.or)((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(outbox.status, 'pending'), (0, drizzle_orm_1.lte)(outbox.availableAt, now)), (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(outbox.status, 'processing'), (0, drizzle_orm_1.lte)(outbox.leaseUntil, now)));
|
|
19
|
+
/** The lease this caller claims to hold, still live. Shared by all three transitions. */
|
|
20
|
+
const heldLease = (eventId, leaseOwner, now) => (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(outbox.id, eventId), (0, drizzle_orm_1.eq)(outbox.status, 'processing'), (0, drizzle_orm_1.eq)(outbox.leaseOwner, leaseOwner), (0, drizzle_orm_1.gt)(outbox.leaseUntil, now));
|
|
21
|
+
const toEvent = (row) => ({
|
|
22
|
+
id: row.id,
|
|
23
|
+
kind: row.kind,
|
|
24
|
+
payload: row.payload,
|
|
25
|
+
attempts: row.attempts,
|
|
26
|
+
availableAt: row.availableAt,
|
|
27
|
+
// `null` is how Postgres stores "no lease"; the event type says absent.
|
|
28
|
+
...(row.leaseOwner === null ? {} : { leaseOwner: row.leaseOwner }),
|
|
29
|
+
...(row.leaseUntil === null ? {} : { leaseUntil: row.leaseUntil }),
|
|
30
|
+
expiresAt: row.expiresAt,
|
|
31
|
+
createdAt: row.createdAt,
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
async enqueue(event, tx) {
|
|
35
|
+
/**
|
|
36
|
+
* The guard, and it is not the same mistake Mongo's guards against.
|
|
37
|
+
*
|
|
38
|
+
* Both `db` and `tx` are a `ModerationPgHandle`, so handing this the POOL
|
|
39
|
+
* handle type-checks perfectly — and then the row commits on its own
|
|
40
|
+
* connection, independently of the domain write it was supposed to be
|
|
41
|
+
* atomic with. That is "the report was answered 201 and never delivered",
|
|
42
|
+
* reached by a different route than a session nobody opened a transaction
|
|
43
|
+
* on, with the same silence.
|
|
44
|
+
*
|
|
45
|
+
* `PgTransaction` is a real runtime class in `drizzle-orm/pg-core`, so this
|
|
46
|
+
* is a genuine check rather than a duck-typed guess.
|
|
47
|
+
*/
|
|
48
|
+
if (!(tx instanceof pg_core_1.PgTransaction)) {
|
|
49
|
+
throw new service_js_1.ModerationOutboxTransactionError(event.eventId);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `ON CONFLICT DO NOTHING`, never `DO UPDATE`.
|
|
53
|
+
*
|
|
54
|
+
* A repeated enqueue is ORDINARY — a transaction retry, two concurrent
|
|
55
|
+
* duplicate submissions, a reconciliation sweep re-deriving an event — and
|
|
56
|
+
* the dispatcher is concurrently taking, renewing and completing leases on
|
|
57
|
+
* these same rows. `DO UPDATE` would make each repeat a real write, which
|
|
58
|
+
* conflicts with a live lease update and aborts the enclosing transaction:
|
|
59
|
+
* the exact defect the Mongo store's `timestamps: false` exists to prevent,
|
|
60
|
+
* reintroduced in a dialect where nothing forces it on you.
|
|
61
|
+
*
|
|
62
|
+
* One behavioural difference from Mongo, and Postgres has the better end of
|
|
63
|
+
* it: if a CONCURRENT UNCOMMITTED transaction holds this same key, Postgres
|
|
64
|
+
* WAITS for it and then proceeds (finding the row committed, and doing
|
|
65
|
+
* nothing), where Mongo raises `WriteConflict` (code 112) and aborts the
|
|
66
|
+
* enclosing transaction. Waiting is the outcome a caller wants.
|
|
67
|
+
*
|
|
68
|
+
* `created_at` and `updated_at` are written explicitly from the caller's
|
|
69
|
+
* clock rather than left to their defaults, so both backends stamp a row
|
|
70
|
+
* from one instant — and so a test can assert that a repeat changed
|
|
71
|
+
* NOTHING, which is a stronger claim than "no duplicate row".
|
|
72
|
+
*/
|
|
73
|
+
await tx
|
|
74
|
+
.insert(outbox)
|
|
75
|
+
.values({
|
|
76
|
+
id: event.eventId,
|
|
77
|
+
kind: event.kind,
|
|
78
|
+
payload: event.payload,
|
|
79
|
+
status: 'pending',
|
|
80
|
+
attempts: 0,
|
|
81
|
+
availableAt: event.availableAt,
|
|
82
|
+
expiresAt: event.expiresAt,
|
|
83
|
+
createdAt: event.now,
|
|
84
|
+
updatedAt: event.now,
|
|
85
|
+
})
|
|
86
|
+
.onConflictDoNothing({ target: outbox.id });
|
|
87
|
+
},
|
|
88
|
+
async claim({ leaseOwner, leaseUntil, now, eventId }) {
|
|
89
|
+
/**
|
|
90
|
+
* `SKIP LOCKED` is load-bearing, not tuning.
|
|
91
|
+
*
|
|
92
|
+
* Without it, under READ COMMITTED the sub-select is evaluated once: the
|
|
93
|
+
* loser blocks on the head row and then returns ZERO rows. `claim` answers
|
|
94
|
+
* `null`, `dispatch` breaks out of its batch, and a deployment running N
|
|
95
|
+
* tasks silently drains at 1/N the rate with nothing failing anywhere.
|
|
96
|
+
*
|
|
97
|
+
* The `FOR UPDATE` lives in the SUB-SELECT because that is what locks the
|
|
98
|
+
* one row this claim intends to take, before the UPDATE touches it — the
|
|
99
|
+
* documented Postgres idiom for a work queue.
|
|
100
|
+
*/
|
|
101
|
+
const due = db
|
|
102
|
+
.select({ id: outbox.id })
|
|
103
|
+
.from(outbox)
|
|
104
|
+
.where(eventId === undefined
|
|
105
|
+
? dueOrExpired(now)
|
|
106
|
+
: (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(outbox.id, eventId), dueOrExpired(now)))
|
|
107
|
+
// Oldest first, so a backlog drains in the order it was filed rather than
|
|
108
|
+
// in whatever order the storage engine finds convenient.
|
|
109
|
+
.orderBy((0, drizzle_orm_1.asc)(outbox.createdAt))
|
|
110
|
+
.limit(1)
|
|
111
|
+
.for('update', { skipLocked: true });
|
|
112
|
+
const rows = await db
|
|
113
|
+
.update(outbox)
|
|
114
|
+
.set({
|
|
115
|
+
status: 'processing',
|
|
116
|
+
leaseOwner,
|
|
117
|
+
leaseUntil,
|
|
118
|
+
// The COLUMN is interpolated, not a value: `attempts` is incremented by
|
|
119
|
+
// the database so two claimers cannot both read 3 and both write 4.
|
|
120
|
+
attempts: (0, drizzle_orm_1.sql) `${outbox.attempts} + 1`,
|
|
121
|
+
// The previous attempt's error, cleared with the claim so a stale
|
|
122
|
+
// message can never be read as this attempt's. `null` CLEARS in
|
|
123
|
+
// drizzle; `undefined` would leave it alone.
|
|
124
|
+
lastError: null,
|
|
125
|
+
updatedAt: now,
|
|
126
|
+
})
|
|
127
|
+
.where((0, drizzle_orm_1.inArray)(outbox.id, due))
|
|
128
|
+
.returning({
|
|
129
|
+
id: outbox.id,
|
|
130
|
+
kind: outbox.kind,
|
|
131
|
+
payload: outbox.payload,
|
|
132
|
+
attempts: outbox.attempts,
|
|
133
|
+
availableAt: outbox.availableAt,
|
|
134
|
+
leaseOwner: outbox.leaseOwner,
|
|
135
|
+
leaseUntil: outbox.leaseUntil,
|
|
136
|
+
expiresAt: outbox.expiresAt,
|
|
137
|
+
createdAt: outbox.createdAt,
|
|
138
|
+
});
|
|
139
|
+
const [row] = rows;
|
|
140
|
+
return row === undefined ? null : toEvent(row);
|
|
141
|
+
},
|
|
142
|
+
/**
|
|
143
|
+
* ## Why all three transitions read `RETURNING`, and what that collapses
|
|
144
|
+
*
|
|
145
|
+
* Mongo's `complete` and `fail` answer `modifiedCount === 1` while its
|
|
146
|
+
* `renew` answers `matchedCount === 1`. `RETURNING` counts MATCHED rows, so
|
|
147
|
+
* this store answers the `matchedCount` question in all three places.
|
|
148
|
+
*
|
|
149
|
+
* That is equivalent HERE, and the argument is worth writing down because it
|
|
150
|
+
* is an argument rather than a test: the WHERE clause requires
|
|
151
|
+
* `status = 'processing'`, and `complete` and `fail` both write a different
|
|
152
|
+
* status, so a matched row is always a modified row. `renew` writes only
|
|
153
|
+
* `lease_until`/`updated_at` and Mongo already used `matchedCount` for it —
|
|
154
|
+
* a renewal that lands on the values already stored is still a lease this
|
|
155
|
+
* caller holds, and reporting it as lost would make a dispatcher abandon an
|
|
156
|
+
* event it still owns.
|
|
157
|
+
*
|
|
158
|
+
* If a later transition ever stops changing `status`, this equivalence stops
|
|
159
|
+
* holding and the difference becomes silent.
|
|
160
|
+
*/
|
|
161
|
+
async complete({ eventId, leaseOwner, now }) {
|
|
162
|
+
const rows = await db
|
|
163
|
+
.update(outbox)
|
|
164
|
+
.set({
|
|
165
|
+
status: 'processed',
|
|
166
|
+
processedAt: now,
|
|
167
|
+
updatedAt: now,
|
|
168
|
+
leaseOwner: null,
|
|
169
|
+
leaseUntil: null,
|
|
170
|
+
lastError: null,
|
|
171
|
+
})
|
|
172
|
+
.where(heldLease(eventId, leaseOwner, now))
|
|
173
|
+
.returning({ id: outbox.id });
|
|
174
|
+
return rows.length === 1;
|
|
175
|
+
},
|
|
176
|
+
async renew({ eventId, leaseOwner, leaseUntil, now }) {
|
|
177
|
+
const rows = await db
|
|
178
|
+
.update(outbox)
|
|
179
|
+
.set({ leaseUntil, updatedAt: now })
|
|
180
|
+
.where(heldLease(eventId, leaseOwner, now))
|
|
181
|
+
.returning({ id: outbox.id });
|
|
182
|
+
return rows.length === 1;
|
|
183
|
+
},
|
|
184
|
+
async fail({ eventId, leaseOwner, status, availableAt, lastError, now }) {
|
|
185
|
+
const rows = await db
|
|
186
|
+
.update(outbox)
|
|
187
|
+
.set({
|
|
188
|
+
status,
|
|
189
|
+
availableAt,
|
|
190
|
+
lastError,
|
|
191
|
+
updatedAt: now,
|
|
192
|
+
leaseOwner: null,
|
|
193
|
+
leaseUntil: null,
|
|
194
|
+
})
|
|
195
|
+
.where(heldLease(eventId, leaseOwner, now))
|
|
196
|
+
.returning({ id: outbox.id });
|
|
197
|
+
return rows.length === 1;
|
|
198
|
+
},
|
|
199
|
+
async statusOf(eventId) {
|
|
200
|
+
const rows = await db
|
|
201
|
+
.select({ status: outbox.status })
|
|
202
|
+
.from(outbox)
|
|
203
|
+
.where((0, drizzle_orm_1.eq)(outbox.id, eventId))
|
|
204
|
+
.limit(1);
|
|
205
|
+
const [row] = rows;
|
|
206
|
+
return row === undefined ? null : row.status;
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=outbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/outbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ModerationReportStore } from '../../store/types.js';
|
|
2
|
+
import type { ModerationReportFields } from '../../types.js';
|
|
3
|
+
import type { ModerationReportTable } from '../reportColumns.js';
|
|
4
|
+
import type { ModerationPgHandle } from './transaction.js';
|
|
5
|
+
/**
|
|
6
|
+
* The application's own report table, in Postgres.
|
|
7
|
+
*
|
|
8
|
+
* The only store that takes a table it did not define, which is what makes the
|
|
9
|
+
* structural type worth having: an adopter who forgets a column this package
|
|
10
|
+
* queries gets a COMPILE ERROR at the `postgresModerationStore(...)` call rather
|
|
11
|
+
* than a runtime failure on the first delivery.
|
|
12
|
+
*
|
|
13
|
+
* `scripts/test-report-table-type.mjs` proves that gate in both directions and
|
|
14
|
+
* records exactly what it does not catch — a column present under the right name
|
|
15
|
+
* but the wrong TYPE passes, because every member of `ModerationReportTable` is a
|
|
16
|
+
* bare `PgColumn`. The DDL and the schema tests are what cover that half.
|
|
17
|
+
*
|
|
18
|
+
* ## Two Mongo hazards that do not exist here
|
|
19
|
+
*
|
|
20
|
+
* **A malformed id is not an error.** `id` is `text`, so an id nothing could have
|
|
21
|
+
* generated simply matches no rows and `findById` answers `null` — which is
|
|
22
|
+
* exactly what the delivery worker already does with "the report is gone".
|
|
23
|
+
* Mongoose raises a `CastError` for the same input and its store has to catch it.
|
|
24
|
+
* There is nothing to catch here, and adding a branch for `22P02` would be
|
|
25
|
+
* writing a handler for an error this column cannot raise.
|
|
26
|
+
*
|
|
27
|
+
* **A bound parameter cannot become a query operator.** `requireIdentifier` in
|
|
28
|
+
* `intake.ts` still runs, and still should — a non-string corrupts data on any
|
|
29
|
+
* backend, and the function is exported for callers with no route validation. But
|
|
30
|
+
* the specific failure it was written for, `{ $ne: null }` arriving as a value and
|
|
31
|
+
* matching an unrelated report, is a Mongo shape: here a parameter is a parameter.
|
|
32
|
+
*
|
|
33
|
+
* ## One thing that is worse, stated plainly
|
|
34
|
+
*
|
|
35
|
+
* Every read hands the caller `TReport`, and no type can prove that: the adopter
|
|
36
|
+
* owns the type AND the table, and the two are only connected by their names. See
|
|
37
|
+
* {@link asReport}.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* The fields `ModerationReportFields` declares OPTIONAL, computed from the type.
|
|
41
|
+
*
|
|
42
|
+
* Postgres stores an absent value as NULL; Mongo omits the field. Both are the
|
|
43
|
+
* same claim — "this never happened" — and one suite has to be able to assert it
|
|
44
|
+
* once, so the package's own optional fields come back ABSENT from either
|
|
45
|
+
* backend. Two `describe.each` pairs failed on exactly this before it was
|
|
46
|
+
* settled, both reading `expected null to be undefined`, and neither was about
|
|
47
|
+
* behaviour.
|
|
48
|
+
*
|
|
49
|
+
* Only the fields the PORT owns are normalised. An adopter's own nullable column
|
|
50
|
+
* is theirs: `extra` goes in untouched and comes back untouched.
|
|
51
|
+
*/
|
|
52
|
+
type OptionalReportField = {
|
|
53
|
+
[K in keyof ModerationReportFields]-?: undefined extends ModerationReportFields[K] ? K : never;
|
|
54
|
+
}[keyof ModerationReportFields];
|
|
55
|
+
declare const OPTIONAL_REPORT_FIELDS: readonly ["details", "localStatusReason", "crowdSourceReportId", "crowdSourceCaseId", "crowdSourceMerged", "contentSnapshotHash", "submittedAt", "lastDeliveryError", "decisionId", "decisionRevision", "decisionOutcome", "decisionStatus", "decidedAt", "enforcedAction", "enforcedAt"];
|
|
56
|
+
/** `T` when it is `never`, and a compile error naming the field when it is not. */
|
|
57
|
+
type AssertNever<T extends never> = T;
|
|
58
|
+
/**
|
|
59
|
+
* The exhaustiveness gate. `satisfies` above refuses a field that is not optional;
|
|
60
|
+
* THIS refuses an optional field that is missing from the list — the direction
|
|
61
|
+
* that would otherwise ship as a backend difference nobody looked for.
|
|
62
|
+
*/
|
|
63
|
+
export type UncoveredOptionalReportField = AssertNever<Exclude<OptionalReportField, (typeof OPTIONAL_REPORT_FIELDS)[number]>>;
|
|
64
|
+
export declare function postgresReportStore<TReport extends ModerationReportFields>(input: {
|
|
65
|
+
db: ModerationPgHandle;
|
|
66
|
+
reportTable: ModerationReportTable;
|
|
67
|
+
}): ModerationReportStore<TReport, ModerationPgHandle>;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=reports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reports.d.ts","sourceRoot":"","sources":["../../../../src/outbox/postgres/store/reports.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH;;;;;;;;;;;;GAYG;AACH,KAAK,mBAAmB,GAAG;KACxB,CAAC,IAAI,MAAM,sBAAsB,CAAC,CAAC,GAAG,SAAS,SAAS,sBAAsB,CAAC,CAAC,CAAC,GAC9E,CAAC,GACD,KAAK;CACV,CAAC,MAAM,sBAAsB,CAAC,CAAC;AAEhC,QAAA,MAAM,sBAAsB,2RAgBuB,CAAC;AAEpD,mFAAmF;AACnF,KAAK,WAAW,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC;AAEtC;;;;GAIG;AACH,MAAM,MAAM,4BAA4B,GAAG,WAAW,CACpD,OAAO,CAAC,mBAAmB,EAAE,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC,CACtE,CAAC;AA+BF,wBAAgB,mBAAmB,CAAC,OAAO,SAAS,sBAAsB,EAAE,KAAK,EAAE;IACjF,EAAE,EAAE,kBAAkB,CAAC;IACvB,WAAW,EAAE,qBAAqB,CAAC;CACpC,GAAG,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAwMrD"}
|