@objectstack/plugin-webhooks 5.2.0 → 6.0.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 +28 -28
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-JN76ZRWN.js → chunk-33LYZT7O.js} +21 -1
- package/dist/chunk-33LYZT7O.js.map +1 -0
- package/dist/chunk-BS2QTZH3.js +256 -0
- package/dist/chunk-BS2QTZH3.js.map +1 -0
- package/dist/chunk-FA66GQEO.cjs +256 -0
- package/dist/chunk-FA66GQEO.cjs.map +1 -0
- package/dist/{chunk-OW7ESXOK.cjs → chunk-MJZGD37S.cjs} +21 -1
- package/dist/chunk-MJZGD37S.cjs.map +1 -0
- package/dist/index.cjs +175 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +167 -6
- package/dist/index.js.map +1 -1
- package/dist/{outbox-bPQmKYPN.d.cts → outbox-CIn7LSyB.d.cts} +28 -1
- package/dist/{outbox-bPQmKYPN.d.ts → outbox-CIn7LSyB.d.ts} +28 -1
- package/dist/schema.cjs +2 -2
- package/dist/schema.d.cts +16 -1
- package/dist/schema.d.ts +16 -1
- package/dist/schema.js +1 -1
- package/dist/sql-outbox.cjs +4 -180
- package/dist/sql-outbox.cjs.map +1 -1
- package/dist/sql-outbox.d.cts +2 -1
- package/dist/sql-outbox.d.ts +2 -1
- package/dist/sql-outbox.js +3 -179
- package/dist/sql-outbox.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +1 -0
- package/src/memory-outbox.test.ts +86 -0
- package/src/memory-outbox.ts +28 -0
- package/src/outbox.ts +34 -0
- package/src/sql-outbox.test.ts +80 -0
- package/src/sql-outbox.ts +61 -0
- package/src/sys-webhook-delivery.object.ts +22 -0
- package/src/webhook-outbox-plugin.ts +167 -5
- package/dist/chunk-JN76ZRWN.js.map +0 -1
- package/dist/chunk-M4M5FWIH.cjs +0 -15
- package/dist/chunk-M4M5FWIH.cjs.map +0 -1
- package/dist/chunk-NYSUNT6X.js +0 -15
- package/dist/chunk-NYSUNT6X.js.map +0 -1
- package/dist/chunk-OW7ESXOK.cjs.map +0 -1
|
@@ -99,6 +99,16 @@ interface AckFailure {
|
|
|
99
99
|
dead?: boolean;
|
|
100
100
|
}
|
|
101
101
|
type AckResult = AckSuccess | AckFailure;
|
|
102
|
+
/**
|
|
103
|
+
* Error raised by `IWebhookOutbox.redeliver` when the requested row is
|
|
104
|
+
* either missing or in a non-terminal state. The dispatcher / admin UI
|
|
105
|
+
* surfaces this verbatim to the caller — never throw it for transient
|
|
106
|
+
* conditions (transport errors should bubble as native `Error`).
|
|
107
|
+
*/
|
|
108
|
+
declare class RedeliverError extends Error {
|
|
109
|
+
readonly code: 'not_found' | 'not_eligible';
|
|
110
|
+
constructor(message: string, code: 'not_found' | 'not_eligible');
|
|
111
|
+
}
|
|
102
112
|
/**
|
|
103
113
|
* Pluggable storage backend for delivery rows. Implementations MUST make
|
|
104
114
|
* `claim()` atomic across concurrent callers — that property is the
|
|
@@ -123,6 +133,23 @@ interface IWebhookOutbox {
|
|
|
123
133
|
list(filter?: {
|
|
124
134
|
status?: DeliveryStatus;
|
|
125
135
|
}): Promise<WebhookDelivery[]>;
|
|
136
|
+
/**
|
|
137
|
+
* Reset a terminal row back to `pending` so the dispatcher will pick
|
|
138
|
+
* it up again on its next tick.
|
|
139
|
+
*
|
|
140
|
+
* - Eligible source states: `success`, `failed`, `dead`.
|
|
141
|
+
* - Rejects `pending` / `in_flight` rows — replaying those would
|
|
142
|
+
* double-deliver because they're either already queued or actively
|
|
143
|
+
* being sent.
|
|
144
|
+
* - Resets `attempts=0` so the retry budget restarts.
|
|
145
|
+
* - Clears `claimed_by`, `claimed_at`, `next_retry_at`, `error`,
|
|
146
|
+
* `response_code`, `response_body`. URL / payload / secret are NOT
|
|
147
|
+
* touched — replay reproduces the original POST byte-for-byte.
|
|
148
|
+
*
|
|
149
|
+
* Throws `RedeliverError` with code `not_found` or `not_eligible`.
|
|
150
|
+
* Returns the post-reset row.
|
|
151
|
+
*/
|
|
152
|
+
redeliver(id: string): Promise<WebhookDelivery>;
|
|
126
153
|
}
|
|
127
154
|
|
|
128
|
-
export type
|
|
155
|
+
export { type AckFailure as A, type ClaimOptions as C, type DeliveryStatus as D, type EnqueueInput as E, type IWebhookOutbox as I, RedeliverError as R, type WebhookDelivery as W, type AckResult as a, type AckSuccess as b };
|
|
@@ -99,6 +99,16 @@ interface AckFailure {
|
|
|
99
99
|
dead?: boolean;
|
|
100
100
|
}
|
|
101
101
|
type AckResult = AckSuccess | AckFailure;
|
|
102
|
+
/**
|
|
103
|
+
* Error raised by `IWebhookOutbox.redeliver` when the requested row is
|
|
104
|
+
* either missing or in a non-terminal state. The dispatcher / admin UI
|
|
105
|
+
* surfaces this verbatim to the caller — never throw it for transient
|
|
106
|
+
* conditions (transport errors should bubble as native `Error`).
|
|
107
|
+
*/
|
|
108
|
+
declare class RedeliverError extends Error {
|
|
109
|
+
readonly code: 'not_found' | 'not_eligible';
|
|
110
|
+
constructor(message: string, code: 'not_found' | 'not_eligible');
|
|
111
|
+
}
|
|
102
112
|
/**
|
|
103
113
|
* Pluggable storage backend for delivery rows. Implementations MUST make
|
|
104
114
|
* `claim()` atomic across concurrent callers — that property is the
|
|
@@ -123,6 +133,23 @@ interface IWebhookOutbox {
|
|
|
123
133
|
list(filter?: {
|
|
124
134
|
status?: DeliveryStatus;
|
|
125
135
|
}): Promise<WebhookDelivery[]>;
|
|
136
|
+
/**
|
|
137
|
+
* Reset a terminal row back to `pending` so the dispatcher will pick
|
|
138
|
+
* it up again on its next tick.
|
|
139
|
+
*
|
|
140
|
+
* - Eligible source states: `success`, `failed`, `dead`.
|
|
141
|
+
* - Rejects `pending` / `in_flight` rows — replaying those would
|
|
142
|
+
* double-deliver because they're either already queued or actively
|
|
143
|
+
* being sent.
|
|
144
|
+
* - Resets `attempts=0` so the retry budget restarts.
|
|
145
|
+
* - Clears `claimed_by`, `claimed_at`, `next_retry_at`, `error`,
|
|
146
|
+
* `response_code`, `response_body`. URL / payload / secret are NOT
|
|
147
|
+
* touched — replay reproduces the original POST byte-for-byte.
|
|
148
|
+
*
|
|
149
|
+
* Throws `RedeliverError` with code `not_found` or `not_eligible`.
|
|
150
|
+
* Returns the post-reset row.
|
|
151
|
+
*/
|
|
152
|
+
redeliver(id: string): Promise<WebhookDelivery>;
|
|
126
153
|
}
|
|
127
154
|
|
|
128
|
-
export type
|
|
155
|
+
export { type AckFailure as A, type ClaimOptions as C, type DeliveryStatus as D, type EnqueueInput as E, type IWebhookOutbox as I, RedeliverError as R, type WebhookDelivery as W, type AckResult as a, type AckSuccess as b };
|
package/dist/schema.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var _chunkMJZGD37Scjs = require('./chunk-MJZGD37S.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
exports.SYS_WEBHOOK_DELIVERY =
|
|
8
|
+
exports.SYS_WEBHOOK_DELIVERY = _chunkMJZGD37Scjs.SYS_WEBHOOK_DELIVERY; exports.SysWebhookDelivery = _chunkMJZGD37Scjs.SysWebhookDelivery;
|
|
9
9
|
//# sourceMappingURL=schema.cjs.map
|
package/dist/schema.d.cts
CHANGED
|
@@ -72,7 +72,7 @@ declare const SysWebhookDelivery: Omit<{
|
|
|
72
72
|
abstract: boolean;
|
|
73
73
|
datasource: string;
|
|
74
74
|
fields: Record<string, {
|
|
75
|
-
type: "number" | "boolean" | "date" | "file" | "tags" | "code" | "datetime" | "signature" | "progress" | "
|
|
75
|
+
type: "number" | "boolean" | "date" | "file" | "url" | "tags" | "code" | "datetime" | "signature" | "progress" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "location" | "address" | "json" | "color" | "rating" | "slider" | "qrcode" | "vector";
|
|
76
76
|
required: boolean;
|
|
77
77
|
searchable: boolean;
|
|
78
78
|
multiple: boolean;
|
|
@@ -747,6 +747,21 @@ declare const SysWebhookDelivery: Omit<{
|
|
|
747
747
|
readonly displayNameField: "id";
|
|
748
748
|
readonly titleFormat: "{event_type} → {url}";
|
|
749
749
|
readonly compactLayout: ["event_type", "url", "status", "attempts", "next_retry_at"];
|
|
750
|
+
readonly actions: [{
|
|
751
|
+
readonly name: "redeliver";
|
|
752
|
+
readonly label: "Redeliver";
|
|
753
|
+
readonly icon: "refresh-cw";
|
|
754
|
+
readonly variant: "secondary";
|
|
755
|
+
readonly locations: ["list_item", "record_header"];
|
|
756
|
+
readonly type: "api";
|
|
757
|
+
readonly target: "/api/v1/webhooks/redeliver";
|
|
758
|
+
readonly method: "POST";
|
|
759
|
+
readonly recordIdParam: "deliveryId";
|
|
760
|
+
readonly confirmText: "Replay this delivery? The receiver will get the original payload again — they must be idempotent on the X-Objectstack-Delivery header.";
|
|
761
|
+
readonly successMessage: "Queued for redelivery";
|
|
762
|
+
readonly refreshAfter: true;
|
|
763
|
+
readonly disabled: "!(status in ['success', 'failed', 'dead'])";
|
|
764
|
+
}];
|
|
750
765
|
readonly listViews: {
|
|
751
766
|
readonly recent: {
|
|
752
767
|
readonly type: "grid";
|
package/dist/schema.d.ts
CHANGED
|
@@ -72,7 +72,7 @@ declare const SysWebhookDelivery: Omit<{
|
|
|
72
72
|
abstract: boolean;
|
|
73
73
|
datasource: string;
|
|
74
74
|
fields: Record<string, {
|
|
75
|
-
type: "number" | "boolean" | "date" | "file" | "tags" | "code" | "datetime" | "signature" | "progress" | "
|
|
75
|
+
type: "number" | "boolean" | "date" | "file" | "url" | "tags" | "code" | "datetime" | "signature" | "progress" | "text" | "textarea" | "email" | "phone" | "password" | "markdown" | "html" | "richtext" | "currency" | "percent" | "time" | "toggle" | "select" | "multiselect" | "radio" | "checkboxes" | "lookup" | "master_detail" | "tree" | "image" | "avatar" | "video" | "audio" | "formula" | "summary" | "autonumber" | "location" | "address" | "json" | "color" | "rating" | "slider" | "qrcode" | "vector";
|
|
76
76
|
required: boolean;
|
|
77
77
|
searchable: boolean;
|
|
78
78
|
multiple: boolean;
|
|
@@ -747,6 +747,21 @@ declare const SysWebhookDelivery: Omit<{
|
|
|
747
747
|
readonly displayNameField: "id";
|
|
748
748
|
readonly titleFormat: "{event_type} → {url}";
|
|
749
749
|
readonly compactLayout: ["event_type", "url", "status", "attempts", "next_retry_at"];
|
|
750
|
+
readonly actions: [{
|
|
751
|
+
readonly name: "redeliver";
|
|
752
|
+
readonly label: "Redeliver";
|
|
753
|
+
readonly icon: "refresh-cw";
|
|
754
|
+
readonly variant: "secondary";
|
|
755
|
+
readonly locations: ["list_item", "record_header"];
|
|
756
|
+
readonly type: "api";
|
|
757
|
+
readonly target: "/api/v1/webhooks/redeliver";
|
|
758
|
+
readonly method: "POST";
|
|
759
|
+
readonly recordIdParam: "deliveryId";
|
|
760
|
+
readonly confirmText: "Replay this delivery? The receiver will get the original payload again — they must be idempotent on the X-Objectstack-Delivery header.";
|
|
761
|
+
readonly successMessage: "Queued for redelivery";
|
|
762
|
+
readonly refreshAfter: true;
|
|
763
|
+
readonly disabled: "!(status in ['success', 'failed', 'dead'])";
|
|
764
|
+
}];
|
|
750
765
|
readonly listViews: {
|
|
751
766
|
readonly recent: {
|
|
752
767
|
readonly type: "grid";
|
package/dist/schema.js
CHANGED
package/dist/sql-outbox.cjs
CHANGED
|
@@ -1,184 +1,8 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkFA66GQEOcjs = require('./chunk-FA66GQEO.cjs');
|
|
4
|
+
require('./chunk-MJZGD37S.cjs');
|
|
4
5
|
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// src/sql-outbox.ts
|
|
9
|
-
var _crypto = require('crypto');
|
|
10
|
-
var SqlWebhookOutbox = class {
|
|
11
|
-
constructor(engine, opts) {
|
|
12
|
-
this.engine = engine;
|
|
13
|
-
if (opts.partitionCount <= 0) {
|
|
14
|
-
throw new Error("SqlWebhookOutbox: partitionCount must be > 0");
|
|
15
|
-
}
|
|
16
|
-
this.objectName = _nullishCoalesce(opts.objectName, () => ( _chunkOW7ESXOKcjs.SYS_WEBHOOK_DELIVERY));
|
|
17
|
-
this.partitionCount = opts.partitionCount;
|
|
18
|
-
}
|
|
19
|
-
async enqueue(input) {
|
|
20
|
-
const existing = await this.engine.findOne(this.objectName, {
|
|
21
|
-
where: { event_id: input.eventId, webhook_id: input.webhookId },
|
|
22
|
-
fields: ["id"]
|
|
23
|
-
});
|
|
24
|
-
if (_optionalChain([existing, 'optionalAccess', _ => _.id])) return existing.id;
|
|
25
|
-
const id = _crypto.randomUUID.call(void 0, );
|
|
26
|
-
const now = Date.now();
|
|
27
|
-
const row = {
|
|
28
|
-
id,
|
|
29
|
-
webhook_id: input.webhookId,
|
|
30
|
-
event_id: input.eventId,
|
|
31
|
-
event_type: input.eventType,
|
|
32
|
-
url: input.url,
|
|
33
|
-
method: _nullishCoalesce(input.method, () => ( "POST")),
|
|
34
|
-
headers_json: input.headers ? JSON.stringify(input.headers) : void 0,
|
|
35
|
-
secret: input.secret,
|
|
36
|
-
timeout_ms: input.timeoutMs,
|
|
37
|
-
payload_json: JSON.stringify(_nullishCoalesce(input.payload, () => ( null))),
|
|
38
|
-
partition_key: _chunkM4M5FWIHcjs.hashPartition.call(void 0, input.webhookId, this.partitionCount),
|
|
39
|
-
status: "pending",
|
|
40
|
-
attempts: 0,
|
|
41
|
-
created_at: now,
|
|
42
|
-
updated_at: now
|
|
43
|
-
};
|
|
44
|
-
try {
|
|
45
|
-
await this.engine.insert(this.objectName, row);
|
|
46
|
-
return id;
|
|
47
|
-
} catch (err) {
|
|
48
|
-
const winner = await this.engine.findOne(this.objectName, {
|
|
49
|
-
where: { event_id: input.eventId, webhook_id: input.webhookId },
|
|
50
|
-
fields: ["id"]
|
|
51
|
-
});
|
|
52
|
-
if (_optionalChain([winner, 'optionalAccess', _2 => _2.id])) return winner.id;
|
|
53
|
-
throw err;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async claim(opts) {
|
|
57
|
-
const now = _nullishCoalesce(opts.now, () => ( Date.now()));
|
|
58
|
-
await this.engine.update(
|
|
59
|
-
this.objectName,
|
|
60
|
-
{ status: "pending", claimed_by: null, claimed_at: null, updated_at: now },
|
|
61
|
-
{
|
|
62
|
-
where: {
|
|
63
|
-
status: "in_flight",
|
|
64
|
-
claimed_at: { $lt: now - opts.claimTtlMs }
|
|
65
|
-
},
|
|
66
|
-
multi: true
|
|
67
|
-
}
|
|
68
|
-
);
|
|
69
|
-
const partitionFilter = opts.partition ? { partition_key: opts.partition.index } : {};
|
|
70
|
-
const candidates = await this.engine.find(this.objectName, {
|
|
71
|
-
where: {
|
|
72
|
-
status: "pending",
|
|
73
|
-
...partitionFilter,
|
|
74
|
-
// next_retry_at <= now OR null
|
|
75
|
-
$or: [
|
|
76
|
-
{ next_retry_at: null },
|
|
77
|
-
{ next_retry_at: { $lte: now } }
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
fields: ["id"],
|
|
81
|
-
// No orderBy for portability — drivers handle the natural insert order.
|
|
82
|
-
limit: opts.limit
|
|
83
|
-
});
|
|
84
|
-
if (candidates.length === 0) return [];
|
|
85
|
-
const ids = candidates.map((c) => c.id);
|
|
86
|
-
await this.engine.update(
|
|
87
|
-
this.objectName,
|
|
88
|
-
{
|
|
89
|
-
status: "in_flight",
|
|
90
|
-
claimed_by: opts.nodeId,
|
|
91
|
-
claimed_at: now,
|
|
92
|
-
updated_at: now
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
where: { id: { $in: ids }, status: "pending" },
|
|
96
|
-
multi: true
|
|
97
|
-
}
|
|
98
|
-
);
|
|
99
|
-
const claimed = await this.engine.find(this.objectName, {
|
|
100
|
-
where: {
|
|
101
|
-
id: { $in: ids },
|
|
102
|
-
claimed_by: opts.nodeId,
|
|
103
|
-
claimed_at: now,
|
|
104
|
-
status: "in_flight"
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
return claimed.map((r) => this.toDelivery(r));
|
|
108
|
-
}
|
|
109
|
-
async ack(id, result) {
|
|
110
|
-
const current = await this.engine.findOne(this.objectName, {
|
|
111
|
-
where: { id },
|
|
112
|
-
fields: ["attempts"]
|
|
113
|
-
});
|
|
114
|
-
if (!current) return;
|
|
115
|
-
const now = Date.now();
|
|
116
|
-
let status;
|
|
117
|
-
let nextRetryAt;
|
|
118
|
-
let error;
|
|
119
|
-
if (result.success) {
|
|
120
|
-
status = "success";
|
|
121
|
-
nextRetryAt = null;
|
|
122
|
-
error = null;
|
|
123
|
-
} else if (result.dead) {
|
|
124
|
-
status = "dead";
|
|
125
|
-
nextRetryAt = null;
|
|
126
|
-
error = _nullishCoalesce(result.error, () => ( null));
|
|
127
|
-
} else {
|
|
128
|
-
status = "pending";
|
|
129
|
-
nextRetryAt = _nullishCoalesce(result.nextRetryAt, () => ( null));
|
|
130
|
-
error = _nullishCoalesce(result.error, () => ( null));
|
|
131
|
-
}
|
|
132
|
-
await this.engine.update(
|
|
133
|
-
this.objectName,
|
|
134
|
-
{
|
|
135
|
-
status,
|
|
136
|
-
attempts: (_nullishCoalesce(current.attempts, () => ( 0))) + 1,
|
|
137
|
-
last_attempted_at: now,
|
|
138
|
-
claimed_by: null,
|
|
139
|
-
claimed_at: null,
|
|
140
|
-
response_code: _nullishCoalesce(result.httpStatus, () => ( null)),
|
|
141
|
-
response_body: _nullishCoalesce(result.responseBody, () => ( null)),
|
|
142
|
-
next_retry_at: nextRetryAt,
|
|
143
|
-
error,
|
|
144
|
-
updated_at: now
|
|
145
|
-
},
|
|
146
|
-
{ where: { id }, multi: false }
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
async list(filter) {
|
|
150
|
-
const rows = await this.engine.find(this.objectName, {
|
|
151
|
-
where: _optionalChain([filter, 'optionalAccess', _3 => _3.status]) ? { status: filter.status } : {}
|
|
152
|
-
});
|
|
153
|
-
return rows.map((r) => this.toDelivery(r));
|
|
154
|
-
}
|
|
155
|
-
toDelivery(r) {
|
|
156
|
-
return {
|
|
157
|
-
id: r.id,
|
|
158
|
-
webhookId: r.webhook_id,
|
|
159
|
-
eventId: r.event_id,
|
|
160
|
-
eventType: r.event_type,
|
|
161
|
-
url: r.url,
|
|
162
|
-
method: _nullishCoalesce(r.method, () => ( void 0)),
|
|
163
|
-
headers: r.headers_json ? JSON.parse(r.headers_json) : void 0,
|
|
164
|
-
secret: _nullishCoalesce(r.secret, () => ( void 0)),
|
|
165
|
-
timeoutMs: _nullishCoalesce(r.timeout_ms, () => ( void 0)),
|
|
166
|
-
payload: JSON.parse(r.payload_json),
|
|
167
|
-
status: r.status,
|
|
168
|
-
attempts: r.attempts,
|
|
169
|
-
claimedBy: _nullishCoalesce(r.claimed_by, () => ( void 0)),
|
|
170
|
-
claimedAt: _nullishCoalesce(r.claimed_at, () => ( void 0)),
|
|
171
|
-
nextRetryAt: _nullishCoalesce(r.next_retry_at, () => ( void 0)),
|
|
172
|
-
lastAttemptedAt: _nullishCoalesce(r.last_attempted_at, () => ( void 0)),
|
|
173
|
-
responseCode: _nullishCoalesce(r.response_code, () => ( void 0)),
|
|
174
|
-
responseBody: _nullishCoalesce(r.response_body, () => ( void 0)),
|
|
175
|
-
error: _nullishCoalesce(r.error, () => ( void 0)),
|
|
176
|
-
createdAt: r.created_at,
|
|
177
|
-
updatedAt: r.updated_at
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
exports.SqlWebhookOutbox = SqlWebhookOutbox;
|
|
7
|
+
exports.SqlWebhookOutbox = _chunkFA66GQEOcjs.SqlWebhookOutbox;
|
|
184
8
|
//# sourceMappingURL=sql-outbox.cjs.map
|
package/dist/sql-outbox.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/framework/framework/packages/plugins/plugin-webhooks/dist/sql-outbox.cjs","../src/sql-outbox.ts"],"names":[],"mappings":"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","file":"/home/runner/work/framework/framework/packages/plugins/plugin-webhooks/dist/sql-outbox.cjs","sourcesContent":[null,"// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n AckResult,\n ClaimOptions,\n DeliveryStatus,\n EnqueueInput,\n IWebhookOutbox,\n WebhookDelivery,\n} from './outbox.js';\nimport { hashPartition } from './partition.js';\nimport { SYS_WEBHOOK_DELIVERY } from './schema.js';\n\nexport interface SqlWebhookOutboxOptions {\n /**\n * Total partition count — MUST match the dispatcher's `partitionCount`.\n * Used at enqueue time to precompute `partition_key`.\n */\n partitionCount: number;\n /**\n * Object name to read/write. Defaults to `sys_webhook_delivery`. Override\n * only if you've registered the schema under a different name.\n */\n objectName?: string;\n}\n\ninterface DeliveryRow {\n id: string;\n webhook_id: string;\n event_id: string;\n event_type: string;\n url: string;\n method?: string | null;\n headers_json?: string | null;\n secret?: string | null;\n timeout_ms?: number | null;\n payload_json: string;\n partition_key: number;\n status: DeliveryStatus;\n attempts: number;\n claimed_by?: string | null;\n claimed_at?: number | null;\n next_retry_at?: number | null;\n last_attempted_at?: number | null;\n response_code?: number | null;\n response_body?: string | null;\n error?: string | null;\n created_at: number;\n updated_at: number;\n}\n\n/**\n * Durable `IWebhookOutbox` backed by ObjectQL — the production storage\n * impl. Works against any registered driver (SQL, Turso, Mongo, in-memory)\n * because everything goes through the driver-agnostic `IDataEngine` API.\n *\n * **Why no `FOR UPDATE SKIP LOCKED`?** ObjectQL is driver-agnostic — that\n * SQL feature is Postgres-only. We get equivalent safety from two layers:\n *\n * 1. `cluster.lock` held per partition by the dispatcher (the primary\n * mutex). One node owns one partition at a time → no two claimers.\n * 2. Atomic `UPDATE WHERE status='pending'` (the backup). Even if two\n * claimers slip through (e.g. admin reschedule + dispatcher), only\n * the first UPDATE matches each row.\n *\n * **Why precompute `partition_key` on enqueue?** ObjectQL has no\n * cross-driver `hash()` function in WHERE clauses. Storing the partition\n * as a column makes the claim query a plain indexed lookup.\n *\n * **Dedup race**: SELECT-then-INSERT has a tiny window where two\n * concurrent producers both miss the SELECT and both INSERT. The unique\n * index `(event_id, webhook_id)` on the table catches it — the second\n * INSERT errors, the producer ignores it. Receivers MUST be idempotent\n * on the `X-Objectstack-Delivery` header anyway.\n */\nexport class SqlWebhookOutbox implements IWebhookOutbox {\n private readonly objectName: string;\n private readonly partitionCount: number;\n\n constructor(\n private readonly engine: IDataEngine,\n opts: SqlWebhookOutboxOptions,\n ) {\n if (opts.partitionCount <= 0) {\n throw new Error('SqlWebhookOutbox: partitionCount must be > 0');\n }\n this.objectName = opts.objectName ?? SYS_WEBHOOK_DELIVERY;\n this.partitionCount = opts.partitionCount;\n }\n\n async enqueue(input: EnqueueInput): Promise<string> {\n // Cheap pre-check to absorb most duplicates without hitting the\n // unique-index error path. Race window with the INSERT below is\n // intentional and documented.\n const existing = await this.engine.findOne(this.objectName, {\n where: { event_id: input.eventId, webhook_id: input.webhookId },\n fields: ['id'],\n });\n if (existing?.id) return existing.id as string;\n\n const id = randomUUID();\n const now = Date.now();\n const row: Omit<DeliveryRow, 'response_body' | 'error'> = {\n id,\n webhook_id: input.webhookId,\n event_id: input.eventId,\n event_type: input.eventType,\n url: input.url,\n method: input.method ?? 'POST',\n headers_json: input.headers ? JSON.stringify(input.headers) : undefined,\n secret: input.secret,\n timeout_ms: input.timeoutMs,\n payload_json: JSON.stringify(input.payload ?? null),\n partition_key: hashPartition(input.webhookId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n created_at: now,\n updated_at: now,\n };\n try {\n await this.engine.insert(this.objectName, row);\n return id;\n } catch (err) {\n // Unique-index collision (dedup race) → look up the winner and\n // return its id. Any other error propagates.\n const winner = await this.engine.findOne(this.objectName, {\n where: { event_id: input.eventId, webhook_id: input.webhookId },\n fields: ['id'],\n });\n if (winner?.id) return winner.id as string;\n throw err;\n }\n }\n\n async claim(opts: ClaimOptions): Promise<WebhookDelivery[]> {\n const now = opts.now ?? Date.now();\n\n // 1. Reap stale in_flight rows — visibility-timeout recovery.\n await this.engine.update(\n this.objectName,\n { status: 'pending', claimed_by: null, claimed_at: null, updated_at: now },\n {\n where: {\n status: 'in_flight',\n claimed_at: { $lt: now - opts.claimTtlMs },\n },\n multi: true,\n },\n );\n\n // 2. Pick candidate ids.\n const partitionFilter = opts.partition\n ? { partition_key: opts.partition.index }\n : {};\n const candidates = await this.engine.find(this.objectName, {\n where: {\n status: 'pending',\n ...partitionFilter,\n // next_retry_at <= now OR null\n $or: [\n { next_retry_at: null },\n { next_retry_at: { $lte: now } },\n ],\n },\n fields: ['id'],\n // No orderBy for portability — drivers handle the natural insert order.\n limit: opts.limit,\n });\n if (candidates.length === 0) return [];\n\n const ids = (candidates as Array<{ id: string }>).map((c) => c.id);\n\n // 3. Atomic claim. WHERE status='pending' rejects any rows another\n // worker swept up between steps 2 and 3.\n await this.engine.update(\n this.objectName,\n {\n status: 'in_flight',\n claimed_by: opts.nodeId,\n claimed_at: now,\n updated_at: now,\n },\n {\n where: { id: { $in: ids }, status: 'pending' },\n multi: true,\n },\n );\n\n // 4. Read back the rows we actually own.\n const claimed = (await this.engine.find(this.objectName, {\n where: {\n id: { $in: ids },\n claimed_by: opts.nodeId,\n claimed_at: now,\n status: 'in_flight',\n },\n })) as DeliveryRow[];\n\n return claimed.map((r) => this.toDelivery(r));\n }\n\n async ack(id: string, result: AckResult): Promise<void> {\n // ObjectQL has no atomic $inc across drivers, so read-then-write.\n // Safe enough: ack is single-writer per row (only the claimer acks).\n const current = (await this.engine.findOne(this.objectName, {\n where: { id },\n fields: ['attempts'],\n })) as { attempts?: number } | null;\n if (!current) return;\n\n const now = Date.now();\n let status: DeliveryStatus;\n let nextRetryAt: number | null;\n let error: string | null;\n\n if (result.success) {\n status = 'success';\n nextRetryAt = null;\n error = null;\n } else if (result.dead) {\n status = 'dead';\n nextRetryAt = null;\n error = result.error ?? null;\n } else {\n status = 'pending';\n nextRetryAt = result.nextRetryAt ?? null;\n error = result.error ?? null;\n }\n\n await this.engine.update(\n this.objectName,\n {\n status,\n attempts: (current.attempts ?? 0) + 1,\n last_attempted_at: now,\n claimed_by: null,\n claimed_at: null,\n response_code: result.httpStatus ?? null,\n response_body: result.responseBody ?? null,\n next_retry_at: nextRetryAt,\n error,\n updated_at: now,\n },\n { where: { id }, multi: false },\n );\n }\n\n async list(filter?: { status?: DeliveryStatus }): Promise<WebhookDelivery[]> {\n const rows = (await this.engine.find(this.objectName, {\n where: filter?.status ? { status: filter.status } : {},\n })) as DeliveryRow[];\n return rows.map((r) => this.toDelivery(r));\n }\n\n private toDelivery(r: DeliveryRow): WebhookDelivery {\n return {\n id: r.id,\n webhookId: r.webhook_id,\n eventId: r.event_id,\n eventType: r.event_type,\n url: r.url,\n method: r.method ?? undefined,\n headers: r.headers_json ? JSON.parse(r.headers_json) : undefined,\n secret: r.secret ?? undefined,\n timeoutMs: r.timeout_ms ?? undefined,\n payload: JSON.parse(r.payload_json),\n status: r.status,\n attempts: r.attempts,\n claimedBy: r.claimed_by ?? undefined,\n claimedAt: r.claimed_at ?? undefined,\n nextRetryAt: r.next_retry_at ?? undefined,\n lastAttemptedAt: r.last_attempted_at ?? undefined,\n responseCode: r.response_code ?? undefined,\n responseBody: r.response_body ?? undefined,\n error: r.error ?? undefined,\n createdAt: r.created_at,\n updatedAt: r.updated_at,\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/framework/framework/packages/plugins/plugin-webhooks/dist/sql-outbox.cjs"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B,gCAA6B;AAC7B;AACE;AACF,8DAAC","file":"/home/runner/work/framework/framework/packages/plugins/plugin-webhooks/dist/sql-outbox.cjs"}
|
package/dist/sql-outbox.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IDataEngine } from '@objectstack/spec/contracts';
|
|
2
|
-
import { I as IWebhookOutbox, E as EnqueueInput, C as ClaimOptions, W as WebhookDelivery, a as AckResult, D as DeliveryStatus } from './outbox-
|
|
2
|
+
import { I as IWebhookOutbox, E as EnqueueInput, C as ClaimOptions, W as WebhookDelivery, a as AckResult, D as DeliveryStatus } from './outbox-CIn7LSyB.cjs';
|
|
3
3
|
|
|
4
4
|
interface SqlWebhookOutboxOptions {
|
|
5
5
|
/**
|
|
@@ -48,6 +48,7 @@ declare class SqlWebhookOutbox implements IWebhookOutbox {
|
|
|
48
48
|
list(filter?: {
|
|
49
49
|
status?: DeliveryStatus;
|
|
50
50
|
}): Promise<WebhookDelivery[]>;
|
|
51
|
+
redeliver(id: string): Promise<WebhookDelivery>;
|
|
51
52
|
private toDelivery;
|
|
52
53
|
}
|
|
53
54
|
|
package/dist/sql-outbox.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IDataEngine } from '@objectstack/spec/contracts';
|
|
2
|
-
import { I as IWebhookOutbox, E as EnqueueInput, C as ClaimOptions, W as WebhookDelivery, a as AckResult, D as DeliveryStatus } from './outbox-
|
|
2
|
+
import { I as IWebhookOutbox, E as EnqueueInput, C as ClaimOptions, W as WebhookDelivery, a as AckResult, D as DeliveryStatus } from './outbox-CIn7LSyB.js';
|
|
3
3
|
|
|
4
4
|
interface SqlWebhookOutboxOptions {
|
|
5
5
|
/**
|
|
@@ -48,6 +48,7 @@ declare class SqlWebhookOutbox implements IWebhookOutbox {
|
|
|
48
48
|
list(filter?: {
|
|
49
49
|
status?: DeliveryStatus;
|
|
50
50
|
}): Promise<WebhookDelivery[]>;
|
|
51
|
+
redeliver(id: string): Promise<WebhookDelivery>;
|
|
51
52
|
private toDelivery;
|
|
52
53
|
}
|
|
53
54
|
|
package/dist/sql-outbox.js
CHANGED
|
@@ -1,183 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import
|
|
5
|
-
SYS_WEBHOOK_DELIVERY
|
|
6
|
-
} from "./chunk-JN76ZRWN.js";
|
|
7
|
-
|
|
8
|
-
// src/sql-outbox.ts
|
|
9
|
-
import { randomUUID } from "crypto";
|
|
10
|
-
var SqlWebhookOutbox = class {
|
|
11
|
-
constructor(engine, opts) {
|
|
12
|
-
this.engine = engine;
|
|
13
|
-
if (opts.partitionCount <= 0) {
|
|
14
|
-
throw new Error("SqlWebhookOutbox: partitionCount must be > 0");
|
|
15
|
-
}
|
|
16
|
-
this.objectName = opts.objectName ?? SYS_WEBHOOK_DELIVERY;
|
|
17
|
-
this.partitionCount = opts.partitionCount;
|
|
18
|
-
}
|
|
19
|
-
async enqueue(input) {
|
|
20
|
-
const existing = await this.engine.findOne(this.objectName, {
|
|
21
|
-
where: { event_id: input.eventId, webhook_id: input.webhookId },
|
|
22
|
-
fields: ["id"]
|
|
23
|
-
});
|
|
24
|
-
if (existing?.id) return existing.id;
|
|
25
|
-
const id = randomUUID();
|
|
26
|
-
const now = Date.now();
|
|
27
|
-
const row = {
|
|
28
|
-
id,
|
|
29
|
-
webhook_id: input.webhookId,
|
|
30
|
-
event_id: input.eventId,
|
|
31
|
-
event_type: input.eventType,
|
|
32
|
-
url: input.url,
|
|
33
|
-
method: input.method ?? "POST",
|
|
34
|
-
headers_json: input.headers ? JSON.stringify(input.headers) : void 0,
|
|
35
|
-
secret: input.secret,
|
|
36
|
-
timeout_ms: input.timeoutMs,
|
|
37
|
-
payload_json: JSON.stringify(input.payload ?? null),
|
|
38
|
-
partition_key: hashPartition(input.webhookId, this.partitionCount),
|
|
39
|
-
status: "pending",
|
|
40
|
-
attempts: 0,
|
|
41
|
-
created_at: now,
|
|
42
|
-
updated_at: now
|
|
43
|
-
};
|
|
44
|
-
try {
|
|
45
|
-
await this.engine.insert(this.objectName, row);
|
|
46
|
-
return id;
|
|
47
|
-
} catch (err) {
|
|
48
|
-
const winner = await this.engine.findOne(this.objectName, {
|
|
49
|
-
where: { event_id: input.eventId, webhook_id: input.webhookId },
|
|
50
|
-
fields: ["id"]
|
|
51
|
-
});
|
|
52
|
-
if (winner?.id) return winner.id;
|
|
53
|
-
throw err;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
async claim(opts) {
|
|
57
|
-
const now = opts.now ?? Date.now();
|
|
58
|
-
await this.engine.update(
|
|
59
|
-
this.objectName,
|
|
60
|
-
{ status: "pending", claimed_by: null, claimed_at: null, updated_at: now },
|
|
61
|
-
{
|
|
62
|
-
where: {
|
|
63
|
-
status: "in_flight",
|
|
64
|
-
claimed_at: { $lt: now - opts.claimTtlMs }
|
|
65
|
-
},
|
|
66
|
-
multi: true
|
|
67
|
-
}
|
|
68
|
-
);
|
|
69
|
-
const partitionFilter = opts.partition ? { partition_key: opts.partition.index } : {};
|
|
70
|
-
const candidates = await this.engine.find(this.objectName, {
|
|
71
|
-
where: {
|
|
72
|
-
status: "pending",
|
|
73
|
-
...partitionFilter,
|
|
74
|
-
// next_retry_at <= now OR null
|
|
75
|
-
$or: [
|
|
76
|
-
{ next_retry_at: null },
|
|
77
|
-
{ next_retry_at: { $lte: now } }
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
fields: ["id"],
|
|
81
|
-
// No orderBy for portability — drivers handle the natural insert order.
|
|
82
|
-
limit: opts.limit
|
|
83
|
-
});
|
|
84
|
-
if (candidates.length === 0) return [];
|
|
85
|
-
const ids = candidates.map((c) => c.id);
|
|
86
|
-
await this.engine.update(
|
|
87
|
-
this.objectName,
|
|
88
|
-
{
|
|
89
|
-
status: "in_flight",
|
|
90
|
-
claimed_by: opts.nodeId,
|
|
91
|
-
claimed_at: now,
|
|
92
|
-
updated_at: now
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
where: { id: { $in: ids }, status: "pending" },
|
|
96
|
-
multi: true
|
|
97
|
-
}
|
|
98
|
-
);
|
|
99
|
-
const claimed = await this.engine.find(this.objectName, {
|
|
100
|
-
where: {
|
|
101
|
-
id: { $in: ids },
|
|
102
|
-
claimed_by: opts.nodeId,
|
|
103
|
-
claimed_at: now,
|
|
104
|
-
status: "in_flight"
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
return claimed.map((r) => this.toDelivery(r));
|
|
108
|
-
}
|
|
109
|
-
async ack(id, result) {
|
|
110
|
-
const current = await this.engine.findOne(this.objectName, {
|
|
111
|
-
where: { id },
|
|
112
|
-
fields: ["attempts"]
|
|
113
|
-
});
|
|
114
|
-
if (!current) return;
|
|
115
|
-
const now = Date.now();
|
|
116
|
-
let status;
|
|
117
|
-
let nextRetryAt;
|
|
118
|
-
let error;
|
|
119
|
-
if (result.success) {
|
|
120
|
-
status = "success";
|
|
121
|
-
nextRetryAt = null;
|
|
122
|
-
error = null;
|
|
123
|
-
} else if (result.dead) {
|
|
124
|
-
status = "dead";
|
|
125
|
-
nextRetryAt = null;
|
|
126
|
-
error = result.error ?? null;
|
|
127
|
-
} else {
|
|
128
|
-
status = "pending";
|
|
129
|
-
nextRetryAt = result.nextRetryAt ?? null;
|
|
130
|
-
error = result.error ?? null;
|
|
131
|
-
}
|
|
132
|
-
await this.engine.update(
|
|
133
|
-
this.objectName,
|
|
134
|
-
{
|
|
135
|
-
status,
|
|
136
|
-
attempts: (current.attempts ?? 0) + 1,
|
|
137
|
-
last_attempted_at: now,
|
|
138
|
-
claimed_by: null,
|
|
139
|
-
claimed_at: null,
|
|
140
|
-
response_code: result.httpStatus ?? null,
|
|
141
|
-
response_body: result.responseBody ?? null,
|
|
142
|
-
next_retry_at: nextRetryAt,
|
|
143
|
-
error,
|
|
144
|
-
updated_at: now
|
|
145
|
-
},
|
|
146
|
-
{ where: { id }, multi: false }
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
async list(filter) {
|
|
150
|
-
const rows = await this.engine.find(this.objectName, {
|
|
151
|
-
where: filter?.status ? { status: filter.status } : {}
|
|
152
|
-
});
|
|
153
|
-
return rows.map((r) => this.toDelivery(r));
|
|
154
|
-
}
|
|
155
|
-
toDelivery(r) {
|
|
156
|
-
return {
|
|
157
|
-
id: r.id,
|
|
158
|
-
webhookId: r.webhook_id,
|
|
159
|
-
eventId: r.event_id,
|
|
160
|
-
eventType: r.event_type,
|
|
161
|
-
url: r.url,
|
|
162
|
-
method: r.method ?? void 0,
|
|
163
|
-
headers: r.headers_json ? JSON.parse(r.headers_json) : void 0,
|
|
164
|
-
secret: r.secret ?? void 0,
|
|
165
|
-
timeoutMs: r.timeout_ms ?? void 0,
|
|
166
|
-
payload: JSON.parse(r.payload_json),
|
|
167
|
-
status: r.status,
|
|
168
|
-
attempts: r.attempts,
|
|
169
|
-
claimedBy: r.claimed_by ?? void 0,
|
|
170
|
-
claimedAt: r.claimed_at ?? void 0,
|
|
171
|
-
nextRetryAt: r.next_retry_at ?? void 0,
|
|
172
|
-
lastAttemptedAt: r.last_attempted_at ?? void 0,
|
|
173
|
-
responseCode: r.response_code ?? void 0,
|
|
174
|
-
responseBody: r.response_body ?? void 0,
|
|
175
|
-
error: r.error ?? void 0,
|
|
176
|
-
createdAt: r.created_at,
|
|
177
|
-
updatedAt: r.updated_at
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
};
|
|
2
|
+
SqlWebhookOutbox
|
|
3
|
+
} from "./chunk-BS2QTZH3.js";
|
|
4
|
+
import "./chunk-33LYZT7O.js";
|
|
181
5
|
export {
|
|
182
6
|
SqlWebhookOutbox
|
|
183
7
|
};
|
package/dist/sql-outbox.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sql-outbox.ts"],"sourcesContent":["// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { IDataEngine } from '@objectstack/spec/contracts';\nimport type {\n AckResult,\n ClaimOptions,\n DeliveryStatus,\n EnqueueInput,\n IWebhookOutbox,\n WebhookDelivery,\n} from './outbox.js';\nimport { hashPartition } from './partition.js';\nimport { SYS_WEBHOOK_DELIVERY } from './schema.js';\n\nexport interface SqlWebhookOutboxOptions {\n /**\n * Total partition count — MUST match the dispatcher's `partitionCount`.\n * Used at enqueue time to precompute `partition_key`.\n */\n partitionCount: number;\n /**\n * Object name to read/write. Defaults to `sys_webhook_delivery`. Override\n * only if you've registered the schema under a different name.\n */\n objectName?: string;\n}\n\ninterface DeliveryRow {\n id: string;\n webhook_id: string;\n event_id: string;\n event_type: string;\n url: string;\n method?: string | null;\n headers_json?: string | null;\n secret?: string | null;\n timeout_ms?: number | null;\n payload_json: string;\n partition_key: number;\n status: DeliveryStatus;\n attempts: number;\n claimed_by?: string | null;\n claimed_at?: number | null;\n next_retry_at?: number | null;\n last_attempted_at?: number | null;\n response_code?: number | null;\n response_body?: string | null;\n error?: string | null;\n created_at: number;\n updated_at: number;\n}\n\n/**\n * Durable `IWebhookOutbox` backed by ObjectQL — the production storage\n * impl. Works against any registered driver (SQL, Turso, Mongo, in-memory)\n * because everything goes through the driver-agnostic `IDataEngine` API.\n *\n * **Why no `FOR UPDATE SKIP LOCKED`?** ObjectQL is driver-agnostic — that\n * SQL feature is Postgres-only. We get equivalent safety from two layers:\n *\n * 1. `cluster.lock` held per partition by the dispatcher (the primary\n * mutex). One node owns one partition at a time → no two claimers.\n * 2. Atomic `UPDATE WHERE status='pending'` (the backup). Even if two\n * claimers slip through (e.g. admin reschedule + dispatcher), only\n * the first UPDATE matches each row.\n *\n * **Why precompute `partition_key` on enqueue?** ObjectQL has no\n * cross-driver `hash()` function in WHERE clauses. Storing the partition\n * as a column makes the claim query a plain indexed lookup.\n *\n * **Dedup race**: SELECT-then-INSERT has a tiny window where two\n * concurrent producers both miss the SELECT and both INSERT. The unique\n * index `(event_id, webhook_id)` on the table catches it — the second\n * INSERT errors, the producer ignores it. Receivers MUST be idempotent\n * on the `X-Objectstack-Delivery` header anyway.\n */\nexport class SqlWebhookOutbox implements IWebhookOutbox {\n private readonly objectName: string;\n private readonly partitionCount: number;\n\n constructor(\n private readonly engine: IDataEngine,\n opts: SqlWebhookOutboxOptions,\n ) {\n if (opts.partitionCount <= 0) {\n throw new Error('SqlWebhookOutbox: partitionCount must be > 0');\n }\n this.objectName = opts.objectName ?? SYS_WEBHOOK_DELIVERY;\n this.partitionCount = opts.partitionCount;\n }\n\n async enqueue(input: EnqueueInput): Promise<string> {\n // Cheap pre-check to absorb most duplicates without hitting the\n // unique-index error path. Race window with the INSERT below is\n // intentional and documented.\n const existing = await this.engine.findOne(this.objectName, {\n where: { event_id: input.eventId, webhook_id: input.webhookId },\n fields: ['id'],\n });\n if (existing?.id) return existing.id as string;\n\n const id = randomUUID();\n const now = Date.now();\n const row: Omit<DeliveryRow, 'response_body' | 'error'> = {\n id,\n webhook_id: input.webhookId,\n event_id: input.eventId,\n event_type: input.eventType,\n url: input.url,\n method: input.method ?? 'POST',\n headers_json: input.headers ? JSON.stringify(input.headers) : undefined,\n secret: input.secret,\n timeout_ms: input.timeoutMs,\n payload_json: JSON.stringify(input.payload ?? null),\n partition_key: hashPartition(input.webhookId, this.partitionCount),\n status: 'pending',\n attempts: 0,\n created_at: now,\n updated_at: now,\n };\n try {\n await this.engine.insert(this.objectName, row);\n return id;\n } catch (err) {\n // Unique-index collision (dedup race) → look up the winner and\n // return its id. Any other error propagates.\n const winner = await this.engine.findOne(this.objectName, {\n where: { event_id: input.eventId, webhook_id: input.webhookId },\n fields: ['id'],\n });\n if (winner?.id) return winner.id as string;\n throw err;\n }\n }\n\n async claim(opts: ClaimOptions): Promise<WebhookDelivery[]> {\n const now = opts.now ?? Date.now();\n\n // 1. Reap stale in_flight rows — visibility-timeout recovery.\n await this.engine.update(\n this.objectName,\n { status: 'pending', claimed_by: null, claimed_at: null, updated_at: now },\n {\n where: {\n status: 'in_flight',\n claimed_at: { $lt: now - opts.claimTtlMs },\n },\n multi: true,\n },\n );\n\n // 2. Pick candidate ids.\n const partitionFilter = opts.partition\n ? { partition_key: opts.partition.index }\n : {};\n const candidates = await this.engine.find(this.objectName, {\n where: {\n status: 'pending',\n ...partitionFilter,\n // next_retry_at <= now OR null\n $or: [\n { next_retry_at: null },\n { next_retry_at: { $lte: now } },\n ],\n },\n fields: ['id'],\n // No orderBy for portability — drivers handle the natural insert order.\n limit: opts.limit,\n });\n if (candidates.length === 0) return [];\n\n const ids = (candidates as Array<{ id: string }>).map((c) => c.id);\n\n // 3. Atomic claim. WHERE status='pending' rejects any rows another\n // worker swept up between steps 2 and 3.\n await this.engine.update(\n this.objectName,\n {\n status: 'in_flight',\n claimed_by: opts.nodeId,\n claimed_at: now,\n updated_at: now,\n },\n {\n where: { id: { $in: ids }, status: 'pending' },\n multi: true,\n },\n );\n\n // 4. Read back the rows we actually own.\n const claimed = (await this.engine.find(this.objectName, {\n where: {\n id: { $in: ids },\n claimed_by: opts.nodeId,\n claimed_at: now,\n status: 'in_flight',\n },\n })) as DeliveryRow[];\n\n return claimed.map((r) => this.toDelivery(r));\n }\n\n async ack(id: string, result: AckResult): Promise<void> {\n // ObjectQL has no atomic $inc across drivers, so read-then-write.\n // Safe enough: ack is single-writer per row (only the claimer acks).\n const current = (await this.engine.findOne(this.objectName, {\n where: { id },\n fields: ['attempts'],\n })) as { attempts?: number } | null;\n if (!current) return;\n\n const now = Date.now();\n let status: DeliveryStatus;\n let nextRetryAt: number | null;\n let error: string | null;\n\n if (result.success) {\n status = 'success';\n nextRetryAt = null;\n error = null;\n } else if (result.dead) {\n status = 'dead';\n nextRetryAt = null;\n error = result.error ?? null;\n } else {\n status = 'pending';\n nextRetryAt = result.nextRetryAt ?? null;\n error = result.error ?? null;\n }\n\n await this.engine.update(\n this.objectName,\n {\n status,\n attempts: (current.attempts ?? 0) + 1,\n last_attempted_at: now,\n claimed_by: null,\n claimed_at: null,\n response_code: result.httpStatus ?? null,\n response_body: result.responseBody ?? null,\n next_retry_at: nextRetryAt,\n error,\n updated_at: now,\n },\n { where: { id }, multi: false },\n );\n }\n\n async list(filter?: { status?: DeliveryStatus }): Promise<WebhookDelivery[]> {\n const rows = (await this.engine.find(this.objectName, {\n where: filter?.status ? { status: filter.status } : {},\n })) as DeliveryRow[];\n return rows.map((r) => this.toDelivery(r));\n }\n\n private toDelivery(r: DeliveryRow): WebhookDelivery {\n return {\n id: r.id,\n webhookId: r.webhook_id,\n eventId: r.event_id,\n eventType: r.event_type,\n url: r.url,\n method: r.method ?? undefined,\n headers: r.headers_json ? JSON.parse(r.headers_json) : undefined,\n secret: r.secret ?? undefined,\n timeoutMs: r.timeout_ms ?? undefined,\n payload: JSON.parse(r.payload_json),\n status: r.status,\n attempts: r.attempts,\n claimedBy: r.claimed_by ?? undefined,\n claimedAt: r.claimed_at ?? undefined,\n nextRetryAt: r.next_retry_at ?? undefined,\n lastAttemptedAt: r.last_attempted_at ?? undefined,\n responseCode: r.response_code ?? undefined,\n responseBody: r.response_body ?? undefined,\n error: r.error ?? undefined,\n createdAt: r.created_at,\n updatedAt: r.updated_at,\n };\n }\n}\n"],"mappings":";;;;;;;;AAEA,SAAS,kBAAkB;AA2EpB,IAAM,mBAAN,MAAiD;AAAA,EAIpD,YACqB,QACjB,MACF;AAFmB;AAGjB,QAAI,KAAK,kBAAkB,GAAG;AAC1B,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAClE;AACA,SAAK,aAAa,KAAK,cAAc;AACrC,SAAK,iBAAiB,KAAK;AAAA,EAC/B;AAAA,EAEA,MAAM,QAAQ,OAAsC;AAIhD,UAAM,WAAW,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY;AAAA,MACxD,OAAO,EAAE,UAAU,MAAM,SAAS,YAAY,MAAM,UAAU;AAAA,MAC9D,QAAQ,CAAC,IAAI;AAAA,IACjB,CAAC;AACD,QAAI,UAAU,GAAI,QAAO,SAAS;AAElC,UAAM,KAAK,WAAW;AACtB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAoD;AAAA,MACtD;AAAA,MACA,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,YAAY,MAAM;AAAA,MAClB,KAAK,MAAM;AAAA,MACX,QAAQ,MAAM,UAAU;AAAA,MACxB,cAAc,MAAM,UAAU,KAAK,UAAU,MAAM,OAAO,IAAI;AAAA,MAC9D,QAAQ,MAAM;AAAA,MACd,YAAY,MAAM;AAAA,MAClB,cAAc,KAAK,UAAU,MAAM,WAAW,IAAI;AAAA,MAClD,eAAe,cAAc,MAAM,WAAW,KAAK,cAAc;AAAA,MACjE,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,YAAY;AAAA,IAChB;AACA,QAAI;AACA,YAAM,KAAK,OAAO,OAAO,KAAK,YAAY,GAAG;AAC7C,aAAO;AAAA,IACX,SAAS,KAAK;AAGV,YAAM,SAAS,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY;AAAA,QACtD,OAAO,EAAE,UAAU,MAAM,SAAS,YAAY,MAAM,UAAU;AAAA,QAC9D,QAAQ,CAAC,IAAI;AAAA,MACjB,CAAC;AACD,UAAI,QAAQ,GAAI,QAAO,OAAO;AAC9B,YAAM;AAAA,IACV;AAAA,EACJ;AAAA,EAEA,MAAM,MAAM,MAAgD;AACxD,UAAM,MAAM,KAAK,OAAO,KAAK,IAAI;AAGjC,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL,EAAE,QAAQ,WAAW,YAAY,MAAM,YAAY,MAAM,YAAY,IAAI;AAAA,MACzE;AAAA,QACI,OAAO;AAAA,UACH,QAAQ;AAAA,UACR,YAAY,EAAE,KAAK,MAAM,KAAK,WAAW;AAAA,QAC7C;AAAA,QACA,OAAO;AAAA,MACX;AAAA,IACJ;AAGA,UAAM,kBAAkB,KAAK,YACvB,EAAE,eAAe,KAAK,UAAU,MAAM,IACtC,CAAC;AACP,UAAM,aAAa,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY;AAAA,MACvD,OAAO;AAAA,QACH,QAAQ;AAAA,QACR,GAAG;AAAA;AAAA,QAEH,KAAK;AAAA,UACD,EAAE,eAAe,KAAK;AAAA,UACtB,EAAE,eAAe,EAAE,MAAM,IAAI,EAAE;AAAA,QACnC;AAAA,MACJ;AAAA,MACA,QAAQ,CAAC,IAAI;AAAA;AAAA,MAEb,OAAO,KAAK;AAAA,IAChB,CAAC;AACD,QAAI,WAAW,WAAW,EAAG,QAAO,CAAC;AAErC,UAAM,MAAO,WAAqC,IAAI,CAAC,MAAM,EAAE,EAAE;AAIjE,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL;AAAA,QACI,QAAQ;AAAA,QACR,YAAY,KAAK;AAAA,QACjB,YAAY;AAAA,QACZ,YAAY;AAAA,MAChB;AAAA,MACA;AAAA,QACI,OAAO,EAAE,IAAI,EAAE,KAAK,IAAI,GAAG,QAAQ,UAAU;AAAA,QAC7C,OAAO;AAAA,MACX;AAAA,IACJ;AAGA,UAAM,UAAW,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY;AAAA,MACrD,OAAO;AAAA,QACH,IAAI,EAAE,KAAK,IAAI;AAAA,QACf,YAAY,KAAK;AAAA,QACjB,YAAY;AAAA,QACZ,QAAQ;AAAA,MACZ;AAAA,IACJ,CAAC;AAED,WAAO,QAAQ,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;AAAA,EAChD;AAAA,EAEA,MAAM,IAAI,IAAY,QAAkC;AAGpD,UAAM,UAAW,MAAM,KAAK,OAAO,QAAQ,KAAK,YAAY;AAAA,MACxD,OAAO,EAAE,GAAG;AAAA,MACZ,QAAQ,CAAC,UAAU;AAAA,IACvB,CAAC;AACD,QAAI,CAAC,QAAS;AAEd,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI;AACJ,QAAI;AACJ,QAAI;AAEJ,QAAI,OAAO,SAAS;AAChB,eAAS;AACT,oBAAc;AACd,cAAQ;AAAA,IACZ,WAAW,OAAO,MAAM;AACpB,eAAS;AACT,oBAAc;AACd,cAAQ,OAAO,SAAS;AAAA,IAC5B,OAAO;AACH,eAAS;AACT,oBAAc,OAAO,eAAe;AACpC,cAAQ,OAAO,SAAS;AAAA,IAC5B;AAEA,UAAM,KAAK,OAAO;AAAA,MACd,KAAK;AAAA,MACL;AAAA,QACI;AAAA,QACA,WAAW,QAAQ,YAAY,KAAK;AAAA,QACpC,mBAAmB;AAAA,QACnB,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,eAAe,OAAO,cAAc;AAAA,QACpC,eAAe,OAAO,gBAAgB;AAAA,QACtC,eAAe;AAAA,QACf;AAAA,QACA,YAAY;AAAA,MAChB;AAAA,MACA,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM;AAAA,IAClC;AAAA,EACJ;AAAA,EAEA,MAAM,KAAK,QAAkE;AACzE,UAAM,OAAQ,MAAM,KAAK,OAAO,KAAK,KAAK,YAAY;AAAA,MAClD,OAAO,QAAQ,SAAS,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,IACzD,CAAC;AACD,WAAO,KAAK,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;AAAA,EAC7C;AAAA,EAEQ,WAAW,GAAiC;AAChD,WAAO;AAAA,MACH,IAAI,EAAE;AAAA,MACN,WAAW,EAAE;AAAA,MACb,SAAS,EAAE;AAAA,MACX,WAAW,EAAE;AAAA,MACb,KAAK,EAAE;AAAA,MACP,QAAQ,EAAE,UAAU;AAAA,MACpB,SAAS,EAAE,eAAe,KAAK,MAAM,EAAE,YAAY,IAAI;AAAA,MACvD,QAAQ,EAAE,UAAU;AAAA,MACpB,WAAW,EAAE,cAAc;AAAA,MAC3B,SAAS,KAAK,MAAM,EAAE,YAAY;AAAA,MAClC,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE;AAAA,MACZ,WAAW,EAAE,cAAc;AAAA,MAC3B,WAAW,EAAE,cAAc;AAAA,MAC3B,aAAa,EAAE,iBAAiB;AAAA,MAChC,iBAAiB,EAAE,qBAAqB;AAAA,MACxC,cAAc,EAAE,iBAAiB;AAAA,MACjC,cAAc,EAAE,iBAAiB;AAAA,MACjC,OAAO,EAAE,SAAS;AAAA,MAClB,WAAW,EAAE;AAAA,MACb,WAAW,EAAE;AAAA,IACjB;AAAA,EACJ;AACJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|