@end-close/relay 0.12.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/COMPATIBILITY.md +39 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/config/schema.d.ts +993 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +156 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/crypto/at-rest.d.ts +6 -0
- package/dist/crypto/at-rest.d.ts.map +1 -0
- package/dist/crypto/at-rest.js +19 -0
- package/dist/crypto/at-rest.js.map +1 -0
- package/dist/crypto/keys.d.ts +2 -0
- package/dist/crypto/keys.d.ts.map +1 -0
- package/dist/crypto/keys.js +12 -0
- package/dist/crypto/keys.js.map +1 -0
- package/dist/engine/codec.d.ts +12 -0
- package/dist/engine/codec.d.ts.map +1 -0
- package/dist/engine/codec.js +21 -0
- package/dist/engine/codec.js.map +1 -0
- package/dist/engine/hooks.d.ts +84 -0
- package/dist/engine/hooks.d.ts.map +1 -0
- package/dist/engine/hooks.js +34 -0
- package/dist/engine/hooks.js.map +1 -0
- package/dist/engine/ingest.d.ts +39 -0
- package/dist/engine/ingest.d.ts.map +1 -0
- package/dist/engine/ingest.js +131 -0
- package/dist/engine/ingest.js.map +1 -0
- package/dist/engine/memory-store.d.ts +35 -0
- package/dist/engine/memory-store.d.ts.map +1 -0
- package/dist/engine/memory-store.js +205 -0
- package/dist/engine/memory-store.js.map +1 -0
- package/dist/engine/relay.d.ts +116 -0
- package/dist/engine/relay.d.ts.map +1 -0
- package/dist/engine/relay.js +180 -0
- package/dist/engine/relay.js.map +1 -0
- package/dist/engine/secrets.d.ts +18 -0
- package/dist/engine/secrets.d.ts.map +1 -0
- package/dist/engine/secrets.js +42 -0
- package/dist/engine/secrets.js.map +1 -0
- package/dist/engine/settings.d.ts +29 -0
- package/dist/engine/settings.d.ts.map +1 -0
- package/dist/engine/settings.js +13 -0
- package/dist/engine/settings.js.map +1 -0
- package/dist/engine/store.d.ts +141 -0
- package/dist/engine/store.d.ts.map +1 -0
- package/dist/engine/store.js +52 -0
- package/dist/engine/store.js.map +1 -0
- package/dist/forward/backoff.d.ts +3 -0
- package/dist/forward/backoff.d.ts.map +1 -0
- package/dist/forward/backoff.js +10 -0
- package/dist/forward/backoff.js.map +1 -0
- package/dist/forward/dispatcher.d.ts +98 -0
- package/dist/forward/dispatcher.d.ts.map +1 -0
- package/dist/forward/dispatcher.js +444 -0
- package/dist/forward/dispatcher.js.map +1 -0
- package/dist/forward/endclose-client.d.ts +42 -0
- package/dist/forward/endclose-client.d.ts.map +1 -0
- package/dist/forward/endclose-client.js +76 -0
- package/dist/forward/endclose-client.js.map +1 -0
- package/dist/forward/enrich.d.ts +39 -0
- package/dist/forward/enrich.d.ts.map +1 -0
- package/dist/forward/enrich.js +77 -0
- package/dist/forward/enrich.js.map +1 -0
- package/dist/forward/mapper.d.ts +50 -0
- package/dist/forward/mapper.d.ts.map +1 -0
- package/dist/forward/mapper.js +141 -0
- package/dist/forward/mapper.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/adapters/generic-hmac.d.ts +3 -0
- package/dist/ingest/adapters/generic-hmac.d.ts.map +1 -0
- package/dist/ingest/adapters/generic-hmac.js +60 -0
- package/dist/ingest/adapters/generic-hmac.js.map +1 -0
- package/dist/ingest/adapters/payabli.d.ts +3 -0
- package/dist/ingest/adapters/payabli.d.ts.map +1 -0
- package/dist/ingest/adapters/payabli.js +57 -0
- package/dist/ingest/adapters/payabli.js.map +1 -0
- package/dist/ingest/adapters/registry.d.ts +4 -0
- package/dist/ingest/adapters/registry.d.ts.map +1 -0
- package/dist/ingest/adapters/registry.js +16 -0
- package/dist/ingest/adapters/registry.js.map +1 -0
- package/dist/ingest/adapters/types.d.ts +28 -0
- package/dist/ingest/adapters/types.d.ts.map +1 -0
- package/dist/ingest/adapters/types.js +5 -0
- package/dist/ingest/adapters/types.js.map +1 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +14 -0
- package/dist/logger.js.map +1 -0
- package/dist/mask/defaults.d.ts +7 -0
- package/dist/mask/defaults.d.ts.map +1 -0
- package/dist/mask/defaults.js +51 -0
- package/dist/mask/defaults.js.map +1 -0
- package/dist/mask/paths.d.ts +7 -0
- package/dist/mask/paths.d.ts.map +1 -0
- package/dist/mask/paths.js +36 -0
- package/dist/mask/paths.js.map +1 -0
- package/dist/util/payload-shape.d.ts +6 -0
- package/dist/util/payload-shape.d.ts.map +1 -0
- package/dist/util/payload-shape.js +19 -0
- package/dist/util/payload-shape.js.map +1 -0
- package/dist/util/strings.d.ts +3 -0
- package/dist/util/strings.d.ts.map +1 -0
- package/dist/util/strings.js +7 -0
- package/dist/util/strings.js.map +1 -0
- package/package.json +57 -0
- package/src/config/schema.ts +177 -0
- package/src/crypto/at-rest.ts +21 -0
- package/src/crypto/keys.ts +13 -0
- package/src/engine/codec.ts +30 -0
- package/src/engine/hooks.ts +76 -0
- package/src/engine/ingest.ts +194 -0
- package/src/engine/memory-store.ts +232 -0
- package/src/engine/relay.ts +294 -0
- package/src/engine/secrets.ts +51 -0
- package/src/engine/settings.ts +42 -0
- package/src/engine/store.ts +188 -0
- package/src/forward/backoff.ts +10 -0
- package/src/forward/dispatcher.ts +504 -0
- package/src/forward/endclose-client.ts +102 -0
- package/src/forward/enrich.ts +106 -0
- package/src/forward/mapper.ts +191 -0
- package/src/index.ts +47 -0
- package/src/ingest/adapters/generic-hmac.ts +70 -0
- package/src/ingest/adapters/payabli.ts +67 -0
- package/src/ingest/adapters/registry.ts +21 -0
- package/src/ingest/adapters/types.ts +33 -0
- package/src/logger.ts +26 -0
- package/src/mask/defaults.ts +56 -0
- package/src/mask/paths.ts +36 -0
- package/src/util/payload-shape.ts +19 -0
- package/src/util/strings.ts +7 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { storeOp, } from '../engine/store.js';
|
|
2
|
+
import { RelayHooks } from '../engine/hooks.js';
|
|
3
|
+
import { noopLogger } from '../logger.js';
|
|
4
|
+
import { jsonTopLevelKeys } from '../util/payload-shape.js';
|
|
5
|
+
import { sleep } from '../util/strings.js';
|
|
6
|
+
import { nextAttemptAt } from './backoff.js';
|
|
7
|
+
import { mapEvent, MappingError } from './mapper.js';
|
|
8
|
+
import { EnrichmentError, validateEnrichedValue, withTimeout, } from './enrich.js';
|
|
9
|
+
import { PermanentHttpError, TransientHttpError, } from './endclose-client.js';
|
|
10
|
+
const IN_REQUEST_RETRIES = 2;
|
|
11
|
+
const RESULT_POLL_ATTEMPTS = 5;
|
|
12
|
+
const RESULT_POLL_DELAY_MS = 1000;
|
|
13
|
+
const PRUNE_INTERVAL_MS = 3600_000;
|
|
14
|
+
const PRUNE_BATCH = 50;
|
|
15
|
+
const PRUNE_YIELD_MS = 25;
|
|
16
|
+
export class Dispatcher {
|
|
17
|
+
deps;
|
|
18
|
+
log;
|
|
19
|
+
hooks;
|
|
20
|
+
running = false;
|
|
21
|
+
wakeRequested = false;
|
|
22
|
+
// Rows this instance left 'delivering' (a crash, or a failed release) are reclaimed
|
|
23
|
+
// by owner on the next cycle regardless of lease. Expired leases left by anyone are
|
|
24
|
+
// swept every recoverIntervalMs, so a long-lived instance also covers crashed peers.
|
|
25
|
+
reclaimOwner;
|
|
26
|
+
lastSweepAt = 0;
|
|
27
|
+
inFlight = Promise.resolve();
|
|
28
|
+
pruneWork = Promise.resolve();
|
|
29
|
+
timer;
|
|
30
|
+
pruneTimer;
|
|
31
|
+
stopped = () => !this.running;
|
|
32
|
+
constructor(deps) {
|
|
33
|
+
this.deps = deps;
|
|
34
|
+
this.log = deps.logger ?? noopLogger;
|
|
35
|
+
this.hooks = deps.hooks ?? new RelayHooks();
|
|
36
|
+
this.reclaimOwner = deps.instanceId;
|
|
37
|
+
}
|
|
38
|
+
start() {
|
|
39
|
+
if (this.running)
|
|
40
|
+
return;
|
|
41
|
+
this.running = true;
|
|
42
|
+
this.deps.signal.on('event', this.onSignal);
|
|
43
|
+
this.timer = setInterval(() => this.wake(), this.deps.dispatch.pollIntervalMs);
|
|
44
|
+
if (this.deps.retention) {
|
|
45
|
+
this.pruneTimer = setInterval(() => this.schedulePrune(), PRUNE_INTERVAL_MS);
|
|
46
|
+
this.schedulePrune();
|
|
47
|
+
}
|
|
48
|
+
this.wake();
|
|
49
|
+
}
|
|
50
|
+
onSignal = () => this.wake();
|
|
51
|
+
/**
|
|
52
|
+
* Run exactly one dispatch cycle, serialised with the background loop if it is running.
|
|
53
|
+
* Errors propagate to the caller (they are also emitted as hooks).
|
|
54
|
+
*/
|
|
55
|
+
runOnce() {
|
|
56
|
+
const run = this.inFlight.then(() => this.cycle().catch((err) => {
|
|
57
|
+
this.reportCycleError(err);
|
|
58
|
+
throw err;
|
|
59
|
+
}));
|
|
60
|
+
this.inFlight = run.then(() => { }, () => { });
|
|
61
|
+
return run;
|
|
62
|
+
}
|
|
63
|
+
/** Run retention pruning to completion, serialised with any scheduled prune. */
|
|
64
|
+
pruneNow() {
|
|
65
|
+
const run = this.pruneWork.then(() => this.runPrune());
|
|
66
|
+
this.pruneWork = run.then(() => { }, () => { });
|
|
67
|
+
return run;
|
|
68
|
+
}
|
|
69
|
+
/** Stop accepting new work and wait for the in-flight cycle and prune to drain. */
|
|
70
|
+
async stop() {
|
|
71
|
+
this.running = false;
|
|
72
|
+
this.deps.signal.off('event', this.onSignal);
|
|
73
|
+
if (this.timer)
|
|
74
|
+
clearInterval(this.timer);
|
|
75
|
+
if (this.pruneTimer)
|
|
76
|
+
clearInterval(this.pruneTimer);
|
|
77
|
+
await this.inFlight;
|
|
78
|
+
await this.pruneWork;
|
|
79
|
+
}
|
|
80
|
+
wake() {
|
|
81
|
+
if (!this.running)
|
|
82
|
+
return;
|
|
83
|
+
this.wakeRequested = true;
|
|
84
|
+
this.inFlight = this.inFlight.then(async () => {
|
|
85
|
+
while (this.wakeRequested && this.running) {
|
|
86
|
+
this.wakeRequested = false;
|
|
87
|
+
try {
|
|
88
|
+
await this.cycle(this.stopped);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
this.reportCycleError(err);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
reportCycleError(err) {
|
|
97
|
+
const op = storeOp(err);
|
|
98
|
+
this.log.error('dispatch cycle failed', { error: err.message, ...(op ? { op } : {}) });
|
|
99
|
+
this.hooks.emit('error', { kind: 'dispatch_cycle', error: err, ...(op ? { op } : {}) });
|
|
100
|
+
}
|
|
101
|
+
async cycle(abort = () => false) {
|
|
102
|
+
const now = new Date().toISOString();
|
|
103
|
+
const summary = { delivered: 0, retried: 0, parked: 0, due: [], skipped: [], halted: false };
|
|
104
|
+
const sweepDue = Date.now() - this.lastSweepAt >= this.deps.dispatch.recoverIntervalMs;
|
|
105
|
+
if (this.reclaimOwner !== undefined || sweepDue) {
|
|
106
|
+
try {
|
|
107
|
+
// The owner clause is only safe as a one-off (nothing of ours is in flight yet);
|
|
108
|
+
// the periodic sweep relies on lease expiry alone.
|
|
109
|
+
const recovered = await this.deps.store.recoverDelivering(now, this.reclaimOwner);
|
|
110
|
+
this.reclaimOwner = undefined;
|
|
111
|
+
if (recovered > 0)
|
|
112
|
+
this.log.info('recovered in-flight events', { count: recovered });
|
|
113
|
+
if (sweepDue) {
|
|
114
|
+
// Same cadence for the other housekeeping write; parkAfterMs is days, not seconds.
|
|
115
|
+
await this.deps.store.parkExpired(now, this.deps.dispatch.parkAfterMs);
|
|
116
|
+
this.lastSweepAt = Date.now();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
this.log.error('recover delivering failed', { error: err.message });
|
|
121
|
+
this.hooks.emit('error', { kind: 'recover_delivering', error: err });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if ((await this.deps.control.getKillswitch()) !== 'none') {
|
|
125
|
+
summary.halted = true;
|
|
126
|
+
return summary; // pause/panic: buffer, do not forward
|
|
127
|
+
}
|
|
128
|
+
summary.due = await this.deps.store.routesWithDueEvents(now);
|
|
129
|
+
for (const routeId of summary.due) {
|
|
130
|
+
if (abort())
|
|
131
|
+
break;
|
|
132
|
+
const [route, paused] = await Promise.all([
|
|
133
|
+
this.deps.routes.get(routeId),
|
|
134
|
+
this.deps.control.isRoutePaused(routeId),
|
|
135
|
+
]);
|
|
136
|
+
// An unknown route is the stronger condition: a pause flag left behind by a removed
|
|
137
|
+
// route must not disguise it as merely paused.
|
|
138
|
+
if (!route) {
|
|
139
|
+
summary.skipped.push({ routeId, reason: 'unknown_route' });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (paused) {
|
|
143
|
+
summary.skipped.push({ routeId, reason: 'paused' });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const claimed = await this.deps.store.claimDue(routeId, now, this.deps.dispatch.batchMax, {
|
|
147
|
+
owner: this.deps.instanceId,
|
|
148
|
+
until: new Date(Date.now() + this.deps.dispatch.leaseMs).toISOString(),
|
|
149
|
+
});
|
|
150
|
+
if (claimed.length === 0)
|
|
151
|
+
continue;
|
|
152
|
+
try {
|
|
153
|
+
await this.deliverBatch(route, claimed, summary);
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
await this.releaseLeftover(claimed, 'left delivering after batch');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return summary;
|
|
160
|
+
}
|
|
161
|
+
async deliverBatch(route, claimed, counts) {
|
|
162
|
+
const routeId = route.id;
|
|
163
|
+
// Map and enrich each event independently; an event that cannot be mapped parks, one
|
|
164
|
+
// whose enrichment fails transiently retries, and neither wedges the batch.
|
|
165
|
+
const records = [];
|
|
166
|
+
const mapped = [];
|
|
167
|
+
for (const event of claimed) {
|
|
168
|
+
let payloadKeys;
|
|
169
|
+
let bodyBytes;
|
|
170
|
+
let stage = 'mapping';
|
|
171
|
+
try {
|
|
172
|
+
const plaintext = this.deps.codec.decode(event.payload, event.payload_iv);
|
|
173
|
+
bodyBytes = plaintext.length;
|
|
174
|
+
const payload = JSON.parse(plaintext.toString('utf8'));
|
|
175
|
+
payloadKeys = jsonTopLevelKeys(payload);
|
|
176
|
+
const result = mapEvent(route, payload, event.received_at, this.deps.maskingKey);
|
|
177
|
+
let { record } = result;
|
|
178
|
+
if (result.pending.length > 0) {
|
|
179
|
+
stage = 'enrichment';
|
|
180
|
+
record = await this.resolveEnrichments(event, payload, result);
|
|
181
|
+
}
|
|
182
|
+
records.push(record);
|
|
183
|
+
mapped.push(event);
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
if (err instanceof MappingError || err instanceof EnrichmentError) {
|
|
187
|
+
const reason = `${stage} failed: ${err.message}`;
|
|
188
|
+
await this.deps.store.markParked([event.id], reason);
|
|
189
|
+
this.recordParked([event], reason, counts);
|
|
190
|
+
this.log.warn(`event parked: ${stage} failed`, {
|
|
191
|
+
route: routeId,
|
|
192
|
+
event_id: event.event_id,
|
|
193
|
+
body_bytes: bodyBytes,
|
|
194
|
+
payload_keys: payloadKeys,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
else if (stage === 'enrichment') {
|
|
198
|
+
// The host's lookup threw or timed out: fixable on their side, so retry later.
|
|
199
|
+
await this.retryOne(event, `enrichment failed: ${err.message}`, counts);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (records.length === 0)
|
|
207
|
+
return;
|
|
208
|
+
try {
|
|
209
|
+
const accepted = await this.postWithRetries(records);
|
|
210
|
+
await this.settleResults(accepted.id, mapped, counts);
|
|
211
|
+
this.log.info('batch forwarded', {
|
|
212
|
+
route: routeId,
|
|
213
|
+
events: mapped.length,
|
|
214
|
+
bulk_request_id: accepted.id,
|
|
215
|
+
});
|
|
216
|
+
this.hooks.emit('batch.forwarded', { routeId, events: mapped.length, bulkRequestId: accepted.id });
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
if (err instanceof PermanentHttpError && (err.status === 400 || err.status === 422)) {
|
|
220
|
+
const reason = `${err.message}: ${err.body}`;
|
|
221
|
+
await this.deps.store.markParked(mapped.map((e) => e.id), reason);
|
|
222
|
+
this.recordParked(mapped, reason, counts);
|
|
223
|
+
this.log.error('batch parked: permanent rejection', { route: routeId, status: err.status });
|
|
224
|
+
this.hooks.emit('batch.parked', { routeId, status: err.status, events: mapped.length });
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
// Transient network failure, 5xx, or auth problem (fixable server-side or in
|
|
228
|
+
// config): schedule redelivery with backoff. attempts is per-event.
|
|
229
|
+
const maxAttempts = Math.max(...mapped.map((e) => e.attempts));
|
|
230
|
+
const next = nextAttemptAt(maxAttempts, this.deps.dispatch.backoffBaseMs, this.deps.dispatch.backoffCapMs);
|
|
231
|
+
const message = err.message;
|
|
232
|
+
await this.deps.store.markFailed(mapped.map((e) => e.id), next, message);
|
|
233
|
+
counts.retried += mapped.length;
|
|
234
|
+
this.hooks.emit('forward', { routeId, result: 'retried', count: mapped.length });
|
|
235
|
+
for (const e of mapped)
|
|
236
|
+
this.hooks.emit('settled', { id: e.id, routeId, result: 'retried', error: message });
|
|
237
|
+
this.log.warn('batch delivery failed, will retry', {
|
|
238
|
+
route: routeId,
|
|
239
|
+
events: mapped.length,
|
|
240
|
+
error: message,
|
|
241
|
+
next_attempt_at: next,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Fill the fields mapEvent left to the host, one enrichment at a time. Throws
|
|
248
|
+
* EnrichmentError for a bad reference or return value (park); rethrows anything else
|
|
249
|
+
* the host threw, including a timeout (retry).
|
|
250
|
+
*/
|
|
251
|
+
async resolveEnrichments(event, payload, mapped) {
|
|
252
|
+
const routeId = event.route_id;
|
|
253
|
+
const record = { ...mapped.record, metadata: { ...mapped.record.metadata } };
|
|
254
|
+
for (const { field, enrichment, input } of mapped.pending) {
|
|
255
|
+
const emit = (result, error) => this.hooks.emit('enrich', { routeId, id: event.id, field, enrichment, result, ...(error ? { error } : {}) });
|
|
256
|
+
const fn = this.deps.enrichments?.[enrichment];
|
|
257
|
+
if (!fn) {
|
|
258
|
+
const err = new EnrichmentError(`${field} references unknown enrichment "${enrichment}"`);
|
|
259
|
+
emit('rejected', err.message);
|
|
260
|
+
throw err;
|
|
261
|
+
}
|
|
262
|
+
const ctx = {
|
|
263
|
+
routeId,
|
|
264
|
+
source: event.source,
|
|
265
|
+
eventId: event.event_id,
|
|
266
|
+
eventType: event.event_type,
|
|
267
|
+
receivedAt: event.received_at,
|
|
268
|
+
field,
|
|
269
|
+
payload,
|
|
270
|
+
};
|
|
271
|
+
let value;
|
|
272
|
+
try {
|
|
273
|
+
const returned = await withTimeout(() => fn(input, ctx), this.deps.dispatch.enrichTimeoutMs);
|
|
274
|
+
value = validateEnrichedValue(returned, field);
|
|
275
|
+
if (field === 'description' && value !== undefined && typeof value !== 'string') {
|
|
276
|
+
throw new EnrichmentError(`enriched description must be a string`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
const message = err.message;
|
|
281
|
+
emit(err instanceof EnrichmentError ? 'rejected' : 'failed', `${enrichment}: ${message}`);
|
|
282
|
+
const wrapped = `${field} (${enrichment}): ${message}`;
|
|
283
|
+
throw err instanceof EnrichmentError
|
|
284
|
+
? new EnrichmentError(wrapped, { cause: err })
|
|
285
|
+
: new Error(wrapped, { cause: err });
|
|
286
|
+
}
|
|
287
|
+
if (value === undefined) {
|
|
288
|
+
emit('omitted');
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (field === 'description')
|
|
292
|
+
record.description = value;
|
|
293
|
+
else
|
|
294
|
+
record.metadata[field.slice('metadata.'.length)] = value;
|
|
295
|
+
emit('applied');
|
|
296
|
+
}
|
|
297
|
+
return record;
|
|
298
|
+
}
|
|
299
|
+
/** Schedule one event for redelivery with backoff, leaving the rest of its batch alone. */
|
|
300
|
+
async retryOne(event, message, counts) {
|
|
301
|
+
const next = nextAttemptAt(event.attempts, this.deps.dispatch.backoffBaseMs, this.deps.dispatch.backoffCapMs);
|
|
302
|
+
await this.deps.store.markFailed([event.id], next, message);
|
|
303
|
+
counts.retried += 1;
|
|
304
|
+
this.hooks.emit('forward', { routeId: event.route_id, result: 'retried', count: 1 });
|
|
305
|
+
this.hooks.emit('settled', { id: event.id, routeId: event.route_id, result: 'retried', error: message });
|
|
306
|
+
this.log.warn('event enrichment failed, will retry', {
|
|
307
|
+
route: event.route_id,
|
|
308
|
+
event_id: event.event_id,
|
|
309
|
+
error: message,
|
|
310
|
+
next_attempt_at: next,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
async postWithRetries(records) {
|
|
314
|
+
let lastErr;
|
|
315
|
+
for (let attempt = 0; attempt <= IN_REQUEST_RETRIES; attempt++) {
|
|
316
|
+
try {
|
|
317
|
+
return await this.deps.client.bulkCreateRecords(records);
|
|
318
|
+
}
|
|
319
|
+
catch (err) {
|
|
320
|
+
lastErr = err;
|
|
321
|
+
if (!(err instanceof TransientHttpError))
|
|
322
|
+
throw err;
|
|
323
|
+
await sleep(500 * (attempt + 1));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
throw lastErr;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Bulk processing is async server-side: poll briefly for per-row results. Rows the API
|
|
330
|
+
* rejected park; everything else is delivered. If results don't settle in time, mark
|
|
331
|
+
* delivered with the bulk_request_id recorded for later inspection.
|
|
332
|
+
*/
|
|
333
|
+
async settleResults(bulkRequestId, mapped, counts) {
|
|
334
|
+
const deliveredAt = new Date().toISOString();
|
|
335
|
+
for (let i = 0; i < RESULT_POLL_ATTEMPTS; i++) {
|
|
336
|
+
let status;
|
|
337
|
+
try {
|
|
338
|
+
status = await this.deps.client.getBulkRequest(bulkRequestId);
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
break; // polling is best-effort; the batch was accepted
|
|
342
|
+
}
|
|
343
|
+
if (status.status === 'pending' || status.status === 'processing') {
|
|
344
|
+
await sleep(RESULT_POLL_DELAY_MS);
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const failed = new Set((status.results ?? [])
|
|
348
|
+
.filter((r) => r.status === 'failed' || r.status === 'rejected')
|
|
349
|
+
.map((r) => r.external_id ?? String(r.index)));
|
|
350
|
+
if (failed.size === 0)
|
|
351
|
+
break;
|
|
352
|
+
const parked = [];
|
|
353
|
+
const ok = [];
|
|
354
|
+
mapped.forEach((event, index) => {
|
|
355
|
+
const key = extResultKey(event, index);
|
|
356
|
+
(failed.has(key.externalId) || failed.has(key.index) ? parked : ok).push(event);
|
|
357
|
+
});
|
|
358
|
+
const reason = 'rejected by End Close bulk processing';
|
|
359
|
+
await this.deps.store.markParked(parked.map((e) => e.id), reason);
|
|
360
|
+
await this.deps.store.markDelivered(ok.map((e) => e.id), deliveredAt, bulkRequestId);
|
|
361
|
+
this.recordDelivered(ok, deliveredAt, counts);
|
|
362
|
+
this.recordParked(parked, reason, counts);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
await this.deps.store.markDelivered(mapped.map((e) => e.id), deliveredAt, bulkRequestId);
|
|
366
|
+
this.recordDelivered(mapped, deliveredAt, counts);
|
|
367
|
+
}
|
|
368
|
+
recordDelivered(events, deliveredAt, counts) {
|
|
369
|
+
if (events.length === 0)
|
|
370
|
+
return;
|
|
371
|
+
counts.delivered += events.length;
|
|
372
|
+
this.hooks.emit('forward', { routeId: events[0].route_id, result: 'delivered', count: events.length });
|
|
373
|
+
for (const e of events) {
|
|
374
|
+
this.hooks.emit('delivered', { routeId: e.route_id, receivedAt: e.received_at, deliveredAt });
|
|
375
|
+
this.hooks.emit('settled', { id: e.id, routeId: e.route_id, result: 'delivered' });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
recordParked(events, reason, counts) {
|
|
379
|
+
if (events.length === 0)
|
|
380
|
+
return;
|
|
381
|
+
counts.parked += events.length;
|
|
382
|
+
this.hooks.emit('forward', { routeId: events[0].route_id, result: 'parked', count: events.length });
|
|
383
|
+
for (const e of events) {
|
|
384
|
+
this.hooks.emit('settled', { id: e.id, routeId: e.route_id, result: 'parked', error: reason });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
schedulePrune() {
|
|
388
|
+
// Prune must not block forwarding: failures are logged and emitted, never propagated.
|
|
389
|
+
this.pruneWork = this.pruneWork.then(() => this.runPrune(this.stopped)).catch(() => { });
|
|
390
|
+
}
|
|
391
|
+
/** Bounded batches with a yield between them so ingest and dispatch keep running. */
|
|
392
|
+
async runPrune(abort = () => false) {
|
|
393
|
+
const { retention } = this.deps;
|
|
394
|
+
if (!retention)
|
|
395
|
+
return { wiped: 0, deleted: 0 };
|
|
396
|
+
const now = new Date().toISOString();
|
|
397
|
+
let wiped = 0;
|
|
398
|
+
let deleted = 0;
|
|
399
|
+
try {
|
|
400
|
+
while (!abort()) {
|
|
401
|
+
const batch = await this.deps.store.pruneBatch(now, retention.deliveredDays, retention.ledgerDays, PRUNE_BATCH);
|
|
402
|
+
wiped += batch.wiped;
|
|
403
|
+
deleted += batch.deleted;
|
|
404
|
+
this.hooks.emit('prune', { wiped: batch.wiped, deleted: batch.deleted });
|
|
405
|
+
if (batch.wiped === 0 && batch.deleted === 0)
|
|
406
|
+
break;
|
|
407
|
+
await sleep(PRUNE_YIELD_MS);
|
|
408
|
+
}
|
|
409
|
+
if (wiped || deleted)
|
|
410
|
+
this.log.info('retention prune', { wiped, deleted });
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
this.log.error('retention prune failed', { error: err.message });
|
|
414
|
+
this.hooks.emit('error', { kind: 'prune', error: err });
|
|
415
|
+
throw err;
|
|
416
|
+
}
|
|
417
|
+
return { wiped, deleted };
|
|
418
|
+
}
|
|
419
|
+
async releaseLeftover(claimed, error) {
|
|
420
|
+
const ids = claimed.map((e) => e.id);
|
|
421
|
+
try {
|
|
422
|
+
const n = await this.deps.store.releaseDelivering(ids, new Date().toISOString(), error);
|
|
423
|
+
if (n > 0) {
|
|
424
|
+
this.log.warn('released leftover delivering events', { count: n, error });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
this.reclaimOwner = this.deps.instanceId;
|
|
429
|
+
this.log.error('failed to release leftover delivering events', {
|
|
430
|
+
error: err.message,
|
|
431
|
+
count: ids.length,
|
|
432
|
+
});
|
|
433
|
+
this.hooks.emit('error', { kind: 'recover_delivering', error: err });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function extResultKey(event, index) {
|
|
438
|
+
// Result rows may be keyed by external_id or by index depending on API version.
|
|
439
|
+
const externalId = event.event_id.includes(':')
|
|
440
|
+
? event.event_id.slice(event.event_id.indexOf(':') + 1)
|
|
441
|
+
: event.event_id;
|
|
442
|
+
return { externalId, index: String(index) };
|
|
443
|
+
}
|
|
444
|
+
//# sourceMappingURL=dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../../src/forward/dispatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EndCloseRecord } from './mapper.js';
|
|
2
|
+
export interface BulkRequestSummary {
|
|
3
|
+
id: string;
|
|
4
|
+
status: string;
|
|
5
|
+
total_items?: number;
|
|
6
|
+
failed_items?: number;
|
|
7
|
+
skipped_items?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface BulkResultItem {
|
|
10
|
+
index?: number;
|
|
11
|
+
external_id?: string;
|
|
12
|
+
status: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare class TransientHttpError extends Error {
|
|
16
|
+
readonly status?: number | undefined;
|
|
17
|
+
constructor(message: string, status?: number | undefined);
|
|
18
|
+
}
|
|
19
|
+
export declare class PermanentHttpError extends Error {
|
|
20
|
+
readonly status: number;
|
|
21
|
+
readonly body: string;
|
|
22
|
+
constructor(message: string, status: number, body: string);
|
|
23
|
+
}
|
|
24
|
+
export declare class EndCloseClient {
|
|
25
|
+
private baseUrl;
|
|
26
|
+
private apiKey;
|
|
27
|
+
private fetchImpl;
|
|
28
|
+
constructor(baseUrl: string, apiKey: string, fetchImpl?: typeof fetch);
|
|
29
|
+
/** Deterministic per-batch idempotency key so a retried POST returns the original bulk request. */
|
|
30
|
+
static idempotencyKey(records: EndCloseRecord[]): string;
|
|
31
|
+
bulkCreateRecords(records: EndCloseRecord[]): Promise<BulkRequestSummary>;
|
|
32
|
+
getBulkRequest(id: string): Promise<BulkRequestSummary & {
|
|
33
|
+
results?: BulkResultItem[];
|
|
34
|
+
}>;
|
|
35
|
+
/** Operational call-home. Failures must never affect ingest or dispatch. */
|
|
36
|
+
postRelayEvent(event: {
|
|
37
|
+
name: string;
|
|
38
|
+
properties: Record<string, unknown>;
|
|
39
|
+
}): Promise<void>;
|
|
40
|
+
private request;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=endclose-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endclose-client.d.ts","sourceRoot":"","sources":["../../src/forward/endclose-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAGzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;gBADxB,OAAO,EAAE,MAAM,EACN,MAAM,CAAC,EAAE,MAAM,YAAA;CAI3B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAGzC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAFrB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAIxB;AAID,qBAAa,cAAc;IAEvB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,SAAS;gBAFT,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,OAAO,KAAa;IAGzC,mGAAmG;IACnG,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,MAAM;IAMlD,iBAAiB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAOzE,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG;QAAE,OAAO,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC;IAM9F,4EAA4E;IACtE,cAAc,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAInF,OAAO;CA8BtB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export class TransientHttpError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
constructor(message, status) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.status = status;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class PermanentHttpError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
body;
|
|
12
|
+
constructor(message, status, body) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.body = body;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const TRANSIENT_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
|
|
19
|
+
export class EndCloseClient {
|
|
20
|
+
baseUrl;
|
|
21
|
+
apiKey;
|
|
22
|
+
fetchImpl;
|
|
23
|
+
constructor(baseUrl, apiKey, fetchImpl = fetch) {
|
|
24
|
+
this.baseUrl = baseUrl;
|
|
25
|
+
this.apiKey = apiKey;
|
|
26
|
+
this.fetchImpl = fetchImpl;
|
|
27
|
+
}
|
|
28
|
+
/** Deterministic per-batch idempotency key so a retried POST returns the original bulk request. */
|
|
29
|
+
static idempotencyKey(records) {
|
|
30
|
+
const h = createHash('sha256');
|
|
31
|
+
for (const r of records)
|
|
32
|
+
h.update(`${r.data_stream_key}:${r.external_id}\n`);
|
|
33
|
+
return 'relay-' + h.digest('hex').slice(0, 40);
|
|
34
|
+
}
|
|
35
|
+
async bulkCreateRecords(records) {
|
|
36
|
+
return this.request('POST', '/records/bulk', {
|
|
37
|
+
idempotencyKey: EndCloseClient.idempotencyKey(records),
|
|
38
|
+
body: { on_conflict: 'skip', records },
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async getBulkRequest(id) {
|
|
42
|
+
return this.request('GET', `/bulk_requests/${id}`, {});
|
|
43
|
+
}
|
|
44
|
+
/** Operational call-home. Failures must never affect ingest or dispatch. */
|
|
45
|
+
async postRelayEvent(event) {
|
|
46
|
+
await this.request('POST', '/relays/events', { body: event, timeoutMs: 5_000 });
|
|
47
|
+
}
|
|
48
|
+
async request(method, path, opts) {
|
|
49
|
+
const headers = {
|
|
50
|
+
'X-API-KEY': this.apiKey,
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
};
|
|
53
|
+
if (opts.idempotencyKey)
|
|
54
|
+
headers['Idempotency-Key'] = opts.idempotencyKey;
|
|
55
|
+
let res;
|
|
56
|
+
try {
|
|
57
|
+
res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
58
|
+
method,
|
|
59
|
+
headers,
|
|
60
|
+
body: opts.body === undefined ? null : JSON.stringify(opts.body),
|
|
61
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 30_000),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
throw new TransientHttpError(`network error: ${err.message}`);
|
|
66
|
+
}
|
|
67
|
+
const text = await res.text();
|
|
68
|
+
if (res.ok)
|
|
69
|
+
return text ? JSON.parse(text) : {};
|
|
70
|
+
if (TRANSIENT_STATUSES.has(res.status)) {
|
|
71
|
+
throw new TransientHttpError(`HTTP ${res.status}`, res.status);
|
|
72
|
+
}
|
|
73
|
+
throw new PermanentHttpError(`HTTP ${res.status}`, res.status, text.slice(0, 500));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=endclose-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endclose-client.js","sourceRoot":"","sources":["../../src/forward/endclose-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Json } from '../mask/paths.js';
|
|
2
|
+
export interface EnrichContext {
|
|
3
|
+
routeId: string;
|
|
4
|
+
/** The route's processor adapter name, e.g. "payabli". */
|
|
5
|
+
source: string;
|
|
6
|
+
/** Adapter-derived id, e.g. "ApprovedPayment:txn_1" (or "sha256:…" when unknown). */
|
|
7
|
+
eventId: string;
|
|
8
|
+
eventType: string | null;
|
|
9
|
+
receivedAt: string;
|
|
10
|
+
/** Output field being computed: "metadata.<key>" or "description". */
|
|
11
|
+
field: string;
|
|
12
|
+
/** The decrypted webhook payload, for inputs beyond the `source` value. */
|
|
13
|
+
payload: Json;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compute the value for one enriched field from the value at its `source` (after any
|
|
17
|
+
* transforms). Return `undefined` to omit the field and still send the record. Throw to
|
|
18
|
+
* retry the whole event later with backoff (the host's database was unavailable); throw
|
|
19
|
+
* `EnrichmentError` to park it instead (the input is bad and a retry cannot help).
|
|
20
|
+
*/
|
|
21
|
+
export type Enrichment = (input: Json, ctx: EnrichContext) => Json | undefined | Promise<Json | undefined>;
|
|
22
|
+
/** Enrichment cannot be applied to this event. Parks the event; never retried. */
|
|
23
|
+
export declare class EnrichmentError extends Error {
|
|
24
|
+
}
|
|
25
|
+
export declare class EnrichTimeoutError extends Error {
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check what an enrichment returned before it may enter a record: JSON only (no
|
|
29
|
+
* undefined-in-objects, functions, NaN, class instances), no sensitive key names at any
|
|
30
|
+
* depth, and the hard denylist applied to every string. `undefined` means "omit".
|
|
31
|
+
*/
|
|
32
|
+
export declare function validateEnrichedValue(value: unknown, field: string): Json | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Run a host callback under a deadline. A synchronous throw becomes a rejection; the timer
|
|
35
|
+
* is cleared on settle so it never keeps the process alive. A timed-out call is abandoned,
|
|
36
|
+
* not cancelled: hosts must treat enrichments as reads that may run more than once.
|
|
37
|
+
*/
|
|
38
|
+
export declare function withTimeout<T>(run: () => T | Promise<T>, ms: number): Promise<T>;
|
|
39
|
+
//# sourceMappingURL=enrich.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enrich.d.ts","sourceRoot":"","sources":["../../src/forward/enrich.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAO5C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAA;IACd,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAA;IACb,2EAA2E;IAC3E,OAAO,EAAE,IAAI,CAAA;CACd;AAED;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,aAAa,KAAK,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAAA;AAE1G,kFAAkF;AAClF,qBAAa,eAAgB,SAAQ,KAAK;CAAG;AAE7C,qBAAa,kBAAmB,SAAQ,KAAK;CAAG;AAwChD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAIrF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAgBhF"}
|