@gobing-ai/ts-infra 0.4.38 → 0.4.40

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/README.md CHANGED
@@ -275,6 +275,8 @@ The consumer claims ready jobs, resets stuck processing jobs after the visibilit
275
275
 
276
276
  `stop()` drains gracefully: it stops polling, then waits for work already in flight — including a poll cycle that has claimed nothing yet — before resolving. The wait is capped by `drainTimeoutMs` (default 30s) so a stuck handler cannot block shutdown; when the cap is hit, `queue.consumer.stopped` reports `drained: false` with the outstanding `inFlightAtStop` count.
277
277
 
278
+ **Queue identity.** Once a consumer is event-enabled (an `events` bus is configured), it **must** supply a non-empty, already-trimmed `queueName` — construction throws `Queue consumer queueName …` otherwise, so an observable consumer can never emit an anonymous `queue.consumer.started`/`queue.consumer.stopped` row. The name is validated, never normalized, and is emitted byte-for-byte as `queueName` on both lifecycle details. Silent consumers (no `events`) may omit identity entirely.
279
+
278
280
  **Queue lifecycle events** are opt-in through the injected `EventBus`:
279
281
 
280
282
  ```ts
@@ -291,7 +293,10 @@ events.on('queue.consumer.started', () => { /* ... */ });
291
293
  events.on('queue.consumer.stopped', () => { /* ... */ });
292
294
 
293
295
  const queue = new DBJobQueue<Payload>(dao, events); // emits enqueued
294
- const consumer = new DBQueueConsumer<Payload>(dao, { events }); // emits the rest
296
+ const consumer = new DBQueueConsumer<Payload>(dao, {
297
+ events, // emits lifecycle + job events
298
+ queueName: 'email-jobs', // required when `events` is configured (ADR-068)
299
+ });
295
300
  ```
296
301
 
297
302
  ### Scheduler — cron-like actions
package/dist/events.d.ts CHANGED
@@ -47,6 +47,8 @@ export interface QueueJobEnqueuedDetail extends QueueJobRef, WithEventSeverity {
47
47
  }
48
48
  /** Payload for `queue.consumer.started` — the polling loop began. */
