@fonderie/webhooks 1.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonderie/webhooks",
3
- "version": "1.1.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",
@@ -61,17 +61,18 @@
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"
75
76
  },
76
77
  "dependencies": {
77
78
  "zod": "^4.4.3"