@objectstack/plugin-webhooks 7.5.0 → 7.6.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/.turbo/turbo-build.log +20 -32
- package/CHANGELOG.md +49 -0
- package/dist/chunk-HWFTXTTI.js +138 -0
- package/dist/chunk-HWFTXTTI.js.map +1 -0
- package/dist/chunk-KPKLAXNA.cjs +138 -0
- package/dist/chunk-KPKLAXNA.cjs.map +1 -0
- package/dist/index.cjs +62 -616
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -325
- package/dist/index.d.ts +41 -325
- package/dist/index.js +52 -606
- package/dist/index.js.map +1 -1
- package/dist/schema.cjs +2 -6
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +5 -4764
- package/dist/schema.d.ts +5 -4764
- package/dist/schema.js +3 -7
- package/package.json +4 -11
- package/src/auto-enqueuer.test.ts +83 -116
- package/src/auto-enqueuer.ts +38 -27
- package/src/index.ts +13 -40
- package/src/schema.ts +11 -16
- package/src/webhook-outbox-plugin.ts +80 -296
- package/tsup.config.ts +1 -1
- package/dist/chunk-7HS5DLU2.js +0 -319
- package/dist/chunk-7HS5DLU2.js.map +0 -1
- package/dist/chunk-HF7CCDPB.cjs +0 -256
- package/dist/chunk-HF7CCDPB.cjs.map +0 -1
- package/dist/chunk-KNGLLSSP.js +0 -256
- package/dist/chunk-KNGLLSSP.js.map +0 -1
- package/dist/chunk-TDSI7UHY.cjs +0 -319
- package/dist/chunk-TDSI7UHY.cjs.map +0 -1
- package/dist/outbox-CIn7LSyB.d.cts +0 -155
- package/dist/outbox-CIn7LSyB.d.ts +0 -155
- package/dist/sql-outbox.cjs +0 -8
- package/dist/sql-outbox.cjs.map +0 -1
- package/dist/sql-outbox.d.cts +0 -55
- package/dist/sql-outbox.d.ts +0 -55
- package/dist/sql-outbox.js +0 -8
- package/dist/sql-outbox.js.map +0 -1
- package/src/dispatcher.test.ts +0 -324
- package/src/dispatcher.ts +0 -218
- package/src/http-sender.ts +0 -187
- package/src/memory-outbox.test.ts +0 -86
- package/src/memory-outbox.ts +0 -155
- package/src/outbox.ts +0 -175
- package/src/partition.ts +0 -19
- package/src/retention.test.ts +0 -116
- package/src/retention.ts +0 -144
- package/src/sql-outbox.test.ts +0 -490
- package/src/sql-outbox.ts +0 -343
- package/src/sys-webhook-delivery.object.ts +0 -224
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { Plugin, PluginContext } from '@objectstack/core';
|
|
2
|
-
import { IDataEngine, IRealtimeService
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
2
|
+
import { IDataEngine, IRealtimeService } from '@objectstack/spec/contracts';
|
|
3
|
+
import { EnqueueHttpInput } from '@objectstack/service-messaging';
|
|
4
|
+
export { SysWebhook } from './schema.js';
|
|
5
|
+
import '@objectstack/spec/data';
|
|
5
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Enqueue callback into the shared `service-messaging` HTTP outbox (ADR-0018 M3).
|
|
9
|
+
* The plugin supplies one bound to `messaging.enqueueHttp(...)`; webhooks no
|
|
10
|
+
* longer own a delivery outbox/dispatcher — they share the generic substrate.
|
|
11
|
+
*/
|
|
12
|
+
type HttpEnqueueFn = (input: EnqueueHttpInput) => Promise<string>;
|
|
6
13
|
/**
|
|
7
14
|
* Optional logger interface (subset of console / kernel logger).
|
|
8
15
|
*/
|
|
9
|
-
interface OptionalLogger
|
|
16
|
+
interface OptionalLogger {
|
|
10
17
|
info?(msg: string, meta?: unknown): void;
|
|
11
18
|
warn?(msg: string, meta?: unknown): void;
|
|
12
19
|
debug?(msg: string, meta?: unknown): void;
|
|
@@ -39,7 +46,7 @@ interface AutoEnqueuerOptions {
|
|
|
39
46
|
* the subscription-change event is missed. Default 60s.
|
|
40
47
|
*/
|
|
41
48
|
refreshIntervalMs?: number;
|
|
42
|
-
logger?: OptionalLogger
|
|
49
|
+
logger?: OptionalLogger;
|
|
43
50
|
}
|
|
44
51
|
/**
|
|
45
52
|
* Bridge between `IRealtimeService` (`data.record.*` events emitted by
|
|
@@ -82,7 +89,7 @@ interface AutoEnqueuerOptions {
|
|
|
82
89
|
declare class AutoEnqueuer {
|
|
83
90
|
private readonly engine;
|
|
84
91
|
private readonly realtime;
|
|
85
|
-
private readonly
|
|
92
|
+
private readonly enqueue;
|
|
86
93
|
private readonly subscriptions;
|
|
87
94
|
private readonly subscriptionsObject;
|
|
88
95
|
private readonly refreshIntervalMs;
|
|
@@ -92,7 +99,7 @@ declare class AutoEnqueuer {
|
|
|
92
99
|
private refreshTimer;
|
|
93
100
|
private running;
|
|
94
101
|
private refreshing;
|
|
95
|
-
constructor(engine: IDataEngine, realtime: IRealtimeService,
|
|
102
|
+
constructor(engine: IDataEngine, realtime: IRealtimeService, enqueue: HttpEnqueueFn, opts?: AutoEnqueuerOptions);
|
|
96
103
|
/**
|
|
97
104
|
* Load the subscription cache and start listening for events.
|
|
98
105
|
*/
|
|
@@ -118,287 +125,41 @@ declare class AutoEnqueuer {
|
|
|
118
125
|
snapshot(): ReadonlyMap<string, ReadonlyArray<CachedSubscription>>;
|
|
119
126
|
}
|
|
120
127
|
|
|
121
|
-
|
|
122
|
-
* Default per-request timeout. Receivers SHOULD respond within ~30s; we
|
|
123
|
-
* cap aggressively to free dispatcher slots.
|
|
124
|
-
*/
|
|
125
|
-
declare const DEFAULT_TIMEOUT_MS = 15000;
|
|
126
|
-
type FetchImpl = (input: string, init: {
|
|
127
|
-
method: string;
|
|
128
|
-
headers: Record<string, string>;
|
|
129
|
-
body: string;
|
|
130
|
-
signal: AbortSignal;
|
|
131
|
-
}) => Promise<{
|
|
132
|
-
ok: boolean;
|
|
133
|
-
status: number;
|
|
134
|
-
text(): Promise<string>;
|
|
135
|
-
}>;
|
|
136
|
-
/** Single HTTP attempt classified to an `AckResult` shape (without nextRetryAt). */
|
|
137
|
-
type AttemptOutcome = {
|
|
138
|
-
success: true;
|
|
139
|
-
httpStatus: number;
|
|
140
|
-
responseBody?: string;
|
|
141
|
-
durationMs: number;
|
|
142
|
-
} | {
|
|
143
|
-
success: false;
|
|
144
|
-
retriable: boolean;
|
|
145
|
-
httpStatus?: number;
|
|
146
|
-
responseBody?: string;
|
|
147
|
-
error?: string;
|
|
148
|
-
durationMs: number;
|
|
149
|
-
};
|
|
150
|
-
/**
|
|
151
|
-
* Send one HTTP attempt for the delivery. Pure (no DB writes) so the
|
|
152
|
-
* dispatcher owns retry-schedule + ack logic.
|
|
153
|
-
*
|
|
154
|
-
* - 2xx → success
|
|
155
|
-
* - 4xx (except 408/429) → permanent failure (retriable = false → goes to `dead`)
|
|
156
|
-
* - 408, 429, 5xx, transport → retriable
|
|
157
|
-
*/
|
|
158
|
-
declare function sendOnce(delivery: WebhookDelivery, fetchImpl: FetchImpl): Promise<AttemptOutcome>;
|
|
159
|
-
/**
|
|
160
|
-
* Stripe-style retry schedule. Returns the next `nextRetryAt` ms (relative
|
|
161
|
-
* to `now`) given how many attempts have already happened, or `null` if
|
|
162
|
-
* the row should be moved to `dead`.
|
|
163
|
-
*
|
|
164
|
-
* attempt 1 fails -> retry in ~1s
|
|
165
|
-
* attempt 2 fails -> ~10s
|
|
166
|
-
* attempt 3 fails -> ~1m
|
|
167
|
-
* attempt 4 fails -> ~10m
|
|
168
|
-
* attempt 5 fails -> ~1h
|
|
169
|
-
* attempt 6 fails -> ~6h
|
|
170
|
-
* attempt 7 fails -> ~24h
|
|
171
|
-
* attempt 8+ fails -> dead
|
|
172
|
-
*
|
|
173
|
-
* Each delay is multiplied by jitter ∈ [0.8, 1.2].
|
|
174
|
-
*/
|
|
175
|
-
declare function nextRetryDelayMs(attemptsSoFar: number, rng?: () => number): number | null;
|
|
176
|
-
/**
|
|
177
|
-
* Compose an `AckResult` from an `AttemptOutcome`, applying the retry
|
|
178
|
-
* schedule on retriable failures.
|
|
179
|
-
*/
|
|
180
|
-
declare function classifyAttempt(outcome: AttemptOutcome, attemptsSoFar: number, now?: number, rng?: () => number): AckResult;
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Minimal logger surface — kernel's `Logger` is compatible (extra params
|
|
184
|
-
* accepted). Keeping it permissive avoids a hard dependency on the spec
|
|
185
|
-
* Logger interface here.
|
|
186
|
-
*/
|
|
187
|
-
interface DispatcherLogger {
|
|
188
|
-
warn: (msg: string, meta?: any) => void;
|
|
189
|
-
info?: (msg: string, meta?: any) => void;
|
|
190
|
-
}
|
|
191
|
-
interface DispatcherOptions {
|
|
192
|
-
/** Stable id identifying this dispatcher node. */
|
|
193
|
-
nodeId: string;
|
|
194
|
-
/** Cluster service providing `lock` (and optional metrics). */
|
|
195
|
-
cluster: IClusterService;
|
|
196
|
-
/** Outbox backend. */
|
|
197
|
-
outbox: IWebhookOutbox;
|
|
198
|
-
/**
|
|
199
|
-
* How many partitions to split work across. Each tick the dispatcher
|
|
200
|
-
* attempts to acquire each partition's lock independently — the node
|
|
201
|
-
* that wins owns that partition for the duration of the batch.
|
|
202
|
-
*
|
|
203
|
-
* Default: 8 (matches webhook-delivery.mdx §4 example).
|
|
204
|
-
*/
|
|
205
|
-
partitionCount?: number;
|
|
206
|
-
/** Max rows to claim from each partition per tick. Default 32. */
|
|
207
|
-
batchSize?: number;
|
|
208
|
-
/** Tick interval in ms. Default 250. */
|
|
209
|
-
intervalMs?: number;
|
|
210
|
-
/** Per-partition lock TTL. Default = 5 × intervalMs. */
|
|
211
|
-
lockTtlMs?: number;
|
|
212
|
-
/** Visibility timeout for claimed rows. Default = 2 × lockTtlMs. */
|
|
213
|
-
claimTtlMs?: number;
|
|
214
|
-
/** Override `globalThis.fetch` (tests). */
|
|
215
|
-
fetchImpl?: FetchImpl;
|
|
216
|
-
/** Hook fired after every attempt — observability hook. */
|
|
217
|
-
onAttempt?: (delivery: WebhookDelivery, success: boolean) => void;
|
|
218
|
-
/** RNG override for the retry-jitter schedule (tests). */
|
|
219
|
-
rng?: () => number;
|
|
220
|
-
/** Logger callback (optional). */
|
|
221
|
-
logger?: DispatcherLogger;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Cross-node webhook dispatcher.
|
|
225
|
-
*
|
|
226
|
-
* **Design** — each tick the dispatcher iterates over `partitionCount`
|
|
227
|
-
* logical partitions. For each, it tries to acquire a cluster-scoped lock
|
|
228
|
-
* (`webhook.dispatcher.partition.{i}`) with a short TTL. If it wins the
|
|
229
|
-
* lock, it claims up to `batchSize` ready rows whose `hash(webhookId) mod
|
|
230
|
-
* partitionCount === i`, POSTs them, and acks. The lock is released
|
|
231
|
-
* immediately after the batch so other nodes can fairly rotate through.
|
|
232
|
-
*
|
|
233
|
-
* **Why per-partition locks rather than one global lock?**
|
|
234
|
-
*
|
|
235
|
-
* 1. Throughput — N nodes can process N partitions concurrently.
|
|
236
|
-
* 2. Partition affinity — rows for the same webhook always sort into the
|
|
237
|
-
* same partition, preserving in-order delivery per webhook.
|
|
238
|
-
* 3. Failure isolation — a stuck node only blocks its partition until the
|
|
239
|
-
* TTL elapses; other partitions keep moving.
|
|
240
|
-
*
|
|
241
|
-
* **At-least-once, not exactly-once.** Receivers MUST be idempotent on the
|
|
242
|
-
* `X-Objectstack-Delivery` (== row id) header. If the HTTP call succeeds
|
|
243
|
-
* but the ack write fails, the row reverts to pending after the claim TTL
|
|
244
|
-
* and will be re-posted.
|
|
245
|
-
*/
|
|
246
|
-
declare class WebhookDispatcher {
|
|
247
|
-
private readonly opts;
|
|
248
|
-
private timer;
|
|
249
|
-
private running;
|
|
250
|
-
private inflightTick;
|
|
251
|
-
constructor(options: DispatcherOptions);
|
|
252
|
-
/** Begin the periodic loop. Safe to call once; subsequent calls are no-ops. */
|
|
253
|
-
start(): void;
|
|
254
|
-
/** Stop the loop and wait for the in-flight tick to drain. */
|
|
255
|
-
stop(): Promise<void>;
|
|
256
|
-
/**
|
|
257
|
-
* Run one full tick (all partitions, single attempt each). Exposed for
|
|
258
|
-
* deterministic tests that want to step the dispatcher manually.
|
|
259
|
-
*/
|
|
260
|
-
tick(): Promise<void>;
|
|
261
|
-
private scheduleTick;
|
|
262
|
-
private runTick;
|
|
263
|
-
private runPartition;
|
|
264
|
-
private processRow;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
interface OptionalLogger {
|
|
268
|
-
info?(msg: string, meta?: unknown): void;
|
|
269
|
-
warn?(msg: string, meta?: unknown): void;
|
|
270
|
-
debug?(msg: string, meta?: unknown): void;
|
|
271
|
-
}
|
|
272
|
-
interface DeliveryRetentionOptions {
|
|
273
|
-
/**
|
|
274
|
-
* Object name backing the outbox. Defaults to `sys_webhook_delivery`.
|
|
275
|
-
*/
|
|
276
|
-
objectName?: string;
|
|
277
|
-
/**
|
|
278
|
-
* How long to keep `success` rows. Default 7 days. Set to `0` to
|
|
279
|
-
* disable the success sweep (keep forever — not recommended in
|
|
280
|
-
* production).
|
|
281
|
-
*/
|
|
282
|
-
successTtlMs?: number;
|
|
283
|
-
/**
|
|
284
|
-
* How long to keep `dead` rows. Default 30 days. Set to `0` to
|
|
285
|
-
* keep forever.
|
|
286
|
-
*/
|
|
287
|
-
deadTtlMs?: number;
|
|
288
|
-
/**
|
|
289
|
-
* How often to run the sweep. Default 1h.
|
|
290
|
-
*/
|
|
291
|
-
sweepIntervalMs?: number;
|
|
292
|
-
logger?: OptionalLogger;
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Periodically prunes `sys_webhook_delivery` rows so the table doesn't
|
|
296
|
-
* grow unbounded.
|
|
297
|
-
*
|
|
298
|
-
* Without this every successful POST would leave a permanent row. At
|
|
299
|
-
* even moderate scale (10 events/s × 3 webhooks = 30 rows/s = ~2.6M
|
|
300
|
-
* rows/day) the table becomes a problem within a week.
|
|
301
|
-
*
|
|
302
|
-
* Retention defaults mirror Stripe/GitHub:
|
|
303
|
-
* - `success`: 7 days
|
|
304
|
-
* - `dead`: 30 days (kept longer for audit & manual re-delivery)
|
|
305
|
-
* - `pending`/`in_flight`/`failed`: never auto-pruned (they're
|
|
306
|
-
* either live work or signal something needs human attention)
|
|
307
|
-
*
|
|
308
|
-
* Runs on whichever node holds the sweeper interval — it doesn't need
|
|
309
|
-
* a cluster lock because DELETE WHERE created_at < threshold is
|
|
310
|
-
* idempotent; multiple nodes running concurrently is wasteful but
|
|
311
|
-
* safe.
|
|
312
|
-
*/
|
|
313
|
-
declare class DeliveryRetentionSweeper {
|
|
314
|
-
private readonly engine;
|
|
315
|
-
private readonly objectName;
|
|
316
|
-
private readonly successTtlMs;
|
|
317
|
-
private readonly deadTtlMs;
|
|
318
|
-
private readonly sweepIntervalMs;
|
|
319
|
-
private readonly logger;
|
|
320
|
-
private timer;
|
|
321
|
-
private running;
|
|
322
|
-
constructor(engine: IDataEngine, opts?: DeliveryRetentionOptions);
|
|
323
|
-
start(): void;
|
|
324
|
-
stop(): void;
|
|
325
|
-
/** Run one sweep immediately. Returns the number of rows deleted. */
|
|
326
|
-
sweep(now?: number): Promise<{
|
|
327
|
-
success: number;
|
|
328
|
-
dead: number;
|
|
329
|
-
}>;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
interface WebhookOutboxPluginOptions extends Partial<Omit<DispatcherOptions, 'cluster' | 'outbox' | 'nodeId'>> {
|
|
333
|
-
/**
|
|
334
|
-
* Override the outbox backend. If omitted a fresh `MemoryWebhookOutbox`
|
|
335
|
-
* is used — fine for local development, **not for production**: each
|
|
336
|
-
* node will see only its own rows.
|
|
337
|
-
*
|
|
338
|
-
* Pass a factory if you need the kernel-resolved `IDataEngine`:
|
|
339
|
-
*
|
|
340
|
-
* ```ts
|
|
341
|
-
* outbox: (ctx) => new SqlWebhookOutbox(
|
|
342
|
-
* ctx.getService('objectql'), { partitionCount: 8 },
|
|
343
|
-
* ),
|
|
344
|
-
* ```
|
|
345
|
-
*/
|
|
346
|
-
outbox?: IWebhookOutbox | ((ctx: PluginContext) => IWebhookOutbox);
|
|
347
|
-
/**
|
|
348
|
-
* Stable node id. If omitted, uses `process.env.OS_NODE_ID`
|
|
349
|
-
* (legacy `OBJECTSTACK_NODE_ID` still honoured with deprecation warning)
|
|
350
|
-
* or a random UUID generated at plugin init.
|
|
351
|
-
*/
|
|
352
|
-
nodeId?: string;
|
|
353
|
-
/**
|
|
354
|
-
* If `false`, the plugin registers the outbox/dispatcher services but
|
|
355
|
-
* does NOT auto-start the loop — useful for tests that want to step
|
|
356
|
-
* the dispatcher manually via `dispatcher.tick()`.
|
|
357
|
-
*
|
|
358
|
-
* Default: true.
|
|
359
|
-
*/
|
|
360
|
-
autoStart?: boolean;
|
|
128
|
+
interface WebhookOutboxPluginOptions {
|
|
361
129
|
/**
|
|
362
|
-
* Auto-enqueue config. When enabled (default `true` if the realtime
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
130
|
+
* Auto-enqueue config. When enabled (default `true` if the realtime + data
|
|
131
|
+
* engine services are available), the plugin subscribes to `data.record.*`
|
|
132
|
+
* events and enqueues a delivery onto the shared messaging HTTP outbox for
|
|
133
|
+
* every matching `sys_webhook` row.
|
|
366
134
|
*
|
|
367
|
-
* Set `false` to disable and
|
|
368
|
-
* `outbox.enqueue()` API.
|
|
135
|
+
* Set `false` to disable and enqueue webhooks imperatively elsewhere.
|
|
369
136
|
*/
|
|
370
137
|
autoEnqueue?: boolean | AutoEnqueuerOptions;
|
|
371
|
-
/**
|
|
372
|
-
* Retention sweep config. When enabled (default `true` if a SQL
|
|
373
|
-
* outbox is in use), a periodic timer prunes old `success` and
|
|
374
|
-
* `dead` rows from `sys_webhook_delivery`.
|
|
375
|
-
*
|
|
376
|
-
* Set `false` to disable (e.g. when using `MemoryWebhookOutbox`).
|
|
377
|
-
*/
|
|
378
|
-
retention?: boolean | DeliveryRetentionOptions;
|
|
379
138
|
}
|
|
380
139
|
/**
|
|
381
|
-
* Wires
|
|
140
|
+
* Wires webhook fan-out on top of the shared outbound-HTTP delivery substrate
|
|
141
|
+
* (ADR-0018 M3).
|
|
382
142
|
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
143
|
+
* Webhooks are no longer their own delivery engine: the durable outbox, the
|
|
144
|
+
* cluster-coordinated dispatcher, the retry/backoff/dead-letter schedule, and
|
|
145
|
+
* the retention sweep all live in `@objectstack/service-messaging`
|
|
146
|
+
* (`sys_http_delivery` + `HttpDispatcher`). This plugin owns only the
|
|
147
|
+
* webhook-specific concerns:
|
|
148
|
+
* - the `sys_webhook` configuration object,
|
|
149
|
+
* - the {@link AutoEnqueuer} that turns `data.record.*` events into outbox
|
|
150
|
+
* rows (`source: 'webhook'`), and
|
|
151
|
+
* - the redeliver admin endpoint.
|
|
388
152
|
*
|
|
389
|
-
* End-to-end flow
|
|
153
|
+
* End-to-end flow:
|
|
390
154
|
*
|
|
391
155
|
* engine.insert('contact', {...})
|
|
392
156
|
* → engine publishes data.record.created via IRealtimeService
|
|
393
157
|
* → AutoEnqueuer matches active sys_webhook rows in O(1)
|
|
394
|
-
* →
|
|
395
|
-
* →
|
|
158
|
+
* → messaging.enqueueHttp() runs fire-and-forget (off the write path)
|
|
159
|
+
* → messaging HttpDispatcher claims and POSTs (cluster-coordinated, retried)
|
|
396
160
|
*
|
|
397
|
-
* **
|
|
398
|
-
*
|
|
399
|
-
* dispatcher works correctly inside a single process; with a real driver
|
|
400
|
-
* (`@objectstack/service-cluster-redis`) it correctly coordinates work
|
|
401
|
-
* across nodes.
|
|
161
|
+
* **Requires** `MessagingServicePlugin` (`@objectstack/service-messaging`),
|
|
162
|
+
* which is a foundational, always-on capability.
|
|
402
163
|
*/
|
|
403
164
|
declare class WebhookOutboxPlugin implements Plugin {
|
|
404
165
|
private readonly options;
|
|
@@ -406,65 +167,20 @@ declare class WebhookOutboxPlugin implements Plugin {
|
|
|
406
167
|
version: string;
|
|
407
168
|
type: "standard";
|
|
408
169
|
dependencies: string[];
|
|
409
|
-
private dispatcher;
|
|
410
170
|
private autoEnqueuer;
|
|
411
|
-
private retention;
|
|
412
|
-
private outboxInstance;
|
|
413
171
|
constructor(options?: WebhookOutboxPluginOptions);
|
|
414
172
|
init(ctx: PluginContext): Promise<void>;
|
|
415
173
|
dispose(): Promise<void>;
|
|
416
|
-
private
|
|
174
|
+
private getMessaging;
|
|
417
175
|
private bootAutoEnqueue;
|
|
418
|
-
private bootRetention;
|
|
419
176
|
private tryGetService;
|
|
420
177
|
/**
|
|
421
|
-
* Mount POST /api/v1/webhooks/redeliver on the host Hono app, if one
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
* the better-auth session cookie — every authenticated user counts.
|
|
178
|
+
* Mount POST /api/v1/webhooks/redeliver on the host Hono app, if one is
|
|
179
|
+
* available. Delegates to `messaging.redeliverHttp(deliveryId)`. Auth is the
|
|
180
|
+
* better-auth session cookie — every authenticated user counts.
|
|
425
181
|
*/
|
|
426
182
|
private registerAdminRoutes;
|
|
427
|
-
/**
|
|
428
|
-
* Resolve the requesting user's id from a better-auth session cookie.
|
|
429
|
-
* Returns `undefined` for anonymous callers — the caller decides
|
|
430
|
-
* whether that's a 401.
|
|
431
|
-
*/
|
|
432
183
|
private resolveSessionUserId;
|
|
433
184
|
}
|
|
434
185
|
|
|
435
|
-
|
|
436
|
-
* In-memory `IWebhookOutbox` for tests and single-process development.
|
|
437
|
-
*
|
|
438
|
-
* Implements the atomic-claim semantics by running its claim/ack logic
|
|
439
|
-
* synchronously (single-threaded JS event loop) inside one `Map`. Two
|
|
440
|
-
* `MemoryWebhookOutbox` instances do NOT share state — for the cross-node
|
|
441
|
-
* test the *same* instance is passed to both dispatchers (simulating one
|
|
442
|
-
* shared database).
|
|
443
|
-
*
|
|
444
|
-
* A production SQL-backed implementation will live in a sibling file and
|
|
445
|
-
* use `SELECT ... FOR UPDATE SKIP LOCKED`.
|
|
446
|
-
*/
|
|
447
|
-
declare class MemoryWebhookOutbox implements IWebhookOutbox {
|
|
448
|
-
private readonly rows;
|
|
449
|
-
/** Dedup index keyed by `${eventId}::${webhookId}` -> row id. */
|
|
450
|
-
private readonly dedup;
|
|
451
|
-
enqueue(input: EnqueueInput): Promise<string>;
|
|
452
|
-
claim(opts: ClaimOptions): Promise<WebhookDelivery[]>;
|
|
453
|
-
ack(id: string, result: AckResult): Promise<void>;
|
|
454
|
-
list(filter?: {
|
|
455
|
-
status?: DeliveryStatus;
|
|
456
|
-
}): Promise<WebhookDelivery[]>;
|
|
457
|
-
redeliver(id: string): Promise<WebhookDelivery>;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Stable, framework-free partition hash. The dispatcher uses this to
|
|
462
|
-
* assign webhooks to partitions; the in-memory outbox uses the same hash
|
|
463
|
-
* to filter rows in `claim()`. Both call sites MUST agree, which is why
|
|
464
|
-
* this is a single shared helper.
|
|
465
|
-
*
|
|
466
|
-
* Uses a 32-bit FNV-1a variant — fast, no allocations, deterministic.
|
|
467
|
-
*/
|
|
468
|
-
declare function hashPartition(key: string, count: number): number;
|
|
469
|
-
|
|
470
|
-
export { AckResult, type AttemptOutcome, AutoEnqueuer, type AutoEnqueuerOptions, ClaimOptions, DEFAULT_TIMEOUT_MS, type DeliveryRetentionOptions, DeliveryRetentionSweeper, DeliveryStatus, type DispatcherOptions, EnqueueInput, type FetchImpl, IWebhookOutbox, MemoryWebhookOutbox, WebhookDelivery, WebhookDispatcher, WebhookOutboxPlugin, type WebhookOutboxPluginOptions, classifyAttempt, hashPartition, nextRetryDelayMs, sendOnce };
|
|
186
|
+
export { AutoEnqueuer, type AutoEnqueuerOptions, type HttpEnqueueFn, WebhookOutboxPlugin, type WebhookOutboxPluginOptions };
|