@nodii/telemetry 0.13.0 → 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.
Files changed (53) hide show
  1. package/dist/worker/checkpoint-store.d.ts +66 -0
  2. package/dist/worker/checkpoint-store.d.ts.map +1 -0
  3. package/dist/worker/checkpoint-store.js +131 -0
  4. package/dist/worker/checkpoint-store.js.map +1 -0
  5. package/dist/worker/dead-letter.d.ts +81 -0
  6. package/dist/worker/dead-letter.d.ts.map +1 -0
  7. package/dist/worker/dead-letter.js +165 -0
  8. package/dist/worker/dead-letter.js.map +1 -0
  9. package/dist/worker/dedupe-store.d.ts +46 -0
  10. package/dist/worker/dedupe-store.d.ts.map +1 -0
  11. package/dist/worker/dedupe-store.js +113 -0
  12. package/dist/worker/dedupe-store.js.map +1 -0
  13. package/dist/worker/index.d.ts +23 -0
  14. package/dist/worker/index.d.ts.map +1 -0
  15. package/dist/worker/index.js +46 -0
  16. package/dist/worker/index.js.map +1 -0
  17. package/dist/worker/observability-hooks.d.ts +99 -0
  18. package/dist/worker/observability-hooks.d.ts.map +1 -0
  19. package/dist/worker/observability-hooks.js +189 -0
  20. package/dist/worker/observability-hooks.js.map +1 -0
  21. package/dist/worker/outbox-dispatcher.d.ts +106 -0
  22. package/dist/worker/outbox-dispatcher.d.ts.map +1 -0
  23. package/dist/worker/outbox-dispatcher.js +285 -0
  24. package/dist/worker/outbox-dispatcher.js.map +1 -0
  25. package/dist/worker/replication-worker.d.ts +170 -0
  26. package/dist/worker/replication-worker.d.ts.map +1 -0
  27. package/dist/worker/replication-worker.js +576 -0
  28. package/dist/worker/replication-worker.js.map +1 -0
  29. package/dist/worker/signal-drain.d.ts +83 -0
  30. package/dist/worker/signal-drain.d.ts.map +1 -0
  31. package/dist/worker/signal-drain.js +131 -0
  32. package/dist/worker/signal-drain.js.map +1 -0
  33. package/dist/worker/single-active-lease.d.ts +180 -0
  34. package/dist/worker/single-active-lease.d.ts.map +1 -0
  35. package/dist/worker/single-active-lease.js +394 -0
  36. package/dist/worker/single-active-lease.js.map +1 -0
  37. package/dist/worker/streams-worker.d.ts +256 -0
  38. package/dist/worker/streams-worker.d.ts.map +1 -0
  39. package/dist/worker/streams-worker.js +621 -0
  40. package/dist/worker/streams-worker.js.map +1 -0
  41. package/dist/worker/sweeper-worker.d.ts +107 -0
  42. package/dist/worker/sweeper-worker.d.ts.map +1 -0
  43. package/dist/worker/sweeper-worker.js +245 -0
  44. package/dist/worker/sweeper-worker.js.map +1 -0
  45. package/dist/worker/tx.d.ts +39 -0
  46. package/dist/worker/tx.d.ts.map +1 -0
  47. package/dist/worker/tx.js +51 -0
  48. package/dist/worker/tx.js.map +1 -0
  49. package/dist/worker/worker-handle.d.ts +94 -0
  50. package/dist/worker/worker-handle.d.ts.map +1 -0
  51. package/dist/worker/worker-handle.js +15 -0
  52. package/dist/worker/worker-handle.js.map +1 -0
  53. package/package.json +6 -1
