@fonderie/webhooks 1.0.0 → 1.1.1
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/brain/outcomes.md +53 -0
- package/brain/signatures.md +72 -0
- package/dist/index.cjs +28 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +33 -5
- package/dist/index.js.map +1 -1
- package/package.json +12 -8
- package/dist/migrations/sql/001_webhooks.sql +0 -37
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
|
|
2
|
+
|
|
3
|
+
# @fonderie/webhooks — outcomes
|
|
4
|
+
|
|
5
|
+
What this package does to a running app: tables its migrations create,
|
|
6
|
+
rows it seeds, routes it registers. Generated from the migration SQL and
|
|
7
|
+
route tables in source — trust this file instead of reading `dist/` or
|
|
8
|
+
downloading tarballs.
|
|
9
|
+
|
|
10
|
+
## Database tables (after all migrations)
|
|
11
|
+
|
|
12
|
+
### `fonderie_webhook_deliveries`
|
|
13
|
+
|
|
14
|
+
```sql
|
|
15
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
16
|
+
endpoint_id UUID NOT NULL REFERENCES fonderie_webhook_endpoints(id) ON DELETE CASCADE
|
|
17
|
+
event_id TEXT NOT NULL
|
|
18
|
+
event_type TEXT NOT NULL
|
|
19
|
+
payload JSONB NOT NULL
|
|
20
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
21
|
+
attempts INT NOT NULL DEFAULT 0
|
|
22
|
+
response_status INT
|
|
23
|
+
response_body TEXT
|
|
24
|
+
next_attempt_at TIMESTAMPTZ
|
|
25
|
+
delivered_at TIMESTAMPTZ
|
|
26
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### `fonderie_webhook_endpoints`
|
|
30
|
+
|
|
31
|
+
```sql
|
|
32
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
|
33
|
+
workspace_id UUID NOT NULL
|
|
34
|
+
url TEXT NOT NULL
|
|
35
|
+
secret TEXT NOT NULL
|
|
36
|
+
events TEXT[] NOT NULL DEFAULT '{}'
|
|
37
|
+
enabled BOOLEAN NOT NULL DEFAULT true
|
|
38
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Raw SQL ships in `node_modules/@fonderie/webhooks/dist/migrations/sql/` — read it there if you must; never download tarballs.
|
|
42
|
+
|
|
43
|
+
## HTTP routes registered
|
|
44
|
+
|
|
45
|
+
| Method | Path | Middleware chain (auth / validation / handler) |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| GET | `/webhooks` | `requireAuth → async (ctx) => { if (!ctx.workspace) return setApiResponse( HTTP.UNPROCESSABLE, 'MISSING_WORKSPACE', 'Workspace context required', ); const list = await new EndpointModel(store).list(ctx.workspace.id); return setApiResponse(HTTP.OK, 'WEBHOOKS_FETCHED', 'Webhook endpoints retrieved.', { endpoints: list.map(toEndpointDTO), }); }` |
|
|
48
|
+
| POST | `/webhooks` | `requireAuth → validate(createEndpointSchema) → withBody → async (ctx) => { if (!ctx.workspace) return setApiResponse( HTTP.UNPROCESSABLE, 'MISSING_WORKSPACE', 'Workspace context required', ); const body = ctx.meta['body'] as { url?: string; events?: string[] } | undefined; if (!body?.url) return setApiResponse(HTTP.UNPROCESSABLE, 'MISSING_FIELD', 'url is required'); const endpoint = await new EndpointModel(store).create({ workspaceId: ctx.workspace.id, url: body.url, secret: generateSecret(), events: body.events ?? [], }); return setApiResponse( HTTP.CREATED, 'WEBHOOK_CREATED', 'Webhook endpoint registered.', toEndpointCreatedDTO(endpoint), ); }` |
|
|
49
|
+
| DELETE | `/webhooks/:endpointId` | `requireAuth → async (ctx) => { if (!ctx.workspace) return setApiResponse( HTTP.UNPROCESSABLE, 'MISSING_WORKSPACE', 'Workspace context required', ); const { endpointId } = ctx.meta['params'] as { endpointId: string }; const deleted = await new EndpointModel(store).delete(endpointId, ctx.workspace.id); if (!deleted) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found'); return new Response(null, { status: HTTP.NO_CONTENT }); }` |
|
|
50
|
+
| GET | `/webhooks/:endpointId` | `requireAuth → async (ctx) => { if (!ctx.workspace) return setApiResponse( HTTP.UNPROCESSABLE, 'MISSING_WORKSPACE', 'Workspace context required', ); const { endpointId } = ctx.meta['params'] as { endpointId: string }; const endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id); if (!endpoint) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found'); return setApiResponse( HTTP.OK, 'WEBHOOK_FETCHED', 'Webhook endpoint retrieved.', toEndpointDTO(endpoint), ); }` |
|
|
51
|
+
| PATCH | `/webhooks/:endpointId` | `requireAuth → validate(updateEndpointSchema) → withBody → async (ctx) => { if (!ctx.workspace) return setApiResponse( HTTP.UNPROCESSABLE, 'MISSING_WORKSPACE', 'Workspace context required', ); const { endpointId } = ctx.meta['params'] as { endpointId: string }; const body = ctx.meta['body'] as | { url?: string; events?: string[]; enabled?: boolean } | undefined; const patch: { url?: string; events?: string[]; enabled?: boolean } = {}; if (body?.url !== undefined) patch.url = body.url; if (body?.events !== undefined) patch.events = body.events; if (body?.enabled !== undefined) patch.enabled = body.enabled; const updated = await new EndpointModel(store).update(endpointId, ctx.workspace.id, patch); if (!updated) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found'); return setApiResponse( HTTP.OK, 'WEBHOOK_UPDATED', 'Webhook endpoint updated.', toEndpointDTO(updated), ); }` |
|
|
52
|
+
| GET | `/webhooks/:endpointId/deliveries` | `requireAuth → async (ctx) => { if (!ctx.workspace) return setApiResponse( HTTP.UNPROCESSABLE, 'MISSING_WORKSPACE', 'Workspace context required', ); const { endpointId } = ctx.meta['params'] as { endpointId: string }; const endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id); if (!endpoint) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found'); const list = await new DeliveryModel(store).listByEndpoint(endpointId); return setApiResponse(HTTP.OK, 'DELIVERIES_FETCHED', 'Deliveries retrieved.', { deliveries: list.map(toDeliveryDTO), }); }` |
|
|
53
|
+
| POST | `/webhooks/:endpointId/test` | `requireAuth → async (ctx) => { if (!ctx.workspace) return setApiResponse( HTTP.UNPROCESSABLE, 'MISSING_WORKSPACE', 'Workspace context required', ); const { endpointId } = ctx.meta['params'] as { endpointId: string }; const endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id); if (!endpoint) return setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found'); const body = JSON.stringify({ id: `test-${Date.now()}`, type: 'webhook.test', data: { workspaceId: ctx.workspace.id, message: 'Test webhook delivery.' }, }); try { const res = await fetch(endpoint.url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Webhook-Signature': signPayload(endpoint.secret, body), 'X-Webhook-Event': 'webhook.test', }, body, signal: AbortSignal.timeout(10_000), }); return setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', { status: res.status, ok: res.ok, }); } catch (err) { return setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', { status: null, ok: false, error: err instanceof Error ? err.message : String(err), }); } }` |
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<!-- GENERATED — do not edit. Regenerate with: npm run docs:signatures -->
|
|
2
|
+
|
|
3
|
+
# @fonderie/webhooks — signatures
|
|
4
|
+
|
|
5
|
+
## @fonderie/webhooks
|
|
6
|
+
|
|
7
|
+
Subpath exports: `@fonderie/webhooks/migrations`
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
new WebhooksModule(store: IStoreAdapter, config?: IWebhooksConfig, bus?: EventBus | undefined): WebhooksModule
|
|
11
|
+
.name: "@fonderie/webhooks"
|
|
12
|
+
.deps: string[]
|
|
13
|
+
.install(app: IFonderieApp): void
|
|
14
|
+
|
|
15
|
+
interface IWebhooksConfig {
|
|
16
|
+
maxAttempts?: number;
|
|
17
|
+
retryDelays?: number[];
|
|
18
|
+
retryInterval?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface IWebhookEndpoint {
|
|
22
|
+
id: string;
|
|
23
|
+
workspaceId: string;
|
|
24
|
+
url: string;
|
|
25
|
+
secret: string;
|
|
26
|
+
events: string[];
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
createdAt: Date;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface IWebhookDelivery {
|
|
32
|
+
id: string;
|
|
33
|
+
endpointId: string;
|
|
34
|
+
eventId: string;
|
|
35
|
+
eventType: string;
|
|
36
|
+
payload: Record<string, unknown>;
|
|
37
|
+
status: DeliveryStatus;
|
|
38
|
+
attempts: number;
|
|
39
|
+
responseStatus: number | null;
|
|
40
|
+
responseBody: string | null;
|
|
41
|
+
nextAttemptAt: Date | null;
|
|
42
|
+
deliveredAt: Date | null;
|
|
43
|
+
createdAt: Date;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type DeliveryStatus = 'pending' | 'delivered' | 'failed';
|
|
47
|
+
|
|
48
|
+
interface IWebhookEndpointDTO {
|
|
49
|
+
id: string;
|
|
50
|
+
url: string;
|
|
51
|
+
events: string[];
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
createdAt: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface IWebhookEndpointCreatedDTO extends IWebhookEndpointDTO {
|
|
57
|
+
secret: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface IWebhookDeliveryDTO {
|
|
61
|
+
id: string;
|
|
62
|
+
eventId: string;
|
|
63
|
+
eventType: string;
|
|
64
|
+
status: string;
|
|
65
|
+
attempts: number;
|
|
66
|
+
responseStatus: number | null;
|
|
67
|
+
deliveredAt: string | null;
|
|
68
|
+
createdAt: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
namespace schemas — exports: createEndpointSchema, updateEndpointSchema
|
|
72
|
+
```
|
package/dist/index.cjs
CHANGED
|
@@ -20,7 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
WebhooksModule: () => WebhooksModule
|
|
23
|
+
WebhooksModule: () => WebhooksModule,
|
|
24
|
+
schemas: () => schemas_exports
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(index_exports);
|
|
26
27
|
|
|
@@ -207,7 +208,7 @@ var WebhookDispatcher = class {
|
|
|
207
208
|
const pending = await deliveries.claimForRetry();
|
|
208
209
|
await Promise.allSettled(
|
|
209
210
|
pending.map(
|
|
210
|
-
({ delivery, url, secret }) => this.attemptDelivery(
|
|
211
|
+
({ delivery, url: url2, secret }) => this.attemptDelivery(url2, secret, delivery, deliveries)
|
|
211
212
|
)
|
|
212
213
|
);
|
|
213
214
|
}
|
|
@@ -220,7 +221,7 @@ var WebhookDispatcher = class {
|
|
|
220
221
|
});
|
|
221
222
|
await this.attemptDelivery(endpoint.url, endpoint.secret, delivery, deliveries);
|
|
222
223
|
}
|
|
223
|
-
async attemptDelivery(
|
|
224
|
+
async attemptDelivery(url2, secret, delivery, deliveries) {
|
|
224
225
|
const body = JSON.stringify({
|
|
225
226
|
id: delivery.eventId,
|
|
226
227
|
type: delivery.eventType,
|
|
@@ -228,7 +229,7 @@ var WebhookDispatcher = class {
|
|
|
228
229
|
});
|
|
229
230
|
const signature = signPayload(secret, body);
|
|
230
231
|
try {
|
|
231
|
-
const res = await fetch(
|
|
232
|
+
const res = await fetch(url2, {
|
|
232
233
|
method: "POST",
|
|
233
234
|
headers: {
|
|
234
235
|
"Content-Type": "application/json",
|
|
@@ -267,6 +268,25 @@ var import_core = require("@fonderie/core");
|
|
|
267
268
|
var import_middlewares = require("@fonderie/core/middlewares");
|
|
268
269
|
var import_middlewares2 = require("@fonderie/core/middlewares");
|
|
269
270
|
|
|
271
|
+
// src/schemas.ts
|
|
272
|
+
var schemas_exports = {};
|
|
273
|
+
__export(schemas_exports, {
|
|
274
|
+
createEndpointSchema: () => createEndpointSchema,
|
|
275
|
+
updateEndpointSchema: () => updateEndpointSchema
|
|
276
|
+
});
|
|
277
|
+
var import_zod = require("zod");
|
|
278
|
+
var url = import_zod.z.string().trim().pipe(import_zod.z.url());
|
|
279
|
+
var events = import_zod.z.array(import_zod.z.string().min(1).max(200)).max(100);
|
|
280
|
+
var createEndpointSchema = import_zod.z.object({
|
|
281
|
+
url,
|
|
282
|
+
events: events.optional()
|
|
283
|
+
});
|
|
284
|
+
var updateEndpointSchema = import_zod.z.object({
|
|
285
|
+
url: url.optional(),
|
|
286
|
+
events: events.optional(),
|
|
287
|
+
enabled: import_zod.z.boolean().optional()
|
|
288
|
+
}).refine((o) => Object.values(o).some((v) => v !== void 0), "Provide at least one field");
|
|
289
|
+
|
|
270
290
|
// src/dtos/webhook.ts
|
|
271
291
|
function toEndpointDTO(e) {
|
|
272
292
|
return {
|
|
@@ -300,6 +320,7 @@ function buildWebhookRoutes(store, config = {}) {
|
|
|
300
320
|
"POST",
|
|
301
321
|
"/webhooks",
|
|
302
322
|
import_middlewares.requireAuth,
|
|
323
|
+
(0, import_middlewares.validate)(createEndpointSchema),
|
|
303
324
|
import_middlewares2.withBody,
|
|
304
325
|
async (ctx) => {
|
|
305
326
|
if (!ctx.workspace)
|
|
@@ -369,6 +390,7 @@ function buildWebhookRoutes(store, config = {}) {
|
|
|
369
390
|
"PATCH",
|
|
370
391
|
"/webhooks/:endpointId",
|
|
371
392
|
import_middlewares.requireAuth,
|
|
393
|
+
(0, import_middlewares.validate)(updateEndpointSchema),
|
|
372
394
|
import_middlewares2.withBody,
|
|
373
395
|
async (ctx) => {
|
|
374
396
|
if (!ctx.workspace)
|
|
@@ -514,6 +536,7 @@ var WebhooksModule = class {
|
|
|
514
536
|
};
|
|
515
537
|
// Annotate the CommonJS export names for ESM import in node:
|
|
516
538
|
0 && (module.exports = {
|
|
517
|
-
WebhooksModule
|
|
539
|
+
WebhooksModule,
|
|
540
|
+
schemas
|
|
518
541
|
});
|
|
519
542
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/models/endpoint.model.ts","../src/models/delivery.model.ts","../src/signing.ts","../src/dispatcher.ts","../src/routes.ts","../src/dtos/webhook.ts","../src/module.ts"],"sourcesContent":["export { WebhooksModule } from './module';\nexport type { IWebhooksConfig } from './config';\nexport type {\n\tIWebhookEndpoint,\n\tIWebhookDelivery,\n\tDeliveryStatus,\n} from './types';\nexport type {\n\tIWebhookEndpointDTO,\n\tIWebhookEndpointCreatedDTO,\n\tIWebhookDeliveryDTO,\n} from './dtos/webhook';\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWebhookEndpoint } from '../types';\n\nconst COLS = `id, workspace_id as \"workspaceId\", url, secret, events,\n enabled, created_at as \"createdAt\"`;\n\nexport class EndpointModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync create(data: {\n\t\tworkspaceId: string;\n\t\turl: string;\n\t\tsecret: string;\n\t\tevents: string[];\n\t}): Promise<IWebhookEndpoint> {\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`INSERT INTO fonderie_webhook_endpoints (workspace_id, url, secret, events)\n\t\t\t VALUES ($1, $2, $3, $4)\n\t\t\t RETURNING ${COLS}`,\n\t\t\t[data.workspaceId, data.url, data.secret, data.events],\n\t\t);\n\t\treturn row!;\n\t}\n\n\tlist(workspaceId: string): Promise<IWebhookEndpoint[]> {\n\t\treturn this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE workspace_id = $1\n\t\t\t ORDER BY created_at DESC`,\n\t\t\t[workspaceId],\n\t\t);\n\t}\n\n\tasync findById(id: string, workspaceId: string): Promise<IWebhookEndpoint | null> {\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE id = $1 AND workspace_id = $2`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tworkspaceId: string,\n\t\tdata: { url?: string; events?: string[]; enabled?: boolean },\n\t): Promise<IWebhookEndpoint | null> {\n\t\tconst sets: string[] = [];\n\t\tconst params: unknown[] = [id, workspaceId];\n\n\t\tif (data.url !== undefined) {\n\t\t\tparams.push(data.url);\n\t\t\tsets.push(`url = $${params.length}`);\n\t\t}\n\t\tif (data.events !== undefined) {\n\t\t\tparams.push(data.events);\n\t\t\tsets.push(`events = $${params.length}`);\n\t\t}\n\t\tif (data.enabled !== undefined) {\n\t\t\tparams.push(data.enabled);\n\t\t\tsets.push(`enabled = $${params.length}`);\n\t\t}\n\n\t\tif (sets.length === 0) return this.findById(id, workspaceId);\n\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`UPDATE fonderie_webhook_endpoints SET ${sets.join(', ')}\n\t\t\t WHERE id = $1 AND workspace_id = $2\n\t\t\t RETURNING ${COLS}`,\n\t\t\tparams,\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync delete(id: string, workspaceId: string): Promise<boolean> {\n\t\tconst rows = await this.store.query<{ id: string }>(\n\t\t\t`DELETE FROM fonderie_webhook_endpoints WHERE id = $1 AND workspace_id = $2 RETURNING id`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\treturn rows.length > 0;\n\t}\n\n\tfindForEvent(workspaceId: string, eventType: string): Promise<IWebhookEndpoint[]> {\n\t\treturn this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE workspace_id = $1\n\t\t\t AND enabled = true\n\t\t\t AND (events = '{}' OR $2 = ANY(events))`,\n\t\t\t[workspaceId, eventType],\n\t\t);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWebhookDelivery } from '../types';\n\nconst COLS = `id, endpoint_id as \"endpointId\", event_id as \"eventId\",\n event_type as \"eventType\", payload, status, attempts,\n response_status as \"responseStatus\", response_body as \"responseBody\",\n next_attempt_at as \"nextAttemptAt\", delivered_at as \"deliveredAt\",\n created_at as \"createdAt\"`;\n\nconst D_COLS = `d.id, d.endpoint_id as \"endpointId\", d.event_id as \"eventId\",\n d.event_type as \"eventType\", d.payload, d.status, d.attempts,\n d.response_status as \"responseStatus\", d.response_body as \"responseBody\",\n d.next_attempt_at as \"nextAttemptAt\", d.delivered_at as \"deliveredAt\",\n d.created_at as \"createdAt\"`;\n\nexport interface IPendingRetry {\n\tdelivery: IWebhookDelivery;\n\turl: string;\n\tsecret: string;\n}\n\nexport class DeliveryModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync create(data: {\n\t\tendpointId: string;\n\t\teventId: string;\n\t\teventType: string;\n\t\tpayload: Record<string, unknown>;\n\t}): Promise<IWebhookDelivery> {\n\t\tconst [row] = await this.store.query<IWebhookDelivery>(\n\t\t\t`INSERT INTO fonderie_webhook_deliveries (endpoint_id, event_id, event_type, payload)\n\t\t\t VALUES ($1, $2, $3, $4)\n\t\t\t RETURNING ${COLS}`,\n\t\t\t[data.endpointId, data.eventId, data.eventType, JSON.stringify(data.payload)],\n\t\t);\n\t\treturn row!;\n\t}\n\n\tasync markResult(\n\t\tid: string,\n\t\tresult: {\n\t\t\tok: boolean;\n\t\t\tresponseStatus: number | null;\n\t\t\tresponseBody: string;\n\t\t\tnextAttemptAt: Date | null;\n\t\t},\n\t): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`UPDATE fonderie_webhook_deliveries\n\t\t\t SET status = $2,\n\t\t\t attempts = attempts + 1,\n\t\t\t response_status = $3,\n\t\t\t response_body = $4,\n\t\t\t next_attempt_at = $5,\n\t\t\t delivered_at = $6\n\t\t\t WHERE id = $1`,\n\t\t\t[\n\t\t\t\tid,\n\t\t\t\tresult.ok ? 'delivered' : 'failed',\n\t\t\t\tresult.responseStatus,\n\t\t\t\tresult.responseBody,\n\t\t\t\tresult.nextAttemptAt,\n\t\t\t\tresult.ok ? new Date() : null,\n\t\t\t],\n\t\t);\n\t}\n\n\tlistByEndpoint(endpointId: string, limit = 50): Promise<IWebhookDelivery[]> {\n\t\treturn this.store.query<IWebhookDelivery>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_deliveries\n\t\t\t WHERE endpoint_id = $1\n\t\t\t ORDER BY created_at DESC\n\t\t\t LIMIT $2`,\n\t\t\t[endpointId, limit],\n\t\t);\n\t}\n\n\tclaimForRetry(limit = 10): Promise<IPendingRetry[]> {\n\t\treturn this.store.query<IPendingRetry>(\n\t\t\t`SELECT ${D_COLS},\n\t\t\t e.url, e.secret\n\t\t\t FROM fonderie_webhook_deliveries d\n\t\t\t JOIN fonderie_webhook_endpoints e ON e.id = d.endpoint_id\n\t\t\t WHERE d.status = 'failed'\n\t\t\t AND d.next_attempt_at IS NOT NULL\n\t\t\t AND d.next_attempt_at <= now()\n\t\t\t AND e.enabled = true\n\t\t\t ORDER BY d.next_attempt_at\n\t\t\t LIMIT $1`,\n\t\t\t[limit],\n\t\t);\n\t}\n}\n","import { createHmac, randomBytes } from 'node:crypto';\n\nexport function generateSecret(): string {\n\treturn randomBytes(32).toString('hex');\n}\n\nexport function signPayload(secret: string, body: string): string {\n\treturn `sha256=${createHmac('sha256', secret).update(body).digest('hex')}`;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { IEventMeta } from '@fonderie/events';\n\nimport type { IWebhooksConfig } from './config';\nimport type { IWebhookEndpoint, IWebhookDelivery } from './types';\nimport { EndpointModel } from './models/endpoint.model';\nimport { DeliveryModel } from './models/delivery.model';\nimport { signPayload } from './signing';\n\nexport class WebhookDispatcher {\n\tprivate readonly maxAttempts: number;\n\tprivate readonly retryDelays: number[];\n\n\tconstructor(\n\t\tprivate readonly store: IStoreAdapter,\n\t\tprivate readonly config: IWebhooksConfig = {},\n\t) {\n\t\tthis.maxAttempts = config.maxAttempts ?? 3;\n\t\tthis.retryDelays = config.retryDelays ?? [60_000, 300_000, 1_800_000];\n\t}\n\n\t// Called by the bus consumer for every event.\n\t// Skips events that don't carry a workspaceId — not workspace-scoped.\n\tasync dispatch(payload: Record<string, unknown>, meta: IEventMeta): Promise<void> {\n\t\tconst workspaceId = payload['workspaceId'];\n\t\tif (typeof workspaceId !== 'string') return;\n\n\t\tconst endpoints = await new EndpointModel(this.store).findForEvent(workspaceId, meta.type);\n\t\tif (endpoints.length === 0) return;\n\n\t\tconst deliveries = new DeliveryModel(this.store);\n\t\tawait Promise.allSettled(endpoints.map((ep) => this.deliver(ep, payload, meta, deliveries)));\n\t}\n\n\t// Retries failed deliveries whose next_attempt_at has passed.\n\tasync retry(): Promise<void> {\n\t\tconst deliveries = new DeliveryModel(this.store);\n\t\tconst pending = await deliveries.claimForRetry();\n\t\tawait Promise.allSettled(\n\t\t\tpending.map(({ delivery, url, secret }) =>\n\t\t\t\tthis.attemptDelivery(url, secret, delivery, deliveries),\n\t\t\t),\n\t\t);\n\t}\n\n\tprivate async deliver(\n\t\tendpoint: IWebhookEndpoint,\n\t\tpayload: Record<string, unknown>,\n\t\tmeta: IEventMeta,\n\t\tdeliveries: DeliveryModel,\n\t): Promise<void> {\n\t\tconst delivery = await deliveries.create({\n\t\t\tendpointId: endpoint.id,\n\t\t\teventId: meta.id,\n\t\t\teventType: meta.type,\n\t\t\tpayload,\n\t\t});\n\t\tawait this.attemptDelivery(endpoint.url, endpoint.secret, delivery, deliveries);\n\t}\n\n\tasync attemptDelivery(\n\t\turl: string,\n\t\tsecret: string,\n\t\tdelivery: IWebhookDelivery,\n\t\tdeliveries: DeliveryModel,\n\t): Promise<void> {\n\t\tconst body = JSON.stringify({\n\t\t\tid: delivery.eventId,\n\t\t\ttype: delivery.eventType,\n\t\t\tdata: delivery.payload,\n\t\t});\n\n\t\tconst signature = signPayload(secret, body);\n\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t'X-Webhook-Signature': signature,\n\t\t\t\t\t'X-Webhook-Event': delivery.eventType,\n\t\t\t\t\t'X-Webhook-ID': delivery.id,\n\t\t\t\t},\n\t\t\t\tbody,\n\t\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t\t});\n\n\t\t\tconst responseBody = await res.text().catch(() => '');\n\n\t\t\tawait deliveries.markResult(delivery.id, {\n\t\t\t\tok: res.ok,\n\t\t\t\tresponseStatus: res.status,\n\t\t\t\tresponseBody,\n\t\t\t\tnextAttemptAt: res.ok ? null : this.nextRetryAt(delivery.attempts),\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tawait deliveries.markResult(delivery.id, {\n\t\t\t\tok: false,\n\t\t\t\tresponseStatus: null,\n\t\t\t\tresponseBody: err instanceof Error ? err.message : String(err),\n\t\t\t\tnextAttemptAt: this.nextRetryAt(delivery.attempts),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate nextRetryAt(currentAttempts: number): Date | null {\n\t\tif (currentAttempts + 1 >= this.maxAttempts) return null;\n\t\tconst delay =\n\t\t\tthis.retryDelays[currentAttempts] ?? this.retryDelays[this.retryDelays.length - 1]!;\n\t\treturn new Date(Date.now() + delay);\n\t}\n}\n","import type { Middleware } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport { requireAuth } from '@fonderie/core/middlewares';\nimport { withBody } from '@fonderie/core/middlewares';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { EndpointModel } from './models/endpoint.model';\nimport { DeliveryModel } from './models/delivery.model';\nimport { WebhookDispatcher } from './dispatcher';\nimport { generateSecret, signPayload } from './signing';\nimport { toEndpointDTO, toEndpointCreatedDTO, toDeliveryDTO } from './dtos/webhook';\nimport type { IWebhooksConfig } from './config';\n\ntype Route = [string, string, ...Middleware[]];\n\nexport function buildWebhookRoutes(store: IStoreAdapter, config: IWebhooksConfig = {}): Route[] {\n\treturn [\n\t\t[\n\t\t\t'POST',\n\t\t\t'/webhooks',\n\t\t\trequireAuth,\n\t\t\twithBody,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst body = ctx.meta['body'] as { url?: string; events?: string[] } | undefined;\n\t\t\t\tif (!body?.url)\n\t\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'MISSING_FIELD', 'url is required');\n\n\t\t\t\tconst endpoint = await new EndpointModel(store).create({\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t\turl: body.url,\n\t\t\t\t\tsecret: generateSecret(),\n\t\t\t\t\tevents: body.events ?? [],\n\t\t\t\t});\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.CREATED,\n\t\t\t\t\t'WEBHOOK_CREATED',\n\t\t\t\t\t'Webhook endpoint registered.',\n\t\t\t\t\ttoEndpointCreatedDTO(endpoint),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst list = await new EndpointModel(store).list(ctx.workspace.id);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'WEBHOOKS_FETCHED', 'Webhook endpoints retrieved.', {\n\t\t\t\t\tendpoints: list.map(toEndpointDTO),\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.OK,\n\t\t\t\t\t'WEBHOOK_FETCHED',\n\t\t\t\t\t'Webhook endpoint retrieved.',\n\t\t\t\t\ttoEndpointDTO(endpoint),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'PATCH',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\twithBody,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst body = ctx.meta['body'] as\n\t\t\t\t\t| { url?: string; events?: string[]; enabled?: boolean }\n\t\t\t\t\t| undefined;\n\n\t\t\t\tconst patch: { url?: string; events?: string[]; enabled?: boolean } = {};\n\t\t\t\tif (body?.url !== undefined) patch.url = body.url;\n\t\t\t\tif (body?.events !== undefined) patch.events = body.events;\n\t\t\t\tif (body?.enabled !== undefined) patch.enabled = body.enabled;\n\n\t\t\t\tconst updated = await new EndpointModel(store).update(endpointId, ctx.workspace.id, patch);\n\t\t\t\tif (!updated)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.OK,\n\t\t\t\t\t'WEBHOOK_UPDATED',\n\t\t\t\t\t'Webhook endpoint updated.',\n\t\t\t\t\ttoEndpointDTO(updated),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'DELETE',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst deleted = await new EndpointModel(store).delete(endpointId, ctx.workspace.id);\n\t\t\t\tif (!deleted)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn new Response(null, { status: HTTP.NO_CONTENT });\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks/:endpointId/deliveries',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\tconst list = await new DeliveryModel(store).listByEndpoint(endpointId);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'DELIVERIES_FETCHED', 'Deliveries retrieved.', {\n\t\t\t\t\tdeliveries: list.map(toDeliveryDTO),\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'POST',\n\t\t\t'/webhooks/:endpointId/test',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\tconst body = JSON.stringify({\n\t\t\t\t\tid: `test-${Date.now()}`,\n\t\t\t\t\ttype: 'webhook.test',\n\t\t\t\t\tdata: { workspaceId: ctx.workspace.id, message: 'Test webhook delivery.' },\n\t\t\t\t});\n\n\t\t\t\ttry {\n\t\t\t\t\tconst res = await fetch(endpoint.url, {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t\t'X-Webhook-Signature': signPayload(endpoint.secret, body),\n\t\t\t\t\t\t\t'X-Webhook-Event': 'webhook.test',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t\t\t\t});\n\n\t\t\t\t\treturn setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', {\n\t\t\t\t\t\tstatus: res.status,\n\t\t\t\t\t\tok: res.ok,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', {\n\t\t\t\t\t\tstatus: null,\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t],\n\t];\n}\n","import type { IWebhookEndpoint, IWebhookDelivery } from '../types';\n\nexport interface IWebhookEndpointDTO {\n\tid: string;\n\turl: string;\n\tevents: string[];\n\tenabled: boolean;\n\tcreatedAt: string;\n}\n\nexport interface IWebhookEndpointCreatedDTO extends IWebhookEndpointDTO {\n\tsecret: string;\n}\n\nexport interface IWebhookDeliveryDTO {\n\tid: string;\n\teventId: string;\n\teventType: string;\n\tstatus: string;\n\tattempts: number;\n\tresponseStatus: number | null;\n\tdeliveredAt: string | null;\n\tcreatedAt: string;\n}\n\nexport function toEndpointDTO(e: IWebhookEndpoint): IWebhookEndpointDTO {\n\treturn {\n\t\tid: e.id,\n\t\turl: e.url,\n\t\tevents: e.events,\n\t\tenabled: e.enabled,\n\t\tcreatedAt: e.createdAt.toISOString(),\n\t};\n}\n\nexport function toEndpointCreatedDTO(e: IWebhookEndpoint): IWebhookEndpointCreatedDTO {\n\treturn { ...toEndpointDTO(e), secret: e.secret };\n}\n\nexport function toDeliveryDTO(d: IWebhookDelivery): IWebhookDeliveryDTO {\n\treturn {\n\t\tid: d.id,\n\t\teventId: d.eventId,\n\t\teventType: d.eventType,\n\t\tstatus: d.status,\n\t\tattempts: d.attempts,\n\t\tresponseStatus: d.responseStatus,\n\t\tdeliveredAt: d.deliveredAt?.toISOString() ?? null,\n\t\tcreatedAt: d.createdAt.toISOString(),\n\t};\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport type { EventBus } from '@fonderie/events';\n\nimport type { IWebhooksConfig } from './config';\nimport { WebhookDispatcher } from './dispatcher';\nimport { buildWebhookRoutes } from './routes';\n\nexport class WebhooksModule implements IFonderieModule {\n\treadonly name = '@fonderie/webhooks';\n\treadonly deps = ['@fonderie/auth', '@fonderie/workspaces'];\n\n\tprivate retryTimer?: ReturnType<typeof setInterval>;\n\n\tconstructor(\n\t\tprivate readonly store: IStoreAdapter,\n\t\tprivate readonly config: IWebhooksConfig = {},\n\t\tprivate readonly bus?: EventBus,\n\t) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tconst dispatcher = new WebhookDispatcher(this.store, this.config);\n\n\t\tthis.bus?.on<Record<string, unknown>>(\n\t\t\t'*',\n\t\t\tasync (payload, meta) => {\n\t\t\t\tawait dispatcher.dispatch(payload, meta);\n\t\t\t},\n\t\t\t'webhooks',\n\t\t);\n\n\t\tconst interval = this.config.retryInterval ?? 60_000;\n\t\tthis.retryTimer = setInterval(() => {\n\t\t\tdispatcher.retry().catch((err) => console.error('[webhooks] retry error:', err));\n\t\t}, interval);\n\n\t\tconst routes = buildWebhookRoutes(this.store, this.config);\n\t\tfor (const [method, path, ...handlers] of routes) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["COLS","import_middlewares"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/models/endpoint.model.ts","../src/models/delivery.model.ts","../src/signing.ts","../src/dispatcher.ts","../src/routes.ts","../src/schemas.ts","../src/dtos/webhook.ts","../src/module.ts"],"sourcesContent":["export { WebhooksModule } from './module';\nexport type { IWebhooksConfig } from './config';\nexport type {\n\tIWebhookEndpoint,\n\tIWebhookDelivery,\n\tDeliveryStatus,\n} from './types';\nexport type {\n\tIWebhookEndpointDTO,\n\tIWebhookEndpointCreatedDTO,\n\tIWebhookDeliveryDTO,\n} from './dtos/webhook';\n\n// Request validation — enforced contract for body-taking routes; exported\n// for docs generation and typed clients.\nexport * as schemas from './schemas';\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWebhookEndpoint } from '../types';\n\nconst COLS = `id, workspace_id as \"workspaceId\", url, secret, events,\n enabled, created_at as \"createdAt\"`;\n\nexport class EndpointModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync create(data: {\n\t\tworkspaceId: string;\n\t\turl: string;\n\t\tsecret: string;\n\t\tevents: string[];\n\t}): Promise<IWebhookEndpoint> {\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`INSERT INTO fonderie_webhook_endpoints (workspace_id, url, secret, events)\n\t\t\t VALUES ($1, $2, $3, $4)\n\t\t\t RETURNING ${COLS}`,\n\t\t\t[data.workspaceId, data.url, data.secret, data.events],\n\t\t);\n\t\treturn row!;\n\t}\n\n\tlist(workspaceId: string): Promise<IWebhookEndpoint[]> {\n\t\treturn this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE workspace_id = $1\n\t\t\t ORDER BY created_at DESC`,\n\t\t\t[workspaceId],\n\t\t);\n\t}\n\n\tasync findById(id: string, workspaceId: string): Promise<IWebhookEndpoint | null> {\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE id = $1 AND workspace_id = $2`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tworkspaceId: string,\n\t\tdata: { url?: string; events?: string[]; enabled?: boolean },\n\t): Promise<IWebhookEndpoint | null> {\n\t\tconst sets: string[] = [];\n\t\tconst params: unknown[] = [id, workspaceId];\n\n\t\tif (data.url !== undefined) {\n\t\t\tparams.push(data.url);\n\t\t\tsets.push(`url = $${params.length}`);\n\t\t}\n\t\tif (data.events !== undefined) {\n\t\t\tparams.push(data.events);\n\t\t\tsets.push(`events = $${params.length}`);\n\t\t}\n\t\tif (data.enabled !== undefined) {\n\t\t\tparams.push(data.enabled);\n\t\t\tsets.push(`enabled = $${params.length}`);\n\t\t}\n\n\t\tif (sets.length === 0) return this.findById(id, workspaceId);\n\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`UPDATE fonderie_webhook_endpoints SET ${sets.join(', ')}\n\t\t\t WHERE id = $1 AND workspace_id = $2\n\t\t\t RETURNING ${COLS}`,\n\t\t\tparams,\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync delete(id: string, workspaceId: string): Promise<boolean> {\n\t\tconst rows = await this.store.query<{ id: string }>(\n\t\t\t`DELETE FROM fonderie_webhook_endpoints WHERE id = $1 AND workspace_id = $2 RETURNING id`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\treturn rows.length > 0;\n\t}\n\n\tfindForEvent(workspaceId: string, eventType: string): Promise<IWebhookEndpoint[]> {\n\t\treturn this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE workspace_id = $1\n\t\t\t AND enabled = true\n\t\t\t AND (events = '{}' OR $2 = ANY(events))`,\n\t\t\t[workspaceId, eventType],\n\t\t);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWebhookDelivery } from '../types';\n\nconst COLS = `id, endpoint_id as \"endpointId\", event_id as \"eventId\",\n event_type as \"eventType\", payload, status, attempts,\n response_status as \"responseStatus\", response_body as \"responseBody\",\n next_attempt_at as \"nextAttemptAt\", delivered_at as \"deliveredAt\",\n created_at as \"createdAt\"`;\n\nconst D_COLS = `d.id, d.endpoint_id as \"endpointId\", d.event_id as \"eventId\",\n d.event_type as \"eventType\", d.payload, d.status, d.attempts,\n d.response_status as \"responseStatus\", d.response_body as \"responseBody\",\n d.next_attempt_at as \"nextAttemptAt\", d.delivered_at as \"deliveredAt\",\n d.created_at as \"createdAt\"`;\n\nexport interface IPendingRetry {\n\tdelivery: IWebhookDelivery;\n\turl: string;\n\tsecret: string;\n}\n\nexport class DeliveryModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync create(data: {\n\t\tendpointId: string;\n\t\teventId: string;\n\t\teventType: string;\n\t\tpayload: Record<string, unknown>;\n\t}): Promise<IWebhookDelivery> {\n\t\tconst [row] = await this.store.query<IWebhookDelivery>(\n\t\t\t`INSERT INTO fonderie_webhook_deliveries (endpoint_id, event_id, event_type, payload)\n\t\t\t VALUES ($1, $2, $3, $4)\n\t\t\t RETURNING ${COLS}`,\n\t\t\t[data.endpointId, data.eventId, data.eventType, JSON.stringify(data.payload)],\n\t\t);\n\t\treturn row!;\n\t}\n\n\tasync markResult(\n\t\tid: string,\n\t\tresult: {\n\t\t\tok: boolean;\n\t\t\tresponseStatus: number | null;\n\t\t\tresponseBody: string;\n\t\t\tnextAttemptAt: Date | null;\n\t\t},\n\t): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`UPDATE fonderie_webhook_deliveries\n\t\t\t SET status = $2,\n\t\t\t attempts = attempts + 1,\n\t\t\t response_status = $3,\n\t\t\t response_body = $4,\n\t\t\t next_attempt_at = $5,\n\t\t\t delivered_at = $6\n\t\t\t WHERE id = $1`,\n\t\t\t[\n\t\t\t\tid,\n\t\t\t\tresult.ok ? 'delivered' : 'failed',\n\t\t\t\tresult.responseStatus,\n\t\t\t\tresult.responseBody,\n\t\t\t\tresult.nextAttemptAt,\n\t\t\t\tresult.ok ? new Date() : null,\n\t\t\t],\n\t\t);\n\t}\n\n\tlistByEndpoint(endpointId: string, limit = 50): Promise<IWebhookDelivery[]> {\n\t\treturn this.store.query<IWebhookDelivery>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_deliveries\n\t\t\t WHERE endpoint_id = $1\n\t\t\t ORDER BY created_at DESC\n\t\t\t LIMIT $2`,\n\t\t\t[endpointId, limit],\n\t\t);\n\t}\n\n\tclaimForRetry(limit = 10): Promise<IPendingRetry[]> {\n\t\treturn this.store.query<IPendingRetry>(\n\t\t\t`SELECT ${D_COLS},\n\t\t\t e.url, e.secret\n\t\t\t FROM fonderie_webhook_deliveries d\n\t\t\t JOIN fonderie_webhook_endpoints e ON e.id = d.endpoint_id\n\t\t\t WHERE d.status = 'failed'\n\t\t\t AND d.next_attempt_at IS NOT NULL\n\t\t\t AND d.next_attempt_at <= now()\n\t\t\t AND e.enabled = true\n\t\t\t ORDER BY d.next_attempt_at\n\t\t\t LIMIT $1`,\n\t\t\t[limit],\n\t\t);\n\t}\n}\n","import { createHmac, randomBytes } from 'node:crypto';\n\nexport function generateSecret(): string {\n\treturn randomBytes(32).toString('hex');\n}\n\nexport function signPayload(secret: string, body: string): string {\n\treturn `sha256=${createHmac('sha256', secret).update(body).digest('hex')}`;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { IEventMeta } from '@fonderie/events';\n\nimport type { IWebhooksConfig } from './config';\nimport type { IWebhookEndpoint, IWebhookDelivery } from './types';\nimport { EndpointModel } from './models/endpoint.model';\nimport { DeliveryModel } from './models/delivery.model';\nimport { signPayload } from './signing';\n\nexport class WebhookDispatcher {\n\tprivate readonly maxAttempts: number;\n\tprivate readonly retryDelays: number[];\n\n\tconstructor(\n\t\tprivate readonly store: IStoreAdapter,\n\t\tprivate readonly config: IWebhooksConfig = {},\n\t) {\n\t\tthis.maxAttempts = config.maxAttempts ?? 3;\n\t\tthis.retryDelays = config.retryDelays ?? [60_000, 300_000, 1_800_000];\n\t}\n\n\t// Called by the bus consumer for every event.\n\t// Skips events that don't carry a workspaceId — not workspace-scoped.\n\tasync dispatch(payload: Record<string, unknown>, meta: IEventMeta): Promise<void> {\n\t\tconst workspaceId = payload['workspaceId'];\n\t\tif (typeof workspaceId !== 'string') return;\n\n\t\tconst endpoints = await new EndpointModel(this.store).findForEvent(workspaceId, meta.type);\n\t\tif (endpoints.length === 0) return;\n\n\t\tconst deliveries = new DeliveryModel(this.store);\n\t\tawait Promise.allSettled(endpoints.map((ep) => this.deliver(ep, payload, meta, deliveries)));\n\t}\n\n\t// Retries failed deliveries whose next_attempt_at has passed.\n\tasync retry(): Promise<void> {\n\t\tconst deliveries = new DeliveryModel(this.store);\n\t\tconst pending = await deliveries.claimForRetry();\n\t\tawait Promise.allSettled(\n\t\t\tpending.map(({ delivery, url, secret }) =>\n\t\t\t\tthis.attemptDelivery(url, secret, delivery, deliveries),\n\t\t\t),\n\t\t);\n\t}\n\n\tprivate async deliver(\n\t\tendpoint: IWebhookEndpoint,\n\t\tpayload: Record<string, unknown>,\n\t\tmeta: IEventMeta,\n\t\tdeliveries: DeliveryModel,\n\t): Promise<void> {\n\t\tconst delivery = await deliveries.create({\n\t\t\tendpointId: endpoint.id,\n\t\t\teventId: meta.id,\n\t\t\teventType: meta.type,\n\t\t\tpayload,\n\t\t});\n\t\tawait this.attemptDelivery(endpoint.url, endpoint.secret, delivery, deliveries);\n\t}\n\n\tasync attemptDelivery(\n\t\turl: string,\n\t\tsecret: string,\n\t\tdelivery: IWebhookDelivery,\n\t\tdeliveries: DeliveryModel,\n\t): Promise<void> {\n\t\tconst body = JSON.stringify({\n\t\t\tid: delivery.eventId,\n\t\t\ttype: delivery.eventType,\n\t\t\tdata: delivery.payload,\n\t\t});\n\n\t\tconst signature = signPayload(secret, body);\n\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t'X-Webhook-Signature': signature,\n\t\t\t\t\t'X-Webhook-Event': delivery.eventType,\n\t\t\t\t\t'X-Webhook-ID': delivery.id,\n\t\t\t\t},\n\t\t\t\tbody,\n\t\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t\t});\n\n\t\t\tconst responseBody = await res.text().catch(() => '');\n\n\t\t\tawait deliveries.markResult(delivery.id, {\n\t\t\t\tok: res.ok,\n\t\t\t\tresponseStatus: res.status,\n\t\t\t\tresponseBody,\n\t\t\t\tnextAttemptAt: res.ok ? null : this.nextRetryAt(delivery.attempts),\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tawait deliveries.markResult(delivery.id, {\n\t\t\t\tok: false,\n\t\t\t\tresponseStatus: null,\n\t\t\t\tresponseBody: err instanceof Error ? err.message : String(err),\n\t\t\t\tnextAttemptAt: this.nextRetryAt(delivery.attempts),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate nextRetryAt(currentAttempts: number): Date | null {\n\t\tif (currentAttempts + 1 >= this.maxAttempts) return null;\n\t\tconst delay =\n\t\t\tthis.retryDelays[currentAttempts] ?? this.retryDelays[this.retryDelays.length - 1]!;\n\t\treturn new Date(Date.now() + delay);\n\t}\n}\n","import type { Middleware } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport { requireAuth, validate } from '@fonderie/core/middlewares';\nimport { withBody } from '@fonderie/core/middlewares';\n\nimport { createEndpointSchema, updateEndpointSchema } from './schemas';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { EndpointModel } from './models/endpoint.model';\nimport { DeliveryModel } from './models/delivery.model';\nimport { WebhookDispatcher } from './dispatcher';\nimport { generateSecret, signPayload } from './signing';\nimport { toEndpointDTO, toEndpointCreatedDTO, toDeliveryDTO } from './dtos/webhook';\nimport type { IWebhooksConfig } from './config';\n\ntype Route = [string, string, ...Middleware[]];\n\nexport function buildWebhookRoutes(store: IStoreAdapter, config: IWebhooksConfig = {}): Route[] {\n\treturn [\n\t\t[\n\t\t\t'POST',\n\t\t\t'/webhooks',\n\t\t\trequireAuth,\n\t\t\tvalidate(createEndpointSchema),\n\t\t\twithBody,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst body = ctx.meta['body'] as { url?: string; events?: string[] } | undefined;\n\t\t\t\tif (!body?.url)\n\t\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'MISSING_FIELD', 'url is required');\n\n\t\t\t\tconst endpoint = await new EndpointModel(store).create({\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t\turl: body.url,\n\t\t\t\t\tsecret: generateSecret(),\n\t\t\t\t\tevents: body.events ?? [],\n\t\t\t\t});\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.CREATED,\n\t\t\t\t\t'WEBHOOK_CREATED',\n\t\t\t\t\t'Webhook endpoint registered.',\n\t\t\t\t\ttoEndpointCreatedDTO(endpoint),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst list = await new EndpointModel(store).list(ctx.workspace.id);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'WEBHOOKS_FETCHED', 'Webhook endpoints retrieved.', {\n\t\t\t\t\tendpoints: list.map(toEndpointDTO),\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.OK,\n\t\t\t\t\t'WEBHOOK_FETCHED',\n\t\t\t\t\t'Webhook endpoint retrieved.',\n\t\t\t\t\ttoEndpointDTO(endpoint),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'PATCH',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tvalidate(updateEndpointSchema),\n\t\t\twithBody,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst body = ctx.meta['body'] as\n\t\t\t\t\t| { url?: string; events?: string[]; enabled?: boolean }\n\t\t\t\t\t| undefined;\n\n\t\t\t\tconst patch: { url?: string; events?: string[]; enabled?: boolean } = {};\n\t\t\t\tif (body?.url !== undefined) patch.url = body.url;\n\t\t\t\tif (body?.events !== undefined) patch.events = body.events;\n\t\t\t\tif (body?.enabled !== undefined) patch.enabled = body.enabled;\n\n\t\t\t\tconst updated = await new EndpointModel(store).update(endpointId, ctx.workspace.id, patch);\n\t\t\t\tif (!updated)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.OK,\n\t\t\t\t\t'WEBHOOK_UPDATED',\n\t\t\t\t\t'Webhook endpoint updated.',\n\t\t\t\t\ttoEndpointDTO(updated),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'DELETE',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst deleted = await new EndpointModel(store).delete(endpointId, ctx.workspace.id);\n\t\t\t\tif (!deleted)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn new Response(null, { status: HTTP.NO_CONTENT });\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks/:endpointId/deliveries',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\tconst list = await new DeliveryModel(store).listByEndpoint(endpointId);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'DELIVERIES_FETCHED', 'Deliveries retrieved.', {\n\t\t\t\t\tdeliveries: list.map(toDeliveryDTO),\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'POST',\n\t\t\t'/webhooks/:endpointId/test',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\tconst body = JSON.stringify({\n\t\t\t\t\tid: `test-${Date.now()}`,\n\t\t\t\t\ttype: 'webhook.test',\n\t\t\t\t\tdata: { workspaceId: ctx.workspace.id, message: 'Test webhook delivery.' },\n\t\t\t\t});\n\n\t\t\t\ttry {\n\t\t\t\t\tconst res = await fetch(endpoint.url, {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t\t'X-Webhook-Signature': signPayload(endpoint.secret, body),\n\t\t\t\t\t\t\t'X-Webhook-Event': 'webhook.test',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t\t\t\t});\n\n\t\t\t\t\treturn setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', {\n\t\t\t\t\t\tstatus: res.status,\n\t\t\t\t\t\tok: res.ok,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', {\n\t\t\t\t\t\tstatus: null,\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t],\n\t];\n}\n","import { z } from 'zod';\n\n// Request schemas — the validation contract for webhooks' body-taking routes.\n// Wired via @fonderie/core's validate(); same pattern as @fonderie/auth.\n\nconst url = z.string().trim().pipe(z.url());\nconst events = z.array(z.string().min(1).max(200)).max(100);\n\nexport const createEndpointSchema = z.object({\n\turl,\n\tevents: events.optional(),\n});\n\nexport const updateEndpointSchema = z\n\t.object({\n\t\turl: url.optional(),\n\t\tevents: events.optional(),\n\t\tenabled: z.boolean().optional(),\n\t})\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), 'Provide at least one field');\n","import type { IWebhookEndpoint, IWebhookDelivery } from '../types';\n\nexport interface IWebhookEndpointDTO {\n\tid: string;\n\turl: string;\n\tevents: string[];\n\tenabled: boolean;\n\tcreatedAt: string;\n}\n\nexport interface IWebhookEndpointCreatedDTO extends IWebhookEndpointDTO {\n\tsecret: string;\n}\n\nexport interface IWebhookDeliveryDTO {\n\tid: string;\n\teventId: string;\n\teventType: string;\n\tstatus: string;\n\tattempts: number;\n\tresponseStatus: number | null;\n\tdeliveredAt: string | null;\n\tcreatedAt: string;\n}\n\nexport function toEndpointDTO(e: IWebhookEndpoint): IWebhookEndpointDTO {\n\treturn {\n\t\tid: e.id,\n\t\turl: e.url,\n\t\tevents: e.events,\n\t\tenabled: e.enabled,\n\t\tcreatedAt: e.createdAt.toISOString(),\n\t};\n}\n\nexport function toEndpointCreatedDTO(e: IWebhookEndpoint): IWebhookEndpointCreatedDTO {\n\treturn { ...toEndpointDTO(e), secret: e.secret };\n}\n\nexport function toDeliveryDTO(d: IWebhookDelivery): IWebhookDeliveryDTO {\n\treturn {\n\t\tid: d.id,\n\t\teventId: d.eventId,\n\t\teventType: d.eventType,\n\t\tstatus: d.status,\n\t\tattempts: d.attempts,\n\t\tresponseStatus: d.responseStatus,\n\t\tdeliveredAt: d.deliveredAt?.toISOString() ?? null,\n\t\tcreatedAt: d.createdAt.toISOString(),\n\t};\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport type { EventBus } from '@fonderie/events';\n\nimport type { IWebhooksConfig } from './config';\nimport { WebhookDispatcher } from './dispatcher';\nimport { buildWebhookRoutes } from './routes';\n\nexport class WebhooksModule implements IFonderieModule {\n\treadonly name = '@fonderie/webhooks';\n\treadonly deps = ['@fonderie/auth', '@fonderie/workspaces'];\n\n\tprivate retryTimer?: ReturnType<typeof setInterval>;\n\n\tconstructor(\n\t\tprivate readonly store: IStoreAdapter,\n\t\tprivate readonly config: IWebhooksConfig = {},\n\t\tprivate readonly bus?: EventBus,\n\t) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tconst dispatcher = new WebhookDispatcher(this.store, this.config);\n\n\t\tthis.bus?.on<Record<string, unknown>>(\n\t\t\t'*',\n\t\t\tasync (payload, meta) => {\n\t\t\t\tawait dispatcher.dispatch(payload, meta);\n\t\t\t},\n\t\t\t'webhooks',\n\t\t);\n\n\t\tconst interval = this.config.retryInterval ?? 60_000;\n\t\tthis.retryTimer = setInterval(() => {\n\t\t\tdispatcher.retry().catch((err) => console.error('[webhooks] retry error:', err));\n\t\t}, interval);\n\n\t\tconst routes = buildWebhookRoutes(this.store, this.config);\n\t\tfor (const [method, path, ...handlers] of routes) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["COLS","url","import_middlewares"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IFonderieModule, IFonderieApp } from '@fonderie/core';
|
|
2
2
|
import { IStoreAdapter } from '@fonderie/store';
|
|
3
3
|
import { EventBus } from '@fonderie/events';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
|
|
5
6
|
interface IWebhooksConfig {
|
|
6
7
|
maxAttempts?: number;
|
|
@@ -65,4 +66,20 @@ interface IWebhookDeliveryDTO {
|
|
|
65
66
|
createdAt: string;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
declare const createEndpointSchema: z.ZodObject<{
|
|
70
|
+
url: z.ZodPipe<z.ZodString, z.ZodURL>;
|
|
71
|
+
events: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
72
|
+
}, z.core.$strip>;
|
|
73
|
+
declare const updateEndpointSchema: z.ZodObject<{
|
|
74
|
+
url: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodURL>>;
|
|
75
|
+
events: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
76
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
|
|
79
|
+
declare const schemas_createEndpointSchema: typeof createEndpointSchema;
|
|
80
|
+
declare const schemas_updateEndpointSchema: typeof updateEndpointSchema;
|
|
81
|
+
declare namespace schemas {
|
|
82
|
+
export { schemas_createEndpointSchema as createEndpointSchema, schemas_updateEndpointSchema as updateEndpointSchema };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { type DeliveryStatus, type IWebhookDelivery, type IWebhookDeliveryDTO, type IWebhookEndpoint, type IWebhookEndpointCreatedDTO, type IWebhookEndpointDTO, type IWebhooksConfig, WebhooksModule, schemas };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IFonderieModule, IFonderieApp } from '@fonderie/core';
|
|
2
2
|
import { IStoreAdapter } from '@fonderie/store';
|
|
3
3
|
import { EventBus } from '@fonderie/events';
|
|
4
|
+
import { z } from 'zod';
|
|
4
5
|
|
|
5
6
|
interface IWebhooksConfig {
|
|
6
7
|
maxAttempts?: number;
|
|
@@ -65,4 +66,20 @@ interface IWebhookDeliveryDTO {
|
|
|
65
66
|
createdAt: string;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
declare const createEndpointSchema: z.ZodObject<{
|
|
70
|
+
url: z.ZodPipe<z.ZodString, z.ZodURL>;
|
|
71
|
+
events: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
72
|
+
}, z.core.$strip>;
|
|
73
|
+
declare const updateEndpointSchema: z.ZodObject<{
|
|
74
|
+
url: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodURL>>;
|
|
75
|
+
events: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
76
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
77
|
+
}, z.core.$strip>;
|
|
78
|
+
|
|
79
|
+
declare const schemas_createEndpointSchema: typeof createEndpointSchema;
|
|
80
|
+
declare const schemas_updateEndpointSchema: typeof updateEndpointSchema;
|
|
81
|
+
declare namespace schemas {
|
|
82
|
+
export { schemas_createEndpointSchema as createEndpointSchema, schemas_updateEndpointSchema as updateEndpointSchema };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { type DeliveryStatus, type IWebhookDelivery, type IWebhookDeliveryDTO, type IWebhookEndpoint, type IWebhookEndpointCreatedDTO, type IWebhookEndpointDTO, type IWebhooksConfig, WebhooksModule, schemas };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
1
7
|
// src/models/endpoint.model.ts
|
|
2
8
|
var COLS = `id, workspace_id as "workspaceId", url, secret, events,
|
|
3
9
|
enabled, created_at as "createdAt"`;
|
|
@@ -181,7 +187,7 @@ var WebhookDispatcher = class {
|
|
|
181
187
|
const pending = await deliveries.claimForRetry();
|
|
182
188
|
await Promise.allSettled(
|
|
183
189
|
pending.map(
|
|
184
|
-
({ delivery, url, secret }) => this.attemptDelivery(
|
|
190
|
+
({ delivery, url: url2, secret }) => this.attemptDelivery(url2, secret, delivery, deliveries)
|
|
185
191
|
)
|
|
186
192
|
);
|
|
187
193
|
}
|
|
@@ -194,7 +200,7 @@ var WebhookDispatcher = class {
|
|
|
194
200
|
});
|
|
195
201
|
await this.attemptDelivery(endpoint.url, endpoint.secret, delivery, deliveries);
|
|
196
202
|
}
|
|
197
|
-
async attemptDelivery(
|
|
203
|
+
async attemptDelivery(url2, secret, delivery, deliveries) {
|
|
198
204
|
const body = JSON.stringify({
|
|
199
205
|
id: delivery.eventId,
|
|
200
206
|
type: delivery.eventType,
|
|
@@ -202,7 +208,7 @@ var WebhookDispatcher = class {
|
|
|
202
208
|
});
|
|
203
209
|
const signature = signPayload(secret, body);
|
|
204
210
|
try {
|
|
205
|
-
const res = await fetch(
|
|
211
|
+
const res = await fetch(url2, {
|
|
206
212
|
method: "POST",
|
|
207
213
|
headers: {
|
|
208
214
|
"Content-Type": "application/json",
|
|
@@ -238,9 +244,28 @@ var WebhookDispatcher = class {
|
|
|
238
244
|
|
|
239
245
|
// src/routes.ts
|
|
240
246
|
import { setApiResponse, HTTP } from "@fonderie/core";
|
|
241
|
-
import { requireAuth } from "@fonderie/core/middlewares";
|
|
247
|
+
import { requireAuth, validate } from "@fonderie/core/middlewares";
|
|
242
248
|
import { withBody } from "@fonderie/core/middlewares";
|
|
243
249
|
|
|
250
|
+
// src/schemas.ts
|
|
251
|
+
var schemas_exports = {};
|
|
252
|
+
__export(schemas_exports, {
|
|
253
|
+
createEndpointSchema: () => createEndpointSchema,
|
|
254
|
+
updateEndpointSchema: () => updateEndpointSchema
|
|
255
|
+
});
|
|
256
|
+
import { z } from "zod";
|
|
257
|
+
var url = z.string().trim().pipe(z.url());
|
|
258
|
+
var events = z.array(z.string().min(1).max(200)).max(100);
|
|
259
|
+
var createEndpointSchema = z.object({
|
|
260
|
+
url,
|
|
261
|
+
events: events.optional()
|
|
262
|
+
});
|
|
263
|
+
var updateEndpointSchema = z.object({
|
|
264
|
+
url: url.optional(),
|
|
265
|
+
events: events.optional(),
|
|
266
|
+
enabled: z.boolean().optional()
|
|
267
|
+
}).refine((o) => Object.values(o).some((v) => v !== void 0), "Provide at least one field");
|
|
268
|
+
|
|
244
269
|
// src/dtos/webhook.ts
|
|
245
270
|
function toEndpointDTO(e) {
|
|
246
271
|
return {
|
|
@@ -274,6 +299,7 @@ function buildWebhookRoutes(store, config = {}) {
|
|
|
274
299
|
"POST",
|
|
275
300
|
"/webhooks",
|
|
276
301
|
requireAuth,
|
|
302
|
+
validate(createEndpointSchema),
|
|
277
303
|
withBody,
|
|
278
304
|
async (ctx) => {
|
|
279
305
|
if (!ctx.workspace)
|
|
@@ -343,6 +369,7 @@ function buildWebhookRoutes(store, config = {}) {
|
|
|
343
369
|
"PATCH",
|
|
344
370
|
"/webhooks/:endpointId",
|
|
345
371
|
requireAuth,
|
|
372
|
+
validate(updateEndpointSchema),
|
|
346
373
|
withBody,
|
|
347
374
|
async (ctx) => {
|
|
348
375
|
if (!ctx.workspace)
|
|
@@ -487,6 +514,7 @@ var WebhooksModule = class {
|
|
|
487
514
|
}
|
|
488
515
|
};
|
|
489
516
|
export {
|
|
490
|
-
WebhooksModule
|
|
517
|
+
WebhooksModule,
|
|
518
|
+
schemas_exports as schemas
|
|
491
519
|
};
|
|
492
520
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/models/endpoint.model.ts","../src/models/delivery.model.ts","../src/signing.ts","../src/dispatcher.ts","../src/routes.ts","../src/dtos/webhook.ts","../src/module.ts"],"sourcesContent":["import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWebhookEndpoint } from '../types';\n\nconst COLS = `id, workspace_id as \"workspaceId\", url, secret, events,\n enabled, created_at as \"createdAt\"`;\n\nexport class EndpointModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync create(data: {\n\t\tworkspaceId: string;\n\t\turl: string;\n\t\tsecret: string;\n\t\tevents: string[];\n\t}): Promise<IWebhookEndpoint> {\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`INSERT INTO fonderie_webhook_endpoints (workspace_id, url, secret, events)\n\t\t\t VALUES ($1, $2, $3, $4)\n\t\t\t RETURNING ${COLS}`,\n\t\t\t[data.workspaceId, data.url, data.secret, data.events],\n\t\t);\n\t\treturn row!;\n\t}\n\n\tlist(workspaceId: string): Promise<IWebhookEndpoint[]> {\n\t\treturn this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE workspace_id = $1\n\t\t\t ORDER BY created_at DESC`,\n\t\t\t[workspaceId],\n\t\t);\n\t}\n\n\tasync findById(id: string, workspaceId: string): Promise<IWebhookEndpoint | null> {\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE id = $1 AND workspace_id = $2`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tworkspaceId: string,\n\t\tdata: { url?: string; events?: string[]; enabled?: boolean },\n\t): Promise<IWebhookEndpoint | null> {\n\t\tconst sets: string[] = [];\n\t\tconst params: unknown[] = [id, workspaceId];\n\n\t\tif (data.url !== undefined) {\n\t\t\tparams.push(data.url);\n\t\t\tsets.push(`url = $${params.length}`);\n\t\t}\n\t\tif (data.events !== undefined) {\n\t\t\tparams.push(data.events);\n\t\t\tsets.push(`events = $${params.length}`);\n\t\t}\n\t\tif (data.enabled !== undefined) {\n\t\t\tparams.push(data.enabled);\n\t\t\tsets.push(`enabled = $${params.length}`);\n\t\t}\n\n\t\tif (sets.length === 0) return this.findById(id, workspaceId);\n\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`UPDATE fonderie_webhook_endpoints SET ${sets.join(', ')}\n\t\t\t WHERE id = $1 AND workspace_id = $2\n\t\t\t RETURNING ${COLS}`,\n\t\t\tparams,\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync delete(id: string, workspaceId: string): Promise<boolean> {\n\t\tconst rows = await this.store.query<{ id: string }>(\n\t\t\t`DELETE FROM fonderie_webhook_endpoints WHERE id = $1 AND workspace_id = $2 RETURNING id`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\treturn rows.length > 0;\n\t}\n\n\tfindForEvent(workspaceId: string, eventType: string): Promise<IWebhookEndpoint[]> {\n\t\treturn this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE workspace_id = $1\n\t\t\t AND enabled = true\n\t\t\t AND (events = '{}' OR $2 = ANY(events))`,\n\t\t\t[workspaceId, eventType],\n\t\t);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWebhookDelivery } from '../types';\n\nconst COLS = `id, endpoint_id as \"endpointId\", event_id as \"eventId\",\n event_type as \"eventType\", payload, status, attempts,\n response_status as \"responseStatus\", response_body as \"responseBody\",\n next_attempt_at as \"nextAttemptAt\", delivered_at as \"deliveredAt\",\n created_at as \"createdAt\"`;\n\nconst D_COLS = `d.id, d.endpoint_id as \"endpointId\", d.event_id as \"eventId\",\n d.event_type as \"eventType\", d.payload, d.status, d.attempts,\n d.response_status as \"responseStatus\", d.response_body as \"responseBody\",\n d.next_attempt_at as \"nextAttemptAt\", d.delivered_at as \"deliveredAt\",\n d.created_at as \"createdAt\"`;\n\nexport interface IPendingRetry {\n\tdelivery: IWebhookDelivery;\n\turl: string;\n\tsecret: string;\n}\n\nexport class DeliveryModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync create(data: {\n\t\tendpointId: string;\n\t\teventId: string;\n\t\teventType: string;\n\t\tpayload: Record<string, unknown>;\n\t}): Promise<IWebhookDelivery> {\n\t\tconst [row] = await this.store.query<IWebhookDelivery>(\n\t\t\t`INSERT INTO fonderie_webhook_deliveries (endpoint_id, event_id, event_type, payload)\n\t\t\t VALUES ($1, $2, $3, $4)\n\t\t\t RETURNING ${COLS}`,\n\t\t\t[data.endpointId, data.eventId, data.eventType, JSON.stringify(data.payload)],\n\t\t);\n\t\treturn row!;\n\t}\n\n\tasync markResult(\n\t\tid: string,\n\t\tresult: {\n\t\t\tok: boolean;\n\t\t\tresponseStatus: number | null;\n\t\t\tresponseBody: string;\n\t\t\tnextAttemptAt: Date | null;\n\t\t},\n\t): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`UPDATE fonderie_webhook_deliveries\n\t\t\t SET status = $2,\n\t\t\t attempts = attempts + 1,\n\t\t\t response_status = $3,\n\t\t\t response_body = $4,\n\t\t\t next_attempt_at = $5,\n\t\t\t delivered_at = $6\n\t\t\t WHERE id = $1`,\n\t\t\t[\n\t\t\t\tid,\n\t\t\t\tresult.ok ? 'delivered' : 'failed',\n\t\t\t\tresult.responseStatus,\n\t\t\t\tresult.responseBody,\n\t\t\t\tresult.nextAttemptAt,\n\t\t\t\tresult.ok ? new Date() : null,\n\t\t\t],\n\t\t);\n\t}\n\n\tlistByEndpoint(endpointId: string, limit = 50): Promise<IWebhookDelivery[]> {\n\t\treturn this.store.query<IWebhookDelivery>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_deliveries\n\t\t\t WHERE endpoint_id = $1\n\t\t\t ORDER BY created_at DESC\n\t\t\t LIMIT $2`,\n\t\t\t[endpointId, limit],\n\t\t);\n\t}\n\n\tclaimForRetry(limit = 10): Promise<IPendingRetry[]> {\n\t\treturn this.store.query<IPendingRetry>(\n\t\t\t`SELECT ${D_COLS},\n\t\t\t e.url, e.secret\n\t\t\t FROM fonderie_webhook_deliveries d\n\t\t\t JOIN fonderie_webhook_endpoints e ON e.id = d.endpoint_id\n\t\t\t WHERE d.status = 'failed'\n\t\t\t AND d.next_attempt_at IS NOT NULL\n\t\t\t AND d.next_attempt_at <= now()\n\t\t\t AND e.enabled = true\n\t\t\t ORDER BY d.next_attempt_at\n\t\t\t LIMIT $1`,\n\t\t\t[limit],\n\t\t);\n\t}\n}\n","import { createHmac, randomBytes } from 'node:crypto';\n\nexport function generateSecret(): string {\n\treturn randomBytes(32).toString('hex');\n}\n\nexport function signPayload(secret: string, body: string): string {\n\treturn `sha256=${createHmac('sha256', secret).update(body).digest('hex')}`;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { IEventMeta } from '@fonderie/events';\n\nimport type { IWebhooksConfig } from './config';\nimport type { IWebhookEndpoint, IWebhookDelivery } from './types';\nimport { EndpointModel } from './models/endpoint.model';\nimport { DeliveryModel } from './models/delivery.model';\nimport { signPayload } from './signing';\n\nexport class WebhookDispatcher {\n\tprivate readonly maxAttempts: number;\n\tprivate readonly retryDelays: number[];\n\n\tconstructor(\n\t\tprivate readonly store: IStoreAdapter,\n\t\tprivate readonly config: IWebhooksConfig = {},\n\t) {\n\t\tthis.maxAttempts = config.maxAttempts ?? 3;\n\t\tthis.retryDelays = config.retryDelays ?? [60_000, 300_000, 1_800_000];\n\t}\n\n\t// Called by the bus consumer for every event.\n\t// Skips events that don't carry a workspaceId — not workspace-scoped.\n\tasync dispatch(payload: Record<string, unknown>, meta: IEventMeta): Promise<void> {\n\t\tconst workspaceId = payload['workspaceId'];\n\t\tif (typeof workspaceId !== 'string') return;\n\n\t\tconst endpoints = await new EndpointModel(this.store).findForEvent(workspaceId, meta.type);\n\t\tif (endpoints.length === 0) return;\n\n\t\tconst deliveries = new DeliveryModel(this.store);\n\t\tawait Promise.allSettled(endpoints.map((ep) => this.deliver(ep, payload, meta, deliveries)));\n\t}\n\n\t// Retries failed deliveries whose next_attempt_at has passed.\n\tasync retry(): Promise<void> {\n\t\tconst deliveries = new DeliveryModel(this.store);\n\t\tconst pending = await deliveries.claimForRetry();\n\t\tawait Promise.allSettled(\n\t\t\tpending.map(({ delivery, url, secret }) =>\n\t\t\t\tthis.attemptDelivery(url, secret, delivery, deliveries),\n\t\t\t),\n\t\t);\n\t}\n\n\tprivate async deliver(\n\t\tendpoint: IWebhookEndpoint,\n\t\tpayload: Record<string, unknown>,\n\t\tmeta: IEventMeta,\n\t\tdeliveries: DeliveryModel,\n\t): Promise<void> {\n\t\tconst delivery = await deliveries.create({\n\t\t\tendpointId: endpoint.id,\n\t\t\teventId: meta.id,\n\t\t\teventType: meta.type,\n\t\t\tpayload,\n\t\t});\n\t\tawait this.attemptDelivery(endpoint.url, endpoint.secret, delivery, deliveries);\n\t}\n\n\tasync attemptDelivery(\n\t\turl: string,\n\t\tsecret: string,\n\t\tdelivery: IWebhookDelivery,\n\t\tdeliveries: DeliveryModel,\n\t): Promise<void> {\n\t\tconst body = JSON.stringify({\n\t\t\tid: delivery.eventId,\n\t\t\ttype: delivery.eventType,\n\t\t\tdata: delivery.payload,\n\t\t});\n\n\t\tconst signature = signPayload(secret, body);\n\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t'X-Webhook-Signature': signature,\n\t\t\t\t\t'X-Webhook-Event': delivery.eventType,\n\t\t\t\t\t'X-Webhook-ID': delivery.id,\n\t\t\t\t},\n\t\t\t\tbody,\n\t\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t\t});\n\n\t\t\tconst responseBody = await res.text().catch(() => '');\n\n\t\t\tawait deliveries.markResult(delivery.id, {\n\t\t\t\tok: res.ok,\n\t\t\t\tresponseStatus: res.status,\n\t\t\t\tresponseBody,\n\t\t\t\tnextAttemptAt: res.ok ? null : this.nextRetryAt(delivery.attempts),\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tawait deliveries.markResult(delivery.id, {\n\t\t\t\tok: false,\n\t\t\t\tresponseStatus: null,\n\t\t\t\tresponseBody: err instanceof Error ? err.message : String(err),\n\t\t\t\tnextAttemptAt: this.nextRetryAt(delivery.attempts),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate nextRetryAt(currentAttempts: number): Date | null {\n\t\tif (currentAttempts + 1 >= this.maxAttempts) return null;\n\t\tconst delay =\n\t\t\tthis.retryDelays[currentAttempts] ?? this.retryDelays[this.retryDelays.length - 1]!;\n\t\treturn new Date(Date.now() + delay);\n\t}\n}\n","import type { Middleware } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport { requireAuth } from '@fonderie/core/middlewares';\nimport { withBody } from '@fonderie/core/middlewares';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { EndpointModel } from './models/endpoint.model';\nimport { DeliveryModel } from './models/delivery.model';\nimport { WebhookDispatcher } from './dispatcher';\nimport { generateSecret, signPayload } from './signing';\nimport { toEndpointDTO, toEndpointCreatedDTO, toDeliveryDTO } from './dtos/webhook';\nimport type { IWebhooksConfig } from './config';\n\ntype Route = [string, string, ...Middleware[]];\n\nexport function buildWebhookRoutes(store: IStoreAdapter, config: IWebhooksConfig = {}): Route[] {\n\treturn [\n\t\t[\n\t\t\t'POST',\n\t\t\t'/webhooks',\n\t\t\trequireAuth,\n\t\t\twithBody,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst body = ctx.meta['body'] as { url?: string; events?: string[] } | undefined;\n\t\t\t\tif (!body?.url)\n\t\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'MISSING_FIELD', 'url is required');\n\n\t\t\t\tconst endpoint = await new EndpointModel(store).create({\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t\turl: body.url,\n\t\t\t\t\tsecret: generateSecret(),\n\t\t\t\t\tevents: body.events ?? [],\n\t\t\t\t});\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.CREATED,\n\t\t\t\t\t'WEBHOOK_CREATED',\n\t\t\t\t\t'Webhook endpoint registered.',\n\t\t\t\t\ttoEndpointCreatedDTO(endpoint),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst list = await new EndpointModel(store).list(ctx.workspace.id);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'WEBHOOKS_FETCHED', 'Webhook endpoints retrieved.', {\n\t\t\t\t\tendpoints: list.map(toEndpointDTO),\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.OK,\n\t\t\t\t\t'WEBHOOK_FETCHED',\n\t\t\t\t\t'Webhook endpoint retrieved.',\n\t\t\t\t\ttoEndpointDTO(endpoint),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'PATCH',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\twithBody,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst body = ctx.meta['body'] as\n\t\t\t\t\t| { url?: string; events?: string[]; enabled?: boolean }\n\t\t\t\t\t| undefined;\n\n\t\t\t\tconst patch: { url?: string; events?: string[]; enabled?: boolean } = {};\n\t\t\t\tif (body?.url !== undefined) patch.url = body.url;\n\t\t\t\tif (body?.events !== undefined) patch.events = body.events;\n\t\t\t\tif (body?.enabled !== undefined) patch.enabled = body.enabled;\n\n\t\t\t\tconst updated = await new EndpointModel(store).update(endpointId, ctx.workspace.id, patch);\n\t\t\t\tif (!updated)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.OK,\n\t\t\t\t\t'WEBHOOK_UPDATED',\n\t\t\t\t\t'Webhook endpoint updated.',\n\t\t\t\t\ttoEndpointDTO(updated),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'DELETE',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst deleted = await new EndpointModel(store).delete(endpointId, ctx.workspace.id);\n\t\t\t\tif (!deleted)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn new Response(null, { status: HTTP.NO_CONTENT });\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks/:endpointId/deliveries',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\tconst list = await new DeliveryModel(store).listByEndpoint(endpointId);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'DELIVERIES_FETCHED', 'Deliveries retrieved.', {\n\t\t\t\t\tdeliveries: list.map(toDeliveryDTO),\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'POST',\n\t\t\t'/webhooks/:endpointId/test',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\tconst body = JSON.stringify({\n\t\t\t\t\tid: `test-${Date.now()}`,\n\t\t\t\t\ttype: 'webhook.test',\n\t\t\t\t\tdata: { workspaceId: ctx.workspace.id, message: 'Test webhook delivery.' },\n\t\t\t\t});\n\n\t\t\t\ttry {\n\t\t\t\t\tconst res = await fetch(endpoint.url, {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t\t'X-Webhook-Signature': signPayload(endpoint.secret, body),\n\t\t\t\t\t\t\t'X-Webhook-Event': 'webhook.test',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t\t\t\t});\n\n\t\t\t\t\treturn setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', {\n\t\t\t\t\t\tstatus: res.status,\n\t\t\t\t\t\tok: res.ok,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', {\n\t\t\t\t\t\tstatus: null,\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t],\n\t];\n}\n","import type { IWebhookEndpoint, IWebhookDelivery } from '../types';\n\nexport interface IWebhookEndpointDTO {\n\tid: string;\n\turl: string;\n\tevents: string[];\n\tenabled: boolean;\n\tcreatedAt: string;\n}\n\nexport interface IWebhookEndpointCreatedDTO extends IWebhookEndpointDTO {\n\tsecret: string;\n}\n\nexport interface IWebhookDeliveryDTO {\n\tid: string;\n\teventId: string;\n\teventType: string;\n\tstatus: string;\n\tattempts: number;\n\tresponseStatus: number | null;\n\tdeliveredAt: string | null;\n\tcreatedAt: string;\n}\n\nexport function toEndpointDTO(e: IWebhookEndpoint): IWebhookEndpointDTO {\n\treturn {\n\t\tid: e.id,\n\t\turl: e.url,\n\t\tevents: e.events,\n\t\tenabled: e.enabled,\n\t\tcreatedAt: e.createdAt.toISOString(),\n\t};\n}\n\nexport function toEndpointCreatedDTO(e: IWebhookEndpoint): IWebhookEndpointCreatedDTO {\n\treturn { ...toEndpointDTO(e), secret: e.secret };\n}\n\nexport function toDeliveryDTO(d: IWebhookDelivery): IWebhookDeliveryDTO {\n\treturn {\n\t\tid: d.id,\n\t\teventId: d.eventId,\n\t\teventType: d.eventType,\n\t\tstatus: d.status,\n\t\tattempts: d.attempts,\n\t\tresponseStatus: d.responseStatus,\n\t\tdeliveredAt: d.deliveredAt?.toISOString() ?? null,\n\t\tcreatedAt: d.createdAt.toISOString(),\n\t};\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport type { EventBus } from '@fonderie/events';\n\nimport type { IWebhooksConfig } from './config';\nimport { WebhookDispatcher } from './dispatcher';\nimport { buildWebhookRoutes } from './routes';\n\nexport class WebhooksModule implements IFonderieModule {\n\treadonly name = '@fonderie/webhooks';\n\treadonly deps = ['@fonderie/auth', '@fonderie/workspaces'];\n\n\tprivate retryTimer?: ReturnType<typeof setInterval>;\n\n\tconstructor(\n\t\tprivate readonly store: IStoreAdapter,\n\t\tprivate readonly config: IWebhooksConfig = {},\n\t\tprivate readonly bus?: EventBus,\n\t) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tconst dispatcher = new WebhookDispatcher(this.store, this.config);\n\n\t\tthis.bus?.on<Record<string, unknown>>(\n\t\t\t'*',\n\t\t\tasync (payload, meta) => {\n\t\t\t\tawait dispatcher.dispatch(payload, meta);\n\t\t\t},\n\t\t\t'webhooks',\n\t\t);\n\n\t\tconst interval = this.config.retryInterval ?? 60_000;\n\t\tthis.retryTimer = setInterval(() => {\n\t\t\tdispatcher.retry().catch((err) => console.error('[webhooks] retry error:', err));\n\t\t}, interval);\n\n\t\tconst routes = buildWebhookRoutes(this.store, this.config);\n\t\tfor (const [method, path, ...handlers] of routes) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["COLS"]}
|
|
1
|
+
{"version":3,"sources":["../src/models/endpoint.model.ts","../src/models/delivery.model.ts","../src/signing.ts","../src/dispatcher.ts","../src/routes.ts","../src/schemas.ts","../src/dtos/webhook.ts","../src/module.ts"],"sourcesContent":["import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWebhookEndpoint } from '../types';\n\nconst COLS = `id, workspace_id as \"workspaceId\", url, secret, events,\n enabled, created_at as \"createdAt\"`;\n\nexport class EndpointModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync create(data: {\n\t\tworkspaceId: string;\n\t\turl: string;\n\t\tsecret: string;\n\t\tevents: string[];\n\t}): Promise<IWebhookEndpoint> {\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`INSERT INTO fonderie_webhook_endpoints (workspace_id, url, secret, events)\n\t\t\t VALUES ($1, $2, $3, $4)\n\t\t\t RETURNING ${COLS}`,\n\t\t\t[data.workspaceId, data.url, data.secret, data.events],\n\t\t);\n\t\treturn row!;\n\t}\n\n\tlist(workspaceId: string): Promise<IWebhookEndpoint[]> {\n\t\treturn this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE workspace_id = $1\n\t\t\t ORDER BY created_at DESC`,\n\t\t\t[workspaceId],\n\t\t);\n\t}\n\n\tasync findById(id: string, workspaceId: string): Promise<IWebhookEndpoint | null> {\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE id = $1 AND workspace_id = $2`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\tworkspaceId: string,\n\t\tdata: { url?: string; events?: string[]; enabled?: boolean },\n\t): Promise<IWebhookEndpoint | null> {\n\t\tconst sets: string[] = [];\n\t\tconst params: unknown[] = [id, workspaceId];\n\n\t\tif (data.url !== undefined) {\n\t\t\tparams.push(data.url);\n\t\t\tsets.push(`url = $${params.length}`);\n\t\t}\n\t\tif (data.events !== undefined) {\n\t\t\tparams.push(data.events);\n\t\t\tsets.push(`events = $${params.length}`);\n\t\t}\n\t\tif (data.enabled !== undefined) {\n\t\t\tparams.push(data.enabled);\n\t\t\tsets.push(`enabled = $${params.length}`);\n\t\t}\n\n\t\tif (sets.length === 0) return this.findById(id, workspaceId);\n\n\t\tconst [row] = await this.store.query<IWebhookEndpoint>(\n\t\t\t`UPDATE fonderie_webhook_endpoints SET ${sets.join(', ')}\n\t\t\t WHERE id = $1 AND workspace_id = $2\n\t\t\t RETURNING ${COLS}`,\n\t\t\tparams,\n\t\t);\n\t\treturn row ?? null;\n\t}\n\n\tasync delete(id: string, workspaceId: string): Promise<boolean> {\n\t\tconst rows = await this.store.query<{ id: string }>(\n\t\t\t`DELETE FROM fonderie_webhook_endpoints WHERE id = $1 AND workspace_id = $2 RETURNING id`,\n\t\t\t[id, workspaceId],\n\t\t);\n\t\treturn rows.length > 0;\n\t}\n\n\tfindForEvent(workspaceId: string, eventType: string): Promise<IWebhookEndpoint[]> {\n\t\treturn this.store.query<IWebhookEndpoint>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_endpoints\n\t\t\t WHERE workspace_id = $1\n\t\t\t AND enabled = true\n\t\t\t AND (events = '{}' OR $2 = ANY(events))`,\n\t\t\t[workspaceId, eventType],\n\t\t);\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IWebhookDelivery } from '../types';\n\nconst COLS = `id, endpoint_id as \"endpointId\", event_id as \"eventId\",\n event_type as \"eventType\", payload, status, attempts,\n response_status as \"responseStatus\", response_body as \"responseBody\",\n next_attempt_at as \"nextAttemptAt\", delivered_at as \"deliveredAt\",\n created_at as \"createdAt\"`;\n\nconst D_COLS = `d.id, d.endpoint_id as \"endpointId\", d.event_id as \"eventId\",\n d.event_type as \"eventType\", d.payload, d.status, d.attempts,\n d.response_status as \"responseStatus\", d.response_body as \"responseBody\",\n d.next_attempt_at as \"nextAttemptAt\", d.delivered_at as \"deliveredAt\",\n d.created_at as \"createdAt\"`;\n\nexport interface IPendingRetry {\n\tdelivery: IWebhookDelivery;\n\turl: string;\n\tsecret: string;\n}\n\nexport class DeliveryModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tasync create(data: {\n\t\tendpointId: string;\n\t\teventId: string;\n\t\teventType: string;\n\t\tpayload: Record<string, unknown>;\n\t}): Promise<IWebhookDelivery> {\n\t\tconst [row] = await this.store.query<IWebhookDelivery>(\n\t\t\t`INSERT INTO fonderie_webhook_deliveries (endpoint_id, event_id, event_type, payload)\n\t\t\t VALUES ($1, $2, $3, $4)\n\t\t\t RETURNING ${COLS}`,\n\t\t\t[data.endpointId, data.eventId, data.eventType, JSON.stringify(data.payload)],\n\t\t);\n\t\treturn row!;\n\t}\n\n\tasync markResult(\n\t\tid: string,\n\t\tresult: {\n\t\t\tok: boolean;\n\t\t\tresponseStatus: number | null;\n\t\t\tresponseBody: string;\n\t\t\tnextAttemptAt: Date | null;\n\t\t},\n\t): Promise<void> {\n\t\tawait this.store.query(\n\t\t\t`UPDATE fonderie_webhook_deliveries\n\t\t\t SET status = $2,\n\t\t\t attempts = attempts + 1,\n\t\t\t response_status = $3,\n\t\t\t response_body = $4,\n\t\t\t next_attempt_at = $5,\n\t\t\t delivered_at = $6\n\t\t\t WHERE id = $1`,\n\t\t\t[\n\t\t\t\tid,\n\t\t\t\tresult.ok ? 'delivered' : 'failed',\n\t\t\t\tresult.responseStatus,\n\t\t\t\tresult.responseBody,\n\t\t\t\tresult.nextAttemptAt,\n\t\t\t\tresult.ok ? new Date() : null,\n\t\t\t],\n\t\t);\n\t}\n\n\tlistByEndpoint(endpointId: string, limit = 50): Promise<IWebhookDelivery[]> {\n\t\treturn this.store.query<IWebhookDelivery>(\n\t\t\t`SELECT ${COLS} FROM fonderie_webhook_deliveries\n\t\t\t WHERE endpoint_id = $1\n\t\t\t ORDER BY created_at DESC\n\t\t\t LIMIT $2`,\n\t\t\t[endpointId, limit],\n\t\t);\n\t}\n\n\tclaimForRetry(limit = 10): Promise<IPendingRetry[]> {\n\t\treturn this.store.query<IPendingRetry>(\n\t\t\t`SELECT ${D_COLS},\n\t\t\t e.url, e.secret\n\t\t\t FROM fonderie_webhook_deliveries d\n\t\t\t JOIN fonderie_webhook_endpoints e ON e.id = d.endpoint_id\n\t\t\t WHERE d.status = 'failed'\n\t\t\t AND d.next_attempt_at IS NOT NULL\n\t\t\t AND d.next_attempt_at <= now()\n\t\t\t AND e.enabled = true\n\t\t\t ORDER BY d.next_attempt_at\n\t\t\t LIMIT $1`,\n\t\t\t[limit],\n\t\t);\n\t}\n}\n","import { createHmac, randomBytes } from 'node:crypto';\n\nexport function generateSecret(): string {\n\treturn randomBytes(32).toString('hex');\n}\n\nexport function signPayload(secret: string, body: string): string {\n\treturn `sha256=${createHmac('sha256', secret).update(body).digest('hex')}`;\n}\n","import type { IStoreAdapter } from '@fonderie/store';\nimport type { IEventMeta } from '@fonderie/events';\n\nimport type { IWebhooksConfig } from './config';\nimport type { IWebhookEndpoint, IWebhookDelivery } from './types';\nimport { EndpointModel } from './models/endpoint.model';\nimport { DeliveryModel } from './models/delivery.model';\nimport { signPayload } from './signing';\n\nexport class WebhookDispatcher {\n\tprivate readonly maxAttempts: number;\n\tprivate readonly retryDelays: number[];\n\n\tconstructor(\n\t\tprivate readonly store: IStoreAdapter,\n\t\tprivate readonly config: IWebhooksConfig = {},\n\t) {\n\t\tthis.maxAttempts = config.maxAttempts ?? 3;\n\t\tthis.retryDelays = config.retryDelays ?? [60_000, 300_000, 1_800_000];\n\t}\n\n\t// Called by the bus consumer for every event.\n\t// Skips events that don't carry a workspaceId — not workspace-scoped.\n\tasync dispatch(payload: Record<string, unknown>, meta: IEventMeta): Promise<void> {\n\t\tconst workspaceId = payload['workspaceId'];\n\t\tif (typeof workspaceId !== 'string') return;\n\n\t\tconst endpoints = await new EndpointModel(this.store).findForEvent(workspaceId, meta.type);\n\t\tif (endpoints.length === 0) return;\n\n\t\tconst deliveries = new DeliveryModel(this.store);\n\t\tawait Promise.allSettled(endpoints.map((ep) => this.deliver(ep, payload, meta, deliveries)));\n\t}\n\n\t// Retries failed deliveries whose next_attempt_at has passed.\n\tasync retry(): Promise<void> {\n\t\tconst deliveries = new DeliveryModel(this.store);\n\t\tconst pending = await deliveries.claimForRetry();\n\t\tawait Promise.allSettled(\n\t\t\tpending.map(({ delivery, url, secret }) =>\n\t\t\t\tthis.attemptDelivery(url, secret, delivery, deliveries),\n\t\t\t),\n\t\t);\n\t}\n\n\tprivate async deliver(\n\t\tendpoint: IWebhookEndpoint,\n\t\tpayload: Record<string, unknown>,\n\t\tmeta: IEventMeta,\n\t\tdeliveries: DeliveryModel,\n\t): Promise<void> {\n\t\tconst delivery = await deliveries.create({\n\t\t\tendpointId: endpoint.id,\n\t\t\teventId: meta.id,\n\t\t\teventType: meta.type,\n\t\t\tpayload,\n\t\t});\n\t\tawait this.attemptDelivery(endpoint.url, endpoint.secret, delivery, deliveries);\n\t}\n\n\tasync attemptDelivery(\n\t\turl: string,\n\t\tsecret: string,\n\t\tdelivery: IWebhookDelivery,\n\t\tdeliveries: DeliveryModel,\n\t): Promise<void> {\n\t\tconst body = JSON.stringify({\n\t\t\tid: delivery.eventId,\n\t\t\ttype: delivery.eventType,\n\t\t\tdata: delivery.payload,\n\t\t});\n\n\t\tconst signature = signPayload(secret, body);\n\n\t\ttry {\n\t\t\tconst res = await fetch(url, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t'X-Webhook-Signature': signature,\n\t\t\t\t\t'X-Webhook-Event': delivery.eventType,\n\t\t\t\t\t'X-Webhook-ID': delivery.id,\n\t\t\t\t},\n\t\t\t\tbody,\n\t\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t\t});\n\n\t\t\tconst responseBody = await res.text().catch(() => '');\n\n\t\t\tawait deliveries.markResult(delivery.id, {\n\t\t\t\tok: res.ok,\n\t\t\t\tresponseStatus: res.status,\n\t\t\t\tresponseBody,\n\t\t\t\tnextAttemptAt: res.ok ? null : this.nextRetryAt(delivery.attempts),\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tawait deliveries.markResult(delivery.id, {\n\t\t\t\tok: false,\n\t\t\t\tresponseStatus: null,\n\t\t\t\tresponseBody: err instanceof Error ? err.message : String(err),\n\t\t\t\tnextAttemptAt: this.nextRetryAt(delivery.attempts),\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate nextRetryAt(currentAttempts: number): Date | null {\n\t\tif (currentAttempts + 1 >= this.maxAttempts) return null;\n\t\tconst delay =\n\t\t\tthis.retryDelays[currentAttempts] ?? this.retryDelays[this.retryDelays.length - 1]!;\n\t\treturn new Date(Date.now() + delay);\n\t}\n}\n","import type { Middleware } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport { requireAuth, validate } from '@fonderie/core/middlewares';\nimport { withBody } from '@fonderie/core/middlewares';\n\nimport { createEndpointSchema, updateEndpointSchema } from './schemas';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { EndpointModel } from './models/endpoint.model';\nimport { DeliveryModel } from './models/delivery.model';\nimport { WebhookDispatcher } from './dispatcher';\nimport { generateSecret, signPayload } from './signing';\nimport { toEndpointDTO, toEndpointCreatedDTO, toDeliveryDTO } from './dtos/webhook';\nimport type { IWebhooksConfig } from './config';\n\ntype Route = [string, string, ...Middleware[]];\n\nexport function buildWebhookRoutes(store: IStoreAdapter, config: IWebhooksConfig = {}): Route[] {\n\treturn [\n\t\t[\n\t\t\t'POST',\n\t\t\t'/webhooks',\n\t\t\trequireAuth,\n\t\t\tvalidate(createEndpointSchema),\n\t\t\twithBody,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst body = ctx.meta['body'] as { url?: string; events?: string[] } | undefined;\n\t\t\t\tif (!body?.url)\n\t\t\t\t\treturn setApiResponse(HTTP.UNPROCESSABLE, 'MISSING_FIELD', 'url is required');\n\n\t\t\t\tconst endpoint = await new EndpointModel(store).create({\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t\turl: body.url,\n\t\t\t\t\tsecret: generateSecret(),\n\t\t\t\t\tevents: body.events ?? [],\n\t\t\t\t});\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.CREATED,\n\t\t\t\t\t'WEBHOOK_CREATED',\n\t\t\t\t\t'Webhook endpoint registered.',\n\t\t\t\t\ttoEndpointCreatedDTO(endpoint),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst list = await new EndpointModel(store).list(ctx.workspace.id);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'WEBHOOKS_FETCHED', 'Webhook endpoints retrieved.', {\n\t\t\t\t\tendpoints: list.map(toEndpointDTO),\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.OK,\n\t\t\t\t\t'WEBHOOK_FETCHED',\n\t\t\t\t\t'Webhook endpoint retrieved.',\n\t\t\t\t\ttoEndpointDTO(endpoint),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'PATCH',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tvalidate(updateEndpointSchema),\n\t\t\twithBody,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst body = ctx.meta['body'] as\n\t\t\t\t\t| { url?: string; events?: string[]; enabled?: boolean }\n\t\t\t\t\t| undefined;\n\n\t\t\t\tconst patch: { url?: string; events?: string[]; enabled?: boolean } = {};\n\t\t\t\tif (body?.url !== undefined) patch.url = body.url;\n\t\t\t\tif (body?.events !== undefined) patch.events = body.events;\n\t\t\t\tif (body?.enabled !== undefined) patch.enabled = body.enabled;\n\n\t\t\t\tconst updated = await new EndpointModel(store).update(endpointId, ctx.workspace.id, patch);\n\t\t\t\tif (!updated)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn setApiResponse(\n\t\t\t\t\tHTTP.OK,\n\t\t\t\t\t'WEBHOOK_UPDATED',\n\t\t\t\t\t'Webhook endpoint updated.',\n\t\t\t\t\ttoEndpointDTO(updated),\n\t\t\t\t);\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'DELETE',\n\t\t\t'/webhooks/:endpointId',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst deleted = await new EndpointModel(store).delete(endpointId, ctx.workspace.id);\n\t\t\t\tif (!deleted)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\treturn new Response(null, { status: HTTP.NO_CONTENT });\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'GET',\n\t\t\t'/webhooks/:endpointId/deliveries',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\tconst list = await new DeliveryModel(store).listByEndpoint(endpointId);\n\t\t\t\treturn setApiResponse(HTTP.OK, 'DELIVERIES_FETCHED', 'Deliveries retrieved.', {\n\t\t\t\t\tdeliveries: list.map(toDeliveryDTO),\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\n\t\t[\n\t\t\t'POST',\n\t\t\t'/webhooks/:endpointId/test',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst { endpointId } = ctx.meta['params'] as { endpointId: string };\n\t\t\t\tconst endpoint = await new EndpointModel(store).findById(endpointId, ctx.workspace.id);\n\t\t\t\tif (!endpoint)\n\t\t\t\t\treturn setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Webhook endpoint not found');\n\n\t\t\t\tconst body = JSON.stringify({\n\t\t\t\t\tid: `test-${Date.now()}`,\n\t\t\t\t\ttype: 'webhook.test',\n\t\t\t\t\tdata: { workspaceId: ctx.workspace.id, message: 'Test webhook delivery.' },\n\t\t\t\t});\n\n\t\t\t\ttry {\n\t\t\t\t\tconst res = await fetch(endpoint.url, {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t\t'X-Webhook-Signature': signPayload(endpoint.secret, body),\n\t\t\t\t\t\t\t'X-Webhook-Event': 'webhook.test',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\tsignal: AbortSignal.timeout(10_000),\n\t\t\t\t\t});\n\n\t\t\t\t\treturn setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', {\n\t\t\t\t\t\tstatus: res.status,\n\t\t\t\t\t\tok: res.ok,\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn setApiResponse(HTTP.OK, 'TEST_SENT', 'Test delivery attempted.', {\n\t\t\t\t\t\tstatus: null,\n\t\t\t\t\t\tok: false,\n\t\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t},\n\t\t],\n\t];\n}\n","import { z } from 'zod';\n\n// Request schemas — the validation contract for webhooks' body-taking routes.\n// Wired via @fonderie/core's validate(); same pattern as @fonderie/auth.\n\nconst url = z.string().trim().pipe(z.url());\nconst events = z.array(z.string().min(1).max(200)).max(100);\n\nexport const createEndpointSchema = z.object({\n\turl,\n\tevents: events.optional(),\n});\n\nexport const updateEndpointSchema = z\n\t.object({\n\t\turl: url.optional(),\n\t\tevents: events.optional(),\n\t\tenabled: z.boolean().optional(),\n\t})\n\t.refine((o) => Object.values(o).some((v) => v !== undefined), 'Provide at least one field');\n","import type { IWebhookEndpoint, IWebhookDelivery } from '../types';\n\nexport interface IWebhookEndpointDTO {\n\tid: string;\n\turl: string;\n\tevents: string[];\n\tenabled: boolean;\n\tcreatedAt: string;\n}\n\nexport interface IWebhookEndpointCreatedDTO extends IWebhookEndpointDTO {\n\tsecret: string;\n}\n\nexport interface IWebhookDeliveryDTO {\n\tid: string;\n\teventId: string;\n\teventType: string;\n\tstatus: string;\n\tattempts: number;\n\tresponseStatus: number | null;\n\tdeliveredAt: string | null;\n\tcreatedAt: string;\n}\n\nexport function toEndpointDTO(e: IWebhookEndpoint): IWebhookEndpointDTO {\n\treturn {\n\t\tid: e.id,\n\t\turl: e.url,\n\t\tevents: e.events,\n\t\tenabled: e.enabled,\n\t\tcreatedAt: e.createdAt.toISOString(),\n\t};\n}\n\nexport function toEndpointCreatedDTO(e: IWebhookEndpoint): IWebhookEndpointCreatedDTO {\n\treturn { ...toEndpointDTO(e), secret: e.secret };\n}\n\nexport function toDeliveryDTO(d: IWebhookDelivery): IWebhookDeliveryDTO {\n\treturn {\n\t\tid: d.id,\n\t\teventId: d.eventId,\n\t\teventType: d.eventType,\n\t\tstatus: d.status,\n\t\tattempts: d.attempts,\n\t\tresponseStatus: d.responseStatus,\n\t\tdeliveredAt: d.deliveredAt?.toISOString() ?? null,\n\t\tcreatedAt: d.createdAt.toISOString(),\n\t};\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\nimport type { EventBus } from '@fonderie/events';\n\nimport type { IWebhooksConfig } from './config';\nimport { WebhookDispatcher } from './dispatcher';\nimport { buildWebhookRoutes } from './routes';\n\nexport class WebhooksModule implements IFonderieModule {\n\treadonly name = '@fonderie/webhooks';\n\treadonly deps = ['@fonderie/auth', '@fonderie/workspaces'];\n\n\tprivate retryTimer?: ReturnType<typeof setInterval>;\n\n\tconstructor(\n\t\tprivate readonly store: IStoreAdapter,\n\t\tprivate readonly config: IWebhooksConfig = {},\n\t\tprivate readonly bus?: EventBus,\n\t) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tconst dispatcher = new WebhookDispatcher(this.store, this.config);\n\n\t\tthis.bus?.on<Record<string, unknown>>(\n\t\t\t'*',\n\t\t\tasync (payload, meta) => {\n\t\t\t\tawait dispatcher.dispatch(payload, meta);\n\t\t\t},\n\t\t\t'webhooks',\n\t\t);\n\n\t\tconst interval = this.config.retryInterval ?? 60_000;\n\t\tthis.retryTimer = setInterval(() => {\n\t\t\tdispatcher.retry().catch((err) => console.error('[webhooks] retry error:', err));\n\t\t}, interval);\n\n\t\tconst routes = buildWebhookRoutes(this.store, this.config);\n\t\tfor (const [method, path, ...handlers] of routes) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["COLS","url"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonderie/webhooks",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Outgoing webhook engine — register endpoints, fan out workspace-scoped events, track delivery, and retry on failure.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fonderie-js",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"module": "./dist/index.js",
|
|
30
30
|
"types": "./dist/index.d.ts",
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "tsup &&
|
|
32
|
+
"build": "tsup && tsup --config tsup.migrations.ts",
|
|
33
33
|
"dev": "tsup --watch",
|
|
34
34
|
"typecheck": "tsc --noEmit",
|
|
35
35
|
"test": "tsx --test src/__tests__/*.test.ts",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"check": "biome check --write src"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@fonderie/core": "^0.1.
|
|
42
|
-
"@fonderie/events": "^1.0.
|
|
43
|
-
"@fonderie/store": "^0.1.
|
|
41
|
+
"@fonderie/core": "^0.1.1",
|
|
42
|
+
"@fonderie/events": "^1.0.1",
|
|
43
|
+
"@fonderie/store": "^0.1.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependenciesMeta": {
|
|
46
46
|
"@fonderie/events": {
|
|
@@ -61,16 +61,20 @@
|
|
|
61
61
|
},
|
|
62
62
|
"files": [
|
|
63
63
|
"dist",
|
|
64
|
+
"brain",
|
|
64
65
|
"LICENSE",
|
|
65
66
|
"README.md"
|
|
66
67
|
],
|
|
67
68
|
"repository": {
|
|
68
69
|
"type": "git",
|
|
69
|
-
"url": "git+https://github.com/
|
|
70
|
+
"url": "git+https://github.com/fonderiejs/sdk.git",
|
|
70
71
|
"directory": "packages/webhooks"
|
|
71
72
|
},
|
|
72
|
-
"homepage": "https://github.com/
|
|
73
|
+
"homepage": "https://github.com/fonderiejs/sdk/tree/main/packages/webhooks#readme",
|
|
73
74
|
"bugs": {
|
|
74
|
-
"url": "https://github.com/
|
|
75
|
+
"url": "https://github.com/fonderiejs/sdk/issues"
|
|
76
|
+
},
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"zod": "^4.4.3"
|
|
75
79
|
}
|
|
76
80
|
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
-- fonderie_webhook_endpoints
|
|
2
|
-
CREATE TABLE IF NOT EXISTS fonderie_webhook_endpoints (
|
|
3
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4
|
-
workspace_id UUID NOT NULL,
|
|
5
|
-
url TEXT NOT NULL,
|
|
6
|
-
secret TEXT NOT NULL,
|
|
7
|
-
events TEXT[] NOT NULL DEFAULT '{}',
|
|
8
|
-
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
9
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
10
|
-
);
|
|
11
|
-
|
|
12
|
-
CREATE INDEX IF NOT EXISTS idx_fwhe_workspace
|
|
13
|
-
ON fonderie_webhook_endpoints (workspace_id)
|
|
14
|
-
WHERE enabled = true;
|
|
15
|
-
|
|
16
|
-
-- fonderie_webhook_deliveries
|
|
17
|
-
CREATE TABLE IF NOT EXISTS fonderie_webhook_deliveries (
|
|
18
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
19
|
-
endpoint_id UUID NOT NULL REFERENCES fonderie_webhook_endpoints(id) ON DELETE CASCADE,
|
|
20
|
-
event_id TEXT NOT NULL,
|
|
21
|
-
event_type TEXT NOT NULL,
|
|
22
|
-
payload JSONB NOT NULL,
|
|
23
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
24
|
-
attempts INT NOT NULL DEFAULT 0,
|
|
25
|
-
response_status INT,
|
|
26
|
-
response_body TEXT,
|
|
27
|
-
next_attempt_at TIMESTAMPTZ,
|
|
28
|
-
delivered_at TIMESTAMPTZ,
|
|
29
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
CREATE INDEX IF NOT EXISTS idx_fwhd_endpoint
|
|
33
|
-
ON fonderie_webhook_deliveries (endpoint_id, created_at DESC);
|
|
34
|
-
|
|
35
|
-
CREATE INDEX IF NOT EXISTS idx_fwhd_retry
|
|
36
|
-
ON fonderie_webhook_deliveries (next_attempt_at)
|
|
37
|
-
WHERE status = 'failed' AND next_attempt_at IS NOT NULL;
|