@rotorsoft/act-http 1.10.8 → 1.10.10
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 +1 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/receiver/start.d.ts.map +1 -1
- package/dist/@types/webhook/index.d.ts.map +1 -1
- package/dist/@types/webhook/types.d.ts +6 -0
- package/dist/@types/webhook/types.d.ts.map +1 -1
- package/dist/receiver/index.cjs +32 -1
- package/dist/receiver/index.cjs.map +1 -1
- package/dist/receiver/index.js +32 -1
- package/dist/receiver/index.js.map +1 -1
- package/dist/webhook/index.cjs +31 -24
- package/dist/webhook/index.cjs.map +1 -1
- package/dist/webhook/index.js +31 -24
- package/dist/webhook/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/webhook/types.ts","../../src/webhook/classify.ts","../../src/webhook/sign.ts","../../src/webhook/index.ts"],"sourcesContent":["import {\n type Committed,\n NonRetryableError,\n type Schemas,\n} from \"@rotorsoft/act\";\n\n/**\n * Function or static value resolver. Used so callers can pass either a\n * constant or a per-event function for headers / body / url.\n *\n * The static side `T` is constrained to non-function types so that a\n * passed `(event) => ...` is unambiguously typed as the function variant.\n */\nexport type WebhookResolver<TEvents extends Schemas, T> =\n | T\n | ((event: Committed<TEvents, keyof TEvents>) => T);\n\n/**\n * Plain-data body shape the helper accepts as a static value. Functions\n * are deliberately excluded so the union with the resolver function is\n * unambiguous at the call site (TypeScript can discriminate by shape).\n */\nexport type WebhookBody =\n | string\n | { readonly [k: string]: unknown }\n | readonly unknown[];\n\n/**\n * Configuration for {@link webhook}.\n *\n * @template TEvents - Event schemas; resolvers receive the typed committed event.\n */\nexport type WebhookConfig<TEvents extends Schemas = Schemas> = {\n /** Target URL — static string or per-event function. */\n readonly url: WebhookResolver<TEvents, string>;\n /** HTTP method. Defaults to `\"POST\"`. */\n readonly method?: \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n /**\n * Headers to send. Resolver may return a record per event. The\n * `Content-Type: application/json` and `Idempotency-Key` headers are\n * applied automatically; both can be overridden by returning a header\n * with the same name (case-insensitive).\n */\n readonly headers?: WebhookResolver<TEvents, Record<string, string>>;\n /**\n * Request body. Static plain data (object, array, string) or a\n * per-event function returning the same. Strings are sent as-is;\n * anything else is JSON-serialized. Defaults to the committed event\n * itself.\n */\n readonly body?:\n | WebhookBody\n | ((event: Committed<TEvents, keyof TEvents>) => WebhookBody);\n /**\n * Per-request timeout in milliseconds. Defaults to 5000.\n * The handler throws after the timeout via `AbortController`.\n */\n readonly timeoutMs?: number;\n /**\n * Override for the auto-generated `Idempotency-Key`. By default, the\n * helper sends `event.id` (the immutable, monotonic event identifier).\n * Return a string to override; return `null` to skip the header entirely.\n */\n readonly idempotencyKey?: (\n event: Committed<TEvents, keyof TEvents>\n ) => string | null;\n /**\n * Injection point for tests. Defaults to global `fetch`.\n */\n readonly fetch?: typeof fetch;\n /**\n * HMAC-SHA256 signing key. When set, the webhook helper attaches\n * two headers to every request:\n *\n * - `X-Webhook-Signature: sha256=<hex>` — HMAC of\n * `${timestamp}.${body}` (`body` is the final serialized payload)\n * - `X-Webhook-Timestamp: <unix-seconds>`\n *\n * Pair with `verifyWebhook` from `@rotorsoft/act-http/receiver` on\n * the receiving side. When undefined, no signature headers are\n * added — back-compat with consumers that don't need signing.\n *\n * Callers can override either header by returning it from the\n * `headers` resolver (case-insensitive), the same way the\n * `Idempotency-Key` and `Content-Type` defaults yield to caller\n * intent.\n */\n readonly secret?: string;\n};\n\n/**\n * Common fields carried on every HTTP delivery error in this package.\n */\nexport type HttpDeliveryErrorInit = {\n status: number;\n url: string;\n responseBody?: string;\n};\n\n/**\n * Thrown when an HTTP delivery fails in a way the drain pipeline\n * should retry: network failure, timeout, or 5xx response. `status` is\n * `0` for network / timeout errors, the HTTP status code otherwise.\n *\n * The class itself is the retry signal — if a reaction throws this,\n * drain treats it like any other error (counts against `maxRetries`,\n * paces with `backoff`). For permanent failures, throw\n * {@link NonRetryableHttpError} instead.\n *\n * Generic enough to cover any custom HTTP-like integration (gRPC\n * bridges, SDK-based reactions). {@link WebhookError} is a\n * webhook-specific subclass kept for backward compatibility.\n */\nexport class RetryableHttpError extends Error {\n readonly status: number;\n readonly url: string;\n readonly responseBody?: string;\n\n constructor(message: string, init: HttpDeliveryErrorInit) {\n super(message);\n this.name = \"RetryableHttpError\";\n this.status = init.status;\n this.url = init.url;\n this.responseBody = init.responseBody;\n }\n}\n\n/**\n * Thrown when an HTTP delivery returns a 3xx or 4xx response —\n * permanent client errors that won't recover on retry. Extends\n * {@link NonRetryableError} so the drain finalizer blocks the stream\n * on the first failed attempt (when `blockOnError` is true) — no\n * wasted retries on a malformed payload or wrong URL.\n *\n * Generic enough to cover any custom HTTP-like integration.\n * {@link NonRetryableWebhookError} is a webhook-specific subclass kept\n * for backward compatibility.\n */\nexport class NonRetryableHttpError extends NonRetryableError {\n readonly status: number;\n readonly url: string;\n readonly responseBody?: string;\n\n constructor(message: string, init: HttpDeliveryErrorInit) {\n super(message);\n this.name = \"NonRetryableHttpError\";\n this.status = init.status;\n this.url = init.url;\n this.responseBody = init.responseBody;\n }\n}\n\n/**\n * Webhook-specific subclass of {@link RetryableHttpError}. Thrown by\n * the {@link webhook} helper on 5xx responses, network failures, and\n * timeouts. Existing `instanceof WebhookError` checks continue to\n * work; new code targeting the generic HTTP integration shape can\n * catch {@link RetryableHttpError} instead and handle webhook +\n * custom integrations uniformly.\n */\nexport class WebhookError extends RetryableHttpError {\n constructor(message: string, init: HttpDeliveryErrorInit) {\n super(message, init);\n this.name = \"WebhookError\";\n }\n}\n\n/**\n * Webhook-specific subclass of {@link NonRetryableHttpError}. Thrown\n * by the {@link webhook} helper on 3xx/4xx responses. Existing\n * `instanceof NonRetryableWebhookError` checks continue to work; new\n * code can catch {@link NonRetryableHttpError} or\n * {@link NonRetryableError} for broader coverage.\n */\nexport class NonRetryableWebhookError extends NonRetryableHttpError {\n constructor(message: string, init: HttpDeliveryErrorInit) {\n super(message, init);\n this.name = \"NonRetryableWebhookError\";\n }\n}\n","import { NonRetryableHttpError, RetryableHttpError } from \"./types.js\";\n\n/**\n * Three buckets for an HTTP response from an outbound delivery:\n *\n * - `ok` — the receiver accepted the delivery (2xx). Stop and return.\n * - `retry` — the receiver had a transient problem (5xx). Throw a\n * retryable error; drain will pace the next attempt per `backoff`.\n * - `block` — the receiver rejected the delivery permanently (3xx\n * or 4xx). Throw a non-retryable error; drain blocks the stream\n * on the first failed attempt (when `blockOnError` is true) and\n * surfaces it via the `\"blocked\"` lifecycle event.\n *\n * The 3xx → `block` mapping is intentional: a redirect at the\n * delivery layer means the configured URL is wrong, and retrying\n * the same URL won't fix that. Manual operator review is the right\n * next step, which is what the block path produces.\n */\nexport type HttpDisposition = \"ok\" | \"retry\" | \"block\";\n\n/**\n * Classify an HTTP response as `ok` (2xx), `retry` (5xx), or\n * `block` (3xx, 4xx). The classification {@link webhook} uses\n * internally, lifted here so custom integrations (gRPC bridges,\n * SDK-based reactions, etc.) can apply the same retry semantics\n * without inventing a parallel rule.\n */\nexport function classifyHttpResponse(response: Response): HttpDisposition {\n if (response.ok) return \"ok\";\n if (response.status >= 500) return \"retry\";\n return \"block\";\n}\n\n/** Options for {@link tryOk}. */\nexport type TryOkOptions = {\n /** The endpoint that received the request. Surfaced on the thrown error and in its message. */\n url: string;\n /**\n * Label prefixed onto the error message — typically the\n * integration's identity (`\"webhook\"`, `\"my_sdk\"`, `\"grpc\"`).\n * Default: `\"request\"`.\n */\n label?: string;\n};\n\n/**\n * If `response` is 2xx, return. Otherwise, capture the response body\n * (best-effort) and throw a {@link RetryableHttpError} (for 5xx) or\n * {@link NonRetryableHttpError} (for 3xx/4xx). Collapses the\n * classify-and-throw boilerplate every custom HTTP-like reaction\n * would otherwise write into one line:\n *\n * ```ts\n * .on(\"OrderConfirmed\").do(async (event) => {\n * const response = await my_sdk.deliver(event);\n * await try_ok(response, { url: my_sdk.url, label: \"my_sdk\" });\n * // ...response was 2xx; continue with downstream work...\n * });\n * ```\n *\n * The {@link webhook} helper throws webhook-specific subclasses\n * ({@link WebhookError} / {@link NonRetryableWebhookError}) for\n * backward compatibility — both extend the generic classes thrown\n * here, so `instanceof RetryableHttpError` matches both webhook and\n * custom-integration errors uniformly.\n */\nexport async function tryOk(\n response: Response,\n options: TryOkOptions\n): Promise<void> {\n const disposition = classifyHttpResponse(response);\n if (disposition === \"ok\") return;\n\n let responseBody: string | undefined;\n try {\n responseBody = await response.text();\n } catch {\n // best-effort body capture; ignore read errors\n }\n\n const label = options.label ?? \"request\";\n const ErrorClass =\n disposition === \"retry\" ? RetryableHttpError : NonRetryableHttpError;\n throw new ErrorClass(`${label} ${options.url} responded ${response.status}`, {\n status: response.status,\n url: options.url,\n responseBody,\n });\n}\n","import { createHmac } from \"node:crypto\";\n\n/**\n * Compute the HMAC-SHA256 signature for an outbound webhook request.\n *\n * The signed payload is `${timestamp}.${body}` — Stripe-style. The\n * timestamp is included so the receiver can reject replays via a\n * window check, and the dot separator prevents `timestamp + body`\n * ambiguity (12 + 345 vs 123 + 45).\n *\n * Returns `{ signature, timestamp }` so the webhook helper can attach\n * both as headers — `X-Webhook-Signature: sha256=<hex>` and\n * `X-Webhook-Timestamp: <unix-seconds>` — for the receiver to verify\n * via `verifyWebhook` from `@rotorsoft/act-http/receiver`.\n *\n * `now` is exposed for tests; production callers should leave it\n * undefined so wall-clock is used.\n *\n * @internal Reachable from tests via the source path. Not re-exported\n * from the package's `./webhook` entry — the webhook helper calls\n * it internally, and operators don't need it directly.\n */\nexport function sign_request(\n body: string,\n secret: string,\n now: number = Math.floor(Date.now() / 1000)\n): { signature: string; timestamp: string } {\n const timestamp = String(now);\n const payload = `${timestamp}.${body}`;\n const hex = createHmac(\"sha256\", secret).update(payload).digest(\"hex\");\n return { signature: `sha256=${hex}`, timestamp };\n}\n","/**\n * @packageDocumentation\n * @module act-http/webhook\n *\n * Reaction-handler sugar for POSTing committed events to external URLs.\n *\n * Wraps `fetch` with timeouts, automatic `Idempotency-Key` derivation, and\n * status-classified errors. Designed to be composed with the reaction\n * options shipped in ACT-601 (`maxRetries`, `blockOnError`, `backoff`):\n *\n * ```ts\n * import { webhook } from \"@rotorsoft/act-http/webhook\";\n *\n * .on(\"OrderConfirmed\")\n * .do(\n * webhook({\n * url: \"https://api.example.com/webhooks/orders\",\n * headers: (e) => ({ Authorization: \"Bearer ...\" }),\n * body: (e) => ({ orderId: e.stream, total: e.data.total }),\n * timeoutMs: 5_000,\n * }),\n * { maxRetries: 5, backoff: { strategy: \"exponential\", baseMs: 200, maxMs: 30_000 } }\n * )\n * .to(resolver);\n * ```\n */\n\nimport type { Committed, ReactionHandler, Schemas } from \"@rotorsoft/act\";\nimport { classifyHttpResponse } from \"./classify.js\";\nimport { sign_request } from \"./sign.js\";\nimport {\n NonRetryableWebhookError,\n type WebhookConfig,\n WebhookError,\n} from \"./types.js\";\n\nexport type { HttpDisposition, TryOkOptions } from \"./classify.js\";\n// Documented in this subpath's README as public API since the helpers were\n// written — implemented, tested, and with a doc-comment describing external\n// use — but never actually exported (#1621). See rfcs/1621-webhook-classify-exports.md.\nexport { classifyHttpResponse, tryOk } from \"./classify.js\";\nexport type {\n HttpDeliveryErrorInit,\n WebhookBody,\n WebhookConfig,\n WebhookResolver,\n} from \"./types.js\";\nexport {\n NonRetryableHttpError,\n NonRetryableWebhookError,\n RetryableHttpError,\n WebhookError,\n} from \"./types.js\";\n\nfunction resolve<TEvents extends Schemas, T>(\n resolver: T | ((e: Committed<TEvents, keyof TEvents>) => T) | undefined,\n event: Committed<TEvents, keyof TEvents>,\n fallback: T\n): T {\n if (resolver === undefined) return fallback;\n return typeof resolver === \"function\"\n ? (resolver as (e: Committed<TEvents, keyof TEvents>) => T)(event)\n : resolver;\n}\n\n/** Case-insensitive lookup; returns true if a header is already set. */\nfunction has_header(headers: Record<string, string>, name: string): boolean {\n const lower = name.toLowerCase();\n for (const k of Object.keys(headers)) {\n if (k.toLowerCase() === lower) return true;\n }\n return false;\n}\n\n/**\n * Build a reaction handler that POSTs each event to an external URL.\n *\n * Behavior:\n *\n * - 2xx and 3xx return successfully.\n * - 5xx responses, network errors, and timeouts throw\n * {@link WebhookError} (`status: 0` for network/timeout). Drain\n * retries per the reaction's `maxRetries` / `backoff`.\n * - 4xx responses throw {@link NonRetryableWebhookError}, which\n * extends `NonRetryableError`. The drain finalizer blocks the\n * stream immediately (when `blockOnError` is true) without\n * consuming the retry budget.\n */\nexport function webhook<TEvents extends Schemas = Schemas>(\n config: WebhookConfig<TEvents>\n): ReactionHandler<TEvents, keyof TEvents> {\n const timeoutMs = config.timeoutMs ?? 5_000;\n const method = config.method ?? \"POST\";\n const fetch_impl = config.fetch ?? globalThis.fetch;\n\n // Named function: slice/act builders require non-anonymous reaction\n // handlers so lifecycle telemetry can attribute work.\n return async function webhook_deliver(event) {\n const url = resolve(config.url, event, \"\");\n\n const custom_headers = resolve(\n config.headers,\n event,\n {} as Record<string, string>\n );\n const headers: Record<string, string> = { ...custom_headers };\n\n if (!has_header(headers, \"content-type\")) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n if (!has_header(headers, \"idempotency-key\")) {\n const key = config.idempotencyKey\n ? config.idempotencyKey(event)\n : String(event.id);\n if (key !== null) headers[\"Idempotency-Key\"] = key;\n }\n\n const rawBody = resolve(config.body, event, event as unknown);\n const body =\n typeof rawBody === \"string\" ? rawBody : JSON.stringify(rawBody);\n\n if (config.secret && !has_header(headers, \"x-webhook-signature\")) {\n const { signature, timestamp } = sign_request(body, config.secret);\n headers[\"X-Webhook-Signature\"] = signature;\n if (!has_header(headers, \"x-webhook-timestamp\")) {\n headers[\"X-Webhook-Timestamp\"] = timestamp;\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n let response: Response;\n try {\n response = await fetch_impl(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n } catch (err) {\n const aborted = controller.signal.aborted;\n throw new WebhookError(\n aborted\n ? `webhook ${method} ${url} timed out after ${timeoutMs}ms`\n : `webhook ${method} ${url} failed: ${(err as Error).message}`,\n { status: 0, url }\n );\n } finally {\n clearTimeout(timer);\n }\n\n const disposition = classifyHttpResponse(response);\n if (disposition === \"ok\") return;\n\n let responseBody: string | undefined;\n try {\n responseBody = await response.text();\n } catch {\n // best-effort body capture; ignore read errors\n }\n\n const ErrorClass =\n disposition === \"retry\" ? WebhookError : NonRetryableWebhookError;\n throw new ErrorClass(\n `webhook ${method} ${url} responded ${response.status}`,\n { status: response.status, url, responseBody }\n );\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/webhook/types.ts","../../src/webhook/classify.ts","../../src/webhook/sign.ts","../../src/webhook/index.ts"],"sourcesContent":["import {\n type Committed,\n NonRetryableError,\n type Schemas,\n} from \"@rotorsoft/act\";\n\n/**\n * Function or static value resolver. Used so callers can pass either a\n * constant or a per-event function for headers / body / url.\n *\n * The static side `T` is constrained to non-function types so that a\n * passed `(event) => ...` is unambiguously typed as the function variant.\n */\nexport type WebhookResolver<TEvents extends Schemas, T> =\n | T\n | ((event: Committed<TEvents, keyof TEvents>) => T);\n\n/**\n * Plain-data body shape the helper accepts as a static value. Functions\n * are deliberately excluded so the union with the resolver function is\n * unambiguous at the call site (TypeScript can discriminate by shape).\n */\nexport type WebhookBody =\n | string\n | { readonly [k: string]: unknown }\n | readonly unknown[];\n\n/**\n * Configuration for {@link webhook}.\n *\n * @template TEvents - Event schemas; resolvers receive the typed committed event.\n */\nexport type WebhookConfig<TEvents extends Schemas = Schemas> = {\n /** Target URL — static string or per-event function. */\n readonly url: WebhookResolver<TEvents, string>;\n /** HTTP method. Defaults to `\"POST\"`. */\n readonly method?: \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n /**\n * Headers to send. Resolver may return a record per event. The\n * `Content-Type: application/json` and `Idempotency-Key` headers are\n * applied automatically; both can be overridden by returning a header\n * with the same name (case-insensitive).\n */\n readonly headers?: WebhookResolver<TEvents, Record<string, string>>;\n /**\n * Request body. Static plain data (object, array, string) or a\n * per-event function returning the same. Strings are sent as-is;\n * anything else is JSON-serialized. Defaults to the committed event\n * itself.\n */\n readonly body?:\n | WebhookBody\n | ((event: Committed<TEvents, keyof TEvents>) => WebhookBody);\n /**\n * Per-request timeout in milliseconds. Defaults to 5000.\n * The handler throws after the timeout via `AbortController`.\n *\n * The budget covers the **whole** exchange, not just the connection:\n * `fetch` resolves on response headers, so the timer stays armed through\n * the error-path body read as well. A receiver that answers with headers\n * and then stalls its body times out like any other overrun instead of\n * hanging the handler forever (#1701).\n */\n readonly timeoutMs?: number;\n /**\n * Override for the auto-generated `Idempotency-Key`. By default, the\n * helper sends `event.id` (the immutable, monotonic event identifier).\n * Return a string to override; return `null` to skip the header entirely.\n */\n readonly idempotencyKey?: (\n event: Committed<TEvents, keyof TEvents>\n ) => string | null;\n /**\n * Injection point for tests. Defaults to global `fetch`.\n */\n readonly fetch?: typeof fetch;\n /**\n * HMAC-SHA256 signing key. When set, the webhook helper attaches\n * two headers to every request:\n *\n * - `X-Webhook-Signature: sha256=<hex>` — HMAC of\n * `${timestamp}.${body}` (`body` is the final serialized payload)\n * - `X-Webhook-Timestamp: <unix-seconds>`\n *\n * Pair with `verifyWebhook` from `@rotorsoft/act-http/receiver` on\n * the receiving side. When undefined, no signature headers are\n * added — back-compat with consumers that don't need signing.\n *\n * Callers can override either header by returning it from the\n * `headers` resolver (case-insensitive), the same way the\n * `Idempotency-Key` and `Content-Type` defaults yield to caller\n * intent.\n */\n readonly secret?: string;\n};\n\n/**\n * Common fields carried on every HTTP delivery error in this package.\n */\nexport type HttpDeliveryErrorInit = {\n status: number;\n url: string;\n responseBody?: string;\n};\n\n/**\n * Thrown when an HTTP delivery fails in a way the drain pipeline\n * should retry: network failure, timeout, or 5xx response. `status` is\n * `0` for network / timeout errors, the HTTP status code otherwise.\n *\n * The class itself is the retry signal — if a reaction throws this,\n * drain treats it like any other error (counts against `maxRetries`,\n * paces with `backoff`). For permanent failures, throw\n * {@link NonRetryableHttpError} instead.\n *\n * Generic enough to cover any custom HTTP-like integration (gRPC\n * bridges, SDK-based reactions). {@link WebhookError} is a\n * webhook-specific subclass kept for backward compatibility.\n */\nexport class RetryableHttpError extends Error {\n readonly status: number;\n readonly url: string;\n readonly responseBody?: string;\n\n constructor(message: string, init: HttpDeliveryErrorInit) {\n super(message);\n this.name = \"RetryableHttpError\";\n this.status = init.status;\n this.url = init.url;\n this.responseBody = init.responseBody;\n }\n}\n\n/**\n * Thrown when an HTTP delivery returns a 3xx or 4xx response —\n * permanent client errors that won't recover on retry. Extends\n * {@link NonRetryableError} so the drain finalizer blocks the stream\n * on the first failed attempt (when `blockOnError` is true) — no\n * wasted retries on a malformed payload or wrong URL.\n *\n * Generic enough to cover any custom HTTP-like integration.\n * {@link NonRetryableWebhookError} is a webhook-specific subclass kept\n * for backward compatibility.\n */\nexport class NonRetryableHttpError extends NonRetryableError {\n readonly status: number;\n readonly url: string;\n readonly responseBody?: string;\n\n constructor(message: string, init: HttpDeliveryErrorInit) {\n super(message);\n this.name = \"NonRetryableHttpError\";\n this.status = init.status;\n this.url = init.url;\n this.responseBody = init.responseBody;\n }\n}\n\n/**\n * Webhook-specific subclass of {@link RetryableHttpError}. Thrown by\n * the {@link webhook} helper on 5xx responses, network failures, and\n * timeouts. Existing `instanceof WebhookError` checks continue to\n * work; new code targeting the generic HTTP integration shape can\n * catch {@link RetryableHttpError} instead and handle webhook +\n * custom integrations uniformly.\n */\nexport class WebhookError extends RetryableHttpError {\n constructor(message: string, init: HttpDeliveryErrorInit) {\n super(message, init);\n this.name = \"WebhookError\";\n }\n}\n\n/**\n * Webhook-specific subclass of {@link NonRetryableHttpError}. Thrown\n * by the {@link webhook} helper on 3xx/4xx responses. Existing\n * `instanceof NonRetryableWebhookError` checks continue to work; new\n * code can catch {@link NonRetryableHttpError} or\n * {@link NonRetryableError} for broader coverage.\n */\nexport class NonRetryableWebhookError extends NonRetryableHttpError {\n constructor(message: string, init: HttpDeliveryErrorInit) {\n super(message, init);\n this.name = \"NonRetryableWebhookError\";\n }\n}\n","import { NonRetryableHttpError, RetryableHttpError } from \"./types.js\";\n\n/**\n * Three buckets for an HTTP response from an outbound delivery:\n *\n * - `ok` — the receiver accepted the delivery (2xx). Stop and return.\n * - `retry` — the receiver had a transient problem (5xx). Throw a\n * retryable error; drain will pace the next attempt per `backoff`.\n * - `block` — the receiver rejected the delivery permanently (3xx\n * or 4xx). Throw a non-retryable error; drain blocks the stream\n * on the first failed attempt (when `blockOnError` is true) and\n * surfaces it via the `\"blocked\"` lifecycle event.\n *\n * The 3xx → `block` mapping is intentional: a redirect at the\n * delivery layer means the configured URL is wrong, and retrying\n * the same URL won't fix that. Manual operator review is the right\n * next step, which is what the block path produces.\n */\nexport type HttpDisposition = \"ok\" | \"retry\" | \"block\";\n\n/**\n * Classify an HTTP response as `ok` (2xx), `retry` (5xx), or\n * `block` (3xx, 4xx). The classification {@link webhook} uses\n * internally, lifted here so custom integrations (gRPC bridges,\n * SDK-based reactions, etc.) can apply the same retry semantics\n * without inventing a parallel rule.\n */\nexport function classifyHttpResponse(response: Response): HttpDisposition {\n if (response.ok) return \"ok\";\n if (response.status >= 500) return \"retry\";\n return \"block\";\n}\n\n/** Options for {@link tryOk}. */\nexport type TryOkOptions = {\n /** The endpoint that received the request. Surfaced on the thrown error and in its message. */\n url: string;\n /**\n * Label prefixed onto the error message — typically the\n * integration's identity (`\"webhook\"`, `\"my_sdk\"`, `\"grpc\"`).\n * Default: `\"request\"`.\n */\n label?: string;\n};\n\n/**\n * If `response` is 2xx, return. Otherwise, capture the response body\n * (best-effort) and throw a {@link RetryableHttpError} (for 5xx) or\n * {@link NonRetryableHttpError} (for 3xx/4xx). Collapses the\n * classify-and-throw boilerplate every custom HTTP-like reaction\n * would otherwise write into one line:\n *\n * ```ts\n * .on(\"OrderConfirmed\").do(async (event) => {\n * const response = await my_sdk.deliver(event);\n * await try_ok(response, { url: my_sdk.url, label: \"my_sdk\" });\n * // ...response was 2xx; continue with downstream work...\n * });\n * ```\n *\n * The {@link webhook} helper throws webhook-specific subclasses\n * ({@link WebhookError} / {@link NonRetryableWebhookError}) for\n * backward compatibility — both extend the generic classes thrown\n * here, so `instanceof RetryableHttpError` matches both webhook and\n * custom-integration errors uniformly.\n */\nexport async function tryOk(\n response: Response,\n options: TryOkOptions\n): Promise<void> {\n const disposition = classifyHttpResponse(response);\n if (disposition === \"ok\") return;\n\n let responseBody: string | undefined;\n try {\n responseBody = await response.text();\n } catch {\n // best-effort body capture; ignore read errors\n }\n\n const label = options.label ?? \"request\";\n const ErrorClass =\n disposition === \"retry\" ? RetryableHttpError : NonRetryableHttpError;\n throw new ErrorClass(`${label} ${options.url} responded ${response.status}`, {\n status: response.status,\n url: options.url,\n responseBody,\n });\n}\n","import { createHmac } from \"node:crypto\";\n\n/**\n * Compute the HMAC-SHA256 signature for an outbound webhook request.\n *\n * The signed payload is `${timestamp}.${body}` — Stripe-style. The\n * timestamp is included so the receiver can reject replays via a\n * window check, and the dot separator prevents `timestamp + body`\n * ambiguity (12 + 345 vs 123 + 45).\n *\n * Returns `{ signature, timestamp }` so the webhook helper can attach\n * both as headers — `X-Webhook-Signature: sha256=<hex>` and\n * `X-Webhook-Timestamp: <unix-seconds>` — for the receiver to verify\n * via `verifyWebhook` from `@rotorsoft/act-http/receiver`.\n *\n * `now` is exposed for tests; production callers should leave it\n * undefined so wall-clock is used.\n *\n * @internal Reachable from tests via the source path. Not re-exported\n * from the package's `./webhook` entry — the webhook helper calls\n * it internally, and operators don't need it directly.\n */\nexport function sign_request(\n body: string,\n secret: string,\n now: number = Math.floor(Date.now() / 1000)\n): { signature: string; timestamp: string } {\n const timestamp = String(now);\n const payload = `${timestamp}.${body}`;\n const hex = createHmac(\"sha256\", secret).update(payload).digest(\"hex\");\n return { signature: `sha256=${hex}`, timestamp };\n}\n","/**\n * @packageDocumentation\n * @module act-http/webhook\n *\n * Reaction-handler sugar for POSTing committed events to external URLs.\n *\n * Wraps `fetch` with timeouts, automatic `Idempotency-Key` derivation, and\n * status-classified errors. Designed to be composed with the reaction\n * options shipped in ACT-601 (`maxRetries`, `blockOnError`, `backoff`):\n *\n * ```ts\n * import { webhook } from \"@rotorsoft/act-http/webhook\";\n *\n * .on(\"OrderConfirmed\")\n * .do(\n * webhook({\n * url: \"https://api.example.com/webhooks/orders\",\n * headers: (e) => ({ Authorization: \"Bearer ...\" }),\n * body: (e) => ({ orderId: e.stream, total: e.data.total }),\n * timeoutMs: 5_000,\n * }),\n * { maxRetries: 5, backoff: { strategy: \"exponential\", baseMs: 200, maxMs: 30_000 } }\n * )\n * .to(resolver);\n * ```\n */\n\nimport type { Committed, ReactionHandler, Schemas } from \"@rotorsoft/act\";\nimport { classifyHttpResponse } from \"./classify.js\";\nimport { sign_request } from \"./sign.js\";\nimport {\n NonRetryableWebhookError,\n type WebhookConfig,\n WebhookError,\n} from \"./types.js\";\n\nexport type { HttpDisposition, TryOkOptions } from \"./classify.js\";\n// Documented in this subpath's README as public API since the helpers were\n// written — implemented, tested, and with a doc-comment describing external\n// use — but never actually exported (#1621). See rfcs/1621-webhook-classify-exports.md.\nexport { classifyHttpResponse, tryOk } from \"./classify.js\";\nexport type {\n HttpDeliveryErrorInit,\n WebhookBody,\n WebhookConfig,\n WebhookResolver,\n} from \"./types.js\";\nexport {\n NonRetryableHttpError,\n NonRetryableWebhookError,\n RetryableHttpError,\n WebhookError,\n} from \"./types.js\";\n\nfunction resolve<TEvents extends Schemas, T>(\n resolver: T | ((e: Committed<TEvents, keyof TEvents>) => T) | undefined,\n event: Committed<TEvents, keyof TEvents>,\n fallback: T\n): T {\n if (resolver === undefined) return fallback;\n return typeof resolver === \"function\"\n ? (resolver as (e: Committed<TEvents, keyof TEvents>) => T)(event)\n : resolver;\n}\n\n/** Case-insensitive lookup; returns true if a header is already set. */\nfunction has_header(headers: Record<string, string>, name: string): boolean {\n const lower = name.toLowerCase();\n for (const k of Object.keys(headers)) {\n if (k.toLowerCase() === lower) return true;\n }\n return false;\n}\n\n/**\n * Build a reaction handler that POSTs each event to an external URL.\n *\n * Behavior:\n *\n * - 2xx and 3xx return successfully.\n * - 5xx responses, network errors, and timeouts throw\n * {@link WebhookError} (`status: 0` for network/timeout). Drain\n * retries per the reaction's `maxRetries` / `backoff`.\n * - 4xx responses throw {@link NonRetryableWebhookError}, which\n * extends `NonRetryableError`. The drain finalizer blocks the\n * stream immediately (when `blockOnError` is true) without\n * consuming the retry budget.\n */\nexport function webhook<TEvents extends Schemas = Schemas>(\n config: WebhookConfig<TEvents>\n): ReactionHandler<TEvents, keyof TEvents> {\n const timeoutMs = config.timeoutMs ?? 5_000;\n const method = config.method ?? \"POST\";\n const fetch_impl = config.fetch ?? globalThis.fetch;\n\n // Named function: slice/act builders require non-anonymous reaction\n // handlers so lifecycle telemetry can attribute work.\n return async function webhook_deliver(event) {\n const url = resolve(config.url, event, \"\");\n\n const custom_headers = resolve(\n config.headers,\n event,\n {} as Record<string, string>\n );\n const headers: Record<string, string> = { ...custom_headers };\n\n if (!has_header(headers, \"content-type\")) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n if (!has_header(headers, \"idempotency-key\")) {\n const key = config.idempotencyKey\n ? config.idempotencyKey(event)\n : String(event.id);\n if (key !== null) headers[\"Idempotency-Key\"] = key;\n }\n\n const rawBody = resolve(config.body, event, event as unknown);\n const body =\n typeof rawBody === \"string\" ? rawBody : JSON.stringify(rawBody);\n\n if (config.secret && !has_header(headers, \"x-webhook-signature\")) {\n const { signature, timestamp } = sign_request(body, config.secret);\n headers[\"X-Webhook-Signature\"] = signature;\n if (!has_header(headers, \"x-webhook-timestamp\")) {\n headers[\"X-Webhook-Timestamp\"] = timestamp;\n }\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n // The timer has to outlive the fetch. `fetch` resolves on response\n // HEADERS, so clearing it as soon as the fetch settled left the\n // error-path body read below unbounded: a receiver that answers with\n // headers and then never ends the body hung the handler forever, with\n // no timer and no signal still watching it (#1701). A hang is worse\n // than a throw here — an overrun lease is re-claimed by the next\n // holder and the attempt never spends its retry budget loudly, so the\n // stream never reaches its `blockOnError` verdict (the #1418 shape).\n try {\n let response: Response;\n try {\n response = await fetch_impl(url, {\n method,\n headers,\n body,\n signal: controller.signal,\n });\n } catch (err) {\n const aborted = controller.signal.aborted;\n throw new WebhookError(\n aborted\n ? `webhook ${method} ${url} timed out after ${timeoutMs}ms`\n : `webhook ${method} ${url} failed: ${(err as Error).message}`,\n { status: 0, url }\n );\n }\n\n const disposition = classifyHttpResponse(response);\n if (disposition === \"ok\") return;\n\n let responseBody: string | undefined;\n try {\n responseBody = await response.text();\n } catch {\n // Best-effort body capture — a read that fails for its own reasons\n // (a reset mid-body) still reports the status below. An abort lands\n // here too, and that one is NOT best-effort: the timeout fired while\n // reading, so report the timeout rather than a response never read.\n if (controller.signal.aborted)\n throw new WebhookError(\n `webhook ${method} ${url} timed out after ${timeoutMs}ms reading the response body`,\n { status: response.status, url }\n );\n }\n\n const ErrorClass =\n disposition === \"retry\" ? WebhookError : NonRetryableWebhookError;\n throw new ErrorClass(\n `webhook ${method} ${url} responded ${response.status}`,\n { status: response.status, url, responseBody }\n );\n } finally {\n clearTimeout(timer);\n }\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rotorsoft/act-http",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.10.
|
|
4
|
+
"version": "1.10.10",
|
|
5
5
|
"description": "HTTP integrations for act apps — webhooks and SSE",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"typescript",
|
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"@hono/node-server": "^2.1.1",
|
|
94
|
-
"@rotorsoft/act": "^1.
|
|
95
|
-
"@rotorsoft/act-ops": "^0.2.
|
|
94
|
+
"@rotorsoft/act": "^1.32.4",
|
|
95
|
+
"@rotorsoft/act-ops": "^0.2.10",
|
|
96
96
|
"@trpc/server": ">=11.18.0",
|
|
97
97
|
"express": "^5.2.1",
|
|
98
98
|
"fastify": "^5.12.5",
|
|
@@ -124,7 +124,7 @@
|
|
|
124
124
|
"devDependencies": {
|
|
125
125
|
"@hono/node-server": "^2.1.1",
|
|
126
126
|
"@hono/zod-validator": "^0.9.1",
|
|
127
|
-
"@rotorsoft/act-ops": "^0.2.
|
|
127
|
+
"@rotorsoft/act-ops": "^0.2.10",
|
|
128
128
|
"@stryker-mutator/core": "^10.0.0",
|
|
129
129
|
"@stryker-mutator/vitest-runner": "^10.0.0",
|
|
130
130
|
"@trpc/server": "^11.18.0",
|