@fonderie/webhooks 1.1.0 → 1.1.2

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
+ ```
@@ -0,0 +1,37 @@
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fonderie/webhooks",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
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"