@nodii/telemetry 0.12.0 → 0.14.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.
Files changed (60) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +5 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/lambda/index.d.ts +77 -0
  5. package/dist/lambda/index.d.ts.map +1 -0
  6. package/dist/lambda/index.js +130 -0
  7. package/dist/lambda/index.js.map +1 -0
  8. package/dist/worker/checkpoint-store.d.ts +66 -0
  9. package/dist/worker/checkpoint-store.d.ts.map +1 -0
  10. package/dist/worker/checkpoint-store.js +131 -0
  11. package/dist/worker/checkpoint-store.js.map +1 -0
  12. package/dist/worker/dead-letter.d.ts +81 -0
  13. package/dist/worker/dead-letter.d.ts.map +1 -0
  14. package/dist/worker/dead-letter.js +165 -0
  15. package/dist/worker/dead-letter.js.map +1 -0
  16. package/dist/worker/dedupe-store.d.ts +46 -0
  17. package/dist/worker/dedupe-store.d.ts.map +1 -0
  18. package/dist/worker/dedupe-store.js +113 -0
  19. package/dist/worker/dedupe-store.js.map +1 -0
  20. package/dist/worker/index.d.ts +23 -0
  21. package/dist/worker/index.d.ts.map +1 -0
  22. package/dist/worker/index.js +46 -0
  23. package/dist/worker/index.js.map +1 -0
  24. package/dist/worker/observability-hooks.d.ts +99 -0
  25. package/dist/worker/observability-hooks.d.ts.map +1 -0
  26. package/dist/worker/observability-hooks.js +189 -0
  27. package/dist/worker/observability-hooks.js.map +1 -0
  28. package/dist/worker/outbox-dispatcher.d.ts +106 -0
  29. package/dist/worker/outbox-dispatcher.d.ts.map +1 -0
  30. package/dist/worker/outbox-dispatcher.js +285 -0
  31. package/dist/worker/outbox-dispatcher.js.map +1 -0
  32. package/dist/worker/replication-worker.d.ts +162 -0
  33. package/dist/worker/replication-worker.d.ts.map +1 -0
  34. package/dist/worker/replication-worker.js +513 -0
  35. package/dist/worker/replication-worker.js.map +1 -0
  36. package/dist/worker/signal-drain.d.ts +83 -0
  37. package/dist/worker/signal-drain.d.ts.map +1 -0
  38. package/dist/worker/signal-drain.js +131 -0
  39. package/dist/worker/signal-drain.js.map +1 -0
  40. package/dist/worker/single-active-lease.d.ts +180 -0
  41. package/dist/worker/single-active-lease.d.ts.map +1 -0
  42. package/dist/worker/single-active-lease.js +394 -0
  43. package/dist/worker/single-active-lease.js.map +1 -0
  44. package/dist/worker/streams-worker.d.ts +192 -0
  45. package/dist/worker/streams-worker.d.ts.map +1 -0
  46. package/dist/worker/streams-worker.js +488 -0
  47. package/dist/worker/streams-worker.js.map +1 -0
  48. package/dist/worker/sweeper-worker.d.ts +107 -0
  49. package/dist/worker/sweeper-worker.d.ts.map +1 -0
  50. package/dist/worker/sweeper-worker.js +245 -0
  51. package/dist/worker/sweeper-worker.js.map +1 -0
  52. package/dist/worker/tx.d.ts +39 -0
  53. package/dist/worker/tx.d.ts.map +1 -0
  54. package/dist/worker/tx.js +51 -0
  55. package/dist/worker/tx.js.map +1 -0
  56. package/dist/worker/worker-handle.d.ts +94 -0
  57. package/dist/worker/worker-handle.d.ts.map +1 -0
  58. package/dist/worker/worker-handle.js +15 -0
  59. package/dist/worker/worker-handle.js.map +1 -0
  60. package/package.json +10 -1
