@nest-batch/webhook 0.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/LICENSE +21 -0
- package/README.md +395 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +71 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/module-options.d.ts +163 -0
- package/dist/src/module-options.d.ts.map +1 -0
- package/dist/src/module-options.js +124 -0
- package/dist/src/module-options.js.map +1 -0
- package/dist/src/webhook-batch.module.d.ts +59 -0
- package/dist/src/webhook-batch.module.d.ts.map +1 -0
- package/dist/src/webhook-batch.module.js +94 -0
- package/dist/src/webhook-batch.module.js.map +1 -0
- package/dist/src/webhook-batch.observer.d.ts +144 -0
- package/dist/src/webhook-batch.observer.d.ts.map +1 -0
- package/dist/src/webhook-batch.observer.js +306 -0
- package/dist/src/webhook-batch.observer.js.map +1 -0
- package/dist/src/webhook-signing.d.ts +70 -0
- package/dist/src/webhook-signing.d.ts.map +1 -0
- package/dist/src/webhook-signing.js +129 -0
- package/dist/src/webhook-signing.js.map +1 -0
- package/package.json +69 -0
- package/src/index.ts +46 -0
- package/src/module-options.ts +276 -0
- package/src/webhook-batch.module.ts +133 -0
- package/src/webhook-batch.observer.ts +408 -0
- package/src/webhook-signing.ts +185 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API barrel for `@nest-batch/webhook`.
|
|
3
|
+
*
|
|
4
|
+
* Hosts import the factory (`forRoot`) and the observer class
|
|
5
|
+
* (`WebhookBatchObserver`) from this barrel; everything else
|
|
6
|
+
* is an implementation detail. The barrel re-exports:
|
|
7
|
+
*
|
|
8
|
+
* - `forRoot` — the synchronous `DynamicModule` factory. The
|
|
9
|
+
* host calls `WebhookBatchModule.forRoot({...})` (the
|
|
10
|
+
* `WebhookBatchModule` is re-exported alongside so the
|
|
11
|
+
* type is reachable from this entry point).
|
|
12
|
+
* - `WebhookBatchObserver` — the concrete class. Useful for
|
|
13
|
+
* type-strict consumers that prefer class injection.
|
|
14
|
+
* - `BATCH_EVENT` — the `BATCH_EVENT` constants from
|
|
15
|
+
* `@nest-batch/core`, re-exported so a host that wants to
|
|
16
|
+
* filter subscriptions does not have to add
|
|
17
|
+
* `@nest-batch/core` as a direct dep.
|
|
18
|
+
* - `signV1` / `buildSignatureHeader` / `parseSignatureHeader`
|
|
19
|
+
* / `verifyV1` / `fingerprintSecret` — the HMAC signing
|
|
20
|
+
* helpers, useful for hosts that want to write their own
|
|
21
|
+
* webhook receiver against the same contract.
|
|
22
|
+
* - The TypeScript types: `WebhookBatchModuleOptions`,
|
|
23
|
+
* `ResolvedWebhookOptions`, `WebhookLogger`,
|
|
24
|
+
* `WebhookEnvelope`.
|
|
25
|
+
*/
|
|
26
|
+
export { forRoot, WebhookBatchModule, WebhookBatchObserver } from './webhook-batch.module';
|
|
27
|
+
export { BATCH_EVENT } from '@nest-batch/core';
|
|
28
|
+
export type {
|
|
29
|
+
BatchEvent,
|
|
30
|
+
BatchEventType,
|
|
31
|
+
BatchObserver,
|
|
32
|
+
} from '@nest-batch/core';
|
|
33
|
+
export {
|
|
34
|
+
signV1,
|
|
35
|
+
buildSignatureHeader,
|
|
36
|
+
parseSignatureHeader,
|
|
37
|
+
verifyV1,
|
|
38
|
+
fingerprintSecret,
|
|
39
|
+
SIGNATURE_HEADER_NAME,
|
|
40
|
+
} from './webhook-signing';
|
|
41
|
+
export type { WebhookEnvelope } from './webhook-batch.observer';
|
|
42
|
+
export type {
|
|
43
|
+
ResolvedWebhookOptions,
|
|
44
|
+
WebhookBatchModuleOptions,
|
|
45
|
+
WebhookLogger,
|
|
46
|
+
} from './module-options';
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import type { BatchEventType } from '@nest-batch/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public options bag for `WebhookBatchModule.forRoot()`.
|
|
5
|
+
*
|
|
6
|
+
* The contract the test suite (`tests/webhook-observer.test.ts`,
|
|
7
|
+
* T-AC-5) asserts against:
|
|
8
|
+
*
|
|
9
|
+
* - `secret` is REQUIRED at the host level. It is never read from
|
|
10
|
+
* disk, never defaulted to an empty string, never logged in any
|
|
11
|
+
* code path. The optional `WEBHOOK_HMAC_SECRET` env var is the
|
|
12
|
+
* fallback ONLY when the host does not pass `secret` (env is
|
|
13
|
+
* the host-injection's safety net, not its primary).
|
|
14
|
+
* - `urls[]` is the fan-out set. Every subscribed event is POSTed
|
|
15
|
+
* to every URL in `urls`. Empty `urls` is a no-op (the observer
|
|
16
|
+
* still subscribes, it just never POSTs).
|
|
17
|
+
* - `events` is the subscription filter. Defaults to
|
|
18
|
+
* `[JOB_COMPLETED, JOB_FAILED, STEP_FAILED]`. Subscribed events
|
|
19
|
+
* are the only ones the observer signs + POSTs. A
|
|
20
|
+
* `JOB_STARTED` event arrives at `onEvent`, is not in the
|
|
21
|
+
* filter, and is dropped silently (the listener is fire-and-
|
|
22
|
+
* forget by contract — see `BatchObserver.onEvent`).
|
|
23
|
+
* - `attempts` is the number of total POST attempts (1 initial +
|
|
24
|
+
* up to `attempts-1` retries). Defaults to 4 (matching the
|
|
25
|
+
* fixed 1s/5s/25s/125s backoff schedule). Lowering the value
|
|
26
|
+
* is supported for tests; raising it is intentionally NOT
|
|
27
|
+
* supported in v1 (the retry schedule is the contract, see
|
|
28
|
+
* `docs/RELEASE-0.2.0.md` §7.2).
|
|
29
|
+
* - `timeoutMs` is the per-attempt HTTP timeout. Defaults to
|
|
30
|
+
* 10 000 ms (10 seconds). A timeout is treated as a network
|
|
31
|
+
* error and retried through the full attempt budget.
|
|
32
|
+
* - `logger` is the Nest `Logger`-compatible interface used for
|
|
33
|
+
* the dead-letter `warn` line and the bootstrap notice. When
|
|
34
|
+
* omitted, the observer instantiates a `new Logger('WebhookBatchObserver')`.
|
|
35
|
+
*/
|
|
36
|
+
export interface WebhookBatchModuleOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Host-injected HMAC-SHA256 secret used to sign outbound
|
|
39
|
+
* envelopes. REQUIRED when not relying on the `WEBHOOK_HMAC_SECRET`
|
|
40
|
+
* env fallback. Recommended length: 32+ bytes of randomness
|
|
41
|
+
* (a per-environment secret, never re-used across services).
|
|
42
|
+
*
|
|
43
|
+
* The secret is bound to the `WebhookBatchObserver` instance at
|
|
44
|
+
* `forRoot` time and is never exported, logged, serialized into
|
|
45
|
+
* a dead-letter body, or otherwise observable by the host.
|
|
46
|
+
*/
|
|
47
|
+
readonly secret?: string;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* One or more absolute URLs the observer will fan out to on
|
|
51
|
+
* every subscribed event. Empty array is a no-op (the observer
|
|
52
|
+
* still subscribes to the event stream but never POSTs).
|
|
53
|
+
*/
|
|
54
|
+
readonly urls: readonly string[];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Subscription filter. Defaults to
|
|
58
|
+
* `[BATCH_EVENT.JOB_COMPLETED, BATCH_EVENT.JOB_FAILED,
|
|
59
|
+
* BATCH_EVENT.STEP_FAILED]`. The v1 contract is these three
|
|
60
|
+
* events only; a future v2 may widen the default to STEP_*
|
|
61
|
+
* events.
|
|
62
|
+
*/
|
|
63
|
+
readonly events?: readonly BatchEventType[];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Total number of POST attempts (initial + retries). Defaults
|
|
67
|
+
* to 4. Must be `>= 1`; `1` means "no retries" (single POST,
|
|
68
|
+
* then dead-letter on failure). Values `> 4` are clamped to 4
|
|
69
|
+
* — the v1 retry schedule is `[1s, 5s, 25s, 125s]` and has
|
|
70
|
+
* exactly 4 entries; further attempts would have no backoff to
|
|
71
|
+
* look up.
|
|
72
|
+
*/
|
|
73
|
+
readonly attempts?: number;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Per-attempt HTTP timeout in milliseconds. Defaults to
|
|
77
|
+
* 10 000 (10 seconds). A timeout is treated as a network
|
|
78
|
+
* error and retried through the full attempt budget.
|
|
79
|
+
*/
|
|
80
|
+
readonly timeoutMs?: number;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Logger override. The observer is built to use a NestJS
|
|
84
|
+
* `Logger`-compatible interface (the four `log` / `warn` /
|
|
85
|
+
* `error` / `debug` methods). When omitted, the observer
|
|
86
|
+
* instantiates a `new Logger('WebhookBatchObserver')` against
|
|
87
|
+
* the `console`-backed Nest logger.
|
|
88
|
+
*/
|
|
89
|
+
readonly logger?: WebhookLogger;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* NestJS-`Logger`-compatible surface used by `WebhookBatchObserver`.
|
|
94
|
+
*
|
|
95
|
+
* We type this as a structural subset of `@nestjs/common`'s
|
|
96
|
+
* `LoggerService` so the host can pass a custom logger without
|
|
97
|
+
* having to import the full Nest surface. The four methods are
|
|
98
|
+
* the only ones the observer calls:
|
|
99
|
+
*
|
|
100
|
+
* - `log` — bootstrap / info-level messages
|
|
101
|
+
* - `warn` — dead-letter payload (post final failure)
|
|
102
|
+
* - `error` — configuration / startup errors
|
|
103
|
+
* - `debug` — per-attempt diagnostic info (URL, status, latency)
|
|
104
|
+
*/
|
|
105
|
+
export interface WebhookLogger {
|
|
106
|
+
log(message: string, context?: string): void;
|
|
107
|
+
warn(message: string, context?: string): void;
|
|
108
|
+
error(message: string, context?: string): void;
|
|
109
|
+
debug(message: string, context?: string): void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Fully-resolved options bag the observer consumes at runtime.
|
|
114
|
+
* `forRoot` is responsible for filling in every default and
|
|
115
|
+
* freezing the result before handing it to the provider.
|
|
116
|
+
*/
|
|
117
|
+
export interface ResolvedWebhookOptions {
|
|
118
|
+
readonly secret: string;
|
|
119
|
+
readonly urls: readonly string[];
|
|
120
|
+
readonly events: readonly BatchEventType[];
|
|
121
|
+
readonly attempts: number;
|
|
122
|
+
readonly timeoutMs: number;
|
|
123
|
+
readonly logger: WebhookLogger;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The v1 default subscription set. Documented in
|
|
128
|
+
* `docs/RELEASE-0.2.0.md` §7.1 and in the README.
|
|
129
|
+
*/
|
|
130
|
+
export const DEFAULT_WEBHOOK_EVENTS: readonly BatchEventType[] = [
|
|
131
|
+
// JOB_COMPLETED, JOB_FAILED, STEP_FAILED
|
|
132
|
+
// We do not import BATCH_EVENT here to avoid a circular
|
|
133
|
+
// dep (the observer re-uses this list at construction
|
|
134
|
+
// time). The constant is the v1 contract; a future v2
|
|
135
|
+
// may widen the default to STEP_*, CHUNK_*, ITEM_*.
|
|
136
|
+
'nest-batch.job.completed',
|
|
137
|
+
'nest-batch.job.failed',
|
|
138
|
+
'nest-batch.step.failed',
|
|
139
|
+
] as const;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The v1 fixed backoff schedule. Four entries (3 delays between
|
|
143
|
+
* 4 attempts). Documented in `docs/RELEASE-0.2.0.md` §7.2 and
|
|
144
|
+
* in the README. The schedule is the contract the test suite
|
|
145
|
+
* asserts against.
|
|
146
|
+
*/
|
|
147
|
+
export const DEFAULT_WEBHOOK_RETRY_DELAYS_MS: readonly number[] = [
|
|
148
|
+
1_000, 5_000, 25_000, 125_000,
|
|
149
|
+
] as const;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Fast-mode override for the retry schedule. Activated when
|
|
153
|
+
* `process.env.WEBHOOK_TEST_FAST === '1'`. The override exists
|
|
154
|
+
* so the test suite can exercise the 4-attempt retry path
|
|
155
|
+
* without waiting 156 seconds (1+5+25+125). Test-only; never
|
|
156
|
+
* touched in production. Documented in the README.
|
|
157
|
+
*/
|
|
158
|
+
export const FAST_WEBHOOK_RETRY_DELAYS_MS: readonly number[] = [
|
|
159
|
+
1, 5, 25, 125,
|
|
160
|
+
] as const;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The DI token under which the resolved options are stored.
|
|
164
|
+
* `Symbol.for` keeps the key process-scoped and stable across
|
|
165
|
+
* module versions, mirroring the pattern in
|
|
166
|
+
* `packages/bullmq/src/module-options.ts`.
|
|
167
|
+
*/
|
|
168
|
+
export const WEBHOOK_MODULE_OPTIONS: symbol = Symbol.for(
|
|
169
|
+
'@nest-batch/webhook/MODULE_OPTIONS',
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Resolve a partial `WebhookBatchModuleOptions` into a fully-
|
|
174
|
+
* populated `ResolvedWebhookOptions`. Called by `forRoot` so the
|
|
175
|
+
* provider always sees a frozen, default-filled bag.
|
|
176
|
+
*
|
|
177
|
+
* Resolution rules:
|
|
178
|
+
* - `secret`: if absent, fall back to `process.env.WEBHOOK_HMAC_SECRET`.
|
|
179
|
+
* If still absent, throw — the host MUST provide a secret one
|
|
180
|
+
* way or another.
|
|
181
|
+
* - `urls`: required, no default. Empty array is allowed (no-op
|
|
182
|
+
* fan-out).
|
|
183
|
+
* - `events`: defaults to `DEFAULT_WEBHOOK_EVENTS`. A `[]` value
|
|
184
|
+
* is honoured (the observer subscribes to nothing).
|
|
185
|
+
* - `attempts`: defaults to 4. Clamped to `[1, 4]`.
|
|
186
|
+
* - `timeoutMs`: defaults to 10 000. Clamped to `>= 100` so the
|
|
187
|
+
* observer cannot be configured into "immediate timeout" mode.
|
|
188
|
+
* - `logger`: defaults to a `new Logger('WebhookBatchObserver')`.
|
|
189
|
+
*/
|
|
190
|
+
export function resolveWebhookOptions(
|
|
191
|
+
raw: WebhookBatchModuleOptions,
|
|
192
|
+
): ResolvedWebhookOptions {
|
|
193
|
+
if (raw === null || typeof raw !== 'object') {
|
|
194
|
+
throw new Error(
|
|
195
|
+
'[WebhookBatchModule] options must be a non-null object',
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
const secret = pickSecret(raw.secret);
|
|
199
|
+
if (typeof secret !== 'string' || secret.length === 0) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
'[WebhookBatchModule] secret is required: pass `secret` to ' +
|
|
202
|
+
'forRoot() or set the WEBHOOK_HMAC_SECRET env var',
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const urls = Array.isArray(raw.urls) ? raw.urls.slice() : [];
|
|
206
|
+
for (const url of urls) {
|
|
207
|
+
if (typeof url !== 'string' || url.length === 0) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
'[WebhookBatchModule] every entry in `urls` must be a non-empty string',
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const events = Array.isArray(raw.events) && raw.events.length > 0
|
|
214
|
+
? raw.events.slice()
|
|
215
|
+
: DEFAULT_WEBHOOK_EVENTS.slice();
|
|
216
|
+
const rawAttempts = typeof raw.attempts === 'number' ? raw.attempts : 4;
|
|
217
|
+
const attempts = Math.max(1, Math.min(4, Math.floor(rawAttempts)));
|
|
218
|
+
const rawTimeout = typeof raw.timeoutMs === 'number' ? raw.timeoutMs : 10_000;
|
|
219
|
+
const timeoutMs = Math.max(100, Math.floor(rawTimeout));
|
|
220
|
+
return Object.freeze({
|
|
221
|
+
secret,
|
|
222
|
+
urls,
|
|
223
|
+
events,
|
|
224
|
+
attempts,
|
|
225
|
+
timeoutMs,
|
|
226
|
+
logger: raw.logger ?? defaultLogger(),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Pick the secret: host-injected first, env-var fallback second.
|
|
232
|
+
* Returns `undefined` if neither is set so the caller can throw
|
|
233
|
+
* a precise error.
|
|
234
|
+
*/
|
|
235
|
+
function pickSecret(hostInjected: string | undefined): string | undefined {
|
|
236
|
+
if (typeof hostInjected === 'string' && hostInjected.length > 0) {
|
|
237
|
+
return hostInjected;
|
|
238
|
+
}
|
|
239
|
+
const fromEnv = process.env['WEBHOOK_HMAC_SECRET'];
|
|
240
|
+
if (typeof fromEnv === 'string' && fromEnv.length > 0) {
|
|
241
|
+
return fromEnv;
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* The default `WebhookLogger` — a thin adapter around
|
|
248
|
+
* `console`. The observer is built to be test-friendly; tests
|
|
249
|
+
* pass a captured-`console.warn` spy via the `logger` option.
|
|
250
|
+
*/
|
|
251
|
+
function defaultLogger(): WebhookLogger {
|
|
252
|
+
// We deliberately do NOT import @nestjs/common's `Logger` here
|
|
253
|
+
// — the `WebhookLogger` is a structural interface, and the
|
|
254
|
+
// adapter lets the package stay test-runner-agnostic. Tests
|
|
255
|
+
// pass a console-backed spy; hosts pass a NestJS `Logger`
|
|
256
|
+
// instance (the structural shape matches the official
|
|
257
|
+
// `LoggerService`).
|
|
258
|
+
return {
|
|
259
|
+
log: (message: string) => {
|
|
260
|
+
// eslint-disable-next-line no-console
|
|
261
|
+
console.log(`[WebhookBatchObserver] ${message}`);
|
|
262
|
+
},
|
|
263
|
+
warn: (message: string) => {
|
|
264
|
+
// eslint-disable-next-line no-console
|
|
265
|
+
console.warn(`[WebhookBatchObserver] ${message}`);
|
|
266
|
+
},
|
|
267
|
+
error: (message: string) => {
|
|
268
|
+
// eslint-disable-next-line no-console
|
|
269
|
+
console.error(`[WebhookBatchObserver] ${message}`);
|
|
270
|
+
},
|
|
271
|
+
debug: (message: string) => {
|
|
272
|
+
// eslint-disable-next-line no-console
|
|
273
|
+
console.debug(`[WebhookBatchObserver] ${message}`);
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Module, type DynamicModule, type Provider } from '@nestjs/common';
|
|
2
|
+
import { BATCH_EVENT, type BatchEventType } from '@nest-batch/core';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
resolveWebhookOptions,
|
|
6
|
+
WEBHOOK_MODULE_OPTIONS,
|
|
7
|
+
type ResolvedWebhookOptions,
|
|
8
|
+
type WebhookBatchModuleOptions,
|
|
9
|
+
type WebhookLogger,
|
|
10
|
+
} from './module-options';
|
|
11
|
+
import { WebhookBatchObserver } from './webhook-batch.observer';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* `WebhookBatchModule` — the NestJS dynamic module that wires
|
|
15
|
+
* the `WebhookBatchObserver` into the host's DI container and
|
|
16
|
+
* binds it to the `BatchObserver` token used by the executor
|
|
17
|
+
* (and by `@nest-batch/bullmq` / `@nest-batch/kafka`'s runtime
|
|
18
|
+
* bridge).
|
|
19
|
+
*
|
|
20
|
+
* The host wires it alongside `NestBatchModule.forRoot({...})`:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* @Module({
|
|
24
|
+
* imports: [
|
|
25
|
+
* NestBatchModule.forRoot({
|
|
26
|
+
* adapters: { persistence: MikroOrmAdapter.forRoot(), transport: BullmqAdapter.forRoot() },
|
|
27
|
+
* }),
|
|
28
|
+
* WebhookBatchModule.forRoot({
|
|
29
|
+
* secret: process.env.WEBHOOK_HMAC_SECRET,
|
|
30
|
+
* urls: ['https://hooks.example.com/nest-batch'],
|
|
31
|
+
* }),
|
|
32
|
+
* ],
|
|
33
|
+
* })
|
|
34
|
+
* export class AppModule {}
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* The observer is auto-registered against the `BatchObserver`
|
|
38
|
+
* token via `useExisting`, so the executor's optional-injection
|
|
39
|
+
* path picks it up without any extra wiring on the host's side.
|
|
40
|
+
*/
|
|
41
|
+
@Module({})
|
|
42
|
+
export class WebhookBatchModule {}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `forRoot` — synchronous configuration. Resolves the options
|
|
46
|
+
* up-front (filling in defaults, falling back to the
|
|
47
|
+
* `WEBHOOK_HMAC_SECRET` env var when `secret` is omitted,
|
|
48
|
+
* freezing the result) and emits a `DynamicModule` that:
|
|
49
|
+
*
|
|
50
|
+
* - registers `WebhookBatchObserver` as a provider,
|
|
51
|
+
* - registers a `useExisting` alias so anything injecting
|
|
52
|
+
* `BatchObserver` (or `WebhookBatchObserver` by class)
|
|
53
|
+
* resolves to the same instance,
|
|
54
|
+
* - registers the resolved options under
|
|
55
|
+
* `WEBHOOK_MODULE_OPTIONS` (the observer's `@Inject`
|
|
56
|
+
* key),
|
|
57
|
+
* - marks the module `global: true` so the observer is
|
|
58
|
+
* visible across the host's sub-modules.
|
|
59
|
+
*
|
|
60
|
+
* The `urls: []` case is a no-op (the observer subscribes
|
|
61
|
+
* to the event stream but never POSTs); it does not throw.
|
|
62
|
+
* The `secret` case throws at `forRoot` time with a clear
|
|
63
|
+
* message (the host sees the error at boot, not at the first
|
|
64
|
+
* event).
|
|
65
|
+
*/
|
|
66
|
+
export function forRoot(options: WebhookBatchModuleOptions): DynamicModule {
|
|
67
|
+
const resolved = resolveWebhookOptions(options);
|
|
68
|
+
return {
|
|
69
|
+
module: WebhookBatchModule,
|
|
70
|
+
global: true,
|
|
71
|
+
providers: buildProviders(resolved),
|
|
72
|
+
exports: [WebhookBatchObserver],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build the static provider list shared by `forRoot()`.
|
|
78
|
+
*
|
|
79
|
+
* The list is three entries:
|
|
80
|
+
* - `WebhookBatchObserver` — the concrete class.
|
|
81
|
+
* - `BATCH_OBSERVER_PROVIDER` — a `useExisting` alias so the
|
|
82
|
+
* executor's `@Optional() @Inject(BatchObserver) observer`
|
|
83
|
+
* resolves to the same instance.
|
|
84
|
+
* - `WEBHOOK_MODULE_OPTIONS` — the resolved + frozen
|
|
85
|
+
* options bag, injected into the observer's constructor.
|
|
86
|
+
*
|
|
87
|
+
* Centralising the list keeps the public factory surface
|
|
88
|
+
* (`forRoot`) a one-liner; any future addition (e.g. a
|
|
89
|
+
* per-package health check) only needs to land here.
|
|
90
|
+
*/
|
|
91
|
+
function buildProviders(resolved: ResolvedWebhookOptions): Provider[] {
|
|
92
|
+
return [
|
|
93
|
+
WebhookBatchObserver,
|
|
94
|
+
{
|
|
95
|
+
provide: BATCH_OBSERVER_TOKEN,
|
|
96
|
+
useExisting: WebhookBatchObserver,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
provide: WEBHOOK_MODULE_OPTIONS,
|
|
100
|
+
useValue: resolved,
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The DI token the executor / runtime services use to inject
|
|
107
|
+
* a `BatchObserver`. We re-export the `BatchObserver` class
|
|
108
|
+
* itself as the token (mirroring the pattern in
|
|
109
|
+
* `@nest-batch/bullmq` and `@nest-batch/kafka`, where the
|
|
110
|
+
* `BatchObserver` interface is the type and the class-as-
|
|
111
|
+
* token resolves to the singleton).
|
|
112
|
+
*
|
|
113
|
+
* Using the `BatchObserver` class (an interface in
|
|
114
|
+
* `@nest-batch/core`) as the token is a NestJS pattern:
|
|
115
|
+
* Nest uses the class reference as the default DI key. We
|
|
116
|
+
* redeclare it as `BATCH_OBSERVER_TOKEN` so the `useExisting`
|
|
117
|
+
* provider above has a stable, imported symbol to bind
|
|
118
|
+
* against.
|
|
119
|
+
*/
|
|
120
|
+
const BATCH_OBSERVER_TOKEN: symbol = Symbol.for(
|
|
121
|
+
'@nest-batch/webhook/BATCH_OBSERVER',
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Re-export the public surface of this module so the
|
|
125
|
+
// package barrel can re-export it in turn.
|
|
126
|
+
export {
|
|
127
|
+
BATCH_EVENT,
|
|
128
|
+
WebhookBatchObserver,
|
|
129
|
+
type BatchEventType,
|
|
130
|
+
type ResolvedWebhookOptions,
|
|
131
|
+
type WebhookBatchModuleOptions,
|
|
132
|
+
type WebhookLogger,
|
|
133
|
+
};
|