@noony-serverless/core 0.3.4 → 0.4.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 (53) hide show
  1. package/README.md +199 -0
  2. package/build/core/containerPool.d.ts +129 -26
  3. package/build/core/containerPool.js +213 -68
  4. package/build/core/handler.d.ts +2 -2
  5. package/build/core/handler.js +6 -12
  6. package/build/core/index.d.ts +1 -0
  7. package/build/core/index.js +1 -0
  8. package/build/core/logger.d.ts +89 -1
  9. package/build/core/logger.js +136 -5
  10. package/build/core/telemetry/config.d.ts +331 -0
  11. package/build/core/telemetry/config.js +153 -0
  12. package/build/core/telemetry/index.d.ts +22 -0
  13. package/build/core/telemetry/index.js +45 -0
  14. package/build/core/telemetry/provider.d.ts +203 -0
  15. package/build/core/telemetry/provider.js +3 -0
  16. package/build/core/telemetry/providers/console-provider.d.ts +54 -0
  17. package/build/core/telemetry/providers/console-provider.js +124 -0
  18. package/build/core/telemetry/providers/index.d.ts +10 -0
  19. package/build/core/telemetry/providers/index.js +19 -0
  20. package/build/core/telemetry/providers/noop-provider.d.ts +51 -0
  21. package/build/core/telemetry/providers/noop-provider.js +67 -0
  22. package/build/core/telemetry/providers/opentelemetry-provider.d.ts +102 -0
  23. package/build/core/telemetry/providers/opentelemetry-provider.js +342 -0
  24. package/build/middlewares/bodyValidationMiddleware.js +1 -1
  25. package/build/middlewares/dependencyInjectionMiddleware.d.ts +16 -8
  26. package/build/middlewares/dependencyInjectionMiddleware.js +31 -11
  27. package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.d.ts +1 -1
  28. package/build/middlewares/guards/guards/FastAuthGuard.d.ts +5 -5
  29. package/build/middlewares/guards/guards/FastAuthGuard.js +3 -2
  30. package/build/middlewares/guards/guards/PermissionGuardFactory.d.ts +7 -9
  31. package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.d.ts +1 -1
  32. package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.js +1 -1
  33. package/build/middlewares/guards/resolvers/PermissionResolver.d.ts +1 -1
  34. package/build/middlewares/guards/resolvers/PlainPermissionResolver.d.ts +1 -1
  35. package/build/middlewares/guards/resolvers/WildcardPermissionResolver.d.ts +1 -1
  36. package/build/middlewares/guards/services/FastUserContextService.d.ts +11 -32
  37. package/build/middlewares/index.d.ts +1 -0
  38. package/build/middlewares/index.js +1 -0
  39. package/build/middlewares/openTelemetryMiddleware.d.ts +162 -0
  40. package/build/middlewares/openTelemetryMiddleware.js +359 -0
  41. package/build/middlewares/rateLimitingMiddleware.js +16 -5
  42. package/build/utils/container.utils.js +4 -1
  43. package/build/utils/fastify-wrapper.d.ts +74 -0
  44. package/build/utils/fastify-wrapper.js +175 -0
  45. package/build/utils/index.d.ts +4 -0
  46. package/build/utils/index.js +23 -1
  47. package/build/utils/otel.helper.d.ts +122 -0
  48. package/build/utils/otel.helper.js +258 -0
  49. package/build/utils/pubsub-trace.utils.d.ts +102 -0
  50. package/build/utils/pubsub-trace.utils.js +155 -0
  51. package/build/utils/wrapper-utils.d.ts +177 -0
  52. package/build/utils/wrapper-utils.js +236 -0
  53. package/package.json +61 -2
