@gobing-ai/ts-infra 0.2.8 → 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 (61) hide show
  1. package/README.md +49 -13
  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 +1 -3
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -2
  7. package/dist/job-queue/db-job-queue.d.ts.map +1 -1
  8. package/dist/job-queue/db-job-queue.js +12 -2
  9. package/dist/job-queue/types.d.ts +1 -1
  10. package/dist/job-queue/types.d.ts.map +1 -1
  11. package/dist/logger.js +5 -6
  12. package/dist/scheduler/cloudflare.d.ts +0 -4
  13. package/dist/scheduler/cloudflare.d.ts.map +1 -1
  14. package/dist/scheduler/cloudflare.js +10 -1
  15. package/dist/scheduler/factory.d.ts +0 -3
  16. package/dist/scheduler/factory.d.ts.map +1 -1
  17. package/dist/scheduler/factory.js +5 -14
  18. package/dist/scheduler/node.d.ts +0 -4
  19. package/dist/scheduler/node.d.ts.map +1 -1
  20. package/dist/scheduler/node.js +11 -0
  21. package/dist/telemetry/config.d.ts +0 -5
  22. package/dist/telemetry/config.d.ts.map +1 -1
  23. package/dist/telemetry/config.js +0 -3
  24. package/dist/telemetry/index.d.ts +1 -1
  25. package/dist/telemetry/index.d.ts.map +1 -1
  26. package/dist/telemetry/index.js +1 -1
  27. package/dist/telemetry/metrics.d.ts +1 -10
  28. package/dist/telemetry/metrics.d.ts.map +1 -1
  29. package/dist/telemetry/metrics.js +9 -26
  30. package/dist/telemetry/otel-node.d.ts +37 -0
  31. package/dist/telemetry/otel-node.d.ts.map +1 -0
  32. package/dist/telemetry/otel-node.js +85 -0
  33. package/dist/telemetry/sdk.d.ts +12 -1
  34. package/dist/telemetry/sdk.d.ts.map +1 -1
  35. package/dist/telemetry/sdk.js +17 -27
  36. package/package.json +36 -7
  37. package/src/event-bus/event-bus.ts +4 -0
  38. package/src/index.ts +0 -9
  39. package/src/job-queue/db-job-queue.ts +17 -2
  40. package/src/job-queue/types.ts +1 -1
  41. package/src/logger.ts +4 -4
  42. package/src/scheduler/cloudflare.ts +8 -1
  43. package/src/scheduler/factory.ts +2 -15
  44. package/src/scheduler/node.ts +10 -0
  45. package/src/telemetry/config.ts +0 -8
  46. package/src/telemetry/index.ts +0 -6
  47. package/src/telemetry/metrics.ts +9 -38
  48. package/src/telemetry/otel-node.ts +116 -0
  49. package/src/telemetry/sdk.ts +18 -30
  50. package/dist/events/app-events.d.ts +0 -7
  51. package/dist/events/app-events.d.ts.map +0 -1
  52. package/dist/events/app-events.js +0 -4
  53. package/dist/events/create-system-bus.d.ts +0 -6
  54. package/dist/events/create-system-bus.d.ts.map +0 -1
  55. package/dist/events/create-system-bus.js +0 -7
  56. package/dist/events/index.d.ts +0 -3
  57. package/dist/events/index.d.ts.map +0 -1
  58. package/dist/events/index.js +0 -1
  59. package/src/events/app-events.ts +0 -8
  60. package/src/events/create-system-bus.ts +0 -8
  61. package/src/events/index.ts +0 -2
