@stratusagent/gateway 0.13.7 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/event-intake.d.ts +159 -0
- package/dist/event-intake.d.ts.map +1 -0
- package/dist/event-intake.js +445 -0
- package/dist/event-intake.js.map +1 -0
- package/dist/events.d.ts +256 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +462 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +108 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +451 -24
- package/dist/index.js.map +1 -1
- package/dist/schedules.d.ts +84 -1
- package/dist/schedules.d.ts.map +1 -1
- package/dist/schedules.js +550 -49
- package/dist/schedules.js.map +1 -1
- package/dist/sessions.d.ts +15 -7
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +89 -19
- package/dist/sessions.js.map +1 -1
- package/package.json +7 -7
package/dist/events.js
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { MAX_IDEMPOTENCY_KEY_LENGTH } from '@stratusagent/core';
|
|
3
|
+
import { openFleetDatabase, SCHEDULE_SESSION_ID_PREFIX, utf8Prefix } from "./schedules.js";
|
|
4
|
+
// Event triggers' durable half (card 590, agent-plans
|
|
5
|
+
// roadmap/590-event-triggers.md): deliveries, dedup tombstones and firings,
|
|
6
|
+
// in fleet.db next to the schedules a trigger is. The listener decides what a
|
|
7
|
+
// request is and which triggers it matches; the dispatch side decides when a
|
|
8
|
+
// firing goes out. This file owns only what must survive a crash between
|
|
9
|
+
// those steps, and the keys that make a firing run at most once.
|
|
10
|
+
/** Accepted ids are remembered this long: longer than GitHub's 3-day manual redelivery. */
|
|
11
|
+
export const EVENT_DEDUP_WINDOW_MS = 7 * 24 * 60 * 60 * 1_000;
|
|
12
|
+
/** Delivery rows listed at most: the newest this many, within the dedup window. */
|
|
13
|
+
export const EVENT_DELIVERY_ROW_CAP = 10_000;
|
|
14
|
+
/** A firing's rendered event block, and so the event text a turn sees, is at most this many bytes. */
|
|
15
|
+
export const EVENT_BLOCK_MAX_BYTES = 16 * 1_024;
|
|
16
|
+
const OPEN_STATUSES = ['pending', 'held', 'rate_limited', 'firing'];
|
|
17
|
+
const OPEN_SQL = OPEN_STATUSES.map((status) => `'${status}'`).join(', ');
|
|
18
|
+
const sha256 = (text) => createHash('sha256').update(text).digest('hex');
|
|
19
|
+
/**
|
|
20
|
+
* What sessions, holds and rate limits group by: source + subject when the
|
|
21
|
+
* event has one, so every Codex round on one PR is one conversation, and
|
|
22
|
+
* source + id when it doesn't, so subjectless events never share a bucket.
|
|
23
|
+
* JSON, so no subject can be spelled to collide with an id key.
|
|
24
|
+
*/
|
|
25
|
+
export const eventCoalescingKey = (sourceId, event) => {
|
|
26
|
+
const subject = event.attributes.subject;
|
|
27
|
+
return subject !== undefined && subject.length > 0
|
|
28
|
+
? JSON.stringify([sourceId, 'subject', subject])
|
|
29
|
+
: JSON.stringify([sourceId, 'id', event.attributes.id ?? '']);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A trigger's session for one coalescing key. In the reserved `schedule:`
|
|
33
|
+
* namespace, which the gateway refuses to dispatch into from outside, so the
|
|
34
|
+
* only turns on it are the trigger's own firings.
|
|
35
|
+
*/
|
|
36
|
+
export const eventSessionId = (triggerId, coalescingKey) => `${SCHEDULE_SESSION_ID_PREFIX}${triggerId}:event-${sha256(coalescingKey).slice(0, 32)}`;
|
|
37
|
+
/**
|
|
38
|
+
* The gateway idempotency key for one trigger's firing on one event: a
|
|
39
|
+
* repeat never starts a second turn, and a turn whose process died continues
|
|
40
|
+
* once. Hashed when over the gateway's bound, as channel-slack does, because
|
|
41
|
+
* a sender picks the event id's length.
|
|
42
|
+
*/
|
|
43
|
+
export const eventDispatchKey = (triggerId, sourceId, eventId) => {
|
|
44
|
+
const key = `event:${triggerId}:${sourceId}:${eventId}`;
|
|
45
|
+
return key.length <= MAX_IDEMPOTENCY_KEY_LENGTH ? key : `event-sha256:${sha256(key)}`;
|
|
46
|
+
};
|
|
47
|
+
const ATTRIBUTE_ORDER = ['id', 'source', 'type', 'subject', 'time'];
|
|
48
|
+
/**
|
|
49
|
+
* What a firing's turn sees after the trigger's prompt. Rendered when the
|
|
50
|
+
* delivery is matched and stored with the firing, so a firing that goes out
|
|
51
|
+
* hours later needs neither the request body nor a second match. Attributes
|
|
52
|
+
* and data are JSON, so a newline in a sender's subject or comment can't pass
|
|
53
|
+
* for a line of the block. Capped at `EVENT_BLOCK_MAX_BYTES` with a note: the
|
|
54
|
+
* agent fetches the rest with its own tools (`gh` for a PR).
|
|
55
|
+
*/
|
|
56
|
+
export const renderEventBlock = (sourceId, event) => {
|
|
57
|
+
const attributes = {};
|
|
58
|
+
for (const name of ATTRIBUTE_ORDER) {
|
|
59
|
+
const value = event.attributes[name];
|
|
60
|
+
if (value !== undefined) {
|
|
61
|
+
attributes[name] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const name of Object.keys(event.attributes).sort()) {
|
|
65
|
+
if (name !== 'specversion' && !ATTRIBUTE_ORDER.includes(name)) {
|
|
66
|
+
attributes[name] = event.attributes[name];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const full = [
|
|
70
|
+
`Event from source "${sourceId}":`,
|
|
71
|
+
JSON.stringify(attributes),
|
|
72
|
+
'data:',
|
|
73
|
+
...(event.data !== undefined ? [JSON.stringify(event.data)] : []),
|
|
74
|
+
].join('\n');
|
|
75
|
+
const size = Buffer.byteLength(full);
|
|
76
|
+
if (size <= EVENT_BLOCK_MAX_BYTES) {
|
|
77
|
+
return full;
|
|
78
|
+
}
|
|
79
|
+
const note = `\n[cut at ${Math.round(EVENT_BLOCK_MAX_BYTES / 1_024)} KB of ${Math.round(size / 1_024)} KB; fetch the rest with your own tools]`;
|
|
80
|
+
return utf8Prefix(full, EVENT_BLOCK_MAX_BYTES - Buffer.byteLength(note)) + note;
|
|
81
|
+
};
|
|
82
|
+
const deliveryFrom = (row) => ({
|
|
83
|
+
seq: row.seq,
|
|
84
|
+
source: row.source,
|
|
85
|
+
eventId: row.event_id,
|
|
86
|
+
type: row.type,
|
|
87
|
+
...(row.subject !== '' ? { subject: row.subject } : {}),
|
|
88
|
+
status: row.status,
|
|
89
|
+
...(row.reason !== '' ? { reason: row.reason } : {}),
|
|
90
|
+
receivedAt: row.received_at,
|
|
91
|
+
});
|
|
92
|
+
const firingFrom = (row) => ({
|
|
93
|
+
id: row.id,
|
|
94
|
+
deliverySeq: row.delivery_seq,
|
|
95
|
+
triggerId: row.trigger_id,
|
|
96
|
+
agentId: row.agent_id,
|
|
97
|
+
source: row.source,
|
|
98
|
+
eventId: row.event_id,
|
|
99
|
+
coalescingKey: row.coalescing_key,
|
|
100
|
+
sessionId: row.session_id,
|
|
101
|
+
dispatchKey: row.dispatch_key,
|
|
102
|
+
...(row.batch_key !== '' ? { batchKey: row.batch_key } : {}),
|
|
103
|
+
status: row.status,
|
|
104
|
+
...(row.started_at !== '' ? { startedAt: row.started_at } : {}),
|
|
105
|
+
...(row.block !== null ? { block: row.block } : {}),
|
|
106
|
+
...(row.reason !== '' ? { reason: row.reason } : {}),
|
|
107
|
+
createdAt: row.created_at,
|
|
108
|
+
updatedAt: row.updated_at,
|
|
109
|
+
});
|
|
110
|
+
const minus = (iso, ms) => new Date(Date.parse(iso) - ms).toISOString();
|
|
111
|
+
/**
|
|
112
|
+
* Deliveries, dedup tombstones and firings, durable in `fleet.db`. Its own
|
|
113
|
+
* connection, like the schedule store's. `stratus events` reads through the
|
|
114
|
+
* running daemon instead of this file, because the requests refused before
|
|
115
|
+
* a row are counted only in the listener.
|
|
116
|
+
*
|
|
117
|
+
* Every method that changes more than one row does it in one transaction,
|
|
118
|
+
* and every one that moves a firing between statuses is all-or-nothing and
|
|
119
|
+
* conditional on the statuses it expects: a delivery and its firings are
|
|
120
|
+
* committed together before the sender hears 2xx, a batch is claimed whole
|
|
121
|
+
* before its dispatch, and a refused transition changes nothing.
|
|
122
|
+
*/
|
|
123
|
+
export class SqliteEventStore {
|
|
124
|
+
db;
|
|
125
|
+
constructor(filePath, options = {}) {
|
|
126
|
+
this.db = openFleetDatabase(filePath, options, `
|
|
127
|
+
CREATE TABLE IF NOT EXISTS event_deliveries (
|
|
128
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
129
|
+
source TEXT NOT NULL,
|
|
130
|
+
event_id TEXT NOT NULL,
|
|
131
|
+
type TEXT NOT NULL,
|
|
132
|
+
subject TEXT NOT NULL,
|
|
133
|
+
status TEXT NOT NULL,
|
|
134
|
+
reason TEXT NOT NULL,
|
|
135
|
+
received_at TEXT NOT NULL
|
|
136
|
+
);
|
|
137
|
+
CREATE INDEX IF NOT EXISTS event_deliveries_received ON event_deliveries (received_at);
|
|
138
|
+
CREATE TABLE IF NOT EXISTS event_seen (
|
|
139
|
+
source TEXT NOT NULL,
|
|
140
|
+
id_hash TEXT NOT NULL,
|
|
141
|
+
seen_at TEXT NOT NULL,
|
|
142
|
+
PRIMARY KEY (source, id_hash)
|
|
143
|
+
) WITHOUT ROWID;
|
|
144
|
+
CREATE TABLE IF NOT EXISTS event_firings (
|
|
145
|
+
id TEXT PRIMARY KEY,
|
|
146
|
+
delivery_seq INTEGER NOT NULL,
|
|
147
|
+
trigger_id TEXT NOT NULL,
|
|
148
|
+
agent_id TEXT NOT NULL,
|
|
149
|
+
source TEXT NOT NULL,
|
|
150
|
+
event_id TEXT NOT NULL,
|
|
151
|
+
coalescing_key TEXT NOT NULL,
|
|
152
|
+
session_id TEXT NOT NULL,
|
|
153
|
+
dispatch_key TEXT NOT NULL,
|
|
154
|
+
batch_key TEXT NOT NULL,
|
|
155
|
+
status TEXT NOT NULL,
|
|
156
|
+
started_at TEXT NOT NULL DEFAULT '',
|
|
157
|
+
block TEXT,
|
|
158
|
+
reason TEXT NOT NULL,
|
|
159
|
+
created_at TEXT NOT NULL,
|
|
160
|
+
updated_at TEXT NOT NULL
|
|
161
|
+
);
|
|
162
|
+
CREATE INDEX IF NOT EXISTS event_firings_status ON event_firings (status, created_at);
|
|
163
|
+
CREATE INDEX IF NOT EXISTS event_firings_trigger ON event_firings (trigger_id, status, updated_at);
|
|
164
|
+
CREATE INDEX IF NOT EXISTS event_firings_batch ON event_firings (batch_key);
|
|
165
|
+
`);
|
|
166
|
+
// A table made before `started_at` existed (an unreleased build of
|
|
167
|
+
// this feature) gains it; rows claimed then count from their update.
|
|
168
|
+
const columns = this.db.prepare('PRAGMA table_info(event_firings)').all();
|
|
169
|
+
if (!columns.some((column) => column.name === 'started_at')) {
|
|
170
|
+
this.db.exec("ALTER TABLE event_firings ADD COLUMN started_at TEXT NOT NULL DEFAULT ''");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
transaction(work) {
|
|
174
|
+
// IMMEDIATE takes the write lock up front, so the dedup check and the
|
|
175
|
+
// insert it guards can't interleave with another writer's.
|
|
176
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
177
|
+
try {
|
|
178
|
+
const result = work();
|
|
179
|
+
this.db.exec('COMMIT');
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
this.db.exec('ROLLBACK');
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
insertDelivery(delivery, status, reason = '') {
|
|
188
|
+
const result = this.db
|
|
189
|
+
.prepare('INSERT INTO event_deliveries (source, event_id, type, subject, status, reason, received_at) VALUES (?, ?, ?, ?, ?, ?, ?)')
|
|
190
|
+
.run(delivery.source, delivery.eventId, delivery.type ?? '', delivery.subject ?? '', status, reason, delivery.receivedAt);
|
|
191
|
+
return Number(result.lastInsertRowid);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Record a delivery whose signature checked out and whose body parsed:
|
|
195
|
+
* `duplicate` when its (source, id) was accepted within the dedup window,
|
|
196
|
+
* else `matched` with one `pending` firing per match, or `unmatched`. One
|
|
197
|
+
* write, made before the sender hears 2xx. Accepted deliveries leave a
|
|
198
|
+
* tombstone that outlives the row cap, so dedup never depends on it.
|
|
199
|
+
*/
|
|
200
|
+
accept(delivery) {
|
|
201
|
+
return this.transaction(() => {
|
|
202
|
+
// Insert, or revive a tombstone older than the window; zero changes
|
|
203
|
+
// means a live tombstone, so the id was seen.
|
|
204
|
+
const fresh = this.db
|
|
205
|
+
.prepare(`
|
|
206
|
+
INSERT INTO event_seen (source, id_hash, seen_at) VALUES (?, ?, ?)
|
|
207
|
+
ON CONFLICT (source, id_hash) DO UPDATE SET seen_at = excluded.seen_at WHERE seen_at < ?
|
|
208
|
+
`)
|
|
209
|
+
.run(delivery.source, sha256(delivery.eventId), delivery.receivedAt, minus(delivery.receivedAt, EVENT_DEDUP_WINDOW_MS))
|
|
210
|
+
.changes > 0;
|
|
211
|
+
if (!fresh) {
|
|
212
|
+
return { status: 'duplicate', seq: this.insertDelivery(delivery, 'duplicate'), firings: [] };
|
|
213
|
+
}
|
|
214
|
+
const status = delivery.firings.length > 0 ? 'matched' : 'unmatched';
|
|
215
|
+
const seq = this.insertDelivery(delivery, status);
|
|
216
|
+
const insert = this.db.prepare(`
|
|
217
|
+
INSERT INTO event_firings (id, delivery_seq, trigger_id, agent_id, source, event_id, coalescing_key,
|
|
218
|
+
session_id, dispatch_key, batch_key, status, block, reason, created_at, updated_at)
|
|
219
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, '', 'pending', ?, '', ?, ?)
|
|
220
|
+
`);
|
|
221
|
+
const firings = delivery.firings.map((firing) => {
|
|
222
|
+
const id = randomUUID();
|
|
223
|
+
insert.run(id, seq, firing.triggerId, firing.agentId, delivery.source, delivery.eventId, firing.coalescingKey, firing.sessionId, firing.dispatchKey, firing.block, delivery.receivedAt, delivery.receivedAt);
|
|
224
|
+
return {
|
|
225
|
+
id,
|
|
226
|
+
deliverySeq: seq,
|
|
227
|
+
triggerId: firing.triggerId,
|
|
228
|
+
agentId: firing.agentId,
|
|
229
|
+
source: delivery.source,
|
|
230
|
+
eventId: delivery.eventId,
|
|
231
|
+
coalescingKey: firing.coalescingKey,
|
|
232
|
+
sessionId: firing.sessionId,
|
|
233
|
+
dispatchKey: firing.dispatchKey,
|
|
234
|
+
status: 'pending',
|
|
235
|
+
block: firing.block,
|
|
236
|
+
createdAt: delivery.receivedAt,
|
|
237
|
+
updatedAt: delivery.receivedAt,
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
return { status, seq, firings };
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Record a signed delivery the listener refused (a form-encoded body, a
|
|
245
|
+
* malformed event). No tombstone: the sender can fix it and retry with the
|
|
246
|
+
* same id. Unsigned requests never reach here; they leave no row at all.
|
|
247
|
+
*/
|
|
248
|
+
refuse(delivery) {
|
|
249
|
+
const seq = this.insertDelivery(delivery, 'refused', delivery.reason);
|
|
250
|
+
return deliveryFrom(this.db.prepare('SELECT * FROM event_deliveries WHERE seq = ?').get(seq));
|
|
251
|
+
}
|
|
252
|
+
/** Delivery rows, newest first. */
|
|
253
|
+
deliveries(options = {}) {
|
|
254
|
+
const limit = options.limit ?? 100;
|
|
255
|
+
const rows = (options.source !== undefined
|
|
256
|
+
? this.db.prepare('SELECT * FROM event_deliveries WHERE source = ? ORDER BY seq DESC LIMIT ?').all(options.source, limit)
|
|
257
|
+
: this.db.prepare('SELECT * FROM event_deliveries ORDER BY seq DESC LIMIT ?').all(limit));
|
|
258
|
+
return rows.map(deliveryFrom);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Per source with any row: its last delivery, its last refused one, and
|
|
262
|
+
* counts since `sinceIso` (`stratus events`, `/health`). Grouped in the
|
|
263
|
+
* database, so the cost doesn't grow with the 7 days of rows kept.
|
|
264
|
+
*/
|
|
265
|
+
activity(sinceIso) {
|
|
266
|
+
const result = {};
|
|
267
|
+
const entry = (source) => (result[source] ??= { deliveries: {}, firings: {} });
|
|
268
|
+
const latest = this.db.prepare(`
|
|
269
|
+
SELECT d.* FROM event_deliveries d
|
|
270
|
+
JOIN (SELECT source, MAX(seq) AS seq FROM event_deliveries GROUP BY source) newest ON newest.seq = d.seq
|
|
271
|
+
`).all();
|
|
272
|
+
for (const row of latest) {
|
|
273
|
+
entry(row.source).lastDelivery = {
|
|
274
|
+
at: row.received_at,
|
|
275
|
+
type: row.type,
|
|
276
|
+
...(row.subject !== '' ? { subject: row.subject } : {}),
|
|
277
|
+
status: row.status,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const refused = this.db.prepare(`
|
|
281
|
+
SELECT d.* FROM event_deliveries d
|
|
282
|
+
JOIN (SELECT source, MAX(seq) AS seq FROM event_deliveries WHERE status = 'refused' GROUP BY source) newest ON newest.seq = d.seq
|
|
283
|
+
`).all();
|
|
284
|
+
for (const row of refused) {
|
|
285
|
+
entry(row.source).lastRefused = { at: row.received_at, reason: row.reason };
|
|
286
|
+
}
|
|
287
|
+
const deliveries = this.db.prepare(`
|
|
288
|
+
SELECT source, status, COUNT(*) AS n FROM event_deliveries WHERE received_at >= ? GROUP BY source, status
|
|
289
|
+
`).all(sinceIso);
|
|
290
|
+
for (const row of deliveries) {
|
|
291
|
+
entry(row.source).deliveries[row.status] = Number(row.n);
|
|
292
|
+
}
|
|
293
|
+
const firings = this.db.prepare(`
|
|
294
|
+
SELECT source, status, COUNT(*) AS n FROM event_firings
|
|
295
|
+
WHERE status IN (${OPEN_SQL}) OR updated_at >= ? GROUP BY source, status
|
|
296
|
+
`).all(sinceIso);
|
|
297
|
+
for (const row of firings) {
|
|
298
|
+
entry(row.source).firings[row.status] = Number(row.n);
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
firing(id) {
|
|
303
|
+
const row = this.db.prepare('SELECT * FROM event_firings WHERE id = ?').get(id);
|
|
304
|
+
return row ? firingFrom(row) : undefined;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Every open firing (`pending`, `held`, `rate_limited`, `firing`), oldest
|
|
308
|
+
* first, with its block and batch key: what the startup sweep recovers and
|
|
309
|
+
* what a hold releases. A `firing` row is a claim whose dispatch may or may
|
|
310
|
+
* not have reached the gateway; its batch goes out again as stored, and the
|
|
311
|
+
* idempotency key decides whether that starts or continues a turn.
|
|
312
|
+
*/
|
|
313
|
+
open() {
|
|
314
|
+
return this.db
|
|
315
|
+
.prepare(`SELECT * FROM event_firings WHERE status IN (${OPEN_SQL}) ORDER BY created_at ASC, delivery_seq ASC, rowid ASC`)
|
|
316
|
+
.all().map(firingFrom);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Run `update` on exactly `ids`, all or nothing: false, with nothing
|
|
320
|
+
* changed, unless `precheck` (inside the same transaction) passes and
|
|
321
|
+
* every row matched the update's own conditions.
|
|
322
|
+
*/
|
|
323
|
+
allOrNothing(ids, update, precheck = () => true) {
|
|
324
|
+
if (ids.length === 0 || new Set(ids).size !== ids.length) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
return this.transaction(() => {
|
|
329
|
+
if (!precheck()) {
|
|
330
|
+
throw new RefusedTransition();
|
|
331
|
+
}
|
|
332
|
+
for (const id of ids) {
|
|
333
|
+
if (update(id) === 0) {
|
|
334
|
+
throw new RefusedTransition();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
if (error instanceof RefusedTransition) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Claim firings to go out as one turn under `batchKey`, in one write
|
|
349
|
+
* before the dispatch. Either every row is free and they become a new
|
|
350
|
+
* batch, or `ids` is exactly an existing batch under exactly its key,
|
|
351
|
+
* which is how recovery resends one. A batch is fixed once claimed: no
|
|
352
|
+
* row joins it later, and it never changes key. False, with nothing
|
|
353
|
+
* changed, for anything else, or if any row is finished.
|
|
354
|
+
*
|
|
355
|
+
* With `triggerId`, also false unless that schedule row still exists, in
|
|
356
|
+
* the same transaction: `stratus schedules cancel` deletes from another
|
|
357
|
+
* process, and a trigger cancelled after the scan read it must not fire,
|
|
358
|
+
* as a timed schedule's conditional slot claim already guarantees.
|
|
359
|
+
*/
|
|
360
|
+
claim(ids, batchKey, nowIso, options = {}) {
|
|
361
|
+
const members = this.db.prepare('SELECT id FROM event_firings WHERE batch_key = ?');
|
|
362
|
+
const update = this.db.prepare(`
|
|
363
|
+
UPDATE event_firings
|
|
364
|
+
SET status = 'firing', batch_key = ?, updated_at = ?,
|
|
365
|
+
started_at = CASE WHEN started_at = '' THEN ? ELSE started_at END
|
|
366
|
+
WHERE id = ? AND status IN (${OPEN_SQL}) AND (batch_key = '' OR batch_key = ?)
|
|
367
|
+
`);
|
|
368
|
+
const wanted = new Set(ids);
|
|
369
|
+
return this.allOrNothing(ids, (id) => Number(update.run(batchKey, nowIso, nowIso, id, batchKey).changes), () => {
|
|
370
|
+
if (options.triggerId !== undefined
|
|
371
|
+
&& this.db.prepare('SELECT 1 FROM schedules WHERE id = ?').get(options.triggerId) === undefined) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
const existing = members.all(batchKey).map((row) => row.id);
|
|
375
|
+
return existing.length === 0 || (existing.length === wanted.size && existing.every((id) => wanted.has(id)));
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Hold open firings for quiet hours or paused intake (`held`) or over the
|
|
380
|
+
* trigger's hourly limit (`rate_limited`). The block and any batch key
|
|
381
|
+
* stay, so a held batch still goes out only as itself.
|
|
382
|
+
*/
|
|
383
|
+
hold(ids, status, nowIso) {
|
|
384
|
+
const update = this.db.prepare(`UPDATE event_firings SET status = ?, updated_at = ? WHERE id = ? AND status IN (${OPEN_SQL})`);
|
|
385
|
+
return this.allOrNothing(ids, (id) => Number(update.run(status, nowIso, id).changes));
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Finish claimed firings after their dispatch, clearing the block in the
|
|
389
|
+
* same write: from here the payload lives only in the session it fired.
|
|
390
|
+
* Only a `firing` row can finish, so nothing is marked fired that wasn't
|
|
391
|
+
* claimed first.
|
|
392
|
+
*/
|
|
393
|
+
finish(ids, outcome, nowIso) {
|
|
394
|
+
const reason = outcome.status === 'failed' ? outcome.reason : '';
|
|
395
|
+
const update = this.db.prepare(`
|
|
396
|
+
UPDATE event_firings SET status = ?, reason = ?, block = NULL, updated_at = ?
|
|
397
|
+
WHERE id = ? AND status = 'firing'
|
|
398
|
+
`);
|
|
399
|
+
return this.allOrNothing(ids, (id) => Number(update.run(outcome.status, reason, nowIso, id).changes));
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Retire open firings that will never go out: `superseded` by newer ones
|
|
403
|
+
* for the same key, or `expired` (held over 24 hours, or their trigger was
|
|
404
|
+
* cancelled). Clears their blocks. Returns how many were retired; a
|
|
405
|
+
* finished firing is left as it is.
|
|
406
|
+
*/
|
|
407
|
+
retire(ids, status, reason, nowIso) {
|
|
408
|
+
const update = this.db.prepare(`
|
|
409
|
+
UPDATE event_firings SET status = ?, reason = ?, block = NULL, updated_at = ?
|
|
410
|
+
WHERE id = ? AND status IN (${OPEN_SQL})
|
|
411
|
+
`);
|
|
412
|
+
return this.transaction(() => ids.reduce((count, id) => count + Number(update.run(status, reason, nowIso, id).changes), 0));
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Turns a trigger has started since `sinceIso`, for its hourly limit,
|
|
416
|
+
* counted from when each batch was first claimed (a two-hour turn doesn't
|
|
417
|
+
* hold the hour for two more): a batch running now, one that finished,
|
|
418
|
+
* and one that failed all count,
|
|
419
|
+
* since each was a turn started unattended. A failing provider would
|
|
420
|
+
* otherwise let every new event start another. A batch is one turn, so
|
|
421
|
+
* this counts batch keys, not rows; `exceptBatchKey` leaves out a batch
|
|
422
|
+
* being re-sent after a restart, which must not count against itself.
|
|
423
|
+
*/
|
|
424
|
+
turnsStartedSince(triggerId, sinceIso, exceptBatchKey = '') {
|
|
425
|
+
const row = this.db
|
|
426
|
+
.prepare(`
|
|
427
|
+
SELECT COUNT(DISTINCT batch_key) AS turns FROM event_firings
|
|
428
|
+
WHERE trigger_id = ? AND status IN ('firing', 'fired', 'failed')
|
|
429
|
+
AND (CASE WHEN started_at = '' THEN updated_at ELSE started_at END) >= ? AND batch_key != ?
|
|
430
|
+
`)
|
|
431
|
+
.get(triggerId, sinceIso, exceptBatchKey);
|
|
432
|
+
return row.turns;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Drop what the dedup window and the row cap no longer cover: tombstones
|
|
436
|
+
* and delivery rows past 7 days, delivery rows past the newest 10,000, and
|
|
437
|
+
* finished firings past 7 days. An open firing is never pruned: it holds
|
|
438
|
+
* the only copy of an event that hasn't gone out.
|
|
439
|
+
*/
|
|
440
|
+
prune(nowIso) {
|
|
441
|
+
const cutoff = minus(nowIso, EVENT_DEDUP_WINDOW_MS);
|
|
442
|
+
this.transaction(() => {
|
|
443
|
+
this.db.prepare('DELETE FROM event_seen WHERE seen_at < ?').run(cutoff);
|
|
444
|
+
this.db.prepare('DELETE FROM event_deliveries WHERE received_at < ?').run(cutoff);
|
|
445
|
+
this.db
|
|
446
|
+
.prepare('DELETE FROM event_deliveries WHERE seq <= (SELECT seq FROM event_deliveries ORDER BY seq DESC LIMIT 1 OFFSET ?)')
|
|
447
|
+
.run(EVENT_DELIVERY_ROW_CAP);
|
|
448
|
+
this.db.prepare(`DELETE FROM event_firings WHERE status NOT IN (${OPEN_SQL}) AND updated_at < ?`).run(cutoff);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
close() {
|
|
452
|
+
this.db.close();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/** Unwinds an all-or-nothing transition's transaction; never escapes the store. */
|
|
456
|
+
class RefusedTransition extends Error {
|
|
457
|
+
constructor() {
|
|
458
|
+
super('refused transition');
|
|
459
|
+
this.name = 'RefusedTransition';
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|