@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.
@@ -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(url, secret, delivery, deliveries)
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(url, secret, delivery, deliveries) {
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(url, {
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
@@ -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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACIA,IAAM,OAAO;AAAA;AAGN,IAAM,gBAAN,MAAoB;AAAA,EAC1B,YAA6B,OAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAE7B,MAAM,OAAO,MAKiB;AAC7B,UAAM,CAAC,GAAG,IAAI,MAAM,KAAK,MAAM;AAAA,MAC9B;AAAA;AAAA,gBAEa,IAAI;AAAA,MACjB,CAAC,KAAK,aAAa,KAAK,KAAK,KAAK,QAAQ,KAAK,MAAM;AAAA,IACtD;AACA,WAAO;AAAA,EACR;AAAA,EAEA,KAAK,aAAkD;AACtD,WAAO,KAAK,MAAM;AAAA,MACjB,UAAU,IAAI;AAAA;AAAA;AAAA,MAGd,CAAC,WAAW;AAAA,IACb;AAAA,EACD;AAAA,EAEA,MAAM,SAAS,IAAY,aAAuD;AACjF,UAAM,CAAC,GAAG,IAAI,MAAM,KAAK,MAAM;AAAA,MAC9B,UAAU,IAAI;AAAA;AAAA,MAEd,CAAC,IAAI,WAAW;AAAA,IACjB;AACA,WAAO,OAAO;AAAA,EACf;AAAA,EAEA,MAAM,OACL,IACA,aACA,MACmC;AACnC,UAAM,OAAiB,CAAC;AACxB,UAAM,SAAoB,CAAC,IAAI,WAAW;AAE1C,QAAI,KAAK,QAAQ,QAAW;AAC3B,aAAO,KAAK,KAAK,GAAG;AACpB,WAAK,KAAK,UAAU,OAAO,MAAM,EAAE;AAAA,IACpC;AACA,QAAI,KAAK,WAAW,QAAW;AAC9B,aAAO,KAAK,KAAK,MAAM;AACvB,WAAK,KAAK,aAAa,OAAO,MAAM,EAAE;AAAA,IACvC;AACA,QAAI,KAAK,YAAY,QAAW;AAC/B,aAAO,KAAK,KAAK,OAAO;AACxB,WAAK,KAAK,cAAc,OAAO,MAAM,EAAE;AAAA,IACxC;AAEA,QAAI,KAAK,WAAW,EAAG,QAAO,KAAK,SAAS,IAAI,WAAW;AAE3D,UAAM,CAAC,GAAG,IAAI,MAAM,KAAK,MAAM;AAAA,MAC9B,yCAAyC,KAAK,KAAK,IAAI,CAAC;AAAA;AAAA,gBAE3C,IAAI;AAAA,MACjB;AAAA,IACD;AACA,WAAO,OAAO;AAAA,EACf;AAAA,EAEA,MAAM,OAAO,IAAY,aAAuC;AAC/D,UAAM,OAAO,MAAM,KAAK,MAAM;AAAA,MAC7B;AAAA,MACA,CAAC,IAAI,WAAW;AAAA,IACjB;AACA,WAAO,KAAK,SAAS;AAAA,EACtB;AAAA,EAEA,aAAa,aAAqB,WAAgD;AACjF,WAAO,KAAK,MAAM;AAAA,MACjB,UAAU,IAAI;AAAA;AAAA;AAAA;AAAA,MAId,CAAC,aAAa,SAAS;AAAA,IACxB;AAAA,EACD;AACD;;;ACxFA,IAAMA,QAAO;AAAA;AAAA;AAAA;AAAA;AAMb,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAYR,IAAM,gBAAN,MAAoB;AAAA,EAC1B,YAA6B,OAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAE7B,MAAM,OAAO,MAKiB;AAC7B,UAAM,CAAC,GAAG,IAAI,MAAM,KAAK,MAAM;AAAA,MAC9B;AAAA;AAAA,gBAEaA,KAAI;AAAA,MACjB,CAAC,KAAK,YAAY,KAAK,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,CAAC;AAAA,IAC7E;AACA,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,WACL,IACA,QAMgB;AAChB,UAAM,KAAK,MAAM;AAAA,MAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA;AAAA,QACC;AAAA,QACA,OAAO,KAAK,cAAc;AAAA,QAC1B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO,KAAK,oBAAI,KAAK,IAAI;AAAA,MAC1B;AAAA,IACD;AAAA,EACD;AAAA,EAEA,eAAe,YAAoB,QAAQ,IAAiC;AAC3E,WAAO,KAAK,MAAM;AAAA,MACjB,UAAUA,KAAI;AAAA;AAAA;AAAA;AAAA,MAId,CAAC,YAAY,KAAK;AAAA,IACnB;AAAA,EACD;AAAA,EAEA,cAAc,QAAQ,IAA8B;AACnD,WAAO,KAAK,MAAM;AAAA,MACjB,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUhB,CAAC,KAAK;AAAA,IACP;AAAA,EACD;AACD;;;AC9FA,yBAAwC;AAEjC,SAAS,iBAAyB;AACxC,aAAO,gCAAY,EAAE,EAAE,SAAS,KAAK;AACtC;AAEO,SAAS,YAAY,QAAgB,MAAsB;AACjE,SAAO,cAAU,+BAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK,CAAC;AACzE;;;ACCO,IAAM,oBAAN,MAAwB;AAAA,EAI9B,YACkB,OACA,SAA0B,CAAC,GAC3C;AAFgB;AACA;AAEjB,SAAK,cAAc,OAAO,eAAe;AACzC,SAAK,cAAc,OAAO,eAAe,CAAC,KAAQ,KAAS,IAAS;AAAA,EACrE;AAAA,EALkB;AAAA,EACA;AAAA,EALD;AAAA,EACA;AAAA;AAAA;AAAA,EAYjB,MAAM,SAAS,SAAkC,MAAiC;AACjF,UAAM,cAAc,QAAQ,aAAa;AACzC,QAAI,OAAO,gBAAgB,SAAU;AAErC,UAAM,YAAY,MAAM,IAAI,cAAc,KAAK,KAAK,EAAE,aAAa,aAAa,KAAK,IAAI;AACzF,QAAI,UAAU,WAAW,EAAG;AAE5B,UAAM,aAAa,IAAI,cAAc,KAAK,KAAK;AAC/C,UAAM,QAAQ,WAAW,UAAU,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,SAAS,MAAM,UAAU,CAAC,CAAC;AAAA,EAC5F;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC5B,UAAM,aAAa,IAAI,cAAc,KAAK,KAAK;AAC/C,UAAM,UAAU,MAAM,WAAW,cAAc;AAC/C,UAAM,QAAQ;AAAA,MACb,QAAQ;AAAA,QAAI,CAAC,EAAE,UAAU,KAAK,OAAO,MACpC,KAAK,gBAAgB,KAAK,QAAQ,UAAU,UAAU;AAAA,MACvD;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,QACb,UACA,SACA,MACA,YACgB;AAChB,UAAM,WAAW,MAAM,WAAW,OAAO;AAAA,MACxC,YAAY,SAAS;AAAA,MACrB,SAAS,KAAK;AAAA,MACd,WAAW,KAAK;AAAA,MAChB;AAAA,IACD,CAAC;AACD,UAAM,KAAK,gBAAgB,SAAS,KAAK,SAAS,QAAQ,UAAU,UAAU;AAAA,EAC/E;AAAA,EAEA,MAAM,gBACL,KACA,QACA,UACA,YACgB;AAChB,UAAM,OAAO,KAAK,UAAU;AAAA,MAC3B,IAAI,SAAS;AAAA,MACb,MAAM,SAAS;AAAA,MACf,MAAM,SAAS;AAAA,IAChB,CAAC;AAED,UAAM,YAAY,YAAY,QAAQ,IAAI;AAE1C,QAAI;AACH,YAAM,MAAM,MAAM,MAAM,KAAK;AAAA,QAC5B,QAAQ;AAAA,QACR,SAAS;AAAA,UACR,gBAAgB;AAAA,UAChB,uBAAuB;AAAA,UACvB,mBAAmB,SAAS;AAAA,UAC5B,gBAAgB,SAAS;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACnC,CAAC;AAED,YAAM,eAAe,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAEpD,YAAM,WAAW,WAAW,SAAS,IAAI;AAAA,QACxC,IAAI,IAAI;AAAA,QACR,gBAAgB,IAAI;AAAA,QACpB;AAAA,QACA,eAAe,IAAI,KAAK,OAAO,KAAK,YAAY,SAAS,QAAQ;AAAA,MAClE,CAAC;AAAA,IACF,SAAS,KAAK;AACb,YAAM,WAAW,WAAW,SAAS,IAAI;AAAA,QACxC,IAAI;AAAA,QACJ,gBAAgB;AAAA,QAChB,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QAC7D,eAAe,KAAK,YAAY,SAAS,QAAQ;AAAA,MAClD,CAAC;AAAA,IACF;AAAA,EACD;AAAA,EAEQ,YAAY,iBAAsC;AACzD,QAAI,kBAAkB,KAAK,KAAK,YAAa,QAAO;AACpD,UAAM,QACL,KAAK,YAAY,eAAe,KAAK,KAAK,YAAY,KAAK,YAAY,SAAS,CAAC;AAClF,WAAO,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK;AAAA,EACnC;AACD;;;AC9GA,kBAAqC;AACrC,yBAA4B;AAC5B,IAAAC,sBAAyB;;;ACsBlB,SAAS,cAAc,GAA0C;AACvE,SAAO;AAAA,IACN,IAAI,EAAE;AAAA,IACN,KAAK,EAAE;AAAA,IACP,QAAQ,EAAE;AAAA,IACV,SAAS,EAAE;AAAA,IACX,WAAW,EAAE,UAAU,YAAY;AAAA,EACpC;AACD;AAEO,SAAS,qBAAqB,GAAiD;AACrF,SAAO,EAAE,GAAG,cAAc,CAAC,GAAG,QAAQ,EAAE,OAAO;AAChD;AAEO,SAAS,cAAc,GAA0C;AACvE,SAAO;AAAA,IACN,IAAI,EAAE;AAAA,IACN,SAAS,EAAE;AAAA,IACX,WAAW,EAAE;AAAA,IACb,QAAQ,EAAE;AAAA,IACV,UAAU,EAAE;AAAA,IACZ,gBAAgB,EAAE;AAAA,IAClB,aAAa,EAAE,aAAa,YAAY,KAAK;AAAA,IAC7C,WAAW,EAAE,UAAU,YAAY;AAAA,EACpC;AACD;;;ADnCO,SAAS,mBAAmB,OAAsB,SAA0B,CAAC,GAAY;AAC/F,SAAO;AAAA,IACN;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,qBAAO;AAAA,YACN,iBAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,YAAI,CAAC,MAAM;AACV,qBAAO,4BAAe,iBAAK,eAAe,iBAAiB,iBAAiB;AAE7E,cAAM,WAAW,MAAM,IAAI,cAAc,KAAK,EAAE,OAAO;AAAA,UACtD,aAAa,IAAI,UAAU;AAAA,UAC3B,KAAK,KAAK;AAAA,UACV,QAAQ,eAAe;AAAA,UACvB,QAAQ,KAAK,UAAU,CAAC;AAAA,QACzB,CAAC;AAED,mBAAO;AAAA,UACN,iBAAK;AAAA,UACL;AAAA,UACA;AAAA,UACA,qBAAqB,QAAQ;AAAA,QAC9B;AAAA,MACD;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,qBAAO;AAAA,YACN,iBAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,OAAO,MAAM,IAAI,cAAc,KAAK,EAAE,KAAK,IAAI,UAAU,EAAE;AACjE,mBAAO,4BAAe,iBAAK,IAAI,oBAAoB,gCAAgC;AAAA,UAClF,WAAW,KAAK,IAAI,aAAa;AAAA,QAClC,CAAC;AAAA,MACF;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,qBAAO;AAAA,YACN,iBAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,WAAW,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,YAAY,IAAI,UAAU,EAAE;AACrF,YAAI,CAAC;AACJ,qBAAO,4BAAe,iBAAK,WAAW,aAAa,4BAA4B;AAEhF,mBAAO;AAAA,UACN,iBAAK;AAAA,UACL;AAAA,UACA;AAAA,UACA,cAAc,QAAQ;AAAA,QACvB;AAAA,MACD;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,qBAAO;AAAA,YACN,iBAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,OAAO,IAAI,KAAK,MAAM;AAI5B,cAAM,QAAgE,CAAC;AACvE,YAAI,MAAM,QAAQ,OAAW,OAAM,MAAM,KAAK;AAC9C,YAAI,MAAM,WAAW,OAAW,OAAM,SAAS,KAAK;AACpD,YAAI,MAAM,YAAY,OAAW,OAAM,UAAU,KAAK;AAEtD,cAAM,UAAU,MAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,IAAI,UAAU,IAAI,KAAK;AACzF,YAAI,CAAC;AACJ,qBAAO,4BAAe,iBAAK,WAAW,aAAa,4BAA4B;AAEhF,mBAAO;AAAA,UACN,iBAAK;AAAA,UACL;AAAA,UACA;AAAA,UACA,cAAc,OAAO;AAAA,QACtB;AAAA,MACD;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,qBAAO;AAAA,YACN,iBAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,UAAU,MAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,IAAI,UAAU,EAAE;AAClF,YAAI,CAAC;AACJ,qBAAO,4BAAe,iBAAK,WAAW,aAAa,4BAA4B;AAEhF,eAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,iBAAK,WAAW,CAAC;AAAA,MACtD;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,qBAAO;AAAA,YACN,iBAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,WAAW,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,YAAY,IAAI,UAAU,EAAE;AACrF,YAAI,CAAC;AACJ,qBAAO,4BAAe,iBAAK,WAAW,aAAa,4BAA4B;AAEhF,cAAM,OAAO,MAAM,IAAI,cAAc,KAAK,EAAE,eAAe,UAAU;AACrE,mBAAO,4BAAe,iBAAK,IAAI,sBAAsB,yBAAyB;AAAA,UAC7E,YAAY,KAAK,IAAI,aAAa;AAAA,QACnC,CAAC;AAAA,MACF;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,qBAAO;AAAA,YACN,iBAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,WAAW,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,YAAY,IAAI,UAAU,EAAE;AACrF,YAAI,CAAC;AACJ,qBAAO,4BAAe,iBAAK,WAAW,aAAa,4BAA4B;AAEhF,cAAM,OAAO,KAAK,UAAU;AAAA,UAC3B,IAAI,QAAQ,KAAK,IAAI,CAAC;AAAA,UACtB,MAAM;AAAA,UACN,MAAM,EAAE,aAAa,IAAI,UAAU,IAAI,SAAS,yBAAyB;AAAA,QAC1E,CAAC;AAED,YAAI;AACH,gBAAM,MAAM,MAAM,MAAM,SAAS,KAAK;AAAA,YACrC,QAAQ;AAAA,YACR,SAAS;AAAA,cACR,gBAAgB;AAAA,cAChB,uBAAuB,YAAY,SAAS,QAAQ,IAAI;AAAA,cACxD,mBAAmB;AAAA,YACpB;AAAA,YACA;AAAA,YACA,QAAQ,YAAY,QAAQ,GAAM;AAAA,UACnC,CAAC;AAED,qBAAO,4BAAe,iBAAK,IAAI,aAAa,4BAA4B;AAAA,YACvE,QAAQ,IAAI;AAAA,YACZ,IAAI,IAAI;AAAA,UACT,CAAC;AAAA,QACF,SAAS,KAAK;AACb,qBAAO,4BAAe,iBAAK,IAAI,aAAa,4BAA4B;AAAA,YACvE,QAAQ;AAAA,YACR,IAAI;AAAA,YACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,UACvD,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;;;AEzNO,IAAM,iBAAN,MAAgD;AAAA,EAMtD,YACkB,OACA,SAA0B,CAAC,GAC3B,KAChB;AAHgB;AACA;AACA;AAAA,EACf;AAAA,EAHe;AAAA,EACA;AAAA,EACA;AAAA,EART,OAAO;AAAA,EACP,OAAO,CAAC,kBAAkB,sBAAsB;AAAA,EAEjD;AAAA,EAQR,QAAQ,KAAyB;AAChC,UAAM,aAAa,IAAI,kBAAkB,KAAK,OAAO,KAAK,MAAM;AAEhE,SAAK,KAAK;AAAA,MACT;AAAA,MACA,OAAO,SAAS,SAAS;AACxB,cAAM,WAAW,SAAS,SAAS,IAAI;AAAA,MACxC;AAAA,MACA;AAAA,IACD;AAEA,UAAM,WAAW,KAAK,OAAO,iBAAiB;AAC9C,SAAK,aAAa,YAAY,MAAM;AACnC,iBAAW,MAAM,EAAE,MAAM,CAAC,QAAQ,QAAQ,MAAM,2BAA2B,GAAG,CAAC;AAAA,IAChF,GAAG,QAAQ;AAEX,UAAM,SAAS,mBAAmB,KAAK,OAAO,KAAK,MAAM;AACzD,eAAW,CAAC,QAAQ,MAAM,GAAG,QAAQ,KAAK,QAAQ;AACjD,UAAI,SAAS,QAAQ,MAAM,GAAG,QAAQ;AAAA,IACvC;AAAA,EACD;AACD;","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
- export { type DeliveryStatus, type IWebhookDelivery, type IWebhookDeliveryDTO, type IWebhookEndpoint, type IWebhookEndpointCreatedDTO, type IWebhookEndpointDTO, type IWebhooksConfig, WebhooksModule };
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
- export { type DeliveryStatus, type IWebhookDelivery, type IWebhookDeliveryDTO, type IWebhookEndpoint, type IWebhookEndpointCreatedDTO, type IWebhookEndpointDTO, type IWebhooksConfig, WebhooksModule };
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(url, secret, delivery, deliveries)
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(url, secret, delivery, deliveries) {
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(url, {
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":";;;;;;;AAIA,IAAM,OAAO;AAAA;AAGN,IAAM,gBAAN,MAAoB;AAAA,EAC1B,YAA6B,OAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAE7B,MAAM,OAAO,MAKiB;AAC7B,UAAM,CAAC,GAAG,IAAI,MAAM,KAAK,MAAM;AAAA,MAC9B;AAAA;AAAA,gBAEa,IAAI;AAAA,MACjB,CAAC,KAAK,aAAa,KAAK,KAAK,KAAK,QAAQ,KAAK,MAAM;AAAA,IACtD;AACA,WAAO;AAAA,EACR;AAAA,EAEA,KAAK,aAAkD;AACtD,WAAO,KAAK,MAAM;AAAA,MACjB,UAAU,IAAI;AAAA;AAAA;AAAA,MAGd,CAAC,WAAW;AAAA,IACb;AAAA,EACD;AAAA,EAEA,MAAM,SAAS,IAAY,aAAuD;AACjF,UAAM,CAAC,GAAG,IAAI,MAAM,KAAK,MAAM;AAAA,MAC9B,UAAU,IAAI;AAAA;AAAA,MAEd,CAAC,IAAI,WAAW;AAAA,IACjB;AACA,WAAO,OAAO;AAAA,EACf;AAAA,EAEA,MAAM,OACL,IACA,aACA,MACmC;AACnC,UAAM,OAAiB,CAAC;AACxB,UAAM,SAAoB,CAAC,IAAI,WAAW;AAE1C,QAAI,KAAK,QAAQ,QAAW;AAC3B,aAAO,KAAK,KAAK,GAAG;AACpB,WAAK,KAAK,UAAU,OAAO,MAAM,EAAE;AAAA,IACpC;AACA,QAAI,KAAK,WAAW,QAAW;AAC9B,aAAO,KAAK,KAAK,MAAM;AACvB,WAAK,KAAK,aAAa,OAAO,MAAM,EAAE;AAAA,IACvC;AACA,QAAI,KAAK,YAAY,QAAW;AAC/B,aAAO,KAAK,KAAK,OAAO;AACxB,WAAK,KAAK,cAAc,OAAO,MAAM,EAAE;AAAA,IACxC;AAEA,QAAI,KAAK,WAAW,EAAG,QAAO,KAAK,SAAS,IAAI,WAAW;AAE3D,UAAM,CAAC,GAAG,IAAI,MAAM,KAAK,MAAM;AAAA,MAC9B,yCAAyC,KAAK,KAAK,IAAI,CAAC;AAAA;AAAA,gBAE3C,IAAI;AAAA,MACjB;AAAA,IACD;AACA,WAAO,OAAO;AAAA,EACf;AAAA,EAEA,MAAM,OAAO,IAAY,aAAuC;AAC/D,UAAM,OAAO,MAAM,KAAK,MAAM;AAAA,MAC7B;AAAA,MACA,CAAC,IAAI,WAAW;AAAA,IACjB;AACA,WAAO,KAAK,SAAS;AAAA,EACtB;AAAA,EAEA,aAAa,aAAqB,WAAgD;AACjF,WAAO,KAAK,MAAM;AAAA,MACjB,UAAU,IAAI;AAAA;AAAA;AAAA;AAAA,MAId,CAAC,aAAa,SAAS;AAAA,IACxB;AAAA,EACD;AACD;;;ACxFA,IAAMA,QAAO;AAAA;AAAA;AAAA;AAAA;AAMb,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAYR,IAAM,gBAAN,MAAoB;AAAA,EAC1B,YAA6B,OAAsB;AAAtB;AAAA,EAAuB;AAAA,EAAvB;AAAA,EAE7B,MAAM,OAAO,MAKiB;AAC7B,UAAM,CAAC,GAAG,IAAI,MAAM,KAAK,MAAM;AAAA,MAC9B;AAAA;AAAA,gBAEaA,KAAI;AAAA,MACjB,CAAC,KAAK,YAAY,KAAK,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,CAAC;AAAA,IAC7E;AACA,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,WACL,IACA,QAMgB;AAChB,UAAM,KAAK,MAAM;AAAA,MAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAQA;AAAA,QACC;AAAA,QACA,OAAO,KAAK,cAAc;AAAA,QAC1B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO,KAAK,oBAAI,KAAK,IAAI;AAAA,MAC1B;AAAA,IACD;AAAA,EACD;AAAA,EAEA,eAAe,YAAoB,QAAQ,IAAiC;AAC3E,WAAO,KAAK,MAAM;AAAA,MACjB,UAAUA,KAAI;AAAA;AAAA;AAAA;AAAA,MAId,CAAC,YAAY,KAAK;AAAA,IACnB;AAAA,EACD;AAAA,EAEA,cAAc,QAAQ,IAA8B;AACnD,WAAO,KAAK,MAAM;AAAA,MACjB,UAAU,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAUhB,CAAC,KAAK;AAAA,IACP;AAAA,EACD;AACD;;;AC9FA,SAAS,YAAY,mBAAmB;AAEjC,SAAS,iBAAyB;AACxC,SAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AACtC;AAEO,SAAS,YAAY,QAAgB,MAAsB;AACjE,SAAO,UAAU,WAAW,UAAU,MAAM,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK,CAAC;AACzE;;;ACCO,IAAM,oBAAN,MAAwB;AAAA,EAI9B,YACkB,OACA,SAA0B,CAAC,GAC3C;AAFgB;AACA;AAEjB,SAAK,cAAc,OAAO,eAAe;AACzC,SAAK,cAAc,OAAO,eAAe,CAAC,KAAQ,KAAS,IAAS;AAAA,EACrE;AAAA,EALkB;AAAA,EACA;AAAA,EALD;AAAA,EACA;AAAA;AAAA;AAAA,EAYjB,MAAM,SAAS,SAAkC,MAAiC;AACjF,UAAM,cAAc,QAAQ,aAAa;AACzC,QAAI,OAAO,gBAAgB,SAAU;AAErC,UAAM,YAAY,MAAM,IAAI,cAAc,KAAK,KAAK,EAAE,aAAa,aAAa,KAAK,IAAI;AACzF,QAAI,UAAU,WAAW,EAAG;AAE5B,UAAM,aAAa,IAAI,cAAc,KAAK,KAAK;AAC/C,UAAM,QAAQ,WAAW,UAAU,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,SAAS,MAAM,UAAU,CAAC,CAAC;AAAA,EAC5F;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC5B,UAAM,aAAa,IAAI,cAAc,KAAK,KAAK;AAC/C,UAAM,UAAU,MAAM,WAAW,cAAc;AAC/C,UAAM,QAAQ;AAAA,MACb,QAAQ;AAAA,QAAI,CAAC,EAAE,UAAU,KAAAC,MAAK,OAAO,MACpC,KAAK,gBAAgBA,MAAK,QAAQ,UAAU,UAAU;AAAA,MACvD;AAAA,IACD;AAAA,EACD;AAAA,EAEA,MAAc,QACb,UACA,SACA,MACA,YACgB;AAChB,UAAM,WAAW,MAAM,WAAW,OAAO;AAAA,MACxC,YAAY,SAAS;AAAA,MACrB,SAAS,KAAK;AAAA,MACd,WAAW,KAAK;AAAA,MAChB;AAAA,IACD,CAAC;AACD,UAAM,KAAK,gBAAgB,SAAS,KAAK,SAAS,QAAQ,UAAU,UAAU;AAAA,EAC/E;AAAA,EAEA,MAAM,gBACLA,MACA,QACA,UACA,YACgB;AAChB,UAAM,OAAO,KAAK,UAAU;AAAA,MAC3B,IAAI,SAAS;AAAA,MACb,MAAM,SAAS;AAAA,MACf,MAAM,SAAS;AAAA,IAChB,CAAC;AAED,UAAM,YAAY,YAAY,QAAQ,IAAI;AAE1C,QAAI;AACH,YAAM,MAAM,MAAM,MAAMA,MAAK;AAAA,QAC5B,QAAQ;AAAA,QACR,SAAS;AAAA,UACR,gBAAgB;AAAA,UAChB,uBAAuB;AAAA,UACvB,mBAAmB,SAAS;AAAA,UAC5B,gBAAgB,SAAS;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,QAAQ,YAAY,QAAQ,GAAM;AAAA,MACnC,CAAC;AAED,YAAM,eAAe,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAEpD,YAAM,WAAW,WAAW,SAAS,IAAI;AAAA,QACxC,IAAI,IAAI;AAAA,QACR,gBAAgB,IAAI;AAAA,QACpB;AAAA,QACA,eAAe,IAAI,KAAK,OAAO,KAAK,YAAY,SAAS,QAAQ;AAAA,MAClE,CAAC;AAAA,IACF,SAAS,KAAK;AACb,YAAM,WAAW,WAAW,SAAS,IAAI;AAAA,QACxC,IAAI;AAAA,QACJ,gBAAgB;AAAA,QAChB,cAAc,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QAC7D,eAAe,KAAK,YAAY,SAAS,QAAQ;AAAA,MAClD,CAAC;AAAA,IACF;AAAA,EACD;AAAA,EAEQ,YAAY,iBAAsC;AACzD,QAAI,kBAAkB,KAAK,KAAK,YAAa,QAAO;AACpD,UAAM,QACL,KAAK,YAAY,eAAe,KAAK,KAAK,YAAY,KAAK,YAAY,SAAS,CAAC;AAClF,WAAO,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK;AAAA,EACnC;AACD;;;AC9GA,SAAS,gBAAgB,YAAY;AACrC,SAAS,aAAa,gBAAgB;AACtC,SAAS,gBAAgB;;;ACHzB;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,SAAS;AAKlB,IAAM,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC;AAC1C,IAAM,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG;AAEnD,IAAM,uBAAuB,EAAE,OAAO;AAAA,EAC5C;AAAA,EACA,QAAQ,OAAO,SAAS;AACzB,CAAC;AAEM,IAAM,uBAAuB,EAClC,OAAO;AAAA,EACP,KAAK,IAAI,SAAS;AAAA,EAClB,QAAQ,OAAO,SAAS;AAAA,EACxB,SAAS,EAAE,QAAQ,EAAE,SAAS;AAC/B,CAAC,EACA,OAAO,CAAC,MAAM,OAAO,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,MAAM,MAAS,GAAG,4BAA4B;;;ACMpF,SAAS,cAAc,GAA0C;AACvE,SAAO;AAAA,IACN,IAAI,EAAE;AAAA,IACN,KAAK,EAAE;AAAA,IACP,QAAQ,EAAE;AAAA,IACV,SAAS,EAAE;AAAA,IACX,WAAW,EAAE,UAAU,YAAY;AAAA,EACpC;AACD;AAEO,SAAS,qBAAqB,GAAiD;AACrF,SAAO,EAAE,GAAG,cAAc,CAAC,GAAG,QAAQ,EAAE,OAAO;AAChD;AAEO,SAAS,cAAc,GAA0C;AACvE,SAAO;AAAA,IACN,IAAI,EAAE;AAAA,IACN,SAAS,EAAE;AAAA,IACX,WAAW,EAAE;AAAA,IACb,QAAQ,EAAE;AAAA,IACV,UAAU,EAAE;AAAA,IACZ,gBAAgB,EAAE;AAAA,IAClB,aAAa,EAAE,aAAa,YAAY,KAAK;AAAA,IAC7C,WAAW,EAAE,UAAU,YAAY;AAAA,EACpC;AACD;;;AFjCO,SAAS,mBAAmB,OAAsB,SAA0B,CAAC,GAAY;AAC/F,SAAO;AAAA,IACN;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,oBAAoB;AAAA,MAC7B;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,iBAAO;AAAA,YACN,KAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,OAAO,IAAI,KAAK,MAAM;AAC5B,YAAI,CAAC,MAAM;AACV,iBAAO,eAAe,KAAK,eAAe,iBAAiB,iBAAiB;AAE7E,cAAM,WAAW,MAAM,IAAI,cAAc,KAAK,EAAE,OAAO;AAAA,UACtD,aAAa,IAAI,UAAU;AAAA,UAC3B,KAAK,KAAK;AAAA,UACV,QAAQ,eAAe;AAAA,UACvB,QAAQ,KAAK,UAAU,CAAC;AAAA,QACzB,CAAC;AAED,eAAO;AAAA,UACN,KAAK;AAAA,UACL;AAAA,UACA;AAAA,UACA,qBAAqB,QAAQ;AAAA,QAC9B;AAAA,MACD;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,iBAAO;AAAA,YACN,KAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,OAAO,MAAM,IAAI,cAAc,KAAK,EAAE,KAAK,IAAI,UAAU,EAAE;AACjE,eAAO,eAAe,KAAK,IAAI,oBAAoB,gCAAgC;AAAA,UAClF,WAAW,KAAK,IAAI,aAAa;AAAA,QAClC,CAAC;AAAA,MACF;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,iBAAO;AAAA,YACN,KAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,WAAW,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,YAAY,IAAI,UAAU,EAAE;AACrF,YAAI,CAAC;AACJ,iBAAO,eAAe,KAAK,WAAW,aAAa,4BAA4B;AAEhF,eAAO;AAAA,UACN,KAAK;AAAA,UACL;AAAA,UACA;AAAA,UACA,cAAc,QAAQ;AAAA,QACvB;AAAA,MACD;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,oBAAoB;AAAA,MAC7B;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,iBAAO;AAAA,YACN,KAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,OAAO,IAAI,KAAK,MAAM;AAI5B,cAAM,QAAgE,CAAC;AACvE,YAAI,MAAM,QAAQ,OAAW,OAAM,MAAM,KAAK;AAC9C,YAAI,MAAM,WAAW,OAAW,OAAM,SAAS,KAAK;AACpD,YAAI,MAAM,YAAY,OAAW,OAAM,UAAU,KAAK;AAEtD,cAAM,UAAU,MAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,IAAI,UAAU,IAAI,KAAK;AACzF,YAAI,CAAC;AACJ,iBAAO,eAAe,KAAK,WAAW,aAAa,4BAA4B;AAEhF,eAAO;AAAA,UACN,KAAK;AAAA,UACL;AAAA,UACA;AAAA,UACA,cAAc,OAAO;AAAA,QACtB;AAAA,MACD;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,iBAAO;AAAA,YACN,KAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,UAAU,MAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,IAAI,UAAU,EAAE;AAClF,YAAI,CAAC;AACJ,iBAAO,eAAe,KAAK,WAAW,aAAa,4BAA4B;AAEhF,eAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,WAAW,CAAC;AAAA,MACtD;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,iBAAO;AAAA,YACN,KAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,WAAW,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,YAAY,IAAI,UAAU,EAAE;AACrF,YAAI,CAAC;AACJ,iBAAO,eAAe,KAAK,WAAW,aAAa,4BAA4B;AAEhF,cAAM,OAAO,MAAM,IAAI,cAAc,KAAK,EAAE,eAAe,UAAU;AACrE,eAAO,eAAe,KAAK,IAAI,sBAAsB,yBAAyB;AAAA,UAC7E,YAAY,KAAK,IAAI,aAAa;AAAA,QACnC,CAAC;AAAA,MACF;AAAA,IACD;AAAA,IAEA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AACd,YAAI,CAAC,IAAI;AACR,iBAAO;AAAA,YACN,KAAK;AAAA,YACL;AAAA,YACA;AAAA,UACD;AAED,cAAM,EAAE,WAAW,IAAI,IAAI,KAAK,QAAQ;AACxC,cAAM,WAAW,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,YAAY,IAAI,UAAU,EAAE;AACrF,YAAI,CAAC;AACJ,iBAAO,eAAe,KAAK,WAAW,aAAa,4BAA4B;AAEhF,cAAM,OAAO,KAAK,UAAU;AAAA,UAC3B,IAAI,QAAQ,KAAK,IAAI,CAAC;AAAA,UACtB,MAAM;AAAA,UACN,MAAM,EAAE,aAAa,IAAI,UAAU,IAAI,SAAS,yBAAyB;AAAA,QAC1E,CAAC;AAED,YAAI;AACH,gBAAM,MAAM,MAAM,MAAM,SAAS,KAAK;AAAA,YACrC,QAAQ;AAAA,YACR,SAAS;AAAA,cACR,gBAAgB;AAAA,cAChB,uBAAuB,YAAY,SAAS,QAAQ,IAAI;AAAA,cACxD,mBAAmB;AAAA,YACpB;AAAA,YACA;AAAA,YACA,QAAQ,YAAY,QAAQ,GAAM;AAAA,UACnC,CAAC;AAED,iBAAO,eAAe,KAAK,IAAI,aAAa,4BAA4B;AAAA,YACvE,QAAQ,IAAI;AAAA,YACZ,IAAI,IAAI;AAAA,UACT,CAAC;AAAA,QACF,SAAS,KAAK;AACb,iBAAO,eAAe,KAAK,IAAI,aAAa,4BAA4B;AAAA,YACvE,QAAQ;AAAA,YACR,IAAI;AAAA,YACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,UACvD,CAAC;AAAA,QACF;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;;;AG7NO,IAAM,iBAAN,MAAgD;AAAA,EAMtD,YACkB,OACA,SAA0B,CAAC,GAC3B,KAChB;AAHgB;AACA;AACA;AAAA,EACf;AAAA,EAHe;AAAA,EACA;AAAA,EACA;AAAA,EART,OAAO;AAAA,EACP,OAAO,CAAC,kBAAkB,sBAAsB;AAAA,EAEjD;AAAA,EAQR,QAAQ,KAAyB;AAChC,UAAM,aAAa,IAAI,kBAAkB,KAAK,OAAO,KAAK,MAAM;AAEhE,SAAK,KAAK;AAAA,MACT;AAAA,MACA,OAAO,SAAS,SAAS;AACxB,cAAM,WAAW,SAAS,SAAS,IAAI;AAAA,MACxC;AAAA,MACA;AAAA,IACD;AAEA,UAAM,WAAW,KAAK,OAAO,iBAAiB;AAC9C,SAAK,aAAa,YAAY,MAAM;AACnC,iBAAW,MAAM,EAAE,MAAM,CAAC,QAAQ,QAAQ,MAAM,2BAA2B,GAAG,CAAC;AAAA,IAChF,GAAG,QAAQ;AAEX,UAAM,SAAS,mBAAmB,KAAK,OAAO,KAAK,MAAM;AACzD,eAAW,CAAC,QAAQ,MAAM,GAAG,QAAQ,KAAK,QAAQ;AACjD,UAAI,SAAS,QAAQ,MAAM,GAAG,QAAQ;AAAA,IACvC;AAAA,EACD;AACD;","names":["COLS","url"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonderie/webhooks",
3
- "version": "1.0.0",
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 && node -e \"require('node:fs').cpSync('src/migrations/sql', 'dist/migrations/sql', {recursive:true})\"",
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.0",
42
- "@fonderie/events": "^1.0.0",
43
- "@fonderie/store": "^0.1.0"
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/fonderie-js/sdk.git",
70
+ "url": "git+https://github.com/fonderiejs/sdk.git",
70
71
  "directory": "packages/webhooks"
71
72
  },
72
- "homepage": "https://github.com/fonderie-js/sdk/tree/main/packages/webhooks#readme",
73
+ "homepage": "https://github.com/fonderiejs/sdk/tree/main/packages/webhooks#readme",
73
74
  "bugs": {
74
- "url": "https://github.com/fonderie-js/sdk/issues"
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;