@@ -0,0 +1,285 @@
1
+ // D402 — OutboxDispatcher: the Pattern-3 PRODUCER drain.
2
+ //
3
+ // D402 (LOCKED): "OutboxDispatcher — Pattern-3 producer drain." It GENERALIZES
4
+ // the pattern shipped in `ts/outbox-dispatcher/src/drainer.ts` (which this lib
5
+ // does NOT edit — outbox-dispatcher gets re-based onto this substrate in
6
+ // Phase 2). The producer side of the both-sides-completeness rule: every outbox
7
+ // topic has a standardized producer (this) AND a standardized consumer
8
+ // (StreamsWorker / ReplicationWorker).
9
+ //
10
+ // ── Composition (which primitives, how) ──────────────────────────────────────
11
+ // - SingleActiveLease — single-active per (service, outbox) so two dispatcher
12
+ // replicas don't both drain. ttl/2 renew inside the lease. `validateFence`
13
+ // guards the mark-dispatched write (a lease-lost dispatcher's late mark is
14
+ // rejected — it must not mark a row another dispatcher is publishing).
15
+ // - CheckpointStore — tracks the HWM (the last published stream id) so the
16
+ // `nodii.worker.current_hwm` gauge + resume position are durable. The mark
17
+ // UPDATE and the cursor advance run in the SAME claim tx (atomic).
18
+ // - ObservabilityHooks — lease/hwm/depth/drain metrics, canonical names.
19
+ // - SignalDrain — `stop({timeoutMs})` registerable; bounded drain.
20
+ // - DeadLetterSink — OPTIONAL and currently NOT invoked on any path. Per
21
+ // D401 the producer drain has NO source-side dead-letter: a terminal /
22
+ // unpublishable row is surfaced via `recordFailure` (keep-row,
23
+ // publish_attempts bumped) + the 24h-unpublished sweep, NOT a sink. The sink
24
+ // is accepted for API symmetry with the consumer workers + a future opt-in
25
+ // (PII-redacted if provided), but no drain path calls it today.
26
+ //
27
+ // ── The drain sequence (per 01-comms § 9.2 / outbox-dispatcher § 5.2) ─────────
28
+ // acquire-lease → claim a batch (dispatched_at IS NULL, occurred_at order,
29
+ // FOR UPDATE SKIP LOCKED) inside a tx → publish each (injected publisher) →
30
+ // mark dispatched (fence-guarded) + advance the HWM cursor, all in the SAME
31
+ // tx → commit. D401: a publish failure increments attempts + leaves the row in
32
+ // place (never DLQ).
33
+ //
34
+ // ── At-least-once semantics (NOT exactly-once on the producer side) ───────────
35
+ // The PUBLISH (XADD) runs BEFORE `validateFence` — it is NOT fenced. A stale or
36
+ // retried dispatcher's publish therefore DOES land: the producer is
37
+ // AT-LEAST-ONCE (the same row may be re-published after a crash-before-commit
38
+ // or a lease handoff). The fence guards only the mark-dispatched UPDATE — it
39
+ // stops a lease-lost dispatcher from falsely recording a row as dispatched
40
+ // (which would suppress the legitimate successor's re-publish). END-TO-END
41
+ // exactly-once is achieved by the CONSUMER's `consumed_events` dedupe (D198),
42
+ // NOT by the fence preventing the publish.
43
+ import { ensurePiiRedaction } from "./dead-letter.js";
44
+ import { hwmGaugeValue, ObservabilityHooks } from "./observability-hooks.js";
45
+ import { FenceCheckUnavailableError, FenceLostError, SingleActiveLease, } from "./single-active-lease.js";
46
+ const DEFAULT_BATCH_SIZE = 100;
47
+ const DEFAULT_POLL_MS = 1_000;
48
+ const DEFAULT_STOP_TIMEOUT_MS = 30_000;
49
+ /**
50
+ * OutboxDispatcher — the Pattern-3 producer drain. Composes lease + checkpoint +
51
+ * hooks + drain. Single-active; the mark-dispatched + cursor advance are atomic
52
+ * in the claim tx; publish is at-least-once (the consumer dedupes).
53
+ */
54
+ export class OutboxDispatcher {
55
+ publish;
56
+ begin;
57
+ claimBatch;
58
+ markDispatched;
59
+ recordFailure;
60
+ checkpoint;
61
+ lease;
62
+ subscription;
63
+ // OPTIONAL + currently uninvoked (see the opts docstring). Kept for API
64
+ // symmetry / a future opt-in; PII-redacted if provided.
65
+ deadLetter;
66
+ batchSize;
67
+ pollMs;
68
+ hooks;
69
+ autoStart;
70
+ _running = false;
71
+ _stopped = false;
72
+ _inFlight = 0;
73
+ _hwm = { lastStreamId: null };
74
+ _lastAppliedAtMs = null;
75
+ loop = Promise.resolve();
76
+ constructor(opts) {
77
+ this.publish = opts.publish;
78
+ this.begin = opts.begin;
79
+ this.claimBatch = opts.claimBatch;
80
+ this.markDispatched = opts.markDispatched;
81
+ this.recordFailure = opts.recordFailure;
82
+ this.checkpoint = opts.checkpoint;
83
+ this.lease = new SingleActiveLease({
84
+ ...opts.lease,
85
+ onLeaseLost: () => this.hooks.leaseLost(),
86
+ });
87
+ this.subscription = opts.subscription ?? `outbox:${opts.lease.service}`;
88
+ this.deadLetter = opts.deadLetter
89
+ ? ensurePiiRedaction(opts.deadLetter)
90
+ : undefined;
91
+ this.batchSize = opts.batchSize ?? DEFAULT_BATCH_SIZE;
92
+ this.pollMs = opts.pollMs ?? DEFAULT_POLL_MS;
93
+ this.autoStart = opts.autoStart !== false;
94
+ this.hooks =
95
+ opts.hooks ??
96
+ new ObservabilityHooks({
97
+ attributes: {
98
+ service: opts.lease.service,
99
+ scope: opts.lease.scope,
100
+ worker: opts.workerLabel ?? "outbox",
101
+ },
102
+ });
103
+ }
104
+ get running() {
105
+ return this._running;
106
+ }
107
+ get leaseAcquired() {
108
+ return this.lease.leaseAcquired;
109
+ }
110
+ get lag() {
111
+ if (this._lastAppliedAtMs === null)
112
+ return null;
113
+ return Math.max(0, (Date.now() - this._lastAppliedAtMs) / 1000);
114
+ }
115
+ get currentHwm() {
116
+ return this._hwm;
117
+ }
118
+ async start() {
119
+ if (this._running)
120
+ return;
121
+ this._stopped = false;
122
+ const acquired = await this.lease.acquire();
123
+ if (acquired)
124
+ this.hooks.leaseAcquired();
125
+ await this.hydrateCursor();
126
+ this._running = true;
127
+ // Background drain loop. `autoStart:false` lets a test drive `drainOnce()`.
128
+ if (this.autoStart)
129
+ this.loop = this.runLoop();
130
+ }
131
+ async hydrateCursor() {
132
+ try {
133
+ await this.begin(async (tx) => {
134
+ const cp = await this.checkpoint.get(this.subscription, tx);
135
+ if (cp)
136
+ this._hwm = { lastStreamId: cp.lastStreamId };
137
+ });
138
+ }
139
+ catch {
140
+ /* cold start — cursor stays null */
141
+ }
142
+ }
143
+ async runLoop() {
144
+ while (!this._stopped) {
145
+ try {
146
+ if (!this.lease.leaseAcquired) {
147
+ const got = await this.lease.acquire();
148
+ if (got)
149
+ this.hooks.leaseAcquired();
150
+ else {
151
+ await new Promise((r) => setTimeout(r, this.pollMs));
152
+ continue;
153
+ }
154
+ }
155
+ const n = await this.drainOnce();
156
+ if (n < this.batchSize) {
157
+ await new Promise((r) => setTimeout(r, this.pollMs));
158
+ }
159
+ }
160
+ catch (err) {
161
+ // A drain-level error (claim tx itself rejected — Postgres down) is the
162
+ // Pillar-5 infra-failure case: back off, NEVER crash the loop.
163
+ this.hooks.recordDepth(-1, { error: "drain_failed" });
164
+ void err;
165
+ await new Promise((r) => setTimeout(r, Math.min(this.pollMs, 200)));
166
+ }
167
+ }
168
+ }
169
+ /**
170
+ * One drain pass: claim a batch in ONE tx, publish each, mark dispatched +
171
+ * advance the HWM cursor (fence-guarded) in the SAME tx. Returns the count
172
+ * dispatched. The publish runs INSIDE the claim tx so the row-lock is held
173
+ * across it (a second dispatcher can't double-claim); at-least-once safe (a
174
+ * crash after publish but before commit re-publishes; the consumer dedupes).
175
+ */
176
+ async drainOnce() {
177
+ if (!this.lease.leaseAcquired) {
178
+ const got = await this.lease.acquire();
179
+ if (got)
180
+ this.hooks.leaseAcquired();
181
+ else
182
+ return 0;
183
+ }
184
+ const token = this.lease.fencingToken;
185
+ let dispatched = 0;
186
+ let lastStreamId = null;
187
+ await this.begin(async (tx) => {
188
+ const rows = await this.claimBatch(tx, this.batchSize);
189
+ if (rows.length === 0)
190
+ return;
191
+ for (const row of rows) {
192
+ if (this.lease.lost)
193
+ break;
194
+ this._inFlight += 1;
195
+ let fenceLost = false;
196
+ try {
197
+ const streamId = await this.publish(row);
198
+ // The fence guard BEFORE marking the row dispatched: a lease-lost
199
+ // dispatcher must not mark a row a successor is also publishing.
200
+ if (token === null) {
201
+ throw new FenceLostError({
202
+ leaseKey: this.lease.key,
203
+ expectedToken: -1,
204
+ current: null,
205
+ });
206
+ }
207
+ await this.lease.validateFence(token);
208
+ await this.markDispatched(tx, row.id, streamId);
209
+ if (streamId)
210
+ lastStreamId = streamId;
211
+ dispatched += 1;
212
+ this._lastAppliedAtMs = Date.now();
213
+ }
214
+ catch (err) {
215
+ if (err instanceof FenceLostError ||
216
+ err instanceof FenceCheckUnavailableError) {
217
+ // The lease was lost/taken-over (FenceLostError) OR the fence-check
218
+ // READ failed transiently (FenceCheckUnavailableError — lease-redis
219
+ // blip). Either way ABORT the rest of the batch WITHOUT recordFailure:
220
+ // a transient lease-redis blip is NOT a publish failure, so it must
221
+ // NOT bump publish_attempts (5 blips would otherwise silently abandon
222
+ // a row whose every publish succeeded). The row is retried on lease
223
+ // recovery; the tx commits only the rows marked BEFORE the loss (each
224
+ // fence-validated). Mirrors Streams/Replication + the Go dispatcher.
225
+ this.hooks.leaseLost();
226
+ fenceLost = true;
227
+ }
228
+ else {
229
+ // D401: a publish failure NEVER deletes / DLQs the row — record the
230
+ // failure (bump attempts) + leave it. The 24h-unpublished sweep
231
+ // surfaces persistent liveness problems.
232
+ const msg = err instanceof Error ? err.message : String(err);
233
+ if (this.recordFailure) {
234
+ await this.recordFailure(tx, row.id, msg);
235
+ }
236
+ }
237
+ }
238
+ finally {
239
+ this._inFlight -= 1;
240
+ }
241
+ if (fenceLost)
242
+ break;
243
+ }
244
+ // Advance the HWM cursor in the SAME tx as the marks (atomic): the cursor
245
+ // can never advance past a row that wasn't actually marked dispatched.
246
+ if (lastStreamId !== null) {
247
+ await this.checkpoint.advance({ subscription: this.subscription, lastStreamId }, tx);
248
+ }
249
+ });
250
+ if (lastStreamId !== null) {
251
+ this._hwm = { lastStreamId };
252
+ this.hooks.recordHwm(hwmGaugeValue(lastStreamId));
253
+ }
254
+ this.hooks.recordDepth(dispatched);
255
+ return dispatched;
256
+ }
257
+ /** Run exactly one drain pass (test seam / cron tick). */
258
+ async runOnce() {
259
+ return this.drainOnce();
260
+ }
261
+ async stop(opts) {
262
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_STOP_TIMEOUT_MS;
263
+ const start = Date.now();
264
+ this._stopped = true;
265
+ this._running = false;
266
+ try {
267
+ await Promise.race([
268
+ this.loop,
269
+ new Promise((r) => setTimeout(r, timeoutMs)),
270
+ ]);
271
+ }
272
+ catch {
273
+ /* swallow */
274
+ }
275
+ while (this._inFlight > 0 && Date.now() - start < timeoutMs) {
276
+ await new Promise((r) => setTimeout(r, 10));
277
+ }
278
+ const drainedCleanly = this._inFlight === 0;
279
+ await this.lease.release();
280
+ const durationMs = Date.now() - start;
281
+ this.hooks.recordDrainDuration(durationMs);
282
+ return { drainedCleanly, durationMs };
283
+ }
284
+ }
285
+ //# sourceMappingURL=outbox-dispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox-dispatcher.js","sourceRoot":"","sources":["../../src/worker/outbox-dispatcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,170 @@
1
+ import type { CheckpointStore } from "./checkpoint-store.js";
2
+ import type { DeadLetterSink } from "./dead-letter.js";
3
+ import type { DedupeStore } from "./dedupe-store.js";
4
+ import { ObservabilityHooks } from "./observability-hooks.js";
5
+ import { type SingleActiveLeaseOpts } from "./single-active-lease.js";
6
+ import { type StreamsRedisLike } from "./streams-worker.js";
7
+ import type { SqlExecutor } from "./tx.js";
8
+ import type { WorkerHandle, WorkerHwm, WorkerStopOptions, WorkerStopResult } from "./worker-handle.js";
9
+ /** A replication event off the source stream. Carries the LWW version. */
10
+ export interface ReplicationEvent {
11
+ /** Dedupe id — `(event_id, topic)` is the composite dedupe key (D198). */
12
+ event_id: string;
13
+ /** The outbox topic. */
14
+ topic: string;
15
+ /** The replicated entity's id (the projection key). */
16
+ entity_id: string;
17
+ /** The monotonic source version — drives last-write-wins. */
18
+ entity_version: number | string | bigint;
19
+ /** Optional source emission ms for lag. */
20
+ occurred_at_ms?: number;
21
+ /** The rest of the payload (the projection input). */
22
+ [key: string]: unknown;
23
+ }
24
+ /** The apply context handed to the injected projection upsert INSIDE the tx. */
25
+ export interface ReplicationApplyContext {
26
+ tx: SqlExecutor;
27
+ event: ReplicationEvent;
28
+ streamId: string;
29
+ /** THE fence guard — call BEFORE the projection upsert. */
30
+ validateFence(): Promise<void>;
31
+ }
32
+ /** The injected projection upsert. Runs inside the worker's apply tx. */
33
+ export type ReplicationApplyHandler = (ctx: ReplicationApplyContext) => Promise<void>;
34
+ /** The injected audit emitter — fires AFTER a successful commit (§ 4 audit). */
35
+ export type ReplicationAuditHook = (info: {
36
+ event: ReplicationEvent;
37
+ streamId: string;
38
+ /** True when the event applied; false when LWW-skipped (still consumed). */
39
+ applied: boolean;
40
+ }) => Promise<void> | void;
41
+ /** A snapshot bootstrap source — streams the snapshot rows + the HWM to stitch. */
42
+ export interface SnapshotBootstrap {
43
+ /**
44
+ * Apply the full snapshot into the projection and return the high-water
45
+ * stream id the live consume must stitch from. The worker writes this as the
46
+ * cursor so live consume resumes exactly there (§ 7 stitch). The bootstrap
47
+ * runs its own apply (it owns the projection schema); the worker only records
48
+ * the returned HWM as the durable cursor.
49
+ */
50
+ bootstrap(tx: SqlExecutor): Promise<{
51
+ highWaterStreamId: string;
52
+ }>;
53
+ }
54
+ export interface ReplicationWorkerOpts {
55
+ redis: StreamsRedisLike;
56
+ /** The source replication stream key. */
57
+ sourceStream: string;
58
+ /** The injected projection upsert. */
59
+ apply: ReplicationApplyHandler;
60
+ /**
61
+ * Reads the projection's CURRENT source_entity_version for an entity, for the
62
+ * LWW comparison. Returns null when the entity isn't projected yet (→ apply).
63
+ */
64
+ currentVersion: (tx: SqlExecutor, event: ReplicationEvent) => Promise<bigint | null>;
65
+ /** Opens an apply transaction; the worker runs dedupe/checkpoint/apply on it. */
66
+ begin: <T>(fn: (tx: SqlExecutor) => Promise<T>) => Promise<T>;
67
+ dedupe: DedupeStore;
68
+ checkpoint: CheckpointStore;
69
+ lease: Omit<SingleActiveLeaseOpts, "onLeaseLost">;
70
+ /** Checkpoint subscription key. Default `replication:<sourceStream>`. */
71
+ subscription?: string;
72
+ deadLetter: DeadLetterSink;
73
+ /** Optional snapshot bootstrap (run once if the cursor is absent). */
74
+ snapshot?: SnapshotBootstrap;
75
+ /** Optional audit hook (fires after each successful commit). */
76
+ audit?: ReplicationAuditHook;
77
+ retryMaxAttempts?: number;
78
+ pollBatch?: number;
79
+ blockMs?: number;
80
+ /** XAUTOCLAIM crash-recovery threshold (ms). Default max(lease ttlMs, 30_000). */
81
+ reclaimMinIdleMs?: number;
82
+ hooks?: ObservabilityHooks;
83
+ workerLabel?: string;
84
+ /** Launch the background consume loop on `start()`. Default true; tests pass
85
+ * false to drive `runOnce()` deterministically. */
86
+ autoStart?: boolean;
87
+ }
88
+ /** Consumer-group name — `${service}:${stream}` (canonical per replica-consumer). */
89
+ export declare function replicationConsumerGroup(service: string, stream: string): string;
90
+ /**
91
+ * ReplicationWorker — the Pattern-3 subscriber. Composes lease + dedupe +
92
+ * checkpoint + hooks + drain + snapshot-bootstrap + LWW + audit. EXACTLY-ONCE
93
+ * apply, monotonic projection (no LWW regression), atomic cursor+dedupe+upsert.
94
+ */
95
+ export declare class ReplicationWorker implements WorkerHandle {
96
+ private readonly redis;
97
+ private readonly sourceStream;
98
+ private readonly applyHandler;
99
+ private readonly currentVersion;
100
+ private readonly begin;
101
+ private readonly dedupe;
102
+ private readonly checkpoint;
103
+ private readonly lease;
104
+ private readonly subscription;
105
+ private readonly deadLetter;
106
+ private readonly snapshot?;
107
+ private readonly audit?;
108
+ private readonly retryMaxAttempts;
109
+ private readonly pollBatch;
110
+ private readonly blockMs;
111
+ private readonly reclaimMinIdleMs;
112
+ private readonly hooks;
113
+ private readonly autoStart;
114
+ private readonly group;
115
+ private readonly consumer;
116
+ private readonly retries;
117
+ private _running;
118
+ private _stopped;
119
+ private _inFlight;
120
+ private _bootstrapped;
121
+ private _hwm;
122
+ private _lastAppliedAtMs;
123
+ private groupEnsured;
124
+ private loop;
125
+ constructor(opts: ReplicationWorkerOpts);
126
+ get running(): boolean;
127
+ get leaseAcquired(): boolean;
128
+ get lag(): number | null;
129
+ get currentHwm(): WorkerHwm;
130
+ /** True once the snapshot bootstrap (if any) has run / been determined absent. */
131
+ get bootstrapped(): boolean;
132
+ private ensureGroup;
133
+ start(): Promise<void>;
134
+ /**
135
+ * Snapshot-bootstrap + HWM stitch (§ 7): if the durable cursor is ABSENT (cold
136
+ * start) AND a snapshot source is wired, run the snapshot bootstrap inside ONE
137
+ * tx and write the returned high-water stream id as the cursor — live consume
138
+ * then resumes EXACTLY from there (no gap, no double-apply of the snapshot
139
+ * window). If a cursor already exists we skip the snapshot and resume live.
140
+ */
141
+ maybeBootstrap(): Promise<void>;
142
+ private runLoop;
143
+ private pollOnce;
144
+ /**
145
+ * Dead-letter + XACK every structurally-unparseable (malformed-JSON) entry. A
146
+ * malformed entry is a POISON message (no decodable event) — leaving it in the
147
+ * PEL only re-cycles it through XAUTOCLAIM forever. Route the raw bytes through
148
+ * the (PII-redacting) dead-letter sink, then XACK so it leaves the PEL. If the
149
+ * sink fails, leave the entry in the PEL (recoverable; never silently dropped).
150
+ */
151
+ private handleMalformedEntries;
152
+ /**
153
+ * Apply one replication event: dedupe-skip → LWW check → apply tx (projection
154
+ * + dedupe.record + checkpoint.advance, fence-guarded, ONE tx) → XACK → audit.
155
+ */
156
+ private applyOne;
157
+ /**
158
+ * XACK an already-committed entry, SWALLOWING a transient ack failure. The
159
+ * XACK is post-commit (or post-dedupe-skip) and NOT part of the apply tx — a
160
+ * failing XACK is a transient redis blip, never a poison event. On failure the
161
+ * entry stays un-acked in the PEL and is re-acked next poll once redis
162
+ * recovers (the durable dedupe row makes the redelivery a no-op). Matches Go's
163
+ * `_, _ = Ack(...)`; a throw here must NEVER reach retry/DLQ.
164
+ */
165
+ private ackSwallow;
166
+ private retryOrDeadLetter;
167
+ runOnce(): Promise<number>;
168
+ stop(opts?: WorkerStopOptions): Promise<WorkerStopResult>;
169
+ }
170
+ //# sourceMappingURL=replication-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replication-worker.d.ts","sourceRoot":"","sources":["../../src/worker/replication-worker.ts"],"names":[],"mappings":"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"}