@shivaedev/webhooks 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,7 +19,7 @@ const sources = {
19
19
 
20
20
  ## Using the inbox
21
21
 
22
- A source has a name, a verification strategy, a signing key loaded through Effect Config, and event registrations. Strategies read delivery identity and event names from headers and verify the raw bytes before JSON decoding. Event schemas describe only the fields your application needs; the mapping returns typed job requests. No worker handler runs in the route.
22
+ A source has a name, a verification strategy, a signing key loaded through Effect Config, and event registrations. Strategies read delivery identity and event names from headers and verify the raw bytes before JSON decoding. Event schemas describe only the fields your application needs; the mapping returns typed job requests, directly or in an Effect. No worker handler runs in the route.
23
23
 
24
24
  ### Register receipts and queues
25
25
 
@@ -81,6 +81,23 @@ const Host = appHost({
81
81
 
82
82
  `databaseUrl`, `loader`, `Api`, `handlers` and `sessions` are the application's existing host inputs. The route above receives GitHub deliveries at `POST /webhooks/github`. It uses the existing `appHost.routes` option and the host's `maxBodySize`; the jobs client and SQL client must point at the same PostgreSQL database. Register workers separately with the job definitions' `handle` method, in the processes your application chooses. A missing signing key fails the route Layer with `ConfigError`; composing it into `Host` preserves that typed error for the application's startup to handle.
83
83
 
84
+ ### Decide with services
85
+
86
+ The `webhookEvent` mapping can return an Effect to read application services and the clock before accepting a verified delivery. Return an empty array to ignore it, or fail with a typed error to refuse it. Pure mappings keep the same signature.
87
+
88
+ ```ts
89
+ const issues = webhookEvent(IssueEvent, ({ issue }) =>
90
+ Effect.gen(function* () {
91
+ const preferences = yield* SenderPreferences;
92
+ const now = yield* Clock.currentTimeMillis;
93
+ if (!(yield* preferences.allows(issue.sender, now))) return [];
94
+ return [webhookJob(IndexIssue, { number: issue.number })];
95
+ }),
96
+ );
97
+ ```
98
+
99
+ `Clock` comes from `effect`; `SenderPreferences` and its `allows` method belong to the application, as does the sender field in its event schema. Supply the mapping's services to `webhookRoute` with `Layer.provide`; they remain required in the route Layer's type. `WebhookEvent<R, E>` and `WebhookSource<R, E>` preserve the mapping's service requirements and typed failures. The mapping runs after verification and schema decoding, inside the receipt transaction. An ignored delivery commits an ignored receipt with no jobs. A failed check rolls back the receipt and any transactional database changes; the sender can retry. Duplicate deliveries skip the mapping.
100
+
84
101
  ### Answers and retries
85
102
 
86
103
  | Request | Status | Body |
@@ -93,11 +110,11 @@ const Host = appHost({
93
110
  | Missing delivery ID or event header | 400 | `Invalid delivery` |
94
111
  | Payload rejected by its Schema | 400 | Empty |
95
112
  | Body exceeds the host limit | 413 | Empty |
96
- | Database or enqueue failure | 503 | Empty |
113
+ | Mapping, database or enqueue failure | 503 | Empty |
97
114
 
98
115
  Refused requests create no receipt and no jobs. Signatures are checked before decoding; unlisted events are verified and recorded without decoding. The signing key, signature and raw body stay out of responses, logs and spans.
99
116
 
100
- Database and enqueue failures log their redacted cause and `errorTag` at error level before the empty 503 response. The HTTP request span records the redacted failure with its original tag. Receiving a delivery runs under the `Webhooks.receive` span.
117
+ Mapping, database and enqueue failures log their redacted cause and `errorTag` at error level before the empty 503 response. The HTTP request span records the redacted failure with its original tag. Receiving a delivery runs under the `Webhooks.receive` span.
101
118
 
102
119
  The receipt insert and every enqueue use one SQL transaction. Concurrent copies wait on the receipt's unique key: a committed first copy makes the second a duplicate; a rolled-back first copy lets the second receive the delivery itself. If any enqueue fails, earlier enqueues and the receipt roll back, and the sender's retry can succeed. Receipts for ignored events are deduplicated too.
103
120
 
@@ -1,4 +1,11 @@
1
- import type { WebhookSource, WebhookStrategy } from "#server/registration.ts";
1
+ import { Effect } from "effect";
2
+ import type { WebhookEvent, WebhookSource, WebhookStrategy } from "#server/registration.ts";
2
3
  export declare const githubStrategy: WebhookStrategy;
3
- export declare function githubSource<R>(options: Omit<WebhookSource<R>, "strategy">): WebhookSource<R>;
4
+ type EventEffect<TEvents extends Readonly<Record<string, WebhookEvent<unknown, unknown>>>> = ReturnType<TEvents[keyof TEvents]["decode"]>;
5
+ export declare function githubSource<R, E = never>(options: Omit<WebhookSource<R, E>, "strategy">): WebhookSource<R, E>;
6
+ export declare function githubSource<const TEvents extends Readonly<Record<string, WebhookEvent<unknown, unknown>>>>(options: {
7
+ readonly events: TEvents;
8
+ readonly key: WebhookSource["key"];
9
+ }): WebhookSource<Effect.Services<EventEffect<TEvents>>, Effect.Error<EventEffect<TEvents>>>;
10
+ export {};
4
11
  //# sourceMappingURL=github.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/server/github.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG9E,eAAO,MAAM,cAAc,EAAE,eAG5B,CAAC;AAEF,wBAAgB,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAE7F"}
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/server/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG5F,eAAO,MAAM,cAAc,EAAE,eAG5B,CAAC;AAEF,KAAK,WAAW,CAAC,OAAO,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,MAAM,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AAE1I,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,CAAC,GAAG,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAChH,wBAAgB,YAAY,CAAC,KAAK,CAAC,OAAO,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE;IACrH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;CACnC,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/server/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAEnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAErE,MAAM,CAAC,MAAM,cAAc,GAAoB;IAC9C,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;IAC/F,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,qBAAqB,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,mBAAmB,EAAE,CAAC,CAAC;CAC/I,CAAC;AAEF,MAAM,UAAU,YAAY,CAAI,OAA2C;IAC1E,OAAO,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AACjD,CAAC"}
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/server/github.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAEnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAErE,MAAM,CAAC,MAAM,cAAc,GAAoB;IAC9C,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;IAC/F,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,qBAAqB,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,mBAAmB,EAAE,CAAC,CAAC;CAC/I,CAAC;AASF,MAAM,UAAU,YAAY,CAAe,OAA8C;IACxF,OAAO,EAAE,GAAG,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AACjD,CAAC"}
@@ -3,5 +3,5 @@ import { HttpServerResponse } from "effect/http";
3
3
  import { SqlClient } from "effect/sql";
4
4
  import type { PgBossService } from "@shivaedev/jobs/server/service.ts";
5
5
  import { type WebhookSource } from "#server/registration.ts";
6
- export declare const receive: <R>(name: string, source: WebhookSource<R>, key: Redacted.Redacted<string>, body: Uint8Array<ArrayBufferLike>, headers: Readonly<Record<string, string>>, jobs: PgBossService) => Effect.Effect<HttpServerResponse.HttpServerResponse, import("@shivaedev/jobs/server/error.ts").PgBossError | import("@shivaedev/jobs/server/error.ts").PgBossPayloadError | import("effect/Schema").SchemaError | import("effect/sql/SqlError").SqlError | import("../WebhookUnauthorized.ts").WebhookUnauthorized, R | SqlClient.SqlClient>;
6
+ export declare const receive: <R, E>(name: string, source: WebhookSource<R, E>, key: Redacted.Redacted<string>, body: Uint8Array<ArrayBufferLike>, headers: Readonly<Record<string, string>>, jobs: PgBossService) => Effect.Effect<HttpServerResponse.HttpServerResponse, E | import("@shivaedev/jobs/server/error.ts").PgBossError | import("@shivaedev/jobs/server/error.ts").PgBossPayloadError | import("effect/Schema").SchemaError | import("effect/sql/SqlError").SqlError | import("../WebhookUnauthorized.ts").WebhookUnauthorized, R | SqlClient.SqlClient>;
7
7
  //# sourceMappingURL=receive.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"receive.d.ts","sourceRoot":"","sources":["../../../src/server/internal/receive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAEjF,eAAO,MAAM,OAAO,GAA4C,CAAC,4fA+B/D,CAAC"}
1
+ {"version":3,"file":"receive.d.ts","sourceRoot":"","sources":["../../../src/server/internal/receive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAEjF,eAAO,MAAM,OAAO,GAA4C,CAAC,EAAE,CAAC,mgBA+BlE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"receive.js","sourceRoot":"","sources":["../../../src/server/internal/receive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAiB,MAAM,QAAQ,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EAAE,kBAAkB,EAAsB,MAAM,yBAAyB,CAAC;AAEjF,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,QAAQ,CAAC,EAC7D,IAAY,EACZ,MAAwB,EACxB,GAA8B,EAC9B,IAAgB,EAChB,OAAyC,EACzC,IAAmB;IAEnB,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACxD,IAAI,CAAC,CAAC,EAAE,IAAI,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,kBAAkB,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACtG,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC1F,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,OAAO,KAAK,CAAC,CAAC,GAAG,CAAC,eAAe,CAChC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,GAAG,CAAA;cAChB,IAAI,KAAK,EAAE,KAAK,KAAK;uEACoC,CAAC;QACrE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,kBAAkB,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5F,CAAC;QACD,MAAM,QAAQ,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChF,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAChF,KAAK,CAAC,CAAC,GAAG,CAAA,+CAA+C,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAA,GAAG,KAAK,EAAE,CAAC,CAAC,wBAAwB,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,mBAAmB,IAAI,sBAAsB,EAAE,EAAE,CAAC;QACzN,OAAO,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE;YAC5E,MAAM,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC;SACjG,CAAC,CAAC;IACJ,CAAC,CAAC,CACF,CAAC;AACH,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"receive.js","sourceRoot":"","sources":["../../../src/server/internal/receive.ts"],"names":[],"mappings":"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"}
@@ -8,12 +8,12 @@ export interface WebhookJob<R = never> {
8
8
  readonly [enqueue]: (jobs: PgBossService) => Effect.Effect<Option.Option<string>, PgBossError | PgBossPayloadError, R>;
9
9
  }
10
10
  export declare function webhookJob<const TName extends string, TPayload extends JobPayloadSchema>(queue: QueueDefinition<TName, TPayload>, payload: NoInfer<Schema.Schema.Type<TPayload>>): WebhookJob<TPayload["EncodingServices"]>;
11
- export interface WebhookEvent<R = never> {
12
- readonly decode: (body: Uint8Array) => Effect.Effect<readonly WebhookJob<R>[], Schema.SchemaError, R>;
11
+ export interface WebhookEvent<R = never, E = never> {
12
+ readonly decode: (body: Uint8Array) => Effect.Effect<readonly WebhookJob<R>[], Schema.SchemaError | E, R>;
13
13
  }
14
- export declare function webhookEvent<TSchema extends Schema.Constraint, R = never>(schema: TSchema, jobs: (payload: TSchema["Type"]) => readonly WebhookJob<R>[]): WebhookEvent<TSchema["DecodingServices"] | R>;
15
- export interface WebhookSource<R = never> {
16
- readonly events: Readonly<Record<string, WebhookEvent<R>>>;
14
+ export declare function webhookEvent<TSchema extends Schema.Constraint, R = never, E = never>(schema: TSchema, jobs: (payload: TSchema["Type"]) => readonly WebhookJob<R>[] | Effect.Effect<readonly WebhookJob<R>[], E, R>): WebhookEvent<TSchema["DecodingServices"] | R, E>;
15
+ export interface WebhookSource<R = never, E = never> {
16
+ readonly events: Readonly<Record<string, WebhookEvent<R, E>>>;
17
17
  readonly key: Config.Config<Redacted.Redacted<string>>;
18
18
  readonly strategy: WebhookStrategy;
19
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../../src/server/registration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAC9F,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACvF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAE1E,QAAA,MAAM,OAAO,eAA+B,CAAC;AAE7C,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,KAAK;IACpC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,kBAAkB,EAAE,CAAC,CAAC,CAAC;CACvH;AAED,wBAAgB,UAAU,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,QAAQ,SAAS,gBAAgB,EACvF,KAAK,EAAE,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,EACvC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAC5C,UAAU,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAE1C;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,KAAK;IACtC,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;CACtG;AAED,wBAAgB,YAAY,CAAC,OAAO,SAAS,MAAM,CAAC,UAAU,EAAE,CAAC,GAAG,KAAK,EACxE,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,GAC1D,YAAY,CAAC,OAAO,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAI/C;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACvD,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK;QAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IAC1I,QAAQ,CAAC,MAAM,EAAE,CAChB,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACzC,GAAG,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,KAC1B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;CAC9C;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,aAAa,+EAE5F"}
1
+ {"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../../src/server/registration.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,10 @@ export function webhookJob(queue, payload) {
5
5
  }
6
6
  export function webhookEvent(schema, jobs) {
7
7
  return {
8
- decode: (body) => Schema.decodeUnknownEffect(Schema.fromJsonString(schema))(new TextDecoder().decode(body)).pipe(Effect.map(jobs)),
8
+ decode: (body) => Schema.decodeUnknownEffect(Schema.fromJsonString(schema))(new TextDecoder().decode(body)).pipe(Effect.flatMap((payload) => {
9
+ const result = jobs(payload);
10
+ return Effect.isEffect(result) ? result : Effect.succeed(result);
11
+ })),
9
12
  };
10
13
  }
11
14
  export function enqueueWebhookJobs(requests, jobs) {
@@ -1 +1 @@
1
- {"version":3,"file":"registration.js","sourceRoot":"","sources":["../../src/server/registration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,MAAM,EAA8B,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMjF,MAAM,OAAO,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAM7C,MAAM,UAAU,UAAU,CACzB,KAAuC,EACvC,OAA8C;IAE9C,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;AAC9D,CAAC;AAMD,MAAM,UAAU,YAAY,CAC3B,MAAe,EACf,IAA4D;IAE5D,OAAO;QACN,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;KAClI,CAAC;AACH,CAAC;AAiBD,MAAM,UAAU,kBAAkB,CAAI,QAAkC,EAAE,IAAmB;IAC5F,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACtE,CAAC"}
1
+ {"version":3,"file":"registration.js","sourceRoot":"","sources":["../../src/server/registration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,MAAM,EAA8B,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMjF,MAAM,OAAO,GAAG,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAM7C,MAAM,UAAU,UAAU,CACzB,KAAuC,EACvC,OAA8C;IAE9C,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;AAC9D,CAAC;AAMD,MAAM,UAAU,YAAY,CAC3B,MAAe,EACf,IAA4G;IAE5G,OAAO;QACN,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAChB,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAC7F,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClE,CAAC,CAAC,CACF;KACF,CAAC;AACH,CAAC;AAiBD,MAAM,UAAU,kBAAkB,CAAI,QAAkC,EAAE,IAAmB;IAC5F,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACtE,CAAC"}
@@ -1,12 +1,18 @@
1
- import { type Context, Layer } from "effect";
1
+ import { type Config, type Context, Layer } from "effect";
2
2
  import { HttpRouter } from "effect/http";
3
3
  import type { SqlClient } from "effect/sql";
4
4
  import type { PgBossService } from "@shivaedev/jobs/server/service.ts";
5
5
  import type { WebhookSource } from "#server/registration.ts";
6
- export interface WebhookRouteOptions<TJobs, R> {
6
+ export interface WebhookRouteOptions<TJobs, R, E = never> {
7
7
  readonly basePath: `/${string}`;
8
8
  readonly jobs: Context.Key<TJobs, PgBossService>;
9
- readonly sources: Readonly<Record<string, WebhookSource<R>>>;
9
+ readonly sources: Readonly<Record<string, WebhookSource<R, E>>>;
10
10
  }
11
- export declare function webhookRoute<TJobs, R>(options: WebhookRouteOptions<TJobs, R>): Layer.Layer<never, import("effect/Config").ConfigError, HttpRouter.HttpRouter | HttpRouter.Request<"Error", unknown> | SqlClient.SqlClient | Exclude<R, import("effect/Scope").Scope> | Exclude<TJobs, import("effect/Scope").Scope>>;
11
+ type SourceServices<T> = T extends WebhookSource<infer R, unknown> ? R : never;
12
+ type RouteLayer<TJobs, R> = Layer.Layer<never, Config.ConfigError, HttpRouter.HttpRouter | TJobs | R | SqlClient.SqlClient>;
13
+ export declare function webhookRoute<TJobs, R, E = never>(options: WebhookRouteOptions<TJobs, R, E>): RouteLayer<TJobs, R>;
14
+ export declare function webhookRoute<TJobs, const TSources extends Readonly<Record<string, WebhookSource<unknown, unknown>>>>(options: Pick<WebhookRouteOptions<TJobs, never>, "basePath" | "jobs"> & {
15
+ readonly sources: TSources;
16
+ }): RouteLayer<TJobs, SourceServices<TSources[keyof TSources]>>;
17
+ export {};
12
18
  //# sourceMappingURL=route.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/server/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,OAAO,EAAU,KAAK,EAAiB,MAAM,QAAQ,CAAC;AAC3E,OAAO,EAAE,UAAU,EAA6C,MAAM,aAAa,CAAC;AAEpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,mBAAmB,CAAC,KAAK,EAAE,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,IAAI,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAC7D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,mBAAmB,CAAC,KAAK,EAAE,CAAC,CAAC,yOAuC5E"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/server/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,MAAM,EAAE,KAAK,OAAO,EAAU,KAAK,EAA4B,MAAM,QAAQ,CAAC;AACnG,OAAO,EAAE,UAAU,EAA6C,MAAM,aAAa,CAAC;AAEpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,WAAW,mBAAmB,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK;IACvD,QAAQ,CAAC,QAAQ,EAAE,IAAI,MAAM,EAAE,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;CAChE;AAED,KAAK,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAC/E,KAAK,UAAU,CAAC,KAAK,EAAE,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,UAAU,GAAG,KAAK,GAAG,CAAC,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;AAE5H,wBAAgB,YAAY,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,mBAAmB,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACnH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnH,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,GACpG,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,QAAQ,CAAC,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC"}
@@ -1,4 +1,4 @@
1
- import { Cause, Effect, Layer } from "effect";
1
+ import { Cause, Effect, Layer, Predicate } from "effect";
2
2
  import { HttpRouter, HttpServerRespondable, HttpServerResponse } from "effect/http";
3
3
  import * as HttpStatus from "effect/http/HttpStatus";
4
4
  import { redactDiagnosticCause } from "@shivaedev/errors/redactCause.ts";
@@ -25,8 +25,9 @@ export function webhookRoute(options) {
25
25
  }).pipe(Effect.catchTag("RequestBodyTooLarge", () => Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("PayloadTooLarge") }))), Effect.catchTag("WebhookUnauthorized", () => Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("Unauthorized") }))), Effect.catchTag("SchemaError", () => Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("BadRequest") }))), Effect.catch((error) => {
26
26
  const response = HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") });
27
27
  // Preserve the failure for HTTP tracing without attaching the request's credentials to a synthetic 503 error.
28
- const cause = redactDiagnosticCause(Cause.fail(Object.assign(error, { [HttpServerRespondable.symbol]: () => Effect.succeed(response) })));
29
- return Effect.logError("Webhook receipt failed", cause).pipe(Effect.annotateLogs({ errorTag: error._tag }), Effect.andThen(Effect.failCause(cause)));
28
+ const failure = Predicate.isObject(error) ? error : new Error("Webhook receipt failed", { cause: error });
29
+ const cause = redactDiagnosticCause(Cause.fail(Object.assign(failure, { [HttpServerRespondable.symbol]: () => Effect.succeed(response) })));
30
+ return Effect.logError("Webhook receipt failed", cause).pipe(Effect.annotateLogs({ errorTag: Predicate.hasProperty(error, "_tag") ? error._tag : "Unknown" }), Effect.andThen(Effect.failCause(cause)));
30
31
  })));
31
32
  }));
