@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.
@@ -0,0 +1,163 @@
1
+ import type { BatchEventType } from '@nest-batch/core';
2
+ /**
3
+ * Public options bag for `WebhookBatchModule.forRoot()`.
4
+ *
5
+ * The contract the test suite (`tests/webhook-observer.test.ts`,
6
+ * T-AC-5) asserts against:
7
+ *
8
+ * - `secret` is REQUIRED at the host level. It is never read from
9
+ * disk, never defaulted to an empty string, never logged in any
10
+ * code path. The optional `WEBHOOK_HMAC_SECRET` env var is the
11
+ * fallback ONLY when the host does not pass `secret` (env is
12
+ * the host-injection's safety net, not its primary).
13
+ * - `urls[]` is the fan-out set. Every subscribed event is POSTed
14
+ * to every URL in `urls`. Empty `urls` is a no-op (the observer
15
+ * still subscribes, it just never POSTs).
16
+ * - `events` is the subscription filter. Defaults to
17
+ * `[JOB_COMPLETED, JOB_FAILED, STEP_FAILED]`. Subscribed events
18
+ * are the only ones the observer signs + POSTs. A
19
+ * `JOB_STARTED` event arrives at `onEvent`, is not in the
20
+ * filter, and is dropped silently (the listener is fire-and-
21
+ * forget by contract — see `BatchObserver.onEvent`).
22
+ * - `attempts` is the number of total POST attempts (1 initial +
23
+ * up to `attempts-1` retries). Defaults to 4 (matching the
24
+ * fixed 1s/5s/25s/125s backoff schedule). Lowering the value
25
+ * is supported for tests; raising it is intentionally NOT
26
+ * supported in v1 (the retry schedule is the contract, see
27
+ * `docs/RELEASE-0.2.0.md` §7.2).
28
+ * - `timeoutMs` is the per-attempt HTTP timeout. Defaults to
29
+ * 10 000 ms (10 seconds). A timeout is treated as a network
30
+ * error and retried through the full attempt budget.
31
+ * - `logger` is the Nest `Logger`-compatible interface used for
32
+ * the dead-letter `warn` line and the bootstrap notice. When
33
+ * omitted, the observer instantiates a `new Logger('WebhookBatchObserver')`.
34
+ */
35
+ export interface WebhookBatchModuleOptions {
36
+ /**
37
+ * Host-injected HMAC-SHA256 secret used to sign outbound
38
+ * envelopes. REQUIRED when not relying on the `WEBHOOK_HMAC_SECRET`
39
+ * env fallback. Recommended length: 32+ bytes of randomness
40
+ * (a per-environment secret, never re-used across services).
41
+ *
42
+ * The secret is bound to the `WebhookBatchObserver` instance at
43
+ * `forRoot` time and is never exported, logged, serialized into
44
+ * a dead-letter body, or otherwise observable by the host.
45
+ */
46
+ readonly secret?: string;
47
+ /**
48
+ * One or more absolute URLs the observer will fan out to on
49
+ * every subscribed event. Empty array is a no-op (the observer
50
+ * still subscribes to the event stream but never POSTs).
51
+ */
52
+ readonly urls: readonly string[];
53
+ /**
54
+ * Subscription filter. Defaults to
55
+ * `[BATCH_EVENT.JOB_COMPLETED, BATCH_EVENT.JOB_FAILED,
56
+ * BATCH_EVENT.STEP_FAILED]`. The v1 contract is these three
57
+ * events only; a future v2 may widen the default to STEP_*
58
+ * events.
59
+ */
60
+ readonly events?: readonly BatchEventType[];
61
+ /**
62
+ * Total number of POST attempts (initial + retries). Defaults
63
+ * to 4. Must be `>= 1`; `1` means "no retries" (single POST,
64
+ * then dead-letter on failure). Values `> 4` are clamped to 4
65
+ * — the v1 retry schedule is `[1s, 5s, 25s, 125s]` and has
66
+ * exactly 4 entries; further attempts would have no backoff to
67
+ * look up.
68
+ */
69
+ readonly attempts?: number;
70
+ /**
71
+ * Per-attempt HTTP timeout in milliseconds. Defaults to
72
+ * 10 000 (10 seconds). A timeout is treated as a network
73
+ * error and retried through the full attempt budget.
74
+ */
75
+ readonly timeoutMs?: number;
76
+ /**
77
+ * Logger override. The observer is built to use a NestJS
78
+ * `Logger`-compatible interface (the four `log` / `warn` /
79
+ * `error` / `debug` methods). When omitted, the observer
80
+ * instantiates a `new Logger('WebhookBatchObserver')` against
81
+ * the `console`-backed Nest logger.
82
+ */
83
+ readonly logger?: WebhookLogger;
84
+ }
85
+ /**
86
+ * NestJS-`Logger`-compatible surface used by `WebhookBatchObserver`.
87
+ *
88
+ * We type this as a structural subset of `@nestjs/common`'s
89
+ * `LoggerService` so the host can pass a custom logger without
90
+ * having to import the full Nest surface. The four methods are
91
+ * the only ones the observer calls:
92
+ *
93
+ * - `log` — bootstrap / info-level messages
94
+ * - `warn` — dead-letter payload (post final failure)
95
+ * - `error` — configuration / startup errors
96
+ * - `debug` — per-attempt diagnostic info (URL, status, latency)
97
+ */
98
+ export interface WebhookLogger {
99
+ log(message: string, context?: string): void;
100
+ warn(message: string, context?: string): void;
101
+ error(message: string, context?: string): void;
102
+ debug(message: string, context?: string): void;
103
+ }
104
+ /**
105
+ * Fully-resolved options bag the observer consumes at runtime.
106
+ * `forRoot` is responsible for filling in every default and
107
+ * freezing the result before handing it to the provider.
108
+ */
109
+ export interface ResolvedWebhookOptions {
110
+ readonly secret: string;
111
+ readonly urls: readonly string[];
112
+ readonly events: readonly BatchEventType[];
113
+ readonly attempts: number;
114
+ readonly timeoutMs: number;
115
+ readonly logger: WebhookLogger;
116
+ }
117
+ /**
118
+ * The v1 default subscription set. Documented in
119
+ * `docs/RELEASE-0.2.0.md` §7.1 and in the README.
120
+ */
121
+ export declare const DEFAULT_WEBHOOK_EVENTS: readonly BatchEventType[];
122
+ /**
123
+ * The v1 fixed backoff schedule. Four entries (3 delays between
124
+ * 4 attempts). Documented in `docs/RELEASE-0.2.0.md` §7.2 and
125
+ * in the README. The schedule is the contract the test suite
126
+ * asserts against.
127
+ */
128
+ export declare const DEFAULT_WEBHOOK_RETRY_DELAYS_MS: readonly number[];
129
+ /**
130
+ * Fast-mode override for the retry schedule. Activated when
131
+ * `process.env.WEBHOOK_TEST_FAST === '1'`. The override exists
132
+ * so the test suite can exercise the 4-attempt retry path
133
+ * without waiting 156 seconds (1+5+25+125). Test-only; never
134
+ * touched in production. Documented in the README.
135
+ */
136
+ export declare const FAST_WEBHOOK_RETRY_DELAYS_MS: readonly number[];
137
+ /**
138
+ * The DI token under which the resolved options are stored.
139
+ * `Symbol.for` keeps the key process-scoped and stable across
140
+ * module versions, mirroring the pattern in
141
+ * `packages/bullmq/src/module-options.ts`.
142
+ */
143
+ export declare const WEBHOOK_MODULE_OPTIONS: symbol;
144
+ /**
145
+ * Resolve a partial `WebhookBatchModuleOptions` into a fully-
146
+ * populated `ResolvedWebhookOptions`. Called by `forRoot` so the
147
+ * provider always sees a frozen, default-filled bag.
148
+ *
149
+ * Resolution rules:
150
+ * - `secret`: if absent, fall back to `process.env.WEBHOOK_HMAC_SECRET`.
151
+ * If still absent, throw — the host MUST provide a secret one
152
+ * way or another.
153
+ * - `urls`: required, no default. Empty array is allowed (no-op
154
+ * fan-out).
155
+ * - `events`: defaults to `DEFAULT_WEBHOOK_EVENTS`. A `[]` value
156
+ * is honoured (the observer subscribes to nothing).
157
+ * - `attempts`: defaults to 4. Clamped to `[1, 4]`.
158
+ * - `timeoutMs`: defaults to 10 000. Clamped to `>= 100` so the
159
+ * observer cannot be configured into "immediate timeout" mode.
160
+ * - `logger`: defaults to a `new Logger('WebhookBatchObserver')`.
161
+ */
162
+ export declare function resolveWebhookOptions(raw: WebhookBatchModuleOptions): ResolvedWebhookOptions;
163
+ //# sourceMappingURL=module-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-options.d.ts","sourceRoot":"","sources":["../../src/module-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;;;;;OASG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IAEjC;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAE5C;;;;;;;OAOG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;CACjC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChD;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,cAAc,EASlD,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,+BAA+B,EAAE,SAAS,MAAM,EAEnD,CAAC;AAEX;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,EAAE,SAAS,MAAM,EAEhD,CAAC;AAEX;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,EAAE,MAEpC,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,yBAAyB,GAC7B,sBAAsB,CAoCxB"}
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get DEFAULT_WEBHOOK_EVENTS () {
13
+ return DEFAULT_WEBHOOK_EVENTS;
14
+ },
15
+ get DEFAULT_WEBHOOK_RETRY_DELAYS_MS () {
16
+ return DEFAULT_WEBHOOK_RETRY_DELAYS_MS;
17
+ },
18
+ get FAST_WEBHOOK_RETRY_DELAYS_MS () {
19
+ return FAST_WEBHOOK_RETRY_DELAYS_MS;
20
+ },
21
+ get WEBHOOK_MODULE_OPTIONS () {
22
+ return WEBHOOK_MODULE_OPTIONS;
23
+ },
24
+ get resolveWebhookOptions () {
25
+ return resolveWebhookOptions;
26
+ }
27
+ });
28
+ const DEFAULT_WEBHOOK_EVENTS = [
29
+ // JOB_COMPLETED, JOB_FAILED, STEP_FAILED
30
+ // We do not import BATCH_EVENT here to avoid a circular
31
+ // dep (the observer re-uses this list at construction
32
+ // time). The constant is the v1 contract; a future v2
33
+ // may widen the default to STEP_*, CHUNK_*, ITEM_*.
34
+ 'nest-batch.job.completed',
35
+ 'nest-batch.job.failed',
36
+ 'nest-batch.step.failed'
37
+ ];
38
+ const DEFAULT_WEBHOOK_RETRY_DELAYS_MS = [
39
+ 1_000,
40
+ 5_000,
41
+ 25_000,
42
+ 125_000
43
+ ];
44
+ const FAST_WEBHOOK_RETRY_DELAYS_MS = [
45
+ 1,
46
+ 5,
47
+ 25,
48
+ 125
49
+ ];
50
+ const WEBHOOK_MODULE_OPTIONS = Symbol.for('@nest-batch/webhook/MODULE_OPTIONS');
51
+ function resolveWebhookOptions(raw) {
52
+ if (raw === null || typeof raw !== 'object') {
53
+ throw new Error('[WebhookBatchModule] options must be a non-null object');
54
+ }
55
+ const secret = pickSecret(raw.secret);
56
+ if (typeof secret !== 'string' || secret.length === 0) {
57
+ throw new Error('[WebhookBatchModule] secret is required: pass `secret` to ' + 'forRoot() or set the WEBHOOK_HMAC_SECRET env var');
58
+ }
59
+ const urls = Array.isArray(raw.urls) ? raw.urls.slice() : [];
60
+ for (const url of urls){
61
+ if (typeof url !== 'string' || url.length === 0) {
62
+ throw new Error('[WebhookBatchModule] every entry in `urls` must be a non-empty string');
63
+ }
64
+ }
65
+ const events = Array.isArray(raw.events) && raw.events.length > 0 ? raw.events.slice() : DEFAULT_WEBHOOK_EVENTS.slice();
66
+ const rawAttempts = typeof raw.attempts === 'number' ? raw.attempts : 4;
67
+ const attempts = Math.max(1, Math.min(4, Math.floor(rawAttempts)));
68
+ const rawTimeout = typeof raw.timeoutMs === 'number' ? raw.timeoutMs : 10_000;
69
+ const timeoutMs = Math.max(100, Math.floor(rawTimeout));
70
+ return Object.freeze({
71
+ secret,
72
+ urls,
73
+ events,
74
+ attempts,
75
+ timeoutMs,
76
+ logger: raw.logger ?? defaultLogger()
77
+ });
78
+ }
79
+ /**
80
+ * Pick the secret: host-injected first, env-var fallback second.
81
+ * Returns `undefined` if neither is set so the caller can throw
82
+ * a precise error.
83
+ */ function pickSecret(hostInjected) {
84
+ if (typeof hostInjected === 'string' && hostInjected.length > 0) {
85
+ return hostInjected;
86
+ }
87
+ const fromEnv = process.env['WEBHOOK_HMAC_SECRET'];
88
+ if (typeof fromEnv === 'string' && fromEnv.length > 0) {
89
+ return fromEnv;
90
+ }
91
+ return undefined;
92
+ }
93
+ /**
94
+ * The default `WebhookLogger` — a thin adapter around
95
+ * `console`. The observer is built to be test-friendly; tests
96
+ * pass a captured-`console.warn` spy via the `logger` option.
97
+ */ function defaultLogger() {
98
+ // We deliberately do NOT import @nestjs/common's `Logger` here
99
+ // — the `WebhookLogger` is a structural interface, and the
100
+ // adapter lets the package stay test-runner-agnostic. Tests
101
+ // pass a console-backed spy; hosts pass a NestJS `Logger`
102
+ // instance (the structural shape matches the official
103
+ // `LoggerService`).
104
+ return {
105
+ log: (message)=>{
106
+ // eslint-disable-next-line no-console
107
+ console.log(`[WebhookBatchObserver] ${message}`);
108
+ },
109
+ warn: (message)=>{
110
+ // eslint-disable-next-line no-console
111
+ console.warn(`[WebhookBatchObserver] ${message}`);
112
+ },
113
+ error: (message)=>{
114
+ // eslint-disable-next-line no-console
115
+ console.error(`[WebhookBatchObserver] ${message}`);
116
+ },
117
+ debug: (message)=>{
118
+ // eslint-disable-next-line no-console
119
+ console.debug(`[WebhookBatchObserver] ${message}`);
120
+ }
121
+ };
122
+ }
123
+
124
+ //# sourceMappingURL=module-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/module-options.ts"],"sourcesContent":["import type { BatchEventType } from '@nest-batch/core';\n\n/**\n * Public options bag for `WebhookBatchModule.forRoot()`.\n *\n * The contract the test suite (`tests/webhook-observer.test.ts`,\n * T-AC-5) asserts against:\n *\n * - `secret` is REQUIRED at the host level. It is never read from\n * disk, never defaulted to an empty string, never logged in any\n * code path. The optional `WEBHOOK_HMAC_SECRET` env var is the\n * fallback ONLY when the host does not pass `secret` (env is\n * the host-injection's safety net, not its primary).\n * - `urls[]` is the fan-out set. Every subscribed event is POSTed\n * to every URL in `urls`. Empty `urls` is a no-op (the observer\n * still subscribes, it just never POSTs).\n * - `events` is the subscription filter. Defaults to\n * `[JOB_COMPLETED, JOB_FAILED, STEP_FAILED]`. Subscribed events\n * are the only ones the observer signs + POSTs. A\n * `JOB_STARTED` event arrives at `onEvent`, is not in the\n * filter, and is dropped silently (the listener is fire-and-\n * forget by contract — see `BatchObserver.onEvent`).\n * - `attempts` is the number of total POST attempts (1 initial +\n * up to `attempts-1` retries). Defaults to 4 (matching the\n * fixed 1s/5s/25s/125s backoff schedule). Lowering the value\n * is supported for tests; raising it is intentionally NOT\n * supported in v1 (the retry schedule is the contract, see\n * `docs/RELEASE-0.2.0.md` §7.2).\n * - `timeoutMs` is the per-attempt HTTP timeout. Defaults to\n * 10 000 ms (10 seconds). A timeout is treated as a network\n * error and retried through the full attempt budget.\n * - `logger` is the Nest `Logger`-compatible interface used for\n * the dead-letter `warn` line and the bootstrap notice. When\n * omitted, the observer instantiates a `new Logger('WebhookBatchObserver')`.\n */\nexport interface WebhookBatchModuleOptions {\n /**\n * Host-injected HMAC-SHA256 secret used to sign outbound\n * envelopes. REQUIRED when not relying on the `WEBHOOK_HMAC_SECRET`\n * env fallback. Recommended length: 32+ bytes of randomness\n * (a per-environment secret, never re-used across services).\n *\n * The secret is bound to the `WebhookBatchObserver` instance at\n * `forRoot` time and is never exported, logged, serialized into\n * a dead-letter body, or otherwise observable by the host.\n */\n readonly secret?: string;\n\n /**\n * One or more absolute URLs the observer will fan out to on\n * every subscribed event. Empty array is a no-op (the observer\n * still subscribes to the event stream but never POSTs).\n */\n readonly urls: readonly string[];\n\n /**\n * Subscription filter. Defaults to\n * `[BATCH_EVENT.JOB_COMPLETED, BATCH_EVENT.JOB_FAILED,\n * BATCH_EVENT.STEP_FAILED]`. The v1 contract is these three\n * events only; a future v2 may widen the default to STEP_*\n * events.\n */\n readonly events?: readonly BatchEventType[];\n\n /**\n * Total number of POST attempts (initial + retries). Defaults\n * to 4. Must be `>= 1`; `1` means \"no retries\" (single POST,\n * then dead-letter on failure). Values `> 4` are clamped to 4\n * — the v1 retry schedule is `[1s, 5s, 25s, 125s]` and has\n * exactly 4 entries; further attempts would have no backoff to\n * look up.\n */\n readonly attempts?: number;\n\n /**\n * Per-attempt HTTP timeout in milliseconds. Defaults to\n * 10 000 (10 seconds). A timeout is treated as a network\n * error and retried through the full attempt budget.\n */\n readonly timeoutMs?: number;\n\n /**\n * Logger override. The observer is built to use a NestJS\n * `Logger`-compatible interface (the four `log` / `warn` /\n * `error` / `debug` methods). When omitted, the observer\n * instantiates a `new Logger('WebhookBatchObserver')` against\n * the `console`-backed Nest logger.\n */\n readonly logger?: WebhookLogger;\n}\n\n/**\n * NestJS-`Logger`-compatible surface used by `WebhookBatchObserver`.\n *\n * We type this as a structural subset of `@nestjs/common`'s\n * `LoggerService` so the host can pass a custom logger without\n * having to import the full Nest surface. The four methods are\n * the only ones the observer calls:\n *\n * - `log` — bootstrap / info-level messages\n * - `warn` — dead-letter payload (post final failure)\n * - `error` — configuration / startup errors\n * - `debug` — per-attempt diagnostic info (URL, status, latency)\n */\nexport interface WebhookLogger {\n log(message: string, context?: string): void;\n warn(message: string, context?: string): void;\n error(message: string, context?: string): void;\n debug(message: string, context?: string): void;\n}\n\n/**\n * Fully-resolved options bag the observer consumes at runtime.\n * `forRoot` is responsible for filling in every default and\n * freezing the result before handing it to the provider.\n */\nexport interface ResolvedWebhookOptions {\n readonly secret: string;\n readonly urls: readonly string[];\n readonly events: readonly BatchEventType[];\n readonly attempts: number;\n readonly timeoutMs: number;\n readonly logger: WebhookLogger;\n}\n\n/**\n * The v1 default subscription set. Documented in\n * `docs/RELEASE-0.2.0.md` §7.1 and in the README.\n */\nexport const DEFAULT_WEBHOOK_EVENTS: readonly BatchEventType[] = [\n // JOB_COMPLETED, JOB_FAILED, STEP_FAILED\n // We do not import BATCH_EVENT here to avoid a circular\n // dep (the observer re-uses this list at construction\n // time). The constant is the v1 contract; a future v2\n // may widen the default to STEP_*, CHUNK_*, ITEM_*.\n 'nest-batch.job.completed',\n 'nest-batch.job.failed',\n 'nest-batch.step.failed',\n] as const;\n\n/**\n * The v1 fixed backoff schedule. Four entries (3 delays between\n * 4 attempts). Documented in `docs/RELEASE-0.2.0.md` §7.2 and\n * in the README. The schedule is the contract the test suite\n * asserts against.\n */\nexport const DEFAULT_WEBHOOK_RETRY_DELAYS_MS: readonly number[] = [\n 1_000, 5_000, 25_000, 125_000,\n] as const;\n\n/**\n * Fast-mode override for the retry schedule. Activated when\n * `process.env.WEBHOOK_TEST_FAST === '1'`. The override exists\n * so the test suite can exercise the 4-attempt retry path\n * without waiting 156 seconds (1+5+25+125). Test-only; never\n * touched in production. Documented in the README.\n */\nexport const FAST_WEBHOOK_RETRY_DELAYS_MS: readonly number[] = [\n 1, 5, 25, 125,\n] as const;\n\n/**\n * The DI token under which the resolved options are stored.\n * `Symbol.for` keeps the key process-scoped and stable across\n * module versions, mirroring the pattern in\n * `packages/bullmq/src/module-options.ts`.\n */\nexport const WEBHOOK_MODULE_OPTIONS: symbol = Symbol.for(\n '@nest-batch/webhook/MODULE_OPTIONS',\n);\n\n/**\n * Resolve a partial `WebhookBatchModuleOptions` into a fully-\n * populated `ResolvedWebhookOptions`. Called by `forRoot` so the\n * provider always sees a frozen, default-filled bag.\n *\n * Resolution rules:\n * - `secret`: if absent, fall back to `process.env.WEBHOOK_HMAC_SECRET`.\n * If still absent, throw — the host MUST provide a secret one\n * way or another.\n * - `urls`: required, no default. Empty array is allowed (no-op\n * fan-out).\n * - `events`: defaults to `DEFAULT_WEBHOOK_EVENTS`. A `[]` value\n * is honoured (the observer subscribes to nothing).\n * - `attempts`: defaults to 4. Clamped to `[1, 4]`.\n * - `timeoutMs`: defaults to 10 000. Clamped to `>= 100` so the\n * observer cannot be configured into \"immediate timeout\" mode.\n * - `logger`: defaults to a `new Logger('WebhookBatchObserver')`.\n */\nexport function resolveWebhookOptions(\n raw: WebhookBatchModuleOptions,\n): ResolvedWebhookOptions {\n if (raw === null || typeof raw !== 'object') {\n throw new Error(\n '[WebhookBatchModule] options must be a non-null object',\n );\n }\n const secret = pickSecret(raw.secret);\n if (typeof secret !== 'string' || secret.length === 0) {\n throw new Error(\n '[WebhookBatchModule] secret is required: pass `secret` to ' +\n 'forRoot() or set the WEBHOOK_HMAC_SECRET env var',\n );\n }\n const urls = Array.isArray(raw.urls) ? raw.urls.slice() : [];\n for (const url of urls) {\n if (typeof url !== 'string' || url.length === 0) {\n throw new Error(\n '[WebhookBatchModule] every entry in `urls` must be a non-empty string',\n );\n }\n }\n const events = Array.isArray(raw.events) && raw.events.length > 0\n ? raw.events.slice()\n : DEFAULT_WEBHOOK_EVENTS.slice();\n const rawAttempts = typeof raw.attempts === 'number' ? raw.attempts : 4;\n const attempts = Math.max(1, Math.min(4, Math.floor(rawAttempts)));\n const rawTimeout = typeof raw.timeoutMs === 'number' ? raw.timeoutMs : 10_000;\n const timeoutMs = Math.max(100, Math.floor(rawTimeout));\n return Object.freeze({\n secret,\n urls,\n events,\n attempts,\n timeoutMs,\n logger: raw.logger ?? defaultLogger(),\n });\n}\n\n/**\n * Pick the secret: host-injected first, env-var fallback second.\n * Returns `undefined` if neither is set so the caller can throw\n * a precise error.\n */\nfunction pickSecret(hostInjected: string | undefined): string | undefined {\n if (typeof hostInjected === 'string' && hostInjected.length > 0) {\n return hostInjected;\n }\n const fromEnv = process.env['WEBHOOK_HMAC_SECRET'];\n if (typeof fromEnv === 'string' && fromEnv.length > 0) {\n return fromEnv;\n }\n return undefined;\n}\n\n/**\n * The default `WebhookLogger` — a thin adapter around\n * `console`. The observer is built to be test-friendly; tests\n * pass a captured-`console.warn` spy via the `logger` option.\n */\nfunction defaultLogger(): WebhookLogger {\n // We deliberately do NOT import @nestjs/common's `Logger` here\n // — the `WebhookLogger` is a structural interface, and the\n // adapter lets the package stay test-runner-agnostic. Tests\n // pass a console-backed spy; hosts pass a NestJS `Logger`\n // instance (the structural shape matches the official\n // `LoggerService`).\n return {\n log: (message: string) => {\n // eslint-disable-next-line no-console\n console.log(`[WebhookBatchObserver] ${message}`);\n },\n warn: (message: string) => {\n // eslint-disable-next-line no-console\n console.warn(`[WebhookBatchObserver] ${message}`);\n },\n error: (message: string) => {\n // eslint-disable-next-line no-console\n console.error(`[WebhookBatchObserver] ${message}`);\n },\n debug: (message: string) => {\n // eslint-disable-next-line no-console\n console.debug(`[WebhookBatchObserver] ${message}`);\n },\n };\n}\n"],"names":["DEFAULT_WEBHOOK_EVENTS","DEFAULT_WEBHOOK_RETRY_DELAYS_MS","FAST_WEBHOOK_RETRY_DELAYS_MS","WEBHOOK_MODULE_OPTIONS","resolveWebhookOptions","Symbol","for","raw","Error","secret","pickSecret","length","urls","Array","isArray","slice","url","events","rawAttempts","attempts","Math","max","min","floor","rawTimeout","timeoutMs","Object","freeze","logger","defaultLogger","hostInjected","fromEnv","process","env","undefined","log","message","console","warn","error","debug"],"mappings":";;;;;;;;;;;QAiIaA;eAAAA;;QAiBAC;eAAAA;;QAWAC;eAAAA;;QAUAC;eAAAA;;QAsBGC;eAAAA;;;AA5DT,MAAMJ,yBAAoD;IAC/D,yCAAyC;IACzC,wDAAwD;IACxD,sDAAsD;IACtD,sDAAsD;IACtD,oDAAoD;IACpD;IACA;IACA;CACD;AAQM,MAAMC,kCAAqD;IAChE;IAAO;IAAO;IAAQ;CACvB;AASM,MAAMC,+BAAkD;IAC7D;IAAG;IAAG;IAAI;CACX;AAQM,MAAMC,yBAAiCE,OAAOC,GAAG,CACtD;AAqBK,SAASF,sBACdG,GAA8B;IAE9B,IAAIA,QAAQ,QAAQ,OAAOA,QAAQ,UAAU;QAC3C,MAAM,IAAIC,MACR;IAEJ;IACA,MAAMC,SAASC,WAAWH,IAAIE,MAAM;IACpC,IAAI,OAAOA,WAAW,YAAYA,OAAOE,MAAM,KAAK,GAAG;QACrD,MAAM,IAAIH,MACR,+DACE;IAEN;IACA,MAAMI,OAAOC,MAAMC,OAAO,CAACP,IAAIK,IAAI,IAAIL,IAAIK,IAAI,CAACG,KAAK,KAAK,EAAE;IAC5D,KAAK,MAAMC,OAAOJ,KAAM;QACtB,IAAI,OAAOI,QAAQ,YAAYA,IAAIL,MAAM,KAAK,GAAG;YAC/C,MAAM,IAAIH,MACR;QAEJ;IACF;IACA,MAAMS,SAASJ,MAAMC,OAAO,CAACP,IAAIU,MAAM,KAAKV,IAAIU,MAAM,CAACN,MAAM,GAAG,IAC5DJ,IAAIU,MAAM,CAACF,KAAK,KAChBf,uBAAuBe,KAAK;IAChC,MAAMG,cAAc,OAAOX,IAAIY,QAAQ,KAAK,WAAWZ,IAAIY,QAAQ,GAAG;IACtE,MAAMA,WAAWC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,GAAG,CAAC,GAAGF,KAAKG,KAAK,CAACL;IACpD,MAAMM,aAAa,OAAOjB,IAAIkB,SAAS,KAAK,WAAWlB,IAAIkB,SAAS,GAAG;IACvE,MAAMA,YAAYL,KAAKC,GAAG,CAAC,KAAKD,KAAKG,KAAK,CAACC;IAC3C,OAAOE,OAAOC,MAAM,CAAC;QACnBlB;QACAG;QACAK;QACAE;QACAM;QACAG,QAAQrB,IAAIqB,MAAM,IAAIC;IACxB;AACF;AAEA;;;;CAIC,GACD,SAASnB,WAAWoB,YAAgC;IAClD,IAAI,OAAOA,iBAAiB,YAAYA,aAAanB,MAAM,GAAG,GAAG;QAC/D,OAAOmB;IACT;IACA,MAAMC,UAAUC,QAAQC,GAAG,CAAC,sBAAsB;IAClD,IAAI,OAAOF,YAAY,YAAYA,QAAQpB,MAAM,GAAG,GAAG;QACrD,OAAOoB;IACT;IACA,OAAOG;AACT;AAEA;;;;CAIC,GACD,SAASL;IACP,+DAA+D;IAC/D,2DAA2D;IAC3D,4DAA4D;IAC5D,0DAA0D;IAC1D,sDAAsD;IACtD,oBAAoB;IACpB,OAAO;QACLM,KAAK,CAACC;YACJ,sCAAsC;YACtCC,QAAQF,GAAG,CAAC,CAAC,uBAAuB,EAAEC,SAAS;QACjD;QACAE,MAAM,CAACF;YACL,sCAAsC;YACtCC,QAAQC,IAAI,CAAC,CAAC,uBAAuB,EAAEF,SAAS;QAClD;QACAG,OAAO,CAACH;YACN,sCAAsC;YACtCC,QAAQE,KAAK,CAAC,CAAC,uBAAuB,EAAEH,SAAS;QACnD;QACAI,OAAO,CAACJ;YACN,sCAAsC;YACtCC,QAAQG,KAAK,CAAC,CAAC,uBAAuB,EAAEJ,SAAS;QACnD;IACF;AACF"}
@@ -0,0 +1,59 @@
1
+ import { type DynamicModule } from '@nestjs/common';
2
+ import { BATCH_EVENT, type BatchEventType } from '@nest-batch/core';
3
+ import { type ResolvedWebhookOptions, type WebhookBatchModuleOptions, type WebhookLogger } from './module-options';
4
+ import { WebhookBatchObserver } from './webhook-batch.observer';
5
+ /**
6
+ * `WebhookBatchModule` — the NestJS dynamic module that wires
7
+ * the `WebhookBatchObserver` into the host's DI container and
8
+ * binds it to the `BatchObserver` token used by the executor
9
+ * (and by `@nest-batch/bullmq` / `@nest-batch/kafka`'s runtime
10
+ * bridge).
11
+ *
12
+ * The host wires it alongside `NestBatchModule.forRoot({...})`:
13
+ *
14
+ * ```ts
15
+ * @Module({
16
+ * imports: [
17
+ * NestBatchModule.forRoot({
18
+ * adapters: { persistence: MikroOrmAdapter.forRoot(), transport: BullmqAdapter.forRoot() },
19
+ * }),
20
+ * WebhookBatchModule.forRoot({
21
+ * secret: process.env.WEBHOOK_HMAC_SECRET,
22
+ * urls: ['https://hooks.example.com/nest-batch'],
23
+ * }),
24
+ * ],
25
+ * })
26
+ * export class AppModule {}
27
+ * ```
28
+ *
29
+ * The observer is auto-registered against the `BatchObserver`
30
+ * token via `useExisting`, so the executor's optional-injection
31
+ * path picks it up without any extra wiring on the host's side.
32
+ */
33
+ export declare class WebhookBatchModule {
34
+ }
35
+ /**
36
+ * `forRoot` — synchronous configuration. Resolves the options
37
+ * up-front (filling in defaults, falling back to the
38
+ * `WEBHOOK_HMAC_SECRET` env var when `secret` is omitted,
39
+ * freezing the result) and emits a `DynamicModule` that:
40
+ *
41
+ * - registers `WebhookBatchObserver` as a provider,
42
+ * - registers a `useExisting` alias so anything injecting
43
+ * `BatchObserver` (or `WebhookBatchObserver` by class)
44
+ * resolves to the same instance,
45
+ * - registers the resolved options under
46
+ * `WEBHOOK_MODULE_OPTIONS` (the observer's `@Inject`
47
+ * key),
48
+ * - marks the module `global: true` so the observer is
49
+ * visible across the host's sub-modules.
50
+ *
51
+ * The `urls: []` case is a no-op (the observer subscribes
52
+ * to the event stream but never POSTs); it does not throw.
53
+ * The `secret` case throws at `forRoot` time with a clear
54
+ * message (the host sees the error at boot, not at the first
55
+ * event).
56
+ */
57
+ export declare function forRoot(options: WebhookBatchModuleOptions): DynamicModule;
58
+ export { BATCH_EVENT, WebhookBatchObserver, type BatchEventType, type ResolvedWebhookOptions, type WebhookBatchModuleOptions, type WebhookLogger, };
59
+ //# sourceMappingURL=webhook-batch.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-batch.module.d.ts","sourceRoot":"","sources":["../../src/webhook-batch.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEpE,OAAO,EAGL,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBACa,kBAAkB;CAAG;AAElC;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,yBAAyB,GAAG,aAAa,CAQzE;AAoDD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,aAAa,GACnB,CAAC"}
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get BATCH_EVENT () {
13
+ return _core.BATCH_EVENT;
14
+ },
15
+ get WebhookBatchModule () {
16
+ return WebhookBatchModule;
17
+ },
18
+ get WebhookBatchObserver () {
19
+ return _webhookbatchobserver.WebhookBatchObserver;
20
+ },
21
+ get forRoot () {
22
+ return forRoot;
23
+ }
24
+ });
25
+ const _common = require("@nestjs/common");
26
+ const _core = require("@nest-batch/core");
27
+ const _moduleoptions = require("./module-options");
28
+ const _webhookbatchobserver = require("./webhook-batch.observer");
29
+ function _ts_decorate(decorators, target, key, desc) {
30
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
32
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
33
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
34
+ }
35
+ let WebhookBatchModule = class WebhookBatchModule {
36
+ };
37
+ WebhookBatchModule = _ts_decorate([
38
+ (0, _common.Module)({})
39
+ ], WebhookBatchModule);
40
+ function forRoot(options) {
41
+ const resolved = (0, _moduleoptions.resolveWebhookOptions)(options);
42
+ return {
43
+ module: WebhookBatchModule,
44
+ global: true,
45
+ providers: buildProviders(resolved),
46
+ exports: [
47
+ _webhookbatchobserver.WebhookBatchObserver
48
+ ]
49
+ };
50
+ }
51
+ /**
52
+ * Build the static provider list shared by `forRoot()`.
53
+ *
54
+ * The list is three entries:
55
+ * - `WebhookBatchObserver` — the concrete class.
56
+ * - `BATCH_OBSERVER_PROVIDER` — a `useExisting` alias so the
57
+ * executor's `@Optional() @Inject(BatchObserver) observer`
58
+ * resolves to the same instance.
59
+ * - `WEBHOOK_MODULE_OPTIONS` — the resolved + frozen
60
+ * options bag, injected into the observer's constructor.
61
+ *
62
+ * Centralising the list keeps the public factory surface
63
+ * (`forRoot`) a one-liner; any future addition (e.g. a
64
+ * per-package health check) only needs to land here.
65
+ */ function buildProviders(resolved) {
66
+ return [
67
+ _webhookbatchobserver.WebhookBatchObserver,
68
+ {
69
+ provide: BATCH_OBSERVER_TOKEN,
70
+ useExisting: _webhookbatchobserver.WebhookBatchObserver
71
+ },
72
+ {
73
+ provide: _moduleoptions.WEBHOOK_MODULE_OPTIONS,
74
+ useValue: resolved
75
+ }
76
+ ];
77
+ }
78
+ /**
79
+ * The DI token the executor / runtime services use to inject
80
+ * a `BatchObserver`. We re-export the `BatchObserver` class
81
+ * itself as the token (mirroring the pattern in
82
+ * `@nest-batch/bullmq` and `@nest-batch/kafka`, where the
83
+ * `BatchObserver` interface is the type and the class-as-
84
+ * token resolves to the singleton).
85
+ *
86
+ * Using the `BatchObserver` class (an interface in
87
+ * `@nest-batch/core`) as the token is a NestJS pattern:
88
+ * Nest uses the class reference as the default DI key. We
89
+ * redeclare it as `BATCH_OBSERVER_TOKEN` so the `useExisting`
90
+ * provider above has a stable, imported symbol to bind
91
+ * against.
92
+ */ const BATCH_OBSERVER_TOKEN = Symbol.for('@nest-batch/webhook/BATCH_OBSERVER');
93
+
94
+ //# sourceMappingURL=webhook-batch.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/webhook-batch.module.ts"],"sourcesContent":["import { Module, type DynamicModule, type Provider } from '@nestjs/common';\nimport { BATCH_EVENT, type BatchEventType } from '@nest-batch/core';\n\nimport {\n resolveWebhookOptions,\n WEBHOOK_MODULE_OPTIONS,\n type ResolvedWebhookOptions,\n type WebhookBatchModuleOptions,\n type WebhookLogger,\n} from './module-options';\nimport { WebhookBatchObserver } from './webhook-batch.observer';\n\n/**\n * `WebhookBatchModule` — the NestJS dynamic module that wires\n * the `WebhookBatchObserver` into the host's DI container and\n * binds it to the `BatchObserver` token used by the executor\n * (and by `@nest-batch/bullmq` / `@nest-batch/kafka`'s runtime\n * bridge).\n *\n * The host wires it alongside `NestBatchModule.forRoot({...})`:\n *\n * ```ts\n * @Module({\n * imports: [\n * NestBatchModule.forRoot({\n * adapters: { persistence: MikroOrmAdapter.forRoot(), transport: BullmqAdapter.forRoot() },\n * }),\n * WebhookBatchModule.forRoot({\n * secret: process.env.WEBHOOK_HMAC_SECRET,\n * urls: ['https://hooks.example.com/nest-batch'],\n * }),\n * ],\n * })\n * export class AppModule {}\n * ```\n *\n * The observer is auto-registered against the `BatchObserver`\n * token via `useExisting`, so the executor's optional-injection\n * path picks it up without any extra wiring on the host's side.\n */\n@Module({})\nexport class WebhookBatchModule {}\n\n/**\n * `forRoot` — synchronous configuration. Resolves the options\n * up-front (filling in defaults, falling back to the\n * `WEBHOOK_HMAC_SECRET` env var when `secret` is omitted,\n * freezing the result) and emits a `DynamicModule` that:\n *\n * - registers `WebhookBatchObserver` as a provider,\n * - registers a `useExisting` alias so anything injecting\n * `BatchObserver` (or `WebhookBatchObserver` by class)\n * resolves to the same instance,\n * - registers the resolved options under\n * `WEBHOOK_MODULE_OPTIONS` (the observer's `@Inject`\n * key),\n * - marks the module `global: true` so the observer is\n * visible across the host's sub-modules.\n *\n * The `urls: []` case is a no-op (the observer subscribes\n * to the event stream but never POSTs); it does not throw.\n * The `secret` case throws at `forRoot` time with a clear\n * message (the host sees the error at boot, not at the first\n * event).\n */\nexport function forRoot(options: WebhookBatchModuleOptions): DynamicModule {\n const resolved = resolveWebhookOptions(options);\n return {\n module: WebhookBatchModule,\n global: true,\n providers: buildProviders(resolved),\n exports: [WebhookBatchObserver],\n };\n}\n\n/**\n * Build the static provider list shared by `forRoot()`.\n *\n * The list is three entries:\n * - `WebhookBatchObserver` — the concrete class.\n * - `BATCH_OBSERVER_PROVIDER` — a `useExisting` alias so the\n * executor's `@Optional() @Inject(BatchObserver) observer`\n * resolves to the same instance.\n * - `WEBHOOK_MODULE_OPTIONS` — the resolved + frozen\n * options bag, injected into the observer's constructor.\n *\n * Centralising the list keeps the public factory surface\n * (`forRoot`) a one-liner; any future addition (e.g. a\n * per-package health check) only needs to land here.\n */\nfunction buildProviders(resolved: ResolvedWebhookOptions): Provider[] {\n return [\n WebhookBatchObserver,\n {\n provide: BATCH_OBSERVER_TOKEN,\n useExisting: WebhookBatchObserver,\n },\n {\n provide: WEBHOOK_MODULE_OPTIONS,\n useValue: resolved,\n },\n ];\n}\n\n/**\n * The DI token the executor / runtime services use to inject\n * a `BatchObserver`. We re-export the `BatchObserver` class\n * itself as the token (mirroring the pattern in\n * `@nest-batch/bullmq` and `@nest-batch/kafka`, where the\n * `BatchObserver` interface is the type and the class-as-\n * token resolves to the singleton).\n *\n * Using the `BatchObserver` class (an interface in\n * `@nest-batch/core`) as the token is a NestJS pattern:\n * Nest uses the class reference as the default DI key. We\n * redeclare it as `BATCH_OBSERVER_TOKEN` so the `useExisting`\n * provider above has a stable, imported symbol to bind\n * against.\n */\nconst BATCH_OBSERVER_TOKEN: symbol = Symbol.for(\n '@nest-batch/webhook/BATCH_OBSERVER',\n);\n\n// Re-export the public surface of this module so the\n// package barrel can re-export it in turn.\nexport {\n BATCH_EVENT,\n WebhookBatchObserver,\n type BatchEventType,\n type ResolvedWebhookOptions,\n type WebhookBatchModuleOptions,\n type WebhookLogger,\n};\n"],"names":["BATCH_EVENT","WebhookBatchModule","WebhookBatchObserver","forRoot","options","resolved","resolveWebhookOptions","module","global","providers","buildProviders","exports","provide","BATCH_OBSERVER_TOKEN","useExisting","WEBHOOK_MODULE_OPTIONS","useValue","Symbol","for"],"mappings":";;;;;;;;;;;QA8HEA;eAAAA,iBAAW;;QArFAC;eAAAA;;QAsFXC;eAAAA,0CAAoB;;QA9DNC;eAAAA;;;wBAjE0C;sBACT;+BAQ1C;sCAC8B;;;;;;;AA+B9B,IAAA,AAAMF,qBAAN,MAAMA;AAAoB;;;;AAwB1B,SAASE,QAAQC,OAAkC;IACxD,MAAMC,WAAWC,IAAAA,oCAAqB,EAACF;IACvC,OAAO;QACLG,QAAQN;QACRO,QAAQ;QACRC,WAAWC,eAAeL;QAC1BM,SAAS;YAACT,0CAAoB;SAAC;IACjC;AACF;AAEA;;;;;;;;;;;;;;CAcC,GACD,SAASQ,eAAeL,QAAgC;IACtD,OAAO;QACLH,0CAAoB;QACpB;YACEU,SAASC;YACTC,aAAaZ,0CAAoB;QACnC;QACA;YACEU,SAASG,qCAAsB;YAC/BC,UAAUX;QACZ;KACD;AACH;AAEA;;;;;;;;;;;;;;CAcC,GACD,MAAMQ,uBAA+BI,OAAOC,GAAG,CAC7C"}
@@ -0,0 +1,144 @@
1
+ import { BATCH_EVENT, type BatchEvent, type BatchEventType, type BatchObserver } from '@nest-batch/core';
2
+ import { type ResolvedWebhookOptions } from './module-options';
3
+ /**
4
+ * `WebhookBatchObserver` — the v1 webhook delivery observer.
5
+ *
6
+ * Implements `BatchObserver` from `@nest-batch/core`. On every
7
+ * subscribed `BATCH_EVENT.*` (default:
8
+ * `[JOB_COMPLETED, JOB_FAILED, STEP_FAILED]`) the observer:
9
+ *
10
+ * 1. Serializes a normalized JSON envelope
11
+ * `{ version: 1, type, timestamp, jobId, execution }`.
12
+ * 2. Computes the v1 HMAC-SHA256 signature over
13
+ * `<unix>.<raw-body>` (Stripe-style).
14
+ * 3. POSTs the envelope + `X-Nest-Batch-Signature` header to
15
+ * every URL in `urls`.
16
+ * 4. Retries on 5xx and network errors through the fixed
17
+ * 4-attempt budget at `[1s, 5s, 25s, 125s]`. HTTP 4xx
18
+ * responses are NOT retried (client error, won't change).
19
+ * 5. On final failure, emits a `logger.warn` dead-letter line
20
+ * including the URL, attempt count, last status / error,
21
+ * and a SHA-256 fingerprint of the secret (NEVER the
22
+ * secret itself).
23
+ *
24
+ * The observer is the v1 contract documented in
25
+ * `docs/RELEASE-0.2.0.md` §7 and pinned by T-AC-5
26
+ * (`packages/webhook/tests/webhook-observer.test.ts`).
27
+ */
28
+ export declare class WebhookBatchObserver implements BatchObserver {
29
+ private readonly logger;
30
+ /** Resolved + frozen options. The secret lives here and nowhere else. */
31
+ private readonly options;
32
+ /**
33
+ * Cached lookup of the subscription set. Built once at
34
+ * construction time so `onEvent` is a single `Set.has` check.
35
+ */
36
+ private readonly subscribed;
37
+ /**
38
+ * Test-only override for the retry schedule. When
39
+ * `process.env.WEBHOOK_TEST_FAST === '1'`, the schedule is
40
+ * `[1ms, 5ms, 25ms, 125ms]` so the suite can exercise the
41
+ * 4-attempt path without waiting 156 seconds. The override
42
+ * is gated behind an env var so production cannot trip it
43
+ * by accident.
44
+ */
45
+ private readonly retryDelaysMs;
46
+ /**
47
+ * Sentinel subscriber set: defaults to
48
+ * `[JOB_COMPLETED, JOB_FAILED, STEP_FAILED]`. Overridable via
49
+ * the `events` option in `forRoot({...})`.
50
+ */
51
+ constructor(options: ResolvedWebhookOptions);
52
+ /**
53
+ * `BatchObserver` entry point. Filters by the subscription
54
+ * set, then dispatches to every URL. NEVER throws — a slow /
55
+ * failing observer must not poison the executor (the
56
+ * JobExecutor already swallows observer errors, but we are
57
+ * defensive in depth).
58
+ */
59
+ onEvent(event: BatchEvent): Promise<void>;
60
+ /**
61
+ * Build the envelope once, then POST to every URL in
62
+ * `urls` in parallel. A single URL's retry exhaustion does
63
+ * not affect the other URLs — each URL has its own
64
+ * `deliverToUrl` invocation and its own dead-letter line.
65
+ *
66
+ * The envelope is built with `JSON.stringify` (NOT a Nest
67
+ * serializer) so the bytes are stable and match the HMAC
68
+ * input byte-for-byte. The body string is the literal
69
+ * argument to `fetch`, so the receiver sees the same
70
+ * bytes the observer signed.
71
+ */
72
+ private deliverToAll;
73
+ /**
74
+ * Build the v1 envelope payload. The shape is the contract
75
+ * the receiver expects; changing it is a breaking change.
76
+ *
77
+ * - `version: 1` — the envelope schema version (the
78
+ * `v1=` in the signature header is the SIGNATURE
79
+ * version, not the ENVELOPE version; they are
80
+ * independent).
81
+ * - `type` — the `BatchEvent.type` string verbatim
82
+ * (e.g. `nest-batch.job.completed`).
83
+ * - `timestamp` — the event's `Date` serialized as
84
+ * ISO-8601 (the original `Date` is not JSON-safe).
85
+ * - `jobId` — the `jobExecutionId` (the `BatchEvent`
86
+ * contract guarantees this is always set).
87
+ * - `execution` — the `JobExecution` shape derived from
88
+ * the event's `data` payload. The observer treats
89
+ * `data` as opaque `JsonValue` and passes it through
90
+ * after a defensive deep-copy via `structuredClone`
91
+ * so the observer cannot mutate the executor's
92
+ * internal state by reference.
93
+ * - `stepId` — present for STEP\_\* / CHUNK\_\* / ITEM\_\*
94
+ * events; absent for JOB\_\* events. Mirrors the
95
+ * `BatchEvent.stepExecutionId` contract.
96
+ */
97
+ private buildEnvelope;
98
+ /**
99
+ * POST the envelope to one URL with the full retry budget.
100
+ * Stops on the first 2xx; retries on 5xx and network errors;
101
+ * does NOT retry on 4xx; emits a dead-letter `warn` on
102
+ * exhaustion. The body is signed once; the same signed body
103
+ * is sent on every attempt.
104
+ */
105
+ private deliverToUrl;
106
+ /**
107
+ * Single POST attempt. The signature header is sent on
108
+ * every attempt (the body bytes are identical across
109
+ * attempts; the receiver can verify the signature against
110
+ * any of them).
111
+ *
112
+ * The result is a discriminated union:
113
+ * - `kind: 'success'` — 2xx (or 3xx; we follow
114
+ * the redirect by default in `fetch`, but the
115
+ * receiver's terminal status is what we report)
116
+ * - `kind: 'client-error'` — 4xx (no retry)
117
+ * - `kind: 'server-error'` — 5xx (retry)
118
+ * - `kind: 'network-error'` — fetch threw, or
119
+ * `AbortError` from the timeout (retry)
120
+ */
121
+ private attemptOnce;
122
+ }
123
+ /**
124
+ * The v1 webhook envelope payload. This is the contract the
125
+ * receiver's parser expects. Fields are stable; new fields
126
+ * are additive only and use the `x-` prefix to mark them
127
+ * as out-of-contract for v1.
128
+ */
129
+ export interface WebhookEnvelope {
130
+ /** Envelope schema version. Always `1` for v1. */
131
+ readonly version: 1;
132
+ /** The `BatchEvent.type` string (e.g. `nest-batch.job.completed`). */
133
+ readonly type: BatchEventType;
134
+ /** Event timestamp as ISO-8601. */
135
+ readonly timestamp: string;
136
+ /** The `JobExecution.id` (a.k.a. `jobExecutionId`). */
137
+ readonly jobId: string;
138
+ /** The `StepExecution.id` (a.k.a. `stepExecutionId`). STEP\_\* / CHUNK\_\* / ITEM\_\* events only. */
139
+ readonly stepId?: string;
140
+ /** The `BatchEvent.data` payload, deep-cloned for safety. */
141
+ readonly execution: unknown;
142
+ }
143
+ export { BATCH_EVENT };
144
+ //# sourceMappingURL=webhook-batch.observer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-batch.observer.d.ts","sourceRoot":"","sources":["../../src/webhook-batch.observer.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAIL,KAAK,sBAAsB,EAE5B,MAAM,kBAAkB,CAAC;AAO1B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBACa,oBAAqB,YAAW,aAAa;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;IAEvC,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;IAEjD;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8B;IAEzD;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAElD;;;;OAIG;gBAE+B,OAAO,EAAE,sBAAsB;IAWjE;;;;;;OAMG;IACG,OAAO,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B/C;;;;;;;;;;;OAWG;YACW,YAAY;IAQ1B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,OAAO,CAAC,aAAa;IAiBrB;;;;;;OAMG;YACW,YAAY;IAmF1B;;;;;;;;;;;;;;OAcG;YACW,WAAW;CAmD1B;AA6CD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,mCAAmC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sGAAsG;IACtG,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAMD,OAAO,EAAE,WAAW,EAAE,CAAC"}