@@ -0,0 +1,74 @@
1
+ import type { FastifyRequest, FastifyReply } from 'fastify';
2
+ import { Handler } from '../core/handler';
3
+ /**
4
+ * Create a Fastify route handler wrapper for a Noony handler
5
+ *
6
+ * Wraps a Noony handler into a Fastify route handler for use with Fastify server.
7
+ * This pattern enables running Noony handlers with Fastify's high-performance HTTP framework.
8
+ *
9
+ * @param noonyHandler - The Noony handler to wrap (contains middleware chain and controller)
10
+ * @param functionName - Name for error logging purposes
11
+ * @param initializeDependencies - Async function that initializes dependencies (database, services, etc.)
12
+ * Uses singleton pattern to prevent re-initialization across requests
13
+ * @returns Fastify route handler: `(req: FastifyRequest, reply: FastifyReply) => Promise<void>`
14
+ *
15
+ * @remarks
16
+ * This wrapper ensures:
17
+ * - Dependencies are initialized before handler execution (singleton pattern for efficiency)
18
+ * - Noony handlers work seamlessly with Fastify routing
19
+ * - Errors are caught and returned as proper HTTP responses
20
+ * - Response is not sent twice (`reply.sent` check)
21
+ * - `RESPONSE_SENT` errors are ignored (response already sent by middleware)
22
+ * - Real errors return 500 with generic message for security
23
+ *
24
+ * @example
25
+ * Creating Fastify app with multiple routes:
26
+ * ```typescript
27
+ * import Fastify from 'fastify';
28
+ * import { createFastifyHandler } from '@noony-serverless/core';
29
+ * import { loginHandler, getConfigHandler } from './handlers';
30
+ *
31
+ * // Initialize dependencies once per app startup
32
+ * let initialized = false;
33
+ * async function initializeDependencies(): Promise<void> {
34
+ * if (initialized) return;
35
+ * const db = await databaseService.connect();
36
+ * await initializeServices(db);
37
+ * initialized = true;
38
+ * }
39
+ *
40
+ * const server = Fastify({ logger: true });
41
+ *
42
+ * // Helper shorthand
43
+ * const adapt = (handler, name) => createFastifyHandler(handler, name, initializeDependencies);
44
+ *
45
+ * // Auth routes
46
+ * server.post('/api/auth/login', adapt(loginHandler, 'login'));
47
+ *
48
+ * // Config routes
49
+ * server.get('/api/config', adapt(getConfigHandler, 'getConfig'));
50
+ *
51
+ * // Start server
52
+ * server.listen({ port: 3000 }, (err) => {
53
+ * if (err) throw err;
54
+ * console.log('Server running on port 3000');
55
+ * });
56
+ * ```
57
+ *
58
+ * @example
59
+ * Fastify routing with path parameters:
60
+ * ```typescript
61
+ * const server = Fastify();
62
+ *
63
+ * // Routes with path parameters work seamlessly
64
+ * server.get('/api/users/:userId', adapt(getUserHandler, 'getUser'));
65
+ * server.patch('/api/config/sections/:sectionId', adapt(updateSectionHandler, 'updateSection'));
66
+ *
67
+ * // Path parameters available in Noony handler via context.req.params
68
+ * ```
69
+ *
70
+ * @see {@link createHttpFunction} for Cloud Functions Framework integration (production deployment)
71
+ * @see {@link wrapNoonyHandler} for Express integration
72
+ */
73
+ export declare function createFastifyHandler(noonyHandler: Handler<unknown>, functionName: string, initializeDependencies: () => Promise<void>): (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
74
+ //# sourceMappingURL=fastify-wrapper.d.ts.map
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createFastifyHandler = createFastifyHandler;
4
+ const logger_1 = require("../core/logger");
5
+ /**
6
+ * Adapt Fastify Request to GenericRequest for Noony handlers
7
+ *
8
+ * @internal
9
+ */
10
+ function adaptFastifyRequest(req) {
11
+ return {
12
+ method: req.method,
13
+ url: req.url,
14
+ path: req.routeOptions?.url || req.url,
15
+ headers: req.headers,
16
+ query: (req.query || {}),
17
+ params: (req.params || {}),
18
+ body: req.body,
19
+ // Fastify already parses the body, so set parsedBody for BodyValidationMiddleware
20
+ parsedBody: req.body,
21
+ ip: req.ip,
22
+ userAgent: req.headers['user-agent'],
23
+ };
24
+ }
25
+ /**
26
+ * Adapt Fastify Reply to GenericResponse for Noony handlers
27
+ *
28
+ * @internal
29
+ */
30
+ function adaptFastifyResponse(reply) {
31
+ let statusCode = 200;
32
+ let headersSent = false;
33
+ const response = {
34
+ status(code) {
35
+ statusCode = code;
36
+ reply.code(code);
37
+ return response;
38
+ },
39
+ json(data) {
40
+ headersSent = true;
41
+ reply.send(data);
42
+ return response;
43
+ },
44
+ send(data) {
45
+ headersSent = true;
46
+ reply.send(data);
47
+ return response;
48
+ },
49
+ header(name, value) {
50
+ reply.header(name, value);
51
+ return response;
52
+ },
53
+ headers(headers) {
54
+ Object.entries(headers).forEach(([key, value]) => {
55
+ reply.header(key, value);
56
+ });
57
+ return response;
58
+ },
59
+ end() {
60
+ headersSent = true;
61
+ reply.send();
62
+ },
63
+ get statusCode() {
64
+ return statusCode;
65
+ },
66
+ get headersSent() {
67
+ return headersSent || reply.sent;
68
+ },
69
+ };
70
+ return response;
71
+ }
72
+ /**
73
+ * Create a Fastify route handler wrapper for a Noony handler
74
+ *
75
+ * Wraps a Noony handler into a Fastify route handler for use with Fastify server.
76
+ * This pattern enables running Noony handlers with Fastify's high-performance HTTP framework.
77
+ *
78
+ * @param noonyHandler - The Noony handler to wrap (contains middleware chain and controller)
79
+ * @param functionName - Name for error logging purposes
80
+ * @param initializeDependencies - Async function that initializes dependencies (database, services, etc.)
81
+ * Uses singleton pattern to prevent re-initialization across requests
82
+ * @returns Fastify route handler: `(req: FastifyRequest, reply: FastifyReply) => Promise<void>`
83
+ *
84
+ * @remarks
85
+ * This wrapper ensures:
86
+ * - Dependencies are initialized before handler execution (singleton pattern for efficiency)
87
+ * - Noony handlers work seamlessly with Fastify routing
88
+ * - Errors are caught and returned as proper HTTP responses
89
+ * - Response is not sent twice (`reply.sent` check)
90
+ * - `RESPONSE_SENT` errors are ignored (response already sent by middleware)
91
+ * - Real errors return 500 with generic message for security
92
+ *
93
+ * @example
94
+ * Creating Fastify app with multiple routes:
95
+ * ```typescript
96
+ * import Fastify from 'fastify';
97
+ * import { createFastifyHandler } from '@noony-serverless/core';
98
+ * import { loginHandler, getConfigHandler } from './handlers';
99
+ *
100
+ * // Initialize dependencies once per app startup
101
+ * let initialized = false;
102
+ * async function initializeDependencies(): Promise<void> {
103
+ * if (initialized) return;
104
+ * const db = await databaseService.connect();
105
+ * await initializeServices(db);
106
+ * initialized = true;
107
+ * }
108
+ *
109
+ * const server = Fastify({ logger: true });
110
+ *
111
+ * // Helper shorthand
112
+ * const adapt = (handler, name) => createFastifyHandler(handler, name, initializeDependencies);
113
+ *
114
+ * // Auth routes
115
+ * server.post('/api/auth/login', adapt(loginHandler, 'login'));
116
+ *
117
+ * // Config routes
118
+ * server.get('/api/config', adapt(getConfigHandler, 'getConfig'));
119
+ *
120
+ * // Start server
121
+ * server.listen({ port: 3000 }, (err) => {
122
+ * if (err) throw err;
123
+ * console.log('Server running on port 3000');
124
+ * });
125
+ * ```
126
+ *
127
+ * @example
128
+ * Fastify routing with path parameters:
129
+ * ```typescript
130
+ * const server = Fastify();
131
+ *
132
+ * // Routes with path parameters work seamlessly
133
+ * server.get('/api/users/:userId', adapt(getUserHandler, 'getUser'));
134
+ * server.patch('/api/config/sections/:sectionId', adapt(updateSectionHandler, 'updateSection'));
135
+ *
136
+ * // Path parameters available in Noony handler via context.req.params
137
+ * ```
138
+ *
139
+ * @see {@link createHttpFunction} for Cloud Functions Framework integration (production deployment)
140
+ * @see {@link wrapNoonyHandler} for Express integration
141
+ */
142
+ function createFastifyHandler(noonyHandler, functionName, initializeDependencies) {
143
+ return async (req, reply) => {
144
+ try {
145
+ // Ensure dependencies are initialized
146
+ await initializeDependencies();
147
+ // Adapt Fastify req/reply to GenericRequest/GenericResponse
148
+ const genericReq = adaptFastifyRequest(req);
149
+ const genericRes = adaptFastifyResponse(reply);
150
+ // Execute Noony handler with adapted request/response
151
+ await noonyHandler.executeGeneric(genericReq, genericRes);
152
+ }
153
+ catch (error) {
154
+ // Ignore RESPONSE_SENT markers (response already sent by middleware)
155
+ if (error instanceof Error && error.message === 'RESPONSE_SENT') {
156
+ return;
157
+ }
158
+ logger_1.logger.error(`${functionName} handler error`, {
159
+ error: error instanceof Error ? error.message : 'Unknown error',
160
+ stack: error instanceof Error ? error.stack : undefined,
161
+ });
162
+ // Graceful error handling - only send if response not already sent
163
+ if (!reply.sent) {
164
+ reply.code(500).send({
165
+ success: false,
166
+ error: {
167
+ code: 'INTERNAL_SERVER_ERROR',
168
+ message: 'An unexpected error occurred',
169
+ },
170
+ });
171
+ }
172
+ }
173
+ };
174
+ }
175
+ //# sourceMappingURL=fastify-wrapper.js.map
@@ -3,4 +3,8 @@
3
3
  */
4
4
  export { getService } from './container.utils';
5
5
  export { asString, asStringArray, asNumber, asBoolean, } from './query-param.utils';
6
+ export { isPubSubMessage, extractTraceContext, injectTraceContext, createParentContext, type PubSubMessage, type TraceContext, } from './pubsub-trace.utils';
7
+ export { createOTELMixin, getOTELContext, getOTELContextFromSpan, getOTELContextFromContext, formatTraceIdForCloudLogging, createCloudLoggingEntry, isOTELActive, isOTELInstalled, type OTELLogContext, } from './otel.helper';
8
+ export { createHttpFunction, wrapNoonyHandler } from './wrapper-utils';
9
+ export { createFastifyHandler } from './fastify-wrapper';
6
10
  //# sourceMappingURL=index.d.ts.map
@@ -3,7 +3,7 @@
3
3
  * Utility functions for Noony Core
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.asBoolean = exports.asNumber = exports.asStringArray = exports.asString = exports.getService = void 0;
6
+ exports.createFastifyHandler = exports.wrapNoonyHandler = exports.createHttpFunction = exports.isOTELInstalled = exports.isOTELActive = exports.createCloudLoggingEntry = exports.formatTraceIdForCloudLogging = exports.getOTELContextFromContext = exports.getOTELContextFromSpan = exports.getOTELContext = exports.createOTELMixin = exports.createParentContext = exports.injectTraceContext = exports.extractTraceContext = exports.isPubSubMessage = exports.asBoolean = exports.asNumber = exports.asStringArray = exports.asString = exports.getService = void 0;
7
7
  // Container utilities
8
8
  var container_utils_1 = require("./container.utils");
9
9
  Object.defineProperty(exports, "getService", { enumerable: true, get: function () { return container_utils_1.getService; } });
@@ -13,4 +13,26 @@ Object.defineProperty(exports, "asString", { enumerable: true, get: function ()
13
13
  Object.defineProperty(exports, "asStringArray", { enumerable: true, get: function () { return query_param_utils_1.asStringArray; } });
14
14
  Object.defineProperty(exports, "asNumber", { enumerable: true, get: function () { return query_param_utils_1.asNumber; } });
15
15
  Object.defineProperty(exports, "asBoolean", { enumerable: true, get: function () { return query_param_utils_1.asBoolean; } });
16
+ // Pub/Sub trace propagation utilities
17
+ var pubsub_trace_utils_1 = require("./pubsub-trace.utils");
18
+ Object.defineProperty(exports, "isPubSubMessage", { enumerable: true, get: function () { return pubsub_trace_utils_1.isPubSubMessage; } });
19
+ Object.defineProperty(exports, "extractTraceContext", { enumerable: true, get: function () { return pubsub_trace_utils_1.extractTraceContext; } });
20
+ Object.defineProperty(exports, "injectTraceContext", { enumerable: true, get: function () { return pubsub_trace_utils_1.injectTraceContext; } });
21
+ Object.defineProperty(exports, "createParentContext", { enumerable: true, get: function () { return pubsub_trace_utils_1.createParentContext; } });
22
+ // OpenTelemetry logger integration utilities
23
+ var otel_helper_1 = require("./otel.helper");
24
+ Object.defineProperty(exports, "createOTELMixin", { enumerable: true, get: function () { return otel_helper_1.createOTELMixin; } });
25
+ Object.defineProperty(exports, "getOTELContext", { enumerable: true, get: function () { return otel_helper_1.getOTELContext; } });
26
+ Object.defineProperty(exports, "getOTELContextFromSpan", { enumerable: true, get: function () { return otel_helper_1.getOTELContextFromSpan; } });
27
+ Object.defineProperty(exports, "getOTELContextFromContext", { enumerable: true, get: function () { return otel_helper_1.getOTELContextFromContext; } });
28
+ Object.defineProperty(exports, "formatTraceIdForCloudLogging", { enumerable: true, get: function () { return otel_helper_1.formatTraceIdForCloudLogging; } });
29
+ Object.defineProperty(exports, "createCloudLoggingEntry", { enumerable: true, get: function () { return otel_helper_1.createCloudLoggingEntry; } });
30
+ Object.defineProperty(exports, "isOTELActive", { enumerable: true, get: function () { return otel_helper_1.isOTELActive; } });
31
+ Object.defineProperty(exports, "isOTELInstalled", { enumerable: true, get: function () { return otel_helper_1.isOTELInstalled; } });
32
+ // Wrapper utilities for GCP Functions, Express, and Fastify
33
+ var wrapper_utils_1 = require("./wrapper-utils");
34
+ Object.defineProperty(exports, "createHttpFunction", { enumerable: true, get: function () { return wrapper_utils_1.createHttpFunction; } });
35
+ Object.defineProperty(exports, "wrapNoonyHandler", { enumerable: true, get: function () { return wrapper_utils_1.wrapNoonyHandler; } });
36
+ var fastify_wrapper_1 = require("./fastify-wrapper");
37
+ Object.defineProperty(exports, "createFastifyHandler", { enumerable: true, get: function () { return fastify_wrapper_1.createFastifyHandler; } });
16
38
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,122 @@
1
+ /**
2
+ * OpenTelemetry Helper Utilities
3
+ *
4
+ * Provides helper functions for integrating OpenTelemetry with logging systems.
5
+ * These utilities enable automatic trace/span ID injection into log entries for
6
+ * correlation with distributed traces in Cloud Logging and other observability platforms.
7
+ *
8
+ * @module utils/otel.helper
9
+ */
10
+ import type { Context as OtelContext, Span } from '@opentelemetry/api';
11
+ /**
12
+ * OTEL context object for logger integration
13
+ */
14
+ export interface OTELLogContext {
15
+ traceId?: string;
16
+ spanId?: string;
17
+ traceFlags?: number;
18
+ }
19
+ /**
20
+ * Create Pino mixin for automatic trace/span ID injection
21
+ *
22
+ * This function creates a Pino mixin that automatically adds OpenTelemetry
23
+ * trace and span IDs to every log entry. This enables log-trace correlation
24
+ * in Cloud Logging and other observability platforms.
25
+ *
26
+ * Usage with Pino:
27
+ * ```typescript
28
+ * import pino from 'pino';
29
+ * import { createOTELMixin } from '@noony-serverless/core';
30
+ *
31
+ * const logger = pino({
32
+ * mixin: createOTELMixin,
33
+ * // ... other config
34
+ * });
35
+ *
36
+ * logger.info('User created'); // Automatically includes traceId, spanId, traceFlags
37
+ * ```
38
+ *
39
+ * Log output example:
40
+ * ```json
41
+ * {
42
+ * "level": 30,
43
+ * "time": 1640000000000,
44
+ * "msg": "User created",
45
+ * "traceId": "13ea7e3c2d3b4547baaa399062df1f2d",
46
+ * "spanId": "1234567890123456",
47
+ * "traceFlags": 1
48
+ * }
49
+ * ```
50
+ *
51
+ * @returns Mixin object with trace context or empty object if no active span
52
+ */
53
+ export declare const createOTELMixin: () => OTELLogContext;
54
+ /**
55
+ * Extract OTEL context from active span
56
+ *
57
+ * Similar to createOTELMixin but returns undefined if no span is active,
58
+ * making it easier to conditionally add trace context.
59
+ *
60
+ * @returns OTEL context or undefined if no active span
61
+ */
62
+ export declare const getOTELContext: () => OTELLogContext | undefined;
63
+ /**
64
+ * Extract OTEL context from a specific span
65
+ *
66
+ * Useful when you have a reference to a span and want to extract its context
67
+ * for logging or propagation purposes.
68
+ *
69
+ * @param span - The OpenTelemetry span to extract context from
70
+ * @returns OTEL context from the span
71
+ */
72
+ export declare const getOTELContextFromSpan: (span: Span) => OTELLogContext;
73
+ /**
74
+ * Extract OTEL context from an OTEL Context object
75
+ *
76
+ * Useful when working with OTEL Context propagation (e.g., in Pub/Sub messages)
77
+ * and you need to extract the span context for logging.
78
+ *
79
+ * @param context - The OpenTelemetry context to extract from
80
+ * @returns OTEL context from the context or undefined if no span
81
+ */
82
+ export declare const getOTELContextFromContext: (context: OtelContext) => OTELLogContext | undefined;
83
+ /**
84
+ * Format trace ID for Cloud Logging
85
+ *
86
+ * Cloud Logging expects trace IDs in a specific format:
87
+ * projects/[PROJECT_ID]/traces/[TRACE_ID]
88
+ *
89
+ * This function formats a raw trace ID into the Cloud Logging format.
90
+ *
91
+ * @param traceId - Raw trace ID (32-character hex string)
92
+ * @param projectId - GCP project ID (optional, defaults to GOOGLE_CLOUD_PROJECT env var)
93
+ * @returns Formatted trace ID for Cloud Logging or undefined if inputs invalid
94
+ */
95
+ export declare const formatTraceIdForCloudLogging: (traceId?: string, projectId?: string) => string | undefined;
96
+ /**
97
+ * Create Cloud Logging compatible log entry
98
+ *
99
+ * Combines OTEL context with log metadata to create a Cloud Logging compatible
100
+ * log entry with trace correlation.
101
+ *
102
+ * @param message - Log message
103
+ * @param metadata - Additional log metadata
104
+ * @param projectId - GCP project ID (optional)
105
+ * @returns Cloud Logging compatible log entry
106
+ */
107
+ export declare const createCloudLoggingEntry: (message: string, metadata?: Record<string, any>, projectId?: string) => Record<string, any>;
108
+ /**
109
+ * Check if OpenTelemetry is available and active
110
+ *
111
+ * Useful for conditional OTEL feature usage in libraries and applications.
112
+ *
113
+ * @returns true if OTEL is available and there's an active span
114
+ */
115
+ export declare const isOTELActive: () => boolean;
116
+ /**
117
+ * Check if OpenTelemetry SDK is installed
118
+ *
119
+ * @returns true if @opentelemetry/api is installed
120
+ */
121
+ export declare const isOTELInstalled: () => boolean;
122
+ //# sourceMappingURL=otel.helper.d.ts.map
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ /**
3
+ * OpenTelemetry Helper Utilities
4
+ *
5
+ * Provides helper functions for integrating OpenTelemetry with logging systems.
6
+ * These utilities enable automatic trace/span ID injection into log entries for
7
+ * correlation with distributed traces in Cloud Logging and other observability platforms.
8
+ *
9
+ * @module utils/otel.helper
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.isOTELInstalled = exports.isOTELActive = exports.createCloudLoggingEntry = exports.formatTraceIdForCloudLogging = exports.getOTELContextFromContext = exports.getOTELContextFromSpan = exports.getOTELContext = exports.createOTELMixin = void 0;
13
+ // Conditional OpenTelemetry import (optional dependency)
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ let trace;
16
+ try {
17
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
18
+ const otelApi = require('@opentelemetry/api');
19
+ trace = otelApi.trace;
20
+ }
21
+ catch {
22
+ // OpenTelemetry not installed - helpers will return empty objects
23
+ trace = null;
24
+ }
25
+ /**
26
+ * Create Pino mixin for automatic trace/span ID injection
27
+ *
28
+ * This function creates a Pino mixin that automatically adds OpenTelemetry
29
+ * trace and span IDs to every log entry. This enables log-trace correlation
30
+ * in Cloud Logging and other observability platforms.
31
+ *
32
+ * Usage with Pino:
33
+ * ```typescript
34
+ * import pino from 'pino';
35
+ * import { createOTELMixin } from '@noony-serverless/core';
36
+ *
37
+ * const logger = pino({
38
+ * mixin: createOTELMixin,
39
+ * // ... other config
40
+ * });
41
+ *
42
+ * logger.info('User created'); // Automatically includes traceId, spanId, traceFlags
43
+ * ```
44
+ *
45
+ * Log output example:
46
+ * ```json
47
+ * {
48
+ * "level": 30,
49
+ * "time": 1640000000000,
50
+ * "msg": "User created",
51
+ * "traceId": "13ea7e3c2d3b4547baaa399062df1f2d",
52
+ * "spanId": "1234567890123456",
53
+ * "traceFlags": 1
54
+ * }
55
+ * ```
56
+ *
57
+ * @returns Mixin object with trace context or empty object if no active span
58
+ */
59
+ const createOTELMixin = () => {
60
+ if (!trace) {
61
+ return {};
62
+ }
63
+ try {
64
+ const span = trace.getActiveSpan();
65
+ if (!span) {
66
+ return {};
67
+ }
68
+ const spanContext = span.spanContext();
69
+ return {
70
+ traceId: spanContext.traceId,
71
+ spanId: spanContext.spanId,
72
+ traceFlags: spanContext.traceFlags,
73
+ };
74
+ }
75
+ catch (error) {
76
+ // Gracefully handle any errors in trace extraction
77
+ return {};
78
+ }
79
+ };
80
+ exports.createOTELMixin = createOTELMixin;
81
+ /**
82
+ * Extract OTEL context from active span
83
+ *
84
+ * Similar to createOTELMixin but returns undefined if no span is active,
85
+ * making it easier to conditionally add trace context.
86
+ *
87
+ * @returns OTEL context or undefined if no active span
88
+ */
89
+ const getOTELContext = () => {
90
+ if (!trace) {
91
+ return undefined;
92
+ }
93
+ try {
94
+ const span = trace.getActiveSpan();
95
+ if (!span) {
96
+ return undefined;
97
+ }
98
+ const spanContext = span.spanContext();
99
+ return {
100
+ traceId: spanContext.traceId,
101
+ spanId: spanContext.spanId,
102
+ traceFlags: spanContext.traceFlags,
103
+ };
104
+ }
105
+ catch (error) {
106
+ return undefined;
107
+ }
108
+ };
109
+ exports.getOTELContext = getOTELContext;
110
+ /**
111
+ * Extract OTEL context from a specific span
112
+ *
113
+ * Useful when you have a reference to a span and want to extract its context
114
+ * for logging or propagation purposes.
115
+ *
116
+ * @param span - The OpenTelemetry span to extract context from
117
+ * @returns OTEL context from the span
118
+ */
119
+ const getOTELContextFromSpan = (span) => {
120
+ try {
121
+ const spanContext = span.spanContext();
122
+ return {
123
+ traceId: spanContext.traceId,
124
+ spanId: spanContext.spanId,
125
+ traceFlags: spanContext.traceFlags,
126
+ };
127
+ }
128
+ catch (error) {
129
+ return {};
130
+ }
131
+ };
132
+ exports.getOTELContextFromSpan = getOTELContextFromSpan;
133
+ /**
134
+ * Extract OTEL context from an OTEL Context object
135
+ *
136
+ * Useful when working with OTEL Context propagation (e.g., in Pub/Sub messages)
137
+ * and you need to extract the span context for logging.
138
+ *
139
+ * @param context - The OpenTelemetry context to extract from
140
+ * @returns OTEL context from the context or undefined if no span
141
+ */
142
+ const getOTELContextFromContext = (context) => {
143
+ if (!trace) {
144
+ return undefined;
145
+ }
146
+ try {
147
+ const span = trace.getSpan(context);
148
+ if (!span) {
149
+ return undefined;
150
+ }
151
+ const spanContext = span.spanContext();
152
+ return {
153
+ traceId: spanContext.traceId,
154
+ spanId: spanContext.spanId,
155
+ traceFlags: spanContext.traceFlags,
156
+ };
157
+ }
158
+ catch (error) {
159
+ return undefined;
160
+ }
161
+ };
162
+ exports.getOTELContextFromContext = getOTELContextFromContext;
163
+ /**
164
+ * Format trace ID for Cloud Logging
165
+ *
166
+ * Cloud Logging expects trace IDs in a specific format:
167
+ * projects/[PROJECT_ID]/traces/[TRACE_ID]
168
+ *
169
+ * This function formats a raw trace ID into the Cloud Logging format.
170
+ *
171
+ * @param traceId - Raw trace ID (32-character hex string)
172
+ * @param projectId - GCP project ID (optional, defaults to GOOGLE_CLOUD_PROJECT env var)
173
+ * @returns Formatted trace ID for Cloud Logging or undefined if inputs invalid
174
+ */
175
+ const formatTraceIdForCloudLogging = (traceId, projectId) => {
176
+ if (!traceId) {
177
+ return undefined;
178
+ }
179
+ const project = projectId || process.env.GOOGLE_CLOUD_PROJECT || process.env.GCP_PROJECT;
180
+ if (!project) {
181
+ return undefined;
182
+ }
183
+ return `projects/${project}/traces/${traceId}`;
184
+ };
185
+ exports.formatTraceIdForCloudLogging = formatTraceIdForCloudLogging;
186
+ /**
187
+ * Create Cloud Logging compatible log entry
188
+ *
189
+ * Combines OTEL context with log metadata to create a Cloud Logging compatible
190
+ * log entry with trace correlation.
191
+ *
192
+ * @param message - Log message
193
+ * @param metadata - Additional log metadata
194
+ * @param projectId - GCP project ID (optional)
195
+ * @returns Cloud Logging compatible log entry
196
+ */
197
+ const createCloudLoggingEntry = (message,
198
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
+ metadata = {}, projectId
200
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
201
+ ) => {
202
+ const otelContext = (0, exports.getOTELContext)();
203
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
204
+ const entry = {
205
+ message,
206
+ ...metadata,
207
+ };
208
+ if (otelContext?.traceId) {
209
+ entry.traceId = otelContext.traceId;
210
+ entry.spanId = otelContext.spanId;
211
+ // Add Cloud Logging trace reference
212
+ const formattedTrace = (0, exports.formatTraceIdForCloudLogging)(otelContext.traceId, projectId);
213
+ if (formattedTrace) {
214
+ entry['logging.googleapis.com/trace'] = formattedTrace;
215
+ }
216
+ // Add span ID for Cloud Logging correlation
217
+ if (otelContext.spanId) {
218
+ entry['logging.googleapis.com/spanId'] = otelContext.spanId;
219
+ }
220
+ // Add trace sampled flag
221
+ if (otelContext.traceFlags !== undefined) {
222
+ entry['logging.googleapis.com/trace_sampled'] =
223
+ (otelContext.traceFlags & 1) === 1;
224
+ }
225
+ }
226
+ return entry;
227
+ };
228
+ exports.createCloudLoggingEntry = createCloudLoggingEntry;
229
+ /**
230
+ * Check if OpenTelemetry is available and active
231
+ *
232
+ * Useful for conditional OTEL feature usage in libraries and applications.
233
+ *
234
+ * @returns true if OTEL is available and there's an active span
235
+ */
236
+ const isOTELActive = () => {
237
+ if (!trace) {
238
+ return false;
239
+ }
240
+ try {
241
+ const span = trace.getActiveSpan();
242
+ return !!span;
243
+ }
244
+ catch {
245
+ return false;
246
+ }
247
+ };
248
+ exports.isOTELActive = isOTELActive;
249
+ /**
250
+ * Check if OpenTelemetry SDK is installed
251
+ *
252
+ * @returns true if @opentelemetry/api is installed
253
+ */
254
+ const isOTELInstalled = () => {
255
+ return !!trace;
256
+ };
257
+ exports.isOTELInstalled = isOTELInstalled;
258
+ //# sourceMappingURL=otel.helper.js.map