@gobing-ai/ts-infra 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
@@ -3,28 +3,21 @@
3
3
  * All degrade to no-ops when telemetry is disabled.
4
4
  */
5
5
  import { type Counter, type Histogram, metrics } from '@opentelemetry/api';
6
- import type { MeterProvider } from '@opentelemetry/sdk-metrics';
7
6
 
8
7
  export type { Counter, Histogram } from '@opentelemetry/api';
9
8
 
10
- import type { TelemetryConfig } from './config';
11
-
12
- let meterProvider: MeterProvider | undefined;
13
9
  let metricsInitialized = false;
14
10
 
15
11
  export function isMetricsInitialized(): boolean {
16
12
  return metricsInitialized;
17
13
  }
18
14
 
19
- export function getMeterProvider(): MeterProvider {
20
- return meterProvider ?? (metrics.getMeterProvider() as MeterProvider);
21
- }
22
-
23
15
  const METER_NAME = '@gobing-ai/ts-infra';
24
16
  const METER_VERSION = '0.1.0';
25
17
 
18
+ /** Meter from the globally-registered provider (no-op when none registered). */
26
19
  function getMeter() {
27
- return getMeterProvider().getMeter(METER_NAME, METER_VERSION);
20
+ return metrics.getMeter(METER_NAME, METER_VERSION);
28
21
  }
29
22
 
30
23
  // ── Instrument cache ────────────────────────────────────────────────
@@ -45,20 +38,6 @@ function getOrCreateHistogram(key: string, name: string, description: string, un
45
38
  return instruments[key] as Histogram;
46
39
  }
47
40
 
48
- // ── HTTP server ─────────────────────────────────────────────────────
49
-
50
- export function getHttpServerRequestTotal(): Counter {
51
- return getOrCreateCounter('httpSrvReq', 'http.server.request.total', 'Total inbound HTTP requests', '{request}');
52
- }
53
-
54
- export function getHttpServerRequestDuration(): Histogram {
55
- return getOrCreateHistogram('httpSrvDur', 'http.server.request.duration', 'Inbound HTTP request duration');
56
- }
57
-
58
- export function getHttpServerRequestErrors(): Counter {
59
- return getOrCreateCounter('httpSrvErr', 'http.server.request.errors', 'Inbound HTTP 5xx errors', '{error}');
60
- }
61
-
62
41
  // ── HTTP client ─────────────────────────────────────────────────────
63
42
 
