@gobing-ai/ts-infra 0.2.7 → 0.2.9

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 (67) hide show
  1. package/README.md +111 -20
  2. package/dist/event-bus/event-bus.d.ts.map +1 -1
  3. package/dist/event-bus/event-bus.js +4 -0
  4. package/dist/index.d.ts +2 -3
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +3 -2
  7. package/dist/job-queue/db-job-queue.d.ts +40 -0
  8. package/dist/job-queue/db-job-queue.d.ts.map +1 -0
  9. package/dist/job-queue/db-job-queue.js +154 -0
  10. package/dist/job-queue/index.d.ts +1 -0
  11. package/dist/job-queue/index.d.ts.map +1 -1
  12. package/dist/job-queue/index.js +1 -0
  13. package/dist/job-queue/types.d.ts +3 -5
  14. package/dist/job-queue/types.d.ts.map +1 -1
  15. package/dist/job-queue/types.js +2 -4
  16. package/dist/logger.js +5 -6
  17. package/dist/scheduler/cloudflare.d.ts +0 -4
  18. package/dist/scheduler/cloudflare.d.ts.map +1 -1
  19. package/dist/scheduler/cloudflare.js +10 -1
  20. package/dist/scheduler/factory.d.ts +0 -3
  21. package/dist/scheduler/factory.d.ts.map +1 -1
  22. package/dist/scheduler/factory.js +5 -14
  23. package/dist/scheduler/node.d.ts +0 -4
  24. package/dist/scheduler/node.d.ts.map +1 -1
  25. package/dist/scheduler/node.js +11 -0
  26. package/dist/telemetry/config.d.ts +0 -5
  27. package/dist/telemetry/config.d.ts.map +1 -1
  28. package/dist/telemetry/config.js +0 -3
  29. package/dist/telemetry/index.d.ts +1 -1
  30. package/dist/telemetry/index.d.ts.map +1 -1
  31. package/dist/telemetry/index.js +1 -1
  32. package/dist/telemetry/metrics.d.ts +1 -10
  33. package/dist/telemetry/metrics.d.ts.map +1 -1
  34. package/dist/telemetry/metrics.js +9 -26
  35. package/dist/telemetry/otel-node.d.ts +37 -0
  36. package/dist/telemetry/otel-node.d.ts.map +1 -0
  37. package/dist/telemetry/otel-node.js +85 -0
  38. package/dist/telemetry/sdk.d.ts +12 -1
  39. package/dist/telemetry/sdk.d.ts.map +1 -1
  40. package/dist/telemetry/sdk.js +17 -27
  41. package/package.json +36 -7
  42. package/src/event-bus/event-bus.ts +4 -0
  43. package/src/index.ts +2 -11
  44. package/src/job-queue/db-job-queue.ts +178 -0
  45. package/src/job-queue/index.ts +1 -0
  46. package/src/job-queue/types.ts +3 -5
  47. package/src/logger.ts +4 -4
  48. package/src/scheduler/cloudflare.ts +8 -1
  49. package/src/scheduler/factory.ts +2 -15
  50. package/src/scheduler/node.ts +10 -0
  51. package/src/telemetry/config.ts +0 -8
  52. package/src/telemetry/index.ts +0 -6
  53. package/src/telemetry/metrics.ts +9 -38
  54. package/src/telemetry/otel-node.ts +116 -0
  55. package/src/telemetry/sdk.ts +18 -30
  56. package/dist/events/app-events.d.ts +0 -7
  57. package/dist/events/app-events.d.ts.map +0 -1
  58. package/dist/events/app-events.js +0 -4
  59. package/dist/events/create-system-bus.d.ts +0 -6
  60. package/dist/events/create-system-bus.d.ts.map +0 -1
  61. package/dist/events/create-system-bus.js +0 -7
  62. package/dist/events/index.d.ts +0 -3
  63. package/dist/events/index.d.ts.map +0 -1
  64. package/dist/events/index.js +0 -1
  65. package/src/events/app-events.ts +0 -8
  66. package/src/events/create-system-bus.ts +0 -8
  67. package/src/events/index.ts +0 -2
package/README.md CHANGED
@@ -1,19 +1,18 @@
1
1
  # @gobing-ai/ts-infra
2
2
 
3
- Infrastructure backbone — typed event bus, job queue (types), cron scheduler, OpenTelemetry telemetry, HTTP API client, and structured logging. Designed to be wired together at bootstrap via `RuntimeContext`.
3
+ Infrastructure backbone — typed event bus, DB-backed job queue, cron scheduler, OpenTelemetry telemetry, HTTP API client, and structured logging.
4
4
 
5
5
  ## Overview
6
6
 
7
- `ts-infra` provides seven subsystems that form the application backbone:
7
+ `ts-infra` provides six subsystems that form the application backbone:
8
8
 
9
9
  | Subsystem | Module | Purpose |
10
10
  |-----------|--------|---------|
11
11
  | **Event Bus** | `event-bus/` | Typed pub/sub with sync + async dispatch, lifecycle self-observability |
