@foam-ai/node-cliengo 0.1.0-alpha.1

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 (39) hide show
  1. package/dist/node-cliengo/src/constants.d.ts +26 -0
  2. package/dist/node-cliengo/src/constants.js +49 -0
  3. package/dist/node-cliengo/src/http/express.d.ts +41 -0
  4. package/dist/node-cliengo/src/http/express.js +123 -0
  5. package/dist/node-cliengo/src/http/fastify.d.ts +28 -0
  6. package/dist/node-cliengo/src/http/fastify.js +101 -0
  7. package/dist/node-cliengo/src/http/request-context.d.ts +32 -0
  8. package/dist/node-cliengo/src/http/request-context.js +114 -0
  9. package/dist/node-cliengo/src/index.d.ts +26 -0
  10. package/dist/node-cliengo/src/index.js +42 -0
  11. package/dist/node-cliengo/src/init.d.ts +50 -0
  12. package/dist/node-cliengo/src/init.js +348 -0
  13. package/dist/node-cliengo/src/job.d.ts +31 -0
  14. package/dist/node-cliengo/src/job.js +86 -0
  15. package/dist/node-cliengo/src/logs/pino-destination.d.ts +31 -0
  16. package/dist/node-cliengo/src/logs/pino-destination.js +100 -0
  17. package/dist/node-cliengo/src/logs/pino-mixin.d.ts +22 -0
  18. package/dist/node-cliengo/src/logs/pino-mixin.js +40 -0
  19. package/dist/node-cliengo/src/logs/winston-format.d.ts +28 -0
  20. package/dist/node-cliengo/src/logs/winston-format.js +48 -0
  21. package/dist/node-cliengo/src/logs/winston-transport.d.ts +31 -0
  22. package/dist/node-cliengo/src/logs/winston-transport.js +93 -0
  23. package/dist/node-cliengo/src/metrics/instruments.d.ts +29 -0
  24. package/dist/node-cliengo/src/metrics/instruments.js +78 -0
  25. package/dist/node-cliengo/src/nr.d.ts +35 -0
  26. package/dist/node-cliengo/src/nr.js +71 -0
  27. package/dist/node-cliengo/src/sns.d.ts +19 -0
  28. package/dist/node-cliengo/src/sns.js +31 -0
  29. package/dist/node-cliengo/src/sqs.d.ts +35 -0
  30. package/dist/node-cliengo/src/sqs.js +110 -0
  31. package/dist/node-cliengo/src/trace-bridge.d.ts +39 -0
  32. package/dist/node-cliengo/src/trace-bridge.js +79 -0
  33. package/dist/node-cliengo/src/types.d.ts +115 -0
  34. package/dist/node-cliengo/src/types.js +26 -0
  35. package/dist/shared/constants.d.ts +1 -0
  36. package/dist/shared/constants.js +4 -0
  37. package/dist/shared/util.d.ts +9 -0
  38. package/dist/shared/util.js +21 -0
  39. package/package.json +54 -0
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ /**
3
+ * W3C trace context bridge between New Relic and OpenTelemetry.
4
+ *
5
+ * Why this file exists:
6
+ * NR owns the active trace (it creates transactions via http monkey-patching),
7
+ * but our OTel spans need to share the same traceId so Foam and NR traces
8
+ * correlate. This module reads NR's trace metadata and converts it to/from
9
+ * W3C traceparent format (`00-{traceId}-{spanId}-{flags}`).
10
+ *
11
+ * Functions:
12
+ * - buildTraceparent(): reads NR's active traceId/spanId, returns a W3C
13
+ * traceparent string. Used by producers to inject into SNS attributes or
14
+ * BullMQ/Bull job data. Returns undefined when NR is not loaded or no
15
+ * active transaction exists.
16
+ * - extractParentContext(): parses a traceparent string into an OTel Context
17
+ * with a remote SpanContext. Used by consumers to link their OTel spans
18
+ * back to the producer's trace. Returns ROOT_CONTEXT on malformed input.
19
+ * - getTraceContext(): returns { traceId, spanId, traceparent } for log
20
+ * enrichment. Called lazily at log-write time (not at format creation time)
21
+ * so the trace context is always current.
22
+ *
23
+ * Edge cases covered:
24
+ * - NR not loaded: getTraceMetadata() returns undefined — we default to
25
+ * empty strings, buildTraceparent returns undefined, getTraceContext
26
+ * returns empty strings. No crash.
27
+ * - NR loaded but no active transaction: traceId/spanId are empty strings.
28
+ * buildTraceparent returns undefined. Consumers create a fresh trace.
29
+ * - Malformed traceparent (wrong number of parts, wrong hex lengths):
30
+ * extractParentContext returns ROOT_CONTEXT — consumer creates a fresh
31
+ * trace instead of crashing.
32
+ * - Flags field always set to 01 (sampled) on buildTraceparent because we
33
+ * want all async boundary crossings traced.
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildTraceparent = buildTraceparent;
37
+ exports.extractParentContext = extractParentContext;
38
+ exports.getTraceContext = getTraceContext;
39
+ exports.getActiveContext = getActiveContext;
40
+ const api_1 = require("@opentelemetry/api");
41
+ const nr_1 = require("./nr");
42
+ function buildTraceparent() {
43
+ const nr = (0, nr_1.getNr)();
44
+ const meta = nr.getTraceMetadata?.() ?? { traceId: '', spanId: '' };
45
+ const { traceId, spanId } = meta;
46
+ if (!traceId || !spanId) {
47
+ return undefined;
48
+ }
49
+ return `00-${traceId}-${spanId}-01`;
50
+ }
51
+ function extractParentContext(traceparent) {
52
+ const parts = traceparent.split('-');
53
+ if (parts.length < 4 || !parts[1] || !parts[2]) {
54
+ return api_1.ROOT_CONTEXT;
55
+ }
56
+ const traceId = parts[1];
57
+ const spanId = parts[2];
58
+ if (traceId.length !== 32 || spanId.length !== 16) {
59
+ return api_1.ROOT_CONTEXT;
60
+ }
61
+ const spanContext = {
62
+ traceId,
63
+ spanId,
64
+ traceFlags: parseInt(parts[3] ?? '0', 16),
65
+ isRemote: true,
66
+ };
67
+ return api_1.trace.setSpanContext(api_1.ROOT_CONTEXT, spanContext);
68
+ }
69
+ function getTraceContext() {
70
+ const nr = (0, nr_1.getNr)();
71
+ const meta = nr.getTraceMetadata?.() ?? { traceId: '', spanId: '' };
72
+ const traceId = meta.traceId ?? '';
73
+ const spanId = meta.spanId ?? '';
74
+ const traceparent = traceId && spanId ? `00-${traceId}-${spanId}-01` : '';
75
+ return { traceId, spanId, traceparent };
76
+ }
77
+ function getActiveContext() {
78
+ return api_1.context.active();
79
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * All public interfaces and minimal type stubs for @foam/node-cliengo.
3
+ *
4
+ * Why this file exists:
5
+ * Defines every public type the library exposes (OtelBridge, InitOptions,
6
+ * TraceContext, BridgeMetrics) plus lightweight stubs for external frameworks
7
+ * (Express, Fastify, Winston, Pino, New Relic). These stubs exist so the
8
+ * library never imports framework types at the top level — services that only
9
+ * use Pino don't need Winston installed, and vice versa.
10
+ *
11
+ * Edge cases covered:
12
+ * - NewRelicAgent: all methods are optional (?) because hsm-backend may have
13
+ * NR disabled entirely, and the stub API returns empty values.
14
+ * - WinstonLogger.add is optional because @cliengo/logger's internal Winston
15
+ * instance may not expose it.
16
+ * - SqsMessage.MessageAttributes uses { StringValue?, DataType? } to handle
17
+ * both SQS-native attributes and SNS-envelope-wrapped attributes.
18
+ * - Express/Fastify types use `any` deliberately — the library must work with
19
+ * any Express 4/5 or Fastify 4/5 version without requiring their types as
20
+ * dependencies.
21
+ * - Meter/Histogram/Counter/ObservableGauge are derived from MeterProvider's
22
+ * return types to stay in sync with whatever OTel SDK version is installed.
23
+ */
24
+ import type { Context, Tracer } from '@opentelemetry/api';
25
+ import type { LoggerProvider } from '@opentelemetry/sdk-logs';
26
+ import type { MeterProvider } from '@opentelemetry/sdk-metrics';
27
+ import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
28
+ export interface InitOptions {
29
+ enabled?: boolean;
30
+ newrelic?: NewRelicAgent;
31
+ winston?: WinstonLogger;
32
+ enableTraces?: boolean;
33
+ enableLogs?: boolean;
34
+ enableMetrics?: boolean;
35
+ endpoint?: string;
36
+ forceExport?: boolean;
37
+ autoShutdown?: boolean;
38
+ }
39
+ export interface TraceContext {
40
+ traceId: string;
41
+ spanId: string;
42
+ traceparent: string;
43
+ }
44
+ export interface BridgeMetrics {
45
+ messageProcessingDuration: Histogram;
46
+ messageProcessingErrors: Counter;
47
+ messagesProcessed: Counter;
48
+ customHistogram(name: string, description: string, unit?: string): Histogram;
49
+ customCounter(name: string, description: string, unit?: string): Counter;
50
+ customGauge(name: string, description: string, unit?: string): ObservableGauge;
51
+ }
52
+ export interface OtelBridge {
53
+ tracer: Tracer;
54
+ meter: Meter;
55
+ logger: LoggerProvider;
56
+ traceProvider: BasicTracerProvider;
57
+ meterProvider: MeterProvider;
58
+ loggerProvider: LoggerProvider;
59
+ metrics: BridgeMetrics;
60
+ getTraceContext(): TraceContext;
61
+ createExpressMiddleware(): ExpressRequestHandler;
62
+ createExpressErrorHandler(): ExpressErrorHandler;
63
+ createFastifyPlugin(): FastifyPluginAsync;
64
+ createWinstonFormat(): WinstonFormat;
65
+ createWinstonTransport(): WinstonTransport;
66
+ createPinoMixin(): () => Record<string, string>;
67
+ createPinoDestination(): NodeJS.WritableStream;
68
+ buildTraceparent(): string | undefined;
69
+ injectSnsAttributes(attrs: Record<string, SnsMessageAttributeValue>): Record<string, SnsMessageAttributeValue>;
70
+ injectJobData<T extends Record<string, unknown>>(data: T): T & {
71
+ traceparent?: string;
72
+ };
73
+ wrapSqsConsumer(tracer: Tracer, spanName: string, msg: SqsMessage, fn: () => Promise<void>): Promise<void>;
74
+ wrapJobConsumer(tracer: Tracer, spanName: string, jobData: Record<string, unknown>, fn: () => Promise<void>): Promise<void>;
75
+ extractParentContext(traceparent: string): Context;
76
+ shutdown(): Promise<void>;
77
+ }
78
+ export interface NewRelicAgent {
79
+ getTransaction?(): unknown;
80
+ startBackgroundTransaction?(name: string, group: string, handler: () => unknown): unknown;
81
+ getTraceMetadata?(): {
82
+ traceId?: string;
83
+ spanId?: string;
84
+ };
85
+ acceptDistributedTraceHeaders?(transportType: string, headers: Record<string, string>): void;
86
+ }
87
+ export interface WinstonLogger {
88
+ add?(transport: unknown): void;
89
+ format?: unknown;
90
+ level?: string;
91
+ }
92
+ export interface SnsMessageAttributeValue {
93
+ DataType: string;
94
+ StringValue?: string;
95
+ }
96
+ export interface SqsMessage {
97
+ MessageId?: string;
98
+ Body?: string;
99
+ MessageAttributes?: Record<string, {
100
+ StringValue?: string;
101
+ DataType?: string;
102
+ }>;
103
+ ReceiptHandle?: string;
104
+ }
105
+ export type ExpressRequestHandler = (req: any, res: any, next: (err?: any) => void) => void;
106
+ export type ExpressErrorHandler = (err: any, req: any, res: any, next: (err?: any) => void) => void;
107
+ export type FastifyPluginAsync = (instance: any, opts: any) => Promise<void>;
108
+ export type WinstonFormat = {
109
+ transform: (info: any) => any;
110
+ };
111
+ export type WinstonTransport = object;
112
+ export type Meter = ReturnType<MeterProvider['getMeter']>;
113
+ export type Histogram = ReturnType<Meter['createHistogram']>;
114
+ export type Counter = ReturnType<Meter['createCounter']>;
115
+ export type ObservableGauge = ReturnType<Meter['createObservableGauge']>;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /**
3
+ * All public interfaces and minimal type stubs for @foam/node-cliengo.
4
+ *
5
+ * Why this file exists:
6
+ * Defines every public type the library exposes (OtelBridge, InitOptions,
7
+ * TraceContext, BridgeMetrics) plus lightweight stubs for external frameworks
8
+ * (Express, Fastify, Winston, Pino, New Relic). These stubs exist so the
9
+ * library never imports framework types at the top level — services that only
10
+ * use Pino don't need Winston installed, and vice versa.
11
+ *
12
+ * Edge cases covered:
13
+ * - NewRelicAgent: all methods are optional (?) because hsm-backend may have
14
+ * NR disabled entirely, and the stub API returns empty values.
15
+ * - WinstonLogger.add is optional because @cliengo/logger's internal Winston
16
+ * instance may not expose it.
17
+ * - SqsMessage.MessageAttributes uses { StringValue?, DataType? } to handle
18
+ * both SQS-native attributes and SNS-envelope-wrapped attributes.
19
+ * - Express/Fastify types use `any` deliberately — the library must work with
20
+ * any Express 4/5 or Fastify 4/5 version without requiring their types as
21
+ * dependencies.
22
+ * - Meter/Histogram/Counter/ObservableGauge are derived from MeterProvider's
23
+ * return types to stay in sync with whatever OTel SDK version is installed.
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ /* eslint-enable @typescript-eslint/no-explicit-any */
@@ -0,0 +1 @@
1
+ export declare const FOAM_OTEL_ENDPOINT = "https://otel.api.foam.ai";
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FOAM_OTEL_ENDPOINT = void 0;
4
+ exports.FOAM_OTEL_ENDPOINT = 'https://otel.api.foam.ai';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Wraps a function to ensure it never throws by swallowing errors.
3
+ *
4
+ * @example
5
+ * export const init = safe((options: Options) => {
6
+ * // Implementation that might throw
7
+ * });
8
+ */
9
+ export declare function safe<T extends (...args: any[]) => any>(fn: T): T;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.safe = safe;
4
+ /**
5
+ * Wraps a function to ensure it never throws by swallowing errors.
6
+ *
7
+ * @example
8
+ * export const init = safe((options: Options) => {
9
+ * // Implementation that might throw
10
+ * });
11
+ */
12
+ function safe(fn) {
13
+ return ((...args) => {
14
+ try {
15
+ return fn(...args);
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ });
21
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@foam-ai/node-cliengo",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Unified observability (traces, logs, metrics) for Cliengo Node.js services, bridging New Relic APM with Foam's OTel collector.",
5
+ "main": "dist/node-cliengo/src/index.js",
6
+ "types": "dist/node-cliengo/src/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/node-cliengo/src/index.d.ts",
10
+ "import": "./dist/node-cliengo/src/index.js",
11
+ "require": "./dist/node-cliengo/src/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.json"
19
+ },
20
+ "peerDependencies": {
21
+ "@opentelemetry/api": "^1.9.0",
22
+ "newrelic": ">=12.0.0",
23
+ "winston": "^3.0.0",
24
+ "pino": "^8.0.0 || ^9.0.0"
25
+ },
26
+ "peerDependenciesMeta": {
27
+ "newrelic": {
28
+ "optional": true
29
+ },
30
+ "winston": {
31
+ "optional": true
32
+ },
33
+ "pino": {
34
+ "optional": true
35
+ }
36
+ },
37
+ "dependencies": {
38
+ "@opentelemetry/sdk-trace-base": "^2.0.0",
39
+ "@opentelemetry/sdk-logs": "^0.203.0",
40
+ "@opentelemetry/sdk-metrics": "^2.0.1",
41
+ "@opentelemetry/exporter-trace-otlp-http": "^0.203.0",
42
+ "@opentelemetry/exporter-logs-otlp-http": "^0.203.0",
43
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.201.1",
44
+ "@opentelemetry/resources": "^2.0.1",
45
+ "@opentelemetry/api-logs": "^0.203.0"
46
+ },
47
+ "devDependencies": {
48
+ "typescript": "^6.0.2",
49
+ "@types/node": "^20.11.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=18"
53
+ }
54
+ }