64
43
  export function getHttpClientRequestTotal(): Counter {
@@ -73,20 +52,6 @@ export function getHttpClientRequestErrors(): Counter {
73
52
  return getOrCreateCounter('httpCliErr', 'http.client.request.errors', 'Outbound HTTP errors', '{error}');
74
53
  }
75
54
 
76
- // ── DB ──────────────────────────────────────────────────────────────
77
-
78
- export function getDbOperationTotal(): Counter {
79
- return getOrCreateCounter('dbOpTotal', 'db.client.operation.total', 'Total DB operations', '{operation}');
80
- }
81
-
82
- export function getDbOperationDuration(): Histogram {
83
- return getOrCreateHistogram('dbOpDur', 'db.client.operation.duration', 'DB operation duration');
84
- }
85
-
86
- export function getDbOperationErrors(): Counter {
87
- return getOrCreateCounter('dbOpErr', 'db.client.operation.errors', 'DB operation errors', '{error}');
88
- }
89
-
90
55
  // ── Event bus ───────────────────────────────────────────────────────
91
56
 
92
57
  export function getEventbusEmitsTotal(): Counter {
@@ -131,12 +96,18 @@ export function getSchedulerJobFailedTotal(): Counter {
131
96
 
132
97
  // ── Lifecycle ───────────────────────────────────────────────────────
133
98
 
134
- export function initMetrics(_config?: Partial<TelemetryConfig>): void {
99
+ export function initMetrics(): void {
135
100
  if (metricsInitialized) return;
136
101
  metricsInitialized = true;
137
102
  }
138
103
 
139
104
  export function shutdownMetrics(): Promise<void> {
105
+ // The meter provider is owned by whoever registered it globally (the host
106
+ // app or `@gobing-ai/ts-infra/otel-node`). Here we only drop the instrument
107
+ // cache so post-shutdown getters rebuild against a fresh meter.
108
+ for (const key of Object.keys(instruments)) {
109
+ instruments[key] = undefined;
110
+ }
140
111
  metricsInitialized = false;
141
112
  return Promise.resolve();
142
113
  }
@@ -0,0 +1,116 @@
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, type MetricReader, 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
+
23
+ /** Options for {@link initNodeTelemetry}. */
24
+ export interface NodeTelemetryOptions {
25
+ /** Logical service name emitted on every span/metric (resource attribute). */
26
+ serviceName: string;
27
+ /** Optional service version (resource attribute). */
28
+ serviceVersion?: string;
29
+ /**
30
+ * OTLP/HTTP collector endpoint base, e.g. `http://localhost:4318`.
31
+ * Signal-specific paths (`/v1/traces`, `/v1/metrics`) are appended by the
32
+ * exporters. When omitted, the OTel SDK default (`http://localhost:4318`)
33
+ * applies, honouring the standard `OTEL_EXPORTER_OTLP_*` env vars.
34
+ */
35
+ endpoint?: string;
36
+ /** Extra headers sent on every OTLP request (e.g. an auth token). */
37
+ headers?: Record<string, string>;
38
+ /** Metric export interval in ms. Default: 60_000. */
39
+ metricExportIntervalMs?: number;
40
+ /** When false, skip metric export and wire traces only. Default: true. */
41
+ metrics?: boolean;
42
+ }
43
+
44
+ let traceProvider: NodeTracerProvider | undefined;
45
+ let meterProvider: MeterProvider | undefined;
46
+
47
+ /**
48
+ * Wire Node OTLP/HTTP export for traces and (by default) metrics, registering
49
+ * both providers globally. Idempotent: a second call is a no-op until
50
+ * {@link shutdownNodeTelemetry} runs.
51
+ *
52
+ * Call once at process startup, before the first span/metric is recorded.
53
+ */
54
+ export function initNodeTelemetry(options: NodeTelemetryOptions): void {
55
+ if (traceProvider) return;
56
+
57
+ const { serviceName, serviceVersion, endpoint, headers, metricExportIntervalMs = 60_000 } = options;
58
+ const wireMetrics = options.metrics ?? true;
59
+
60
+ const resource = resourceFromAttributes({
61
+ [ATTR_SERVICE_NAME]: serviceName,
62
+ ...(serviceVersion ? { [ATTR_SERVICE_VERSION]: serviceVersion } : {}),
63
+ });
64
+
65
+ const traceExporter = new OTLPTraceExporter({
66
+ ...(endpoint ? { url: `${endpoint.replace(/\/$/, '')}/v1/traces` } : {}),
67
+ ...(headers ? { headers } : {}),
68
+ });
69
+
70
+ traceProvider = new NodeTracerProvider({
71
+ resource,
72
+ spanProcessors: [new BatchSpanProcessor(traceExporter)],
73
+ });
74
+ traceProvider.register();
75
+
76
+ if (wireMetrics) {
77
+ const metricExporter = new OTLPMetricExporter({
78
+ ...(endpoint ? { url: `${endpoint.replace(/\/$/, '')}/v1/metrics` } : {}),
79
+ ...(headers ? { headers } : {}),
80
+ });
81
+
82
+ const reader: MetricReader = new PeriodicExportingMetricReader({
83
+ exporter: metricExporter,
84
+ exportIntervalMillis: metricExportIntervalMs,
85
+ });
86
+
87
+ meterProvider = new MeterProvider({ resource, readers: [reader] });
88
+ metrics.setGlobalMeterProvider(meterProvider);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Flush and shut down the providers installed by {@link initNodeTelemetry}.
94
+ * Safe to call when nothing was initialised. Wire this to SIGTERM/SIGINT for a
95
+ * clean drain of buffered spans and metrics on shutdown.
96
+ */
97
+ export async function shutdownNodeTelemetry(): Promise<void> {
98
+ const pending: Array<Promise<void>> = [];
99
+ if (meterProvider) {
100
+ pending.push(meterProvider.shutdown());
101
+ meterProvider = undefined;
102
+ }
103
+ if (traceProvider) {
104
+ pending.push(traceProvider.shutdown());
105
+ traceProvider = undefined;
106
+ }
107
+ metrics.disable();
108
+ await Promise.all(pending);
109
+ }
110
+
111
+ /** Test-only: reset module state without requiring exporter teardown to resolve. */
112
+ export function _resetNodeTelemetry(): void {
113
+ meterProvider = undefined;
114
+ traceProvider = undefined;
115
+ metrics.disable();
116
+ }
@@ -1,48 +1,44 @@
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, trace } from '@opentelemetry/api';
5
- import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
6
11
  import type { TelemetryConfig } from './config';
7
12
  import { getTelemetryConfig } from './config';
8
13
 
9
- let tracerProvider: NodeTracerProvider | undefined;
14
+ const TRACER_NAME = '@gobing-ai/ts-infra';
15
+ const TRACER_VERSION = '0.1.0';
16
+
10
17
  let telemetryInitialized = false;
11
18
  let resolvedConfig: TelemetryConfig = getTelemetryConfig();
12
19
 
13
- const DEFAULT_TRACER = trace.getTracer('@gobing-ai/ts-infra', '0.1.0');
14
-
15
20
  export function getResolvedConfig(): TelemetryConfig {
16
21
  return resolvedConfig;
17
22
  }
18
23
 
24
+ /**
25
+ * Resolve telemetry config and mark telemetry enabled. Does not register any
26
+ * provider — spans flow to the globally-registered provider (none ⇒ no-op).
27
+ */
19
28
  export function initTelemetry(config?: Partial<TelemetryConfig>): void {
20
29
  if (telemetryInitialized) return;
21
-
22
30
  resolvedConfig = { ...getTelemetryConfig(), ...config };
23
-
24
- if (!resolvedConfig.enabled) {
25
- telemetryInitialized = true;
26
- return;
27
- }
28
-
29
- tracerProvider = new NodeTracerProvider();
30
- tracerProvider.register();
31
31
  telemetryInitialized = true;
32
32
  }
33
33
 
34
- export async function shutdownTelemetry(): Promise<void> {
35
- if (!tracerProvider) {
36
- telemetryInitialized = false;
37
- return;
38
- }
39
- await tracerProvider.shutdown();
40
- tracerProvider = undefined;
34
+ export function shutdownTelemetry(): Promise<void> {
41
35
  telemetryInitialized = false;
36
+ return Promise.resolve();
42
37
  }
43
38
 
39
+ /** The infra tracer from the globally-registered provider (no-op when none). */
44
40
  export function getTracer(): Tracer {
45
- return tracerProvider?.getTracer('@gobing-ai/ts-infra', '0.1.0') ?? DEFAULT_TRACER;
41
+ return trace.getTracer(TRACER_NAME, TRACER_VERSION);
46
42
  }
47
43
 
48
44
  export function isTelemetryEnabled(): boolean {
@@ -50,14 +46,6 @@ export function isTelemetryEnabled(): boolean {
50
46
  }
51
47
 
52
48
  export function _resetTelemetry(): void {
53
- if (tracerProvider) {
54
- try {
55
- tracerProvider.shutdown();
56
- } catch {
57
- /* swallow */
58
- }
59
- tracerProvider = undefined;
60
- }
61
49
  telemetryInitialized = false;
62
50
  resolvedConfig = getTelemetryConfig();
63
51
  }
@@ -1,7 +0,0 @@
1
- /**
2
- * Application event definitions — generic typed event map pattern.
3
- * Apps extend this with their own event types.
4
- */
5
- export type AppEvents = Record<string, (...args: never[]) => void>;
6
- export type AppInternalEvents = Record<string, (...args: never[]) => void>;
7
- //# sourceMappingURL=app-events.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-events.d.ts","sourceRoot":"","sources":["../../src/events/app-events.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC;AAEnE,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC"}
@@ -1,4 +0,0 @@
1
- /**
2
- * Application event definitions — generic typed event map pattern.
3
- * Apps extend this with their own event types.
4
- */
@@ -1,6 +0,0 @@
1
- import { EventBus } from '../event-bus/event-bus';
2
- /**
3
- * Factory that creates a configured system event bus.
4
- */
5
- export declare function createSystemBus(): EventBus<Record<string, (...args: never[]) => void>>;
6
- //# sourceMappingURL=create-system-bus.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"create-system-bus.d.ts","sourceRoot":"","sources":["../../src/events/create-system-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD;;GAEG;AACH,wBAAgB,eAAe,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,IAAI,CAAC,CAAC,CAEtF"}
@@ -1,7 +0,0 @@
1
- import { EventBus } from '../event-bus/event-bus.js';
2
- /**
3
- * Factory that creates a configured system event bus.
4
- */
5
- export function createSystemBus() {
6
- return new EventBus();
7
- }
@@ -1,3 +0,0 @@
1
- export type { AppEvents, AppInternalEvents } from './app-events';
2
- export { createSystemBus } from './create-system-bus';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/events/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
@@ -1 +0,0 @@
1
- export { createSystemBus } from './create-system-bus.js';
@@ -1,8 +0,0 @@
1
- /**
2
- * Application event definitions — generic typed event map pattern.
3
- * Apps extend this with their own event types.
4
- */
5
-
6
- export type AppEvents = Record<string, (...args: never[]) => void>;
7
-
8
- export type AppInternalEvents = Record<string, (...args: never[]) => void>;
@@ -1,8 +0,0 @@
1
- import { EventBus } from '../event-bus/event-bus';
2
-
3
- /**
4
- * Factory that creates a configured system event bus.
5
- */
6
- export function createSystemBus(): EventBus<Record<string, (...args: never[]) => void>> {
7
- return new EventBus();
8
- }
@@ -1,2 +0,0 @@
1
- export type { AppEvents, AppInternalEvents } from './app-events';
2
- export { createSystemBus } from './create-system-bus';