@heavybit/shared-lib 1.3.2 → 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 +261 -0
- 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/errors.js +44 -5
- package/dist/cjs/common/errors.js.map +1 -1
- 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 +63 -11
- package/dist/cjs/common/phone.js.map +1 -1
- package/dist/cjs/common/response.js +78 -5
- package/dist/cjs/common/response.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 +140 -20
- package/dist/cjs/config/index.js.map +1 -1
- package/dist/cjs/context/index.js +49 -0
- package/dist/cjs/context/index.js.map +1 -0
- package/dist/cjs/contracts/index.js +0 -2
- package/dist/cjs/contracts/index.js.map +1 -1
- package/dist/cjs/contracts/notification-dispatch.js +87 -63
- package/dist/cjs/contracts/notification-dispatch.js.map +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 +88 -41
- package/dist/cjs/express/auth.js.map +1 -1
- package/dist/cjs/express/index.js +3 -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/middleware.js +154 -34
- package/dist/cjs/express/middleware.js.map +1 -1
- package/dist/cjs/express/respond.js +47 -0
- package/dist/cjs/express/respond.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 +7 -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/index.js +76 -40
- package/dist/cjs/logging/index.js.map +1 -1
- package/dist/cjs/logging/redact.js +112 -0
- package/dist/cjs/logging/redact.js.map +1 -0
- 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 +3 -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 +36 -44
- package/dist/cjs/messaging/staff-notifier.js.map +1 -1
- package/dist/cjs/messaging/staff-recipients.js +57 -0
- package/dist/cjs/messaging/staff-recipients.js.map +1 -0
- 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/cjs/validation/index.js +27 -3
- package/dist/cjs/validation/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/errors.js +42 -4
- package/dist/esm/common/errors.js.map +1 -1
- 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 +63 -11
- package/dist/esm/common/phone.js.map +1 -1
- package/dist/esm/common/response.js +75 -5
- package/dist/esm/common/response.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 +136 -19
- package/dist/esm/config/index.js.map +1 -1
- package/dist/esm/context/index.js +44 -0
- package/dist/esm/context/index.js.map +1 -0
- package/dist/esm/contracts/index.js +0 -2
- package/dist/esm/contracts/index.js.map +1 -1
- package/dist/esm/contracts/notification-dispatch.js +85 -62
- package/dist/esm/contracts/notification-dispatch.js.map +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 +87 -40
- package/dist/esm/express/auth.js.map +1 -1
- package/dist/esm/express/index.js +3 -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/middleware.js +146 -27
- package/dist/esm/express/middleware.js.map +1 -1
- package/dist/esm/express/respond.js +42 -0
- package/dist/esm/express/respond.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 +7 -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/index.js +68 -39
- package/dist/esm/logging/index.js.map +1 -1
- package/dist/esm/logging/redact.js +106 -0
- package/dist/esm/logging/redact.js.map +1 -0
- 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 +3 -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 +36 -43
- package/dist/esm/messaging/staff-notifier.js.map +1 -1
- package/dist/esm/messaging/staff-recipients.js +53 -0
- package/dist/esm/messaging/staff-recipients.js.map +1 -0
- 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/esm/validation/index.js +27 -2
- package/dist/esm/validation/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/alerting/index.d.ts +1 -1
- package/dist/types/alerting/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/errors.d.ts +48 -7
- package/dist/types/common/errors.d.ts.map +1 -1
- 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 +45 -6
- package/dist/types/common/phone.d.ts.map +1 -1
- package/dist/types/common/response.d.ts +66 -5
- package/dist/types/common/response.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 +252 -28
- package/dist/types/config/index.d.ts.map +1 -1
- package/dist/types/context/index.d.ts +39 -0
- package/dist/types/context/index.d.ts.map +1 -0
- package/dist/types/contracts/index.d.ts +0 -2
- package/dist/types/contracts/index.d.ts.map +1 -1
- package/dist/types/contracts/notification-dispatch.d.ts +204 -86
- package/dist/types/contracts/notification-dispatch.d.ts.map +1 -1
- 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 +63 -11
- package/dist/types/express/auth.d.ts.map +1 -1
- package/dist/types/express/index.d.ts +3 -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/middleware.d.ts +86 -7
- package/dist/types/express/middleware.d.ts.map +1 -1
- package/dist/types/express/respond.d.ts +41 -0
- package/dist/types/express/respond.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 +7 -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/index.d.ts +30 -4
- package/dist/types/logging/index.d.ts.map +1 -1
- package/dist/types/logging/redact.d.ts +54 -0
- package/dist/types/logging/redact.d.ts.map +1 -0
- 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 +8 -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 +69 -38
- package/dist/types/messaging/staff-notifier.d.ts.map +1 -1
- package/dist/types/messaging/staff-recipients.d.ts +50 -0
- package/dist/types/messaging/staff-recipients.d.ts.map +1 -0
- 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 +62 -15
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/validation/index.d.ts +33 -2
- package/dist/types/validation/index.d.ts.map +1 -1
- package/package.json +3 -1
- package/dist/cjs/contracts/editorial-draft.js +0 -120
- package/dist/cjs/contracts/editorial-draft.js.map +0 -1
- package/dist/cjs/contracts/upload-import.js +0 -54
- package/dist/cjs/contracts/upload-import.js.map +0 -1
- package/dist/esm/contracts/editorial-draft.js +0 -117
- package/dist/esm/contracts/editorial-draft.js.map +0 -1
- package/dist/esm/contracts/upload-import.js +0 -51
- package/dist/esm/contracts/upload-import.js.map +0 -1
- package/dist/types/contracts/editorial-draft.d.ts +0 -610
- package/dist/types/contracts/editorial-draft.d.ts.map +0 -1
- package/dist/types/contracts/upload-import.d.ts +0 -96
- package/dist/types/contracts/upload-import.d.ts.map +0 -1
|
@@ -1,51 +1,74 @@
|
|
|
1
|
+
import { and, asc, eq, isNotNull, isNull, lt, sql } from 'drizzle-orm';
|
|
2
|
+
import { DEFAULT_OUTBOX_TABLE } from '../database/table-names.js';
|
|
3
|
+
import { outbox as defaultOutbox } from '../database/tables.js';
|
|
1
4
|
import { silentLogger } from '../logging/index.js';
|
|
5
|
+
import { buildEnvelope, outgoingCorrelationId } from './envelope.js';
|
|
2
6
|
import { isUnroutableError } from './publisher.js';
|
|
3
|
-
export
|
|
4
|
-
const COMMAND_PREFIX = 'command:';
|
|
5
|
-
/** Outbox `routing_key` column value: the event type, or `command:<name>` for commands. */
|
|
6
|
-
export function outboxRoutingKey(envelope, kind = 'event') {
|
|
7
|
-
return kind === 'command' ? `${COMMAND_PREFIX}${envelope.type}` : envelope.type;
|
|
8
|
-
}
|
|
7
|
+
export { DEFAULT_OUTBOX_TABLE };
|
|
9
8
|
/**
|
|
10
|
-
* Transactional outbox:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Transactional outbox: stages a message in the caller's transaction, so it is published if and only if
|
|
10
|
+
* the domain change commits. The {@link OutboxRelay} publishes it later with broker confirms. The
|
|
11
|
+
* correlation id (explicit, else the request context's) is stored with the row and sent as a header.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* @param tx The caller's open Drizzle transaction (or db handle for a single-statement change).
|
|
14
|
+
* @param routingKey Event or command name.
|
|
15
|
+
* @param data Payload fields.
|
|
16
|
+
* @param options Exchange, envelope meta and table.
|
|
17
|
+
* @returns The staged envelope.
|
|
18
|
+
* @example await db.transaction(async (tx) => { await repo.insert(ctx, row, { tx }); await enqueueOutbox(tx, 'thing.created', { thingId: row.id }); });
|
|
16
19
|
*/
|
|
17
|
-
export async function enqueueOutbox(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
export async function enqueueOutbox(tx, routingKey, data, options = {}) {
|
|
21
|
+
const table = options.table ?? defaultOutbox;
|
|
22
|
+
const envelope = buildEnvelope(routingKey, data, options);
|
|
23
|
+
await tx.insert(table).values({
|
|
24
|
+
id: envelope.data.eventId,
|
|
25
|
+
routingKey,
|
|
26
|
+
exchange: options.exchange ?? 'events',
|
|
27
|
+
payload: envelope,
|
|
28
|
+
correlationId: outgoingCorrelationId(options) ?? null,
|
|
22
29
|
attempts: 0,
|
|
23
|
-
created_at: new Date(),
|
|
24
30
|
});
|
|
31
|
+
return envelope;
|
|
25
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Publishes pending outbox rows in id (= time) order. Each batch locks its rows with
|
|
35
|
+
* `FOR UPDATE SKIP LOCKED`, so several replicas can relay at once without double-publishing, and marks a
|
|
36
|
+
* row published only after the broker confirms it. Consumers still dedupe: a crash between confirm and
|
|
37
|
+
* commit re-sends the row.
|
|
38
|
+
*
|
|
39
|
+
* Failures: an unroutable command stays pending and the batch continues; a broker error stops the batch
|
|
40
|
+
* (keeping order) and the poll interval backs off exponentially until a batch succeeds.
|
|
41
|
+
*/
|
|
26
42
|
export class OutboxRelay {
|
|
27
43
|
options;
|
|
28
44
|
timer = null;
|
|
29
45
|
running = null;
|
|
30
46
|
rerun = false;
|
|
47
|
+
failures = 0;
|
|
31
48
|
logger;
|
|
32
49
|
table;
|
|
33
50
|
constructor(options) {
|
|
34
51
|
this.options = options;
|
|
35
52
|
this.logger = options.logger ?? silentLogger;
|
|
36
|
-
this.table = options.table ??
|
|
53
|
+
this.table = options.table ?? defaultOutbox;
|
|
37
54
|
}
|
|
55
|
+
/** Starts polling. */
|
|
38
56
|
start() {
|
|
39
57
|
if (this.timer)
|
|
40
58
|
return;
|
|
41
|
-
this.
|
|
42
|
-
void this.trigger();
|
|
59
|
+
this.schedule(0);
|
|
43
60
|
}
|
|
61
|
+
/** Stops polling (an in-flight batch finishes). */
|
|
44
62
|
stop() {
|
|
45
63
|
if (this.timer)
|
|
46
|
-
|
|
64
|
+
clearTimeout(this.timer);
|
|
47
65
|
this.timer = null;
|
|
48
66
|
}
|
|
67
|
+
/** Delay before the next poll: the interval, doubled per consecutive failed batch, capped. */
|
|
68
|
+
nextDelayMs() {
|
|
69
|
+
const interval = this.options.intervalMs ?? 2000;
|
|
70
|
+
return Math.min(this.options.maxBackoffMs ?? 60_000, interval * 2 ** this.failures);
|
|
71
|
+
}
|
|
49
72
|
/** Publishes pending rows now. Concurrent triggers coalesce into one follow-up run. */
|
|
50
73
|
async trigger() {
|
|
51
74
|
if (this.running) {
|
|
@@ -63,67 +86,87 @@ export class OutboxRelay {
|
|
|
63
86
|
}
|
|
64
87
|
} while (this.rerun);
|
|
65
88
|
}
|
|
66
|
-
/** One batch.
|
|
89
|
+
/** One batch. */
|
|
67
90
|
async flush() {
|
|
68
|
-
const {
|
|
69
|
-
let
|
|
91
|
+
const metrics = { published: 0, failed: 0, lagMs: 0 };
|
|
92
|
+
let brokerDown = false;
|
|
70
93
|
try {
|
|
71
|
-
await
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.
|
|
75
|
-
.whereNull('published_at')
|
|
76
|
-
.orderBy('id', 'asc')
|
|
77
|
-
.limit(this.options.batchSize ?? 100)
|
|
78
|
-
.forUpdate()
|
|
79
|
-
.skipLocked();
|
|
94
|
+
await this.options.db.transaction(async (tx) => {
|
|
95
|
+
const rows = await this.lockPending(tx);
|
|
96
|
+
if (rows[0])
|
|
97
|
+
metrics.lagMs = Math.max(0, Date.now() - new Date(rows[0].createdAt).getTime());
|
|
80
98
|
for (const row of rows) {
|
|
81
|
-
const envelope = (typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload);
|
|
82
99
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
await bus.publishEnvelope(envelope);
|
|
87
|
-
await trx(this.table).where({ id: row.id }).update({ published_at: new Date(), last_error: null });
|
|
88
|
-
published += 1;
|
|
100
|
+
await this.publish(row);
|
|
101
|
+
await tx.update(this.table).set({ publishedAt: new Date(), lastError: null }).where(eq(this.table.id, row.id));
|
|
102
|
+
metrics.published += 1;
|
|
89
103
|
}
|
|
90
104
|
catch (error) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
105
|
+
metrics.failed += 1;
|
|
106
|
+
await tx.update(this.table)
|
|
107
|
+
.set({ attempts: sql `${this.table.attempts} + 1`, lastError: String(error?.message ?? error).slice(0, 500) })
|
|
108
|
+
.where(eq(this.table.id, row.id));
|
|
109
|
+
// Unroutable: only this command's owner is missing. Keep the row and carry on, so one
|
|
110
|
+
// undeclared queue never holds back every other message behind it.
|
|
94
111
|
if (isUnroutableError(error)) {
|
|
95
112
|
this.reportUnroutable(row, error);
|
|
96
113
|
continue;
|
|
97
114
|
}
|
|
98
|
-
// Broker down: stop this batch; the next
|
|
115
|
+
// Broker down: stop this batch; the next poll retries in order.
|
|
116
|
+
brokerDown = true;
|
|
99
117
|
break;
|
|
100
118
|
}
|
|
101
119
|
}
|
|
102
120
|
});
|
|
103
121
|
}
|
|
104
122
|
catch (error) {
|
|
123
|
+
brokerDown = true;
|
|
105
124
|
this.logger.error('Outbox relay batch failed', { error: error.message });
|
|
106
125
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
this.failures = brokerDown ? Math.min(this.failures + 1, 16) : 0;
|
|
127
|
+
if (metrics.published)
|
|
128
|
+
this.logger.debug('Outbox relayed', { published: metrics.published });
|
|
129
|
+
this.options.onMetrics?.(metrics);
|
|
130
|
+
return metrics;
|
|
131
|
+
}
|
|
132
|
+
/** Deletes published rows older than `olderThanDays`; returns how many were removed. */
|
|
133
|
+
async prune(olderThanDays = 7) {
|
|
134
|
+
const cutoff = new Date(Date.now() - olderThanDays * 86_400_000);
|
|
135
|
+
const [result] = await this.options.db.delete(this.table)
|
|
136
|
+
.where(and(isNotNull(this.table.publishedAt), lt(this.table.publishedAt, cutoff)));
|
|
137
|
+
return result?.affectedRows ?? 0;
|
|
138
|
+
}
|
|
139
|
+
lockPending(tx) {
|
|
140
|
+
return tx.select().from(this.table)
|
|
141
|
+
.where(isNull(this.table.publishedAt))
|
|
142
|
+
.orderBy(asc(this.table.id))
|
|
143
|
+
.limit(this.options.batchSize ?? 100)
|
|
144
|
+
.for('update', { skipLocked: true });
|
|
145
|
+
}
|
|
146
|
+
publish(row) {
|
|
147
|
+
const envelope = (typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload);
|
|
148
|
+
const options = row.correlationId ? { correlationId: row.correlationId } : {};
|
|
149
|
+
return row.exchange === 'commands'
|
|
150
|
+
? this.options.bus.sendEnvelope(envelope, options)
|
|
151
|
+
: this.options.bus.publishEnvelope(envelope, options);
|
|
152
|
+
}
|
|
153
|
+
schedule(delayMs) {
|
|
154
|
+
this.timer = setTimeout(() => {
|
|
155
|
+
void this.trigger().finally(() => { if (this.timer)
|
|
156
|
+
this.schedule(this.nextDelayMs()); });
|
|
157
|
+
}, delayMs);
|
|
110
158
|
}
|
|
111
159
|
/**
|
|
112
160
|
* Logged on the 1st, 2nd, 4th, 8th… failure of a row (the attempt count before this one is in `attempts`),
|
|
113
|
-
* so a queue that stays missing for hours is still visible without a line every
|
|
161
|
+
* so a queue that stays missing for hours is still visible without a line every poll.
|
|
114
162
|
*/
|
|
115
163
|
reportUnroutable(row, error) {
|
|
116
164
|
const attempt = Number(row.attempts ?? 0) + 1;
|
|
117
165
|
if ((attempt & (attempt - 1)) !== 0)
|
|
118
166
|
return;
|
|
119
167
|
this.logger.error('Outbox command is unroutable: its consumer queue is not declared; will retry', {
|
|
120
|
-
eventId: row.id, routingKey: row.
|
|
168
|
+
eventId: row.id, routingKey: row.routingKey, target: error.routingKey, attempt,
|
|
121
169
|
});
|
|
122
170
|
}
|
|
123
|
-
/** Deletes published rows older than `olderThanDays`. */
|
|
124
|
-
async prune(olderThanDays = 7) {
|
|
125
|
-
const cutoff = new Date(Date.now() - olderThanDays * 86_400_000);
|
|
126
|
-
return this.options.knex(this.table).whereNotNull('published_at').andWhere('published_at', '<', cutoff).delete();
|
|
127
|
-
}
|
|
128
171
|
}
|
|
129
172
|
//# sourceMappingURL=outbox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../../src/messaging/outbox.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outbox.js","sourceRoot":"","sources":["../../../src/messaging/outbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { ServiceUnavailableError } from '../common/errors.js';
|
|
2
2
|
import { newId } from '../common/id.js';
|
|
3
3
|
import { silentLogger } from '../logging/index.js';
|
|
4
|
-
import { buildEnvelope } from './envelope.js';
|
|
4
|
+
import { buildEnvelope, CORRELATION_HEADER, outgoingCorrelationId } from './envelope.js';
|
|
5
|
+
import { delayQueueName } from './topology.js';
|
|
5
6
|
/**
|
|
6
7
|
* The broker accepted a `mandatory` message but no queue was bound to receive it (the consumer has never
|
|
7
8
|
* started, or its binding is wrong). Retryable: the message is not lost — the caller (or the outbox relay)
|
|
@@ -16,16 +17,20 @@ export class UnroutableMessageError extends ServiceUnavailableError {
|
|
|
16
17
|
this.routingKey = routingKey;
|
|
17
18
|
}
|
|
18
19
|
}
|
|
20
|
+
/** True when `error` is an {@link UnroutableMessageError} (retryable; the target queue is missing). */
|
|
19
21
|
export function isUnroutableError(error) {
|
|
20
22
|
return error instanceof UnroutableMessageError;
|
|
21
23
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
/** Largest delay `scheduleDelayed` accepts (AMQP per-message TTL is a 32-bit value). */
|
|
25
|
+
export const MAX_DELAY_MS = 2 ** 31 - 1;
|
|
26
|
+
const envelopeProps = (envelope, mandatory, options) => ({
|
|
27
|
+
mandatory, messageId: envelope.data.eventId, correlationId: outgoingCorrelationId(options), type: envelope.routingKey,
|
|
24
28
|
});
|
|
25
29
|
/**
|
|
26
30
|
* Event and command publisher on a confirm channel: `publish()` resolves only once the broker
|
|
27
31
|
* has persisted the message, and rejects with 503 when the broker is unreachable so callers
|
|
28
|
-
* (or the outbox relay) can retry.
|
|
32
|
+
* (or the outbox relay) can retry. Every message is persistent JSON with `messageId` = event id and the
|
|
33
|
+
* correlation id in the `x-correlation-id` header.
|
|
29
34
|
*
|
|
30
35
|
* Commands and direct queue sends are `mandatory`: a command has exactly one owner, so one that no
|
|
31
36
|
* queue receives is a fault, not a no-op. The broker returns it (`basic.return`, always before its
|
|
@@ -38,6 +43,8 @@ export class MessagePublisher {
|
|
|
38
43
|
logger;
|
|
39
44
|
/** Message ids the broker returned as unroutable, awaiting their confirm. */
|
|
40
45
|
returned = new Set();
|
|
46
|
+
/** Delay queues already declared, per live channel (a reconnect gets a new channel). */
|
|
47
|
+
declaredDelayQueues = new WeakMap();
|
|
41
48
|
constructor(options) {
|
|
42
49
|
this.options = options;
|
|
43
50
|
this.logger = options.logger ?? silentLogger;
|
|
@@ -54,46 +61,86 @@ export class MessagePublisher {
|
|
|
54
61
|
});
|
|
55
62
|
}, { confirm: true });
|
|
56
63
|
}
|
|
57
|
-
async publish(
|
|
58
|
-
const envelope = buildEnvelope(
|
|
59
|
-
await this.publishEnvelope(envelope);
|
|
64
|
+
async publish(routingKey, data, meta = {}) {
|
|
65
|
+
const envelope = buildEnvelope(routingKey, data, meta);
|
|
66
|
+
await this.publishEnvelope(envelope, meta);
|
|
60
67
|
return envelope;
|
|
61
68
|
}
|
|
62
|
-
async publishEnvelope(envelope) {
|
|
63
|
-
await this.write(this.options.topology.eventsExchange, envelope.
|
|
69
|
+
async publishEnvelope(envelope, options) {
|
|
70
|
+
await this.write(this.options.topology.eventsExchange, envelope.routingKey, envelope, envelopeProps(envelope, false, options));
|
|
64
71
|
}
|
|
65
72
|
async send(command, data, meta = {}) {
|
|
66
|
-
const envelope = buildEnvelope(command, data,
|
|
67
|
-
await this.sendEnvelope(envelope);
|
|
73
|
+
const envelope = buildEnvelope(command, data, meta);
|
|
74
|
+
await this.sendEnvelope(envelope, meta);
|
|
68
75
|
return envelope;
|
|
69
76
|
}
|
|
70
|
-
async sendEnvelope(envelope) {
|
|
71
|
-
|
|
77
|
+
async sendEnvelope(envelope, options) {
|
|
78
|
+
const { topology } = this.options;
|
|
79
|
+
await this.write(topology.commandsExchange, topology.queue(envelope.routingKey), envelope, envelopeProps(envelope, true, options));
|
|
72
80
|
}
|
|
73
81
|
/**
|
|
74
82
|
* Sends `payload` as-is (no envelope) to `queue` through the default exchange. The queue is the
|
|
75
83
|
* consumer's to declare, so it is not asserted here; a missing queue rejects as unroutable.
|
|
76
84
|
*/
|
|
77
85
|
async sendToQueue(queue, payload, options = {}) {
|
|
78
|
-
await this.write('', queue, payload, { mandatory: true, messageId: options.messageId ?? newId(), correlationId: options
|
|
86
|
+
await this.write('', queue, payload, { mandatory: true, messageId: options.messageId ?? newId(), correlationId: outgoingCorrelationId(options) });
|
|
79
87
|
}
|
|
80
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Parks `payload` in a delay queue with a per-message TTL; when it expires the broker dead-letters it
|
|
90
|
+
* into `queue` (MSG-4). Delay queues are bucketed by power of two (see `delayQueueName`). The target
|
|
91
|
+
* queue must exist when the TTL fires, or the broker drops the message.
|
|
92
|
+
*
|
|
93
|
+
* @throws RangeError when `delayMs` is not an integer in 1 … {@link MAX_DELAY_MS}.
|
|
94
|
+
*/
|
|
95
|
+
async scheduleDelayed(queue, payload, delayMs, options = {}) {
|
|
96
|
+
if (!Number.isInteger(delayMs) || delayMs < 1 || delayMs > MAX_DELAY_MS) {
|
|
97
|
+
throw new RangeError('delayMs must be an integer between 1 and 2^31-1');
|
|
98
|
+
}
|
|
99
|
+
const delayQueue = await this.declareDelayQueue(queue, delayMs);
|
|
100
|
+
await this.write('', delayQueue, payload, {
|
|
101
|
+
mandatory: true, messageId: options.messageId ?? newId(), correlationId: outgoingCorrelationId(options), expiration: String(delayMs),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async declareDelayQueue(queue, delayMs) {
|
|
105
|
+
const channel = this.channel();
|
|
106
|
+
const name = delayQueueName(queue, delayMs);
|
|
107
|
+
const declared = this.declaredDelayQueues.get(channel) ?? new Set();
|
|
108
|
+
if (!declared.has(name)) {
|
|
109
|
+
await channel.assertQueue(name, { durable: true, arguments: { 'x-dead-letter-exchange': '', 'x-dead-letter-routing-key': queue } });
|
|
110
|
+
declared.add(name);
|
|
111
|
+
this.declaredDelayQueues.set(channel, declared);
|
|
112
|
+
}
|
|
113
|
+
return name;
|
|
114
|
+
}
|
|
115
|
+
channel() {
|
|
81
116
|
const channel = this.handle?.current();
|
|
82
|
-
if (!channel)
|
|
83
|
-
|
|
117
|
+
if (!channel)
|
|
118
|
+
throw new ServiceUnavailableError('Message broker unavailable');
|
|
119
|
+
return channel;
|
|
120
|
+
}
|
|
121
|
+
write(exchange, routingKey, body, props) {
|
|
122
|
+
let channel;
|
|
123
|
+
try {
|
|
124
|
+
channel = this.channel();
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return Promise.reject(error);
|
|
84
128
|
}
|
|
85
|
-
const { messageId } = props;
|
|
129
|
+
const { messageId, correlationId } = props;
|
|
130
|
+
const publishOptions = {
|
|
131
|
+
persistent: true,
|
|
132
|
+
mandatory: props.mandatory,
|
|
133
|
+
contentType: 'application/json',
|
|
134
|
+
messageId,
|
|
135
|
+
correlationId,
|
|
136
|
+
type: props.type,
|
|
137
|
+
expiration: props.expiration,
|
|
138
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
139
|
+
appId: this.options.source,
|
|
140
|
+
headers: correlationId ? { [CORRELATION_HEADER]: correlationId } : {},
|
|
141
|
+
};
|
|
86
142
|
return new Promise((resolve, reject) => {
|
|
87
|
-
channel.publish(exchange, routingKey, Buffer.from(JSON.stringify(body)), {
|
|
88
|
-
persistent: true,
|
|
89
|
-
mandatory: props.mandatory,
|
|
90
|
-
contentType: 'application/json',
|
|
91
|
-
messageId,
|
|
92
|
-
correlationId: props.correlationId,
|
|
93
|
-
type: props.type,
|
|
94
|
-
timestamp: Math.floor(Date.now() / 1000),
|
|
95
|
-
appId: this.options.source,
|
|
96
|
-
}, (error) => {
|
|
143
|
+
channel.publish(exchange, routingKey, Buffer.from(JSON.stringify(body)), publishOptions, (error) => {
|
|
97
144
|
// The broker sends `basic.return` before the confirm, so a returned message is already recorded here.
|
|
98
145
|
const unroutable = this.returned.delete(messageId);
|
|
99
146
|
if (error) {
|
|
@@ -113,30 +160,31 @@ export class MessagePublisher {
|
|
|
113
160
|
}
|
|
114
161
|
/** Stand-in when messaging is disabled (local tooling, tests): logs and drops. */
|
|
115
162
|
export class NoopMessageBus {
|
|
116
|
-
source;
|
|
117
163
|
logger;
|
|
118
|
-
constructor(
|
|
119
|
-
this.source = source;
|
|
164
|
+
constructor(logger = silentLogger) {
|
|
120
165
|
this.logger = logger;
|
|
121
166
|
}
|
|
122
|
-
async publish(
|
|
123
|
-
const envelope = buildEnvelope(
|
|
124
|
-
this.logger.debug('Messaging disabled; event dropped', {
|
|
167
|
+
async publish(routingKey, data, meta = {}) {
|
|
168
|
+
const envelope = buildEnvelope(routingKey, data, meta);
|
|
169
|
+
this.logger.debug('Messaging disabled; event dropped', { routingKey, eventId: envelope.data.eventId });
|
|
125
170
|
return envelope;
|
|
126
171
|
}
|
|
127
172
|
async publishEnvelope(envelope) {
|
|
128
|
-
this.logger.debug('Messaging disabled; event dropped', {
|
|
173
|
+
this.logger.debug('Messaging disabled; event dropped', { routingKey: envelope.routingKey, eventId: envelope.data.eventId });
|
|
129
174
|
}
|
|
130
175
|
async send(command, data, meta = {}) {
|
|
131
|
-
const envelope = buildEnvelope(command, data,
|
|
132
|
-
this.logger.debug('Messaging disabled; command dropped', { command, eventId: envelope.eventId });
|
|
176
|
+
const envelope = buildEnvelope(command, data, meta);
|
|
177
|
+
this.logger.debug('Messaging disabled; command dropped', { command, eventId: envelope.data.eventId });
|
|
133
178
|
return envelope;
|
|
134
179
|
}
|
|
135
180
|
async sendEnvelope(envelope) {
|
|
136
|
-
this.logger.debug('Messaging disabled; command dropped', { command: envelope.
|
|
181
|
+
this.logger.debug('Messaging disabled; command dropped', { command: envelope.routingKey, eventId: envelope.data.eventId });
|
|
137
182
|
}
|
|
138
183
|
async sendToQueue(queue) {
|
|
139
184
|
this.logger.debug('Messaging disabled; queue message dropped', { queue });
|
|
140
185
|
}
|
|
186
|
+
async scheduleDelayed(queue, _payload, delayMs) {
|
|
187
|
+
this.logger.debug('Messaging disabled; delayed message dropped', { queue, delayMs });
|
|
188
|
+
}
|
|
141
189
|
}
|
|
142
190
|
//# sourceMappingURL=publisher.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"publisher.js","sourceRoot":"","sources":["../../../src/messaging/publisher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,aAAa,EAAqB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"publisher.js","sourceRoot":"","sources":["../../../src/messaging/publisher.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,31 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Staff alerts (messaging layer helper): stage one notification dispatch per recipient of a
|
|
3
|
+
* permission through the caller's outbox. App vocabulary (category, channels, command name) is
|
|
4
|
+
* passed in as options; nothing app-specific is assumed.
|
|
5
|
+
*/
|
|
6
|
+
import { NOTIFICATION_DISPATCH, } from '../contracts/notification-dispatch.js';
|
|
3
7
|
import { silentLogger } from '../logging/index.js';
|
|
4
|
-
/** Recipients from configuration (env), keyed by the permission an alert targets. */
|
|
5
|
-
export class ConfiguredStaffRecipients {
|
|
6
|
-
byPermission;
|
|
7
|
-
constructor(byPermission) {
|
|
8
|
-
this.byPermission = byPermission;
|
|
9
|
-
}
|
|
10
|
-
async resolve(permission) {
|
|
11
|
-
return [...(this.byPermission[permission] ?? [])];
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
8
|
/**
|
|
15
|
-
* Stages one
|
|
16
|
-
* alert is sent if and only if the state change that raised it commits. Never sends
|
|
9
|
+
* Stages one dispatch command per recipient through the outbox, in the caller's transaction, so an
|
|
10
|
+
* alert is sent if and only if the state change that raised it commits. Never sends anything itself.
|
|
17
11
|
* An alert that resolves to nobody is logged as a warning (an unconfigured list must not fail silently).
|
|
12
|
+
* Staff alerts always bypass recipient preferences and record the target permission.
|
|
13
|
+
*
|
|
14
|
+
* @typeParam E The service's transaction/executor type.
|
|
15
|
+
* @typeParam C The app's dispatch categories.
|
|
16
|
+
* @typeParam Ch The app's dispatch channels.
|
|
17
|
+
* @example
|
|
18
|
+
* const notifier = new StaffNotifier(outbox, new ConfiguredStaffRecipients({ 'jobs.manage': config.opsEmails }),
|
|
19
|
+
* { category: 'staff', defaultChannels: ['email'], logger });
|
|
20
|
+
* await notifier.alert(tx, { template: 'job-failed', permission: 'jobs.manage', data: { reference } }, { correlationId });
|
|
18
21
|
*/
|
|
19
22
|
export class StaffNotifier {
|
|
20
23
|
outbox;
|
|
21
24
|
recipients;
|
|
25
|
+
category;
|
|
26
|
+
defaultChannels;
|
|
27
|
+
command;
|
|
22
28
|
logger;
|
|
23
|
-
constructor(outbox, recipients,
|
|
29
|
+
constructor(outbox, recipients, options) {
|
|
24
30
|
this.outbox = outbox;
|
|
25
31
|
this.recipients = recipients;
|
|
26
|
-
this.
|
|
32
|
+
this.category = options.category;
|
|
33
|
+
this.defaultChannels = options.defaultChannels;
|
|
34
|
+
this.command = options.command ?? NOTIFICATION_DISPATCH;
|
|
35
|
+
this.logger = options.logger ?? silentLogger;
|
|
27
36
|
}
|
|
28
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Stage the alert for every recipient of its permission.
|
|
39
|
+
*
|
|
40
|
+
* @param executor The caller's open transaction; the commands commit or roll back with it.
|
|
41
|
+
* @param alert Template, permission, variables and optional channels.
|
|
42
|
+
* @param meta Correlation id for tracing; the actor is always the system.
|
|
43
|
+
* @returns How many commands were staged (0 when nobody is configured).
|
|
44
|
+
*/
|
|
29
45
|
async alert(executor, alert, meta = {}) {
|
|
30
46
|
const targets = await this.recipients.resolve(alert.permission);
|
|
31
47
|
if (targets.length === 0) {
|
|
@@ -35,39 +51,16 @@ export class StaffNotifier {
|
|
|
35
51
|
for (const recipient of targets) {
|
|
36
52
|
const dispatch = {
|
|
37
53
|
template: alert.template,
|
|
38
|
-
category:
|
|
54
|
+
category: this.category,
|
|
39
55
|
recipient,
|
|
40
|
-
channels: alert.channels ??
|
|
56
|
+
channels: [...(alert.channels ?? this.defaultChannels)],
|
|
41
57
|
data: alert.data,
|
|
42
58
|
bypassPreferences: true,
|
|
43
59
|
targetPermission: alert.permission,
|
|
44
60
|
};
|
|
45
|
-
await this.outbox.command(executor,
|
|
61
|
+
await this.outbox.command(executor, this.command, dispatch, { ...meta, actorUserId: null });
|
|
46
62
|
}
|
|
47
63
|
return targets.length;
|
|
48
64
|
}
|
|
49
65
|
}
|
|
50
|
-
const emailSchema = z.string().email().max(DISPATCH_LIMITS.email);
|
|
51
|
-
/**
|
|
52
|
-
* A comma-separated staff address list from env (`"a@x.io, b@y.io"`): trimmed, blanks dropped,
|
|
53
|
-
* de-duplicated case-insensitively. An invalid address fails validation, so a typo is caught at boot
|
|
54
|
-
* instead of dead-lettering in notification-service when the alert finally fires.
|
|
55
|
-
*/
|
|
56
|
-
export const staffEmailListSchema = z
|
|
57
|
-
.string()
|
|
58
|
-
.optional()
|
|
59
|
-
.transform((csv) => {
|
|
60
|
-
const seen = new Set();
|
|
61
|
-
return (csv ?? '')
|
|
62
|
-
.split(',')
|
|
63
|
-
.map((entry) => entry.trim())
|
|
64
|
-
.filter((email) => email && !seen.has(email.toLowerCase()) && seen.add(email.toLowerCase()));
|
|
65
|
-
})
|
|
66
|
-
.superRefine((emails, ctx) => {
|
|
67
|
-
for (const email of emails) {
|
|
68
|
-
if (!emailSchema.safeParse(email).success)
|
|
69
|
-
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Not a valid email address: ${email}` });
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
.transform((emails) => emails.map((email) => ({ email })));
|
|
73
66
|
//# sourceMappingURL=staff-notifier.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staff-notifier.js","sourceRoot":"","sources":["../../../src/messaging/staff-notifier.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"staff-notifier.js","sourceRoot":"","sources":["../../../src/messaging/staff-notifier.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,qBAAqB,GAGtB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAsDnD;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,aAAa;IAOL;IACA;IAPF,QAAQ,CAAI;IACZ,eAAe,CAAyB;IACxC,OAAO,CAAS;IAChB,MAAM,CAAS;IAEhC,YACmB,MAAwB,EACxB,UAAkC,EACnD,OAAoC;QAFnB,WAAM,GAAN,MAAM,CAAkB;QACxB,eAAU,GAAV,UAAU,CAAwB;QAGnD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;QAC/C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,qBAAqB,CAAC;QACxD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,YAAY,CAAC;IAC/C,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,KAAK,CAAC,QAAW,EAAE,KAAqB,EAAE,OAAuB,EAAE;QACvE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAChE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oDAAoD,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;YACnI,OAAO,CAAC,CAAC;QACX,CAAC;QACD,KAAK,MAAM,SAAS,IAAI,OAAO,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAuC;gBACnD,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS;gBACT,QAAQ,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,eAAe,CAAC,CAAC;gBACvD,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,iBAAiB,EAAE,IAAI;gBACvB,gBAAgB,EAAE,KAAK,CAAC,UAAU;aACnC,CAAC;YACF,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9F,CAAC;QACD,OAAO,OAAO,CAAC,MAAM,CAAC;IACxB,CAAC;CACF"}
|