@heavybit/shared-lib 2.1.0 → 2.7.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 +163 -2
- package/dist/cjs/access/index.js +9 -1
- package/dist/cjs/access/index.js.map +1 -1
- package/dist/cjs/approvals/index.js +137 -0
- package/dist/cjs/approvals/index.js.map +1 -0
- package/dist/cjs/audit/index.js +74 -0
- package/dist/cjs/audit/index.js.map +1 -0
- package/dist/cjs/cache/index.js +90 -16
- package/dist/cjs/cache/index.js.map +1 -1
- package/dist/cjs/cache/keyspace.js +101 -0
- package/dist/cjs/cache/keyspace.js.map +1 -0
- package/dist/cjs/common/circuit-breaker.js +74 -0
- package/dist/cjs/common/circuit-breaker.js.map +1 -0
- package/dist/cjs/common/index.js +4 -0
- package/dist/cjs/common/index.js.map +1 -1
- package/dist/cjs/common/internal-client.js +177 -0
- package/dist/cjs/common/internal-client.js.map +1 -0
- package/dist/cjs/common/internal-constants.js +12 -0
- package/dist/cjs/common/internal-constants.js.map +1 -0
- package/dist/cjs/common/internal-service-auth.js +160 -0
- package/dist/cjs/common/internal-service-auth.js.map +1 -0
- package/dist/cjs/common/money.js +215 -0
- package/dist/cjs/common/money.js.map +1 -0
- package/dist/cjs/common/phone.js +41 -22
- package/dist/cjs/common/phone.js.map +1 -1
- package/dist/cjs/common/time.js +28 -2
- package/dist/cjs/common/time.js.map +1 -1
- package/dist/cjs/config/index.js +139 -19
- package/dist/cjs/config/index.js.map +1 -1
- package/dist/cjs/contracts/notification-dispatch.js +1 -1
- package/dist/cjs/database/db.js +37 -0
- package/dist/cjs/database/db.js.map +1 -0
- package/dist/cjs/database/index.js +22 -51
- package/dist/cjs/database/index.js.map +1 -1
- package/dist/cjs/database/pagination.js +88 -0
- package/dist/cjs/database/pagination.js.map +1 -0
- package/dist/cjs/database/pool.js +60 -0
- package/dist/cjs/database/pool.js.map +1 -0
- package/dist/cjs/database/scoped-repository.js +132 -0
- package/dist/cjs/database/scoped-repository.js.map +1 -0
- package/dist/cjs/database/scopes.js +166 -0
- package/dist/cjs/database/scopes.js.map +1 -0
- package/dist/cjs/database/table-names.js +14 -0
- package/dist/cjs/database/table-names.js.map +1 -0
- package/dist/cjs/database/tables.js +71 -0
- package/dist/cjs/database/tables.js.map +1 -0
- package/dist/cjs/express/auth.js +64 -50
- package/dist/cjs/express/auth.js.map +1 -1
- package/dist/cjs/express/index.js +2 -0
- package/dist/cjs/express/index.js.map +1 -1
- package/dist/cjs/express/internal-caller.js +52 -0
- package/dist/cjs/express/internal-caller.js.map +1 -0
- package/dist/cjs/express/step-up.js +41 -0
- package/dist/cjs/express/step-up.js.map +1 -0
- package/dist/cjs/http/index.js +3 -71
- package/dist/cjs/http/index.js.map +1 -1
- package/dist/cjs/identity/index.js +269 -67
- package/dist/cjs/identity/index.js.map +1 -1
- package/dist/cjs/identity/step-up.js +52 -0
- package/dist/cjs/identity/step-up.js.map +1 -0
- package/dist/cjs/index.js +6 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/knex/columns.js +36 -0
- package/dist/cjs/knex/columns.js.map +1 -0
- package/dist/cjs/knex/index.js +26 -43
- package/dist/cjs/knex/index.js.map +1 -1
- package/dist/cjs/knex/migration-helpers.js +57 -0
- package/dist/cjs/knex/migration-helpers.js.map +1 -0
- package/dist/cjs/logging/redact.js +6 -11
- package/dist/cjs/logging/redact.js.map +1 -1
- package/dist/cjs/messaging/base-consumer.js +36 -0
- package/dist/cjs/messaging/base-consumer.js.map +1 -0
- package/dist/cjs/messaging/consumer.js +142 -73
- package/dist/cjs/messaging/consumer.js.map +1 -1
- package/dist/cjs/messaging/envelope.js +53 -10
- package/dist/cjs/messaging/envelope.js.map +1 -1
- package/dist/cjs/messaging/idempotency.js +95 -8
- package/dist/cjs/messaging/idempotency.js.map +1 -1
- package/dist/cjs/messaging/index.js +2 -1
- package/dist/cjs/messaging/index.js.map +1 -1
- package/dist/cjs/messaging/outbox.js +97 -55
- package/dist/cjs/messaging/outbox.js.map +1 -1
- package/dist/cjs/messaging/publisher.js +86 -38
- package/dist/cjs/messaging/publisher.js.map +1 -1
- package/dist/cjs/messaging/staff-notifier.js +4 -4
- package/dist/cjs/messaging/staff-notifier.js.map +1 -1
- package/dist/cjs/messaging/topology.js +41 -5
- package/dist/cjs/messaging/topology.js.map +1 -1
- package/dist/cjs/rate-limit/index.js +57 -6
- package/dist/cjs/rate-limit/index.js.map +1 -1
- package/dist/cjs/rate-limit/keys.js +47 -0
- package/dist/cjs/rate-limit/keys.js.map +1 -0
- package/dist/cjs/socket/index.js +127 -16
- package/dist/cjs/socket/index.js.map +1 -1
- package/dist/cjs/socket/ticket.js +124 -0
- package/dist/cjs/socket/ticket.js.map +1 -0
- package/dist/cjs/testing/index.js +23 -14
- package/dist/cjs/testing/index.js.map +1 -1
- package/dist/esm/access/index.js +9 -1
- package/dist/esm/access/index.js.map +1 -1
- package/dist/esm/approvals/index.js +127 -0
- package/dist/esm/approvals/index.js.map +1 -0
- package/dist/esm/audit/index.js +70 -0
- package/dist/esm/audit/index.js.map +1 -0
- package/dist/esm/cache/index.js +70 -13
- package/dist/esm/cache/index.js.map +1 -1
- package/dist/esm/cache/keyspace.js +95 -0
- package/dist/esm/cache/keyspace.js.map +1 -0
- package/dist/esm/common/circuit-breaker.js +70 -0
- package/dist/esm/common/circuit-breaker.js.map +1 -0
- package/dist/esm/common/index.js +4 -0
- package/dist/esm/common/index.js.map +1 -1
- package/dist/esm/common/internal-client.js +173 -0
- package/dist/esm/common/internal-client.js.map +1 -0
- package/dist/esm/common/internal-constants.js +9 -0
- package/dist/esm/common/internal-constants.js.map +1 -0
- package/dist/esm/common/internal-service-auth.js +150 -0
- package/dist/esm/common/internal-service-auth.js.map +1 -0
- package/dist/esm/common/money.js +207 -0
- package/dist/esm/common/money.js.map +1 -0
- package/dist/esm/common/phone.js +41 -22
- package/dist/esm/common/phone.js.map +1 -1
- package/dist/esm/common/time.js +27 -2
- package/dist/esm/common/time.js.map +1 -1
- package/dist/esm/config/index.js +135 -18
- package/dist/esm/config/index.js.map +1 -1
- package/dist/esm/contracts/notification-dispatch.js +1 -1
- package/dist/esm/database/db.js +32 -0
- package/dist/esm/database/db.js.map +1 -0
- package/dist/esm/database/index.js +9 -45
- package/dist/esm/database/index.js.map +1 -1
- package/dist/esm/database/pagination.js +81 -0
- package/dist/esm/database/pagination.js.map +1 -0
- package/dist/esm/database/pool.js +51 -0
- package/dist/esm/database/pool.js.map +1 -0
- package/dist/esm/database/scoped-repository.js +128 -0
- package/dist/esm/database/scoped-repository.js.map +1 -0
- package/dist/esm/database/scopes.js +157 -0
- package/dist/esm/database/scopes.js.map +1 -0
- package/dist/esm/database/table-names.js +11 -0
- package/dist/esm/database/table-names.js.map +1 -0
- package/dist/esm/database/tables.js +61 -0
- package/dist/esm/database/tables.js.map +1 -0
- package/dist/esm/express/auth.js +62 -48
- package/dist/esm/express/auth.js.map +1 -1
- package/dist/esm/express/index.js +2 -0
- package/dist/esm/express/index.js.map +1 -1
- package/dist/esm/express/internal-caller.js +49 -0
- package/dist/esm/express/internal-caller.js.map +1 -0
- package/dist/esm/express/step-up.js +38 -0
- package/dist/esm/express/step-up.js.map +1 -0
- package/dist/esm/http/index.js +3 -70
- package/dist/esm/http/index.js.map +1 -1
- package/dist/esm/identity/index.js +246 -64
- package/dist/esm/identity/index.js.map +1 -1
- package/dist/esm/identity/step-up.js +47 -0
- package/dist/esm/identity/step-up.js.map +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/knex/columns.js +28 -0
- package/dist/esm/knex/columns.js.map +1 -0
- package/dist/esm/knex/index.js +8 -34
- package/dist/esm/knex/index.js.map +1 -1
- package/dist/esm/knex/migration-helpers.js +51 -0
- package/dist/esm/knex/migration-helpers.js.map +1 -0
- package/dist/esm/logging/redact.js +3 -7
- package/dist/esm/logging/redact.js.map +1 -1
- package/dist/esm/messaging/base-consumer.js +32 -0
- package/dist/esm/messaging/base-consumer.js.map +1 -0
- package/dist/esm/messaging/consumer.js +139 -71
- package/dist/esm/messaging/consumer.js.map +1 -1
- package/dist/esm/messaging/envelope.js +50 -10
- package/dist/esm/messaging/envelope.js.map +1 -1
- package/dist/esm/messaging/idempotency.js +91 -7
- package/dist/esm/messaging/idempotency.js.map +1 -1
- package/dist/esm/messaging/index.js +2 -1
- package/dist/esm/messaging/index.js.map +1 -1
- package/dist/esm/messaging/outbox.js +97 -54
- package/dist/esm/messaging/outbox.js.map +1 -1
- package/dist/esm/messaging/publisher.js +86 -38
- package/dist/esm/messaging/publisher.js.map +1 -1
- package/dist/esm/messaging/staff-notifier.js +4 -4
- package/dist/esm/messaging/staff-notifier.js.map +1 -1
- package/dist/esm/messaging/topology.js +39 -4
- package/dist/esm/messaging/topology.js.map +1 -1
- package/dist/esm/rate-limit/index.js +41 -6
- package/dist/esm/rate-limit/index.js.map +1 -1
- package/dist/esm/rate-limit/keys.js +43 -0
- package/dist/esm/rate-limit/keys.js.map +1 -0
- package/dist/esm/socket/index.js +112 -17
- package/dist/esm/socket/index.js.map +1 -1
- package/dist/esm/socket/ticket.js +117 -0
- package/dist/esm/socket/ticket.js.map +1 -0
- package/dist/esm/testing/index.js +24 -15
- package/dist/esm/testing/index.js.map +1 -1
- package/dist/types/access/index.d.ts +9 -3
- package/dist/types/access/index.d.ts.map +1 -1
- package/dist/types/approvals/index.d.ts +99 -0
- package/dist/types/approvals/index.d.ts.map +1 -0
- package/dist/types/audit/index.d.ts +82 -0
- package/dist/types/audit/index.d.ts.map +1 -0
- package/dist/types/cache/index.d.ts +64 -14
- package/dist/types/cache/index.d.ts.map +1 -1
- package/dist/types/cache/keyspace.d.ts +84 -0
- package/dist/types/cache/keyspace.d.ts.map +1 -0
- package/dist/types/common/circuit-breaker.d.ts +51 -0
- package/dist/types/common/circuit-breaker.d.ts.map +1 -0
- package/dist/types/common/index.d.ts +4 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/internal-client.d.ts +116 -0
- package/dist/types/common/internal-client.d.ts.map +1 -0
- package/dist/types/common/internal-constants.d.ts +9 -0
- package/dist/types/common/internal-constants.d.ts.map +1 -0
- package/dist/types/common/internal-service-auth.d.ts +102 -0
- package/dist/types/common/internal-service-auth.d.ts.map +1 -0
- package/dist/types/common/money.d.ts +104 -0
- package/dist/types/common/money.d.ts.map +1 -0
- package/dist/types/common/phone.d.ts +25 -15
- package/dist/types/common/phone.d.ts.map +1 -1
- package/dist/types/common/time.d.ts +20 -2
- package/dist/types/common/time.d.ts.map +1 -1
- package/dist/types/config/index.d.ts +251 -27
- package/dist/types/config/index.d.ts.map +1 -1
- package/dist/types/contracts/notification-dispatch.d.ts +13 -13
- package/dist/types/database/db.d.ts +46 -0
- package/dist/types/database/db.d.ts.map +1 -0
- package/dist/types/database/index.d.ts +9 -22
- package/dist/types/database/index.d.ts.map +1 -1
- package/dist/types/database/pagination.d.ts +68 -0
- package/dist/types/database/pagination.d.ts.map +1 -0
- package/dist/types/database/pool.d.ts +39 -0
- package/dist/types/database/pool.d.ts.map +1 -0
- package/dist/types/database/scoped-repository.d.ts +92 -0
- package/dist/types/database/scoped-repository.d.ts.map +1 -0
- package/dist/types/database/scopes.d.ts +159 -0
- package/dist/types/database/scopes.d.ts.map +1 -0
- package/dist/types/database/table-names.d.ts +13 -0
- package/dist/types/database/table-names.d.ts.map +1 -0
- package/dist/types/database/tables.d.ts +748 -0
- package/dist/types/database/tables.d.ts.map +1 -0
- package/dist/types/express/auth.d.ts +40 -24
- package/dist/types/express/auth.d.ts.map +1 -1
- package/dist/types/express/index.d.ts +2 -0
- package/dist/types/express/index.d.ts.map +1 -1
- package/dist/types/express/internal-caller.d.ts +53 -0
- package/dist/types/express/internal-caller.d.ts.map +1 -0
- package/dist/types/express/step-up.d.ts +38 -0
- package/dist/types/express/step-up.d.ts.map +1 -0
- package/dist/types/http/index.d.ts +3 -31
- package/dist/types/http/index.d.ts.map +1 -1
- package/dist/types/identity/index.d.ts +159 -21
- package/dist/types/identity/index.d.ts.map +1 -1
- package/dist/types/identity/step-up.d.ts +41 -0
- package/dist/types/identity/step-up.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/knex/columns.d.ts +15 -0
- package/dist/types/knex/columns.d.ts.map +1 -0
- package/dist/types/knex/index.d.ts +3 -11
- package/dist/types/knex/index.d.ts.map +1 -1
- package/dist/types/knex/migration-helpers.d.ts +28 -0
- package/dist/types/knex/migration-helpers.d.ts.map +1 -0
- package/dist/types/logging/redact.d.ts +2 -2
- package/dist/types/logging/redact.d.ts.map +1 -1
- package/dist/types/messaging/base-consumer.d.ts +33 -0
- package/dist/types/messaging/base-consumer.d.ts.map +1 -0
- package/dist/types/messaging/consumer.d.ts +59 -21
- package/dist/types/messaging/consumer.d.ts.map +1 -1
- package/dist/types/messaging/envelope.d.ts +50 -11
- package/dist/types/messaging/envelope.d.ts.map +1 -1
- package/dist/types/messaging/idempotency.d.ts +60 -3
- package/dist/types/messaging/idempotency.d.ts.map +1 -1
- package/dist/types/messaging/index.d.ts +7 -0
- package/dist/types/messaging/index.d.ts.map +1 -1
- package/dist/types/messaging/outbox.d.ts +62 -22
- package/dist/types/messaging/outbox.d.ts.map +1 -1
- package/dist/types/messaging/publisher.d.ts +54 -21
- package/dist/types/messaging/publisher.d.ts.map +1 -1
- package/dist/types/messaging/staff-notifier.d.ts +9 -9
- package/dist/types/messaging/staff-notifier.d.ts.map +1 -1
- package/dist/types/messaging/staff-recipients.d.ts +1 -1
- package/dist/types/messaging/topology.d.ts +40 -8
- package/dist/types/messaging/topology.d.ts.map +1 -1
- package/dist/types/rate-limit/index.d.ts +40 -4
- package/dist/types/rate-limit/index.d.ts.map +1 -1
- package/dist/types/rate-limit/keys.d.ts +52 -0
- package/dist/types/rate-limit/keys.d.ts.map +1 -0
- package/dist/types/socket/index.d.ts +86 -2
- package/dist/types/socket/index.d.ts.map +1 -1
- package/dist/types/socket/ticket.d.ts +90 -0
- package/dist/types/socket/ticket.d.ts.map +1 -0
- package/dist/types/testing/index.d.ts +31 -10
- package/dist/types/testing/index.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +26 -14
- package/dist/types/types/index.d.ts.map +1 -1
- package/package.json +3 -1
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* and it is sensitive when a word (or two adjacent words joined, e.g. `api`+`key`) equals a listed term
|
|
5
5
|
* or its plural. So `accessToken` and `Set-Cookie` are redacted while `pinned` or `spinner` are not.
|
|
6
6
|
*/
|
|
7
|
+
import { maskPhone } from '../common/phone.js';
|
|
7
8
|
/** Words whose values are always replaced with `[REDACTED]`. Apps add their own via `redactKeys`. */
|
|
8
9
|
export const DEFAULT_REDACT_KEYS = [
|
|
9
10
|
'password', 'pass', 'passphrase', 'token', 'secret', 'code', 'otp', 'authorization', 'cookie', 'credential',
|
|
@@ -19,18 +20,13 @@ const PHONE_IN_TEXT = /(?<![\w+]|\d\.)\+?\d{9,15}(?!\w|\.\d)/g;
|
|
|
19
20
|
export const REDACTED = '[REDACTED]';
|
|
20
21
|
const MAX_DEPTH = 8;
|
|
21
22
|
/**
|
|
22
|
-
* Masks every digit except the last four
|
|
23
|
+
* Masks every digit except the last four; the same rule as `maskPhone` in common (one rule for logs and UI).
|
|
23
24
|
*
|
|
24
25
|
* @param value A phone number in any format.
|
|
25
26
|
* @returns e.g. `********5678`; values with fewer than 5 digits are fully masked.
|
|
26
27
|
* @example maskPhoneForLog('15551234567') // '*******4567'
|
|
27
28
|
*/
|
|
28
|
-
export
|
|
29
|
-
const digits = value.replace(/\D/g, '');
|
|
30
|
-
if (digits.length < 5)
|
|
31
|
-
return '*'.repeat(digits.length || 4);
|
|
32
|
-
return `${'*'.repeat(digits.length - 4)}${digits.slice(-4)}`;
|
|
33
|
-
}
|
|
29
|
+
export const maskPhoneForLog = maskPhone;
|
|
34
30
|
/**
|
|
35
31
|
* Masks phone-number-looking runs inside free text.
|
|
36
32
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../../src/logging/redact.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qGAAqG;AACrG,MAAM,CAAC,MAAM,mBAAmB,GAAsB;IACpD,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,YAAY;IAC3G,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW;CACpC,CAAC;AAEF,4GAA4G;AAC5G,MAAM,CAAC,MAAM,kBAAkB,GAAsB,CAAC,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC;AAEzI,8EAA8E;AAC9E,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;AAEtE,uFAAuF;AACvF,MAAM,aAAa,GAAG,wCAAwC,CAAC;AAE/D,+CAA+C;AAC/C,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AACrC,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB;;;;;;GAMG;AACH,MAAM,
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../../src/logging/redact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base for a service's queue consumers (one class per queue in `src/consumers/`). The subclass declares
|
|
3
|
+
* its queue, bindings, payload schema, retry policy, dedupe and scope in the constructor and implements
|
|
4
|
+
* {@link BaseConsumer.handle}; `worker.ts` registers it. Validation, dedupe, retries, dead-lettering and
|
|
5
|
+
* context restore are done by {@link MessageConsumer}.
|
|
6
|
+
*
|
|
7
|
+
* @typeParam T Validated payload type.
|
|
8
|
+
* @typeParam TTx Dedupe transaction type (`DbExecutor` with `ProcessedEventsDeduper`).
|
|
9
|
+
* @example
|
|
10
|
+
* class ThingCreatedConsumer extends BaseConsumer<ThingCreated, DbExecutor> {
|
|
11
|
+
* constructor(private readonly things: ThingService, dedupe: ProcessedEventsDeduper) {
|
|
12
|
+
* super({ queue: 'thing-created', bindings: [{ exchange: 'events', routingKey: 'thing.created' }],
|
|
13
|
+
* schema: thingCreatedSchema, dedupe, scopeFrom: (d) => ({ tenantId: d.tenantId }) });
|
|
14
|
+
* }
|
|
15
|
+
* protected handle({ data, tx, scope }: InboundMessage<ThingCreated, DbExecutor>) { return this.things.onCreated(scope!, data, tx!); }
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
export class BaseConsumer {
|
|
19
|
+
definition;
|
|
20
|
+
constructor(definition) {
|
|
21
|
+
this.definition = definition;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Subscribes this consumer.
|
|
25
|
+
*
|
|
26
|
+
* @param consumer The service's {@link MessageConsumer}.
|
|
27
|
+
*/
|
|
28
|
+
register(consumer) {
|
|
29
|
+
return consumer.subscribe({ ...this.definition, handler: (message) => this.handle(message) });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=base-consumer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-consumer.js","sourceRoot":"","sources":["../../../src/messaging/base-consumer.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,OAAgB,YAAY;IACS;IAAzC,YAAyC,UAAsC;QAAtC,eAAU,GAAV,UAAU,CAA4B;IAAG,CAAC;IAKnF;;;;OAIG;IACH,QAAQ,CAAC,QAAyB;QAChC,OAAO,QAAQ,CAAC,SAAS,CAAS,EAAE,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACxG,CAAC;CACF"}
|
|
@@ -1,125 +1,193 @@
|
|
|
1
1
|
import { isAppError } from '../common/errors.js';
|
|
2
|
+
import { newId } from '../common/id.js';
|
|
3
|
+
import { runWithContext } from '../context/index.js';
|
|
2
4
|
import { silentLogger } from '../logging/index.js';
|
|
3
|
-
import { isEnvelope } from './envelope.js';
|
|
5
|
+
import { CORRELATION_HEADER, isEnvelope, splitEnvelopeData } from './envelope.js';
|
|
4
6
|
import { deadLetterQueueName, retryQueueName } from './topology.js';
|
|
5
7
|
const RETRY_HEADER = 'x-retry-count';
|
|
6
|
-
|
|
8
|
+
/** Total deliveries before a failing message is dead-lettered. */
|
|
9
|
+
export const DEFAULT_MAX_ATTEMPTS = 5;
|
|
10
|
+
/** Base of the exponential retry delay (retry n waits base × 2^n). */
|
|
11
|
+
export const DEFAULT_RETRY_BASE_DELAY_MS = 1000;
|
|
7
12
|
/**
|
|
8
|
-
*
|
|
13
|
+
* Retry delays for `maxAttempts` deliveries: `base × 2^n` for n = 1 … maxAttempts − 1.
|
|
14
|
+
*
|
|
15
|
+
* @example retryDelays(5, 1000) // [2000, 4000, 8000, 16000]
|
|
16
|
+
*/
|
|
17
|
+
export function retryDelays(maxAttempts = DEFAULT_MAX_ATTEMPTS, baseMs = DEFAULT_RETRY_BASE_DELAY_MS) {
|
|
18
|
+
if (!Number.isInteger(maxAttempts) || maxAttempts < 1)
|
|
19
|
+
throw new RangeError('maxAttempts must be a positive integer');
|
|
20
|
+
return Array.from({ length: maxAttempts - 1 }, (_, i) => baseMs * 2 ** (i + 1));
|
|
21
|
+
}
|
|
22
|
+
class Rejected extends Error {
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Durable subscriptions with manual ack, bounded exponential retries and dead-lettering:
|
|
9
26
|
*
|
|
10
27
|
* <queue> ──fail──▶ <queue>.retry.<delay> (TTL, dead-letters back into <queue>)
|
|
11
|
-
* ──
|
|
28
|
+
* ──last attempt / non-retryable / invalid──▶ service DLX ──▶ <queue>.dlq
|
|
12
29
|
*
|
|
13
|
-
* One fixed TTL per retry queue avoids head-of-line blocking of per-message TTLs.
|
|
30
|
+
* One fixed TTL per retry queue avoids head-of-line blocking of per-message TTLs. The main queue also
|
|
31
|
+
* names the service DLX, so broker-side rejects land in the same DLQ. Each message runs inside a request
|
|
32
|
+
* context carrying its correlation id, actor and restored scope.
|
|
14
33
|
*/
|
|
15
34
|
export class MessageConsumer {
|
|
16
35
|
connection;
|
|
17
36
|
topology;
|
|
37
|
+
service;
|
|
18
38
|
handles = [];
|
|
19
39
|
logger;
|
|
20
|
-
|
|
40
|
+
/** Physical DLQ names for every subscription (for monitoring). */
|
|
41
|
+
deadLetterQueues = [];
|
|
42
|
+
constructor(connection, topology, service, logger) {
|
|
21
43
|
this.connection = connection;
|
|
22
44
|
this.topology = topology;
|
|
45
|
+
this.service = service;
|
|
23
46
|
this.logger = logger ?? silentLogger;
|
|
24
47
|
}
|
|
25
|
-
/** Physical DLQ names for every subscription (for monitoring). */
|
|
26
|
-
deadLetterQueues = [];
|
|
27
48
|
async subscribe(options) {
|
|
28
|
-
const queue = this.topology.
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
49
|
+
const queue = this.topology.queueName(this.service, options.queue);
|
|
50
|
+
const route = {
|
|
51
|
+
queue,
|
|
52
|
+
consumerName: `${this.service}.${options.queue}`,
|
|
53
|
+
dlx: this.topology.deadLetterExchange(this.service),
|
|
54
|
+
dlq: deadLetterQueueName(queue),
|
|
55
|
+
delays: retryDelays(options.maxAttempts, options.retryBaseDelayMs),
|
|
56
|
+
};
|
|
57
|
+
this.deadLetterQueues.push(route.dlq);
|
|
32
58
|
const handle = await this.connection.registerChannel(`consumer:${queue}`, async (channel) => {
|
|
33
|
-
await
|
|
34
|
-
await channel.assertExchange(this.topology.commandsExchange, 'direct', { durable: true });
|
|
35
|
-
await channel.assertQueue(queue, { durable: true });
|
|
36
|
-
await channel.assertQueue(dlq, { durable: true });
|
|
37
|
-
for (const delay of delays) {
|
|
38
|
-
await channel.assertQueue(retryQueueName(queue, delay), {
|
|
39
|
-
durable: true,
|
|
40
|
-
arguments: { 'x-message-ttl': delay, 'x-dead-letter-exchange': '', 'x-dead-letter-routing-key': queue },
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
for (const binding of options.bindings) {
|
|
44
|
-
if (binding.exchange === 'events') {
|
|
45
|
-
await channel.bindQueue(queue, this.topology.eventsExchange, binding.routingKey);
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
await channel.bindQueue(queue, this.topology.commandsExchange, this.topology.queue(binding.routingKey));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
59
|
+
await this.declare(channel, route, options.bindings);
|
|
51
60
|
await channel.prefetch(options.prefetch ?? 10);
|
|
52
61
|
await channel.consume(queue, (msg) => {
|
|
53
62
|
if (msg)
|
|
54
|
-
void this.dispatch(channel,
|
|
55
|
-
});
|
|
63
|
+
void this.dispatch(channel, route, msg, options);
|
|
64
|
+
}, { noAck: false });
|
|
56
65
|
});
|
|
57
66
|
this.handles.push(handle);
|
|
58
67
|
this.logger.info('Subscribed', { queue, bindings: options.bindings.map((b) => `${b.exchange}:${b.routingKey}`) });
|
|
59
68
|
}
|
|
60
|
-
async
|
|
61
|
-
const
|
|
69
|
+
async declare(channel, route, bindings) {
|
|
70
|
+
const { topology } = this;
|
|
71
|
+
await channel.assertExchange(topology.eventsExchange, 'topic', { durable: true });
|
|
72
|
+
await channel.assertExchange(topology.commandsExchange, 'direct', { durable: true });
|
|
73
|
+
await channel.assertExchange(route.dlx, 'direct', { durable: true });
|
|
74
|
+
await channel.assertQueue(route.dlq, { durable: true });
|
|
75
|
+
await channel.bindQueue(route.dlq, route.dlx, route.dlq);
|
|
76
|
+
await channel.assertQueue(route.queue, { durable: true, arguments: { 'x-dead-letter-exchange': route.dlx, 'x-dead-letter-routing-key': route.dlq } });
|
|
77
|
+
for (const delay of route.delays) {
|
|
78
|
+
await channel.assertQueue(retryQueueName(route.queue, delay), {
|
|
79
|
+
durable: true,
|
|
80
|
+
arguments: { 'x-message-ttl': delay, 'x-dead-letter-exchange': '', 'x-dead-letter-routing-key': route.queue },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
for (const binding of bindings) {
|
|
84
|
+
if (binding.exchange === 'events')
|
|
85
|
+
await channel.bindQueue(route.queue, topology.eventsExchange, binding.routingKey);
|
|
86
|
+
else
|
|
87
|
+
await channel.bindQueue(route.queue, topology.commandsExchange, topology.queue(binding.routingKey));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async dispatch(channel, route, msg, options) {
|
|
91
|
+
const attempt = Number(msg.properties.headers?.[RETRY_HEADER] ?? 0) + 1;
|
|
92
|
+
let prepared;
|
|
93
|
+
try {
|
|
94
|
+
prepared = this.prepare(msg, options);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
this.deadLetter(channel, route, msg, error.message);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const correlationId = this.correlationIdOf(msg);
|
|
101
|
+
const context = { correlationId, service: this.service, userId: prepared.actorUserId, scopes: prepared.scope ? { ...prepared.scope } : undefined };
|
|
102
|
+
try {
|
|
103
|
+
const outcome = await runWithContext(context, () => this.handle(route, msg, prepared, attempt, options));
|
|
104
|
+
if (outcome === 'duplicate')
|
|
105
|
+
this.logger.debug('Duplicate message skipped', { queue: route.queue, eventId: prepared.eventId, correlationId });
|
|
106
|
+
channel.ack(msg);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const retryable = !isAppError(error) || error.retryable;
|
|
110
|
+
const reason = error?.message ?? String(error);
|
|
111
|
+
this.logger.warn('Message handler failed', { queue: route.queue, attempt, retryable, eventId: prepared.eventId, correlationId, error: reason });
|
|
112
|
+
this.retryOrDeadLetter(channel, route, msg, attempt, reason, retryable);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/** Parses, validates and extracts scope; throws `Rejected` for messages that can never succeed. */
|
|
116
|
+
prepare(msg, options) {
|
|
62
117
|
let parsed;
|
|
63
118
|
try {
|
|
64
119
|
parsed = JSON.parse(msg.content.toString('utf8'));
|
|
65
120
|
}
|
|
66
121
|
catch {
|
|
67
|
-
|
|
68
|
-
return;
|
|
122
|
+
throw new Rejected('Malformed JSON');
|
|
69
123
|
}
|
|
70
124
|
const envelope = isEnvelope(parsed) ? parsed : null;
|
|
71
|
-
|
|
125
|
+
const split = envelope ? splitEnvelopeData(envelope) : null;
|
|
126
|
+
let data = split ? split.payload : parsed;
|
|
72
127
|
if (options.schema) {
|
|
73
128
|
const result = options.schema.safeParse(data);
|
|
74
129
|
if (!result.success) {
|
|
75
|
-
|
|
76
|
-
return;
|
|
130
|
+
throw new Rejected(`Schema validation failed: ${result.error.issues.map((i) => `${i.path.join('.')} ${i.message}`).join('; ')}`);
|
|
77
131
|
}
|
|
78
132
|
data = result.data;
|
|
79
133
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
}
|
|
134
|
+
const messageId = typeof msg.properties.messageId === 'string' ? msg.properties.messageId : undefined;
|
|
135
|
+
const eventId = split?.meta.eventId ?? messageId;
|
|
136
|
+
if (options.dedupe && !eventId)
|
|
137
|
+
throw new Rejected('Missing event id');
|
|
138
|
+
const actor = split?.meta.actorUserId;
|
|
139
|
+
return { data: data, envelope, eventId, actorUserId: actor ?? undefined, scope: this.scopeOf(data, options.scopeFrom) };
|
|
140
|
+
}
|
|
141
|
+
scopeOf(data, scopeFrom) {
|
|
142
|
+
if (!scopeFrom)
|
|
143
|
+
return undefined;
|
|
144
|
+
let values;
|
|
93
145
|
try {
|
|
94
|
-
|
|
95
|
-
if (idemKey)
|
|
96
|
-
await options.idempotency.complete(idemKey, options.idempotencyTtlSec ?? 7 * 86_400).catch(() => undefined);
|
|
97
|
-
channel.ack(msg);
|
|
146
|
+
values = scopeFrom(data);
|
|
98
147
|
}
|
|
99
|
-
catch
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
148
|
+
catch {
|
|
149
|
+
throw new Rejected('Scope could not be read from the message');
|
|
150
|
+
}
|
|
151
|
+
const entries = Object.entries(values);
|
|
152
|
+
const missing = entries.filter(([, v]) => typeof v !== 'string' || v.length === 0).map(([k]) => k);
|
|
153
|
+
if (entries.length === 0 || missing.length > 0)
|
|
154
|
+
throw new Rejected(`Missing scope: ${missing.join(', ') || '(none returned)'}`);
|
|
155
|
+
return Object.freeze(Object.fromEntries(entries));
|
|
156
|
+
}
|
|
157
|
+
correlationIdOf(msg) {
|
|
158
|
+
const header = msg.properties.headers?.[CORRELATION_HEADER];
|
|
159
|
+
if (typeof header === 'string' && header)
|
|
160
|
+
return header;
|
|
161
|
+
const prop = msg.properties.correlationId;
|
|
162
|
+
return typeof prop === 'string' && prop ? prop : newId();
|
|
163
|
+
}
|
|
164
|
+
async handle(route, msg, prepared, attempt, options) {
|
|
165
|
+
const call = (tx) => options.handler({
|
|
166
|
+
data: prepared.data, envelope: prepared.envelope, eventId: prepared.eventId, routingKey: msg.fields.routingKey,
|
|
167
|
+
attempt, tx, scope: prepared.scope, raw: msg,
|
|
168
|
+
});
|
|
169
|
+
if (!options.dedupe) {
|
|
170
|
+
await call(undefined);
|
|
171
|
+
return 'processed';
|
|
106
172
|
}
|
|
173
|
+
return options.dedupe.run(route.consumerName, prepared.eventId, call);
|
|
107
174
|
}
|
|
108
|
-
retryOrDeadLetter(channel,
|
|
109
|
-
|
|
110
|
-
|
|
175
|
+
retryOrDeadLetter(channel, route, msg, attempt, reason, retryable) {
|
|
176
|
+
const delay = route.delays[attempt - 1];
|
|
177
|
+
if (retryable && delay !== undefined) {
|
|
178
|
+
channel.sendToQueue(retryQueueName(route.queue, delay), msg.content, {
|
|
111
179
|
...msg.properties,
|
|
112
180
|
persistent: true,
|
|
113
|
-
headers: { ...(msg.properties.headers ?? {}), [RETRY_HEADER]: attempt
|
|
181
|
+
headers: { ...(msg.properties.headers ?? {}), [RETRY_HEADER]: attempt, 'x-last-error': reason.slice(0, 500), 'x-original-routing-key': msg.fields.routingKey },
|
|
114
182
|
});
|
|
115
183
|
channel.ack(msg);
|
|
116
184
|
return;
|
|
117
185
|
}
|
|
118
|
-
this.deadLetter(channel,
|
|
186
|
+
this.deadLetter(channel, route, msg, reason);
|
|
119
187
|
}
|
|
120
|
-
deadLetter(channel,
|
|
121
|
-
this.logger.error('Message dead-lettered', { dlq, routingKey: msg.fields.routingKey, reason });
|
|
122
|
-
channel.
|
|
188
|
+
deadLetter(channel, route, msg, reason) {
|
|
189
|
+
this.logger.error('Message dead-lettered', { dlq: route.dlq, routingKey: msg.fields.routingKey, reason });
|
|
190
|
+
channel.publish(route.dlx, route.dlq, msg.content, {
|
|
123
191
|
...msg.properties,
|
|
124
192
|
persistent: true,
|
|
125
193
|
headers: { ...(msg.properties.headers ?? {}), 'x-dead-letter-reason': reason.slice(0, 1000), 'x-failed-at': new Date().toISOString(), 'x-original-routing-key': msg.fields.routingKey },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consumer.js","sourceRoot":"","sources":["../../../src/messaging/consumer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"consumer.js","sourceRoot":"","sources":["../../../src/messaging/consumer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,19 +1,59 @@
|
|
|
1
1
|
import { newId } from '../common/id.js';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { getRequestContext } from '../context/index.js';
|
|
3
|
+
import { CONTEXT_HEADERS } from '../identity/index.js';
|
|
4
|
+
/** Names reserved in an envelope's `data`; a payload field with one of these names is overwritten. */
|
|
5
|
+
export const RESERVED_ENVELOPE_FIELDS = ['eventId', 'version', 'actorUserId'];
|
|
6
|
+
/** AMQP header that carries the correlation id between services. */
|
|
7
|
+
export const CORRELATION_HEADER = CONTEXT_HEADERS.CORRELATION_ID;
|
|
8
|
+
/**
|
|
9
|
+
* Builds `{ routingKey, data: { ...payload, eventId, version, actorUserId? }, timestamp }`.
|
|
10
|
+
*
|
|
11
|
+
* @param routingKey Event or command name.
|
|
12
|
+
* @param data Payload fields (an object); {@link RESERVED_ENVELOPE_FIELDS} are set by the envelope.
|
|
13
|
+
* @param meta Optional id, version and actor.
|
|
14
|
+
* @returns The envelope.
|
|
15
|
+
* @example const envelope = buildEnvelope('thing.created', { thingId }, { actorUserId: user.id });
|
|
16
|
+
*/
|
|
17
|
+
export function buildEnvelope(routingKey, data, meta = {}) {
|
|
18
|
+
const actorUserId = meta.actorUserId !== undefined ? meta.actorUserId : getRequestContext()?.userId;
|
|
19
|
+
const reserved = {
|
|
4
20
|
eventId: meta.eventId ?? newId(),
|
|
5
|
-
type,
|
|
6
21
|
version: meta.version ?? 1,
|
|
7
|
-
|
|
8
|
-
source,
|
|
9
|
-
...(meta.correlationId ? { correlationId: meta.correlationId } : {}),
|
|
10
|
-
actor: { userId: meta.actorId ?? null },
|
|
11
|
-
...(meta.aggregate ? { aggregate: meta.aggregate } : {}),
|
|
12
|
-
data,
|
|
22
|
+
...(actorUserId !== undefined ? { actorUserId } : {}),
|
|
13
23
|
};
|
|
24
|
+
return { routingKey, data: { ...data, ...reserved }, timestamp: new Date().toISOString() };
|
|
14
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* True when `value` has the envelope shape (routing key, timestamp and `data.eventId`).
|
|
28
|
+
*
|
|
29
|
+
* @param value Parsed message body.
|
|
30
|
+
* @returns Whether it is an {@link EventEnvelope}.
|
|
31
|
+
*/
|
|
15
32
|
export function isEnvelope(value) {
|
|
16
33
|
const v = value;
|
|
17
|
-
return !!v && typeof v === 'object'
|
|
34
|
+
return !!v && typeof v === 'object'
|
|
35
|
+
&& typeof v.routingKey === 'string'
|
|
36
|
+
&& typeof v.timestamp === 'string'
|
|
37
|
+
&& !!v.data && typeof v.data === 'object'
|
|
38
|
+
&& typeof v.data.eventId === 'string';
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Splits an envelope's `data` into the payload and the reserved fields.
|
|
42
|
+
*
|
|
43
|
+
* @param envelope A received envelope.
|
|
44
|
+
* @returns `payload` (without reserved fields) and `meta` ({@link EnvelopeData}).
|
|
45
|
+
*/
|
|
46
|
+
export function splitEnvelopeData(envelope) {
|
|
47
|
+
const { eventId, version, actorUserId, ...payload } = envelope.data;
|
|
48
|
+
return { payload, meta: { eventId, version, ...(actorUserId !== undefined ? { actorUserId } : {}) } };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The correlation id to send: the explicit one, else the active request context's.
|
|
52
|
+
*
|
|
53
|
+
* @param meta Optional explicit value.
|
|
54
|
+
* @returns The id, or undefined outside a request/consumer context.
|
|
55
|
+
*/
|
|
56
|
+
export function outgoingCorrelationId(meta) {
|
|
57
|
+
return meta?.correlationId ?? getRequestContext()?.correlationId;
|
|
18
58
|
}
|
|
19
59
|
//# sourceMappingURL=envelope.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../src/messaging/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../src/messaging/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,sGAAsG;AACtG,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,CAAU,CAAC;AAEvF,oEAAoE;AACpE,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAC,cAAc,CAAC;AAkBjE;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAmB,UAAkB,EAAE,IAAO,EAAE,OAAqB,EAAE;IAClG,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,iBAAiB,EAAE,EAAE,MAAM,CAAC;IACpG,MAAM,QAAQ,GAAiB;QAC7B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK,EAAE;QAChC,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,CAAC;QAC1B,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;IACF,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;AAC7F,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,MAAM,CAAC,GAAG,KAAsC,CAAC;IACjD,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;WAC9B,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ;WAChC,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;WAC/B,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;WACtC,OAAQ,CAAC,CAAC,IAA8B,CAAC,OAAO,KAAK,QAAQ,CAAC;AACrE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAuB;IACvD,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC;IACpE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;AACxG,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAA0C;IAC9E,OAAO,IAAI,EAAE,aAAa,IAAI,iBAAiB,EAAE,EAAE,aAAa,CAAC;AACnE,CAAC"}
|
|
@@ -1,28 +1,83 @@
|
|
|
1
|
+
import { ServiceUnavailableError } from '../common/errors.js';
|
|
2
|
+
import { newId } from '../common/id.js';
|
|
3
|
+
import { processedEvents as defaultProcessedEvents } from '../database/tables.js';
|
|
4
|
+
/** Thrown inside the ledger transaction to roll back when the event is already recorded. */
|
|
5
|
+
class AlreadyProcessed extends Error {
|
|
6
|
+
}
|
|
7
|
+
const DUPLICATE_KEY = 'ER_DUP_ENTRY';
|
|
8
|
+
/** True for a MySQL duplicate-key error, also when Drizzle wraps it in `cause`. */
|
|
9
|
+
export function isDuplicateKeyError(error) {
|
|
10
|
+
for (let e = error, depth = 0; e && depth < 5; e = e.cause, depth += 1) {
|
|
11
|
+
if (e.code === DUPLICATE_KEY || e.errno === 1062)
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Database ledger (`processed_events`, unique `(consumer, event_id)`): the ledger row and the handler's
|
|
18
|
+
* writes commit in one transaction, so an event is recorded as processed if and only if its effects
|
|
19
|
+
* commit. A concurrent duplicate blocks on the unique key until the first commits, then is skipped.
|
|
20
|
+
*
|
|
21
|
+
* @example const dedupe = new ProcessedEventsDeduper(db); // handler gets `tx` and writes through it
|
|
22
|
+
*/
|
|
23
|
+
export class ProcessedEventsDeduper {
|
|
24
|
+
db;
|
|
25
|
+
table;
|
|
26
|
+
constructor(db, table = defaultProcessedEvents) {
|
|
27
|
+
this.db = db;
|
|
28
|
+
this.table = table;
|
|
29
|
+
}
|
|
30
|
+
async run(consumer, eventId, work) {
|
|
31
|
+
try {
|
|
32
|
+
await this.db.transaction(async (tx) => {
|
|
33
|
+
try {
|
|
34
|
+
await tx.insert(this.table).values({ id: newId(), consumer, eventId });
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (isDuplicateKeyError(error))
|
|
38
|
+
throw new AlreadyProcessed();
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
await work(tx);
|
|
42
|
+
});
|
|
43
|
+
return 'processed';
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof AlreadyProcessed)
|
|
47
|
+
return 'duplicate';
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
1
52
|
const PROCESSING = 'processing';
|
|
2
53
|
const DONE = 'done';
|
|
54
|
+
/** Redis claim store; every key comes from the service keyspace (`{prefix}idem:{key}`). */
|
|
3
55
|
export class RedisIdempotencyStore {
|
|
4
56
|
redis;
|
|
5
|
-
|
|
6
|
-
constructor(redis,
|
|
57
|
+
keyspace;
|
|
58
|
+
constructor(redis, keyspace) {
|
|
7
59
|
this.redis = redis;
|
|
8
|
-
this.
|
|
60
|
+
this.keyspace = keyspace;
|
|
61
|
+
}
|
|
62
|
+
key(key) {
|
|
63
|
+
return this.keyspace.key('idem', key);
|
|
9
64
|
}
|
|
10
65
|
async begin(key, leaseSec) {
|
|
11
|
-
const k = this.
|
|
66
|
+
const k = this.key(key);
|
|
12
67
|
const set = await this.redis.set(k, PROCESSING, 'EX', leaseSec, 'NX');
|
|
13
68
|
if (set === 'OK')
|
|
14
69
|
return 'claimed';
|
|
15
70
|
return (await this.redis.get(k)) === DONE ? 'done' : 'in-flight';
|
|
16
71
|
}
|
|
17
72
|
async complete(key, ttlSec) {
|
|
18
|
-
await this.redis.set(this.
|
|
73
|
+
await this.redis.set(this.key(key), DONE, 'EX', ttlSec);
|
|
19
74
|
}
|
|
20
75
|
async abandon(key) {
|
|
21
|
-
const k = this.prefix + key;
|
|
22
76
|
// Only release our own lease — never erase a completed marker.
|
|
23
|
-
await this.redis.eval("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) end return 0", 1,
|
|
77
|
+
await this.redis.eval("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) end return 0", 1, this.key(key), PROCESSING);
|
|
24
78
|
}
|
|
25
79
|
}
|
|
80
|
+
/** In-process claim store for tests and single-process tools. */
|
|
26
81
|
export class MemoryIdempotencyStore {
|
|
27
82
|
entries = new Map();
|
|
28
83
|
read(key) {
|
|
@@ -49,4 +104,33 @@ export class MemoryIdempotencyStore {
|
|
|
49
104
|
this.entries.delete(key);
|
|
50
105
|
}
|
|
51
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* {@link EventDeduper} over an {@link IdempotencyStore}. A duplicate still in flight elsewhere throws a
|
|
109
|
+
* retryable 503, so the consumer retries it later instead of acking it unprocessed.
|
|
110
|
+
*/
|
|
111
|
+
export class StoreDeduper {
|
|
112
|
+
store;
|
|
113
|
+
options;
|
|
114
|
+
constructor(store, options = {}) {
|
|
115
|
+
this.store = store;
|
|
116
|
+
this.options = options;
|
|
117
|
+
}
|
|
118
|
+
async run(consumer, eventId, work) {
|
|
119
|
+
const key = `${consumer}:${eventId}`;
|
|
120
|
+
const claim = await this.store.begin(key, this.options.leaseSec ?? 300);
|
|
121
|
+
if (claim === 'done')
|
|
122
|
+
return 'duplicate';
|
|
123
|
+
if (claim === 'in-flight')
|
|
124
|
+
throw new ServiceUnavailableError('Duplicate message in flight', { code: 'DUPLICATE_IN_FLIGHT' });
|
|
125
|
+
try {
|
|
126
|
+
await work(undefined);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
await this.store.abandon(key).catch(() => undefined);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
await this.store.complete(key, this.options.ttlSec ?? 7 * 86_400).catch(() => undefined);
|
|
133
|
+
return 'processed';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
52
136
|
//# sourceMappingURL=idempotency.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../../src/messaging/idempotency.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../../src/messaging/idempotency.ts"],"names":[],"mappings":"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"}
|