@shivaedev/webhooks 0.1.3 → 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.
Files changed (66) hide show
  1. package/README.md +12 -13
  2. package/dist/server/WebhookUnauthorized.d.ts.map +1 -0
  3. package/dist/server/WebhookUnauthorized.js.map +1 -0
  4. package/dist/{github.d.ts → server/github.d.ts} +1 -1
  5. package/dist/server/github.d.ts.map +1 -0
  6. package/dist/{github.js → server/github.js} +1 -1
  7. package/dist/server/github.js.map +1 -0
  8. package/dist/server/internal/Receipt.d.ts +14 -0
  9. package/dist/server/internal/Receipt.d.ts.map +1 -0
  10. package/dist/server/internal/Receipt.js +10 -0
  11. package/dist/server/internal/Receipt.js.map +1 -0
  12. package/dist/server/internal/receiptMigrations.d.ts +7 -0
  13. package/dist/server/internal/receiptMigrations.d.ts.map +1 -0
  14. package/dist/server/internal/receiptMigrations.js +12 -0
  15. package/dist/server/internal/receiptMigrations.js.map +1 -0
  16. package/dist/{internal → server/internal}/receive.d.ts +1 -1
  17. package/dist/server/internal/receive.d.ts.map +1 -0
  18. package/dist/{internal → server/internal}/receive.js +4 -4
  19. package/dist/server/internal/receive.js.map +1 -0
  20. package/dist/server/migrations/0001_create_receipts.d.ts +5 -0
  21. package/dist/server/migrations/0001_create_receipts.d.ts.map +1 -0
  22. package/dist/server/migrations/0001_create_receipts.js +15 -0
  23. package/dist/server/migrations/0001_create_receipts.js.map +1 -0
  24. package/dist/server/migrations/0002_receipt_job_ids.d.ts +5 -0
  25. package/dist/server/migrations/0002_receipt_job_ids.d.ts.map +1 -0
  26. package/dist/server/migrations/0002_receipt_job_ids.js +11 -0
  27. package/dist/server/migrations/0002_receipt_job_ids.js.map +1 -0
  28. package/dist/{registration.d.ts → server/registration.d.ts} +1 -1
  29. package/dist/server/registration.d.ts.map +1 -0
  30. package/dist/server/registration.js.map +1 -0
  31. package/dist/{route.d.ts → server/route.d.ts} +2 -2
  32. package/dist/server/route.d.ts.map +1 -0
  33. package/dist/{route.js → server/route.js} +11 -5
  34. package/dist/server/route.js.map +1 -0
  35. package/dist/server/webhookReceipts.d.ts.map +1 -0
  36. package/dist/server/webhookReceipts.js +19 -0
  37. package/dist/server/webhookReceipts.js.map +1 -0
  38. package/package.json +11 -11
  39. package/src/{github.ts → server/github.ts} +2 -2
  40. package/src/server/internal/Receipt.ts +10 -0
  41. package/src/server/internal/receiptMigrations.ts +12 -0
  42. package/src/{internal → server/internal}/receive.ts +4 -4
  43. package/src/server/migrations/0001_create_receipts.ts +15 -0
  44. package/src/server/migrations/0002_receipt_job_ids.ts +11 -0
  45. package/src/{registration.ts → server/registration.ts} +1 -1
  46. package/src/{route.ts → server/route.ts} +15 -6
  47. package/src/server/webhookReceipts.ts +20 -0
  48. package/dist/WebhookUnauthorized.d.ts.map +0 -1
  49. package/dist/WebhookUnauthorized.js.map +0 -1
  50. package/dist/github.d.ts.map +0 -1
  51. package/dist/github.js.map +0 -1
  52. package/dist/internal/receive.d.ts.map +0 -1
  53. package/dist/internal/receive.js.map +0 -1
  54. package/dist/registration.d.ts.map +0 -1
  55. package/dist/registration.js.map +0 -1
  56. package/dist/route.d.ts.map +0 -1
  57. package/dist/route.js.map +0 -1
  58. package/dist/webhookReceipts.d.ts.map +0 -1
  59. package/dist/webhookReceipts.js +0 -23
  60. package/dist/webhookReceipts.js.map +0 -1
  61. package/src/webhookReceipts.ts +0 -24
  62. /package/dist/{WebhookUnauthorized.d.ts → server/WebhookUnauthorized.d.ts} +0 -0
  63. /package/dist/{WebhookUnauthorized.js → server/WebhookUnauthorized.js} +0 -0
  64. /package/dist/{registration.js → server/registration.js} +0 -0
  65. /package/dist/{webhookReceipts.d.ts → server/webhookReceipts.d.ts} +0 -0
  66. /package/src/{WebhookUnauthorized.ts → server/WebhookUnauthorized.ts} +0 -0
package/README.md CHANGED
@@ -23,19 +23,21 @@ A source has a name, a verification strategy, a signing key loaded through Effec
23
23
 
24
24
  ### Register receipts and queues
25
25
 