@@ -0,0 +1,37 @@
1
+ /** Options for {@link initNodeTelemetry}. */
2
+ export interface NodeTelemetryOptions {
3
+ /** Logical service name emitted on every span/metric (resource attribute). */
4
+ serviceName: string;
5
+ /** Optional service version (resource attribute). */
6
+ serviceVersion?: string;
7
+ /**
8
+ * OTLP/HTTP collector endpoint base, e.g. `http://localhost:4318`.
9
+ * Signal-specific paths (`/v1/traces`, `/v1/metrics`) are appended by the
10
+ * exporters. When omitted, the OTel SDK default (`http://localhost:4318`)
11
+ * applies, honouring the standard `OTEL_EXPORTER_OTLP_*` env vars.
12
+ */
13
+ endpoint?: string;
14
+ /** Extra headers sent on every OTLP request (e.g. an auth token). */
15
+ headers?: Record<string, string>;
16
+ /** Metric export interval in ms. Default: 60_000. */
17
+ metricExportIntervalMs?: number;
18
+ /** When false, skip metric export and wire traces only. Default: true. */
19
+ metrics?: boolean;
20
+ }
21
+ /**
22
+ * Wire Node OTLP/HTTP export for traces and (by default) metrics, registering
23
+ * both providers globally. Idempotent: a second call is a no-op until
24
+ * {@link shutdownNodeTelemetry} runs.
25
+ *
26
+ * Call once at process startup, before the first span/metric is recorded.
27
+ */
28
+ export declare function initNodeTelemetry(options: NodeTelemetryOptions): void;
29
+ /**
30
+ * Flush and shut down the providers installed by {@link initNodeTelemetry}.
31
+ * Safe to call when nothing was initialised. Wire this to SIGTERM/SIGINT for a
32
+ * clean drain of buffered spans and metrics on shutdown.
33
+ */
34
+ export declare function shutdownNodeTelemetry(): Promise<void>;
35
+ /** Test-only: reset module state without requiring exporter teardown to resolve. */
36
+ export declare function _resetNodeTelemetry(): void;
37
+ //# sourceMappingURL=otel-node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otel-node.d.ts","sourceRoot":"","sources":["../../src/telemetry/otel-node.ts"],"names":[],"mappings":"AAsBA,6CAA6C;AAC7C,MAAM,WAAW,oBAAoB;IACjC,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,qDAAqD;IACrD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,0EAA0E;IAC1E,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAKD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAoCrE;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAY3D;AAED,oFAAoF;AACpF,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Opt-in Node OTLP wiring for `@gobing-ai/ts-infra`.
3
+ *
4
+ * The core telemetry surface (`initTelemetry`, `getTracer`, the metric getters)
5
+ * only *instruments* — it records spans and metrics against whatever OTel
6
+ * provider is registered globally, and degrades to no-ops when none is. This
7
+ * subpath is the convenience layer that actually *exports*: it builds Node
8
+ * tracer + meter providers wired to OTLP/HTTP and registers them globally, so
9
+ * the already-instrumented call sites start flowing without any core change.
10
+ *
11
+ * Import this only when you want turnkey export. It pulls the OTLP exporter
12
+ * packages (declared as optional peers); the main barrel never imports them, so
13
+ * BYO-collector consumers stay lean and avoid global-provider conflicts.
14
+ */
15
+ import { metrics } from '@opentelemetry/api';
16
+ import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
17
+ import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
18
+ import { resourceFromAttributes } from '@opentelemetry/resources';
19
+ import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
20
+ import { BatchSpanProcessor, NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
21
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
22
+ let traceProvider;
23
+ let meterProvider;
24
+ /**
25
+ * Wire Node OTLP/HTTP export for traces and (by default) metrics, registering
26
+ * both providers globally. Idempotent: a second call is a no-op until
27
+ * {@link shutdownNodeTelemetry} runs.
28
+ *
29
+ * Call once at process startup, before the first span/metric is recorded.
30
+ */
31
+ export function initNodeTelemetry(options) {
32
+ if (traceProvider)
33
+ return;
34
+ const { serviceName, serviceVersion, endpoint, headers, metricExportIntervalMs = 60_000 } = options;
35
+ const wireMetrics = options.metrics ?? true;
36
+ const resource = resourceFromAttributes({
37
+ [ATTR_SERVICE_NAME]: serviceName,
38
+ ...(serviceVersion ? { [ATTR_SERVICE_VERSION]: serviceVersion } : {}),
39
+ });
40
+ const traceExporter = new OTLPTraceExporter({
41
+ ...(endpoint ? { url: `${endpoint.replace(/\/$/, '')}/v1/traces` } : {}),
42
+ ...(headers ? { headers } : {}),
43
+ });
44
+ traceProvider = new NodeTracerProvider({
45
+ resource,
46
+ spanProcessors: [new BatchSpanProcessor(traceExporter)],
47
+ });
48
+ traceProvider.register();
49
+ if (wireMetrics) {
50
+ const metricExporter = new OTLPMetricExporter({
51
+ ...(endpoint ? { url: `${endpoint.replace(/\/$/, '')}/v1/metrics` } : {}),
52
+ ...(headers ? { headers } : {}),
53
+ });
54
+ const reader = new PeriodicExportingMetricReader({
55
+ exporter: metricExporter,
56
+ exportIntervalMillis: metricExportIntervalMs,
57
+ });
58
+ meterProvider = new MeterProvider({ resource, readers: [reader] });
59
+ metrics.setGlobalMeterProvider(meterProvider);
60
+ }
61
+ }
62
+ /**
63
+ * Flush and shut down the providers installed by {@link initNodeTelemetry}.
64
+ * Safe to call when nothing was initialised. Wire this to SIGTERM/SIGINT for a
65
+ * clean drain of buffered spans and metrics on shutdown.
66
+ */
67
+ export async function shutdownNodeTelemetry() {
68
+ const pending = [];
69
+ if (meterProvider) {
70
+ pending.push(meterProvider.shutdown());
71
+ meterProvider = undefined;
72
+ }
73
+ if (traceProvider) {
74
+ pending.push(traceProvider.shutdown());
75
+ traceProvider = undefined;
76
+ }
77
+ metrics.disable();
78
+ await Promise.all(pending);
79
+ }
80
+ /** Test-only: reset module state without requiring exporter teardown to resolve. */
81
+ export function _resetNodeTelemetry() {
82
+ meterProvider = undefined;
83
+ traceProvider = undefined;
84
+ metrics.disable();
85
+ }
@@ -1,11 +1,22 @@
1
1
  /**
2
- * OpenTelemetry SDK initialisation and tracer provider management.
2
+ * Telemetry enablement + tracer access.
3
+ *
4
+ * The core only *instruments* against whatever tracer provider is registered
5
+ * globally — it never constructs or owns a provider. Provider + exporter wiring
6
+ * is the consumer's concern: either the host app registers its own OTel SDK, or
7
+ * it opts into `@gobing-ai/ts-infra/otel-node` for turnkey OTLP export. This
8
+ * keeps the main barrel free of any SDK runtime dependency.
3
9
  */
4
10
  import { type Tracer } from '@opentelemetry/api';
5
11
  import type { TelemetryConfig } from './config';
6
12
  export declare function getResolvedConfig(): TelemetryConfig;
13
+ /**
14
+ * Resolve telemetry config and mark telemetry enabled. Does not register any
15
+ * provider — spans flow to the globally-registered provider (none ⇒ no-op).
16
+ */
7
17
  export declare function initTelemetry(config?: Partial<TelemetryConfig>): void;
8
18
  export declare function shutdownTelemetry(): Promise<void>;
19
+ /** The infra tracer from the globally-registered provider (no-op when none). */
9
20
  export declare function getTracer(): Tracer;
10
21
  export declare function isTelemetryEnabled(): boolean;
11
22
  export declare function _resetTelemetry(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/telemetry/sdk.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,KAAK,MAAM,EAAS,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAShD,wBAAgB,iBAAiB,IAAI,eAAe,CAEnD;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAarE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAQvD;AAED,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAgB,eAAe,IAAI,IAAI,CAWtC;AAED,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/telemetry/sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,KAAK,MAAM,EAAS,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAShD,wBAAgB,iBAAiB,IAAI,eAAe,CAEnD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAIrE;AAED,wBAAgB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAGjD;AAED,gFAAgF;AAChF,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AAED,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC"}
@@ -1,53 +1,43 @@
1
1
  /**
2
- * OpenTelemetry SDK initialisation and tracer provider management.
2
+ * Telemetry enablement + tracer access.
3
+ *
4
+ * The core only *instruments* against whatever tracer provider is registered
5
+ * globally — it never constructs or owns a provider. Provider + exporter wiring
6
+ * is the consumer's concern: either the host app registers its own OTel SDK, or
7
+ * it opts into `@gobing-ai/ts-infra/otel-node` for turnkey OTLP export. This
8
+ * keeps the main barrel free of any SDK runtime dependency.
3
9
  */
4
10
  import { trace } from '@opentelemetry/api';
5
- import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
6
11
  import { getTelemetryConfig } from './config.js';
7
- let tracerProvider;
12
+ const TRACER_NAME = '@gobing-ai/ts-infra';
13
+ const TRACER_VERSION = '0.1.0';
8
14
  let telemetryInitialized = false;
9
15
  let resolvedConfig = getTelemetryConfig();
10
- const DEFAULT_TRACER = trace.getTracer('@gobing-ai/ts-infra', '0.1.0');
11
16
  export function getResolvedConfig() {
12
17
  return resolvedConfig;
13
18
  }
19
+ /**
20
+ * Resolve telemetry config and mark telemetry enabled. Does not register any
21
+ * provider — spans flow to the globally-registered provider (none ⇒ no-op).
22
+ */
14
23
  export function initTelemetry(config) {
15
24
  if (telemetryInitialized)
16
25
  return;
17
26
  resolvedConfig = { ...getTelemetryConfig(), ...config };
18
- if (!resolvedConfig.enabled) {
19
- telemetryInitialized = true;
20
- return;
21
- }
22
- tracerProvider = new NodeTracerProvider();
23
- tracerProvider.register();
24
27
  telemetryInitialized = true;
25
28
  }
26
- export async function shutdownTelemetry() {
27
- if (!tracerProvider) {
28
- telemetryInitialized = false;
29
- return;
30
- }
31
- await tracerProvider.shutdown();
32
- tracerProvider = undefined;
29
+ export function shutdownTelemetry() {
33
30
  telemetryInitialized = false;
31
+ return Promise.resolve();
34
32
  }
33
+ /** The infra tracer from the globally-registered provider (no-op when none). */
35
34
  export function getTracer() {
36
- return tracerProvider?.getTracer('@gobing-ai/ts-infra', '0.1.0') ?? DEFAULT_TRACER;
35
+ return trace.getTracer(TRACER_NAME, TRACER_VERSION);
37
36
  }
38
37
  export function isTelemetryEnabled() {
39
38
  return telemetryInitialized && resolvedConfig.enabled;
40
39
  }
41
40
  export function _resetTelemetry() {
42
- if (tracerProvider) {
43
- try {
44
- tracerProvider.shutdown();
45
- }
46
- catch {
47
- /* swallow */
48
- }
49
- tracerProvider = undefined;
50
- }
51
41
  telemetryInitialized = false;
52
42
  resolvedConfig = getTelemetryConfig();
53
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/ts-infra",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
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",
@@ -31,6 +31,10 @@
31
31
  ".": {
32
32
  "types": "./dist/index.d.ts",
33
33
  "import": "./dist/index.js"
34
+ },
35
+ "./otel-node": {
36
+ "types": "./dist/telemetry/otel-node.d.ts",
37
+ "import": "./dist/telemetry/otel-node.js"
34
38
  }
35
39
  },
36
40
  "files": [
@@ -50,18 +54,43 @@
50
54
  "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"
51
55
  },
52
56
  "dependencies": {
53
- "@gobing-ai/ts-db": "^0.2.8",
54
- "@gobing-ai/ts-runtime": "^0.2.8"
57
+ "@gobing-ai/ts-db": "^0.2.9"
55
58
  },
56
59
  "peerDependencies": {
57
- "@opentelemetry/api": "^1.9.0"
60
+ "@opentelemetry/api": "^1.9.0",
61
+ "@opentelemetry/sdk-trace-node": "^2.0.0",
62
+ "@opentelemetry/sdk-metrics": "^2.0.0",
63
+ "@opentelemetry/resources": "^2.0.0",
64
+ "@opentelemetry/semantic-conventions": "^1.30.0",
65
+ "@opentelemetry/exporter-trace-otlp-http": ">=0.200.0",
66
+ "@opentelemetry/exporter-metrics-otlp-http": ">=0.200.0"
67
+ },
68
+ "peerDependenciesMeta": {
69
+ "@opentelemetry/sdk-trace-node": {
70
+ "optional": true
71
+ },
72
+ "@opentelemetry/sdk-metrics": {
73
+ "optional": true
74
+ },
75
+ "@opentelemetry/resources": {
76
+ "optional": true
77
+ },
78
+ "@opentelemetry/exporter-trace-otlp-http": {
79
+ "optional": true
80
+ },
81
+ "@opentelemetry/exporter-metrics-otlp-http": {
82
+ "optional": true
83
+ }
58
84
  },
59
85
  "devDependencies": {
60
86
  "@types/bun": "1.3.14",
61
87
  "@opentelemetry/api": "^1.9.0",
62
- "@opentelemetry/sdk-trace-node": "^1.30.0",
63
- "@opentelemetry/sdk-metrics": "^1.30.0",
64
- "@opentelemetry/semantic-conventions": "^1.30.0"
88
+ "@opentelemetry/sdk-trace-node": "^2.0.0",
89
+ "@opentelemetry/sdk-metrics": "^2.0.0",
90
+ "@opentelemetry/resources": "^2.0.0",
91
+ "@opentelemetry/semantic-conventions": "^1.30.0",
92
+ "@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
93
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.200.0"
65
94
  },
66
95
  "publishConfig": {
67
96
  "access": "public"
@@ -1,5 +1,6 @@
1
1
  import type { JobQueue } from '../job-queue/types';
2
2
  import { getLogger, type Logger } from '../logger';
3
+ import { getEventbusEmitsTotal, getEventbusErrorsTotal } from '../telemetry/metrics';
3
4
  import type {
4
5
  AsyncEnqueuedDetail,
5
6
  BusLifecycleEvents,
@@ -144,6 +145,9 @@ export class EventBus<TEvents extends EventMap> {
144
145
  const durationMs = performance.now() - startMs;
145
146
  const detail = args.length === 1 ? args[0] : args.length > 1 ? args : undefined;
146
147
 
148
+ getEventbusEmitsTotal().add(1, { event: eventName });
149
+ if (errors > 0) getEventbusErrorsTotal().add(errors, { event: eventName });
150
+
147
151
  this.publishEmitDone({ event: eventName, syncCount, asyncCount, emitDurationMs: durationMs, errors, detail });
148
152
 
149
153
  if (syncCount === 0 && asyncCount === 0) {
package/src/index.ts CHANGED
@@ -4,9 +4,6 @@ export { APIClient, type APIClientConfig, APIError, type RequestOptions } from '
4
4
  // Event Bus
5
5
  export { EventBus, type EventMap, type SubscribeOptions } from './event-bus/index';
6
6
 
7
- // Events
8
- export type { AppEvents, AppInternalEvents } from './events/index';
9
- export { createSystemBus } from './events/index';
10
7
  export type {
11
8
  EnqueueOptions,
12
9
  Job,
@@ -40,17 +37,11 @@ export {
40
37
  addSpanEvent,
41
38
  extractSqlOperation,
42
39
  getActiveSpan,
43
- getDbOperationDuration,
44
- getDbOperationErrors,
45
- getDbOperationTotal,
46
40
  getEventbusEmitsTotal,
47
41
  getEventbusErrorsTotal,
48
42
  getHttpClientRequestDuration,
49
43
  getHttpClientRequestErrors,
50
44
  getHttpClientRequestTotal,
51
- getHttpServerRequestDuration,
52
- getHttpServerRequestErrors,
53
- getHttpServerRequestTotal,
54
45
  getQueueJobCompletedTotal,
55
46
  getQueueJobEnqueuedTotal,
56
47
  getQueueJobFailedTotal,
@@ -1,4 +1,10 @@
1
1
  import type { QueueJobDao, QueueJobRecord, QueueStats } from '@gobing-ai/ts-db';
2
+ import {
3
+ getQueueJobCompletedTotal,
4
+ getQueueJobEnqueuedTotal,
5
+ getQueueJobFailedTotal,
6
+ getQueueJobProcessingDuration,
7
+ } from '../telemetry/metrics';
2
8
  import type { EnqueueOptions, Job, JobHandler, JobQueue, QueueConsumer, QueueConsumerConfig } from './types';
3
9
 
4
10
  /** DB-backed job queue implementation over `@gobing-ai/ts-db`'s `QueueJobDao`. */
@@ -6,11 +12,15 @@ export class DBJobQueue<T = unknown> implements JobQueue<T> {
6
12
  constructor(readonly dao: QueueJobDao) {}
7
13
 
8
14
  async enqueue(type: string, payload: T, options?: EnqueueOptions): Promise<string> {
9
- return this.dao.enqueue(type, payload, options);
15
+ const id = await this.dao.enqueue(type, payload, options);
16
+ getQueueJobEnqueuedTotal().add(1, { type });
17
+ return id;
10
18
  }
11
19
 
12
20
  async enqueueBatch(jobs: Array<{ type: string; payload: T } & EnqueueOptions>): Promise<string[]> {
13
- return this.dao.enqueueBatch(jobs);
21
+ const ids = await this.dao.enqueueBatch(jobs);
22
+ getQueueJobEnqueuedTotal().add(jobs.length);
23
+ return ids;
14
24
  }
15
25
 
16
26
  async stats(): Promise<QueueStats> {
@@ -121,10 +131,14 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
121
131
  return;
122
132
  }
123
133
 
134
+ const startMs = performance.now();
124
135
  try {
125
136
  await handler(job);
126
137
  await this.dao.markCompleted(job.id);
138
+ getQueueJobCompletedTotal().add(1, { type: job.type });
139
+ getQueueJobProcessingDuration().record(performance.now() - startMs, { type: job.type });
127
140
  } catch (error) {
141
+ getQueueJobProcessingDuration().record(performance.now() - startMs, { type: job.type });
128
142
  await this.failOrRetry(job, error);
129
143
  }
130
144
  }
@@ -134,6 +148,7 @@ export class DBQueueConsumer<T = unknown> implements QueueConsumer<T> {
134
148
  const message = error instanceof Error ? error.message : String(error);
135
149
  if (attempts >= job.maxRetries) {
136
150
  await this.dao.markFailed(job.id, attempts, message);
151
+ getQueueJobFailedTotal().add(1, { type: job.type });
137
152
  return;
138
153
  }
139
154
 
@@ -28,6 +28,7 @@ export interface EnqueueOptions {
28
28
  export interface JobQueue<T = unknown> {
29
29
  enqueue(type: string, payload: T, options?: EnqueueOptions): Promise<string>;
30
30
  enqueueBatch(jobs: Array<{ type: string; payload: T } & EnqueueOptions>): Promise<string[]>;
31
+ stats(): Promise<QueueStats>;
31
32
  }
32
33
 
33
34
  export type JobHandler<T = unknown> = (job: Job<T>) => Promise<void>;
@@ -47,7 +48,6 @@ export interface QueueConsumerConfig {
47
48
  baseDelay?: number;
48
49
  maxDelay?: number;
49
50
  drainTimeoutMs?: number;
50
- maxPollBackoff?: number;
51
51
  }
52
52
 
53
53
  export interface QueueConsumer<T = unknown> {
package/src/logger.ts CHANGED
@@ -30,14 +30,14 @@ class ConsoleLogger implements Logger {
30
30
 
31
31
  constructor(
32
32
  private readonly category: string,
33
- private readonly minLevel: LogLevel = 'info',
34
33
  context: Record<string, unknown> = {},
35
34
  ) {
36
35
  this.context = { category, ...context };
37
36
  }
38
37
 
39
38
  private log(level: LogLevel, msg: string, data?: Record<string, unknown>): void {
40
- if (LEVEL_ORDER[level] < LEVEL_ORDER[this.minLevel]) return;
39
+ // Read the level dynamically so re-initialization affects cached loggers too.
40
+ if (LEVEL_ORDER[level] < LEVEL_ORDER[globalLevel]) return;
41
41
  if (globalMuted) return;
42
42
 
43
43
  const entry = {
@@ -86,7 +86,7 @@ class ConsoleLogger implements Logger {
86
86
  }
87
87
 
88
88
  child(context: Record<string, unknown>): Logger {
89
- return new ConsoleLogger(this.category, this.minLevel, { ...this.context, ...context });
89
+ return new ConsoleLogger(this.category, { ...this.context, ...context });
90
90
  }
91
91
  }
92
92
 
@@ -109,7 +109,7 @@ export function getLogger(category: string): Logger {
109
109
  const existing = loggers.get(category);
110
110
  if (existing) return existing;
111
111
 
112
- const logger = new ConsoleLogger(category, globalLevel);
112
+ const logger = new ConsoleLogger(category);
113
113
  loggers.set(category, logger);
114
114
  return logger;
115
115
  }
@@ -2,6 +2,7 @@
2
2
  * Cloudflare Workers scheduler adapter using Cron Triggers.
3
3
  * Uses minimal local type declarations — no @cloudflare/workers-types dependency.
4
4
  */
5
+ import { getSchedulerJobExecutedTotal, getSchedulerJobFailedTotal } from '../telemetry/metrics';
5
6
  import type { ScheduledAction, SchedulerAdapter } from './types';
6
7
 
7
8
  interface CfScheduledEvent {
@@ -39,7 +40,13 @@ export class CloudflareSchedulerAdapter implements SchedulerAdapter {
39
40
  handleScheduledEvent(event: CfScheduledEvent, ctx: CfEventContext): void {
40
41
  const action = this.entries.get(event.cron);
41
42
  if (action) {
42
- ctx.waitUntil(action());
43
+ getSchedulerJobExecutedTotal().add(1, { cron: event.cron });
44
+ ctx.waitUntil(
45
+ action().catch((error: unknown) => {
46
+ getSchedulerJobFailedTotal().add(1, { cron: event.cron });
47
+ throw error;
48
+ }),
49
+ );
43
50
  }
44
51
  }
45
52
  }
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Scheduler factory — selects adapter based on runtime.
3
3
  */
4
+ import { NoopSchedulerAdapter } from './noop';
4
5
  import type { ScheduledAction, SchedulerAdapter } from './types';
5
6
 
6
7
  let runtimeAdapter: SchedulerAdapter | undefined;
@@ -30,7 +31,7 @@ export function getSchedulerAdapter(): SchedulerAdapter | undefined {
30
31
  export function initScheduler(cronEntries?: Array<[string, ScheduledAction]>): SchedulerAdapter {
31
32
  // Default: create a noop adapter. Apps inject their own via setSchedulerAdapter.
32
33
  if (!runtimeAdapter) {
33
- runtimeAdapter = createNoopScheduler();
34
+ runtimeAdapter = new NoopSchedulerAdapter();
34
35
  }
35
36
 
36
37
  if (cronEntries) {
@@ -41,17 +42,3 @@ export function initScheduler(cronEntries?: Array<[string, ScheduledAction]>): S
41
42
 
42
43
  return runtimeAdapter;
43
44
  }
44
-
45
- function createNoopScheduler(): SchedulerAdapter {
46
- return {
47
- register(): void {
48
- /* noop */
49
- },
50
- start(): Promise<void> {
51
- return Promise.resolve();
52
- },
53
- stop(): Promise<void> {
54
- return Promise.resolve();
55
- },
56
- };
57
- }
@@ -2,6 +2,11 @@
2
2
  * Node.js scheduler adapter using a simple setInterval-based approach.
3
3
  * No external cron library dependency — cron expressions are parsed minimally.
4
4
  */
5
+ import {
6
+ getSchedulerJobDuration,
7
+ getSchedulerJobExecutedTotal,
8
+ getSchedulerJobFailedTotal,
9
+ } from '../telemetry/metrics';
5
10
  import type { ScheduledAction, SchedulerAdapter } from './types';
6
11
 
7
12
  /** Simple helper to parse cron-like interval strings into milliseconds. */
@@ -74,10 +79,15 @@ export class NodeSchedulerAdapter implements SchedulerAdapter {
74
79
  }
75
80
 
76
81
  private async _onScheduledTick(entry: ScheduledEntry): Promise<void> {
82
+ const startMs = performance.now();
83
+ getSchedulerJobExecutedTotal().add(1, { cron: entry.cron });
77
84
  try {
78
85
  await entry.action();
79
86
  } catch {
80
87
  // Swallow — scheduler errors should not crash the process
88
+ getSchedulerJobFailedTotal().add(1, { cron: entry.cron });
89
+ } finally {
90
+ getSchedulerJobDuration().record(performance.now() - startMs, { cron: entry.cron });
81
91
  }
82
92
  }
83
93
  }
@@ -9,10 +9,6 @@ export interface TelemetryConfig {
9
9
  serviceName: string;
10
10
  /** Deployment environment (development, staging, production). */
11
11
  environment: string;
12
- /** OTLP exporter endpoint (e.g. `http://localhost:4318/v1/traces`). */
13
- exporterEndpoint?: string | undefined;
14
- /** Export protocol — only `http` is supported in v1. */
15
- exporterProtocol: 'http';
16
12
  /**
17
13
  * Debug-only DB statement capture.
18
14
  *
@@ -32,7 +28,6 @@ export interface TelemetryConfigPartial {
32
28
  enabled?: boolean | undefined;
33
29
  serviceName?: string | undefined;
34
30
  environment?: string | undefined;
35
- exporterEndpoint?: string | undefined;
36
31
  dbStatementDebug?: boolean | undefined;
37
32
  /** Deployment environment fallback (from app.env). */
38
33
  appEnv?: string | undefined;
@@ -42,7 +37,6 @@ const DEFAULTS = {
42
37
  enabled: true as const,
43
38
  serviceName: 'ts-libs' as const,
44
39
  environment: 'development' as const,
45
- exporterProtocol: 'http' as const,
46
40
  };
47
41
 
48
42
  /**
@@ -56,8 +50,6 @@ export function getTelemetryConfig(configPartial: TelemetryConfigPartial = {}):
56
50
  enabled,
57
51
  serviceName,
58
52
  environment: configPartial.environment ?? configPartial.appEnv ?? DEFAULTS.environment,
59
- exporterEndpoint: configPartial.exporterEndpoint,
60
- exporterProtocol: DEFAULTS.exporterProtocol,
61
53
  dbStatementDebug: configPartial.dbStatementDebug ?? false,
62
54
  };
63
55
  }
@@ -2,17 +2,11 @@ export { getTelemetryConfig, type TelemetryConfig, type TelemetryConfigPartial }
2
2
  export { extractSqlOperation, sanitizeSql } from './db-sanitize';
3
3
  export {
4
4
  type Counter,
5
- getDbOperationDuration,
6
- getDbOperationErrors,
7
- getDbOperationTotal,
8
5
  getEventbusEmitsTotal,
9
6
  getEventbusErrorsTotal,
10
7
  getHttpClientRequestDuration,
11
8
  getHttpClientRequestErrors,
12
9
  getHttpClientRequestTotal,
13
- getHttpServerRequestDuration,
14
- getHttpServerRequestErrors,
15
- getHttpServerRequestTotal,
16
10
  getQueueJobCompletedTotal,
17
11
  getQueueJobEnqueuedTotal,
18
12
  getQueueJobFailedTotal,