32
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/server/route.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/server/route.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/webhooks",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Verified webhook receipts and transactional job delivery over Effect HTTP",
5
5
  "keywords": [
6
6
  "effect"
@@ -47,15 +47,15 @@
47
47
  "@shivaedev/errors": "0.1.2",
48
48
  "@shivaedev/github": "0.8.0",
49
49
  "@shivaedev/http": "0.3.0",
50
- "@shivaedev/jobs": "0.23.0",
51
- "@shivaedev/sql": "0.26.0"
50
+ "@shivaedev/jobs": "0.23.1",
51
+ "@shivaedev/sql": "0.26.1"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@effect/platform-node": "4.0.6",
55
- "@shivaedev/quality": "0.46.0",
56
- "@shivaedev/server": "0.6.0",
57
- "@shivaedev/test-sql": "0.12.0",
58
- "@shivaedev/test-story": "0.25.0",
55
+ "@shivaedev/quality": "0.47.0",
56
+ "@shivaedev/server": "0.8.0",
57
+ "@shivaedev/test-sql": "0.14.0",
58
+ "@shivaedev/test-story": "0.26.0",
59
59
  "@types/node": "24.19.2",
60
60
  "effect": "4.0.6",
61
61
  "pg-boss": "12.38.0",
@@ -1,6 +1,6 @@
1
1
  import { Effect } from "effect";
2
2
  import { verifyWebhook } from "@shivaedev/github/verifyWebhook.ts";
3
- import type { WebhookSource, WebhookStrategy } from "#server/registration.ts";
3
+ import type { WebhookEvent, WebhookSource, WebhookStrategy } from "#server/registration.ts";
4
4
  import { WebhookUnauthorized } from "#server/WebhookUnauthorized.ts";
5
5
 
6
6
  export const githubStrategy: WebhookStrategy = {
@@ -8,6 +8,13 @@ export const githubStrategy: WebhookStrategy = {
8
8
  verify: (body, headers, key) => verifyWebhook(body, headers["x-hub-signature-256"], key).pipe(Effect.mapError(() => new WebhookUnauthorized())),
9
9
  };
10
10
 
11
- export function githubSource<R>(options: Omit<WebhookSource<R>, "strategy">): WebhookSource<R> {
11
+ type EventEffect<TEvents extends Readonly<Record<string, WebhookEvent<unknown, unknown>>>> = ReturnType<TEvents[keyof TEvents]["decode"]>;
12
+
13
+ export function githubSource<R, E = never>(options: Omit<WebhookSource<R, E>, "strategy">): WebhookSource<R, E>;
14
+ export function githubSource<const TEvents extends Readonly<Record<string, WebhookEvent<unknown, unknown>>>>(options: {
15
+ readonly events: TEvents;
16
+ readonly key: WebhookSource["key"];
17
+ }): WebhookSource<Effect.Services<EventEffect<TEvents>>, Effect.Error<EventEffect<TEvents>>>;
18
+ export function githubSource<R, E = never>(options: Omit<WebhookSource<R, E>, "strategy">): WebhookSource<R, E> {
12
19
  return { ...options, strategy: githubStrategy };
13
20
  }
@@ -5,9 +5,9 @@ import { SqlClient } from "effect/sql";
5
5
  import type { PgBossService } from "@shivaedev/jobs/server/service.ts";
6
6
  import { enqueueWebhookJobs, type WebhookSource } from "#server/registration.ts";
7
7
 
8
- export const receive = Effect.fn("Webhooks.receive")(function* <R>(
8
+ export const receive = Effect.fn("Webhooks.receive")(function* <R, E>(
9
9
  name: string,
10
- source: WebhookSource<R>,
10
+ source: WebhookSource<R, E>,
11
11
  key: Redacted.Redacted<string>,
12
12
  body: Uint8Array,
13
13
  headers: Readonly<Record<string, string>>,
@@ -17,21 +17,27 @@ export function webhookJob<const TName extends string, TPayload extends JobPaylo
17
17
  return { [enqueue]: (jobs) => jobs.enqueue(queue, payload) };
18
18
  }
19
19
 
20
- export interface WebhookEvent<R = never> {
21
- readonly decode: (body: Uint8Array) => Effect.Effect<readonly WebhookJob<R>[], Schema.SchemaError, R>;
20
+ export interface WebhookEvent<R = never, E = never> {
21
+ readonly decode: (body: Uint8Array) => Effect.Effect<readonly WebhookJob<R>[], Schema.SchemaError | E, R>;
22
22
  }
23
23
 
24
- export function webhookEvent<TSchema extends Schema.Constraint, R = never>(
24
+ export function webhookEvent<TSchema extends Schema.Constraint, R = never, E = never>(
25
25
  schema: TSchema,
26
- jobs: (payload: TSchema["Type"]) => readonly WebhookJob<R>[],
27
- ): WebhookEvent<TSchema["DecodingServices"] | R> {
26
+ jobs: (payload: TSchema["Type"]) => readonly WebhookJob<R>[] | Effect.Effect<readonly WebhookJob<R>[], E, R>,
27
+ ): WebhookEvent<TSchema["DecodingServices"] | R, E> {
28
28
  return {
29
- decode: (body) => Schema.decodeUnknownEffect(Schema.fromJsonString(schema))(new TextDecoder().decode(body)).pipe(Effect.map(jobs)),
29
+ decode: (body) =>
30
+ Schema.decodeUnknownEffect(Schema.fromJsonString(schema))(new TextDecoder().decode(body)).pipe(
31
+ Effect.flatMap((payload) => {
32
+ const result = jobs(payload);
33
+ return Effect.isEffect(result) ? result : Effect.succeed(result);
34
+ }),
35
+ ),
30
36
  };
31
37
  }
32
38
 
33
- export interface WebhookSource<R = never> {
34
- readonly events: Readonly<Record<string, WebhookEvent<R>>>;
39
+ export interface WebhookSource<R = never, E = never> {
40
+ readonly events: Readonly<Record<string, WebhookEvent<R, E>>>;
35
41
  readonly key: Config.Config<Redacted.Redacted<string>>;
36
42
  readonly strategy: WebhookStrategy;
37
43
  }
@@ -1,4 +1,4 @@
1
- import { Cause, type Context, Effect, Layer, type Redacted } from "effect";
1
+ import { Cause, type Config, type Context, Effect, Layer, Predicate, type Redacted } from "effect";
2
2
  import { HttpRouter, HttpServerRespondable, HttpServerResponse } from "effect/http";
3
3
  import * as HttpStatus from "effect/http/HttpStatus";
4
4
  import type { SqlClient } from "effect/sql";
@@ -8,13 +8,20 @@ import type { PgBossService } from "@shivaedev/jobs/server/service.ts";
8
8
  import { receive } from "#server/internal/receive.ts";
9
9
  import type { WebhookSource } from "#server/registration.ts";
10
10
 
11
- export interface WebhookRouteOptions<TJobs, R> {
11
+ export interface WebhookRouteOptions<TJobs, R, E = never> {
12
12
  readonly basePath: `/${string}`;
13
13
  readonly jobs: Context.Key<TJobs, PgBossService>;
14
- readonly sources: Readonly<Record<string, WebhookSource<R>>>;
14
+ readonly sources: Readonly<Record<string, WebhookSource<R, E>>>;
15
15
  }
16
16
 
17
- export function webhookRoute<TJobs, R>(options: WebhookRouteOptions<TJobs, R>) {
17
+ type SourceServices<T> = T extends WebhookSource<infer R, unknown> ? R : never;
18
+ type RouteLayer<TJobs, R> = Layer.Layer<never, Config.ConfigError, HttpRouter.HttpRouter | TJobs | R | SqlClient.SqlClient>;
19
+
20
+ export function webhookRoute<TJobs, R, E = never>(options: WebhookRouteOptions<TJobs, R, E>): RouteLayer<TJobs, R>;
21
+ export function webhookRoute<TJobs, const TSources extends Readonly<Record<string, WebhookSource<unknown, unknown>>>>(
22
+ options: Pick<WebhookRouteOptions<TJobs, never>, "basePath" | "jobs"> & { readonly sources: TSources },
23
+ ): RouteLayer<TJobs, SourceServices<TSources[keyof TSources]>>;
24
+ export function webhookRoute<TJobs, R, E = never>(options: WebhookRouteOptions<TJobs, R, E>) {
18
25
  return Layer.unwrap(
19
26
  Effect.gen(function* () {
20
27
  const context = yield* Effect.context<TJobs | R | SqlClient.SqlClient>();
@@ -43,9 +50,12 @@ export function webhookRoute<TJobs, R>(options: WebhookRouteOptions<TJobs, R>) {
43
50
  Effect.catch((error) => {
44
51
  const response = HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") });
45
52
  // Preserve the failure for HTTP tracing without attaching the request's credentials to a synthetic 503 error.
46
- const cause = redactDiagnosticCause(Cause.fail(Object.assign(error, { [HttpServerRespondable.symbol]: () => Effect.succeed(response) })));
53
+ const failure = Predicate.isObject(error) ? error : new Error("Webhook receipt failed", { cause: error });
54
+ const cause = redactDiagnosticCause(
55
+ Cause.fail(Object.assign(failure, { [HttpServerRespondable.symbol]: () => Effect.succeed(response) })),
56
+ );
47
57
  return Effect.logError("Webhook receipt failed", cause).pipe(
48
- Effect.annotateLogs({ errorTag: error._tag }),
58
+ Effect.annotateLogs({ errorTag: Predicate.hasProperty(error, "_tag") ? error._tag : "Unknown" }),
49
59
  Effect.andThen(Effect.failCause(cause)),
50
60
  );
51
61
  }),