26
- Register `webhookReceipts` in the host's `migrations.components` alongside `pgBossSchema()`. The normal migration mode applies it; `check` reports a missing component, and `wait` waits for it. The receipt table is `webhook_receipts`, keyed by `(source, delivery_id)`, with the event, a UTC `timestamptz` received time, `queued` or `ignored` outcome, and a JSON array of queued job IDs.
26
+ Register `webhookReceipts` in the host's `migrations.components` alongside `pgBossSchema()`. The normal migration mode applies it; `check` reports missing or pending receipt migrations, and `wait` waits for them. The receipt table is `webhook_receipts`, keyed by `(source, delivery_id)`, with the event, a UTC `timestamptz` received time, `queued` or `ignored` outcome, and a `text[]` column of queued job IDs. The component applies numbered migrations recorded in `webhook_receipts_migrations`, preserving existing receipts when their schema changes.
27
+
28
+ When upgrading a database whose `job_ids` column is JSON, stop the older receivers, apply `webhookReceipts`, then start the new receivers. Direct SQL readers use PostgreSQL array operations for `job_ids`.
27
29
 
28
30
  ```ts
29
31
  import { Config, Effect, Layer, Redacted, Schema } from "effect";
30
32
  import { SqlClient } from "effect/sql";
31
33
  import { defineQueue } from "@shivaedev/jobs/server/definition.ts";
32
34
  import { pgBossSchema } from "@shivaedev/jobs/server/schema.ts";
33
- import { makePgBoss } from "@shivaedev/jobs/server/service.ts";
35
+ import { makePgBoss, pgBossLayer } from "@shivaedev/jobs/server/service.ts";
34
36
  import { appHost } from "@shivaedev/server/appHost.ts";
35
- import { githubSource } from "@shivaedev/webhooks/github.ts";
36
- import { webhookEvent, webhookJob } from "@shivaedev/webhooks/registration.ts";
37
- import { webhookRoute } from "@shivaedev/webhooks/route.ts";
38
- import { webhookReceipts } from "@shivaedev/webhooks/webhookReceipts.ts";
37
+ import { githubSource } from "@shivaedev/webhooks/server/github.ts";
38
+ import { webhookEvent, webhookJob } from "@shivaedev/webhooks/server/registration.ts";
39
+ import { webhookRoute } from "@shivaedev/webhooks/server/route.ts";
40
+ import { webhookReceipts } from "@shivaedev/webhooks/server/webhookReceipts.ts";
39
41
 
40
42
  const Jobs = makePgBoss("app/Jobs");
41
43
  const IndexIssue = defineQueue({
@@ -62,11 +64,7 @@ const Host = appHost({
62
64
  },
63
65
  application: Effect.gen(function* () {
64
66
  const sql = yield* SqlClient.SqlClient;
65
- const jobs = Jobs.layer({
66
- connectionString: Redacted.value(databaseUrl),
67
- jobs: [IndexIssue],
68
- migrate: false,
69
- });
67
+ const jobs = Jobs.layer({ jobs: [IndexIssue] }).pipe(Layer.provide(pgBossLayer({ connectionString: databaseUrl, migrate: false })));
70
68
  return {
71
69
  group: Api,
72
70
  handlers,
@@ -75,14 +73,13 @@ const Host = appHost({
75
73
  routes: webhookRoute({ basePath: "/webhooks", jobs: Jobs, sources }).pipe(
76
74
  Layer.provide(jobs),
77
75
  Layer.provide(Layer.succeed(SqlClient.SqlClient, sql)),
78
- Layer.orDie,
79
76
  ),
80
77
  };
81
78
  }),
82
79
  });
83
80
  ```
84
81
 