49
49
  export interface QueueConsumerStartedDetail extends WithEventSeverity {
50
+ /** Queue identity the consumer polls (validated `QueueConsumerConfig.queueName`). */
51
+ queueName: string;
50
52
  /** Epoch ms when the consumer was started. */
51
53
  startedAt: number;
52
54
  /** Polling interval in ms. */
@@ -60,6 +62,8 @@ export interface QueueConsumerStartedDetail extends WithEventSeverity {
60
62
  }
61
63
  /** Payload for `queue.consumer.stopped` — the polling loop was halted. */
62
64
  export interface QueueConsumerStoppedDetail extends WithEventSeverity {
65
+ /** Queue identity the consumer polls (validated `QueueConsumerConfig.queueName`). */
66
+ queueName: string;
63
67
  /** Epoch ms when `stop()` was called. */
64
68
  stoppedAt: number;
65
69
  /** Drain deadline in ms applied to in-flight handlers at stop time. */
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
@@ -25,7 +25,11 @@ export declare class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
25
25
  private readonly baseDelay;
26
26
  private readonly maxDelay;
27
27
  private readonly drainTimeoutMs;
28
- private readonly events;
28
+ /**
29
+ * Validated event sink: the bus plus the non-empty queue name its lifecycle rows
30
+ * require (ADR-068). `undefined` for silent consumers — no `events`, no identity.
31
+ */
32
+ private readonly eventSink;
29
33
  private timer;
30
34
  private running;
31
35
  private inFlight;
@@ -1 +1 @@
1
- {"version":3,"file":"db-job-queue.d.ts","sourceRoot":"","sources":["../../src/job-queue/db-job-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAER,WAAW,EAKd,MAAM,WAAW,CAAC;AAUnB,OAAO,KAAK,EAAE,cAAc,EAAO,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7G,kFAAkF;AAClF,qBAAa,UAAU,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC;IAEnD,QAAQ,CAAC,GAAG,EAAE,WAAW;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBADf,GAAG,EAAE,WAAW,EACR,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,YAAA;IAG7C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAO5E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAY3F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;CAGrC;AAcD,qFAAqF;AACrF,qBAAa,eAAe,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAgB7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAfxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoC;IAC3D,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,WAAW,CAA8B;gBAG5B,GAAG,EAAE,WAAW,EACjC,MAAM,GAAE,mBAAwB;IAYpC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAI9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IActB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoCrB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAIlC,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IA4BpC,OAAO,CAAC,QAAQ;YAWF,IAAI;YAgBJ,UAAU;YA8CV,WAAW;CAqC5B"}
1
+ {"version":3,"file":"db-job-queue.d.ts","sourceRoot":"","sources":["../../src/job-queue/db-job-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAkB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EACR,WAAW,EAKd,MAAM,WAAW,CAAC;AAUnB,OAAO,KAAK,EAAE,cAAc,EAAO,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAQ7G,kFAAkF;AAClF,qBAAa,UAAU,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC;IAEnD,QAAQ,CAAC,GAAG,EAAE,WAAW;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;gBADf,GAAG,EAAE,WAAW,EACR,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,YAAA;IAG7C,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAO5E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAY3F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;CAGrC;AAcD,qFAAqF;AACrF,qBAAa,eAAe,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAoB7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAnBxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC;;;OAGG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgE;IAC1F,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,WAAW,CAA8B;gBAG5B,GAAG,EAAE,WAAW,EACjC,MAAM,GAAE,mBAAwB;IAYpC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAI9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAuCrB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAIlC,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IA4BpC,OAAO,CAAC,QAAQ;YAWF,IAAI;YAgBJ,UAAU;YA8CV,WAAW;CAqC5B"}
@@ -62,7 +62,11 @@ export class DBQueueConsumer {
62
62
  baseDelay;
63
63
  maxDelay;
64
64
  drainTimeoutMs;
65
- events;
65
+ /**
66
+ * Validated event sink: the bus plus the non-empty queue name its lifecycle rows
67
+ * require (ADR-068). `undefined` for silent consumers — no `events`, no identity.
68
+ */
69
+ eventSink;
66
70
  timer = null;
67
71
  running = false;
68
72
  inFlight = 0;
@@ -76,7 +80,7 @@ export class DBQueueConsumer {
76
80
  this.baseDelay = nonNegativeFiniteConfig('baseDelay', config.baseDelay ?? 1_000);
77
81
  this.maxDelay = nonNegativeFiniteConfig('maxDelay', config.maxDelay ?? 60_000);
78
82
  this.drainTimeoutMs = nonNegativeFiniteConfig('drainTimeoutMs', config.drainTimeoutMs ?? 30_000);
79
- this.events = config.events;
83
+ this.eventSink = resolveEventSink(config);
80
84
  }
81
85
  register(type, handler) {
82
86
  this.handlers.set(type, handler);
@@ -86,14 +90,18 @@ export class DBQueueConsumer {
86
90
  return;
87
91
  this.running = true;
88
92
  this.schedule(0);
89
- await this.events?.emit('queue.consumer.started', {
90
- startedAt: Date.now(),
91
- pollInterval: this.pollInterval,
92
- batchSize: this.batchSize,
93
- maxConcurrency: this.maxConcurrency,
94
- visibilityTimeout: this.visibilityTimeout,
95
- severity: 'info',
96
- });
93
+ const sink = this.eventSink;
94
+ if (sink !== undefined) {
95
+ await sink.bus.emit('queue.consumer.started', {
96
+ startedAt: Date.now(),
97
+ pollInterval: this.pollInterval,
98
+ batchSize: this.batchSize,
99
+ maxConcurrency: this.maxConcurrency,
100
+ visibilityTimeout: this.visibilityTimeout,
101
+ queueName: sink.queueName,
102
+ severity: 'info',
103
+ });
104
+ }
97
105
  }
98
106
  async stop() {
99
107
  const wasRunning = this.running;
@@ -117,14 +125,17 @@ export class DBQueueConsumer {
117
125
  }
118
126
  if (wasRunning) {
119
127
  const drained = this.inFlight === 0;
120
- const detail = {
121
- stoppedAt: Date.now(),
122
- drainTimeoutMs: this.drainTimeoutMs,
123
- inFlightAtStop: this.inFlight,
124
- drained,
125
- severity: drained ? 'info' : 'warning',
126
- };
127
- await this.events?.emit('queue.consumer.stopped', detail);
128
+ const sink = this.eventSink;
129
+ if (sink !== undefined) {
130
+ await sink.bus.emit('queue.consumer.stopped', {
131
+ stoppedAt: Date.now(),
132
+ drainTimeoutMs: this.drainTimeoutMs,
133
+ inFlightAtStop: this.inFlight,
134
+ drained,
135
+ queueName: sink.queueName,
136
+ severity: drained ? 'info' : 'warning',
137
+ });
138
+ }
128
139
  }
129
140
  }
130
141
  async stats() {
@@ -220,7 +231,7 @@ export class DBQueueConsumer {
220
231
  attempt: job.attempts,
221
232
  severity: 'info',
222
233
  };
223
- await this.events?.emit('queue.job.completed', completed);
234
+ await this.eventSink?.bus.emit('queue.job.completed', completed);
224
235
  }
225
236
  catch (error) {
226
237
  const durationMs = performance.now() - startMs;
@@ -244,7 +255,7 @@ export class DBQueueConsumer {
244
255
  durationMs,
245
256
  severity: 'error',
246
257
  };
247
- await this.events?.emit('queue.job.failed', failed);
258
+ await this.eventSink?.bus.emit('queue.job.failed', failed);
248
259
  return;
249
260
  }
250
261
  const delay = Math.min(this.maxDelay, this.baseDelay * 2 ** Math.max(0, attempts - 1));
@@ -259,7 +270,7 @@ export class DBQueueConsumer {
259
270
  error: message,
260
271
  severity: 'warning',
261
272
  };
262
- await this.events?.emit('queue.job.retrying', retrying);
273
+ await this.eventSink?.bus.emit('queue.job.retrying', retrying);
263
274
  }
264
275
  }
265
276
  function toJob(record) {
@@ -300,3 +311,23 @@ function nonNegativeFiniteConfig(name, value) {
300
311
  }
301
312
  return value;
302
313
  }
314
+ /**
315
+ * Resolve the validated event sink from `QueueConsumerConfig`: any supplied `queueName`
316
+ * must be non-empty and already trimmed, and an event-enabled consumer (one with an
317
+ * `events` bus) must supply one. Silent consumers may omit identity because they emit no
318
+ * lifecycle rows. The name is validated but never normalized — the emitted detail is
319
+ * byte-for-byte the config.
320
+ */
321
+ function resolveEventSink(config) {
322
+ const name = config.queueName;
323
+ if (name !== undefined && (name.length === 0 || name.trim() !== name)) {
324
+ throw new TypeError(`Queue consumer queueName must be a non-empty, already-trimmed string; received ${JSON.stringify(name)}`);
325
+ }
326
+ if (config.events === undefined) {
327
+ return undefined;
328
+ }
329
+ if (name === undefined) {
330
+ throw new TypeError('Queue consumer queueName is required when events is configured');
331
+ }
332
+ return { bus: config.events, queueName: name };
333
+ }
@@ -56,6 +56,13 @@ export interface QueueConsumerConfig {
56
56
  maxDelay?: number;
57
57
  /** Upper bound (ms, default 30_000) on how long `stop()` waits for in-flight work to drain. */
58
58
  drainTimeoutMs?: number;
59
+ /**
60
+ * Identity of the queue this consumer polls. Runtime-required whenever `events` is
61
+ * configured (ADR-068): an observable consumer must never emit an anonymous lifecycle
62
+ * row. Silent consumers (no `events`) may omit it. A supplied value must be non-empty
63
+ * and already trimmed; it is validated but never normalized.
64
+ */
65
+ queueName?: string;
59
66
  /**
60
67
  * Optional bus for queue lifecycle events with correlator-grade detail payloads:
61
68
  * `queue.consumer.started` / `queue.consumer.stopped` (config snapshot + drain
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/job-queue/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,yEAAyE;AACzE,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,OAAO;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,yGAAyG;IACzG,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC3B,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC;AAED,iDAAiD;AACjD,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAErE,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAClC;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/job-queue/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,yEAAyE;AACzE,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,OAAO;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,CAAC,CAAC;IACX,MAAM,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,yGAAyG;IACzG,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC3B,4FAA4F;IAC5F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,OAAO;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7E,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC;AAED,iDAAiD;AACjD,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAErE,8DAA8D;AAC9D,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,mBAAmB;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+FAA+F;IAC/F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAClC;AAED,gGAAgG;AAChG,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,OAAO;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACrD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-infra",
3
- "version": "0.4.38",
3
+ "version": "0.4.40",
4
4
  "description": "@gobing-ai/ts-infra — Infrastructure backbone: event bus, job queue, scheduler, telemetry, API client, and logging.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -78,12 +78,12 @@
78
78
  "release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-infra-v<version> && git push --tags' && exit 1"
79
79
  },
80
80
  "dependencies": {
81
- "@gobing-ai/ts-utils": "^0.4.38",
81
+ "@gobing-ai/ts-utils": "^0.4.40",
82
82
  "@logtape/logtape": "^2.0.0"
83
83
  },
84
84
  "peerDependencies": {
85
- "@gobing-ai/ts-db": "^0.4.38",
86
- "@gobing-ai/ts-runtime": "^0.4.38",
85
+ "@gobing-ai/ts-db": "^0.4.40",
86
+ "@gobing-ai/ts-runtime": "^0.4.40",
87
87
  "@opentelemetry/api": "^1.9.0",
88
88
  "@opentelemetry/sdk-trace-node": "^2.0.0",
89
89
  "@opentelemetry/sdk-metrics": "^2.0.0",
@@ -116,8 +116,8 @@
116
116
  }
117
117
  },
118
118
  "devDependencies": {
119
- "@gobing-ai/ts-db": "^0.4.38",
120
- "@gobing-ai/ts-runtime": "^0.4.38",
119
+ "@gobing-ai/ts-db": "^0.4.40",
120
+ "@gobing-ai/ts-runtime": "^0.4.40",
121
121
  "@types/bun": "1.3.14",
122
122
  "@opentelemetry/api": "^1.9.0",
123
123
  "@opentelemetry/sdk-trace-node": "^2.0.0",
package/src/events.ts CHANGED
@@ -55,6 +55,8 @@ export interface QueueJobEnqueuedDetail extends QueueJobRef, WithEventSeverity {
55
55
 
56
56
  /** Payload for `queue.consumer.started` — the polling loop began. */
57
57
  export interface QueueConsumerStartedDetail extends WithEventSeverity {
58
+ /** Queue identity the consumer polls (validated `QueueConsumerConfig.queueName`). */
59
+ queueName: string;
58
60
  /** Epoch ms when the consumer was started. */
59
61
  startedAt: number;
60
62
  /** Polling interval in ms. */
@@ -69,6 +71,8 @@ export interface QueueConsumerStartedDetail extends WithEventSeverity {
69
71
 
70
72
  /** Payload for `queue.consumer.stopped` — the polling loop was halted. */
71
73
  export interface QueueConsumerStoppedDetail extends WithEventSeverity {
74
+ /** Queue identity the consumer polls (validated `QueueConsumerConfig.queueName`). */
75
+ queueName: string;
72
76
  /** Epoch ms when `stop()` was called. */
73
77
  stoppedAt: number;
74
78
  /** Drain deadline in ms applied to in-flight handlers at stop time. */
@@ -1,7 +1,6 @@
1
1
  import type { QueueJobDao, QueueJobRecord, QueueStats } from '@gobing-ai/ts-db';
2
2
  import type { EventBus } from '../event-bus/event-bus';
3
3
  import type {
4
- QueueConsumerStoppedDetail,
5
4
  QueueEvents,
6
5
  QueueJobCompletedDetail,
7
6
  QueueJobEnqueuedDetail,
@@ -78,7 +77,11 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
78
77
  private readonly baseDelay: number;
79
78
  private readonly maxDelay: number;
80
79
  private readonly drainTimeoutMs: number;
81
- private readonly events: EventBus<QueueEvents> | undefined;
80
+ /**
81
+ * Validated event sink: the bus plus the non-empty queue name its lifecycle rows
82
+ * require (ADR-068). `undefined` for silent consumers — no `events`, no identity.
83
+ */
84
+ private readonly eventSink: { bus: EventBus<QueueEvents>; queueName: string } | undefined;
82
85
  private timer: ReturnType<typeof setTimeout> | null = null;
83
86
  private running = false;
84
87
  private inFlight = 0;
@@ -95,7 +98,7 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
95
98
  this.baseDelay = nonNegativeFiniteConfig('baseDelay', config.baseDelay ?? 1_000);
96
99
  this.maxDelay = nonNegativeFiniteConfig('maxDelay', config.maxDelay ?? 60_000);
97
100
  this.drainTimeoutMs = nonNegativeFiniteConfig('drainTimeoutMs', config.drainTimeoutMs ?? 30_000);
98
- this.events = config.events;
101
+ this.eventSink = resolveEventSink(config);
99
102
  }
100
103
 
101
104
  register(type: string, handler: JobHandler<T>): void {
@@ -106,14 +109,18 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
106
109
  if (this.running) return;
107
110
  this.running = true;
108
111
  this.schedule(0);
109
- await this.events?.emit('queue.consumer.started', {
110
- startedAt: Date.now(),
111
- pollInterval: this.pollInterval,
112
- batchSize: this.batchSize,
113
- maxConcurrency: this.maxConcurrency,
114
- visibilityTimeout: this.visibilityTimeout,
115
- severity: 'info',
116
- });
112
+ const sink = this.eventSink;
113
+ if (sink !== undefined) {
114
+ await sink.bus.emit('queue.consumer.started', {
115
+ startedAt: Date.now(),
116
+ pollInterval: this.pollInterval,
117
+ batchSize: this.batchSize,
118
+ maxConcurrency: this.maxConcurrency,
119
+ visibilityTimeout: this.visibilityTimeout,
120
+ queueName: sink.queueName,
121
+ severity: 'info',
122
+ });
123
+ }
117
124
  }
118
125
 
119
126
  async stop(): Promise<void> {
@@ -141,14 +148,17 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
141
148
  }
142
149
  if (wasRunning) {
143
150
  const drained = this.inFlight === 0;
144
- const detail: QueueConsumerStoppedDetail = {
145
- stoppedAt: Date.now(),
146
- drainTimeoutMs: this.drainTimeoutMs,
147
- inFlightAtStop: this.inFlight,
148
- drained,
149
- severity: drained ? 'info' : 'warning',
150
- };
151
- await this.events?.emit('queue.consumer.stopped', detail);
151
+ const sink = this.eventSink;
152
+ if (sink !== undefined) {
153
+ await sink.bus.emit('queue.consumer.stopped', {
154
+ stoppedAt: Date.now(),
155
+ drainTimeoutMs: this.drainTimeoutMs,
156
+ inFlightAtStop: this.inFlight,
157
+ drained,
158
+ queueName: sink.queueName,
159
+ severity: drained ? 'info' : 'warning',
160
+ });
161
+ }
152
162
  }
153
163
  }
154
164
 
@@ -249,7 +259,7 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
249
259
  attempt: job.attempts,
250
260
  severity: 'info',
251
261
  };
252
- await this.events?.emit('queue.job.completed', completed);
262
+ await this.eventSink?.bus.emit('queue.job.completed', completed);
253
263
  } catch (error) {
254
264
  const durationMs = performance.now() - startMs;
255
265
  getQueueJobProcessingDuration().record(durationMs, { type: job.type });
@@ -277,7 +287,7 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
277
287
  durationMs,
278
288
  severity: 'error',
279
289
  };
280
- await this.events?.emit('queue.job.failed', failed);
290
+ await this.eventSink?.bus.emit('queue.job.failed', failed);
281
291
  return;
282
292
  }
283
293
 
@@ -293,7 +303,7 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
293
303
  error: message,
294
304
  severity: 'warning',
295
305
  };
296
- await this.events?.emit('queue.job.retrying', retrying);
306
+ await this.eventSink?.bus.emit('queue.job.retrying', retrying);
297
307
  }
298
308
  }
299
309
 
@@ -339,3 +349,26 @@ function nonNegativeFiniteConfig(name: string, value: number): number {
339
349
  }
340
350
  return value;
341
351
  }