@@ -0,0 +1,189 @@
1
+ // D402 — ObservabilityHooks: the canonical worker-metric vocabulary, routed
2
+ // through telemetry's REAL `recordMetric` API (NOT a NOOP sink by default).
3
+ //
4
+ // D402 (LOCKED) — "ObservabilityHooks — lag / HWM / depth metrics with canonical
5
+ // names." The orchestrator's task pins the exact canonical names:
6
+ // - nodii.worker.lease.acquired_total (counter)
7
+ // - nodii.worker.lease.renewed_total (counter)
8
+ // - nodii.worker.lease.lost_total (counter)
9
+ // - nodii.worker.lag (gauge, seconds)
10
+ // - nodii.worker.current_hwm (gauge)
11
+ // - nodii.worker.depth (gauge — backlog/stream depth)
12
+ // - nodii.worker.drain.duration_ms (histogram, ms)
13
+ // - nodii.worker.swept_total (counter — sweepers only)
14
+ //
15
+ // These names live under the existing D18/D197 `async_worker` metric family
16
+ // (background workers are the async_worker family). A consuming service merges
17
+ // `WORKER_SUBSTRATE_VOCABULARY` (below) into `initTelemetry({ vocabularyRegistry
18
+ // })` exactly as it merges `REPLICA_CONSUMER_VOCABULARY` — otherwise the
19
+ // canonical names trip `MetricVocabularyError` (vocabulary is HARD per D24).
20
+ //
21
+ // ── Why this routes through `recordMetric`, never a default no-op ────────────
22
+ // R1 (never-defer) forbids a Noop sink wired as the default. The hooks bind to
23
+ // telemetry's own `recordMetric({ kind, name, value, attributes })`. That fn
24
+ // is the real metric path: it validates the name against the loaded vocabulary
25
+ // + the family + the kind, then records onto the live OTLP-backed metrics
26
+ // backend. The ONLY injection seam is for tests, which `_setMetricsBackend(...)`
27
+ // a capturing backend underneath — the hook itself always calls the real API.
28
+ import { recordMetric } from "../metrics.js";
29
+ /** Canonical worker-substrate metric names, pinned by D402 + the task. */
30
+ export const WORKER_METRIC_NAMES = {
31
+ leaseAcquired: "nodii.worker.lease.acquired_total",
32
+ leaseRenewed: "nodii.worker.lease.renewed_total",
33
+ leaseLost: "nodii.worker.lease.lost_total",
34
+ lag: "nodii.worker.lag",
35
+ currentHwm: "nodii.worker.current_hwm",
36
+ depth: "nodii.worker.depth",
37
+ drainDurationMs: "nodii.worker.drain.duration_ms",
38
+ swept: "nodii.worker.swept_total",
39
+ };
40
+ /**
41
+ * The locked worker-substrate metric vocabulary — merge into
42
+ * `initTelemetry({ vocabularyRegistry: { ...svc, ...WORKER_SUBSTRATE_VOCABULARY } })`
43
+ * so the canonical names register under family `async_worker` instead of
44
+ * tripping `MetricVocabularyError`. Mirrors how `REPLICA_CONSUMER_VOCABULARY`
45
+ * is consumed.
46
+ */
47
+ export const WORKER_SUBSTRATE_VOCABULARY = {
48
+ [WORKER_METRIC_NAMES.leaseAcquired]: {
49
+ family: "async_worker",
50
+ kind: "counter",
51
+ description: "Single-active lease acquisitions (a worker took the lease for its scope).",
52
+ decision: "D402",
53
+ },
54
+ [WORKER_METRIC_NAMES.leaseRenewed]: {
55
+ family: "async_worker",
56
+ kind: "counter",
57
+ description: "Single-active lease renewals at ttl/2 (CAS held).",
58
+ decision: "D402",
59
+ },
60
+ [WORKER_METRIC_NAMES.leaseLost]: {
61
+ family: "async_worker",
62
+ kind: "counter",
63
+ description: "Single-active lease losses (renew CAS failed / lease taken over / errored).",
64
+ decision: "D402",
65
+ },
66
+ [WORKER_METRIC_NAMES.lag]: {
67
+ family: "async_worker",
68
+ kind: "gauge",
69
+ unit: "seconds",
70
+ description: "Wall-clock lag between source emission and consumer apply for the worker.",
71
+ decision: "D402",
72
+ },
73
+ [WORKER_METRIC_NAMES.currentHwm]: {
74
+ family: "async_worker",
75
+ kind: "gauge",
76
+ description: "Current durable high-water-mark (checkpoint cursor position) the worker has advanced to.",
77
+ decision: "D402",
78
+ },
79
+ [WORKER_METRIC_NAMES.depth]: {
80
+ family: "async_worker",
81
+ kind: "gauge",
82
+ description: "Backlog / stream depth pending for the worker (unprocessed entries).",
83
+ decision: "D402",
84
+ },
85
+ [WORKER_METRIC_NAMES.drainDurationMs]: {
86
+ family: "async_worker",
87
+ kind: "histogram",
88
+ unit: "ms",
89
+ description: "Graceful-drain wall-clock duration on stop() (flush in-flight + release leases).",
90
+ decision: "D402",
91
+ },
92
+ [WORKER_METRIC_NAMES.swept]: {
93
+ family: "async_worker",
94
+ kind: "counter",
95
+ description: "Rows removed by a SweeperWorker retention sweep.",
96
+ decision: "D402",
97
+ },
98
+ };
99
+ /** Strip `undefined`-valued attributes so the recordMetric label map is clean. */
100
+ function cleanAttributes(attrs, extra) {
101
+ const out = {};
102
+ for (const [k, v] of Object.entries({ ...attrs, ...extra })) {
103
+ if (v !== undefined)
104
+ out[k] = v;
105
+ }
106
+ return out;
107
+ }
108
+ /**
109
+ * ObservabilityHooks emits the canonical worker-substrate metrics through the
110
+ * real telemetry metric API. One instance per worker; the worker types call
111
+ * the typed methods at the corresponding lifecycle points (lease acquire/renew/
112
+ * lose, per-tick lag/hwm/depth refresh, drain on stop, sweep count).
113
+ *
114
+ * Usage:
115
+ * const hooks = new ObservabilityHooks({
116
+ * attributes: { service: "billing", scope: "outbox", worker: "outbox" },
117
+ * });
118
+ * hooks.leaseAcquired();
119
+ * hooks.recordLag(12.5);
120
+ * hooks.recordDrainDuration(842);
121
+ */
122
+ export class ObservabilityHooks {
123
+ attrs;
124
+ record;
125
+ constructor(opts) {
126
+ this.attrs = opts.attributes;
127
+ // Default = the REAL recordMetric. R1: never a no-op default.
128
+ this.record = opts.record ?? recordMetric;
129
+ }
130
+ emit(kind, name, value, extra) {
131
+ this.record({
132
+ kind,
133
+ name,
134
+ value,
135
+ attributes: cleanAttributes(this.attrs, extra),
136
+ });
137
+ }
138
+ /** The worker acquired its single-active lease. */
139
+ leaseAcquired(extra) {
140
+ this.emit("counter", WORKER_METRIC_NAMES.leaseAcquired, 1, extra);
141
+ }
142
+ /** The worker renewed its lease (CAS held) at ttl/2. */
143
+ leaseRenewed(extra) {
144
+ this.emit("counter", WORKER_METRIC_NAMES.leaseRenewed, 1, extra);
145
+ }
146
+ /** The worker lost its lease (renew CAS failed / taken over / errored). */
147
+ leaseLost(extra) {
148
+ this.emit("counter", WORKER_METRIC_NAMES.leaseLost, 1, extra);
149
+ }
150
+ /** Snapshot the current consumer lag in seconds. */
151
+ recordLag(seconds, extra) {
152
+ this.emit("gauge", WORKER_METRIC_NAMES.lag, seconds, extra);
153
+ }
154
+ /**
155
+ * Snapshot the current durable HWM as a numeric position. A Redis-Streams id
156
+ * (`<ms>-<seq>`) is reduced to its leading ms component so the gauge is a
157
+ * monotonic-ish stall indicator (the same reduction outbox-dispatcher uses
158
+ * for `outbox.hwm`); callers with a numeric cursor pass it directly.
159
+ */
160
+ recordHwm(position, extra) {
161
+ this.emit("gauge", WORKER_METRIC_NAMES.currentHwm, position, extra);
162
+ }
163
+ /** Snapshot the current backlog / stream depth (pending entries). */
164
+ recordDepth(depth, extra) {
165
+ this.emit("gauge", WORKER_METRIC_NAMES.depth, depth, extra);
166
+ }
167
+ /** Record the wall-clock ms a graceful drain (stop) took. */
168
+ recordDrainDuration(ms, extra) {
169
+ this.emit("histogram", WORKER_METRIC_NAMES.drainDurationMs, ms, extra);
170
+ }
171
+ /** Count rows removed by a sweeper retention sweep. */
172
+ recordSwept(count, extra) {
173
+ this.emit("counter", WORKER_METRIC_NAMES.swept, count, extra);
174
+ }
175
+ }
176
+ /**
177
+ * Reduce a Redis-Streams id (`<ms>-<seq>`) to a numeric gauge position (its
178
+ * leading ms component). Mirrors outbox-dispatcher's `hwmGaugeValue`. A
179
+ * non-streams numeric string returns its Number; an unparseable value returns 0.
180
+ */
181
+ export function hwmGaugeValue(streamId) {
182
+ if (!streamId)
183
+ return 0;
184
+ const dash = streamId.indexOf("-");
185
+ const head = dash < 0 ? streamId : streamId.slice(0, dash);
186
+ const n = Number(head);
187
+ return Number.isFinite(n) ? n : 0;
188
+ }
189
+ //# sourceMappingURL=observability-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observability-hooks.js","sourceRoot":"","sources":["../../src/worker/observability-hooks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import type { CheckpointStore } from "./checkpoint-store.js";
2
+ import type { DeadLetterSink } from "./dead-letter.js";
3
+ import { ObservabilityHooks } from "./observability-hooks.js";
4
+ import { type SingleActiveLeaseOpts } from "./single-active-lease.js";
5
+ import type { SqlExecutor } from "./tx.js";
6
+ import type { WorkerHandle, WorkerHwm, WorkerStopOptions, WorkerStopResult } from "./worker-handle.js";
7
+ /** A pending outbox row claimed for dispatch. The generic shape the drain needs. */
8
+ export interface OutboxRow {
9
+ /** Primary key (used to mark dispatched). */
10
+ id: string | number;
11
+ /** The dedupe id the consumer dedupes on. */
12
+ event_id: string;
13
+ /** The outbox topic. */
14
+ topic: string;
15
+ /** Source emission time (ms epoch or ISO) for lag + FIFO order. */
16
+ occurred_at: string | number;
17
+ /** The rest of the row (the publisher serializes whatever it needs). */
18
+ [key: string]: unknown;
19
+ }
20
+ /** The injected publisher — XADDs (or otherwise emits) a row + returns the id. */
21
+ export type OutboxPublisher = (row: OutboxRow) => Promise<string | null>;
22
+ export interface OutboxDispatcherOpts {
23
+ /** The injected publisher (e.g. an XADD to the topic's stream). */
24
+ publish: OutboxPublisher;
25
+ /**
26
+ * Opens a claim transaction. The drain claims rows + marks dispatched +
27
+ * advances the cursor on THIS tx so the mark + cursor commit together-or-none.
28
+ */
29
+ begin: <T>(fn: (tx: SqlExecutor) => Promise<T>) => Promise<T>;
30
+ /** Claims a batch of pending rows ON the claim tx (FOR UPDATE SKIP LOCKED). */
31
+ claimBatch: (tx: SqlExecutor, batchSize: number) => Promise<OutboxRow[]>;
32
+ /** Marks a row dispatched ON the claim tx (sets dispatched_at + stream_id). */
33
+ markDispatched: (tx: SqlExecutor, id: string | number, streamId: string | null) => Promise<void>;
34
+ /** Records a publish failure ON the claim tx (D401: bump attempts; keep row). */
35
+ recordFailure?: (tx: SqlExecutor, id: string | number, errorMessage: string) => Promise<void>;
36
+ checkpoint: CheckpointStore;
37
+ /** Lease config (service + scope=outbox + ttlMs). */
38
+ lease: Omit<SingleActiveLeaseOpts, "onLeaseLost">;
39
+ /** Checkpoint subscription key. Default `outbox:<service>`. */
40
+ subscription?: string;
41
+ /**
42
+ * OPTIONAL and currently NOT invoked on any drain path. Per D401 the producer
43
+ * drain has NO source-side dead-letter: a terminal / unpublishable row is
44
+ * surfaced via `recordFailure` (keep-row, publish_attempts bumped) + the
45
+ * 24h-unpublished sweep, NOT a sink. The sink is accepted for API symmetry with
46
+ * the consumer workers (StreamsWorker/ReplicationWorker, which DO dead-letter
47
+ * poison events) and a future opt-in; if provided it is PII-redacted, but no
48
+ * path calls it today.
49
+ */
50
+ deadLetter?: DeadLetterSink;
51
+ /** Rows claimed per drain pass. Default 100. */
52
+ batchSize?: number;
53
+ /** Poll interval (ms) when the loop runs autonomously. Default 1_000. */
54
+ pollMs?: number;
55
+ hooks?: ObservabilityHooks;
56
+ workerLabel?: string;
57
+ /** Launch the background drain loop on `start()`. Default true; tests pass
58
+ * false to drive `drainOnce()`/`runOnce()` deterministically. */
59
+ autoStart?: boolean;
60
+ }
61
+ /**
62
+ * OutboxDispatcher — the Pattern-3 producer drain. Composes lease + checkpoint +
63
+ * hooks + drain. Single-active; the mark-dispatched + cursor advance are atomic
64
+ * in the claim tx; publish is at-least-once (the consumer dedupes).
65
+ */
66
+ export declare class OutboxDispatcher implements WorkerHandle {
67
+ private readonly publish;
68
+ private readonly begin;
69
+ private readonly claimBatch;
70
+ private readonly markDispatched;
71
+ private readonly recordFailure?;
72
+ private readonly checkpoint;
73
+ private readonly lease;
74
+ private readonly subscription;
75
+ private readonly deadLetter?;
76
+ private readonly batchSize;
77
+ private readonly pollMs;
78
+ private readonly hooks;
79
+ private readonly autoStart;
80
+ private _running;
81
+ private _stopped;
82
+ private _inFlight;
83
+ private _hwm;
84
+ private _lastAppliedAtMs;
85
+ private loop;
86
+ constructor(opts: OutboxDispatcherOpts);
87
+ get running(): boolean;
88
+ get leaseAcquired(): boolean;
89
+ get lag(): number | null;
90
+ get currentHwm(): WorkerHwm;
91
+ start(): Promise<void>;
92
+ private hydrateCursor;
93
+ private runLoop;
94
+ /**
95
+ * One drain pass: claim a batch in ONE tx, publish each, mark dispatched +
96
+ * advance the HWM cursor (fence-guarded) in the SAME tx. Returns the count
97
+ * dispatched. The publish runs INSIDE the claim tx so the row-lock is held
98
+ * across it (a second dispatcher can't double-claim); at-least-once safe (a
99
+ * crash after publish but before commit re-publishes; the consumer dedupes).
100
+ */
101
+ drainOnce(): Promise<number>;
102
+ /** Run exactly one drain pass (test seam / cron tick). */
103
+ runOnce(): Promise<number>;
104
+ stop(opts?: WorkerStopOptions): Promise<WorkerStopResult>;
105
+ }
106
+ //# sourceMappingURL=outbox-dispatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbox-dispatcher.d.ts","sourceRoot":"","sources":["../../src/worker/outbox-dispatcher.ts"],"names":[],"mappings":"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"}
@@ -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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"}