12
- | **Events** | `events/` | Typed event map pattern + system bus factory |
13
- | **Job Queue** | `job-queue/` | Types for DB-backed job processing (`JobQueue`, `QueueConsumer`, `Job`) |
12
+ | **Job Queue** | `job-queue/` | DB-backed enqueue and consume flow (`DBJobQueue`, `DBQueueConsumer`) plus queue interfaces |
14
13
  | **Scheduler** | `scheduler/` | Cron-like scheduled actions — Node (interval), Cloudflare (Cron Triggers), Noop (test) |
15
- | **Telemetry** | `telemetry/` | OpenTelemetry SDK wrapper — tracing (`traceAsync`), metrics (17 instruments), SQL sanitizer |
16
- | **API Client** | `api-client.ts` | Typed HTTP client with OTel tracing, retry, timeout, error handling |
14
+ | **Telemetry** | `telemetry/` | OTel instrumentation — tracing (`traceAsync`), metrics (12 instruments), SQL sanitizer; opt-in OTLP export via the `/otel-node` subpath |
15
+ | **API Client** | `api-client.ts` | Typed HTTP client with OTel tracing, timeout, and error handling |
17
16
  | **Logger** | `logger.ts` | Structured JSON logger with levels, child loggers, and mute toggle |
18
17
 
19
18
  ## Architecture
@@ -32,12 +31,6 @@ classDiagram
32
31
  }
33
32
  }
34
33
 
35
- namespace events {
36
- class EventFactory {
37
- +createSystemBus() EventBus
38
- }
39
- }
40
-
41
34
  namespace job-queue {
42
35
  class JobQueue~T~ {
43
36
  <<interface>>
@@ -51,6 +44,18 @@ classDiagram
51
44
  +stop() Promise~void~
52
45
  +stats() Promise~QueueStats~
53
46
  }
47
+ class DBJobQueue~T~ {
48
+ +enqueue(type, payload, opts?) Promise~string~
49
+ +enqueueBatch(jobs) Promise~string[]~
50
+ +stats() Promise~QueueStats~
51
+ }
52
+ class DBQueueConsumer~T~ {
53
+ +register(type, handler) void
54
+ +start() Promise~void~
55
+ +stop() Promise~void~
56
+ +processOnce() Promise~number~
57
+ +stats() Promise~QueueStats~
58
+ }
54
59
  class Job~T~ {
55
60
  <<interface>>
56
61
  }
@@ -92,12 +97,16 @@ classDiagram
92
97
  +getActiveSpan() Span
93
98
  }
94
99
  class Metrics {
95
- +getHttpServerRequestTotal() Counter
96
- +getHttpServerRequestDuration() Histogram
97
- +getDbOperationTotal() Counter
100
+ +getHttpClientRequestTotal() Counter
101
+ +getHttpClientRequestDuration() Histogram
102
+ +getEventbusEmitsTotal() Counter
98
103
  +getQueueJobEnqueuedTotal() Counter
99
104
  +getSchedulerJobExecutedTotal() Counter
100
105
  }
