@orthacms/webhooks-server 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/lib/application/dto/list-deliveries-query.dto.d.ts +8 -0
- package/dist/lib/application/dto/list-deliveries-query.dto.d.ts.map +1 -0
- package/dist/lib/application/dto/list-deliveries-query.dto.js +64 -0
- package/dist/lib/application/dto/save-webhook-endpoint.dto.d.ts +23 -0
- package/dist/lib/application/dto/save-webhook-endpoint.dto.d.ts.map +1 -0
- package/dist/lib/application/dto/save-webhook-endpoint.dto.js +127 -0
- package/dist/lib/application/webhook-endpoints.service.d.ts +82 -0
- package/dist/lib/application/webhook-endpoints.service.d.ts.map +1 -0
- package/dist/lib/application/webhook-endpoints.service.js +213 -0
- package/dist/lib/docs/describe-webhooks-api.d.ts +16 -0
- package/dist/lib/docs/describe-webhooks-api.d.ts.map +1 -0
- package/dist/lib/docs/describe-webhooks-api.js +170 -0
- package/dist/lib/docs/webhook-schemas.d.ts +21 -0
- package/dist/lib/docs/webhook-schemas.d.ts.map +1 -0
- package/dist/lib/docs/webhook-schemas.js +360 -0
- package/dist/lib/domain/errors/index.d.ts +3 -0
- package/dist/lib/domain/errors/index.d.ts.map +1 -0
- package/dist/lib/domain/errors/index.js +7 -0
- package/dist/lib/domain/errors/webhook-delivery-not-found.error.d.ts +12 -0
- package/dist/lib/domain/errors/webhook-delivery-not-found.error.d.ts.map +1 -0
- package/dist/lib/domain/errors/webhook-delivery-not-found.error.js +19 -0
- package/dist/lib/domain/errors/webhook-endpoint-not-found.error.d.ts +6 -0
- package/dist/lib/domain/errors/webhook-endpoint-not-found.error.d.ts.map +1 -0
- package/dist/lib/domain/errors/webhook-endpoint-not-found.error.js +13 -0
- package/dist/lib/domain/webhook-secret.d.ts +29 -0
- package/dist/lib/domain/webhook-secret.d.ts.map +1 -0
- package/dist/lib/domain/webhook-secret.js +34 -0
- package/dist/lib/domain/webhook-views.d.ts +85 -0
- package/dist/lib/domain/webhook-views.d.ts.map +1 -0
- package/dist/lib/domain/webhook-views.js +2 -0
- package/dist/lib/http/controllers/webhook-deliveries.controller.d.ts +28 -0
- package/dist/lib/http/controllers/webhook-deliveries.controller.d.ts.map +1 -0
- package/dist/lib/http/controllers/webhook-deliveries.controller.js +110 -0
- package/dist/lib/http/controllers/webhook-endpoints.controller.d.ts +61 -0
- package/dist/lib/http/controllers/webhook-endpoints.controller.d.ts.map +1 -0
- package/dist/lib/http/controllers/webhook-endpoints.controller.js +213 -0
- package/dist/lib/http/controllers/webhook-events.controller.d.ts +13 -0
- package/dist/lib/http/controllers/webhook-events.controller.d.ts.map +1 -0
- package/dist/lib/http/controllers/webhook-events.controller.js +38 -0
- package/dist/lib/infrastructure/event-mapping.d.ts +13 -0
- package/dist/lib/infrastructure/event-mapping.d.ts.map +1 -0
- package/dist/lib/infrastructure/event-mapping.js +69 -0
- package/dist/lib/infrastructure/purge/webhook-workspaces.purger.d.ts +42 -0
- package/dist/lib/infrastructure/purge/webhook-workspaces.purger.d.ts.map +1 -0
- package/dist/lib/infrastructure/purge/webhook-workspaces.purger.js +66 -0
- package/dist/lib/infrastructure/schema/index.d.ts +3 -0
- package/dist/lib/infrastructure/schema/index.d.ts.map +1 -0
- package/dist/lib/infrastructure/schema/index.js +8 -0
- package/dist/lib/infrastructure/schema/webhook-deliveries.d.ts +332 -0
- package/dist/lib/infrastructure/schema/webhook-deliveries.d.ts.map +1 -0
- package/dist/lib/infrastructure/schema/webhook-deliveries.js +92 -0
- package/dist/lib/infrastructure/schema/webhook-endpoints.d.ts +330 -0
- package/dist/lib/infrastructure/schema/webhook-endpoints.d.ts.map +1 -0
- package/dist/lib/infrastructure/schema/webhook-endpoints.js +94 -0
- package/dist/lib/infrastructure/webhook-delivery.repository.d.ts +99 -0
- package/dist/lib/infrastructure/webhook-delivery.repository.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-delivery.repository.js +255 -0
- package/dist/lib/infrastructure/webhook-delivery.worker.d.ts +72 -0
- package/dist/lib/infrastructure/webhook-delivery.worker.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-delivery.worker.js +216 -0
- package/dist/lib/infrastructure/webhook-endpoint.repository.d.ts +88 -0
- package/dist/lib/infrastructure/webhook-endpoint.repository.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-endpoint.repository.js +306 -0
- package/dist/lib/infrastructure/webhook-fanout.subscriber.d.ts +36 -0
- package/dist/lib/infrastructure/webhook-fanout.subscriber.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-fanout.subscriber.js +83 -0
- package/dist/lib/infrastructure/webhook-http.client.d.ts +76 -0
- package/dist/lib/infrastructure/webhook-http.client.d.ts.map +1 -0
- package/dist/lib/infrastructure/webhook-http.client.js +225 -0
- package/dist/lib/types/webhooks-config.d.ts +64 -0
- package/dist/lib/types/webhooks-config.d.ts.map +1 -0
- package/dist/lib/types/webhooks-config.js +24 -0
- package/dist/lib/utils/webhooks-plugin.d.ts +35 -0
- package/dist/lib/utils/webhooks-plugin.d.ts.map +1 -0
- package/dist/lib/utils/webhooks-plugin.js +90 -0
- package/dist/lib/webhooks.module.d.ts +18 -0
- package/dist/lib/webhooks.module.d.ts.map +1 -0
- package/dist/lib/webhooks.module.js +72 -0
- package/dist/lib/webhooks.tokens.d.ts +5 -0
- package/dist/lib/webhooks.tokens.d.ts.map +1 -0
- package/dist/lib/webhooks.tokens.js +9 -0
- package/migrations/0000_init_webhooks.sql +53 -0
- package/migrations/meta/0000_snapshot.json +424 -0
- package/migrations/meta/_journal.json +13 -0
- package/package.json +45 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WebhookDeliveryRepository = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
const database_1 = require("@orthacms/database");
|
|
7
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
8
|
+
const webhook_deliveries_1 = require("./schema/webhook-deliveries");
|
|
9
|
+
/** The queue and the log, over one table. */
|
|
10
|
+
let WebhookDeliveryRepository = class WebhookDeliveryRepository {
|
|
11
|
+
db;
|
|
12
|
+
constructor(db) {
|
|
13
|
+
this.db = db;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Queues one delivery per endpoint, ignoring rows that already exist.
|
|
17
|
+
*
|
|
18
|
+
* `ON CONFLICT DO NOTHING` against `unique(endpoint_id, event_id)` is the
|
|
19
|
+
* whole idempotency story for fan-out: outbox delivery is at-least-once, so
|
|
20
|
+
* this subscriber may be handed the same event twice, and the second pass
|
|
21
|
+
* must be a no-op rather than a duplicate POST.
|
|
22
|
+
*/
|
|
23
|
+
async enqueue(deliveries) {
|
|
24
|
+
if (deliveries.length === 0)
|
|
25
|
+
return 0;
|
|
26
|
+
const rows = await this.db
|
|
27
|
+
.insert(webhook_deliveries_1.webhookDeliveries)
|
|
28
|
+
.values(deliveries.map((delivery) => ({
|
|
29
|
+
id: delivery.id,
|
|
30
|
+
endpointId: delivery.endpointId,
|
|
31
|
+
eventId: delivery.eventId,
|
|
32
|
+
eventKind: delivery.eventKind,
|
|
33
|
+
workspaceId: delivery.workspaceId,
|
|
34
|
+
contentType: delivery.contentType,
|
|
35
|
+
payload: delivery.payload,
|
|
36
|
+
status: 'pending'
|
|
37
|
+
})))
|
|
38
|
+
.onConflictDoNothing({
|
|
39
|
+
// Names the partial index explicitly: without the predicate
|
|
40
|
+
// Postgres cannot tell which of the table's unique indexes this
|
|
41
|
+
// arbiter refers to.
|
|
42
|
+
target: [
|
|
43
|
+
webhook_deliveries_1.webhookDeliveries.endpointId,
|
|
44
|
+
webhook_deliveries_1.webhookDeliveries.eventId
|
|
45
|
+
],
|
|
46
|
+
where: (0, drizzle_orm_1.sql) `${webhook_deliveries_1.webhookDeliveries.redeliveryOf} is null`
|
|
47
|
+
})
|
|
48
|
+
.returning({ id: webhook_deliveries_1.webhookDeliveries.id });
|
|
49
|
+
return rows.length;
|
|
50
|
+
}
|
|
51
|
+
/** Queues a single delivery and returns its id (used by redelivery). */
|
|
52
|
+
async enqueueOne(delivery) {
|
|
53
|
+
const [row] = await this.db
|
|
54
|
+
.insert(webhook_deliveries_1.webhookDeliveries)
|
|
55
|
+
.values({
|
|
56
|
+
id: delivery.id,
|
|
57
|
+
endpointId: delivery.endpointId,
|
|
58
|
+
eventId: delivery.eventId,
|
|
59
|
+
eventKind: delivery.eventKind,
|
|
60
|
+
workspaceId: delivery.workspaceId,
|
|
61
|
+
contentType: delivery.contentType,
|
|
62
|
+
payload: delivery.payload,
|
|
63
|
+
redeliveryOf: delivery.redeliveryOf ?? null,
|
|
64
|
+
status: 'pending'
|
|
65
|
+
})
|
|
66
|
+
.returning({ id: webhook_deliveries_1.webhookDeliveries.id });
|
|
67
|
+
return row.id;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Claims up to `limit` deliveries and marks them `delivering`.
|
|
71
|
+
*
|
|
72
|
+
* The transaction covers **only the claim**. That is the point of the whole
|
|
73
|
+
* design: the HTTP request happens after this returns, with nothing open,
|
|
74
|
+
* so a slow receiver cannot hold a database transaction or a pool client.
|
|
75
|
+
*
|
|
76
|
+
* `FOR UPDATE SKIP LOCKED` lets several API processes claim disjoint rows
|
|
77
|
+
* without blocking each other, exactly as the outbox drain does.
|
|
78
|
+
*
|
|
79
|
+
* Rows stuck in `delivering` past `claimTimeoutMs` are reclaimed here
|
|
80
|
+
* rather than by a separate reaper: a worker that died mid-request left the
|
|
81
|
+
* row with no error, no retry and no way out, and folding the recovery into
|
|
82
|
+
* the claim means there is one query to get right instead of two.
|
|
83
|
+
*/
|
|
84
|
+
async claim(limit, claimTimeoutMs) {
|
|
85
|
+
const now = new Date();
|
|
86
|
+
const staleBefore = new Date(now.getTime() - claimTimeoutMs);
|
|
87
|
+
return this.db.transaction(async (tx) => {
|
|
88
|
+
const rows = await tx
|
|
89
|
+
.select({
|
|
90
|
+
id: webhook_deliveries_1.webhookDeliveries.id,
|
|
91
|
+
endpointId: webhook_deliveries_1.webhookDeliveries.endpointId,
|
|
92
|
+
eventId: webhook_deliveries_1.webhookDeliveries.eventId,
|
|
93
|
+
eventKind: webhook_deliveries_1.webhookDeliveries.eventKind,
|
|
94
|
+
workspaceId: webhook_deliveries_1.webhookDeliveries.workspaceId,
|
|
95
|
+
payload: webhook_deliveries_1.webhookDeliveries.payload,
|
|
96
|
+
attempts: webhook_deliveries_1.webhookDeliveries.attempts
|
|
97
|
+
})
|
|
98
|
+
.from(webhook_deliveries_1.webhookDeliveries)
|
|
99
|
+
.where((0, drizzle_orm_1.or)((0, drizzle_orm_1.and)((0, drizzle_orm_1.inArray)(webhook_deliveries_1.webhookDeliveries.status, [
|
|
100
|
+
'pending',
|
|
101
|
+
'failed'
|
|
102
|
+
]), (0, drizzle_orm_1.or)((0, drizzle_orm_1.sql) `${webhook_deliveries_1.webhookDeliveries.nextAttemptAt} is null`, (0, drizzle_orm_1.lte)(webhook_deliveries_1.webhookDeliveries.nextAttemptAt, now))), (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(webhook_deliveries_1.webhookDeliveries.status, 'delivering'), (0, drizzle_orm_1.lt)(webhook_deliveries_1.webhookDeliveries.claimedAt, staleBefore))))
|
|
103
|
+
.orderBy(webhook_deliveries_1.webhookDeliveries.createdAt)
|
|
104
|
+
.limit(limit)
|
|
105
|
+
.for('update', { skipLocked: true });
|
|
106
|
+
if (rows.length === 0)
|
|
107
|
+
return [];
|
|
108
|
+
await tx
|
|
109
|
+
.update(webhook_deliveries_1.webhookDeliveries)
|
|
110
|
+
.set({ status: 'delivering', claimedAt: now })
|
|
111
|
+
.where((0, drizzle_orm_1.inArray)(webhook_deliveries_1.webhookDeliveries.id, rows.map((row) => row.id)));
|
|
112
|
+
return rows;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/** Records a successful attempt. */
|
|
116
|
+
async markSucceeded(id, outcome) {
|
|
117
|
+
await this.db
|
|
118
|
+
.update(webhook_deliveries_1.webhookDeliveries)
|
|
119
|
+
.set({
|
|
120
|
+
status: 'succeeded',
|
|
121
|
+
attempts: (0, drizzle_orm_1.sql) `${webhook_deliveries_1.webhookDeliveries.attempts} + 1`,
|
|
122
|
+
claimedAt: null,
|
|
123
|
+
nextAttemptAt: null,
|
|
124
|
+
lastStatusCode: outcome.statusCode,
|
|
125
|
+
lastError: null,
|
|
126
|
+
responseSnippet: outcome.responseSnippet,
|
|
127
|
+
durationMs: outcome.durationMs,
|
|
128
|
+
completedAt: new Date()
|
|
129
|
+
})
|
|
130
|
+
.where((0, drizzle_orm_1.eq)(webhook_deliveries_1.webhookDeliveries.id, id));
|
|
131
|
+
}
|
|
132
|
+
/** Records a failed attempt that will be tried again at `nextAttemptAt`. */
|
|
133
|
+
async markRetrying(id, outcome, nextAttemptAt) {
|
|
134
|
+
await this.db
|
|
135
|
+
.update(webhook_deliveries_1.webhookDeliveries)
|
|
136
|
+
.set({
|
|
137
|
+
status: 'failed',
|
|
138
|
+
attempts: (0, drizzle_orm_1.sql) `${webhook_deliveries_1.webhookDeliveries.attempts} + 1`,
|
|
139
|
+
claimedAt: null,
|
|
140
|
+
nextAttemptAt,
|
|
141
|
+
lastStatusCode: outcome.statusCode,
|
|
142
|
+
lastError: outcome.error,
|
|
143
|
+
responseSnippet: outcome.responseSnippet,
|
|
144
|
+
durationMs: outcome.durationMs
|
|
145
|
+
})
|
|
146
|
+
.where((0, drizzle_orm_1.eq)(webhook_deliveries_1.webhookDeliveries.id, id));
|
|
147
|
+
}
|
|
148
|
+
/** Records a final failure — no further attempt will be made. */
|
|
149
|
+
async markDead(id, outcome) {
|
|
150
|
+
await this.db
|
|
151
|
+
.update(webhook_deliveries_1.webhookDeliveries)
|
|
152
|
+
.set({
|
|
153
|
+
status: 'dead',
|
|
154
|
+
attempts: (0, drizzle_orm_1.sql) `${webhook_deliveries_1.webhookDeliveries.attempts} + 1`,
|
|
155
|
+
claimedAt: null,
|
|
156
|
+
nextAttemptAt: null,
|
|
157
|
+
lastStatusCode: outcome.statusCode,
|
|
158
|
+
lastError: outcome.error,
|
|
159
|
+
responseSnippet: outcome.responseSnippet,
|
|
160
|
+
durationMs: outcome.durationMs,
|
|
161
|
+
completedAt: new Date()
|
|
162
|
+
})
|
|
163
|
+
.where((0, drizzle_orm_1.eq)(webhook_deliveries_1.webhookDeliveries.id, id));
|
|
164
|
+
}
|
|
165
|
+
/** One page of an endpoint's delivery log, newest first. */
|
|
166
|
+
async list(endpointId, query) {
|
|
167
|
+
const filters = [(0, drizzle_orm_1.eq)(webhook_deliveries_1.webhookDeliveries.endpointId, endpointId)];
|
|
168
|
+
if (query.status) {
|
|
169
|
+
filters.push((0, drizzle_orm_1.eq)(webhook_deliveries_1.webhookDeliveries.status, query.status));
|
|
170
|
+
}
|
|
171
|
+
if (query.eventKind) {
|
|
172
|
+
filters.push((0, drizzle_orm_1.eq)(webhook_deliveries_1.webhookDeliveries.eventKind, query.eventKind));
|
|
173
|
+
}
|
|
174
|
+
const where = (0, drizzle_orm_1.and)(...filters);
|
|
175
|
+
const [{ total }] = await this.db
|
|
176
|
+
.select({ total: (0, drizzle_orm_1.count)() })
|
|
177
|
+
.from(webhook_deliveries_1.webhookDeliveries)
|
|
178
|
+
.where(where);
|
|
179
|
+
const pageCount = Math.max(1, Math.ceil(total / query.pageSize));
|
|
180
|
+
// Clamp rather than return an empty page: the log shrinks under the
|
|
181
|
+
// reader (retention, a redelivery finishing), and a page number that
|
|
182
|
+
// was valid a second ago should show the last page, not nothing.
|
|
183
|
+
const page = Math.min(Math.max(query.page, 1), pageCount);
|
|
184
|
+
const rows = await this.db
|
|
185
|
+
.select()
|
|
186
|
+
.from(webhook_deliveries_1.webhookDeliveries)
|
|
187
|
+
.where(where)
|
|
188
|
+
.orderBy((0, drizzle_orm_1.desc)(webhook_deliveries_1.webhookDeliveries.createdAt))
|
|
189
|
+
.limit(query.pageSize)
|
|
190
|
+
.offset((page - 1) * query.pageSize);
|
|
191
|
+
return {
|
|
192
|
+
items: rows.map(toView),
|
|
193
|
+
total,
|
|
194
|
+
page,
|
|
195
|
+
pageSize: query.pageSize,
|
|
196
|
+
pageCount
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* One delivery with its bodies — scoped to its endpoint so an id from
|
|
201
|
+
* another endpoint reads as "not found" rather than leaking across.
|
|
202
|
+
*/
|
|
203
|
+
async findDetail(endpointId, deliveryId) {
|
|
204
|
+
const [row] = await this.db
|
|
205
|
+
.select()
|
|
206
|
+
.from(webhook_deliveries_1.webhookDeliveries)
|
|
207
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(webhook_deliveries_1.webhookDeliveries.id, deliveryId), (0, drizzle_orm_1.eq)(webhook_deliveries_1.webhookDeliveries.endpointId, endpointId)))
|
|
208
|
+
.limit(1);
|
|
209
|
+
if (!row)
|
|
210
|
+
return null;
|
|
211
|
+
return {
|
|
212
|
+
...toView(row),
|
|
213
|
+
payload: row.payload,
|
|
214
|
+
responseSnippet: row.responseSnippet
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Deletes completed rows older than `before`.
|
|
219
|
+
*
|
|
220
|
+
* Only `succeeded` and `dead` are eligible: a `failed` row is still
|
|
221
|
+
* scheduled, and a `delivering` row may be in flight.
|
|
222
|
+
*/
|
|
223
|
+
async pruneCompletedBefore(before) {
|
|
224
|
+
const rows = await this.db
|
|
225
|
+
.delete(webhook_deliveries_1.webhookDeliveries)
|
|
226
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.inArray)(webhook_deliveries_1.webhookDeliveries.status, ['succeeded', 'dead']), (0, drizzle_orm_1.isNotNull)(webhook_deliveries_1.webhookDeliveries.completedAt), (0, drizzle_orm_1.lt)(webhook_deliveries_1.webhookDeliveries.completedAt, before)))
|
|
227
|
+
.returning({ id: webhook_deliveries_1.webhookDeliveries.id });
|
|
228
|
+
return rows.length;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
exports.WebhookDeliveryRepository = WebhookDeliveryRepository;
|
|
232
|
+
exports.WebhookDeliveryRepository = WebhookDeliveryRepository = tslib_1.__decorate([
|
|
233
|
+
(0, common_1.Injectable)(),
|
|
234
|
+
tslib_1.__param(0, (0, database_1.InjectDatabase)()),
|
|
235
|
+
tslib_1.__metadata("design:paramtypes", [Object])
|
|
236
|
+
], WebhookDeliveryRepository);
|
|
237
|
+
/** Row → the API shape, minus the bodies. */
|
|
238
|
+
function toView(row) {
|
|
239
|
+
return {
|
|
240
|
+
id: row.id,
|
|
241
|
+
endpointId: row.endpointId,
|
|
242
|
+
eventId: row.eventId,
|
|
243
|
+
eventKind: row.eventKind,
|
|
244
|
+
workspaceId: row.workspaceId,
|
|
245
|
+
contentType: row.contentType,
|
|
246
|
+
status: row.status,
|
|
247
|
+
attempts: row.attempts,
|
|
248
|
+
nextAttemptAt: row.nextAttemptAt?.toISOString() ?? null,
|
|
249
|
+
statusCode: row.lastStatusCode,
|
|
250
|
+
error: row.lastError,
|
|
251
|
+
durationMs: row.durationMs,
|
|
252
|
+
createdAt: row.createdAt.toISOString(),
|
|
253
|
+
completedAt: row.completedAt?.toISOString() ?? null
|
|
254
|
+
};
|
|
255
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type OnApplicationBootstrap, type OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import type { ResolvedWebhooksConfig } from '../types/webhooks-config';
|
|
3
|
+
import { WebhookDeliveryRepository } from './webhook-delivery.repository';
|
|
4
|
+
import { WebhookEndpointRepository } from './webhook-endpoint.repository';
|
|
5
|
+
import { WebhookHttpClient } from './webhook-http.client';
|
|
6
|
+
/**
|
|
7
|
+
* Sends queued deliveries.
|
|
8
|
+
*
|
|
9
|
+
* **The one structural rule:** the claim runs in a transaction, the POST does
|
|
10
|
+
* not. `WebhookDeliveryRepository.claim` opens a short transaction, marks a
|
|
11
|
+
* batch `delivering` and commits; only then does this worker touch the network.
|
|
12
|
+
* A receiver that takes the full timeout therefore costs one row's progress,
|
|
13
|
+
* not a held pool client and not the outbox.
|
|
14
|
+
*
|
|
15
|
+
* **Why a plain interval and not a scheduler.** `OutboxDispatcher`'s poll
|
|
16
|
+
* backstop and the alarms sweep set the precedent: one timer in-process, with
|
|
17
|
+
* the work behind it made safe to run concurrently. `FOR UPDATE SKIP LOCKED`
|
|
18
|
+
* means several API processes running this take disjoint rows, so scaling out
|
|
19
|
+
* simply sends faster.
|
|
20
|
+
*
|
|
21
|
+
* Deliveries in one batch run **serially**. Twenty parallel requests would be a
|
|
22
|
+
* burst aimed at whichever receivers happen to be subscribed, and the whole
|
|
23
|
+
* point of the retry schedule's jitter is not to do that.
|
|
24
|
+
*/
|
|
25
|
+
export declare class WebhookDeliveryWorker implements OnApplicationBootstrap, OnModuleDestroy {
|
|
26
|
+
private readonly deliveries;
|
|
27
|
+
private readonly endpoints;
|
|
28
|
+
private readonly http;
|
|
29
|
+
private readonly config;
|
|
30
|
+
private readonly logger;
|
|
31
|
+
private timer;
|
|
32
|
+
/** The tick in flight, so shutdown can wait for it. */
|
|
33
|
+
private running;
|
|
34
|
+
private stopped;
|
|
35
|
+
/** When the retention sweep last ran, so it is not run every tick. */
|
|
36
|
+
private lastPrunedAt;
|
|
37
|
+
constructor(deliveries: WebhookDeliveryRepository, endpoints: WebhookEndpointRepository, http: WebhookHttpClient, config: ResolvedWebhooksConfig);
|
|
38
|
+
/** Arms the interval, unless delivery is switched off. */
|
|
39
|
+
onApplicationBootstrap(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Stops the timer and waits for the tick in flight.
|
|
42
|
+
*
|
|
43
|
+
* Waiting matters: a tick abandoned mid-batch leaves rows in `delivering`
|
|
44
|
+
* that only the claim's stale-row recovery will free, so the deliveries it
|
|
45
|
+
* had already sent would be retried after the timeout for no reason.
|
|
46
|
+
*/
|
|
47
|
+
onModuleDestroy(): Promise<void>;
|
|
48
|
+
/** One guarded tick — skips if the previous one is still going. */
|
|
49
|
+
private tick;
|
|
50
|
+
/**
|
|
51
|
+
* Claims one batch, sends each delivery, then prunes if it is time.
|
|
52
|
+
*
|
|
53
|
+
* Public because "send what is claimable now" is a real operation and not
|
|
54
|
+
* only the timer's business: a deployment that runs the sender out of
|
|
55
|
+
* process, and the e2e suites, both need to drive a batch on demand rather
|
|
56
|
+
* than waiting for an interval to come round.
|
|
57
|
+
*/
|
|
58
|
+
runOnce(): Promise<void>;
|
|
59
|
+
/** Sends one delivery and records what happened. */
|
|
60
|
+
private deliver;
|
|
61
|
+
/** Counts a dead delivery against the endpoint, disabling it at the cap. */
|
|
62
|
+
private penalise;
|
|
63
|
+
/**
|
|
64
|
+
* Deletes completed deliveries past the retention window, at most once an
|
|
65
|
+
* hour.
|
|
66
|
+
*
|
|
67
|
+
* The log is the only table here that nothing else prunes; without this it
|
|
68
|
+
* grows for the life of the deployment at the rate content is edited.
|
|
69
|
+
*/
|
|
70
|
+
private pruneIfDue;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=webhook-delivery.worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-delivery.worker.d.ts","sourceRoot":"","sources":["../../../src/lib/infrastructure/webhook-delivery.worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACvB,MAAM,gBAAgB,CAAC;AAQxB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EACH,yBAAyB,EAG5B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBACa,qBACT,YAAW,sBAAsB,EAAE,eAAe;IAW9C,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAb3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IACjE,OAAO,CAAC,KAAK,CAA+C;IAC5D,uDAAuD;IACvD,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,OAAO,CAAS;IACxB,sEAAsE;IACtE,OAAO,CAAC,YAAY,CAAK;gBAGJ,UAAU,EAAE,yBAAyB,EACrC,SAAS,EAAE,yBAAyB,EACpC,IAAI,EAAE,iBAAiB,EAEvB,MAAM,EAAE,sBAAsB;IAGnD,0DAA0D;IAC1D,sBAAsB,IAAI,IAAI;IAa9B;;;;;;OAMG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAStC,mEAAmE;YACrD,IAAI;IAQlB;;;;;;;OAOG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAuB9B,oDAAoD;YACtC,OAAO;IAwFrB,4EAA4E;YAC9D,QAAQ;IAgBtB;;;;;;OAMG;YACW,UAAU;CAgB3B"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var WebhookDeliveryWorker_1;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.WebhookDeliveryWorker = void 0;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const common_1 = require("@nestjs/common");
|
|
7
|
+
const webhooks_domain_1 = require("@orthacms/webhooks-domain");
|
|
8
|
+
const webhooks_tokens_1 = require("../webhooks.tokens");
|
|
9
|
+
const webhook_delivery_repository_1 = require("./webhook-delivery.repository");
|
|
10
|
+
const webhook_endpoint_repository_1 = require("./webhook-endpoint.repository");
|
|
11
|
+
const webhook_http_client_1 = require("./webhook-http.client");
|
|
12
|
+
/**
|
|
13
|
+
* Sends queued deliveries.
|
|
14
|
+
*
|
|
15
|
+
* **The one structural rule:** the claim runs in a transaction, the POST does
|
|
16
|
+
* not. `WebhookDeliveryRepository.claim` opens a short transaction, marks a
|
|
17
|
+
* batch `delivering` and commits; only then does this worker touch the network.
|
|
18
|
+
* A receiver that takes the full timeout therefore costs one row's progress,
|
|
19
|
+
* not a held pool client and not the outbox.
|
|
20
|
+
*
|
|
21
|
+
* **Why a plain interval and not a scheduler.** `OutboxDispatcher`'s poll
|
|
22
|
+
* backstop and the alarms sweep set the precedent: one timer in-process, with
|
|
23
|
+
* the work behind it made safe to run concurrently. `FOR UPDATE SKIP LOCKED`
|
|
24
|
+
* means several API processes running this take disjoint rows, so scaling out
|
|
25
|
+
* simply sends faster.
|
|
26
|
+
*
|
|
27
|
+
* Deliveries in one batch run **serially**. Twenty parallel requests would be a
|
|
28
|
+
* burst aimed at whichever receivers happen to be subscribed, and the whole
|
|
29
|
+
* point of the retry schedule's jitter is not to do that.
|
|
30
|
+
*/
|
|
31
|
+
let WebhookDeliveryWorker = WebhookDeliveryWorker_1 = class WebhookDeliveryWorker {
|
|
32
|
+
deliveries;
|
|
33
|
+
endpoints;
|
|
34
|
+
http;
|
|
35
|
+
config;
|
|
36
|
+
logger = new common_1.Logger(WebhookDeliveryWorker_1.name);
|
|
37
|
+
timer = null;
|
|
38
|
+
/** The tick in flight, so shutdown can wait for it. */
|
|
39
|
+
running = null;
|
|
40
|
+
stopped = false;
|
|
41
|
+
/** When the retention sweep last ran, so it is not run every tick. */
|
|
42
|
+
lastPrunedAt = 0;
|
|
43
|
+
constructor(deliveries, endpoints, http, config) {
|
|
44
|
+
this.deliveries = deliveries;
|
|
45
|
+
this.endpoints = endpoints;
|
|
46
|
+
this.http = http;
|
|
47
|
+
this.config = config;
|
|
48
|
+
}
|
|
49
|
+
/** Arms the interval, unless delivery is switched off. */
|
|
50
|
+
onApplicationBootstrap() {
|
|
51
|
+
const interval = this.config.deliveryIntervalMs;
|
|
52
|
+
if (interval <= 0) {
|
|
53
|
+
this.logger.log('Webhook delivery is disabled (deliveryIntervalMs is 0); events will queue but nothing will be sent from this process.');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.timer = setInterval(() => void this.tick(), interval);
|
|
57
|
+
// Never hold the process open for a background sender.
|
|
58
|
+
this.timer.unref?.();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Stops the timer and waits for the tick in flight.
|
|
62
|
+
*
|
|
63
|
+
* Waiting matters: a tick abandoned mid-batch leaves rows in `delivering`
|
|
64
|
+
* that only the claim's stale-row recovery will free, so the deliveries it
|
|
65
|
+
* had already sent would be retried after the timeout for no reason.
|
|
66
|
+
*/
|
|
67
|
+
async onModuleDestroy() {
|
|
68
|
+
this.stopped = true;
|
|
69
|
+
if (this.timer) {
|
|
70
|
+
clearInterval(this.timer);
|
|
71
|
+
this.timer = null;
|
|
72
|
+
}
|
|
73
|
+
if (this.running)
|
|
74
|
+
await this.running;
|
|
75
|
+
}
|
|
76
|
+
/** One guarded tick — skips if the previous one is still going. */
|
|
77
|
+
async tick() {
|
|
78
|
+
if (this.running || this.stopped)
|
|
79
|
+
return;
|
|
80
|
+
this.running = this.runOnce().finally(() => {
|
|
81
|
+
this.running = null;
|
|
82
|
+
});
|
|
83
|
+
await this.running;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Claims one batch, sends each delivery, then prunes if it is time.
|
|
87
|
+
*
|
|
88
|
+
* Public because "send what is claimable now" is a real operation and not
|
|
89
|
+
* only the timer's business: a deployment that runs the sender out of
|
|
90
|
+
* process, and the e2e suites, both need to drive a batch on demand rather
|
|
91
|
+
* than waiting for an interval to come round.
|
|
92
|
+
*/
|
|
93
|
+
async runOnce() {
|
|
94
|
+
try {
|
|
95
|
+
const claimed = await this.deliveries.claim(this.config.batchSize, this.config.claimTimeoutMs);
|
|
96
|
+
for (const delivery of claimed) {
|
|
97
|
+
if (this.stopped)
|
|
98
|
+
break;
|
|
99
|
+
await this.deliver(delivery);
|
|
100
|
+
}
|
|
101
|
+
await this.pruneIfDue();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
// A failing tick must never kill the interval; the next one retries
|
|
105
|
+
// from whatever state the database is in.
|
|
106
|
+
this.logger.error('A webhook delivery tick failed', error instanceof Error ? error.stack : String(error));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Sends one delivery and records what happened. */
|
|
110
|
+
async deliver(delivery) {
|
|
111
|
+
const endpoint = await this.endpoints.findForDelivery(delivery.endpointId);
|
|
112
|
+
if (!endpoint) {
|
|
113
|
+
// The endpoint was deleted between the claim and now. The row is on
|
|
114
|
+
// its way out by cascade; closing it keeps it from being re-claimed
|
|
115
|
+
// in the meantime.
|
|
116
|
+
await this.deliveries.markDead(delivery.id, {
|
|
117
|
+
statusCode: null,
|
|
118
|
+
error: 'The endpoint was deleted before this delivery was sent.',
|
|
119
|
+
responseSnippet: null,
|
|
120
|
+
durationMs: 0
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!endpoint.enabled) {
|
|
125
|
+
// Switched off — by hand or automatically — after this row was
|
|
126
|
+
// queued. Sending anyway would defeat the switch.
|
|
127
|
+
await this.deliveries.markDead(delivery.id, {
|
|
128
|
+
statusCode: null,
|
|
129
|
+
error: 'The endpoint was disabled before this delivery was sent.',
|
|
130
|
+
responseSnippet: null,
|
|
131
|
+
durationMs: 0
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const attempt = delivery.attempts + 1;
|
|
136
|
+
const response = await this.http.send({
|
|
137
|
+
url: endpoint.url,
|
|
138
|
+
secret: endpoint.secret,
|
|
139
|
+
headers: endpoint.headers,
|
|
140
|
+
body: delivery.payload,
|
|
141
|
+
eventKind: delivery.eventKind,
|
|
142
|
+
deliveryId: delivery.id,
|
|
143
|
+
eventId: delivery.eventId,
|
|
144
|
+
workspaceId: delivery.workspaceId,
|
|
145
|
+
attempt
|
|
146
|
+
});
|
|
147
|
+
const outcome = {
|
|
148
|
+
statusCode: response.statusCode,
|
|
149
|
+
error: response.error,
|
|
150
|
+
responseSnippet: response.responseSnippet,
|
|
151
|
+
durationMs: response.durationMs
|
|
152
|
+
};
|
|
153
|
+
// No status at all — a timeout, a refused connection, a rejected
|
|
154
|
+
// address — is a transport failure, which is retryable in the same way
|
|
155
|
+
// a 5xx is.
|
|
156
|
+
const verdict = response.statusCode === null
|
|
157
|
+
? { outcome: 'retry' }
|
|
158
|
+
: (0, webhooks_domain_1.classifyStatus)(response.statusCode, response.retryAfterMs);
|
|
159
|
+
if (verdict.outcome === 'succeeded') {
|
|
160
|
+
await this.deliveries.markSucceeded(delivery.id, outcome);
|
|
161
|
+
await this.endpoints.recordSuccess(endpoint.id);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (verdict.outcome === 'dead' ||
|
|
165
|
+
(0, webhooks_domain_1.isExhausted)(attempt, this.config.maxAttempts)) {
|
|
166
|
+
await this.deliveries.markDead(delivery.id, {
|
|
167
|
+
...outcome,
|
|
168
|
+
error: verdict.outcome === 'dead'
|
|
169
|
+
? verdict.reason
|
|
170
|
+
: (outcome.error ??
|
|
171
|
+
`Gave up after ${attempt} attempts.`)
|
|
172
|
+
});
|
|
173
|
+
await this.penalise(endpoint.id, endpoint.name);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const delayMs = verdict.retryAfterMs ?? (0, webhooks_domain_1.nextAttemptDelayMs)(attempt);
|
|
177
|
+
await this.deliveries.markRetrying(delivery.id, outcome, new Date(Date.now() + delayMs));
|
|
178
|
+
}
|
|
179
|
+
/** Counts a dead delivery against the endpoint, disabling it at the cap. */
|
|
180
|
+
async penalise(endpointId, endpointName) {
|
|
181
|
+
const disabled = await this.endpoints.recordFailure(endpointId, this.config.autoDisableAfter);
|
|
182
|
+
if (disabled) {
|
|
183
|
+
this.logger.warn(`Webhook endpoint "${endpointName}" (${endpointId}) was disabled after ` +
|
|
184
|
+
`${this.config.autoDisableAfter} consecutive failed deliveries.`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Deletes completed deliveries past the retention window, at most once an
|
|
189
|
+
* hour.
|
|
190
|
+
*
|
|
191
|
+
* The log is the only table here that nothing else prunes; without this it
|
|
192
|
+
* grows for the life of the deployment at the rate content is edited.
|
|
193
|
+
*/
|
|
194
|
+
async pruneIfDue() {
|
|
195
|
+
const retentionDays = this.config.retentionDays;
|
|
196
|
+
if (retentionDays <= 0)
|
|
197
|
+
return;
|
|
198
|
+
const hour = 60 * 60_000;
|
|
199
|
+
if (Date.now() - this.lastPrunedAt < hour)
|
|
200
|
+
return;
|
|
201
|
+
this.lastPrunedAt = Date.now();
|
|
202
|
+
const cutoff = new Date(Date.now() - retentionDays * 24 * hour);
|
|
203
|
+
const removed = await this.deliveries.pruneCompletedBefore(cutoff);
|
|
204
|
+
if (removed > 0) {
|
|
205
|
+
this.logger.log(`Pruned ${removed} webhook deliveries older than ${retentionDays} days.`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
exports.WebhookDeliveryWorker = WebhookDeliveryWorker;
|
|
210
|
+
exports.WebhookDeliveryWorker = WebhookDeliveryWorker = WebhookDeliveryWorker_1 = tslib_1.__decorate([
|
|
211
|
+
(0, common_1.Injectable)(),
|
|
212
|
+
tslib_1.__param(3, (0, webhooks_tokens_1.InjectWebhooksConfig)()),
|
|
213
|
+
tslib_1.__metadata("design:paramtypes", [webhook_delivery_repository_1.WebhookDeliveryRepository,
|
|
214
|
+
webhook_endpoint_repository_1.WebhookEndpointRepository,
|
|
215
|
+
webhook_http_client_1.WebhookHttpClient, Object])
|
|
216
|
+
], WebhookDeliveryWorker);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type Database } from '@orthacms/database';
|
|
2
|
+
import type { WebhookEndpointView } from '../domain/webhook-views';
|
|
3
|
+
/** The fields a create or update writes. */
|
|
4
|
+
export interface EndpointWriteModel {
|
|
5
|
+
name: string;
|
|
6
|
+
url: string;
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
eventKinds: string[];
|
|
9
|
+
contentTypes: string[];
|
|
10
|
+
allWorkspaces: boolean;
|
|
11
|
+
workspaceIds: string[];
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
/** An endpoint as the delivery path needs it — secret included. */
|
|
15
|
+
export interface EndpointWithSecret {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
url: string;
|
|
19
|
+
secret: string;
|
|
20
|
+
headers: Record<string, string>;
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** The subscription-relevant columns, for fan-out. */
|
|
24
|
+
export interface EndpointSubscriptionRow {
|
|
25
|
+
id: string;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
allWorkspaces: boolean;
|
|
28
|
+
workspaceIds: string[];
|
|
29
|
+
eventKinds: string[];
|
|
30
|
+
contentTypes: string[];
|
|
31
|
+
}
|
|
32
|
+
/** Reads and writes over the two endpoint tables. */
|
|
33
|
+
export declare class WebhookEndpointRepository {
|
|
34
|
+
private readonly db;
|
|
35
|
+
constructor(db: Database);
|
|
36
|
+
/**
|
|
37
|
+
* Every enabled endpoint with its workspace set, for fan-out.
|
|
38
|
+
*
|
|
39
|
+
* One query with an aggregated sub-select rather than a join: a join would
|
|
40
|
+
* return one row per workspace and force the caller to regroup, and this
|
|
41
|
+
* runs on every content write.
|
|
42
|
+
*/
|
|
43
|
+
listSubscriptions(): Promise<EndpointSubscriptionRow[]>;
|
|
44
|
+
/** The endpoint the worker is about to POST to, or null when it is gone. */
|
|
45
|
+
findForDelivery(id: string): Promise<EndpointWithSecret | null>;
|
|
46
|
+
/** Every endpoint, newest first, each with its last delivery. */
|
|
47
|
+
list(): Promise<WebhookEndpointView[]>;
|
|
48
|
+
/** One endpoint, or null. */
|
|
49
|
+
findById(id: string): Promise<WebhookEndpointView | null>;
|
|
50
|
+
/**
|
|
51
|
+
* Creates an endpoint and its workspace set in one transaction, so an
|
|
52
|
+
* endpoint can never exist with half its subscription.
|
|
53
|
+
*/
|
|
54
|
+
create(model: EndpointWriteModel, secret: string, createdBy: string | null): Promise<string>;
|
|
55
|
+
/** Applies a full write model to an existing endpoint. */
|
|
56
|
+
update(id: string, model: EndpointWriteModel): Promise<void>;
|
|
57
|
+
/** Rotates the signing secret. */
|
|
58
|
+
replaceSecret(id: string, secret: string): Promise<void>;
|
|
59
|
+
/** Deletes the endpoint; its workspaces and deliveries cascade. */
|
|
60
|
+
delete(id: string): Promise<void>;
|
|
61
|
+
/** Whether an endpoint with this id exists. */
|
|
62
|
+
exists(id: string): Promise<boolean>;
|
|
63
|
+
/** Clears the consecutive-failure count after a delivery succeeded. */
|
|
64
|
+
recordSuccess(id: string): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Counts one dead delivery against the endpoint and switches it off once
|
|
67
|
+
* `threshold` of them have arrived in a row.
|
|
68
|
+
*
|
|
69
|
+
* The increment and the test are one statement so two workers finishing
|
|
70
|
+
* failed deliveries at the same moment cannot both read the old count and
|
|
71
|
+
* both decide not to disable.
|
|
72
|
+
*
|
|
73
|
+
* @returns whether this failure switched the endpoint off.
|
|
74
|
+
*/
|
|
75
|
+
recordFailure(id: string, threshold: number): Promise<boolean>;
|
|
76
|
+
/** Replaces an endpoint's workspace rows to match `model`. */
|
|
77
|
+
private replaceWorkspaces;
|
|
78
|
+
/** Workspace ids per endpoint, for a set of endpoints. */
|
|
79
|
+
private workspacesFor;
|
|
80
|
+
/**
|
|
81
|
+
* The most recent delivery per endpoint.
|
|
82
|
+
*
|
|
83
|
+
* `DISTINCT ON` rather than a window function or N queries: one index scan
|
|
84
|
+
* over `(endpoint_id, created_at)` answers the whole list page.
|
|
85
|
+
*/
|
|
86
|
+
private lastDeliveriesFor;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=webhook-endpoint.repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook-endpoint.repository.d.ts","sourceRoot":"","sources":["../../../src/lib/infrastructure/webhook-endpoint.repository.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnE,OAAO,KAAK,EACR,mBAAmB,EAEtB,MAAM,yBAAyB,CAAC;AAQjC,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;CACpB;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,qDAAqD;AACrD,qBACa,yBAAyB;IACJ,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ;IAE3D;;;;;;OAMG;IACG,iBAAiB,IAAI,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAyB7D,4EAA4E;IACtE,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAkBrE,iEAAiE;IAC3D,IAAI,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAuB5C,6BAA6B;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAoB/D;;;OAGG;IACG,MAAM,CACR,KAAK,EAAE,kBAAkB,EACzB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAAG,IAAI,GACzB,OAAO,CAAC,MAAM,CAAC;IAuBlB,0DAA0D;IACpD,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBlE,kCAAkC;IAC5B,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW9D,mEAAmE;IAC7D,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvC,+CAA+C;IACzC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS1C,uEAAuE;IACjE,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO9C;;;;;;;;;OASG;IACG,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAepE,8DAA8D;YAChD,iBAAiB;IAqB/B,0DAA0D;YAC5C,aAAa;IAiB3B;;;;;OAKG;YACW,iBAAiB;CAgClC"}
|