85
- `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.
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.
86
83
 
87
84
  ### Answers and retries
88
85
 
@@ -100,6 +97,8 @@ const Host = appHost({
100
97
 
101
98
  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.
102
99
 
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.
101
+
103
102
  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.
104
103
 
105
104
  ### Other senders
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookUnauthorized.d.ts","sourceRoot":"","sources":["../../src/server/WebhookUnauthorized.ts"],"names":[],"mappings":";;;AAEA,qBAAa,mBAAoB,SAAQ,wBAAuC;CAAG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebhookUnauthorized.js","sourceRoot":"","sources":["../../src/server/WebhookUnauthorized.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,MAAM,OAAO,mBAAoB,SAAQ,IAAI,CAAC,WAAW,CAAC,qBAAqB,CAAC;CAAG"}
@@ -1,4 +1,4 @@
1
- import type { WebhookSource, WebhookStrategy } from "#registration.ts";
1
+ import type { WebhookSource, WebhookStrategy } from "#server/registration.ts";
2
2
  export declare const githubStrategy: WebhookStrategy;
3
3
  export declare function githubSource<R>(options: Omit<WebhookSource<R>, "strategy">): WebhookSource<R>;
4
4
  //# sourceMappingURL=github.d.ts.map
@@ -0,0 +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,6 +1,6 @@
1
1
  import { Effect } from "effect";
2
2
  import { verifyWebhook } from "@shivaedev/github/verifyWebhook.ts";
3
- import { WebhookUnauthorized } from "#WebhookUnauthorized.ts";
3
+ import { WebhookUnauthorized } from "#server/WebhookUnauthorized.ts";
4
4
  export const githubStrategy = {
5
5
  delivery: (headers) => ({ event: headers["x-github-event"], id: headers["x-github-delivery"] }),
6
6
  verify: (body, headers, key) => verifyWebhook(body, headers["x-hub-signature-256"], key).pipe(Effect.mapError(() => new WebhookUnauthorized())),
@@ -0,0 +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"}
@@ -0,0 +1,14 @@
1
+ import { Schema } from "effect";
2
+ export declare const Receipt: Schema.encodeKeys<Schema.Struct<{
3
+ readonly deliveryId: Schema.String;
4
+ readonly event: Schema.String;
5
+ readonly jobIds: Schema.$Array<Schema.String>;
6
+ readonly outcome: Schema.Literals<readonly ["queued", "ignored"]>;
7
+ readonly receivedAt: Schema.Date;
8
+ readonly source: Schema.String;
9
+ }>, {
10
+ readonly deliveryId: "delivery_id";
11
+ readonly jobIds: "job_ids";
12
+ readonly receivedAt: "received_at";
13
+ }>;
14
+ //# sourceMappingURL=Receipt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Receipt.d.ts","sourceRoot":"","sources":["../../../src/server/internal/Receipt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,eAAO,MAAM,OAAO;;;;;;;;;;;EAOmF,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { Schema } from "effect";
2
+ export const Receipt = Schema.Struct({
3
+ deliveryId: Schema.String,
4
+ event: Schema.String,
5
+ jobIds: Schema.Array(Schema.String),
6
+ outcome: Schema.Literals(["queued", "ignored"]),
7
+ receivedAt: Schema.Date,
8
+ source: Schema.String,
9
+ }).pipe(Schema.encodeKeys({ deliveryId: "delivery_id", jobIds: "job_ids", receivedAt: "received_at" }));
10
+ //# sourceMappingURL=Receipt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Receipt.js","sourceRoot":"","sources":["../../../src/server/internal/Receipt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC,MAAM;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM;IACpB,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC,IAAI;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM;CACrB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { Migrator } from "effect/sql";
2
+ export declare const receiptMigrations: {
3
+ readonly loader: Migrator.Loader<never>;
4
+ readonly lockTimeout: "5 seconds";
5
+ readonly table: "webhook_receipts_migrations";
6
+ };
7
+ //# sourceMappingURL=receiptMigrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receiptMigrations.d.ts","sourceRoot":"","sources":["../../../src/server/internal/receiptMigrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,eAAO,MAAM,iBAAiB;aAC7B,MAAM;aAIN,WAAW,EAAE,WAAW;aACxB,KAAK,EAAE,6BAA6B;CAC3B,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { Migrator } from "effect/sql";
2
+ import createReceipts from "#server/migrations/0001_create_receipts.ts";
3
+ import receiptJobIds from "#server/migrations/0002_receipt_job_ids.ts";
4
+ export const receiptMigrations = {
5
+ loader: Migrator.fromRecord({
6
+ "0001_create_receipts": createReceipts,
7
+ "0002_receipt_job_ids": receiptJobIds,
8
+ }),
9
+ lockTimeout: "5 seconds",
10
+ table: "webhook_receipts_migrations",
11
+ };
12
+ //# sourceMappingURL=receiptMigrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receiptMigrations.js","sourceRoot":"","sources":["../../../src/server/internal/receiptMigrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,cAAc,MAAM,4CAA4C,CAAC;AACxE,OAAO,aAAa,MAAM,4CAA4C,CAAC;AAEvE,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAChC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC;QAC3B,sBAAsB,EAAE,cAAc;QACtC,sBAAsB,EAAE,aAAa;KACrC,CAAC;IACF,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE,6BAA6B;CAC3B,CAAC"}
@@ -2,6 +2,6 @@ import { Effect, type Redacted } from "effect";
2
2
  import { HttpServerResponse } from "effect/http";
3
3
  import { SqlClient } from "effect/sql";
4
4
  import type { PgBossService } from "@shivaedev/jobs/server/service.ts";
5
- import { type WebhookSource } from "#registration.ts";
5
+ import { type WebhookSource } from "#server/registration.ts";
6
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>;
7
7
  //# sourceMappingURL=receive.d.ts.map
@@ -0,0 +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"}
@@ -2,8 +2,8 @@ import { Effect, Option } from "effect";
2
2
  import { HttpServerResponse } from "effect/http";
3
3
  import * as HttpStatus from "effect/http/HttpStatus";
4
4
  import { SqlClient } from "effect/sql";
5
- import { enqueueWebhookJobs } from "#registration.ts";
6
- export const receive = Effect.fnUntraced(function* (name, source, key, body, headers, jobs) {
5
+ import { enqueueWebhookJobs } from "#server/registration.ts";
6
+ export const receive = Effect.fn("Webhooks.receive")(function* (name, source, key, body, headers, jobs) {
7
7
  yield* source.strategy.verify(body, headers, key);
8
8
  const { id, event } = source.strategy.delivery(headers);
9
9
  if (!(id && event)) {
@@ -13,14 +13,14 @@ export const receive = Effect.fnUntraced(function* (name, source, key, body, hea
13
13
  const sql = yield* SqlClient.SqlClient;
14
14
  return yield* sql.withTransaction(Effect.gen(function* () {
15
15
  const inserted = yield* sql `insert into webhook_receipts (source, delivery_id, event, outcome, job_ids)