352
+
353
+ /**
354
+ * Resolve the validated event sink from `QueueConsumerConfig`: any supplied `queueName`
355
+ * must be non-empty and already trimmed, and an event-enabled consumer (one with an
356
+ * `events` bus) must supply one. Silent consumers may omit identity because they emit no
357
+ * lifecycle rows. The name is validated but never normalized — the emitted detail is
358
+ * byte-for-byte the config.
359
+ */
360
+ function resolveEventSink(config: QueueConsumerConfig): { bus: EventBus<QueueEvents>; queueName: string } | undefined {
361
+ const name = config.queueName;
362
+ if (name !== undefined && (name.length === 0 || name.trim() !== name)) {
363
+ throw new TypeError(
364
+ `Queue consumer queueName must be a non-empty, already-trimmed string; received ${JSON.stringify(name)}`,
365
+ );
366
+ }
367
+ if (config.events === undefined) {
368
+ return undefined;
369
+ }
370
+ if (name === undefined) {
371
+ throw new TypeError('Queue consumer queueName is required when events is configured');
372
+ }
373
+ return { bus: config.events, queueName: name };
374
+ }
@@ -60,6 +60,13 @@ export interface QueueConsumerConfig {
60
60
  maxDelay?: number;
61
61
  /** Upper bound (ms, default 30_000) on how long `stop()` waits for in-flight work to drain. */
62
62
  drainTimeoutMs?: number;
63
+ /**
64
+ * Identity of the queue this consumer polls. Runtime-required whenever `events` is
65
+ * configured (ADR-068): an observable consumer must never emit an anonymous lifecycle
66
+ * row. Silent consumers (no `events`) may omit it. A supplied value must be non-empty
67
+ * and already trimmed; it is validated but never normalized.
68
+ */
69
+ queueName?: string;
63
70
  /**
64
71
  * Optional bus for queue lifecycle events with correlator-grade detail payloads:
65
72
  * `queue.consumer.started` / `queue.consumer.stopped` (config snapshot + drain