106
+ class OtelNode {
107
+ +initNodeTelemetry(opts) void
108
+ +shutdownNodeTelemetry() Promise~void~
109
+ }
101
110
  class DbSanitize {
102
111
  +sanitizeSql(sql) string
103
112
  +extractSqlOperation(sql) string
@@ -141,6 +150,8 @@ classDiagram
141
150
  SchedulerFactory --> CloudflareSchedulerAdapter
142
151
  SchedulerFactory --> NoopSchedulerAdapter
143
152
  EventBus --> JobQueue : "async dispatch"
153
+ DBJobQueue --> JobQueue : "implements"
154
+ DBQueueConsumer --> QueueConsumer : "implements"
144
155
  EventBus --> Logger : "self-observability"
145
156
  APIClient --> Tracing : "traceAsync"
146
157
  APIClient --> Metrics : "counters + histograms"
@@ -182,7 +193,11 @@ bus.once('user.signed_up', () => console.log('one-time'));
182
193
  **Lifecycle events** — inject a second `EventBus` to observe bus internals:
183
194
 
184
195
  ```ts
185
- const lifecycleBus = new EventBus<BusLifecycleEvents>();
196
+ type LifecycleEvents = {
197
+ 'bus.emit.done': (detail: { event: string; syncCount: number; asyncCount: number; emitDurationMs: number }) => void;
198
+ };
199
+
200
+ const lifecycleBus = new EventBus<LifecycleEvents>();
186
201
  lifecycleBus.on('bus.emit.done', (detail) => {
187
202
  // { event, syncCount, asyncCount, emitDurationMs, errors }
188
203
  metrics.recordEmit(detail);
@@ -191,6 +206,45 @@ lifecycleBus.on('bus.emit.done', (detail) => {
191
206
  const bus = new EventBus<AppEvents>({ lifecycleBus });
192
207
  ```
193
208
 
209
+ ### Job Queue — DB-backed async work
210
+
211
+ `DBJobQueue` and `DBQueueConsumer` run over `@gobing-ai/ts-db`'s `QueueJobDao`. Use this when event handlers, schedulers, or API handlers need durable background work with retries.
212
+
213
+ ```ts
214
+ import { createDbAdapter, QueueJobDao } from '@gobing-ai/ts-db';
215
+ import { DBJobQueue, DBQueueConsumer } from '@gobing-ai/ts-infra';
216
+
217
+ const db = await createDbAdapter({ driver: 'bun-sqlite', url: './jobs.db' });
218
+ const dao = new QueueJobDao(db);
219
+
220
+ const queue = new DBJobQueue<{ to: string; subject: string }>(dao);
221
+ await queue.enqueue(
222
+ 'send-email',
223
+ { to: 'alice@example.com', subject: 'Welcome' },
224
+ { maxRetries: 5, delay: 1_000, ttlMs: 86_400_000 },
225
+ );
226
+
227
+ const consumer = new DBQueueConsumer<{ to: string; subject: string }>(dao, {
228
+ batchSize: 10,
229
+ maxConcurrency: 4,
230
+ visibilityTimeout: 30_000,
231
+ });
232
+
233
+ consumer.register('send-email', async (job) => {
234
+ await sendEmail(job.payload.to, job.payload.subject);
235
+ });
236
+
237
+ await consumer.start();
238
+ ```
239
+
240
+ For scheduled drains and tests, call `processOnce()` instead of starting the polling loop:
241
+
242
+ ```ts
243
+ const processed = await consumer.processOnce();
244
+ ```
245
+
246
+ The consumer claims ready jobs, resets stuck processing jobs after the visibility timeout, retries failed jobs with exponential backoff, and marks expired jobs failed through `QueueJobDao`.
247
+
194
248
  ### Scheduler — cron-like actions
195
249
 
196
250
  ```ts
@@ -269,9 +323,7 @@ const reqLog = log.child({ requestId: 'req-123' });
269
323
  reqLog.error('Validation failed', { field: 'email' });
270
324
  // → {...,"category":"auth","requestId":"req-123","field":"email"}
271
325
 
272
- // Mute during tests
273
- import { setLoggerMuted } from './logger.js'; // internal import
274
- setLoggerMuted(true);
326
+ // In tests, prefer injecting a logger boundary or initializing at a quiet level.
275
327
  ```
276
328
 
277
329
  ### Telemetry — OpenTelemetry
@@ -288,7 +340,6 @@ initTelemetry({
288
340
  enabled: true,
289
341
  serviceName: 'my-api',
290
342
  environment: 'production',
291
- exporterEndpoint: 'http://otel-collector:4318/v1/traces',
292
343
  });
293
344
 
294
345
  // Trace an operation
@@ -316,6 +367,46 @@ getQueueJobEnqueuedTotal().add(1, { 'queue.job_type': 'send-email' });
316
367
  getQueueJobProcessingDuration().record(42, { 'queue.job_type': 'send-email' });
317
368
  ```
318
369
 
370
+ #### Instrumentation vs. export
371
+
372
+ The main barrel only **instruments** — it records spans and metrics against the
373
+ globally-registered OpenTelemetry provider, and degrades to no-ops when none is
374
+ registered. It does **not** ship an exporter, so it never forces an OTel SDK or a
375
+ collector opinion on consumers. Two ways to actually export:
376
+
377
+ 1. **BYO** — register your own OTel SDK (tracer + meter providers, exporters) at
378
+ process startup. ts-infra's spans/metrics flow into it automatically.
379
+ 2. **Turnkey OTLP** — opt into the `@gobing-ai/ts-infra/otel-node` subpath, which
380
+ wires Node OTLP/HTTP export for both signals and registers the providers
381
+ globally.
382
+
383
+ ```ts
384
+ // Node-only convenience: pulls the optional OTLP exporter peers.
385
+ import { initNodeTelemetry, shutdownNodeTelemetry } from '@gobing-ai/ts-infra/otel-node';
386
+
387
+ initNodeTelemetry({
388
+ serviceName: 'my-api',
389
+ serviceVersion: '1.4.0',
390
+ endpoint: 'http://otel-collector:4318', // signal paths (/v1/traces, /v1/metrics) appended
391
+ headers: { authorization: `Bearer ${process.env.OTEL_TOKEN}` },
392
+ });
393
+
394
+ process.on('SIGTERM', async () => {
395
+ await shutdownNodeTelemetry(); // flush + drain buffered spans/metrics
396
+ });
397
+ ```
398
+
399
+ The exporter packages are **optional peers** — only consumers of `/otel-node`
400
+ need them installed. The main `@gobing-ai/ts-infra` import never pulls them, so
401
+ BYO and browser/edge consumers stay lean. To use the subpath:
402
+
403
+ ```bash
404
+ bun add @opentelemetry/sdk-trace-node @opentelemetry/sdk-metrics \
405
+ @opentelemetry/resources \
406
+ @opentelemetry/exporter-trace-otlp-http \
407
+ @opentelemetry/exporter-metrics-otlp-http
408
+ ```
409
+
319
410
  ## Usage
320
411
 
321
412
  ### Install
@@ -1 +1 @@
1
- {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/event-bus/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEnD,OAAO,KAAK,EAER,kBAAkB,EAElB,QAAQ,EAER,gBAAgB,EACnB,MAAM,SAAS,CAAC;AAQjB;;;GAGG;AACH,qBAAa,QAAQ,CAAC,OAAO,SAAS,QAAQ;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyD;IACtF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyD;IACvF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiD;IACjF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsC;IACnE,OAAO,CAAC,kBAAkB,CAAK;gBAEnB,IAAI,CAAC,EAAE;QACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;QACpB,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;KAC/C;IAKD,EAAE,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAQzF,IAAI,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAS3F,GAAG,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAkBjE,kBAAkB,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI;IAUtD,IAAI,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAwE7F,aAAa,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM;IAMjF,UAAU,IAAI,MAAM,EAAE;IAOtB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,oBAAoB;CAU/B"}
1
+ {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../../src/event-bus/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAGnD,OAAO,KAAK,EAER,kBAAkB,EAElB,QAAQ,EAER,gBAAgB,EACnB,MAAM,SAAS,CAAC;AAQjB;;;GAGG;AACH,qBAAa,QAAQ,CAAC,OAAO,SAAS,QAAQ;IAC1C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyD;IACtF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyD;IACvF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiD;IACjF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsC;IACnE,OAAO,CAAC,kBAAkB,CAAK;gBAEnB,IAAI,CAAC,EAAE;QACf,QAAQ,CAAC,EAAE,QAAQ,CAAC;QACpB,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;KAC/C;IAKD,EAAE,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAQzF,IAAI,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,IAAI;IAS3F,GAAG,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI;IAkBjE,kBAAkB,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI;IAUtD,IAAI,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA2E7F,aAAa,CAAC,CAAC,SAAS,MAAM,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM;IAMjF,UAAU,IAAI,MAAM,EAAE;IAOtB,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,iBAAiB;IASzB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,oBAAoB;CAU/B"}
@@ -1,4 +1,5 @@
1
1
  import { getLogger } from '../logger.js';
2
+ import { getEventbusEmitsTotal, getEventbusErrorsTotal } from '../telemetry/metrics.js';
2
3
  let _busLogger;
3
4
  function busLogger() {
4
5
  if (!_busLogger)
@@ -123,6 +124,9 @@ export class EventBus {
123
124
  }
124
125
  const durationMs = performance.now() - startMs;
125
126
  const detail = args.length === 1 ? args[0] : args.length > 1 ? args : undefined;
127
+ getEventbusEmitsTotal().add(1, { event: eventName });
128
+ if (errors > 0)
129
+ getEventbusErrorsTotal().add(errors, { event: eventName });
126
130
  this.publishEmitDone({ event: eventName, syncCount, asyncCount, emitDurationMs: durationMs, errors, detail });
127
131
  if (syncCount === 0 && asyncCount === 0) {
128
132
  busLogger().debug('emit with zero handlers', { event: eventName });
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  export { APIClient, type APIClientConfig, APIError, type RequestOptions } from './api-client';
2
2
  export { EventBus, type EventMap, type SubscribeOptions } from './event-bus/index';
3
- export type { AppEvents, AppInternalEvents } from './events/index';
4
- export { createSystemBus } from './events/index';
5
3
  export type { EnqueueOptions, Job, JobHandler, JobQueue, QueueConsumer, QueueConsumerConfig, QueueStats, } from './job-queue/index';
4
+ export { DBJobQueue, DBQueueConsumer } from './job-queue/index';
6
5
  export { getLogger, initializeLogger, type Logger, type LogLevel } from './logger';
7
6
  export { CloudflareSchedulerAdapter, getSchedulerAdapter, initScheduler, NodeSchedulerAdapter, NoopSchedulerAdapter, type ScheduledAction, type SchedulerAdapter, setSchedulerAdapter, } from './scheduler/index';
8
- export { addSpanAttributes, addSpanEvent, extractSqlOperation, getActiveSpan, getDbOperationDuration, getDbOperationErrors, getDbOperationTotal, getEventbusEmitsTotal, getEventbusErrorsTotal, getHttpClientRequestDuration, getHttpClientRequestErrors, getHttpClientRequestTotal, getHttpServerRequestDuration, getHttpServerRequestErrors, getHttpServerRequestTotal, getQueueJobCompletedTotal, getQueueJobEnqueuedTotal, getQueueJobFailedTotal, getQueueJobProcessingDuration, getSchedulerJobDuration, getSchedulerJobExecutedTotal, getSchedulerJobFailedTotal, getTelemetryConfig, getTracer, initMetrics, initTelemetry, isTelemetryEnabled, sanitizeSql, shutdownMetrics, shutdownTelemetry, type TelemetryConfig, type TelemetryConfigPartial, traceAsync, traceSync, withSpan, } from './telemetry/index';
7
+ export { addSpanAttributes, addSpanEvent, extractSqlOperation, getActiveSpan, getEventbusEmitsTotal, getEventbusErrorsTotal, getHttpClientRequestDuration, getHttpClientRequestErrors, getHttpClientRequestTotal, getQueueJobCompletedTotal, getQueueJobEnqueuedTotal, getQueueJobFailedTotal, getQueueJobProcessingDuration, getSchedulerJobDuration, getSchedulerJobExecutedTotal, getSchedulerJobFailedTotal, getTelemetryConfig, getTracer, initMetrics, initTelemetry, isTelemetryEnabled, sanitizeSql, shutdownMetrics, shutdownTelemetry, type TelemetryConfig, type TelemetryConfigPartial, traceAsync, traceSync, withSpan, } from './telemetry/index';
9
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9F,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGnF,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGjD,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGnF,OAAO,EACH,0BAA0B,EAC1B,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,mBAAmB,GACtB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,UAAU,EACV,SAAS,EACT,QAAQ,GACX,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAG9F,OAAO,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEnF,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGhE,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGnF,OAAO,EACH,0BAA0B,EAC1B,mBAAmB,EACnB,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,mBAAmB,GACtB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACH,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,sBAAsB,EACtB,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,0BAA0B,EAC1B,kBAAkB,EAClB,SAAS,EACT,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,eAAe,EACf,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,UAAU,EACV,SAAS,EACT,QAAQ,GACX,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -2,10 +2,11 @@
2
2
  export { APIClient, APIError } from './api-client.js';
3
3
  // Event Bus
4
4
  export { EventBus } from './event-bus/index.js';
5
- export { createSystemBus } from './events/index.js';
5
+ // Job Queue
6
+ export { DBJobQueue, DBQueueConsumer } from './job-queue/index.js';
6
7
  // Logger
7
8
  export { getLogger, initializeLogger } from './logger.js';
8
9
  // Scheduler
9
10
  export { CloudflareSchedulerAdapter, getSchedulerAdapter, initScheduler, NodeSchedulerAdapter, NoopSchedulerAdapter, setSchedulerAdapter, } from './scheduler/index.js';
10
11
  // Telemetry
11
- export { addSpanAttributes, addSpanEvent, extractSqlOperation, getActiveSpan, getDbOperationDuration, getDbOperationErrors, getDbOperationTotal, getEventbusEmitsTotal, getEventbusErrorsTotal, getHttpClientRequestDuration, getHttpClientRequestErrors, getHttpClientRequestTotal, getHttpServerRequestDuration, getHttpServerRequestErrors, getHttpServerRequestTotal, getQueueJobCompletedTotal, getQueueJobEnqueuedTotal, getQueueJobFailedTotal, getQueueJobProcessingDuration, getSchedulerJobDuration, getSchedulerJobExecutedTotal, getSchedulerJobFailedTotal, getTelemetryConfig, getTracer, initMetrics, initTelemetry, isTelemetryEnabled, sanitizeSql, shutdownMetrics, shutdownTelemetry, traceAsync, traceSync, withSpan, } from './telemetry/index.js';
12
+ export { addSpanAttributes, addSpanEvent, extractSqlOperation, getActiveSpan, getEventbusEmitsTotal, getEventbusErrorsTotal, getHttpClientRequestDuration, getHttpClientRequestErrors, getHttpClientRequestTotal, getQueueJobCompletedTotal, getQueueJobEnqueuedTotal, getQueueJobFailedTotal, getQueueJobProcessingDuration, getSchedulerJobDuration, getSchedulerJobExecutedTotal, getSchedulerJobFailedTotal, getTelemetryConfig, getTracer, initMetrics, initTelemetry, isTelemetryEnabled, sanitizeSql, shutdownMetrics, shutdownTelemetry, traceAsync, traceSync, withSpan, } from './telemetry/index.js';
@@ -0,0 +1,40 @@
1
+ import type { QueueJobDao, QueueStats } from '@gobing-ai/ts-db';
2
+ import type { EnqueueOptions, JobHandler, JobQueue, QueueConsumer, QueueConsumerConfig } from './types';
3
+ /** DB-backed job queue implementation over `@gobing-ai/ts-db`'s `QueueJobDao`. */
4
+ export declare class DBJobQueue<T = unknown> implements JobQueue<T> {
5
+ readonly dao: QueueJobDao;
6
+ constructor(dao: QueueJobDao);
7
+ enqueue(type: string, payload: T, options?: EnqueueOptions): Promise<string>;
8
+ enqueueBatch(jobs: Array<{
9
+ type: string;
10
+ payload: T;
11
+ } & EnqueueOptions>): Promise<string[]>;
12
+ stats(): Promise<QueueStats>;
13
+ }
14
+ /** DB-backed queue consumer with polling, retry, and visibility-timeout handling. */
15
+ export declare class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
16
+ private readonly dao;
17
+ private readonly handlers;
18
+ private readonly pollInterval;
19
+ private readonly batchSize;
20
+ private readonly maxConcurrency;
21
+ private readonly visibilityTimeout;
22
+ private readonly baseDelay;
23
+ private readonly maxDelay;
24
+ private readonly drainTimeoutMs;
25
+ private timer;
26
+ private running;
27
+ private inFlight;
28
+ constructor(dao: QueueJobDao, config?: QueueConsumerConfig);
29
+ register(type: string, handler: JobHandler<T>): void;
30
+ start(): Promise<void>;
31
+ stop(): Promise<void>;
32
+ stats(): Promise<QueueStats>;
33
+ /** Process one batch immediately. Useful for tests, schedulers, and manual drains. */
34
+ processOnce(): Promise<number>;
35
+ private schedule;
36
+ private poll;
37
+ private processJob;
38
+ private failOrRetry;
39
+ }
40
+ //# sourceMappingURL=db-job-queue.d.ts.map
@@ -0,0 +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;AAOhF,OAAO,KAAK,EAAE,cAAc,EAAO,UAAU,EAAE,QAAQ,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE7G,kFAAkF;AAClF,qBAAa,UAAU,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,QAAQ,CAAC,CAAC,CAAC;IAC3C,QAAQ,CAAC,GAAG,EAAE,WAAW;gBAAhB,GAAG,EAAE,WAAW;IAE/B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAM5E,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;IAM3F,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;CAGrC;AAED,qFAAqF;AACrF,qBAAa,eAAe,CAAC,CAAC,GAAG,OAAO,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAc7D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAbxB,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,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAK;gBAGA,GAAG,EAAE,WAAW,EACjC,MAAM,GAAE,mBAAwB;IAWpC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI;IAI9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAarB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC;IAIlC,sFAAsF;IAChF,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IAyBpC,OAAO,CAAC,QAAQ;YAMF,IAAI;YASJ,UAAU;YAoBV,WAAW;CAY5B"}
@@ -0,0 +1,154 @@
1
+ import { getQueueJobCompletedTotal, getQueueJobEnqueuedTotal, getQueueJobFailedTotal, getQueueJobProcessingDuration, } from '../telemetry/metrics.js';
2
+ /** DB-backed job queue implementation over `@gobing-ai/ts-db`'s `QueueJobDao`. */
3
+ export class DBJobQueue {
4
+ dao;
5
+ constructor(dao) {
6
+ this.dao = dao;
7
+ }
8
+ async enqueue(type, payload, options) {
9
+ const id = await this.dao.enqueue(type, payload, options);
10
+ getQueueJobEnqueuedTotal().add(1, { type });
11
+ return id;
12
+ }
13
+ async enqueueBatch(jobs) {
14
+ const ids = await this.dao.enqueueBatch(jobs);
15
+ getQueueJobEnqueuedTotal().add(jobs.length);
16
+ return ids;
17
+ }
18
+ async stats() {
19
+ return this.dao.getStats();
20
+ }
21
+ }
22
+ /** DB-backed queue consumer with polling, retry, and visibility-timeout handling. */
23
+ export class DBQueueConsumer {
24
+ dao;
25
+ handlers = new Map();
26
+ pollInterval;
27
+ batchSize;
28
+ maxConcurrency;
29
+ visibilityTimeout;
30
+ baseDelay;
31
+ maxDelay;
32
+ drainTimeoutMs;
33
+ timer = null;
34
+ running = false;
35
+ inFlight = 0;
36
+ constructor(dao, config = {}) {
37
+ this.dao = dao;
38
+ this.pollInterval = config.pollInterval ?? 1_000;
39
+ this.batchSize = config.batchSize ?? 10;
40
+ this.maxConcurrency = config.maxConcurrency ?? this.batchSize;
41
+ this.visibilityTimeout = config.visibilityTimeout ?? 30_000;
42
+ this.baseDelay = config.baseDelay ?? 1_000;
43
+ this.maxDelay = config.maxDelay ?? 60_000;
44
+ this.drainTimeoutMs = config.drainTimeoutMs ?? 30_000;
45
+ }
46
+ register(type, handler) {
47
+ this.handlers.set(type, handler);
48
+ }
49
+ async start() {
50
+ if (this.running)
51
+ return;
52
+ this.running = true;
53
+ this.schedule(0);
54
+ }
55
+ async stop() {
56
+ this.running = false;
57
+ if (this.timer !== null) {
58
+ clearTimeout(this.timer);
59
+ this.timer = null;
60
+ }
61
+ const deadline = Date.now() + this.drainTimeoutMs;
62
+ while (this.inFlight > 0 && Date.now() < deadline) {
63
+ await sleep(10);
64
+ }
65
+ }
66
+ async stats() {
67
+ return this.dao.getStats();
68
+ }
69
+ /** Process one batch immediately. Useful for tests, schedulers, and manual drains. */
70
+ async processOnce() {
71
+ await this.dao.resetStuckJobs(this.visibilityTimeout);
72
+ await this.dao.failExpiredJobs();
73
+ const jobs = await this.dao.claimReady(this.batchSize);
74
+ let processed = 0;
75
+ for (let index = 0; index < jobs.length; index += this.maxConcurrency) {
76
+ const batch = jobs.slice(index, index + this.maxConcurrency);
77
+ await Promise.all(batch.map(async (job) => {
78
+ this.inFlight += 1;
79
+ try {
80
+ await this.processJob(job);
81
+ processed += 1;
82
+ }
83
+ finally {
84
+ this.inFlight -= 1;
85
+ }
86
+ }));
87
+ }
88
+ return processed;
89
+ }
90
+ schedule(delay) {
91
+ this.timer = setTimeout(() => {
92
+ void this.poll();
93
+ }, delay);
94
+ }
95
+ async poll() {
96
+ if (!this.running)
97
+ return;
98
+ try {
99
+ await this.processOnce();
100
+ }
101
+ finally {
102
+ if (this.running)
103
+ this.schedule(this.pollInterval);
104
+ }
105
+ }
106
+ async processJob(record) {
107
+ const job = toJob(record);
108
+ const handler = this.handlers.get(job.type);
109
+ if (handler === undefined) {
110
+ await this.failOrRetry(job, new Error(`No handler registered for job type "${job.type}"`));
111
+ return;
112
+ }
113
+ const startMs = performance.now();
114
+ try {
115
+ await handler(job);
116
+ await this.dao.markCompleted(job.id);
117
+ getQueueJobCompletedTotal().add(1, { type: job.type });
118
+ getQueueJobProcessingDuration().record(performance.now() - startMs, { type: job.type });
119
+ }
120
+ catch (error) {
121
+ getQueueJobProcessingDuration().record(performance.now() - startMs, { type: job.type });
122
+ await this.failOrRetry(job, error);
123
+ }
124
+ }
125
+ async failOrRetry(job, error) {
126
+ const attempts = job.attempts + 1;
127
+ const message = error instanceof Error ? error.message : String(error);
128
+ if (attempts >= job.maxRetries) {
129
+ await this.dao.markFailed(job.id, attempts, message);
130
+ getQueueJobFailedTotal().add(1, { type: job.type });
131
+ return;
132
+ }
133
+ const delay = Math.min(this.maxDelay, this.baseDelay * 2 ** Math.max(0, attempts - 1));
134
+ await this.dao.markForRetry(job.id, attempts, message, Date.now() + delay);
135
+ }
136
+ }
137
+ function toJob(record) {
138
+ return {
139
+ id: record.id,
140
+ type: record.type,
141
+ payload: JSON.parse(record.payload),
142
+ status: record.status,
143
+ attempts: record.attempts,
144
+ maxRetries: record.maxRetries,
145
+ createdAt: record.createdAt,
146
+ updatedAt: record.updatedAt,
147
+ nextRetryAt: record.nextRetryAt,
148
+ lastError: record.lastError,
149
+ processingAt: record.processingAt,
150
+ };
151
+ }
152
+ function sleep(ms) {
153
+ return new Promise((resolve) => setTimeout(resolve, ms));
154
+ }
@@ -1,2 +1,3 @@
1
+ export { DBJobQueue, DBQueueConsumer } from './db-job-queue';
1
2
  export type { EnqueueOptions, Job, JobHandler, JobQueue, QueueConsumer, QueueConsumerConfig, QueueStats, } from './types';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/job-queue/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/job-queue/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC7D,YAAY,EACR,cAAc,EACd,GAAG,EACH,UAAU,EACV,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,UAAU,GACb,MAAM,SAAS,CAAC"}
@@ -0,0 +1 @@
1
+ export { DBJobQueue, DBQueueConsumer } from './db-job-queue.js';
@@ -1,10 +1,8 @@
1
1
  /**
2
2
  * Job queue types for async work processing with retry.
3
3
  *
4
- * **Types only** — the DB-backed `DbQueue` and `DbConsumer` implementations
5
- * that drive the job-queue subsystem live in `@spur/core`. These interfaces
6
- * are provided here so `EventBus` and other infra components can reference
7
- * the queue contract without a circular dependency on the DB layer.
4
+ * The concrete DB-backed implementations live beside these interfaces in
5
+ * `DBJobQueue` and `DBQueueConsumer`.
8
6
  */
9
7
  export interface Job<T = unknown> {
10
8
  id: string;
@@ -30,6 +28,7 @@ export interface JobQueue<T = unknown> {
30
28
  type: string;
31
29
  payload: T;
32
30
  } & EnqueueOptions>): Promise<string[]>;
31
+ stats(): Promise<QueueStats>;
33
32
  }
34
33
  export type JobHandler<T = unknown> = (job: Job<T>) => Promise<void>;
35
34
  export interface QueueStats {
@@ -46,7 +45,6 @@ export interface QueueConsumerConfig {
46
45
  baseDelay?: number;
47
46
  maxDelay?: number;
48
47
  drainTimeoutMs?: number;
49
- maxPollBackoff?: number;
50
48
  }
51
49
  export interface QueueConsumer<T = unknown> {
52
50
  register(type: string, handler: JobHandler<T>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/job-queue/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,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,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,MAAM,WAAW,cAAc;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,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;CAC/F;AAED,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAErE,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,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,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,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,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,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,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,MAAM,WAAW,cAAc;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,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,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAErE,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,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,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,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,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;CAChC"}
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * Job queue types for async work processing with retry.
3
3
  *
4
- * **Types only** — the DB-backed `DbQueue` and `DbConsumer` implementations
5
- * that drive the job-queue subsystem live in `@spur/core`. These interfaces
6
- * are provided here so `EventBus` and other infra components can reference
7
- * the queue contract without a circular dependency on the DB layer.
4
+ * The concrete DB-backed implementations live beside these interfaces in
5
+ * `DBJobQueue` and `DBQueueConsumer`.
8
6
  */
package/dist/logger.js CHANGED
@@ -13,15 +13,14 @@ const LEVEL_ORDER = {
13
13
  };
14
14
  class ConsoleLogger {
15
15
  category;
16
- minLevel;
17
16
  context;
18
- constructor(category, minLevel = 'info', context = {}) {
17
+ constructor(category, context = {}) {
19
18
  this.category = category;
20
- this.minLevel = minLevel;
21
19
  this.context = { category, ...context };
22
20
  }
23
21
  log(level, msg, data) {
24
- if (LEVEL_ORDER[level] < LEVEL_ORDER[this.minLevel])
22
+ // Read the level dynamically so re-initialization affects cached loggers too.
23
+ if (LEVEL_ORDER[level] < LEVEL_ORDER[globalLevel])
25
24
  return;
26
25
  if (globalMuted)
27
26
  return;
@@ -68,7 +67,7 @@ class ConsoleLogger {
68
67
  this.log('fatal', msg, data);
69
68
  }
70
69
  child(context) {
71
- return new ConsoleLogger(this.category, this.minLevel, { ...this.context, ...context });
70
+ return new ConsoleLogger(this.category, { ...this.context, ...context });
72
71
  }
73
72
  }
74
73
  const loggers = new Map();
@@ -87,7 +86,7 @@ export function getLogger(category) {
87
86
  const existing = loggers.get(category);
88
87
  if (existing)
89
88
  return existing;
90
- const logger = new ConsoleLogger(category, globalLevel);
89
+ const logger = new ConsoleLogger(category);
91
90
  loggers.set(category, logger);
92
91
  return logger;
93
92
  }
@@ -1,7 +1,3 @@
1
- /**
2
- * Cloudflare Workers scheduler adapter using Cron Triggers.
3
- * Uses minimal local type declarations — no @cloudflare/workers-types dependency.
4
- */
5
1
  import type { ScheduledAction, SchedulerAdapter } from './types';
6
2
  interface CfScheduledEvent {
7
3
  cron: string;
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/scheduler/cloudflare.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE,UAAU,gBAAgB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,UAAU,cAAc;IACpB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,qBAAa,0BAA2B,YAAW,gBAAgB;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;;IAI9D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IAI/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;;OAGG;IACH,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI;CAM3E"}
1
+ {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/scheduler/cloudflare.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEjE,UAAU,gBAAgB;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,UAAU,cAAc;IACpB,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9C;AAED,qBAAa,0BAA2B,YAAW,gBAAgB;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;;IAI9D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,IAAI;IAI/C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;;OAGG;IACH,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI;CAY3E"}
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Cloudflare Workers scheduler adapter using Cron Triggers.
3
+ * Uses minimal local type declarations — no @cloudflare/workers-types dependency.
4
+ */
5
+ import { getSchedulerJobExecutedTotal, getSchedulerJobFailedTotal } from '../telemetry/metrics.js';
1
6
  export class CloudflareSchedulerAdapter {
2
7
  entries = new Map();
3
8
  constructor() { }
@@ -18,7 +23,11 @@ export class CloudflareSchedulerAdapter {
18
23
  handleScheduledEvent(event, ctx) {
19
24
  const action = this.entries.get(event.cron);
20
25
  if (action) {
21
- ctx.waitUntil(action());
26
+ getSchedulerJobExecutedTotal().add(1, { cron: event.cron });
27
+ ctx.waitUntil(action().catch((error) => {
28
+ getSchedulerJobFailedTotal().add(1, { cron: event.cron });
29
+ throw error;
30
+ }));
22
31
  }
23
32
  }
24
33
  }
@@ -1,6 +1,3 @@
1
- /**
2
- * Scheduler factory — selects adapter based on runtime.
3
- */
4
1
  import type { ScheduledAction, SchedulerAdapter } from './types';
5
2
  export declare function setSchedulerAdapter(adapter: SchedulerAdapter): void;
6
3
  /** Reset the scheduler adapter singleton. For testing. */
@@ -1 +1 @@
1
- {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/scheduler/factory.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAIjE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAEnE;AAED,0DAA0D;AAC1D,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,SAAS,CAElE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,GAAG,gBAAgB,CAa9F"}
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/scheduler/factory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAIjE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAEnE;AAED,0DAA0D;AAC1D,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C;AAED,wBAAgB,mBAAmB,IAAI,gBAAgB,GAAG,SAAS,CAElE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,GAAG,gBAAgB,CAa9F"}