16
- values (${name}, ${id}, ${event}, 'ignored', '[]'::jsonb)
16
+ values (${name}, ${id}, ${event}, 'ignored', array[]::text[])
17
17
  on conflict (source, delivery_id) do nothing returning delivery_id`;
18
18
  if (inserted.length === 0) {
19
19
  return HttpServerResponse.text("already queued", { status: HttpStatus.fromLiteral("OK") });
20
20
  }
21
21
  const requests = definition === undefined ? [] : yield* definition.decode(body);
22
22
  const ids = (yield* enqueueWebhookJobs(requests, jobs)).flatMap(Option.toArray);
23
- yield* sql `update webhook_receipts set job_ids = ${JSON.stringify(ids)}::jsonb, outcome = ${requests.length === 0 ? "ignored" : "queued"} where source = ${name} and delivery_id = ${id}`;
23
+ yield* sql `update webhook_receipts set job_ids = array[${sql.csv(ids.map((jobId) => sql `${jobId}`))}]::text[], outcome = ${requests.length === 0 ? "ignored" : "queued"} where source = ${name} and delivery_id = ${id}`;
24
24
  return HttpServerResponse.text(requests.length === 0 ? "ignored" : "queued", {
25
25
  status: requests.length === 0 ? HttpStatus.fromLiteral("OK") : HttpStatus.fromLiteral("Accepted"),
26
26
  });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receive.js","sourceRoot":"","sources":["../../../src/server/internal/receive.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ declare const _default: Effect.Effect<void, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
4
+ export default _default;
5
+ //# sourceMappingURL=0001_create_receipts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0001_create_receipts.d.ts","sourceRoot":"","sources":["../../../src/server/migrations/0001_create_receipts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ export default Effect.gen(function* () {
4
+ const sql = yield* SqlClient.SqlClient;
5
+ yield* sql `create table if not exists webhook_receipts (
6
+ source text not null,
7
+ delivery_id text not null,
8
+ event text not null,
9
+ received_at timestamptz not null default now(),
10
+ outcome text not null check (outcome in ('queued', 'ignored')),
11
+ job_ids jsonb not null,
12
+ primary key (source, delivery_id)
13
+ )`;
14
+ });
15
+ //# sourceMappingURL=0001_create_receipts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0001_create_receipts.js","sourceRoot":"","sources":["../../../src/server/migrations/0001_create_receipts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,eAAe,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,KAAK,CAAC,CAAC,GAAG,CAAA;;;;;;;;GAQR,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ declare const _default: Effect.Effect<void, import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
4
+ export default _default;
5
+ //# sourceMappingURL=0002_receipt_job_ids.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0002_receipt_job_ids.d.ts","sourceRoot":"","sources":["../../../src/server/migrations/0002_receipt_job_ids.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ export default Effect.gen(function* () {
4
+ const sql = yield* SqlClient.SqlClient;
5
+ yield* sql `alter table webhook_receipts add column job_ids_text text[]`;
6
+ yield* sql `update webhook_receipts set job_ids_text = array(select jsonb_array_elements_text(job_ids))`;
7
+ yield* sql `alter table webhook_receipts alter column job_ids_text set not null`;
8
+ yield* sql `alter table webhook_receipts drop column job_ids`;
9
+ yield* sql `alter table webhook_receipts rename column job_ids_text to job_ids`;
10
+ });
11
+ //# sourceMappingURL=0002_receipt_job_ids.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"0002_receipt_job_ids.js","sourceRoot":"","sources":["../../../src/server/migrations/0002_receipt_job_ids.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,eAAe,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;IACvC,KAAK,CAAC,CAAC,GAAG,CAAA,6DAA6D,CAAC;IACxE,KAAK,CAAC,CAAC,GAAG,CAAA,6FAA6F,CAAC;IACxG,KAAK,CAAC,CAAC,GAAG,CAAA,qEAAqE,CAAC;IAChF,KAAK,CAAC,CAAC,GAAG,CAAA,kDAAkD,CAAC;IAC7D,KAAK,CAAC,CAAC,GAAG,CAAA,oEAAoE,CAAC;AAChF,CAAC,CAAC,CAAC"}
@@ -2,7 +2,7 @@ import { type Config, Effect, type Option, type Redacted, Schema } from "effect"
2
2
  import type { JobPayloadSchema, QueueDefinition } from "@shivaedev/jobs/server/definition.ts";
3
3
  import type { PgBossError, PgBossPayloadError } from "@shivaedev/jobs/server/error.ts";
4
4
  import type { PgBossService } from "@shivaedev/jobs/server/service.ts";
5
- import type { WebhookUnauthorized } from "#WebhookUnauthorized.ts";
5
+ import type { WebhookUnauthorized } from "#server/WebhookUnauthorized.ts";
6
6
  declare const enqueue: unique symbol;
7
7
  export interface WebhookJob<R = never> {
8
8
  readonly [enqueue]: (jobs: PgBossService) => Effect.Effect<Option.Option<string>, PgBossError | PgBossPayloadError, R>;
@@ -0,0 +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"}
@@ -0,0 +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"}
@@ -2,11 +2,11 @@ import { 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
- import type { WebhookSource } from "#registration.ts";
5
+ import type { WebhookSource } from "#server/registration.ts";
6
6
  export interface WebhookRouteOptions<TJobs, R> {
7
7
  readonly basePath: `/${string}`;
8
8
  readonly jobs: Context.Key<TJobs, PgBossService>;
9
9
  readonly sources: Readonly<Record<string, WebhookSource<R>>>;
10
10
  }
11
- export declare function webhookRoute<TJobs, R>(options: WebhookRouteOptions<TJobs, R>): Layer.Layer<never, never, HttpRouter.HttpRouter | SqlClient.SqlClient | Exclude<R, import("effect/Scope").Scope> | Exclude<TJobs, import("effect/Scope").Scope>>;
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>>;
12
12
  //# sourceMappingURL=route.d.ts.map
@@ -0,0 +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,8 +1,9 @@
1
- import { Effect, Layer } from "effect";
2
- import { HttpRouter, HttpServerResponse } from "effect/http";
1
+ import { Cause, Effect, Layer } from "effect";
2
+ import { HttpRouter, HttpServerRespondable, HttpServerResponse } from "effect/http";
3
3
  import * as HttpStatus from "effect/http/HttpStatus";
4
+ import { redactDiagnosticCause } from "@shivaedev/errors/redactCause.ts";
4
5
  import { readBoundedBody } from "@shivaedev/http/server/boundedBody.ts";
5
- import { receive } from "#internal/receive.ts";
6
+ import { receive } from "#server/internal/receive.ts";
6
7
  export function webhookRoute(options) {
7
8
  return Layer.unwrap(Effect.gen(function* () {
8
9
  const context = yield* Effect.context();
@@ -21,7 +22,12 @@ export function webhookRoute(options) {
21
22
  }
22
23
  const body = new Uint8Array(yield* readBoundedBody(request));
23
24
  return yield* receive(name, source, key, body, request.headers, jobs).pipe(Effect.provideContext(context));
24
- }).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(() => Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") })))));
25
- }).pipe(Effect.orDie));
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
+ const response = HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") });
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)));
30
+ })));
31
+ }));
26
32
  }
27
33
  //# sourceMappingURL=route.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/server/route.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhookReceipts.d.ts","sourceRoot":"","sources":["../../src/server/webhookReceipts.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAGzE,eAAO,MAAM,eAAe,EAAE,eAY7B,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import { migratePostgres } from "@shivaedev/sql/migrations.ts";
4
+ import { pendingPostgresMigrations } from "@shivaedev/sql/pendingPostgresMigrations.ts";
5
+ import { receiptMigrations } from "#server/internal/receiptMigrations.ts";
6
+ export const webhookReceipts = {
7
+ apply: migratePostgres(receiptMigrations),
8
+ name: "webhook-receipts",
9
+ pending: Effect.gen(function* () {
10
+ const sql = yield* SqlClient.SqlClient;
11
+ const [row] = yield* sql `select to_regclass('webhook_receipts')::text as name`;
12
+ if (row === undefined || row.name === null) {
13
+ return "webhook_receipts is missing";
14
+ }
15
+ const { migrations } = yield* pendingPostgresMigrations(receiptMigrations);
16
+ return migrations.length === 0 ? undefined : `pending receipt migrations: ${migrations.map(({ name }) => name).join(", ")}`;
17
+ }),
18
+ };
19
+ //# sourceMappingURL=webhookReceipts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhookReceipts.js","sourceRoot":"","sources":["../../src/server/webhookReceipts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,6CAA6C,CAAC;AAExF,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAE1E,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC/C,KAAK,EAAE,eAAe,CAAC,iBAAiB,CAAC;IACzC,IAAI,EAAE,kBAAkB;IACxB,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC5B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAyB,sDAAsD,CAAC;QACxG,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YAC5C,OAAO,6BAA6B,CAAC;QACtC,CAAC;QACD,MAAM,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC,CAAC,yBAAyB,CAAC,iBAAiB,CAAC,CAAC;QAC3E,OAAO,UAAU,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,+BAA+B,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAC7H,CAAC,CAAC;CACF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/webhooks",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Verified webhook receipts and transactional job delivery over Effect HTTP",
5
5
  "keywords": [
6
6
  "effect"
@@ -31,8 +31,7 @@
31
31
  "types": "./dist/*.d.ts",
32
32
  "default": "./dist/*.js"
33
33
  },
34
- "./package.json": "./package.json",
35
- "./internal/*": null
34
+ "./package.json": "./package.json"
36
35
  },
37
36
  "files": [
38
37
  "dist",
@@ -45,17 +44,18 @@
45
44
  "README.md"
46
45
  ],
47
46
  "dependencies": {
48
- "@shivaedev/github": "0.7.2",
49
- "@shivaedev/http": "0.2.0",
50
- "@shivaedev/jobs": "0.22.1",
51
- "@shivaedev/sql": "0.25.0"
47
+ "@shivaedev/errors": "0.1.2",
48
+ "@shivaedev/github": "0.8.0",
49
+ "@shivaedev/http": "0.3.0",
50
+ "@shivaedev/jobs": "0.23.0",
51
+ "@shivaedev/sql": "0.26.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@effect/platform-node": "4.0.6",
55
- "@shivaedev/quality": "0.45.0",
56
- "@shivaedev/server": "0.5.0",
57
- "@shivaedev/test-sql": "0.11.0",
58
- "@shivaedev/test-story": "0.24.0",
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",
59
59
  "@types/node": "24.19.2",
60
60
  "effect": "4.0.6",
61
61
  "pg-boss": "12.38.0",
@@ -1,7 +1,7 @@
1
1
  import { Effect } from "effect";
2
2
  import { verifyWebhook } from "@shivaedev/github/verifyWebhook.ts";
3
- import type { WebhookSource, WebhookStrategy } from "#registration.ts";
4
- import { WebhookUnauthorized } from "#WebhookUnauthorized.ts";
3
+ import type { WebhookSource, WebhookStrategy } from "#server/registration.ts";
4
+ import { WebhookUnauthorized } from "#server/WebhookUnauthorized.ts";
5
5
 
6
6
  export const githubStrategy: WebhookStrategy = {
7
7
  delivery: (headers) => ({ event: headers["x-github-event"], id: headers["x-github-delivery"] }),
@@ -0,0 +1,10 @@
1
+ import { Schema } from "effect";
2
+
3
+ export const Receipt = Schema.Struct({
4
+ deliveryId: Schema.String,
5
+ event: Schema.String,
6
+ jobIds: Schema.Array(Schema.String),
7
+ outcome: Schema.Literals(["queued", "ignored"]),
8
+ receivedAt: Schema.Date,
9
+ source: Schema.String,
10
+ }).pipe(Schema.encodeKeys({ deliveryId: "delivery_id", jobIds: "job_ids", receivedAt: "received_at" }));
@@ -0,0 +1,12 @@
1
+ import { Migrator } from "effect/sql";
2
+ import createReceipts from "#server/migrations/0001_create_receipts.ts";
3
+ import receiptJobIds from "#server/migrations/0002_receipt_job_ids.ts";
4
+
5
+ export const receiptMigrations = {
6
+ loader: Migrator.fromRecord({
7
+ "0001_create_receipts": createReceipts,
8
+ "0002_receipt_job_ids": receiptJobIds,
9
+ }),
10
+ lockTimeout: "5 seconds",
11
+ table: "webhook_receipts_migrations",
12
+ } as const;
@@ -3,9 +3,9 @@ import { HttpServerResponse } from "effect/http";
3
3
  import * as HttpStatus from "effect/http/HttpStatus";
4
4
  import { SqlClient } from "effect/sql";
5
5
  import type { PgBossService } from "@shivaedev/jobs/server/service.ts";
6
- import { enqueueWebhookJobs, type WebhookSource } from "#registration.ts";
6
+ import { enqueueWebhookJobs, type WebhookSource } from "#server/registration.ts";
7
7
 
8
- export const receive = Effect.fnUntraced(function* <R>(
8
+ export const receive = Effect.fn("Webhooks.receive")(function* <R>(
9
9
  name: string,
10
10
  source: WebhookSource<R>,
11
11
  key: Redacted.Redacted<string>,
@@ -23,14 +23,14 @@ export const receive = Effect.fnUntraced(function* <R>(
23
23
  return yield* sql.withTransaction(
24
24
  Effect.gen(function* () {
25
25
  const inserted = yield* sql`insert into webhook_receipts (source, delivery_id, event, outcome, job_ids)
26
- values (${name}, ${id}, ${event}, 'ignored', '[]'::jsonb)
26
+ values (${name}, ${id}, ${event}, 'ignored', array[]::text[])
27
27
  on conflict (source, delivery_id) do nothing returning delivery_id`;
28
28
  if (inserted.length === 0) {
29
29
  return HttpServerResponse.text("already queued", { status: HttpStatus.fromLiteral("OK") });
30
30
  }
31
31
  const requests = definition === undefined ? [] : yield* definition.decode(body);
32
32
  const ids = (yield* enqueueWebhookJobs(requests, jobs)).flatMap(Option.toArray);
33
- yield* sql`update webhook_receipts set job_ids = ${JSON.stringify(ids)}::jsonb, outcome = ${requests.length === 0 ? "ignored" : "queued"} where source = ${name} and delivery_id = ${id}`;
33
+ yield* sql`update webhook_receipts set job_ids = array[${sql.csv(ids.map((jobId) => sql`${jobId}`))}]::text[], outcome = ${requests.length === 0 ? "ignored" : "queued"} where source = ${name} and delivery_id = ${id}`;
34
34
  return HttpServerResponse.text(requests.length === 0 ? "ignored" : "queued", {
35
35
  status: requests.length === 0 ? HttpStatus.fromLiteral("OK") : HttpStatus.fromLiteral("Accepted"),
36
36
  });
@@ -0,0 +1,15 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+
4
+ export default Effect.gen(function* () {
5
+ const sql = yield* SqlClient.SqlClient;
6
+ yield* sql`create table if not exists webhook_receipts (
7
+ source text not null,
8
+ delivery_id text not null,
9
+ event text not null,
10
+ received_at timestamptz not null default now(),
11
+ outcome text not null check (outcome in ('queued', 'ignored')),
12
+ job_ids jsonb not null,
13
+ primary key (source, delivery_id)
14
+ )`;
15
+ });
@@ -0,0 +1,11 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+
4
+ export default Effect.gen(function* () {
5
+ const sql = yield* SqlClient.SqlClient;
6
+ yield* sql`alter table webhook_receipts add column job_ids_text text[]`;
7
+ yield* sql`update webhook_receipts set job_ids_text = array(select jsonb_array_elements_text(job_ids))`;
8
+ yield* sql`alter table webhook_receipts alter column job_ids_text set not null`;
9
+ yield* sql`alter table webhook_receipts drop column job_ids`;
10
+ yield* sql`alter table webhook_receipts rename column job_ids_text to job_ids`;
11
+ });
@@ -2,7 +2,7 @@ import { type Config, Effect, type Option, type Redacted, Schema } from "effect"
2
2
  import type { JobPayloadSchema, QueueDefinition } from "@shivaedev/jobs/server/definition.ts";
3
3
  import type { PgBossError, PgBossPayloadError } from "@shivaedev/jobs/server/error.ts";
4
4
  import type { PgBossService } from "@shivaedev/jobs/server/service.ts";
5
- import type { WebhookUnauthorized } from "#WebhookUnauthorized.ts";
5
+ import type { WebhookUnauthorized } from "#server/WebhookUnauthorized.ts";
6
6
 
7
7
  const enqueue = Symbol("WebhookJob.enqueue");
8
8
 
@@ -1,11 +1,12 @@
1
- import { type Context, Effect, Layer, type Redacted } from "effect";
2
- import { HttpRouter, HttpServerResponse } from "effect/http";
1
+ import { Cause, type Context, Effect, Layer, type Redacted } from "effect";
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";
5
+ import { redactDiagnosticCause } from "@shivaedev/errors/redactCause.ts";
5
6
  import { readBoundedBody } from "@shivaedev/http/server/boundedBody.ts";
6
7
  import type { PgBossService } from "@shivaedev/jobs/server/service.ts";
7
- import { receive } from "#internal/receive.ts";
8
- import type { WebhookSource } from "#registration.ts";
8
+ import { receive } from "#server/internal/receive.ts";
9
+ import type { WebhookSource } from "#server/registration.ts";
9
10
 
10
11
  export interface WebhookRouteOptions<TJobs, R> {
11
12
  readonly basePath: `/${string}`;
@@ -39,9 +40,17 @@ export function webhookRoute<TJobs, R>(options: WebhookRouteOptions<TJobs, R>) {
39
40
  ),
40
41
  Effect.catchTag("WebhookUnauthorized", () => Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("Unauthorized") }))),
41
42
  Effect.catchTag("SchemaError", () => Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("BadRequest") }))),
42
- Effect.catch(() => Effect.succeed(HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") }))),
43
+ Effect.catch((error) => {
44
+ const response = HttpServerResponse.empty({ status: HttpStatus.fromLiteral("ServiceUnavailable") });
45
+ // 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) })));
47
+ return Effect.logError("Webhook receipt failed", cause).pipe(
48
+ Effect.annotateLogs({ errorTag: error._tag }),
49
+ Effect.andThen(Effect.failCause(cause)),
50
+ );
51
+ }),
43
52
  ),
44
53
  );
45
- }).pipe(Effect.orDie),
54
+ }),
46
55
  );
47
56
  }
@@ -0,0 +1,20 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/sql";
3
+ import { migratePostgres } from "@shivaedev/sql/migrations.ts";
4
+ import { pendingPostgresMigrations } from "@shivaedev/sql/pendingPostgresMigrations.ts";
5
+ import type { SchemaComponent } from "@shivaedev/sql/SchemaComponent.ts";
6
+ import { receiptMigrations } from "#server/internal/receiptMigrations.ts";
7
+
8
+ export const webhookReceipts: SchemaComponent = {
9
+ apply: migratePostgres(receiptMigrations),
10
+ name: "webhook-receipts",
11
+ pending: Effect.gen(function* () {
12
+ const sql = yield* SqlClient.SqlClient;
13
+ const [row] = yield* sql<{ name: string | null }>`select to_regclass('webhook_receipts')::text as name`;
14
+ if (row === undefined || row.name === null) {
15
+ return "webhook_receipts is missing";
16
+ }
17
+ const { migrations } = yield* pendingPostgresMigrations(receiptMigrations);
18
+ return migrations.length === 0 ? undefined : `pending receipt migrations: ${migrations.map(({ name }) => name).join(", ")}`;
19
+ }),
20
+ };
@@ -1 +0,0 @@
1
- {"version":3,"file":"WebhookUnauthorized.d.ts","sourceRoot":"","sources":["../src/WebhookUnauthorized.ts"],"names":[],"mappings":";;;AAEA,qBAAa,mBAAoB,SAAQ,wBAAuC;CAAG"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"WebhookUnauthorized.js","sourceRoot":"","sources":["../src/WebhookUnauthorized.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,MAAM,OAAO,mBAAoB,SAAQ,IAAI,CAAC,WAAW,CAAC,qBAAqB,CAAC;CAAG"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGvE,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 +0,0 @@
1
- {"version":3,"file":"github.js","sourceRoot":"","sources":["../src/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,yBAAyB,CAAC;AAE9D,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 +0,0 @@
1
- {"version":3,"file":"receive.d.ts","sourceRoot":"","sources":["../../src/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,kBAAkB,CAAC;AAE1E,eAAO,MAAM,OAAO,GAAgC,CAAC,4fA+BnD,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"receive.js","sourceRoot":"","sources":["../../src/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,kBAAkB,CAAC;AAE1E,MAAM,CAAC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EACjD,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,yCAAyC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,sBAAsB,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,mBAAmB,IAAI,sBAAsB,EAAE,EAAE,CAAC;QAC1L,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 +0,0 @@
1
- {"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../src/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,yBAAyB,CAAC;AAEnE,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 +0,0 @@
1
- {"version":3,"file":"registration.js","sourceRoot":"","sources":["../src/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 +0,0 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAU,KAAK,EAAiB,MAAM,QAAQ,CAAC;AACpE,OAAO,EAAE,UAAU,EAAsB,MAAM,aAAa,CAAC;AAE7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAEvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,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,oKA+B5E"}
package/dist/route.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"route.js","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhookReceipts.d.ts","sourceRoot":"","sources":["../src/webhookReceipts.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEzE,eAAO,MAAM,eAAe,EAAE,eAmB7B,CAAC"}
@@ -1,23 +0,0 @@
1
- import { Effect } from "effect";
2
- import { SqlClient } from "effect/sql";
3
- export const webhookReceipts = {
4
- apply: Effect.gen(function* () {
5
- const sql = yield* SqlClient.SqlClient;
6
- yield* sql `create table if not exists webhook_receipts (
7
- source text not null,
8
- delivery_id text not null,
9
- event text not null,
10
- received_at timestamptz not null default now(),
11
- outcome text not null check (outcome in ('queued', 'ignored')),
12
- job_ids jsonb not null,
13
- primary key (source, delivery_id)
14
- )`;
15
- }),
16
- name: "webhook-receipts",
17
- pending: Effect.gen(function* () {
18
- const sql = yield* SqlClient.SqlClient;
19
- const [row] = yield* sql `select to_regclass('webhook_receipts')::text as name`;
20
- return row === undefined || row.name === null ? "webhook_receipts is missing" : undefined;
21
- }),
22
- };
23
- //# sourceMappingURL=webhookReceipts.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhookReceipts.js","sourceRoot":"","sources":["../src/webhookReceipts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC/C,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC1B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;QACvC,KAAK,CAAC,CAAC,GAAG,CAAA;;;;;;;;IAQR,CAAC;IACJ,CAAC,CAAC;IACF,IAAI,EAAE,kBAAkB;IACxB,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAC5B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAyB,sDAAsD,CAAC;QACxG,OAAO,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3F,CAAC,CAAC;CACF,CAAC"}
@@ -1,24 +0,0 @@
1
- import { Effect } from "effect";
2
- import { SqlClient } from "effect/sql";
3
- import type { SchemaComponent } from "@shivaedev/sql/SchemaComponent.ts";
4
-
5
- export const webhookReceipts: SchemaComponent = {
6
- apply: Effect.gen(function* () {
7
- const sql = yield* SqlClient.SqlClient;
8
- yield* sql`create table if not exists webhook_receipts (
9
- source text not null,
10
- delivery_id text not null,
11
- event text not null,
12
- received_at timestamptz not null default now(),
13
- outcome text not null check (outcome in ('queued', 'ignored')),
14
- job_ids jsonb not null,
15
- primary key (source, delivery_id)
16
- )`;
17
- }),
18
- name: "webhook-receipts",
19
- pending: Effect.gen(function* () {
20
- const sql = yield* SqlClient.SqlClient;
21
- const [row] = yield* sql<{ name: string | null }>`select to_regclass('webhook_receipts')::text as name`;
22
- return row === undefined || row.name === null ? "webhook_receipts is missing" : undefined;
23
- }),
24
- };